]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11156 fix issues section of hotspot rules
authorStas Vilchik <stas.vilchik@sonarsource.com>
Wed, 22 Aug 2018 08:46:42 +0000 (10:46 +0200)
committerSonarTech <sonartech@sonarsource.com>
Fri, 24 Aug 2018 18:21:20 +0000 (20:21 +0200)
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsIssues-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsIssues-test.tsx.snap [new file with mode: 0644]

index dcdc5b733dde97527779f49d1257f92f09b9374d..fabcfaf39ee4cf001c1e2a19186196c205f879f4 100644 (file)
@@ -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>
index 3bb6e1839eb09d5a78c56079e5dc639a76aca3ef..4925b83ccce309eefb8c863079c970a2afa8abf8 100644 (file)
@@ -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 (file)
index 0000000..fb79d05
--- /dev/null
@@ -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 (file)
index 0000000..a3b6fbe
--- /dev/null
@@ -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>
+`;