Browse Source

SONAR-17677 Improve compare action on QP page

tags/9.8.0.63668
stanislavh 1 year ago
parent
commit
3ca3b4477f
26 changed files with 298 additions and 1433 deletions
  1. 69
    5
      server/sonar-web/src/main/js/api/mocks/QualityProfilesServiceMock.ts
  2. 52
    6
      server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfilesApp-it.tsx
  3. 20
    4
      server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResultActivation.tsx
  4. 0
    86
      server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonForm-test.tsx
  5. 0
    45
      server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResultActivation-test.tsx
  6. 0
    104
      server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResults-test.tsx
  7. 0
    21
      server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/__snapshots__/ComparisonForm-test.tsx.snap
  8. 0
    60
      server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/__snapshots__/ComparisonResultActivation-test.tsx.snap
  9. 16
    9
      server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx
  10. 7
    2
      server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx
  11. 1
    1
      server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.tsx
  12. 8
    2
      server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx
  13. 8
    0
      server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ProfileActions-test.tsx.snap
  14. 2
    17
      server/sonar-web/src/main/js/apps/quality-profiles/constants.ts
  15. 67
    60
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.tsx
  16. 0
    214
      server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileHeader-test.tsx.snap
  17. 1
    0
      server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx
  18. 2
    1
      server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.tsx
  19. 7
    2
      server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx
  20. 0
    45
      server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/ProfilesList-test.tsx
  21. 0
    46
      server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/ProfilesListRow-test.tsx
  22. 0
    288
      server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesList-test.tsx.snap
  23. 0
    411
      server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesListRow-test.tsx.snap
  24. 6
    3
      server/sonar-web/src/main/js/apps/quality-profiles/utils.ts
  25. 31
    0
      server/sonar-web/src/main/js/helpers/testMocks.ts
  26. 1
    1
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 69
- 5
server/sonar-web/src/main/js/api/mocks/QualityProfilesServiceMock.ts View File

