diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2018-08-22 10:46:42 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-08-24 20:21:20 +0200 |
commit | fede6d0a2378ce959e7cb1afdf221a92e22b8a6f (patch) | |
tree | 664177136e675521b5f11f3e20272b453a4a5ceb /server/sonar-web/src/main/js | |
parent | 07dd03a5bb3d8460412e4d09043568cb477838c0 (diff) | |
download | sonarqube-fede6d0a2378ce959e7cb1afdf221a92e22b8a6f.tar.gz sonarqube-fede6d0a2378ce959e7cb1afdf221a92e22b8a6f.zip |
SONAR-11156 fix issues section of hotspot rules
Diffstat (limited to 'server/sonar-web/src/main/js')
4 files changed, 312 insertions, 9 deletions
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx index dcdc5b733dd..fabcfaf39ee 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx @@ -254,7 +254,7 @@ export default class RuleDetails extends React.PureComponent<Props, State> { )} {!ruleDetails.isTemplate && ( - <RuleDetailsIssues organization={organization} ruleKey={ruleDetails.key} /> + <RuleDetailsIssues organization={organization} ruleDetails={ruleDetails} /> )} </DeferredSpinner> </div> diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx index 3bb6e1839eb..4925b83ccce 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx @@ -23,13 +23,14 @@ import { Link } from 'react-router'; import DeferredSpinner from '../../../components/common/DeferredSpinner'; import Tooltip from '../../../components/controls/Tooltip'; import { getFacet } from '../../../api/issues'; +import { RuleDetails } from '../../../app/types'; import { getIssuesUrl } from '../../../helpers/urls'; import { formatMeasure } from '../../../helpers/measures'; import { translate } from '../../../helpers/l10n'; interface Props { organization: string | undefined; - ruleKey: string; + ruleDetails: Pick<RuleDetails, 'key' | 'type'>; } interface Project { @@ -59,7 +60,7 @@ export default class RuleDetailsIssues extends React.PureComponent<Props, State> } componentDidUpdate(prevProps: Props) { - if (prevProps.ruleKey !== this.props.ruleKey) { + if (prevProps.ruleDetails !== this.props.ruleDetails) { this.fetchIssues(); } } @@ -68,10 +69,19 @@ export default class RuleDetailsIssues extends React.PureComponent<Props, State> this.mounted = false; } + getBaseIssuesQuery = () => ({ + resolved: 'false', + rules: this.props.ruleDetails.key, + types: + this.props.ruleDetails.type === 'SECURITY_HOTSPOT' + ? 'VULNERABILITY,SECURITY_HOTSPOT' + : undefined + }); + fetchIssues = () => { this.setState({ loading: true }); getFacet( - { organization: this.props.organization, rules: this.props.ruleKey, resolved: false }, + { ...this.getBaseIssuesQuery(), organization: this.props.organization }, 'projects' ).then( ({ facet, response }) => { @@ -100,10 +110,7 @@ export default class RuleDetailsIssues extends React.PureComponent<Props, State> if (total === undefined) { return null; } - const path = getIssuesUrl( - { resolved: 'false', rules: this.props.ruleKey }, - this.props.organization - ); + const path = getIssuesUrl(this.getBaseIssuesQuery(), this.props.organization); const totalItem = ( <span className="little-spacer-left"> @@ -124,7 +131,7 @@ export default class RuleDetailsIssues extends React.PureComponent<Props, State> renderProject = (project: Project) => { const path = getIssuesUrl( - { projects: project.key, resolved: 'false', rules: this.props.ruleKey }, + { ...this.getBaseIssuesQuery(), projects: project.key }, this.props.organization ); return ( diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsIssues-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsIssues-test.tsx new file mode 100644 index 00000000000..fb79d0585ed --- /dev/null +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsIssues-test.tsx @@ -0,0 +1,63 @@ +/* + * 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 RuleDetailsIssues from '../RuleDetailsIssues'; +import { waitAndUpdate } from '../../../../helpers/testUtils'; +import { getFacet } from '../../../../api/issues'; + +jest.mock('../../../../api/issues', () => ({ + getFacet: jest.fn().mockResolvedValue({ + facet: [{ count: 13, val: 'sample-key' }, { count: 5, val: 'example-key' }], + response: { + components: [{ key: 'sample-key', name: 'Sample' }, { key: 'example-key', name: 'Example' }], + paging: { total: 18 } + } + }) +})); + +beforeEach(() => { + (getFacet as jest.Mock).mockClear(); +}); + +it('should fetch issues and render', async () => { + await check('BUG', undefined); +}); + +it('should handle hotspot rules', async () => { + await check('SECURITY_HOTSPOT', ['VULNERABILITY', 'SECURITY_HOTSPOT']); +}); + +async function check(ruleType: string, requestedTypes: string[] | undefined) { + const wrapper = shallow( + <RuleDetailsIssues organization="org" ruleDetails={{ key: 'foo', type: ruleType }} /> + ); + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); + expect(getFacet).toBeCalledWith( + { + organization: 'org', + resolved: 'false', + rules: 'foo', + types: requestedTypes && requestedTypes.join() + }, + 'projects' + ); +} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsIssues-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsIssues-test.tsx.snap new file mode 100644 index 00000000000..a3b6fbe185a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsIssues-test.tsx.snap @@ -0,0 +1,233 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should fetch issues and render 1`] = ` +<div + className="js-rule-issues coding-rule-section" +> + <div + className="coding-rule-section-separator" + /> + <DeferredSpinner + loading={false} + timeout={100} + > + <h3 + className="coding-rules-detail-title" + > + coding_rules.issues + <span + className="little-spacer-left" + > + ( + <Link + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/organizations/org/issues", + "query": Object { + "resolved": "false", + "rules": "foo", + "types": undefined, + }, + } + } + > + 18 + </Link> + ) + </span> + </h3> + <table + className="coding-rules-detail-list coding-rules-most-violated-projects" + > + <tbody> + <tr> + <td + className="coding-rules-detail-list-name" + colSpan={2} + > + coding_rules.most_violating_projects + </td> + </tr> + <tr + key="sample-key" + > + <td + className="coding-rules-detail-list-name" + > + Sample + </td> + <td + className="coding-rules-detail-list-parameters" + > + <Link + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/organizations/org/issues", + "query": Object { + "projects": "sample-key", + "resolved": "false", + "rules": "foo", + "types": undefined, + }, + } + } + > + 13 + </Link> + </td> + </tr> + <tr + key="example-key" + > + <td + className="coding-rules-detail-list-name" + > + Example + </td> + <td + className="coding-rules-detail-list-parameters" + > + <Link + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/organizations/org/issues", + "query": Object { + "projects": "example-key", + "resolved": "false", + "rules": "foo", + "types": undefined, + }, + } + } + > + 5 + </Link> + </td> + </tr> + </tbody> + </table> + </DeferredSpinner> +</div> +`; + +exports[`should handle hotspot rules 1`] = ` +<div + className="js-rule-issues coding-rule-section" +> + <div + className="coding-rule-section-separator" + /> + <DeferredSpinner + loading={false} + timeout={100} + > + <h3 + className="coding-rules-detail-title" + > + coding_rules.issues + <span + className="little-spacer-left" + > + ( + <Link + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/organizations/org/issues", + "query": Object { + "resolved": "false", + "rules": "foo", + "types": "VULNERABILITY,SECURITY_HOTSPOT", + }, + } + } + > + 18 + </Link> + ) + </span> + </h3> + <table + className="coding-rules-detail-list coding-rules-most-violated-projects" + > + <tbody> + <tr> + <td + className="coding-rules-detail-list-name" + colSpan={2} + > + coding_rules.most_violating_projects + </td> + </tr> + <tr + key="sample-key" + > + <td + className="coding-rules-detail-list-name" + > + Sample + </td> + <td + className="coding-rules-detail-list-parameters" + > + <Link + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/organizations/org/issues", + "query": Object { + "projects": "sample-key", + "resolved": "false", + "rules": "foo", + "types": "VULNERABILITY,SECURITY_HOTSPOT", + }, + } + } + > + 13 + </Link> + </td> + </tr> + <tr + key="example-key" + > + <td + className="coding-rules-detail-list-name" + > + Example + </td> + <td + className="coding-rules-detail-list-parameters" + > + <Link + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/organizations/org/issues", + "query": Object { + "projects": "example-key", + "resolved": "false", + "rules": "foo", + "types": "VULNERABILITY,SECURITY_HOTSPOT", + }, + } + } + > + 5 + </Link> + </td> + </tr> + </tbody> + </table> + </DeferredSpinner> +</div> +`; |