]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10821 Display details about external rules
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Thu, 31 May 2018 11:25:16 +0000 (13:25 +0200)
committerSonarTech <sonartech@sonarsource.com>
Fri, 1 Jun 2018 18:20:47 +0000 (20:20 +0200)
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsDescription.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsDescription-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsMeta-test.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsDescription-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsMeta-test.tsx.snap
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index b6e5b8455ef39588a72ac38f2162510cda67eeaa..7e8889b80ade5eb31431ea438ddd24e577df6478 100644 (file)
@@ -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 && (
index 524174513714d17e447eab2370608c7c18eae1ed..c74e8ad3df669b104eb1263dd47db9ed41589b9e 100644 (file)
@@ -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 (file)
index 0000000..1017912
--- /dev/null
@@ -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}
+    />
+  );
+}
index 0a725bc6d0209091048a23ecf72e5b7ef4528b36..1380e6c0bfc060ff1735f6a4f95b58e8f09a908e 100644 (file)
 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 (file)
index 0000000..2e50c52
--- /dev/null
@@ -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>
+`;
index 8e501772a23f62daa06e2c4db5c25e9c99c910ea..2f6ac55b176f2860b741e130d5742a4305667726 100644 (file)
@@ -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"
index b3d6d238765df321e25f2ada7502fd50d250099a..5f7d150df9947bfdca179d983a53eace9246d122 100644 (file)
@@ -1334,6 +1334,8 @@ coding_rules.scope.MAIN=Main sources
 coding_rules.scope.TEST=Test sources
 coding_rules.scope.ALL=Main and Test sources
 
+coding_rules.external_rule.engine=Rule provided by an external rule engine: {0}
+
 #------------------------------------------------------------------------------
 #
 # EMAIL CONFIGURATION