diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-06-29 11:46:24 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@sonarsource.com> | 2017-07-04 16:29:36 +0200 |
commit | 06ee234f17c62f3296deb212e9b1ab20d812db42 (patch) | |
tree | 4c4b5a166d9dfc3b727247f227cd24738ce68375 /server/sonar-web | |
parent | c4355a61e7b28be2f78a75f724733fca86bcfe07 (diff) | |
download | sonarqube-06ee234f17c62f3296deb212e9b1ab20d812db42.tar.gz sonarqube-06ee234f17c62f3296deb212e9b1ab20d812db42.zip |
SONAR-9482 Display the number of missing Sonar Way rules on the QP page
Diffstat (limited to 'server/sonar-web')
16 files changed, 1012 insertions, 222 deletions
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.js index a179e441d55..d9bb55dfefd 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.js @@ -21,12 +21,14 @@ import React from 'react'; import { Link } from 'react-router'; import { keyBy } from 'lodash'; -import ProfileRulesRow from './ProfileRulesRow'; +import ProfileRulesRowOfType from './ProfileRulesRowOfType'; +import ProfileRulesRowTotal from './ProfileRulesRowTotal'; +import ProfileRulesDeprecatedWarning from './ProfileRulesDeprecatedWarning'; +import ProfileRulesSonarWayComparison from './ProfileRulesSonarWayComparison'; import { searchRules, takeFacet } from '../../../api/rules'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { formatMeasure } from '../../../helpers/measures'; -import { getRulesUrl, getDeprecatedActiveRulesUrl } from '../../../helpers/urls'; -import IssueTypeIcon from '../../../components/ui/IssueTypeIcon'; +import { getQualityProfiles } from '../../../api/quality-profiles'; +import { getRulesUrl } from '../../../helpers/urls'; +import { translate } from '../../../helpers/l10n'; import type { Profile } from '../propTypes'; const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL']; @@ -38,20 +40,24 @@ type Props = { }; type State = { - total: ?number, activatedTotal: ?number, + activatedByType?: { [string]: ?{ val: string, count: ?number } }, allByType?: { [string]: ?{ val: string, count: ?number } }, - activatedByType?: { [string]: ?{ val: string, count: ?number } } + compareToSonarWay: ?{ profile: string, profileName: string, missingRuleCount: number }, + loading: boolean, + total: ?number }; export default class ProfileRules extends React.PureComponent { mounted: boolean; props: Props; state: State = { - total: null, activatedTotal: null, + activatedByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val'), allByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val'), - activatedByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val') + compareToSonarWay: null, + loading: true, + total: null }; componentDidMount() { @@ -69,6 +75,16 @@ export default class ProfileRules extends React.PureComponent { this.mounted = false; } + loadProfile() { + if (this.props.profile.isBuiltIn) { + return Promise.resolve(null); + } + return getQualityProfiles({ + compareToSonarWay: true, + profile: this.props.profile.key + }); + } + loadAllRules() { return searchRules({ languages: this.props.profile.language, @@ -87,197 +103,44 @@ export default class ProfileRules extends React.PureComponent { } loadRules() { - Promise.all([this.loadAllRules(), this.loadActivatedRules()]).then(responses => { + this.setState({ loading: true }); + Promise.all([ + this.loadAllRules(), + this.loadActivatedRules(), + this.loadProfile() + ]).then(responses => { if (this.mounted) { - const [allRules, activatedRules] = responses; + const [allRules, activatedRules, showProfile] = responses; this.setState({ - total: allRules.total, activatedTotal: activatedRules.total, allByType: keyBy(takeFacet(allRules, 'types'), 'val'), - activatedByType: keyBy(takeFacet(activatedRules, 'types'), 'val') + activatedByType: keyBy(takeFacet(activatedRules, 'types'), 'val'), + compareToSonarWay: showProfile && showProfile.compareToSonarWay, + loading: false, + total: allRules.total }); } }); } - getTooltip(count: ?number, total: ?number) { - if (count == null || total == null) { - return ''; - } - - return translateWithParameters( - 'quality_profiles.x_activated_out_of_y', - formatMeasure(count, 'INT'), - formatMeasure(total, 'INT') - ); - } - - getTooltipForType(type: string) { - if ( - this.state.activatedByType && - this.state.activatedByType[type] && - this.state.allByType && - this.state.allByType[type] - ) { - const { count } = this.state.activatedByType[type]; - const total = this.state.allByType[type].count; - return this.getTooltip(count, total); - } - } - - renderActiveTitle() { - return ( - <strong> - {translate('total')} - </strong> - ); - } - - renderActiveCount() { - const rulesUrl = getRulesUrl( - { - qprofile: this.props.profile.key, - activation: 'true' - }, - this.props.organization - ); - - if (this.state.activatedTotal == null) { - return null; - } - - return ( - <Link to={rulesUrl}> - <strong> - {formatMeasure(this.state.activatedTotal, 'SHORT_INT')} - </strong> - </Link> - ); - } - - renderActiveTotal() { - const rulesUrl = getRulesUrl( - { - qprofile: this.props.profile.key, - activation: 'false' - }, - this.props.organization - ); - - if (this.state.total == null || this.state.activatedTotal == null) { - return null; - } - - if (this.state.total === this.state.activatedTotal) { - return <span className="note text-muted">0</span>; - } - - return ( - <Link to={rulesUrl} className="small text-muted"> - <strong> - {formatMeasure(this.state.total - this.state.activatedTotal, 'SHORT_INT')} - </strong> - </Link> - ); - } - - renderTitleForType(type: string) { - return ( - <span> - <IssueTypeIcon query={type} className="little-spacer-right" /> - {translate('issue.type', type, 'plural')} - </span> - ); - } - - renderCountForType(type: string) { - const rulesUrl = getRulesUrl( - { - qprofile: this.props.profile.key, - activation: 'true', - types: type - }, - this.props.organization - ); - - const count = this.state.activatedByType && this.state.activatedByType[type] + getRulesCountForType(type: string): ?number { + return this.state.activatedByType && this.state.activatedByType[type] ? this.state.activatedByType[type].count : null; - - if (count == null) { - return null; - } - - return ( - <Link to={rulesUrl}> - {formatMeasure(count, 'SHORT_INT')} - </Link> - ); } - renderTotalForType(type: string) { - const rulesUrl = getRulesUrl( - { - qprofile: this.props.profile.key, - activation: 'false', - types: type - }, - this.props.organization - ); - - const count = this.state.activatedByType && this.state.activatedByType[type] - ? this.state.activatedByType[type].count - : null; - - const total = this.state.allByType && this.state.allByType[type] + getRulesTotalForType(type: string): ?number { + return this.state.allByType && this.state.allByType[type] ? this.state.allByType[type].count : null; - - if (count == null || total == null) { - return null; - } - - if (total === count) { - return <span className="note text-muted">0</span>; - } - - return ( - <Link to={rulesUrl} className="small text-muted"> - {formatMeasure(total - count, 'SHORT_INT')} - </Link> - ); - } - - renderDeprecated() { - const { profile } = this.props; - - if (profile.activeDeprecatedRuleCount === 0) { - return null; - } - - const url = getDeprecatedActiveRulesUrl({ qprofile: profile.key }, this.props.organization); - - return ( - <div className="quality-profile-rules-deprecated clearfix"> - <div className="pull-left"> - {translate('quality_profiles.deprecated_rules')} - </div> - <div className="pull-right"> - <Link to={url}> - {profile.activeDeprecatedRuleCount} - </Link> - </div> - </div> - ); } render() { + const { organization, profile } = this.props; + const { compareToSonarWay } = this.state; const activateMoreUrl = getRulesUrl( - { - qprofile: this.props.profile.key, - activation: 'false' - }, - this.props.organization + { qprofile: profile.key, activation: 'false' }, + organization ); return ( @@ -289,38 +152,53 @@ export default class ProfileRules extends React.PureComponent { <th> <h2>{translate('rules')}</h2> </th> - <th>Active</th> - <th>Inactive</th> + <th>{translate('active')}</th> + <th>{translate('inactive')}</th> </tr> </thead> <tbody> - <ProfileRulesRow + <ProfileRulesRowTotal key="all" - renderTitle={this.renderActiveTitle.bind(this)} - renderCount={this.renderActiveCount.bind(this)} - renderTotal={this.renderActiveTotal.bind(this)} + count={this.state.activatedTotal} + organization={organization} + qprofile={profile.key} + total={this.state.total} /> {TYPES.map(type => ( - <ProfileRulesRow + <ProfileRulesRowOfType key={type} - renderTitle={this.renderTitleForType.bind(this, type)} - renderCount={this.renderCountForType.bind(this, type)} - renderTotal={this.renderTotalForType.bind(this, type)} + count={this.getRulesCountForType(type)} + organization={organization} + qprofile={profile.key} + total={this.getRulesTotalForType(type)} + type={type} /> ))} </tbody> </table> {this.props.canAdmin && - !this.props.profile.isBuiltIn && + !profile.isBuiltIn && <div className="text-right big-spacer-top"> <Link to={activateMoreUrl} className="button js-activate-rules"> {translate('quality_profiles.activate_more')} </Link> </div>} </div> - - {this.renderDeprecated()} + {profile.activeDeprecatedRuleCount > 0 && + <ProfileRulesDeprecatedWarning + activeDeprecatedRules={profile.activeDeprecatedRuleCount} + organization={organization} + profile={profile.key} + />} + {compareToSonarWay != null && + compareToSonarWay.missingRuleCount > 0 && + <ProfileRulesSonarWayComparison + organization={organization} + profile={profile.key} + sonarway={compareToSonarWay.profile} + sonarWayMissingRules={compareToSonarWay.missingRuleCount} + />} </div> ); } diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.js new file mode 100644 index 00000000000..d50c81c227b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.js @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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. + */ +// @flow +import React from 'react'; +import { Link } from 'react-router'; +import Tooltip from '../../../components/controls/Tooltip'; +import { getDeprecatedActiveRulesUrl } from '../../../helpers/urls'; +import { translate } from '../../../helpers/l10n'; + +type Props = { activeDeprecatedRules: number, organization: ?string, profile: string }; + +export default function ProfileRulesDeprecatedWarning(props: Props) { + const url = getDeprecatedActiveRulesUrl({ qprofile: props.profile }, props.organization); + return ( + <div className="quality-profile-rules-deprecated clearfix"> + <span className="pull-left"> + {translate('quality_profiles.deprecated_rules')} + <Tooltip overlay={translate('quality_profiles.deprecated_rules_description')}> + <i className="icon-help spacer-left" /> + </Tooltip> + </span> + <Link className="pull-right" to={url}> + {props.activeDeprecatedRules} + </Link> + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.js new file mode 100644 index 00000000000..e6e04e59627 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.js @@ -0,0 +1,74 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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. + */ +// @flow +import React from 'react'; +import { Link } from 'react-router'; +import IssueTypeIcon from '../../../components/ui/IssueTypeIcon'; +import { formatMeasure } from '../../../helpers/measures'; +import { getRulesUrl } from '../../../helpers/urls'; +import { translate } from '../../../helpers/l10n'; + +type Props = { + count: ?number, + organization: ?string, + qprofile: string, + total: ?number, + type: string +}; + +export default function ProfileRulesRowOfType(props: Props) { + const activeRulesUrl = getRulesUrl( + { qprofile: props.qprofile, activation: 'true', types: props.type }, + props.organization + ); + const inactiveRulesUrl = getRulesUrl( + { qprofile: props.qprofile, activation: 'false', types: props.type }, + props.organization + ); + let inactiveCount = null; + if (props.count != null && props.total != null) { + inactiveCount = props.total - props.count; + } + + return ( + <tr> + <td> + <span> + <IssueTypeIcon query={props.type} className="little-spacer-right" /> + {translate('issue.type', props.type, 'plural')} + </span> + </td> + <td className="thin nowrap text-right"> + {props.count != null && + <Link to={activeRulesUrl}> + {formatMeasure(props.count, 'SHORT_INT')} + </Link>} + </td> + <td className="thin nowrap text-right"> + {inactiveCount != null && + (inactiveCount > 0 + ? <Link to={inactiveRulesUrl} className="small text-muted"> + {formatMeasure(inactiveCount, 'SHORT_INT')} + </Link> + : <span className="note text-muted">0</span>)} + </td> + </tr> + ); +} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.js new file mode 100644 index 00000000000..f17bc2a8c86 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.js @@ -0,0 +1,75 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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. + */ +// @flow +import React from 'react'; +import { Link } from 'react-router'; +import { formatMeasure } from '../../../helpers/measures'; +import { getRulesUrl } from '../../../helpers/urls'; +import { translate } from '../../../helpers/l10n'; + +type Props = { + count: ?number, + organization: ?string, + qprofile: string, + total: ?number +}; + +export default function ProfileRulesRowTotal(props: Props) { + const activeRulesUrl = getRulesUrl( + { qprofile: props.qprofile, activation: 'true' }, + props.organization + ); + const inactiveRulesUrl = getRulesUrl( + { qprofile: props.qprofile, activation: 'false' }, + props.organization + ); + let inactiveCount = null; + if (props.count != null && props.total != null) { + inactiveCount = props.total - props.count; + } + + return ( + <tr> + <td> + <strong> + {translate('total')} + </strong> + </td> + <td className="thin nowrap text-right"> + {props.count != null && + <Link to={activeRulesUrl}> + <strong> + {formatMeasure(props.count, 'SHORT_INT')} + </strong> + </Link>} + </td> + <td className="thin nowrap text-right"> + {inactiveCount != null && + (inactiveCount > 0 + ? <Link to={inactiveRulesUrl} className="small text-muted"> + <strong> + {formatMeasure(inactiveCount, 'SHORT_INT')} + </strong> + </Link> + : <span className="note text-muted">0</span>)} + </td> + </tr> + ); +} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.js new file mode 100644 index 00000000000..97427ee1d34 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.js @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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. + */ +// @flow +import React from 'react'; +import { Link } from 'react-router'; +import Tooltip from '../../../components/controls/Tooltip'; +import { translate } from '../../../helpers/l10n'; + +type Props = { + organization: ?string, + profile: string, + sonarway: string, + sonarWayMissingRules: number +}; + +export default function ProfileRulesSonarWayComparison(props: Props) { + return ( + <div className="quality-profile-rules-sonarway-missing clearfix"> + <span className="pull-left"> + {translate('quality_profiles.sonarway_missing_rules')} + <Tooltip overlay={translate('quality_profiles.sonarway_missing_rules_description')}> + <i className="icon-help spacer-left" /> + </Tooltip> + </span> + <Link className="pull-right"> + {props.sonarWayMissingRules} + </Link> + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.js new file mode 100644 index 00000000000..cd62b6a21d6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.js @@ -0,0 +1,159 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import { shallow } from 'enzyme'; +import ProfileRules from '../ProfileRules'; +import { doAsync } from '../../../../helpers/testUtils'; +import * as apiRules from '../../../../api/rules'; +import * as apiQP from '../../../../api/quality-profiles'; + +const PROFILE = { + key: 'foo', + name: 'Foo', + isBuiltIn: false, + isDefault: false, + isInherited: false, + language: 'java', + languageName: 'Java', + activeRuleCount: 68, + activeDeprecatedRuleCount: 0, + rulesUpdatedAt: '2017-06-28T12:58:44+0000', + depth: 0, + childrenCount: 0 +}; + +const apiResponseAll = { + total: 243, + facets: [ + { + property: 'types', + values: [ + { val: 'CODE_SMELL', count: 168 }, + { val: 'BUG', count: 68 }, + { val: 'VULNERABILITY', count: 7 } + ] + } + ] +}; + +const apiResponseActive = { + total: 68, + facets: [ + { + property: 'types', + values: [ + { val: 'BUG', count: 68 }, + { val: 'CODE_SMELL', count: 0 }, + { val: 'VULNERABILITY', count: 0 } + ] + } + ] +}; + +// Mock api some api functions +// eslint-disable-next-line +apiRules.searchRules = data => + Promise.resolve(data.activation === 'true' ? apiResponseActive : apiResponseAll); +// eslint-disable-next-line +apiQP.getQualityProfiles = () => + Promise.resolve({ + compareToSonarWay: { + profile: 'sonarway', + profileName: 'Sonar way', + missingRuleCount: 4 + } + }); + +it('should render the quality profiles rules with sonarway comparison', () => { + const wrapper = shallow(<ProfileRules canAdmin={false} organization="foo" profile={PROFILE} />); + wrapper.instance().mounted = true; + wrapper.instance().loadRules(); + return doAsync(() => { + wrapper.update(); + expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(1); + expect(wrapper).toMatchSnapshot(); + }); +}); + +it('should show a button to activate more rules for admins', () => { + const wrapper = shallow(<ProfileRules canAdmin={true} organization="foo" profile={PROFILE} />); + expect(wrapper.find('.js-activate-rules')).toMatchSnapshot(); +}); + +it('should show a deprecated rules warning message', () => { + const wrapper = shallow( + <ProfileRules + canAdmin={true} + organization="foo" + profile={{ ...PROFILE, activeDeprecatedRuleCount: 8 }} + /> + ); + expect(wrapper.find('ProfileRulesDeprecatedWarning')).toMatchSnapshot(); +}); + +it('should not show a button to activate more rules on built in profiles', () => { + const wrapper = shallow( + <ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} /> + ); + expect(wrapper.find('.js-activate-rules')).toHaveLength(0); +}); + +it('should not show a button to activate more rules on built in profiles', () => { + const wrapper = shallow( + <ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} /> + ); + expect(wrapper.find('.js-activate-rules')).toHaveLength(0); +}); + +it('should not show sonarway comparison for built in profiles', () => { + // eslint-disable-next-line + apiQP.getQualityProfiles = jest.fn(() => Promise.resolve()); + const wrapper = shallow( + <ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} /> + ); + wrapper.instance().mounted = true; + wrapper.instance().loadRules(); + return doAsync(() => { + wrapper.update(); + expect(apiQP.getQualityProfiles).toHaveBeenCalledTimes(0); + expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(0); + }); +}); + +it('should not show sonarway comparison if there is no missing rules', () => { + // eslint-disable-next-line + apiQP.getQualityProfiles = jest.fn(() => + Promise.resolve({ + compareToSonarWay: { + profile: 'sonarway', + profileName: 'Sonar way', + missingRuleCount: 0 + } + }) + ); + const wrapper = shallow(<ProfileRules canAdmin={true} organization={null} profile={PROFILE} />); + wrapper.instance().mounted = true; + wrapper.instance().loadRules(); + return doAsync(() => { + wrapper.update(); + expect(apiQP.getQualityProfiles).toHaveBeenCalledTimes(1); + expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(0); + }); +}); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesDeprecatedWarning-test.js index 9a7d140fa14..9a3387280e9 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesDeprecatedWarning-test.js @@ -19,31 +19,13 @@ */ // @flow import React from 'react'; +import { shallow } from 'enzyme'; +import ProfileRulesDeprecatedWarning from '../ProfileRulesDeprecatedWarning'; -type Props = { - renderCount: () => ?React.Element<*>, - renderTitle: () => React.Element<*>, - renderTotal: () => ?React.Element<*> -}; - -export default class ProfileRulesRow extends React.PureComponent { - props: Props; - - render() { - const { renderTitle, renderCount, renderTotal } = this.props; - - return ( - <tr> - <td> - {renderTitle()} - </td> - <td className="thin nowrap text-right"> - {renderCount()} - </td> - <td className="thin nowrap text-right"> - {renderTotal()} - </td> - </tr> - ); - } -} +it('should render correctly', () => { + expect( + shallow( + <ProfileRulesDeprecatedWarning activeDeprecatedRules={18} organization="foo" profile="bar" /> + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowOfType-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowOfType-test.js new file mode 100644 index 00000000000..dea445e01bf --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowOfType-test.js @@ -0,0 +1,70 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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. + */ +// @flow +import React from 'react'; +import { shallow } from 'enzyme'; +import ProfileRulesRowOfType from '../ProfileRulesRowOfType'; + +it('should render correctly', () => { + expect( + shallow( + <ProfileRulesRowOfType count={3} organization="foo" qprofile="bar" total={10} type="BUG" /> + ) + ).toMatchSnapshot(); +}); + +it('should render correctly if there is 0 rules', () => { + expect( + shallow( + <ProfileRulesRowOfType + count={0} + organization={null} + qprofile="bar" + total={0} + type="VULNERABILITY" + /> + ) + ).toMatchSnapshot(); +}); + +it('should render correctly if there is missing data', () => { + expect( + shallow( + <ProfileRulesRowOfType + count={5} + organization={null} + qprofile="bar" + total={null} + type="VULNERABILITY" + /> + ) + ).toMatchSnapshot(); + expect( + shallow( + <ProfileRulesRowOfType + count={null} + organization={null} + qprofile="foo" + total={10} + type="VULNERABILITY" + /> + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowTotal-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowTotal-test.js new file mode 100644 index 00000000000..20f2d697d5f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowTotal-test.js @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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. + */ +// @flow +import React from 'react'; +import { shallow } from 'enzyme'; +import ProfileRulesRowTotal from '../ProfileRulesRowTotal'; + +it('should render correctly', () => { + expect( + shallow(<ProfileRulesRowTotal count={3} organization="foo" qprofile="bar" total={10} />) + ).toMatchSnapshot(); +}); + +it('should render correctly if there is 0 rules', () => { + expect( + shallow(<ProfileRulesRowTotal count={0} organization={null} qprofile="bar" total={0} />) + ).toMatchSnapshot(); +}); + +it('should render correctly if there is missing data', () => { + expect( + shallow(<ProfileRulesRowTotal count={5} organization={null} qprofile="bar" total={null} />) + ).toMatchSnapshot(); + expect( + shallow(<ProfileRulesRowTotal count={null} organization={null} qprofile="foo" total={10} />) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesSonarWayComparison-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesSonarWayComparison-test.js new file mode 100644 index 00000000000..35c0548ccb5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesSonarWayComparison-test.js @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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. + */ +// @flow +import React from 'react'; +import { shallow } from 'enzyme'; +import ProfileRulesSonarWayComparison from '../ProfileRulesSonarWayComparison'; + +it('should render correctly', () => { + expect( + shallow( + <ProfileRulesSonarWayComparison + organization="foo" + profile="bar" + sonarway="baz" + sonarWayMissingRules={158} + /> + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.js.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.js.snap new file mode 100644 index 00000000000..ea4e92251cb --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.js.snap @@ -0,0 +1,85 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render the quality profiles rules with sonarway comparison 1`] = ` +<div + className="quality-profile-rules" +> + <div + className="quality-profile-rules-distribution" + > + <table + className="data condensed" + > + <thead> + <tr> + <th> + <h2> + rules + </h2> + </th> + <th> + active + </th> + <th> + inactive + </th> + </tr> + </thead> + <tbody> + <ProfileRulesRowTotal + count={68} + organization="foo" + qprofile="foo" + total={243} + /> + <ProfileRulesRowOfType + count={68} + organization="foo" + qprofile="foo" + total={68} + type="BUG" + /> + <ProfileRulesRowOfType + count={0} + organization="foo" + qprofile="foo" + total={7} + type="VULNERABILITY" + /> + <ProfileRulesRowOfType + count={0} + organization="foo" + qprofile="foo" + total={168} + type="CODE_SMELL" + /> + </tbody> + </table> + </div> + <ProfileRulesSonarWayComparison + organization="foo" + profile="foo" + sonarWayMissingRules={4} + sonarway="sonarway" + /> +</div> +`; + +exports[`should show a button to activate more rules for admins 1`] = ` +<Link + className="button js-activate-rules" + onlyActiveOnIndex={false} + style={Object {}} + to="/organizations/foo/rules#qprofile=foo|activation=false" +> + quality_profiles.activate_more +</Link> +`; + +exports[`should show a deprecated rules warning message 1`] = ` +<ProfileRulesDeprecatedWarning + activeDeprecatedRules={8} + organization="foo" + profile="foo" +/> +`; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.js.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.js.snap new file mode 100644 index 00000000000..54fa91cc49d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.js.snap @@ -0,0 +1,29 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<div + className="quality-profile-rules-deprecated clearfix" +> + <span + className="pull-left" + > + quality_profiles.deprecated_rules + <Tooltip + overlay="quality_profiles.deprecated_rules_description" + placement="bottom" + > + <i + className="icon-help spacer-left" + /> + </Tooltip> + </span> + <Link + className="pull-right" + onlyActiveOnIndex={false} + style={Object {}} + to="/organizations/foo/rules#qprofile=bar|activation=true|statuses=DEPRECATED" + > + 18 + </Link> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.js.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.js.snap new file mode 100644 index 00000000000..62866095c17 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.js.snap @@ -0,0 +1,120 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<tr> + <td> + <span> + <IssueTypeIcon + className="little-spacer-right" + query="BUG" + /> + issue.type.BUG.plural + </span> + </td> + <td + className="thin nowrap text-right" + > + <Link + onlyActiveOnIndex={false} + style={Object {}} + to="/organizations/foo/rules#qprofile=bar|activation=true|types=BUG" + > + 3 + </Link> + </td> + <td + className="thin nowrap text-right" + > + <Link + className="small text-muted" + onlyActiveOnIndex={false} + style={Object {}} + to="/organizations/foo/rules#qprofile=bar|activation=false|types=BUG" + > + 7 + </Link> + </td> +</tr> +`; + +exports[`should render correctly if there is 0 rules 1`] = ` +<tr> + <td> + <span> + <IssueTypeIcon + className="little-spacer-right" + query="VULNERABILITY" + /> + issue.type.VULNERABILITY.plural + </span> + </td> + <td + className="thin nowrap text-right" + > + <Link + onlyActiveOnIndex={false} + style={Object {}} + to="/coding_rules#qprofile=bar|activation=true|types=VULNERABILITY" + > + 0 + </Link> + </td> + <td + className="thin nowrap text-right" + > + <span + className="note text-muted" + > + 0 + </span> + </td> +</tr> +`; + +exports[`should render correctly if there is missing data 1`] = ` +<tr> + <td> + <span> + <IssueTypeIcon + className="little-spacer-right" + query="VULNERABILITY" + /> + issue.type.VULNERABILITY.plural + </span> + </td> + <td + className="thin nowrap text-right" + > + <Link + onlyActiveOnIndex={false} + style={Object {}} + to="/coding_rules#qprofile=bar|activation=true|types=VULNERABILITY" + > + 5 + </Link> + </td> + <td + className="thin nowrap text-right" + /> +</tr> +`; + +exports[`should render correctly if there is missing data 2`] = ` +<tr> + <td> + <span> + <IssueTypeIcon + className="little-spacer-right" + query="VULNERABILITY" + /> + issue.type.VULNERABILITY.plural + </span> + </td> + <td + className="thin nowrap text-right" + /> + <td + className="thin nowrap text-right" + /> +</tr> +`; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.js.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.js.snap new file mode 100644 index 00000000000..f8686659326 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.js.snap @@ -0,0 +1,112 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<tr> + <td> + <strong> + total + </strong> + </td> + <td + className="thin nowrap text-right" + > + <Link + onlyActiveOnIndex={false} + style={Object {}} + to="/organizations/foo/rules#qprofile=bar|activation=true" + > + <strong> + 3 + </strong> + </Link> + </td> + <td + className="thin nowrap text-right" + > + <Link + className="small text-muted" + onlyActiveOnIndex={false} + style={Object {}} + to="/organizations/foo/rules#qprofile=bar|activation=false" + > + <strong> + 7 + </strong> + </Link> + </td> +</tr> +`; + +exports[`should render correctly if there is 0 rules 1`] = ` +<tr> + <td> + <strong> + total + </strong> + </td> + <td + className="thin nowrap text-right" + > + <Link + onlyActiveOnIndex={false} + style={Object {}} + to="/coding_rules#qprofile=bar|activation=true" + > + <strong> + 0 + </strong> + </Link> + </td> + <td + className="thin nowrap text-right" + > + <span + className="note text-muted" + > + 0 + </span> + </td> +</tr> +`; + +exports[`should render correctly if there is missing data 1`] = ` +<tr> + <td> + <strong> + total + </strong> + </td> + <td + className="thin nowrap text-right" + > + <Link + onlyActiveOnIndex={false} + style={Object {}} + to="/coding_rules#qprofile=bar|activation=true" + > + <strong> + 5 + </strong> + </Link> + </td> + <td + className="thin nowrap text-right" + /> +</tr> +`; + +exports[`should render correctly if there is missing data 2`] = ` +<tr> + <td> + <strong> + total + </strong> + </td> + <td + className="thin nowrap text-right" + /> + <td + className="thin nowrap text-right" + /> +</tr> +`; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.js.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.js.snap new file mode 100644 index 00000000000..6864b28e71b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.js.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<div + className="quality-profile-rules-sonarway-missing clearfix" +> + <span + className="pull-left" + > + quality_profiles.sonarway_missing_rules + <Tooltip + overlay="quality_profiles.sonarway_missing_rules_description" + placement="bottom" + > + <i + className="icon-help spacer-left" + /> + </Tooltip> + </span> + <Link + className="pull-right" + onlyActiveOnIndex={false} + style={Object {}} + > + 158 + </Link> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/styles.css b/server/sonar-web/src/main/js/apps/quality-profiles/styles.css index 0498b5b1d48..2712ddf243e 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/styles.css +++ b/server/sonar-web/src/main/js/apps/quality-profiles/styles.css @@ -63,7 +63,9 @@ padding: 15px 20px 20px; } -.quality-profile-rules {} +.quality-profile-rules { + min-height: 182px; +} .quality-profile-rules > header { padding: 15px 20px; @@ -79,6 +81,11 @@ background-color: #f2dede; } +.quality-profile-rules-sonarway-missing { + padding: 15px 20px; + background-color: #fcf8e3; +} + .quality-profile-exporters { margin-top: 20px; } |