]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10867 Add security hotspot new issue type
authorJulien HENRY <julien.henry@sonarsource.com>
Tue, 12 Jun 2018 09:39:04 +0000 (11:39 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 4 Jul 2018 07:31:03 +0000 (09:31 +0200)
20 files changed:
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/EffortAggregator.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregator.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java
server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesStatisticsTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/ws/SearchActionTest.java
server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/display_facets.json
server/sonar-web/src/main/js/apps/coding-rules/components/TypeFacet.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.tsx
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.tsx.snap
server/sonar-web/src/main/js/components/icons-components/SecurityHotspotIcon.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/ui/IssueTypeIcon.tsx
server/sonar-web/src/main/js/helpers/constants.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties
sonar-plugin-api/src/main/java/org/sonar/api/rules/RuleType.java
sonar-plugin-api/src/test/java/org/sonar/api/rules/RuleTypeTest.java
sonar-scanner-protocol/src/main/protobuf/scanner_report.proto
sonar-ws/src/main/protobuf/ws-commons.proto

index 51e4c20d02a041b357f9af9b33e79b93261b8165..78cb17a820821da4e0cd0e6d44695e1360e58381 100644 (file)
@@ -119,6 +119,9 @@ public class EffortAggregator extends IssueVisitor {
           case VULNERABILITY:
             securityEffort += issueEffort;
             break;
+          case SECURITY_HOTSPOT:
+            // Not counted
+            break;
           default:
             throw new IllegalStateException(String.format("Unknown type '%s'", issue.type()));
         }
index a9e3c2e3c93b9a66be781139cd13e682236a4ba5..7c7f9e0473f2e84de1f6b2d386108ade8200d18b 100644 (file)
@@ -121,6 +121,9 @@ public class NewEffortAggregator extends IssueVisitor {
         case VULNERABILITY:
           securitySum.add(newEffort);
           break;
+        case SECURITY_HOTSPOT:
+          // Not counted
+          break;
         default:
           throw new IllegalStateException(String.format("Unknown type '%s'", issue.type()));
       }
index 97243a5ab8751cfc05b4358a1fa0ce7ae5a93735..58801ef4de6182579e7af05da1b0edb98e09ddb1 100644 (file)
@@ -238,6 +238,8 @@ public class TrackerRawInputFactory {
           return RuleType.CODE_SMELL;
         case VULNERABILITY:
           return RuleType.VULNERABILITY;
+        case SECURITY_HOTSPOT:
+          return RuleType.SECURITY_HOTSPOT;
         case UNRECOGNIZED:
         default:
           throw new IllegalStateException("Invalid issue type: " + type);
index 0bbe22f5558f40155de4f7db1439c4641a484e74..b318fab55f5f126e009f239118c55bebf9741404 100644 (file)
@@ -49,7 +49,7 @@ public class NewIssuesStatisticsTest {
   public ExpectedException expectedException = ExpectedException.none();
 
   private final Random random = new Random();
-  private RuleType randomRuleType = RuleType.values()[random.nextInt(RuleType.values().length)];
+  private RuleType randomRuleTypeExceptHotspot = RuleType.values()[random.nextInt(RuleType.values().length - 1)];
   private NewIssuesStatistics underTest = new NewIssuesStatistics(Issue::isNew);
 
   @Test
@@ -126,7 +126,7 @@ public class NewIssuesStatisticsTest {
     List<String> componentUuids = IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> randomAlphabetic(3)).collect(Collectors.toList());
     String assignee = randomAlphanumeric(10);
     componentUuids.stream()
-      .map(componentUuid -> new DefaultIssue().setType(randomRuleType).setComponentUuid(componentUuid).setAssigneeUuid(assignee).setNew(true))
+      .map(componentUuid -> new DefaultIssue().setType(randomRuleTypeExceptHotspot).setComponentUuid(componentUuid).setAssigneeUuid(assignee).setNew(true))
       .forEach(underTest::add);
 
     DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.COMPONENT);
@@ -140,7 +140,7 @@ public class NewIssuesStatisticsTest {
     List<String> componentUuids = IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> randomAlphabetic(3)).collect(Collectors.toList());
     String assignee = randomAlphanumeric(10);
     componentUuids.stream()
-      .map(componentUuid -> new DefaultIssue().setType(randomRuleType).setComponentUuid(componentUuid).setAssigneeUuid(assignee).setNew(false))
+      .map(componentUuid -> new DefaultIssue().setType(randomRuleTypeExceptHotspot).setComponentUuid(componentUuid).setAssigneeUuid(assignee).setNew(false))
       .forEach(underTest::add);
 
     DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.COMPONENT);
@@ -153,7 +153,7 @@ public class NewIssuesStatisticsTest {
   @Test
   public void add_does_not_count_component_if_null_neither_globally_nor_per_assignee() {
     String assignee = randomAlphanumeric(10);
-    underTest.add(new DefaultIssue().setType(randomRuleType).setComponentUuid(null).setAssigneeUuid(assignee).setNew(new Random().nextBoolean()));
+    underTest.add(new DefaultIssue().setType(randomRuleTypeExceptHotspot).setComponentUuid(null).setAssigneeUuid(assignee).setNew(new Random().nextBoolean()));
 
     DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.COMPONENT);
     DistributedMetricStatsInt assigneeDistribution = underTest.getAssigneesStatistics().get(assignee).getDistributedMetricStats(Metric.COMPONENT);
@@ -170,7 +170,7 @@ public class NewIssuesStatisticsTest {
     List<String> ruleKeys = IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> randomAlphabetic(3)).collect(Collectors.toList());
     String assignee = randomAlphanumeric(10);
     ruleKeys.stream()
-      .map(ruleKey -> new DefaultIssue().setType(randomRuleType).setRuleKey(RuleKey.of(repository, ruleKey)).setAssigneeUuid(assignee).setNew(true))
+      .map(ruleKey -> new DefaultIssue().setType(randomRuleTypeExceptHotspot).setRuleKey(RuleKey.of(repository, ruleKey)).setAssigneeUuid(assignee).setNew(true))
       .forEach(underTest::add);
 
     DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.RULE);
@@ -186,7 +186,7 @@ public class NewIssuesStatisticsTest {
     List<String> ruleKeys = IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> randomAlphabetic(3)).collect(Collectors.toList());
     String assignee = randomAlphanumeric(10);
     ruleKeys.stream()
-      .map(ruleKey -> new DefaultIssue().setType(randomRuleType).setRuleKey(RuleKey.of(repository, ruleKey)).setAssigneeUuid(assignee).setNew(false))
+      .map(ruleKey -> new DefaultIssue().setType(randomRuleTypeExceptHotspot).setRuleKey(RuleKey.of(repository, ruleKey)).setAssigneeUuid(assignee).setNew(false))
       .forEach(underTest::add);
 
     DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.RULE);
