Переглянути джерело

SONAR-7938 Display inheritance info for deprecated rules, and make related tests DRYer

tags/7.6
Wouter Admiraal 5 роки тому
джерело
коміт
cf35008a4d

+ 4
- 4
server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonForm-test.tsx Переглянути файл

@@ -20,14 +20,14 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import ComparisonForm from '../ComparisonForm';
import { createFakeProfile } from '../../utils';
import { mockQualityProfile } from '../../testUtils';

it('should render Select with right options', () => {
const profile = createFakeProfile();
const profile = mockQualityProfile();
const profiles = [
profile,
createFakeProfile({ key: 'another', name: 'another name' }),
createFakeProfile({ key: 'java', name: 'java', language: 'java' })
mockQualityProfile({ key: 'another', name: 'another name' }),
mockQualityProfile({ key: 'java', name: 'java', language: 'java' })
];

const output = shallow(

+ 5
- 11
server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx Переглянути файл

@@ -21,22 +21,16 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import { ProfileActions } from '../ProfileActions';
import { click, waitAndUpdate } from '../../../../helpers/testUtils';
import { mockQualityProfile } from '../../testUtils';

const PROFILE = {
const PROFILE = mockQualityProfile({
activeRuleCount: 68,
activeDeprecatedRuleCount: 0,
childrenCount: 0,
depth: 0,
isBuiltIn: false,
isDefault: false,
isInherited: false,
key: 'foo',
language: 'java',
languageName: 'Java',
name: 'Foo',
language: 'js',
organization: 'org',
rulesUpdatedAt: '2017-06-28T12:58:44+0000'
};
});

it('renders with no permissions', () => {
expect(
@@ -107,7 +101,7 @@ it('should copy profile', async () => {

expect(push).toBeCalledWith({
pathname: '/organizations/org/quality_profiles/show',
query: { language: 'java', name: 'new-name' }
query: { language: 'js', name: 'new-name' }
});
expect(wrapper.find('CopyProfileForm').exists()).toBe(false);
});

+ 8
- 10
server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.tsx Переглянути файл

@@ -24,13 +24,13 @@ import { WithRouterProps } from 'react-router';
import ProfileContainer from '../ProfileContainer';
import ProfileNotFound from '../ProfileNotFound';
import ProfileHeader from '../../details/ProfileHeader';
import { createFakeProfile } from '../../utils';
import { mockQualityProfile } from '../../testUtils';

const routerProps = { router: {} } as WithRouterProps;

it('should render ProfileHeader', () => {
const targetProfile = createFakeProfile({ language: 'js', name: 'fake' });
const profiles = [targetProfile, createFakeProfile({ language: 'js', name: 'another' })];
const targetProfile = mockQualityProfile({ name: 'fake' });
const profiles = [targetProfile, mockQualityProfile({ name: 'another' })];
const updateProfiles = jest.fn();
const output = shallow(
<ProfileContainer
@@ -49,10 +49,7 @@ it('should render ProfileHeader', () => {
});

it('should render ProfileNotFound', () => {
const profiles = [
createFakeProfile({ language: 'js', name: 'fake' }),
createFakeProfile({ language: 'js', name: 'another' })
];
const profiles = [mockQualityProfile({ name: 'fake' }), mockQualityProfile({ name: 'another' })];
const output = shallow(
<ProfileContainer
location={{ pathname: '', query: { language: 'js', name: 'random' } }}
@@ -67,11 +64,12 @@ it('should render ProfileNotFound', () => {
});

it('should render Helmet', () => {
const profiles = [createFakeProfile({ language: 'js', name: 'First Profile' })];
const name = 'First Profile';
const profiles = [mockQualityProfile({ name })];
const updateProfiles = jest.fn();
const output = shallow(
<ProfileContainer
location={{ pathname: '', query: { language: 'js', name: 'First Profile' } }}
location={{ pathname: '', query: { language: 'js', name } }}
organization={null}
profiles={profiles}
updateProfiles={updateProfiles}
@@ -81,5 +79,5 @@ it('should render Helmet', () => {
);
const helmet = output.find(Helmet);
expect(helmet.length).toBe(1);
expect(helmet.prop('title')).toContain('First Profile');
expect(helmet.prop('title')).toContain(name);
});

+ 14
- 14
server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ProfileActions-test.tsx.snap Переглянути файл

@@ -10,7 +10,7 @@ exports[`renders with all permissions 1`] = `
"pathname": "/organizations/org/rules",
"query": Object {
"activation": "false",
"qprofile": "foo",
"qprofile": "key",
},
}
}
@@ -18,9 +18,9 @@ exports[`renders with all permissions 1`] = `
quality_profiles.activate_more_rules
</ActionsDropdownItem>
<ActionsDropdownItem
download="foo.xml"
download="key.xml"
id="quality-profile-backup"
to="/api/qualityprofiles/backup?profileKey=foo"
to="/api/qualityprofiles/backup?profileKey=key"
>
backup_verb
</ActionsDropdownItem>
@@ -30,8 +30,8 @@ exports[`renders with all permissions 1`] = `
Object {
"pathname": "/organizations/org/quality_profiles/compare",
"query": Object {
"language": "java",
"name": "Foo",
"language": "js",
"name": "name",
},
}
}
@@ -72,9 +72,9 @@ exports[`renders with no permissions 1`] = `
<Fragment>
<ActionsDropdown>
<ActionsDropdownItem
download="foo.xml"
download="key.xml"
id="quality-profile-backup"
to="/api/qualityprofiles/backup?profileKey=foo"
to="/api/qualityprofiles/backup?profileKey=key"
>
backup_verb
</ActionsDropdownItem>
@@ -84,8 +84,8 @@ exports[`renders with no permissions 1`] = `
Object {
"pathname": "/organizations/org/quality_profiles/compare",
"query": Object {
"language": "java",
"name": "Foo",
"language": "js",
"name": "name",
},
}
}
@@ -106,7 +106,7 @@ exports[`renders with permission to edit only 1`] = `
"pathname": "/organizations/org/rules",
"query": Object {
"activation": "false",
"qprofile": "foo",
"qprofile": "key",
},
}
}
@@ -114,9 +114,9 @@ exports[`renders with permission to edit only 1`] = `
quality_profiles.activate_more_rules
</ActionsDropdownItem>
<ActionsDropdownItem
download="foo.xml"
download="key.xml"
id="quality-profile-backup"
to="/api/qualityprofiles/backup?profileKey=foo"
to="/api/qualityprofiles/backup?profileKey=key"
>
backup_verb
</ActionsDropdownItem>
@@ -126,8 +126,8 @@ exports[`renders with permission to edit only 1`] = `
Object {
"pathname": "/organizations/org/quality_profiles/compare",
"query": Object {
"language": "java",
"name": "Foo",
"language": "js",
"name": "name",
},
}
}

+ 4
- 10
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx Переглянути файл

@@ -23,22 +23,16 @@ import ProfileRules from '../ProfileRules';
import * as apiRules from '../../../../api/rules';
import * as apiQP from '../../../../api/quality-profiles';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { mockQualityProfile } from '../../testUtils';

const PROFILE = {
const PROFILE = mockQualityProfile({
activeRuleCount: 68,
activeDeprecatedRuleCount: 0,
childrenCount: 0,
depth: 0,
isBuiltIn: false,
isDefault: false,
isInherited: false,
key: 'foo',
language: 'java',
languageName: 'Java',
name: 'Foo',
language: 'js',
organization: 'org',
rulesUpdatedAt: '2017-06-28T12:58:44+0000'
};
});

const EDITABLE_PROFILE = { ...PROFILE, actions: { edit: true } };


+ 9
- 9
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.tsx.snap Переглянути файл

@@ -29,14 +29,14 @@ exports[`should render the quality profiles rules with sonarway comparison 1`] =
<ProfileRulesRowTotal
count={68}
organization="foo"
qprofile="foo"
qprofile="key"
total={253}
/>
<ProfileRulesRowOfType
count={68}
key="BUG"
organization="foo"
qprofile="foo"
qprofile="key"
total={68}
type="BUG"
/>
@@ -44,7 +44,7 @@ exports[`should render the quality profiles rules with sonarway comparison 1`] =
count={0}
key="VULNERABILITY"
organization="foo"
qprofile="foo"
qprofile="key"
total={7}
type="VULNERABILITY"
/>
@@ -52,7 +52,7 @@ exports[`should render the quality profiles rules with sonarway comparison 1`] =
count={0}
key="CODE_SMELL"
organization="foo"
qprofile="foo"
qprofile="key"
total={168}
type="CODE_SMELL"
/>
@@ -60,7 +60,7 @@ exports[`should render the quality profiles rules with sonarway comparison 1`] =
count={0}
key="SECURITY_HOTSPOT"
organization="foo"
qprofile="foo"
qprofile="key"
total={10}
type="SECURITY_HOTSPOT"
/>
@@ -68,9 +68,9 @@ exports[`should render the quality profiles rules with sonarway comparison 1`] =
</table>
</div>
<ProfileRulesSonarWayComparison
language="java"
language="js"
organization="foo"
profile="foo"
profile="key"
sonarWayMissingRules={4}
sonarway="sonarway"
/>
@@ -87,7 +87,7 @@ exports[`should show a button to activate more rules for admins 1`] = `
"pathname": "/organizations/foo/rules",
"query": Object {
"activation": "false",
"qprofile": "foo",
"qprofile": "key",
},
}
}
@@ -100,7 +100,7 @@ exports[`should show a deprecated rules warning message 1`] = `
<ProfileRulesDeprecatedWarning
activeDeprecatedRules={8}
organization="foo"
profile="foo"
profile="key"
/>
`;


+ 116
- 46
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.tsx Переглянути файл

@@ -30,55 +30,125 @@ interface Props {
profiles: Profile[];
}

export default function EvolutionDeprecated(props: Props) {
const profilesWithDeprecations = props.profiles.filter(
profile => profile.activeDeprecatedRuleCount > 0
);
interface InheritedRulesInfo {
count: number;
from: Profile;
}

if (profilesWithDeprecations.length === 0) {
return null;
}
export default class EvolutionDeprecated extends React.PureComponent<Props> {
getDeprecatedRulesInheritanceChain = (profile: Profile, profilesWithDeprecations: Profile[]) => {
let rules: InheritedRulesInfo[] = [];
let count = profile.activeDeprecatedRuleCount;

const sortedProfiles = sortBy(profilesWithDeprecations, p => -p.activeDeprecatedRuleCount);
if (count === 0) {
return rules;
}

return (
<div className="boxed-group boxed-group-inner quality-profiles-evolution-deprecated">
<div className="spacer-bottom">
<strong>{translate('quality_profiles.deprecated_rules')}</strong>
</div>
<div className="spacer-bottom">
{translateWithParameters(
'quality_profiles.deprecated_rules_are_still_activated',
profilesWithDeprecations.length
)}
</div>
<ul>
{sortedProfiles.map(profile => (
<li className="spacer-top" key={profile.key}>
<div className="text-ellipsis">
<ProfileLink
className="link-no-underline"
language={profile.language}
name={profile.name}
organization={props.organization}>
{profile.name}
</ProfileLink>
</div>
<div className="note">
{profile.languageName}
{', '}
<Link
className="text-muted"
to={getDeprecatedActiveRulesUrl({ qprofile: profile.key }, props.organization)}>
if (profile.parentKey) {
const parentProfile = profilesWithDeprecations.find(p => p.key === profile.parentKey);
if (parentProfile) {
const parentRules = this.getDeprecatedRulesInheritanceChain(
parentProfile,
profilesWithDeprecations
);
if (parentRules.length) {
count -= parentRules.reduce((n, rule) => n + rule.count, 0);
rules = rules.concat(parentRules);
}
}
}

if (count > 0) {
rules.push({
count,
from: profile
});
}

return rules;
};

renderInheritedInfo = (profile: Profile, profilesWithDeprecations: Profile[]) => {
const rules = this.getDeprecatedRulesInheritanceChain(profile, profilesWithDeprecations);
if (rules.length) {
return (
<>
{rules.map(rule => {
if (rule.from.key === profile.key) {
return null;
}

return (
<div className="muted" key={rule.from.key}>
{' '}
{translateWithParameters(
'quality_profile.x_rules',
profile.activeDeprecatedRuleCount
'coding_rules.filters.inheritance.x_inherited_from_y',
rule.count,
rule.from.name
)}
</Link>
</div>
</li>
))}
</ul>
</div>
);
</div>
);
})}
</>
);
}
return null;
};

render() {
const profilesWithDeprecations = this.props.profiles.filter(
profile => profile.activeDeprecatedRuleCount > 0
);

if (profilesWithDeprecations.length === 0) {
return null;
}

const sortedProfiles = sortBy(profilesWithDeprecations, p => -p.activeDeprecatedRuleCount);

return (
<div className="boxed-group boxed-group-inner quality-profiles-evolution-deprecated">
<div className="spacer-bottom">
<strong>{translate('quality_profiles.deprecated_rules')}</strong>
</div>
<div className="spacer-bottom">
{translateWithParameters(
'quality_profiles.deprecated_rules_are_still_activated',
profilesWithDeprecations.length
)}
</div>
<ul>
{sortedProfiles.map(profile => (
<li className="spacer-top" key={profile.key}>
<div className="text-ellipsis">
<ProfileLink
className="link-no-underline"
language={profile.language}
name={profile.name}
organization={this.props.organization}>
{profile.name}
</ProfileLink>
</div>
<div className="note">
{profile.languageName}
{', '}
<Link
className="text-muted"
to={getDeprecatedActiveRulesUrl(
{ qprofile: profile.key },
this.props.organization
)}>
{translateWithParameters(
'quality_profile.x_rules',
profile.activeDeprecatedRuleCount
)}
</Link>
{this.renderInheritedInfo(profile, profilesWithDeprecations)}
</div>
</li>
))}
</ul>
</div>
);
}
}

+ 66
- 0
server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/EvolutionDeprecated-test.tsx Переглянути файл

@@ -0,0 +1,66 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 * as React from 'react';
import { shallow } from 'enzyme';
import EvolutionDeprecated from '../EvolutionDeprecated';
import { mockQualityProfile } from '../../testUtils';

it('should render correctly', () => {
const wrapper = shallow(
<EvolutionDeprecated
organization="foo"
profiles={[
mockQualityProfile({
key: 'qp-1',
name: 'Quality Profile 1',
activeDeprecatedRuleCount: 0
}),
mockQualityProfile({
key: 'qp-2',
name: 'Quality Profile 2',
childrenCount: 1,
activeDeprecatedRuleCount: 2
}),
mockQualityProfile({
key: 'qp-3',
name: 'Quality Profile 3',
depth: 2,
activeDeprecatedRuleCount: 2,
parentKey: 'qp-2'
}),
mockQualityProfile({
key: 'qp-4',
name: 'Quality Profile 4',
depth: 3,
activeDeprecatedRuleCount: 3,
parentKey: 'qp-3'
}),
mockQualityProfile({
key: 'qp-5',
name: 'Quality Profile 5',
depth: 4,
activeDeprecatedRuleCount: 4,
parentKey: 'qp-4'
})
]}
/>
);
expect(wrapper).toMatchSnapshot();
});

+ 210
- 0
server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/EvolutionDeprecated-test.tsx.snap Переглянути файл

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

exports[`should render correctly 1`] = `
<div
className="boxed-group boxed-group-inner quality-profiles-evolution-deprecated"
>
<div
className="spacer-bottom"
>
<strong>
quality_profiles.deprecated_rules
</strong>
</div>
<div
className="spacer-bottom"
>
quality_profiles.deprecated_rules_are_still_activated.4
</div>
<ul>
<li
className="spacer-top"
key="qp-5"
>
<div
className="text-ellipsis"
>
<ProfileLink
className="link-no-underline"
language="js"
name="Quality Profile 5"
organization="foo"
>
Quality Profile 5
</ProfileLink>
</div>
<div
className="note"
>
JavaScript
,
<Link
className="text-muted"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/organizations/foo/rules",
"query": Object {
"activation": "true",
"qprofile": "qp-5",
"statuses": "DEPRECATED",
},
}
}
>
quality_profile.x_rules.4
</Link>
<div
className="muted"
key="qp-2"
>
coding_rules.filters.inheritance.x_inherited_from_y.2.Quality Profile 2
</div>
<div
className="muted"
key="qp-4"
>
coding_rules.filters.inheritance.x_inherited_from_y.1.Quality Profile 4
</div>
</div>
</li>
<li
className="spacer-top"
key="qp-4"
>
<div
className="text-ellipsis"
>
<ProfileLink
className="link-no-underline"
language="js"
name="Quality Profile 4"
organization="foo"
>
Quality Profile 4
</ProfileLink>
</div>
<div
className="note"
>
JavaScript
,
<Link
className="text-muted"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/organizations/foo/rules",
"query": Object {
"activation": "true",
"qprofile": "qp-4",
"statuses": "DEPRECATED",
},
}
}
>
quality_profile.x_rules.3
</Link>
<div
className="muted"
key="qp-2"
>
coding_rules.filters.inheritance.x_inherited_from_y.2.Quality Profile 2
</div>
</div>
</li>
<li
className="spacer-top"
key="qp-2"
>
<div
className="text-ellipsis"
>
<ProfileLink
className="link-no-underline"
language="js"
name="Quality Profile 2"
organization="foo"
>
Quality Profile 2
</ProfileLink>
</div>
<div
className="note"
>
JavaScript
,
<Link
className="text-muted"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/organizations/foo/rules",
"query": Object {
"activation": "true",
"qprofile": "qp-2",
"statuses": "DEPRECATED",
},
}
}
>
quality_profile.x_rules.2
</Link>
</div>
</li>
<li
className="spacer-top"
key="qp-3"
>
<div
className="text-ellipsis"
>
<ProfileLink
className="link-no-underline"
language="js"
name="Quality Profile 3"
organization="foo"
>
Quality Profile 3
</ProfileLink>
</div>
<div
className="note"
>
JavaScript
,
<Link
className="text-muted"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/organizations/foo/rules",
"query": Object {
"activation": "true",
"qprofile": "qp-3",
"statuses": "DEPRECATED",
},
}
}
>
quality_profile.x_rules.2
</Link>
<div
className="muted"
key="qp-2"
>
coding_rules.filters.inheritance.x_inherited_from_y.2.Quality Profile 2
</div>
</div>
</li>
</ul>
</div>
`;

+ 39
- 0
server/sonar-web/src/main/js/apps/quality-profiles/testUtils.ts Переглянути файл

@@ -0,0 +1,39 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 { Profile } from './types';

export function mockQualityProfile(overrides = {}): Profile {
return {
activeDeprecatedRuleCount: 2,
activeRuleCount: 10,
childrenCount: 0,
depth: 1,
isBuiltIn: false,
isDefault: false,
isInherited: false,
key: 'key',
language: 'js',
languageName: 'JavaScript',
name: 'name',
projectCount: 3,
organization: 'foo',
...overrides
};
}

+ 0
- 15
server/sonar-web/src/main/js/apps/quality-profiles/utils.ts Переглянути файл

@@ -51,21 +51,6 @@ export function sortProfiles(profiles: BaseProfile[]): Profile[] {
return result;
}

export function createFakeProfile(overrides?: any) {
return {
key: 'key',
name: 'name',
isDefault: false,
isInherited: false,
language: 'js',
languageName: 'JavaScript',
activeRuleCount: 10,
activeDeprecatedRuleCount: 2,
projectCount: 3,
...overrides
};
}

export function isStagnant(profile: Profile): boolean {
if (profile.rulesUpdatedAt) {
const updateDate = parseDate(profile.rulesUpdatedAt);

+ 8
- 11
server/sonar-web/src/main/js/helpers/testUtils.ts Переглянути файл

@@ -152,7 +152,14 @@ export function mockComponent(overrides = {}): T.Component {
organization: 'foo',
qualifier: 'TRK',
qualityGate: { isDefault: true, key: '30', name: 'Sonar way' },
qualityProfiles: [mockQualityProfile()],
qualityProfiles: [
{
deleted: false,
key: 'my-qp',
language: 'ts',
name: 'Sonar way'
}
],
tags: [],
...overrides
};
@@ -172,13 +179,3 @@ export function mockOrganization(overrides = {}): T.Organization {
...overrides
};
}

export function mockQualityProfile(overrides = {}): T.ComponentQualityProfile {
return {
deleted: false,
key: 'my-qp',
language: 'ts',
name: 'Sonar way',
...overrides
};
}

+ 1
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Переглянути файл

@@ -1361,6 +1361,7 @@ coding_rules.filters.inheritance=Inheritance
coding_rules.filters.inheritance.inactive=Inheritance criterion is available when an inherited quality profile is selected
coding_rules.filters.inheritance.none=Not Inherited
coding_rules.filters.inheritance.inherited=Inherited
coding_rules.filters.inheritance.x_inherited_from_y={0} inherited from "{1}"
coding_rules.filters.inheritance.overrides=Overridden
coding_rules.filters.key=Key
coding_rules.filters.language=Language

Завантаження…
Відмінити
Зберегти