@@ -19,11 +19,14 @@
*/
import { cloneDeep } from 'lodash';
import { RequestData } from '../../helpers/request';
import { mockQualityProfile } from '../../helpers/testMocks';
import { mockCompareResult, mockQualityProfile, mockRuleDetails } from '../../helpers/testMocks';
import { SearchRulesResponse } from '../../types/coding-rules';
import { Dict, Paging, ProfileInheritanceDetails } from '../../types/types';
import { Dict, Paging, ProfileInheritanceDetails, RuleDetails } from '../../types/types';
import {
activateRule,
changeProfileParent,
compareProfiles,
CompareResponse,
copyProfile,
createQualityProfile,
getImporters,
@@ -35,18 +38,20 @@ import {
SearchQualityProfilesParameters,
SearchQualityProfilesResponse,
} from '../quality-profiles';
import { searchRules } from '../rules';
import { getRuleDetails, searchRules } from '../rules';

export default class QualityProfilesServiceMock {
isAdmin = false;
listQualityProfile: Profile[] = [];

languageMapping: Dict<Partial<Profile>> = {
c: { language: 'c', languageName: 'C' },
};

comparisonResult: CompareResponse = mockCompareResult();

constructor() {
this.resetQualityProfile();
this.resetComparisonResult();

(searchQualityProfiles as jest.Mock).mockImplementation(this.handleSearchQualityProfiles);
(createQualityProfile as jest.Mock).mockImplementation(this.handleCreateQualityProfile);
@@ -56,6 +61,9 @@ export default class QualityProfilesServiceMock {
(copyProfile as jest.Mock).mockImplementation(this.handleCopyProfile);
(getImporters as jest.Mock).mockImplementation(this.handleGetImporters);
(searchRules as jest.Mock).mockImplementation(this.handleSearchRules);
(compareProfiles as jest.Mock).mockImplementation(this.handleCompareQualityProfiles);
(activateRule as jest.Mock).mockImplementation(this.handleActivateRule);
(getRuleDetails as jest.Mock).mockImplementation(this.handleGetRuleDetails);
}

resetQualityProfile() {
@@ -70,12 +78,27 @@ export default class QualityProfilesServiceMock {
mockQualityProfile({
key: 'java-qp',
language: 'java',
languageName: 'Java',
name: 'java quality profile',
activeDeprecatedRuleCount: 0,
}),
mockQualityProfile({
key: 'java-qp-1',
language: 'java',
languageName: 'Java',
name: 'java quality profile #2',
activeDeprecatedRuleCount: 1,
actions: {
edit: true,
},
}),
];
}

resetComparisonResult() {
this.comparisonResult = mockCompareResult();
}

handleGetImporters = () => {
return this.reply([]);
};
@@ -201,7 +224,7 @@ export default class QualityProfilesServiceMock {
profiles = profiles.filter((p) => p.language === language);
}
if (this.isAdmin) {
profiles = profiles.map((p) => ({ ...p, actions: { copy: true } }));
profiles = profiles.map((p) => ({ ...p, actions: { ...p.actions, copy: true } }));
}

return this.reply({
@@ -210,6 +233,46 @@ export default class QualityProfilesServiceMock {
});
};

handleActivateRule = (data: {
key: string;
params?: Dict<string>;
reset?: boolean;
rule: string;
severity?: string;
}): Promise<undefined> => {
const profile = this.listQualityProfile.find((profile) => profile.key === data.key) as Profile;
const keyFilter = profile.name === this.comparisonResult.left.name ? 'inRight' : 'inLeft';

this.comparisonResult[keyFilter] = this.comparisonResult[keyFilter].filter(
({ key }) => key !== data.rule
);

return this.reply(undefined);
};

handleCompareQualityProfiles = (leftKey: string, rightKey: string): Promise<CompareResponse> => {
const comparedProfiles = this.listQualityProfile.reduce((profiles, profile) => {
if (profile.key === leftKey || profile.key === rightKey) {
profiles.push(profile);
}
return profiles;
}, [] as Profile[]);
const [leftName, rightName] = comparedProfiles.map((profile) => profile.name);

this.comparisonResult.left = { name: leftName };
this.comparisonResult.right = { name: rightName };

return this.reply(this.comparisonResult);
};

handleGetRuleDetails = (params: { key: string }): Promise<{ rule: RuleDetails }> => {
return this.reply({
rule: mockRuleDetails({
key: params.key,
}),
});
};

setAdmin() {
this.isAdmin = true;
}
@@ -217,6 +280,7 @@ export default class QualityProfilesServiceMock {
reset() {
this.isAdmin = false;
this.resetQualityProfile();
this.resetComparisonResult();
}

reply<T>(response: T): Promise<T> {

+ 52
- 6
server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfilesApp-it.tsx View File

@@ -39,10 +39,10 @@ const ui = {
cQualityProfileName: 'c quality profile',
newCQualityProfileName: 'New c quality profile',
newCQualityProfileNameFromCreateButton: 'New c quality profile from create',
actionOnCQualityProfile: byRole('button', {
name: 'quality_profiles.actions.c quality profile.C',
}),
profileActions: (name: string, language: string) =>
byRole('button', {
name: `quality_profiles.actions.${name}.${language}`,
}),
extendButton: byRole('button', {
name: 'extend',
}),
@@ -50,6 +50,9 @@ const ui = {
name: 'copy',
}),
createButton: byRole('button', { name: 'create' }),
compareButton: byRole('link', { name: 'compare' }),
compareDropdown: byRole('textbox', { name: 'quality_profiles.compare_with' }),
changelogLink: byRole('link', { name: 'changelog' }),
popup: byRole('dialog'),
copyRadio: byRole('radio', {
name: 'quality_profiles.creation_from_copy quality_profiles.creation_from_copy_description_1 quality_profiles.creation_from_copy_description_2',
@@ -57,6 +60,11 @@ const ui = {
blankRadio: byRole('radio', {
name: 'quality_profiles.creation_from_blank quality_profiles.creation_from_blank_description',
}),
activeRuleButton: (profileName: string) =>
byRole('button', {
name: `quality_profiles.comparison.activate_rule.${profileName}`,
}),
activateConfirmButton: byRole('button', { name: 'coding_rules.activate' }),
namePropupInput: byRole('textbox', { name: 'quality_profiles.new_name field_required' }),
filterByLang: byRole('textbox', { name: 'quality_profiles.filter_by:' }),
listLinkCQualityProfile: byRole('link', { name: 'c quality profile' }),
@@ -74,6 +82,12 @@ const ui = {
name: 'quality_profiles.creation.choose_copy_quality_profile field_required',
}),
nameCreatePopupInput: byRole('textbox', { name: 'name field_required' }),
comparisonDiffTableHeading: (rulesQuantity: number, profileName: string) =>
byRole('heading', { name: `quality_profiles.x_rules_only_in.${rulesQuantity} ${profileName}` }),
comparisonModifiedTableHeading: (rulesQuantity: number) =>
byRole('heading', {
name: `quality_profiles.x_rules_have_different_configuration.${rulesQuantity}`,
}),
};

it('should list Quality Profiles and filter by language', async () => {
@@ -103,7 +117,7 @@ it('should be able to extend Quality Profile', async () => {
serviceMock.setAdmin();
renderQualityProfiles();

await user.click(await ui.actionOnCQualityProfile.find());
await user.click(await ui.profileActions('c quality profile', 'C').find());
await user.click(ui.extendButton.get());

await user.clear(ui.namePropupInput.get());
@@ -129,7 +143,7 @@ it('should be able to copy Quality Profile', async () => {
serviceMock.setAdmin();
renderQualityProfiles();

await user.click(await ui.actionOnCQualityProfile.find());
await user.click(await ui.profileActions('c quality profile', 'C').find());
await user.click(ui.copyButton.get());

await user.clear(ui.namePropupInput.get());
@@ -166,6 +180,38 @@ it('should be able to create blank Quality Profile', async () => {
expect(await ui.listLinkNewCQualityProfile.find()).toBeInTheDocument();
});

it('should be able to compare profiles', async () => {
// From the list page
const user = userEvent.setup();
serviceMock.setAdmin();
renderQualityProfiles();

// For language with 1 profle we should not see compare action
await user.click(await ui.profileActions('c quality profile', 'C').find());
expect(ui.compareButton.query()).not.toBeInTheDocument();

await user.click(ui.profileActions('java quality profile', 'Java').get());
expect(ui.compareButton.get()).toBeInTheDocument();
await user.click(ui.compareButton.get());
expect(ui.compareDropdown.get()).toBeInTheDocument();
expect(ui.profileActions('java quality profile', 'Java').query()).not.toBeInTheDocument();
expect(ui.changelogLink.query()).not.toBeInTheDocument();

await selectEvent.select(ui.compareDropdown.get(), 'java quality profile #2');
expect(ui.comparisonDiffTableHeading(1, 'java quality profile').get()).toBeInTheDocument();
expect(ui.comparisonDiffTableHeading(1, 'java quality profile #2').get()).toBeInTheDocument();
expect(ui.comparisonModifiedTableHeading(1).query()).toBeInTheDocument();

// java quality profile is not editable
expect(ui.activeRuleButton('java quality profile').query()).not.toBeInTheDocument();

await user.click(ui.activeRuleButton('java quality profile #2').get());
expect(ui.popup.get()).toBeInTheDocument();

await user.click(ui.activateConfirmButton.get());
expect(ui.comparisonDiffTableHeading(1, 'java quality profile').query()).not.toBeInTheDocument();
});

function renderQualityProfiles() {
renderAppRoutes('profiles', routes, {
languages: {

+ 20
- 4
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResultActivation.tsx View File

@@ -21,8 +21,9 @@ import * as React from 'react';
import { Profile } from '../../../api/quality-profiles';
import { getRuleDetails } from '../../../api/rules';
import { Button } from '../../../components/controls/buttons';
import Tooltip from '../../../components/controls/Tooltip';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { RuleDetails } from '../../../types/types';
import ActivationFormModal from '../../coding-rules/components/ActivationFormModal';

@@ -83,9 +84,24 @@ export default class ComparisonResultActivation extends React.PureComponent<Prop

return (
<DeferredSpinner loading={this.state.state === 'opening'}>
<Button disabled={this.state.state !== 'closed'} onClick={this.handleButtonClick}>
{this.props.children}
</Button>
<Tooltip
placement="bottom"
overlay={translateWithParameters(
'quality_profiles.comparison.activate_rule',
profile.name
)}
>
<Button
disabled={this.state.state !== 'closed'}
aria-label={translateWithParameters(
'quality_profiles.comparison.activate_rule',
profile.name
)}
onClick={this.handleButtonClick}
>
{this.props.children}
</Button>
</Tooltip>

{this.isOpen(this.state) && (
<ActivationFormModal

+ 0
- 86
server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonForm-test.tsx View File

@@ -1,86 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockReactSelectOptionProps } from '../../../../helpers/mocks/react-select';
import Select from '../../../../components/controls/Select';
import { mockQualityProfile } from '../../../../helpers/testMocks';
import ComparisonForm from '../ComparisonForm';

it('should render Select with right options', () => {
const output = shallowRender().find(Select);

expect(output.length).toBe(1);
expect(output.prop('value')).toEqual([
{ isDefault: true, value: 'another', label: 'another name' },
]);
expect(output.prop('options')).toEqual([
{ isDefault: true, value: 'another', label: 'another name' },
]);
});

it('should render option correctly', () => {
const wrapper = shallowRender();
const mockOptions = [
{
value: 'val',
label: 'label',
isDefault: undefined,
},
];
const OptionRenderer = wrapper.instance().optionRenderer.bind(null, mockOptions);
expect(
shallow(<OptionRenderer {...mockReactSelectOptionProps({ value: 'test' })} />)
).toMatchSnapshot('option render');
});

it('should render value correctly', () => {
const wrapper = shallowRender();
const mockOptions = [
{
value: 'val',
label: 'label',
isDefault: true,
},
];
const ValueRenderer = wrapper.instance().singleValueRenderer.bind(null, mockOptions);
expect(
shallow(<ValueRenderer {...mockReactSelectOptionProps({ value: 'test' })} />)
).toMatchSnapshot('value render');
});

function shallowRender(overrides: Partial<ComparisonForm['props']> = {}) {
const profile = mockQualityProfile();
const profiles = [
profile,
mockQualityProfile({ key: 'another', name: 'another name', isDefault: true }),
mockQualityProfile({ key: 'java', name: 'java', language: 'java' }),
];

return shallow<ComparisonForm>(
<ComparisonForm
onCompare={() => true}
profile={profile}
profiles={profiles}
withKey="another"
{...overrides}
/>
);
}

+ 0
- 45
server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResultActivation-test.tsx View File

@@ -1,45 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { Profile } from '../../../../api/quality-profiles';
import { click, waitAndUpdate } from '../../../../helpers/testUtils';
import ComparisonResultActivation from '../ComparisonResultActivation';

jest.mock('../../../../api/rules', () => ({
getRuleDetails: jest.fn().mockResolvedValue({ key: 'foo' }),
}));

it('should activate', async () => {
const profile = { actions: { edit: true }, key: 'profile-key' } as Profile;
const wrapper = shallow(
<ComparisonResultActivation onDone={jest.fn()} profile={profile} ruleKey="foo" />
);
expect(wrapper).toMatchSnapshot();

click(wrapper.find('Button'));
expect(wrapper).toMatchSnapshot();

await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();

wrapper.find('ActivationFormModal').prop<Function>('onClose')();
expect(wrapper).toMatchSnapshot();
});

+ 0
- 104
server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResults-test.tsx View File

@@ -1,104 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { Profile } from '../../../../api/quality-profiles';
import Link from '../../../../components/common/Link';
import ComparisonEmpty from '../ComparisonEmpty';
import ComparisonResults from '../ComparisonResults';

it('should render ComparisonEmpty', () => {
const output = shallow(
<ComparisonResults
inLeft={[]}
inRight={[]}
left={{ name: 'left' }}
leftProfile={{} as Profile}
modified={[]}
refresh={jest.fn()}
right={{ name: 'right' }}
/>
);
expect(output.is(ComparisonEmpty)).toBe(true);
});

it('should compare', () => {
const inLeft = [{ key: 'rule1', name: 'rule1', severity: 'BLOCKER' }];
const inRight = [
{ key: 'rule2', name: 'rule2', severity: 'CRITICAL' },
{ key: 'rule3', name: 'rule3', severity: 'MAJOR' },
];
const modified = [
{
key: 'rule4',
name: 'rule4',
left: {
severity: 'BLOCKER',
params: { foo: 'bar' },
},
right: {
severity: 'INFO',
params: { foo: 'qwe' },
},
},
];

const output = shallow(
<ComparisonResults
inLeft={inLeft}
inRight={inRight}
left={{ name: 'left' }}
leftProfile={{} as Profile}
modified={modified}
refresh={jest.fn()}
right={{ name: 'right' }}
/>
);

const leftDiffs = output.find('.js-comparison-in-left');
expect(leftDiffs.length).toBe(1);
expect(leftDiffs.find(Link).length).toBe(1);
expect(leftDiffs.find(Link).prop('to')).toHaveProperty('search', '?rule_key=rule1&open=rule1');
expect(leftDiffs.find(Link).prop('children')).toContain('rule1');
expect(leftDiffs.find('SeverityIcon').length).toBe(1);
expect(leftDiffs.find('SeverityIcon').prop('severity')).toBe('BLOCKER');

const rightDiffs = output.find('.js-comparison-in-right');
expect(rightDiffs.length).toBe(2);
expect(rightDiffs.at(0).find(Link).length).toBe(1);
expect(rightDiffs.at(0).find(Link).prop('to')).toHaveProperty(
'search',
'?rule_key=rule2&open=rule2'
);
expect(rightDiffs.at(0).find(Link).prop('children')).toContain('rule2');
expect(rightDiffs.at(0).find('SeverityIcon').length).toBe(1);
expect(rightDiffs.at(0).find('SeverityIcon').prop('severity')).toBe('CRITICAL');

const modifiedDiffs = output.find('.js-comparison-modified');
expect(modifiedDiffs.length).toBe(1);
expect(modifiedDiffs.find(Link).at(0).prop('to')).toHaveProperty(
'search',
'?rule_key=rule4&open=rule4'
);
expect(modifiedDiffs.find(Link).at(0).prop('children')).toContain('rule4');
expect(modifiedDiffs.find('SeverityIcon').length).toBe(2);
expect(modifiedDiffs.text()).toContain('bar');
expect(modifiedDiffs.text()).toContain('qwe');
});

+ 0
- 21
server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/__snapshots__/ComparisonForm-test.tsx.snap View File

@@ -1,21 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render option correctly: option render 1`] = `
<Option
data={
Object {
"value": "test",
}
}
/>
`;

exports[`should render value correctly: value render 1`] = `
<SingleValue
data={
Object {
"value": "test",
}
}
/>
`;

+ 0
- 60
server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/__snapshots__/ComparisonResultActivation-test.tsx.snap View File

@@ -1,60 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should activate 1`] = `
<DeferredSpinner
loading={false}
>
<Button
disabled={false}
onClick={[Function]}
/>
</DeferredSpinner>
`;

exports[`should activate 2`] = `
<DeferredSpinner
loading={true}
>
<Button
disabled={true}
onClick={[Function]}
/>
</DeferredSpinner>
`;

exports[`should activate 3`] = `
<DeferredSpinner
loading={false}
>
<Button
disabled={true}
onClick={[Function]}
/>
<ActivationFormModal
modalHeader="coding_rules.activate_in_quality_profile"
onClose={[Function]}
onDone={[MockFunction]}
profiles={
Array [
Object {
"actions": Object {
"edit": true,
},
"key": "profile-key",
},
]
}
/>
</DeferredSpinner>
`;

exports[`should activate 4`] = `
<DeferredSpinner
loading={false}
>
<Button
disabled={false}
onClick={[Function]}
/>
</DeferredSpinner>
`;

+ 16
- 9
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx View File

@@ -17,6 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import classNames from 'classnames';
import { some } from 'lodash';
import * as React from 'react';
import {
changeProfileParent,
@@ -36,8 +38,9 @@ import { Router, withRouter } from '../../../components/hoc/withRouter';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/system';
import { getRulesUrl } from '../../../helpers/urls';
import { PROFILE_PATH } from '../constants';
import { Profile, ProfileActionModals } from '../types';
import { getProfileComparePath, getProfilePath, PROFILE_PATH } from '../utils';
import { getProfileComparePath, getProfilePath } from '../utils';
import DeleteProfileForm from './DeleteProfileForm';
import ProfileModalForm from './ProfileModalForm';

@@ -45,6 +48,7 @@ interface Props {
className?: string;
profile: Profile;
router: Router;
isComparable: boolean;
updateProfiles: () => Promise<void>;
}

@@ -175,7 +179,7 @@ export class ProfileActions extends React.PureComponent<Props, State> {
};

render() {
const { profile } = this.props;
const { profile, isComparable } = this.props;
const { loading, openModal } = this.state;
const { actions = {} } = profile;

@@ -187,11 +191,12 @@ export class ProfileActions extends React.PureComponent<Props, State> {
});

const hasNoActiveRules = profile.activeRuleCount === 0;
const hasAnyAction = some([...Object.values(actions), !profile.isBuiltIn, isComparable]);

return (
<>
<ActionsDropdown
className={this.props.className}
className={classNames(this.props.className, { invisible: !hasAnyAction })}
label={translateWithParameters(
'quality_profiles.actions',
profile.name,
@@ -217,12 +222,14 @@ export class ProfileActions extends React.PureComponent<Props, State> {
</ActionsDropdownItem>
)}

<ActionsDropdownItem
className="it__quality-profiles__compare"
to={getProfileComparePath(profile.name, profile.language)}
>
{translate('compare')}
</ActionsDropdownItem>
{isComparable && (
<ActionsDropdownItem
className="it__quality-profiles__compare"
to={getProfileComparePath(profile.name, profile.language)}
>
{translate('compare')}
</ActionsDropdownItem>
)}

{actions.copy && (
<>

+ 7
- 2
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx View File

@@ -48,7 +48,8 @@ export function ProfileContainer(props: QualityProfilesContextProps) {
return profileForKey ? null : <ProfileNotFound />;
}

const profile = profiles.find((p) => p.language === language && p.name === name);
const filteredProfiles = profiles.filter((p) => p.language === language);
const profile = filteredProfiles.find((p) => p.name === name);

if (!profile) {
return <ProfileNotFound />;
@@ -62,7 +63,11 @@ export function ProfileContainer(props: QualityProfilesContextProps) {
return (
<div id="quality-profile">
<Helmet defer={false} title={profile.name} />
<ProfileHeader profile={profile} updateProfiles={props.updateProfiles} />
<ProfileHeader
profile={profile}
isComparable={filteredProfiles.length > 1}
updateProfiles={props.updateProfiles}
/>
<Outlet context={context} />
</div>
);

+ 1
- 1
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.tsx View File

@@ -20,7 +20,7 @@
import * as React from 'react';
import { NavLink } from 'react-router-dom';
import { translate } from '../../../helpers/l10n';
import { PROFILE_PATH } from '../utils';
import { PROFILE_PATH } from '../constants';

export default function ProfileNotFound() {
return (

+ 8
- 2
server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx View File

@@ -30,8 +30,8 @@ import {
import { mockQualityProfile, mockRouter } from '../../../../helpers/testMocks';
import { click, waitAndUpdate } from '../../../../helpers/testUtils';
import { queryToSearch } from '../../../../helpers/urls';
import { PROFILE_PATH } from '../../constants';
import { ProfileActionModals } from '../../types';
import { PROFILE_PATH } from '../../utils';
import DeleteProfileForm from '../DeleteProfileForm';
import { ProfileActions } from '../ProfileActions';
import ProfileModalForm from '../ProfileModalForm';
@@ -331,6 +331,12 @@ it('should not allow to set a profile as the default if the profile has no activ
function shallowRender(props: Partial<ProfileActions['props']> = {}) {
const router = mockRouter();
return shallow<ProfileActions>(
<ProfileActions profile={PROFILE} router={router} updateProfiles={jest.fn()} {...props} />
<ProfileActions
isComparable={true}
profile={PROFILE}
router={router}
updateProfiles={jest.fn()}
{...props}
/>
);
}

+ 8
- 0
server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ProfileActions-test.tsx.snap View File

@@ -3,6 +3,7 @@
exports[`renders correctly: all permissions 1`] = `
<Fragment>
<ActionsDropdown
className=""
label="quality_profiles.actions.name.JavaScript"
>
<ActionsDropdownItem
@@ -77,6 +78,7 @@ exports[`renders correctly: all permissions 1`] = `
exports[`renders correctly: copy modal 1`] = `
<Fragment>
<ActionsDropdown
className=""
label="quality_profiles.actions.name.JavaScript"
>
<ActionsDropdownItem
@@ -127,6 +129,7 @@ exports[`renders correctly: copy modal 1`] = `
exports[`renders correctly: delete modal 1`] = `
<Fragment>
<ActionsDropdown
className=""
label="quality_profiles.actions.name.JavaScript"
>
<ActionsDropdownItem
@@ -176,6 +179,7 @@ exports[`renders correctly: delete modal 1`] = `
exports[`renders correctly: edit only 1`] = `
<Fragment>
<ActionsDropdown
className=""
label="quality_profiles.actions.name.JavaScript"
>
<ActionsDropdownItem
@@ -220,6 +224,7 @@ exports[`renders correctly: edit only 1`] = `
exports[`renders correctly: extend modal 1`] = `
<Fragment>
<ActionsDropdown
className=""
label="quality_profiles.actions.name.JavaScript"
>
<ActionsDropdownItem
@@ -270,6 +275,7 @@ exports[`renders correctly: extend modal 1`] = `
exports[`renders correctly: no permissions 1`] = `
<Fragment>
<ActionsDropdown
className=""
label="quality_profiles.actions.name.JavaScript"
>
<ActionsDropdownItem
@@ -297,6 +303,7 @@ exports[`renders correctly: no permissions 1`] = `
exports[`renders correctly: rename modal 1`] = `
<Fragment>
<ActionsDropdown
className=""
label="quality_profiles.actions.name.JavaScript"
>
<ActionsDropdownItem
@@ -347,6 +354,7 @@ exports[`renders correctly: rename modal 1`] = `
exports[`should not allow to set a profile as the default if the profile has no active rules 1`] = `
<Fragment>
<ActionsDropdown
className=""
label="quality_profiles.actions.name.JavaScript"
>
<ActionsDropdownItem

server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileHeader-test.tsx → server/sonar-web/src/main/js/apps/quality-profiles/constants.ts View File

@@ -17,20 +17,5 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockQualityProfile } from '../../../../helpers/testMocks';
import ProfileHeader from '../ProfileHeader';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
expect(shallowRender({ profile: mockQualityProfile({ isDefault: true }) })).toMatchSnapshot(
'for default profile'
);
});

function shallowRender(props: Partial<ProfileHeader['props']> = {}) {
return shallow(
<ProfileHeader profile={mockQualityProfile()} updateProfiles={jest.fn()} {...props} />
);
}
export const PROFILE_PATH = '/profiles';
export const PROFILE_COMPARE_PATH = `${PROFILE_PATH}/compare`;

+ 67
- 60
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.tsx View File

@@ -23,48 +23,55 @@ import { NavLink } from 'react-router-dom';
import Link from '../../../components/common/Link';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import Tooltip from '../../../components/controls/Tooltip';
import { useLocation } from '../../../components/hoc/withRouter';
import DateFromNow from '../../../components/intl/DateFromNow';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getQualityProfileUrl } from '../../../helpers/urls';
import BuiltInQualityProfileBadge from '../components/BuiltInQualityProfileBadge';
import ProfileActions from '../components/ProfileActions';
import ProfileLink from '../components/ProfileLink';
import { PROFILE_PATH } from '../constants';
import { Profile } from '../types';
import { getProfileChangelogPath, getProfilesForLanguagePath, PROFILE_PATH } from '../utils';
import {
getProfileChangelogPath,
getProfilesForLanguagePath,
isProfileComparePath,
} from '../utils';

interface Props {
profile: Profile;
isComparable: boolean;
updateProfiles: () => Promise<void>;
}

export default class ProfileHeader extends React.PureComponent<Props> {
render() {
const { profile } = this.props;
export default function ProfileHeader(props: Props) {
const { profile, isComparable, updateProfiles } = props;
const location = useLocation();

return (
<div className="page-header quality-profile-header">
<div className="note spacer-bottom">
<NavLink end={true} to={PROFILE_PATH}>
{translate('quality_profiles.page')}
</NavLink>
{' / '}
<Link to={getProfilesForLanguagePath(profile.language)}>{profile.languageName}</Link>
</div>

<h1 className="page-title">
<ProfileLink language={profile.language} name={profile.name}>
<span>{profile.name}</span>
</ProfileLink>
{profile.isDefault && (
<Tooltip overlay={translate('quality_profiles.list.default.help')}>
<span className=" spacer-left badge">{translate('default')}</span>
</Tooltip>
)}
{profile.isBuiltIn && (
<BuiltInQualityProfileBadge className="spacer-left" tooltip={false} />
)}
</h1>
return (
<div className="page-header quality-profile-header">
<div className="note spacer-bottom">
<NavLink end={true} to={PROFILE_PATH}>
{translate('quality_profiles.page')}
</NavLink>
{' / '}
<Link to={getProfilesForLanguagePath(profile.language)}>{profile.languageName}</Link>
</div>

<h1 className="page-title">
<ProfileLink language={profile.language} name={profile.name}>
<span>{profile.name}</span>
</ProfileLink>
{profile.isDefault && (
<Tooltip overlay={translate('quality_profiles.list.default.help')}>
<span className=" spacer-left badge">{translate('default')}</span>
</Tooltip>
)}
{profile.isBuiltIn && (
<BuiltInQualityProfileBadge className="spacer-left" tooltip={false} />
)}
</h1>
{!isProfileComparePath(location.pathname) && (
<div className="pull-right">
<ul className="list-inline" style={{ lineHeight: '24px' }}>
<li className="small spacer-right">
@@ -78,47 +85,47 @@ export default class ProfileHeader extends React.PureComponent<Props> {
{translate('changelog')}
</Link>
</li>

<li>
<ProfileActions
className="pull-left"
profile={profile}
updateProfiles={this.props.updateProfiles}
isComparable={isComparable}
updateProfiles={updateProfiles}
/>
</li>
</ul>
</div>
)}

{profile.isBuiltIn && (
<div className="page-description">
{translate('quality_profiles.built_in.description')}
</div>
)}
{profile.isBuiltIn && (
<div className="page-description">{translate('quality_profiles.built_in.description')}</div>
)}

{profile.parentKey && profile.parentName && (
<div className="page-description">
<FormattedMessage
defaultMessage={translate('quality_profiles.extend_description')}
id="quality_profiles.extend_description"
values={{
link: (
<>
<Link to={getQualityProfileUrl(profile.parentName, profile.language)}>
{profile.parentName}
</Link>
<HelpTooltip
className="little-spacer-left"
overlay={translateWithParameters(
'quality_profiles.extend_description_help',
profile.parentName
)}
/>
</>
),
}}
/>
</div>
)}
</div>
);
}
{profile.parentKey && profile.parentName && (
<div className="page-description">
<FormattedMessage
defaultMessage={translate('quality_profiles.extend_description')}
id="quality_profiles.extend_description"
values={{
link: (
<>
<Link to={getQualityProfileUrl(profile.parentName, profile.language)}>
{profile.parentName}
</Link>
<HelpTooltip
className="little-spacer-left"
overlay={translateWithParameters(
'quality_profiles.extend_description_help',
profile.parentName
)}
/>
</>
),
}}
/>
</div>
)}
</div>
);
}

+ 0
- 214
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileHeader-test.tsx.snap View File

@@ -1,214 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<div
className="page-header quality-profile-header"
>
<div
className="note spacer-bottom"
>
<NavLink
end={true}
to="/profiles"
>
quality_profiles.page
</NavLink>
/
<ForwardRef(Link)
to={
Object {
"pathname": "/profiles",
"search": "?language=js",
}
}
>
JavaScript
</ForwardRef(Link)>
</div>
<h1
className="page-title"
>
<ProfileLink
language="js"
name="name"
>
<span>
name
</span>
</ProfileLink>
</h1>
<div
className="pull-right"
>
<ul
className="list-inline"
style={
Object {
"lineHeight": "24px",
}
}
>
<li
className="small spacer-right"
>
quality_profiles.updated_
<DateFromNow />
</li>
<li
className="small big-spacer-right"
>
quality_profiles.used_
<DateFromNow />
</li>
<li>
<ForwardRef(Link)
className="button"
to={
Object {
"pathname": "/profiles/changelog",
"search": "?language=js&name=name",
}
}
>
changelog
</ForwardRef(Link)>
</li>
<li>
<withRouter(ProfileActions)
className="pull-left"
profile={
Object {
"activeDeprecatedRuleCount": 2,
"activeRuleCount": 10,
"childrenCount": 0,
"depth": 1,
"isBuiltIn": false,
"isDefault": false,
"isInherited": false,
"key": "key",
"language": "js",
"languageName": "JavaScript",
"name": "name",
"projectCount": 3,
}
}
updateProfiles={[MockFunction]}
/>
</li>
</ul>
</div>
</div>
`;

exports[`should render correctly: for default profile 1`] = `
<div
className="page-header quality-profile-header"
>
<div
className="note spacer-bottom"
>
<NavLink
end={true}
to="/profiles"
>
quality_profiles.page
</NavLink>
/
<ForwardRef(Link)
to={
Object {
"pathname": "/profiles",
"search": "?language=js",
}
}
>
JavaScript
</ForwardRef(Link)>
</div>
<h1
className="page-title"
>
<ProfileLink
language="js"
name="name"
>
<span>
name
</span>
</ProfileLink>
<Tooltip
overlay="quality_profiles.list.default.help"
>
<span
className=" spacer-left badge"
>
default
</span>
</Tooltip>
</h1>
<div
className="pull-right"
>
<ul
className="list-inline"
style={
Object {
"lineHeight": "24px",
}
}
>
<li
className="small spacer-right"
>
quality_profiles.updated_
<DateFromNow />
</li>
<li
className="small big-spacer-right"
>
quality_profiles.used_
<DateFromNow />
</li>
<li>
<ForwardRef(Link)
className="button"
to={
Object {
"pathname": "/profiles/changelog",
"search": "?language=js&name=name",
}
}
>
changelog
</ForwardRef(Link)>
</li>
<li>
<withRouter(ProfileActions)
className="pull-left"
profile={
Object {
"activeDeprecatedRuleCount": 2,
"activeRuleCount": 10,
"childrenCount": 0,
"depth": 1,
"isBuiltIn": false,
"isDefault": true,
"isInherited": false,
"key": "key",
"language": "js",
"languageName": "JavaScript",
"name": "name",
"projectCount": 3,
}
}
updateProfiles={[MockFunction]}
/>
</li>
</ul>
</div>
</div>
`;

+ 1
- 0
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx View File

@@ -42,6 +42,7 @@ export default class ProfilesList extends React.PureComponent<Props> {
key={profile.key}
profile={profile}
updateProfiles={this.props.updateProfiles}
isComparable={profiles.length > 1}
/>
));
}

+ 2
- 1
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.tsx View File

@@ -21,7 +21,8 @@ import * as React from 'react';
import Select from '../../../components/controls/Select';
import { Router, withRouter } from '../../../components/hoc/withRouter';
import { translate } from '../../../helpers/l10n';
import { getProfilesForLanguagePath, PROFILE_PATH } from '../utils';
import { PROFILE_PATH } from '../constants';
import { getProfilesForLanguagePath } from '../utils';

interface Props {
currentFilter?: string;

+ 7
- 2
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx View File

@@ -31,10 +31,11 @@ import { Profile } from '../types';
export interface ProfilesListRowProps {
profile: Profile;
updateProfiles: () => Promise<void>;
isComparable: boolean;
}

export function ProfilesListRow(props: ProfilesListRowProps) {
const { profile } = props;
const { profile, isComparable } = props;

const offset = 25 * (profile.depth - 1);
const activeRulesUrl = getRulesUrl({
@@ -99,7 +100,11 @@ export function ProfilesListRow(props: ProfilesListRowProps) {
</td>

<td className="quality-profiles-table-actions thin nowrap text-middle text-right">
<ProfileActions profile={profile} updateProfiles={props.updateProfiles} />
<ProfileActions
isComparable={isComparable}
profile={profile}
updateProfiles={props.updateProfiles}
/>
</td>
</tr>
);

+ 0
- 45
server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/ProfilesList-test.tsx View File

@@ -1,45 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockLanguage, mockQualityProfile } from '../../../../helpers/testMocks';
import ProfilesList from '../ProfilesList';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();

expect(shallowRender({ language: 'css' })).toMatchSnapshot();

expect(shallowRender({ language: 'unknown' })).toMatchSnapshot();
});

function shallowRender(props: Partial<ProfilesList['props']> = {}) {
return shallow(
<ProfilesList
languages={[mockLanguage(), mockLanguage({ key: 'js', name: 'JS' })]}
profiles={[
mockQualityProfile(),
mockQualityProfile({ language: 'css', languageName: 'CSS' }),
]}
updateProfiles={jest.fn()}
{...props}
/>
);
}

+ 0
- 46
server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/ProfilesListRow-test.tsx View File

@@ -1,46 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockQualityProfile } from '../../../../helpers/testMocks';
import { ProfilesListRow, ProfilesListRowProps } from '../ProfilesListRow';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ profile: mockQualityProfile({ isBuiltIn: true }) })).toMatchSnapshot(
'built-in profile'
);
expect(shallowRender({ profile: mockQualityProfile({ isDefault: true }) })).toMatchSnapshot(
'default profile'
);
expect(
shallowRender({ profile: mockQualityProfile({ activeDeprecatedRuleCount: 10 }) })
).toMatchSnapshot('with deprecated rules');
});

function shallowRender(props: Partial<ProfilesListRowProps> = {}) {
return shallow(
<ProfilesListRow
profile={mockQualityProfile({ activeDeprecatedRuleCount: 0 })}
updateProfiles={jest.fn()}
{...props}
/>
);
}

+ 0
- 288
server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesList-test.tsx.snap View File

@@ -1,288 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<div>
<withRouter(ProfilesListHeader)
languages={
Array [
Object {
"key": "css",
"name": "CSS",
},
Object {
"key": "js",
"name": "JS",
},
]
}
/>
<div
className="boxed-group boxed-group-inner quality-profiles-table"
key="css"
>
<table
className="data zebra zebra-hover"
data-language="css"
>
<thead>
<tr>
<th>
CSS
,
quality_profiles.x_profiles.1
</th>
<th
className="text-right nowrap"
>
quality_profiles.list.projects
<HelpTooltip
className="table-cell-doc"
overlay={
<div
className="big-padded-top big-padded-bottom"
>
quality_profiles.list.projects.help
</div>
}
/>
</th>
<th
className="text-right nowrap"
>
quality_profiles.list.rules
</th>
<th
className="text-right nowrap"
>
quality_profiles.list.updated
</th>
<th
className="text-right nowrap"
>
quality_profiles.list.used
</th>
<th>
 
</th>
</tr>
</thead>
<tbody>
<Memo(ProfilesListRow)
key="key"
profile={
Object {
"activeDeprecatedRuleCount": 2,
"activeRuleCount": 10,
"childrenCount": 0,
"depth": 1,
"isBuiltIn": false,
"isDefault": false,
"isInherited": false,
"key": "key",
"language": "css",
"languageName": "CSS",
"name": "name",
"projectCount": 3,
}
}
updateProfiles={[MockFunction]}
/>
</tbody>
</table>
</div>
<div
className="boxed-group boxed-group-inner quality-profiles-table"
key="js"
>
<table
className="data zebra zebra-hover"
data-language="js"
>
<thead>
<tr>
<th>
JS
,
quality_profiles.x_profiles.1
</th>
<th
className="text-right nowrap"
>
quality_profiles.list.projects
<HelpTooltip
className="table-cell-doc"
overlay={
<div
className="big-padded-top big-padded-bottom"
>
quality_profiles.list.projects.help
</div>
}
/>
</th>
<th
className="text-right nowrap"
>
quality_profiles.list.rules
</th>
<th
className="text-right nowrap"
>
quality_profiles.list.updated
</th>
<th
className="text-right nowrap"
>
quality_profiles.list.used
</th>
<th>
 
</th>
</tr>
</thead>
<tbody>
<Memo(ProfilesListRow)
key="key"
profile={
Object {
"activeDeprecatedRuleCount": 2,
"activeRuleCount": 10,
"childrenCount": 0,
"depth": 1,
"isBuiltIn": false,
"isDefault": false,
"isInherited": false,
"key": "key",
"language": "js",
"languageName": "JavaScript",
"name": "name",
"projectCount": 3,
}
}
updateProfiles={[MockFunction]}
/>
</tbody>
</table>
</div>
</div>
`;

exports[`should render correctly 2`] = `
<div>
<withRouter(ProfilesListHeader)
currentFilter="css"
languages={
Array [
Object {
"key": "css",
"name": "CSS",
},
Object {
"key": "js",
"name": "JS",
},
]
}
/>
<div
className="boxed-group boxed-group-inner quality-profiles-table"
key="css"
>
<table
className="data zebra zebra-hover"
data-language="css"
>
<thead>
<tr>
<th>
CSS
,
quality_profiles.x_profiles.1
</th>
<th
className="text-right nowrap"
>
quality_profiles.list.projects
<HelpTooltip
className="table-cell-doc"
overlay={
<div
className="big-padded-top big-padded-bottom"
>
quality_profiles.list.projects.help
</div>
}
/>
</th>
<th
className="text-right nowrap"
>
quality_profiles.list.rules
</th>
<th
className="text-right nowrap"
>
quality_profiles.list.updated
</th>
<th
className="text-right nowrap"
>
quality_profiles.list.used
</th>
<th>
 
</th>
</tr>
</thead>
<tbody>
<Memo(ProfilesListRow)
key="key"
profile={
Object {
"activeDeprecatedRuleCount": 2,
"activeRuleCount": 10,
"childrenCount": 0,
"depth": 1,
"isBuiltIn": false,
"isDefault": false,
"isInherited": false,
"key": "key",
"language": "css",
"languageName": "CSS",
"name": "name",
"projectCount": 3,
}
}
updateProfiles={[MockFunction]}
/>
</tbody>
</table>
</div>
</div>
`;

exports[`should render correctly 3`] = `
<div>
<withRouter(ProfilesListHeader)
currentFilter="unknown"
languages={
Array [
Object {
"key": "css",
"name": "CSS",
},
Object {
"key": "js",
"name": "JS",
},
]
}
/>
<Alert
className="spacer-top"
variant="warning"
>
no_results
</Alert>
</div>
`;

+ 0
- 411
server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesListRow-test.tsx.snap View File

@@ -1,411 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: built-in profile 1`] = `
<tr
className="quality-profiles-table-row text-middle"
data-key="key"
data-name="name"
>
<td
className="quality-profiles-table-name text-middle"
>
<div
className="display-flex-center"
style={
Object {
"paddingLeft": 0,
}
}
>
<div>
<ProfileLink
language="js"
name="name"
>
name
</ProfileLink>
</div>
<BuiltInQualityProfileBadge
className="spacer-left"
/>
</div>
</td>
<td
className="quality-profiles-table-projects thin nowrap text-middle text-right"
>
<span>
3
</span>
</td>
<td
className="quality-profiles-table-rules thin nowrap text-middle text-right"
>
<div>
<span
className="spacer-right"
>
<Tooltip
overlay="quality_profiles.deprecated_rules"
>
<ForwardRef(Link)
className="badge badge-error"
to={
Object {
"pathname": "/coding_rules",
"search": "?qprofile=key&activation=true&statuses=DEPRECATED",
}
}
>
2
</ForwardRef(Link)>
</Tooltip>
</span>
<ForwardRef(Link)
to={
Object {
"pathname": "/coding_rules",
"search": "?qprofile=key&activation=true",
}
}
>
10
</ForwardRef(Link)>
</div>
</td>
<td
className="quality-profiles-table-date thin nowrap text-middle text-right"
>
<DateFromNow />
</td>
<td
className="quality-profiles-table-date thin nowrap text-middle text-right"
>
<DateFromNow />
</td>
<td
className="quality-profiles-table-actions thin nowrap text-middle text-right"
>
<withRouter(ProfileActions)
profile={
Object {
"activeDeprecatedRuleCount": 2,
"activeRuleCount": 10,
"childrenCount": 0,
"depth": 1,
"isBuiltIn": true,
"isDefault": false,
"isInherited": false,
"key": "key",
"language": "js",
"languageName": "JavaScript",
"name": "name",
"projectCount": 3,
}
}
updateProfiles={[MockFunction]}
/>
</td>
</tr>
`;

exports[`should render correctly: default 1`] = `
<tr
className="quality-profiles-table-row text-middle"
data-key="key"
data-name="name"
>
<td
className="quality-profiles-table-name text-middle"
>
<div
className="display-flex-center"
style={
Object {
"paddingLeft": 0,
}
}
>
<div>
<ProfileLink
language="js"
name="name"
>
name
</ProfileLink>
</div>
</div>
</td>
<td
className="quality-profiles-table-projects thin nowrap text-middle text-right"
>
<span>
3
</span>
</td>
<td
className="quality-profiles-table-rules thin nowrap text-middle text-right"
>
<div>
<ForwardRef(Link)
to={
Object {
"pathname": "/coding_rules",
"search": "?qprofile=key&activation=true",
}
}
>
10
</ForwardRef(Link)>
</div>
</td>
<td
className="quality-profiles-table-date thin nowrap text-middle text-right"
>
<DateFromNow />
</td>
<td
className="quality-profiles-table-date thin nowrap text-middle text-right"
>
<DateFromNow />
</td>
<td
className="quality-profiles-table-actions thin nowrap text-middle text-right"
>
<withRouter(ProfileActions)
profile={
Object {
"activeDeprecatedRuleCount": 0,
"activeRuleCount": 10,
"childrenCount": 0,
"depth": 1,
"isBuiltIn": false,
"isDefault": false,
"isInherited": false,
"key": "key",
"language": "js",
"languageName": "JavaScript",
"name": "name",
"projectCount": 3,
}
}
updateProfiles={[MockFunction]}
/>
</td>
</tr>
`;

exports[`should render correctly: default profile 1`] = `
<tr
className="quality-profiles-table-row text-middle"
data-key="key"
data-name="name"
>
<td
className="quality-profiles-table-name text-middle"
>
<div
className="display-flex-center"
style={
Object {
"paddingLeft": 0,
}
}
>
<div>
<ProfileLink
language="js"
name="name"
>
name
</ProfileLink>
</div>
</div>
</td>
<td
className="quality-profiles-table-projects thin nowrap text-middle text-right"
>
<Tooltip
overlay="quality_profiles.list.default.help"
>
<span
className="badge"
>
default
</span>
</Tooltip>
</td>
<td
className="quality-profiles-table-rules thin nowrap text-middle text-right"
>
<div>
<span
className="spacer-right"
>
<Tooltip
overlay="quality_profiles.deprecated_rules"
>
<ForwardRef(Link)
className="badge badge-error"
to={
Object {
"pathname": "/coding_rules",
"search": "?qprofile=key&activation=true&statuses=DEPRECATED",
}
}
>
2
</ForwardRef(Link)>
</Tooltip>
</span>
<ForwardRef(Link)
to={
Object {
"pathname": "/coding_rules",
"search": "?qprofile=key&activation=true",
}
}
>
10
</ForwardRef(Link)>
</div>
</td>
<td
className="quality-profiles-table-date thin nowrap text-middle text-right"
>
<DateFromNow />
</td>
<td
className="quality-profiles-table-date thin nowrap text-middle text-right"
>
<DateFromNow />
</td>
<td
className="quality-profiles-table-actions thin nowrap text-middle text-right"
>
<withRouter(ProfileActions)
profile={
Object {
"activeDeprecatedRuleCount": 2,
"activeRuleCount": 10,
"childrenCount": 0,
"depth": 1,
"isBuiltIn": false,
"isDefault": true,
"isInherited": false,
"key": "key",
"language": "js",
"languageName": "JavaScript",
"name": "name",
"projectCount": 3,
}
}
updateProfiles={[MockFunction]}
/>
</td>
</tr>
`;

exports[`should render correctly: with deprecated rules 1`] = `
<tr
className="quality-profiles-table-row text-middle"
data-key="key"
data-name="name"
>
<td
className="quality-profiles-table-name text-middle"
>
<div
className="display-flex-center"
style={
Object {
"paddingLeft": 0,
}
}
>
<div>
<ProfileLink
language="js"
name="name"
>
name
</ProfileLink>
</div>
</div>
</td>
<td
className="quality-profiles-table-projects thin nowrap text-middle text-right"
>
<span>
3
</span>
</td>
<td
className="quality-profiles-table-rules thin nowrap text-middle text-right"
>
<div>
<span
className="spacer-right"
>
<Tooltip
overlay="quality_profiles.deprecated_rules"
>
<ForwardRef(Link)
className="badge badge-error"
to={
Object {
"pathname": "/coding_rules",
"search": "?qprofile=key&activation=true&statuses=DEPRECATED",
}
}
>
10
</ForwardRef(Link)>
</Tooltip>
</span>
<ForwardRef(Link)
to={
Object {
"pathname": "/coding_rules",
"search": "?qprofile=key&activation=true",
}
}
>
10
</ForwardRef(Link)>
</div>
</td>
<td
className="quality-profiles-table-date thin nowrap text-middle text-right"
>
<DateFromNow />
</td>
<td
className="quality-profiles-table-date thin nowrap text-middle text-right"
>
<DateFromNow />
</td>
<td
className="quality-profiles-table-actions thin nowrap text-middle text-right"
>
<withRouter(ProfileActions)
profile={
Object {
"activeDeprecatedRuleCount": 10,
"activeRuleCount": 10,
"childrenCount": 0,
"depth": 1,
"isBuiltIn": false,
"isDefault": false,
"isInherited": false,
"key": "key",
"language": "js",
"languageName": "JavaScript",
"name": "name",
"projectCount": 3,
}
}
updateProfiles={[MockFunction]}
/>
</td>
</tr>
`;

+ 6
- 3
server/sonar-web/src/main/js/apps/quality-profiles/utils.ts View File

@@ -22,6 +22,7 @@ import { sortBy } from 'lodash';
import { Profile as BaseProfile } from '../../api/quality-profiles';
import { isValidDate, parseDate } from '../../helpers/dates';
import { queryToSearch } from '../../helpers/urls';
import { PROFILE_COMPARE_PATH, PROFILE_PATH } from './constants';
import { Profile } from './types';

export function sortProfiles(profiles: BaseProfile[]): Profile[] {
@@ -65,8 +66,6 @@ export function isStagnant(profile: Profile): boolean {
return false;
}

export const PROFILE_PATH = '/profiles';

export const getProfilesForLanguagePath = (language: string) => ({
pathname: PROFILE_PATH,
search: queryToSearch({ language }),
@@ -83,7 +82,7 @@ export const getProfileComparePath = (name: string, language: string, withKey?:
Object.assign(query, { withKey });
}
return {
pathname: `${PROFILE_PATH}/compare`,
pathname: PROFILE_COMPARE_PATH,
search: queryToSearch(query),
};
};
@@ -107,3 +106,7 @@ export const getProfileChangelogPath = (
search: queryToSearch(query),
};
};

export const isProfileComparePath = (pathname: string): boolean => {
return pathname === PROFILE_COMPARE_PATH;
};

+ 31
- 0
server/sonar-web/src/main/js/helpers/testMocks.ts View File

@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { To } from 'react-router-dom';
import { CompareResponse } from '../api/quality-profiles';
import { RuleDescriptionSections } from '../apps/coding-rules/rule';
import { Exporter, Profile } from '../apps/quality-profiles/types';
import { Location, Router } from '../components/hoc/withRouter';
@@ -437,6 +438,36 @@ export function mockQualityProfile(overrides: Partial<Profile> = {}): Profile {
};
}

export function mockCompareResult(overrides: Partial<CompareResponse> = {}): CompareResponse {
return {
left: { name: 'Profile A' },
right: { name: 'Profile B' },
inLeft: [
{
key: 'java:S4604',
name: 'Rule in left',
severity: 'MINOR',
},
],
inRight: [
{
key: 'java:S5128',
name: 'Rule in right',
severity: 'MAJOR',
},
],
modified: [
{
key: 'java:S1698',
name: '== and != should not be used when equals is overridden',
left: { params: {}, severity: 'MINOR' },
right: { params: {}, severity: 'CRITICAL' },
},
],
...overrides,
};
}

export function mockQualityProfileInheritance(
overrides: Partial<ProfileInheritanceDetails> = {}
): ProfileInheritanceDetails {

+ 1
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -1701,6 +1701,7 @@ quality_profile.empty_comparison=The quality profiles are equal.
quality_profiles.activate_more=Activate More
quality_profiles.activate_more.help.built_in=This quality profile is built in, and cannot be updated manually. If you want to activate more rules, create a new profile that inherits from this one and add rules there.
quality_profiles.activate_more_rules=Activate More Rules
quality_profiles.comparison.activate_rule=Activate rule for profile "{0}"
quality_profiles.intro1=Quality profiles are collections of rules to apply during an analysis.
quality_profiles.intro2=For each language there is a default profile. All projects not explicitly assigned to some other profile will be analyzed with the default. Ideally, all projects will use the same profile for a language.
quality_profiles.list.projects=Projects
@@ -1758,7 +1759,6 @@ quality_profiles.actions=Open {0} {1} quality profile actions




#------------------------------------------------------------------------------
#
# QUALITY GATES

Loading…
Cancel
Save