@@ -198,7 +198,7 @@ public class NewIssuesStatisticsTest {
   @Test
   public void add_does_not_count_ruleKey_if_null_neither_globally_nor_per_assignee() {
     String assignee = randomAlphanumeric(10);
-    underTest.add(new DefaultIssue().setType(randomRuleType).setRuleKey(null).setAssigneeUuid(assignee).setNew(new Random().nextBoolean()));
+    underTest.add(new DefaultIssue().setType(randomRuleTypeExceptHotspot).setRuleKey(null).setAssigneeUuid(assignee).setNew(new Random().nextBoolean()));
 
     DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.RULE);
     DistributedMetricStatsInt assigneeDistribution = underTest.getAssigneesStatistics().get(assignee).getDistributedMetricStats(Metric.RULE);
@@ -213,7 +213,7 @@ public class NewIssuesStatisticsTest {
   public void add_counts_issue_per_assignee_on_leak_globally_and_per_assignee() {
     List<String> assignees = IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> randomAlphabetic(3)).collect(Collectors.toList());
     assignees.stream()
-      .map(assignee -> new DefaultIssue().setType(randomRuleType).setAssigneeUuid(assignee).setNew(true))
+      .map(assignee -> new DefaultIssue().setType(randomRuleTypeExceptHotspot).setAssigneeUuid(assignee).setNew(true))
       .forEach(underTest::add);
 
     DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.ASSIGNEE);
@@ -243,7 +243,7 @@ public class NewIssuesStatisticsTest {
   public void add_counts_issue_per_assignee_off_leak_globally_and_per_assignee() {
     List<String> assignees = IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> randomAlphabetic(3)).collect(Collectors.toList());
     assignees.stream()
-      .map(assignee -> new DefaultIssue().setType(randomRuleType).setAssigneeUuid(assignee).setNew(false))
+      .map(assignee -> new DefaultIssue().setType(randomRuleTypeExceptHotspot).setAssigneeUuid(assignee).setNew(false))
       .forEach(underTest::add);
 
     DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.ASSIGNEE);
@@ -271,7 +271,7 @@ public class NewIssuesStatisticsTest {
 
   @Test
   public void add_does_not_assignee_if_empty_neither_globally_nor_per_assignee() {
-    underTest.add(new DefaultIssue().setType(randomRuleType).setAssigneeUuid(null).setNew(new Random().nextBoolean()));
+    underTest.add(new DefaultIssue().setType(randomRuleTypeExceptHotspot).setAssigneeUuid(null).setNew(new Random().nextBoolean()));
 
     DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.ASSIGNEE);
     assertThat(globalDistribution.getTotal()).isEqualTo(0);
@@ -283,7 +283,7 @@ public class NewIssuesStatisticsTest {
   public void add_counts_issue_per_tags_on_leak_globally_and_per_assignee() {
     List<String> tags = IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> randomAlphabetic(3)).collect(Collectors.toList());
     String assignee = randomAlphanumeric(10);
-    underTest.add(new DefaultIssue().setType(randomRuleType).setTags(tags).setAssigneeUuid(assignee).setNew(true));
+    underTest.add(new DefaultIssue().setType(randomRuleTypeExceptHotspot).setTags(tags).setAssigneeUuid(assignee).setNew(true));
 
     DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.TAG);
     DistributedMetricStatsInt assigneeDistribution = underTest.getAssigneesStatistics().get(assignee).getDistributedMetricStats(Metric.TAG);
@@ -295,7 +295,7 @@ public class NewIssuesStatisticsTest {
   public void add_counts_issue_per_tags_off_leak_globally_and_per_assignee() {
     List<String> tags = IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> randomAlphabetic(3)).collect(Collectors.toList());
     String assignee = randomAlphanumeric(10);
-    underTest.add(new DefaultIssue().setType(randomRuleType).setTags(tags).setAssigneeUuid(assignee).setNew(false));
+    underTest.add(new DefaultIssue().setType(randomRuleTypeExceptHotspot).setTags(tags).setAssigneeUuid(assignee).setNew(false));
 
     DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.TAG);
     DistributedMetricStatsInt assigneeDistribution = underTest.getAssigneesStatistics().get(assignee).getDistributedMetricStats(Metric.TAG);
@@ -306,7 +306,7 @@ public class NewIssuesStatisticsTest {
   @Test
   public void add_does_not_count_tags_if_empty_neither_globally_nor_per_assignee() {
     String assignee = randomAlphanumeric(10);
-    underTest.add(new DefaultIssue().setType(randomRuleType).setTags(Collections.emptyList()).setAssigneeUuid(assignee).setNew(new Random().nextBoolean()));
+    underTest.add(new DefaultIssue().setType(randomRuleTypeExceptHotspot).setTags(Collections.emptyList()).setAssigneeUuid(assignee).setNew(new Random().nextBoolean()));
 
     DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.TAG);
     DistributedMetricStatsInt assigneeDistribution = underTest.getAssigneesStatistics().get(assignee).getDistributedMetricStats(Metric.TAG);
@@ -324,7 +324,7 @@ public class NewIssuesStatisticsTest {
     int expected = efforts.stream().mapToInt(s -> s).sum();
     String assignee = randomAlphanumeric(10);
     efforts.stream()
-      .map(effort -> new DefaultIssue().setType(randomRuleType).setEffort(Duration.create(effort)).setAssigneeUuid(assignee).setNew(true))
+      .map(effort -> new DefaultIssue().setType(randomRuleTypeExceptHotspot).setEffort(Duration.create(effort)).setAssigneeUuid(assignee).setNew(true))
       .forEach(underTest::add);
 
     MetricStatsLong globalDistribution = underTest.globalStatistics().effort();
@@ -344,7 +344,7 @@ public class NewIssuesStatisticsTest {
     int expected = efforts.stream().mapToInt(s -> s).sum();
     String assignee = randomAlphanumeric(10);
     efforts.stream()
-      .map(effort -> new DefaultIssue().setType(randomRuleType).setEffort(Duration.create(effort)).setAssigneeUuid(assignee).setNew(false))
+      .map(effort -> new DefaultIssue().setType(randomRuleTypeExceptHotspot).setEffort(Duration.create(effort)).setAssigneeUuid(assignee).setNew(false))
       .forEach(underTest::add);
 
     MetricStatsLong globalDistribution = underTest.globalStatistics().effort();
@@ -360,7 +360,7 @@ public class NewIssuesStatisticsTest {
   @Test
   public void add_does_not_sum_effort_if_null_neither_globally_nor_per_assignee() {
     String assignee = randomAlphanumeric(10);
-    underTest.add(new DefaultIssue().setType(randomRuleType).setEffort(null).setAssigneeUuid(assignee).setNew(new Random().nextBoolean()));
+    underTest.add(new DefaultIssue().setType(randomRuleTypeExceptHotspot).setEffort(null).setAssigneeUuid(assignee).setNew(new Random().nextBoolean()));
 
     MetricStatsLong globalDistribution = underTest.globalStatistics().effort();
     MetricStatsLong assigneeDistribution = underTest.getAssigneesStatistics().get(assignee).effort();
@@ -381,7 +381,7 @@ public class NewIssuesStatisticsTest {
     int effort = 10 + new Random().nextInt(5);
     RuleKey ruleKey = RuleKey.of(randomAlphanumeric(5), randomAlphanumeric(6));
     underTest.add(new DefaultIssue()
-      .setType(randomRuleType)
+      .setType(randomRuleTypeExceptHotspot)
       .setComponentUuid(componentUuid)
       .setTags(ImmutableSet.of(tag))
       .setAssigneeUuid(assignee)
@@ -393,7 +393,7 @@ public class NewIssuesStatisticsTest {
         "assigneesStatistics={" + assignee + "=" +
         "Stats{distributions={" +
         "RULE_TYPE=DistributedMetricStatsInt{globalStats=MetricStatsInt{onLeak=1, offLeak=0}, " +
-        "statsPerLabel={" + randomRuleType.name() + "=MetricStatsInt{onLeak=1, offLeak=0}}}, " +
+        "statsPerLabel={" + randomRuleTypeExceptHotspot.name() + "=MetricStatsInt{onLeak=1, offLeak=0}}}, " +
         "TAG=DistributedMetricStatsInt{globalStats=MetricStatsInt{onLeak=1, offLeak=0}, " +
         "statsPerLabel={" + tag + "=MetricStatsInt{onLeak=1, offLeak=0}}}, " +
         "COMPONENT=DistributedMetricStatsInt{globalStats=MetricStatsInt{onLeak=1, offLeak=0}, " +
@@ -405,7 +405,7 @@ public class NewIssuesStatisticsTest {
         "effortStats=MetricStatsLong{onLeak=" + effort + ", offLeak=0}}}, " +
         "globalStatistics=Stats{distributions={" +
         "RULE_TYPE=DistributedMetricStatsInt{globalStats=MetricStatsInt{onLeak=1, offLeak=0}, " +
-        "statsPerLabel={" + randomRuleType.name() + "=MetricStatsInt{onLeak=1, offLeak=0}}}, " +
+        "statsPerLabel={" + randomRuleTypeExceptHotspot.name() + "=MetricStatsInt{onLeak=1, offLeak=0}}}, " +
         "TAG=DistributedMetricStatsInt{globalStats=MetricStatsInt{onLeak=1, offLeak=0}, " +
         "statsPerLabel={" + tag + "=MetricStatsInt{onLeak=1, offLeak=0}}}, " +
         "COMPONENT=DistributedMetricStatsInt{globalStats=MetricStatsInt{onLeak=1, offLeak=0}, " +
index b3b11aeb5c427cd6f4a06c04cbf6fd3be6798f85..d6fe59d9f61d6e823b4fa2b1a8a1d556d6167ebf 100644 (file)
@@ -138,7 +138,7 @@ public class SetTypeActionTest {
     setUserWithBrowseAndAdministerIssuePermission(issueDto);
 
     expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Value of parameter 'type' (unknown) must be one of: [CODE_SMELL, BUG, VULNERABILITY]");
+    expectedException.expectMessage("Value of parameter 'type' (unknown) must be one of: [CODE_SMELL, BUG, VULNERABILITY, SECURITY_HOTSPOT]");
     call(issueDto.getKey(), "unknown");
   }
 
index 93c88fffd811026b1d8fedf417ee6eb998721603..61cade0fb549fbc5fd30ffd9deb32f0ba8c52bf8 100644 (file)
@@ -861,7 +861,8 @@ public class SearchActionTest {
       .containsExactlyInAnyOrder(
         tuple("BUG" /* rule2 */, 0L),
         tuple("CODE_SMELL"/* rule1 */, 1L),
-        tuple("VULNERABILITY", 0L));
+        tuple("VULNERABILITY", 0L),
+        tuple("SECURITY_HOTSPOT", 0L));
   }
 
   @Test
index 5778db4cb022882d23ed715dcf1ec492cf508408..38fd2f2b97375fe3559413bb15f04bde3d994b3f 100644 (file)
         {
           "val": "VULNERABILITY",
           "count": 0
+        },
+        {
+          "val": "SECURITY_HOTSPOT",
+          "count": 0
         }
       ]
     }
index 8094cf54b33a91bfc05992b0248486ccdeffa337..ff2accac97f0c4ce78ca350bd8804aa60092198a 100644 (file)
@@ -33,7 +33,7 @@ export default class TypeFacet extends React.PureComponent<BasicProps> {
   renderTextName = (type: string) => translate('issue.type', type);
 
   render() {
-    const options = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];
+    const options = ['BUG', 'VULNERABILITY', 'CODE_SMELL', 'SECURITY_HOTSPOT'];
 
     return (
       <Facet
index 889115e7111a9dff701de161984824c9bc5b2bbe..a6a189b7ddb2349b0877a21afcd5ecdbc5b2987f 100644 (file)
@@ -95,7 +95,7 @@ export default class TypeFacet extends React.PureComponent<Props> {
   };
 
   render() {
-    const types = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];
+    const types = ['BUG', 'VULNERABILITY', 'CODE_SMELL', 'SECURITY_HOTSPOT'];
     const values = this.props.types.map(type => translate('issue.type', type));
 
     return (
index a3a73a80a9fba2632a1bbf5124838e93421a6725..261f5998c7505429b6b319fbb9b3da12f66cbb45 100644 (file)
@@ -30,7 +30,7 @@ import { getRulesUrl } from '../../../helpers/urls';
 import { translate } from '../../../helpers/l10n';
 import { Profile } from '../types';
 
-const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];
+const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL', 'SECURITY_HOTSPOT'];
 
 interface Props {
   organization: string | null;
index fcabce9784a291e703165f221728d5c7ca2bcd63..2a313ccb6d8e09c28fcdd677b1bf958c28462906 100644 (file)
@@ -43,14 +43,15 @@ const PROFILE = {
 const EDITABLE_PROFILE = { ...PROFILE, actions: { edit: true } };
 
 const apiResponseAll = {
-  total: 243,
+  total: 253,
   facets: [
     {
       property: 'types',
       values: [
         { val: 'CODE_SMELL', count: 168 },
         { val: 'BUG', count: 68 },
-        { val: 'VULNERABILITY', count: 7 }
+        { val: 'VULNERABILITY', count: 7 },
+        { val: 'SECURITY_HOTSPOT', count: 10 }
       ]
     }
   ]
@@ -64,7 +65,8 @@ const apiResponseActive = {
       values: [
         { val: 'BUG', count: 68 },
         { val: 'CODE_SMELL', count: 0 },
-        { val: 'VULNERABILITY', count: 0 }
+        { val: 'VULNERABILITY', count: 0 },
+        { val: 'SECURITY_HOTSPOT', count: 0 }
       ]
     }
   ]
index 2fd0450b1bdcbe1a3e7416cd835249962911af9e..ea82cabc5dcbd654827c1881b1ea98bdd4cd8fdb 100644 (file)
@@ -30,7 +30,7 @@ exports[`should render the quality profiles rules with sonarway comparison 1`] =
           count={68}
           organization="foo"
           qprofile="foo"
-          total={243}
+          total={253}
         />
         <ProfileRulesRowOfType
           count={68}
@@ -56,6 +56,14 @@ exports[`should render the quality profiles rules with sonarway comparison 1`] =
           total={168}
           type="CODE_SMELL"
         />
+        <ProfileRulesRowOfType
+          count={0}
+          key="SECURITY_HOTSPOT"
+          organization="foo"
+          qprofile="foo"
+          total={10}
+          type="SECURITY_HOTSPOT"
+        />
       </tbody>
     </table>
   </div>
diff --git a/server/sonar-web/src/main/js/components/icons-components/SecurityHotspotIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/SecurityHotspotIcon.tsx
new file mode 100644 (file)
index 0000000..334d108
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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 Icon, { IconProps } from './Icon';
+
+export default function SecurityHotspotIcon({ className, fill = 'currentColor', size }: IconProps) {
+  return (
+    <Icon className={className} size={size}>
+      <g style={{ fill }}>
+        <path
+          d="M10.238 2.416c-0.432-0.895-1.259-1.504-2.202-1.504-1.386 0-2.521 1.318-2.521 2.927v5.481"
+          fill="none"
+          stroke={fill}
+          strokeLinecap="round"
+          strokeWidth="1.1429"
+        />
+        <path d="M8.537 10.372v1.199h-1.099v-1.199c-0.638-0.228-1.099-0.832-1.099-1.546 0-0.909 0.739-1.649 1.648-1.649s1.649 0.74 1.649 1.649c0 0.715-0.461 1.32-1.099 1.546zM10.734 4.979h-5.494c-1.21 0-2.199 0.989-2.199 2.197v4.395c0 1.21 0.989 2.199 2.199 2.199h5.494c1.209 0 2.197-0.989 2.197-2.199v-4.395c0-1.209-0.989-2.197-2.197-2.197z" />
+        <path d="M4.030 6.352h6.923v6.923h-6.923z" />
+        <path
+          d="M7.504 10.283c0-0.423 0.048-0.757 0.144-1.002s0.251-0.457 0.465-0.637c0.215-0.18 0.377-0.344 0.489-0.493s0.167-0.313 0.167-0.493c0-0.438-0.189-0.656-0.565-0.656-0.174 0-0.314 0.064-0.421 0.191s-0.164 0.3-0.17 0.518h-1.469c0.006-0.58 0.189-1.031 0.548-1.354s0.864-0.485 1.513-0.485c0.646 0 1.147 0.149 1.501 0.447s0.532 0.723 0.532 1.274c0 0.241-0.048 0.459-0.144 0.656s-0.249 0.398-0.46 0.604l-0.5 0.465c-0.142 0.136-0.241 0.276-0.296 0.42s-0.086 0.325-0.091 0.545h-1.243zM7.326 11.604c0-0.215 0.078-0.39 0.233-0.528s0.349-0.207 0.58-0.207c0.232 0 0.425 0.068 0.58 0.207s0.233 0.313 0.233 0.528-0.078 0.39-0.233 0.528c-0.155 0.138-0.349 0.207-0.58 0.207s-0.425-0.068-0.58-0.207c-0.155-0.138-0.233-0.313-0.233-0.528z"
+          fill="#fff"
+        />
+      </g>
+    </Icon>
+  );
+}
index 5cf34fe37e324123513846d3d78a89502ed5794b..05252164305b2f267d1a7a563bc8b9cb89f5fac2 100644 (file)
@@ -21,6 +21,7 @@ import * as React from 'react';
 import BugIcon from '../icons-components/BugIcon';
 import VulnerabilityIcon from '../icons-components/VulnerabilityIcon';
 import CodeSmellIcon from '../icons-components/CodeSmellIcon';
+import SecurityHotspotIcon from '../icons-components/SecurityHotspotIcon';
 
 interface Props {
   className?: string;
@@ -47,6 +48,10 @@ export default function IssueTypeIcon({ className, query, size }: Props) {
     case 'new_code_smells':
       icon = <CodeSmellIcon size={size} />;
       break;
+    case 'security_hotspot':
+    case 'security_hotspots':
+      icon = <SecurityHotspotIcon size={size} />;
+      break;
   }
 
   if (!icon) {
index 7c6a893ca7f1b4944486ed01ca4ef2d92ec8c065..0857573b85f32f792ea2e2038780191f9ac13d09 100644 (file)
@@ -21,7 +21,7 @@ import * as theme from '../app/theme';
 
 export const SEVERITIES = ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO'];
 export const STATUSES = ['OPEN', 'REOPENED', 'CONFIRMED', 'RESOLVED', 'CLOSED'];
-export const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];
+export const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL', 'SECURITY_HOTSPOT'];
 export const RULE_STATUSES = ['READY', 'BETA', 'DEPRECATED'];
 
 export const CHART_COLORS_RANGE_PERCENT = [
index 79d87fab775e530612517a0d32e0e781d8869d57..fb53c1bfbde19a285956136b004d76704cab6bac 100644 (file)
@@ -589,9 +589,11 @@ issue.set_type=Change Type
 issue.type.CODE_SMELL=Code Smell
 issue.type.BUG=Bug
 issue.type.VULNERABILITY=Vulnerability
+issue.type.SECURITY_HOTSPOT=Security Hotspot
 issue.type.CODE_SMELL.plural=Code Smells
 issue.type.BUG.plural=Bugs
 issue.type.VULNERABILITY.plural=Vulnerabilities
+issue.type.SECURITY_HOTSPOT.plural=Security Hotspots
 
 
 issue.status.REOPENED=Reopened
@@ -1277,6 +1279,7 @@ coding_rules.to_select_rules=to select rules
 coding_rules.type.tooltip.CODE_SMELL=Code Smell Detection Rule
 coding_rules.type.tooltip.BUG=Bug Detection Rule
 coding_rules.type.tooltip.VULNERABILITY=Vulnerability Detection Rule
+coding_rules.type.tooltip.SECURITY_HOTSPOT=Security Hotspot Detection Rule
 coding_rules.update_custom_rule=Update Custom Rule
 coding_rules.filter_similar_rules=Filter Similar Rules
 
index fc1bb923540e916e06a787058262b8c091e7aedf..79cb255acc61805c5a8b7cd2bf1824fc196d8872 100644 (file)
@@ -29,7 +29,7 @@ import static java.util.Collections.unmodifiableSet;
 import static java.util.stream.Collectors.toList;
 
 public enum RuleType {
-  CODE_SMELL(1), BUG(2), VULNERABILITY(3);
+  CODE_SMELL(1), BUG(2), VULNERABILITY(3), SECURITY_HOTSPOT(4);
 
   private static final Set<String> ALL_NAMES = unmodifiableSet(new LinkedHashSet<>(stream(values())
     .map(Enum::name)
index aec770fbc4243c788422b15b01ad51522d0492c8..7c9c51e33f0c43684387fecf90ec1a725608e4b5 100644 (file)
@@ -39,13 +39,13 @@ public class RuleTypeTest {
   @Test
   public void valueOf_throws_ISE_if_unsupported_db_constant() {
     expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Unsupported type value : 4");
-    RuleType.valueOf(4);
+    expectedException.expectMessage("Unsupported type value : 5");
+    RuleType.valueOf(5);
   }
 
   @Test
   public void test_ALL_NAMES() {
-    assertThat(RuleType.names()).containsOnly("BUG", "VULNERABILITY", "CODE_SMELL");
+    assertThat(RuleType.names()).containsOnly("BUG", "VULNERABILITY", "CODE_SMELL", "SECURITY_HOTSPOT");
   }
 
   @Test
index 66930fb3a698f97d0a6b7c53943221b8cae393c0..821b35e4d9a9f64595b0869348c6e9870014a226 100644 (file)
@@ -204,6 +204,7 @@ enum IssueType {
   CODE_SMELL = 0;
   BUG = 1;
   VULNERABILITY = 2;
+  SECURITY_HOTSPOT = 3;
 }
 
 message IssueLocation {
index 106a3ab1f6f1b1a2c0eba0a3efea1c917b5a71a6..442630a7fcee9517966cd940a0e222c7aea9f933 100644 (file)
@@ -118,6 +118,7 @@ enum RuleType {
   CODE_SMELL = 1;
   BUG = 2;
   VULNERABILITY = 3;
+  SECURITY_HOTSPOT = 4;
 }
 
 enum BranchType {