diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2018-05-31 13:25:16 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-06-01 20:20:47 +0200 |
commit | 270239f18e866cecaa3bda198f24515b48c05e3b (patch) | |
tree | a18610ccadb1423a2d8467433f63b2728ec425e6 /server/sonar-web | |
parent | c72965048f5cf06bdf3414846a347402f2b8dddf (diff) | |
download | sonarqube-270239f18e866cecaa3bda198f24515b48c05e3b.tar.gz sonarqube-270239f18e866cecaa3bda198f24515b48c05e3b.zip |
SONAR-10821 Display details about external rules
Diffstat (limited to 'server/sonar-web')
6 files changed, 546 insertions, 34 deletions
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsDescription.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsDescription.tsx index b6e5b8455ef..7e8889b80ad 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsDescription.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsDescription.tsx @@ -189,18 +189,19 @@ export default class RuleDetailsDescription extends React.PureComponent<Props, S render() { const { ruleDetails } = this.props; + const hasDescription = !ruleDetails.isExternal || ruleDetails.type !== 'UNKNOWN'; return ( <div className="js-rule-description"> - {ruleDetails.isExternal ? ( - <div className="coding-rules-detail-description rule-desc markdown"> - {translateWithParameters('issue.external_issue_description', ruleDetails.name)} - </div> - ) : ( + {hasDescription ? ( <div className="coding-rules-detail-description rule-desc markdown" dangerouslySetInnerHTML={{ __html: ruleDetails.htmlDesc || '' }} /> + ) : ( + <div className="coding-rules-detail-description rule-desc markdown"> + {translateWithParameters('issue.external_issue_description', ruleDetails.name)} + </div> )} {!ruleDetails.templateKey && ( diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx index 52417451371..c74e8ad3df6 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx @@ -28,7 +28,7 @@ import LinkIcon from '../../../components/icons-components/LinkIcon'; import RuleScopeIcon from '../../../components/icons-components/RuleScopeIcon'; import Tooltip from '../../../components/controls/Tooltip'; import DocTooltip from '../../../components/docs/DocTooltip'; -import { translate } from '../../../helpers/l10n'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; import IssueTypeIcon from '../../../components/ui/IssueTypeIcon'; import SeverityHelper from '../../../components/shared/SeverityHelper'; import Dropdown from '../../../components/controls/Dropdown'; @@ -47,6 +47,8 @@ interface Props { ruleDetails: RuleDetails; } +const EXTERNAL_RULE_REPO_PREFIX = 'external_'; + export default class RuleDetailsMeta extends React.PureComponent<Props> { renderType = () => { const { ruleDetails } = this.props; @@ -207,8 +209,29 @@ export default class RuleDetailsMeta extends React.PureComponent<Props> { ); }; + renderExternalBadge = () => { + const { ruleDetails } = this.props; + if (!ruleDetails.repo) { + return null; + } + const engine = ruleDetails.repo.replace(new RegExp(`^${EXTERNAL_RULE_REPO_PREFIX}`), ''); + if (!engine) { + return null; + } + return ( + <Tooltip overlay={translateWithParameters('coding_rules.external_rule.engine', engine)}> + <li className="coding-rules-detail-property"> + <div className="outline-badge badge-tiny-height spacer-left vertical-text-top"> + {engine} + </div> + </li> + </Tooltip> + ); + }; + render() { const { ruleDetails } = this.props; + const hasTypeData = !ruleDetails.isExternal || ruleDetails.type !== 'UNKNOWN'; return ( <div className="js-rule-meta"> <header className="page-header"> @@ -230,18 +253,27 @@ export default class RuleDetailsMeta extends React.PureComponent<Props> { </h3> </header> - {!ruleDetails.isExternal && ( + {hasTypeData && ( <ul className="coding-rules-detail-properties"> {this.renderType()} {this.renderSeverity()} - {this.renderStatus()} - {this.renderScope()} + {!ruleDetails.isExternal && ( + <> + {this.renderStatus()} + {this.renderScope()} + </> + )} {this.renderTags()} - {this.renderCreationDate()} + {!ruleDetails.isExternal && this.renderCreationDate()} {this.renderRepository()} - {this.renderTemplate()} - {this.renderParentTemplate()} - {this.renderRemediation()} + {!ruleDetails.isExternal && ( + <> + {this.renderTemplate()} + {this.renderParentTemplate()} + {this.renderRemediation()} + </> + )} + {ruleDetails.isExternal && this.renderExternalBadge()} </ul> )} </div> diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsDescription-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsDescription-test.tsx new file mode 100644 index 00000000000..10179121078 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsDescription-test.tsx @@ -0,0 +1,90 @@ +/* + * 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 RuleDetailsDescription from '../RuleDetailsDescription'; +import { click, change, waitAndUpdate } from '../../../../helpers/testUtils'; + +jest.mock('../../../../api/rules', () => ({ + updateRule: jest.fn().mockResolvedValue('updatedrule') +})); + +const RULE = { + key: 'squid:S1133', + repo: 'squid', + name: 'Deprecated code should be removed', + createdAt: '2013-07-26T09:40:51+0200', + htmlDesc: '<p>Html Description</p>', + mdNote: 'Md Note', + severity: 'INFO', + status: 'READY', + lang: 'java', + langName: 'Java', + type: 'CODE_SMELL' +}; + +const EXTERNAL_RULE = { + key: 'external_xoo:OneExternalIssuePerLine', + repo: 'external_xoo', + name: 'xoo:OneExternalIssuePerLine', + status: 'READY', + isExternal: true, + type: 'UNKNOWN' +}; + +const EXTERNAL_RULE_WITH_DATA = { + key: 'external_xoo:OneExternalIssueWithDetailsPerLine', + repo: 'external_xoo', + name: 'One external issue per line', + createdAt: '2018-05-31T11:19:51+0200', + htmlDesc: '<p>Html Description</p>', + status: 'READY', + isExternal: true, + type: 'BUG' +}; + +it('should display correctly', () => { + expect(getWrapper()).toMatchSnapshot(); + expect(getWrapper({ ruleDetails: EXTERNAL_RULE })).toMatchSnapshot(); + expect(getWrapper({ ruleDetails: EXTERNAL_RULE_WITH_DATA })).toMatchSnapshot(); +}); + +it('should add extra description', async () => { + const onChange = jest.fn(); + const wrapper = getWrapper({ canWrite: true, onChange }); + click(wrapper.find('#coding-rules-detail-extend-description')); + expect(wrapper.find('textarea').exists()).toBeTruthy(); + change(wrapper.find('textarea'), 'new description'); + click(wrapper.find('#coding-rules-detail-extend-description-submit')); + await waitAndUpdate(wrapper); + expect(onChange).toBeCalledWith('updatedrule'); +}); + +function getWrapper(props = {}) { + return shallow( + <RuleDetailsDescription + canWrite={false} + onChange={jest.fn()} + organization={undefined} + ruleDetails={RULE} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsMeta-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsMeta-test.tsx index 0a725bc6d02..1380e6c0bfc 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsMeta-test.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsMeta-test.tsx @@ -20,33 +20,61 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import RuleDetailsMeta from '../RuleDetailsMeta'; -import { RuleDetails } from '../../../../app/types'; +import { RuleScope } from '../../../../app/types'; import RuleDetailsTagsPopup from '../RuleDetailsTagsPopup'; -const ruleDetails: RuleDetails = { - createdAt: '', - repo: '', - key: 'key', - lang: '', - langName: '', - name: '', - severity: '', - status: '', - type: '' +const RULE = { + key: 'squid:S1133', + repo: 'squid', + name: 'Deprecated code should be removed', + createdAt: '2013-07-26T09:40:51+0200', + severity: 'INFO', + status: 'READY', + lang: 'java', + langName: 'Java', + scope: RuleScope.Main, + type: 'CODE_SMELL' }; +const EXTERNAL_RULE = { + key: 'external_xoo:OneExternalIssuePerLine', + repo: 'external_xoo', + name: 'xoo:OneExternalIssuePerLine', + createdAt: '2018-05-31T11:22:13+0200', + status: 'READY', + scope: RuleScope.All, + isExternal: true, + type: 'UNKNOWN' +}; + +const EXTERNAL_RULE_WITH_DATA = { + key: 'external_xoo:OneExternalIssueWithDetailsPerLine', + repo: 'external_xoo', + name: 'One external issue per line', + createdAt: '2018-05-31T11:19:51+0200', + severity: 'MAJOR', + status: 'READY', + tags: ['tag'], + lang: 'xoo', + langName: 'Xoo', + scope: RuleScope.All, + isExternal: true, + type: 'BUG' +}; + +it('should display right meta info', () => { + expect(getWrapper()).toMatchSnapshot(); + expect( + getWrapper({ hideSimilarRulesFilter: true, ruleDetails: EXTERNAL_RULE }) + ).toMatchSnapshot(); + expect( + getWrapper({ hideSimilarRulesFilter: true, ruleDetails: EXTERNAL_RULE_WITH_DATA }) + ).toMatchSnapshot(); +}); + it('should edit tags', () => { const onTagsChange = jest.fn(); - const wrapper = shallow( - <RuleDetailsMeta - canWrite={true} - onFilterChange={jest.fn()} - onTagsChange={onTagsChange} - organization={undefined} - referencedRepositories={{}} - ruleDetails={ruleDetails} - /> - ); + const wrapper = getWrapper({ onTagsChange }); expect(wrapper.find('[data-meta="tags"]')).toMatchSnapshot(); const overlay = wrapper .find('[data-meta="tags"]') @@ -56,3 +84,17 @@ it('should edit tags', () => { overlay.props.setTags(['foo', 'bar']); expect(onTagsChange).toBeCalledWith(['foo', 'bar']); }); + +function getWrapper(props = {}) { + return shallow( + <RuleDetailsMeta + canWrite={true} + onFilterChange={jest.fn()} + onTagsChange={jest.fn()} + organization={undefined} + referencedRepositories={{}} + ruleDetails={RULE} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsDescription-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsDescription-test.tsx.snap new file mode 100644 index 00000000000..2e50c52359b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsDescription-test.tsx.snap @@ -0,0 +1,64 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should display correctly 1`] = ` +<div + className="js-rule-description" +> + <div + className="coding-rules-detail-description rule-desc markdown" + dangerouslySetInnerHTML={ + Object { + "__html": "<p>Html Description</p>", + } + } + /> + <div + className="coding-rules-detail-description coding-rules-detail-description-extra" + > + <div + id="coding-rules-detail-description-extra" + /> + </div> +</div> +`; + +exports[`should display correctly 2`] = ` +<div + className="js-rule-description" +> + <div + className="coding-rules-detail-description rule-desc markdown" + > + issue.external_issue_description.xoo:OneExternalIssuePerLine + </div> + <div + className="coding-rules-detail-description coding-rules-detail-description-extra" + > + <div + id="coding-rules-detail-description-extra" + /> + </div> +</div> +`; + +exports[`should display correctly 3`] = ` +<div + className="js-rule-description" +> + <div + className="coding-rules-detail-description rule-desc markdown" + dangerouslySetInnerHTML={ + Object { + "__html": "<p>Html Description</p>", + } + } + /> + <div + className="coding-rules-detail-description coding-rules-detail-description-extra" + > + <div + id="coding-rules-detail-description-extra" + /> + </div> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsMeta-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsMeta-test.tsx.snap index 8e501772a23..2f6ac55b176 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsMeta-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsMeta-test.tsx.snap @@ -1,5 +1,288 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`should display right meta info 1`] = ` +<div + className="js-rule-meta" +> + <header + className="page-header" + > + <div + className="pull-right" + > + <span + className="note text-middle" + > + squid:S1133 + </span> + <Link + className="coding-rules-detail-permalink link-no-underline spacer-left text-middle" + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/coding_rules", + "query": Object { + "open": "squid:S1133", + "rule_key": "squid:S1133", + }, + } + } + > + <LinkIcon /> + </Link> + <SimilarRulesFilter + onFilterChange={[MockFunction]} + rule={ + Object { + "createdAt": "2013-07-26T09:40:51+0200", + "key": "squid:S1133", + "lang": "java", + "langName": "Java", + "name": "Deprecated code should be removed", + "repo": "squid", + "scope": "MAIN", + "severity": "INFO", + "status": "READY", + "type": "CODE_SMELL", + } + } + /> + </div> + <h3 + className="page-title coding-rules-detail-header" + > + <big> + Deprecated code should be removed + </big> + </h3> + </header> + <ul + className="coding-rules-detail-properties" + > + <Tooltip + overlay="coding_rules.type.tooltip.CODE_SMELL" + > + <li + className="coding-rules-detail-property" + data-meta="type" + > + <IssueTypeIcon + className="little-spacer-right" + query="CODE_SMELL" + /> + issue.type.CODE_SMELL + </li> + </Tooltip> + <Tooltip + overlay="default_severity" + > + <li + className="coding-rules-detail-property" + data-meta="severity" + > + <SeverityHelper + className="display-inline-flex-center" + severity="INFO" + /> + </li> + </Tooltip> + <React.Fragment> + <Tooltip + overlay="coding_rules.scope.title" + > + <li + className="coding-rules-detail-property" + > + <RuleScopeIcon + className="little-spacer-right" + /> + coding_rules.scope.MAIN + </li> + </Tooltip> + </React.Fragment> + <li + className="coding-rules-detail-property" + data-meta="tags" + > + <Dropdown + closeOnClick={false} + closeOnClickOutside={true} + overlay={ + <RuleDetailsTagsPopup + organization={undefined} + setTags={[MockFunction]} + sysTags={Array []} + tags={Array []} + /> + } + overlayPlacement="bottom-left" + > + <Button + className="button-link" + > + <TagsList + allowUpdate={true} + tags={ + Array [ + "coding_rules.no_tags", + ] + } + /> + </Button> + </Dropdown> + </li> + <li + className="coding-rules-detail-property" + data-meta="available-since" + > + <span + className="little-spacer-right" + > + coding_rules.available_since + </span> + <DateFormatter + date="2013-07-26T09:40:51+0200" + /> + </li> + <React.Fragment /> + </ul> +</div> +`; + +exports[`should display right meta info 2`] = ` +<div + className="js-rule-meta" +> + <header + className="page-header" + > + <div + className="pull-right" + > + <span + className="note text-middle" + > + external_xoo:OneExternalIssuePerLine + </span> + </div> + <h3 + className="page-title coding-rules-detail-header" + > + <big> + xoo:OneExternalIssuePerLine + </big> + </h3> + </header> +</div> +`; + +exports[`should display right meta info 3`] = ` +<div + className="js-rule-meta" +> + <header + className="page-header" + > + <div + className="pull-right" + > + <span + className="note text-middle" + > + external_xoo:OneExternalIssueWithDetailsPerLine + </span> + </div> + <h3 + className="page-title coding-rules-detail-header" + > + <big> + One external issue per line + </big> + </h3> + </header> + <ul + className="coding-rules-detail-properties" + > + <Tooltip + overlay="coding_rules.type.tooltip.BUG" + > + <li + className="coding-rules-detail-property" + data-meta="type" + > + <IssueTypeIcon + className="little-spacer-right" + query="BUG" + /> + issue.type.BUG + </li> + </Tooltip> + <Tooltip + overlay="default_severity" + > + <li + className="coding-rules-detail-property" + data-meta="severity" + > + <SeverityHelper + className="display-inline-flex-center" + severity="MAJOR" + /> + </li> + </Tooltip> + <li + className="coding-rules-detail-property" + data-meta="tags" + > + <Dropdown + closeOnClick={false} + closeOnClickOutside={true} + overlay={ + <RuleDetailsTagsPopup + organization={undefined} + setTags={[MockFunction]} + sysTags={Array []} + tags={ + Array [ + "tag", + ] + } + /> + } + overlayPlacement="bottom-left" + > + <Button + className="button-link" + > + <TagsList + allowUpdate={true} + tags={ + Array [ + "tag", + ] + } + /> + </Button> + </Dropdown> + </li> + <Tooltip + overlay="coding_rules.external_rule.engine.xoo" + > + <li + className="coding-rules-detail-property" + > + <div + className="outline-badge badge-tiny-height spacer-left vertical-text-top" + > + xoo + </div> + </li> + </Tooltip> + </ul> +</div> +`; + exports[`should edit tags 1`] = ` <li className="coding-rules-detail-property" |