From b497380a7647d69e6c2cb486faccce34121da08b Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Wed, 13 Jun 2018 10:34:09 +0200 Subject: [PATCH] SONAR-10874 New issue transitions for security hotspots --- .../projectanalysis/issue/IssueLifecycle.java | 4 - .../projectanalysis/issue/RuleTypeCopier.java | 4 +- .../issue/IssueLifecycleTest.java | 33 ------- .../org/sonar/server/issue/AssignAction.java | 3 +- .../sonar/server/issue/SetSeverityAction.java | 11 ++- .../org/sonar/server/issue/SetTypeAction.java | 7 +- .../sonar/server/issue/workflow/Function.java | 3 + .../issue/workflow/FunctionExecutor.java | 7 ++ .../sonar/server/issue/workflow/HasType.java | 45 +++++++++ .../issue/workflow/IsManualVulnerability.java | 37 ++++++++ .../IsNotHotspotNorManualVulnerability.java | 34 +++++++ .../server/issue/workflow/IssueWorkflow.java | 94 ++++++++++++++++++- .../sonar/server/issue/workflow/SetType.java | 36 +++++++ .../sonar/server/issue/ws/AssignAction.java | 2 + .../server/issue/ws/DoTransitionAction.java | 6 +- .../sonar/server/issue/ws/SearchAction.java | 3 +- .../server/issue/ws/SearchResponseData.java | 2 +- .../server/issue/ws/SearchResponseFormat.java | 1 + .../server/issue/ws/SearchResponseLoader.java | 18 ++-- .../sonar/server/issue/ws/SetTypeAction.java | 3 + .../server/issue/SetSeverityActionTest.java | 11 +++ .../server/issue/ws/AssignActionTest.java | 23 ++++- .../server/issue/ws/SetTypeActionTest.java | 13 +++ server/sonar-web/src/main/js/app/types.ts | 1 + .../__tests__/ConciseIssue-test.tsx | 3 +- .../__snapshots__/ConciseIssue-test.tsx.snap | 1 + .../components/__tests__/LineCode-test.tsx | 1 + .../__tests__/LineIssuesIndicator-test.tsx | 1 + .../__tests__/LineIssuesList-test.tsx | 1 + .../__snapshots__/LineCode-test.tsx.snap | 2 + .../LineIssuesList-test.tsx.snap | 2 + .../issue/components/IssueActionsBar.js | 5 +- .../issue/components/IssueMessage.js | 10 +- .../issue/components/IssueTitleBar.js | 1 + .../components/issue/components/IssueType.js | 6 +- .../components/__tests__/IssueMessage-test.js | 1 + .../__tests__/IssueTitleBar-test.js | 3 +- .../components/__tests__/IssueType-test.js | 6 +- .../__snapshots__/IssueTitleBar-test.js.snap | 5 + .../src/main/js/components/issue/types.js | 1 + .../org/sonar/core/issue/DefaultIssue.java | 1 + .../resources/org/sonar/l10n/core.properties | 16 ++++ .../sonar/api/issue/DefaultTransitions.java | 13 ++- sonar-ws/src/main/protobuf/ws-issues.proto | 1 + 44 files changed, 404 insertions(+), 77 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/issue/workflow/HasType.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsManualVulnerability.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsNotHotspotNorManualVulnerability.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetType.java diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java index c1166f7d55a..52855f4234b 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java @@ -23,7 +23,6 @@ import com.google.common.annotations.VisibleForTesting; import java.util.Date; import java.util.Optional; import org.sonar.api.issue.Issue; -import org.sonar.api.rules.RuleType; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.DefaultIssueComment; import org.sonar.core.issue.FieldDiffs; @@ -132,9 +131,6 @@ public class IssueLifecycle { public void mergeExistingOpenIssue(DefaultIssue raw, DefaultIssue base) { raw.setKey(base.key()); raw.setNew(false); - if (raw.type() == RuleType.SECURITY_HOTSPOT) { - raw.setIsFromHotspot(true); - } copyFields(raw, base); if (base.manualSeverity()) { diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleTypeCopier.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleTypeCopier.java index de0a30cd9da..cecfee91350 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleTypeCopier.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleTypeCopier.java @@ -33,13 +33,13 @@ public class RuleTypeCopier extends IssueVisitor { @Override public void onIssue(Component component, DefaultIssue issue) { + Rule rule = ruleRepository.getByKey(issue.ruleKey()); if (issue.type() == null) { - Rule rule = ruleRepository.getByKey(issue.ruleKey()); if (!rule.isExternal()) { // rule type should never be null for rules created by plugins (non-external rules) issue.setType(rule.getType()); } } - issue.setIsFromHotspot(issue.type() == RuleType.SECURITY_HOTSPOT); + issue.setIsFromHotspot(rule.getType() == RuleType.SECURITY_HOTSPOT); } } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java index 23de9153071..e76865e0c92 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java @@ -23,7 +23,6 @@ import com.google.common.collect.ImmutableMap; import java.util.Date; import org.junit.Rule; import org.junit.Test; -import org.sonar.api.rules.RuleType; import org.sonar.api.utils.Duration; import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; import org.sonar.ce.task.projectanalysis.analysis.Branch; @@ -85,38 +84,6 @@ public class IssueLifecycleTest { assertThat(issue.isCopied()).isFalse(); } - @Test - public void set_fromHotspot_flag_for_existing_vulnerability() { - DefaultIssue raw = new DefaultIssue() - .setNew(true) - .setKey("RAW_KEY") - .setType(RuleType.SECURITY_HOTSPOT) - .setCreationDate(parseDate("2015-10-01")) - .setUpdateDate(parseDate("2015-10-02")) - .setCloseDate(parseDate("2015-10-03")); - - DefaultIssue base = new DefaultIssue() - .setKey("BASE_KEY") - .setType(RuleType.VULNERABILITY) - .setResolution(RESOLUTION_FIXED) - .setStatus(STATUS_CLOSED) - .setSeverity(BLOCKER) - .setCreationDate(parseDate("2015-01-01")) - .setUpdateDate(parseDate("2015-01-02")) - .setCloseDate(parseDate("2015-01-03")); - - underTest.mergeExistingOpenIssue(raw, base); - - assertThat(raw.isNew()).isFalse(); - assertThat(raw.key()).isNotNull(); - assertThat(raw.key()).isEqualTo(base.key()); - assertThat(raw.creationDate()).isEqualTo(base.creationDate()); - assertThat(raw.updateDate()).isEqualTo(base.updateDate()); - assertThat(raw.closeDate()).isEqualTo(base.closeDate()); - assertThat(raw.type()).isEqualTo(RuleType.VULNERABILITY); - assertThat(raw.isFromHotspot()).isTrue(); - } - @Test public void mergeIssueFromShortLivingBranch() { DefaultIssue raw = new DefaultIssue() diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java index 90aa92e568b..5a2054cfab4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import org.sonar.api.issue.condition.IsUnResolved; +import org.sonar.api.rules.RuleType; import org.sonar.api.server.ServerSide; import org.sonar.core.issue.DefaultIssue; import org.sonar.db.DbClient; @@ -51,7 +52,7 @@ public class AssignAction extends Action { super(ASSIGN_KEY); this.dbClient = dbClient; this.issueFieldsSetter = issueFieldsSetter; - super.setConditions(new IsUnResolved()); + super.setConditions(new IsUnResolved(), issue -> ((DefaultIssue) issue).type() != RuleType.SECURITY_HOTSPOT); } @Override diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java index 3b905d1011f..cc7fe251dd8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java @@ -21,14 +21,16 @@ package org.sonar.server.issue; import java.util.Collection; import java.util.Map; +import org.sonar.api.issue.Issue; import org.sonar.api.issue.condition.IsUnResolved; +import org.sonar.api.rules.RuleType; import org.sonar.api.server.ServerSide; -import org.sonar.api.web.UserRole; import org.sonar.core.issue.DefaultIssue; import org.sonar.server.user.UserSession; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.isNullOrEmpty; +import static org.sonar.api.web.UserRole.ISSUE_ADMIN; @ServerSide public class SetSeverityAction extends Action { @@ -43,11 +45,12 @@ public class SetSeverityAction extends Action { super(SET_SEVERITY_KEY); this.issueUpdater = issueUpdater; this.userSession = userSession; - super.setConditions(new IsUnResolved(), issue -> isCurrentUserIssueAdmin(issue.projectUuid())); + super.setConditions(new IsUnResolved(), this::isCurrentUserIssueAdminOrSecurityAuditor); } - private boolean isCurrentUserIssueAdmin(String projectUuid) { - return userSession.hasComponentUuidPermission(UserRole.ISSUE_ADMIN, projectUuid); + private boolean isCurrentUserIssueAdminOrSecurityAuditor(Issue issue) { + DefaultIssue defaultIssue = (DefaultIssue) issue; + return ((defaultIssue.type() != RuleType.SECURITY_HOTSPOT && userSession.hasComponentUuidPermission(ISSUE_ADMIN, issue.projectUuid()))); } @Override diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/SetTypeAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/SetTypeAction.java index 193fe499247..8b6d7f67759 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/SetTypeAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/SetTypeAction.java @@ -21,6 +21,7 @@ package org.sonar.server.issue; import java.util.Collection; import java.util.Map; +import org.sonar.api.issue.Issue; import org.sonar.api.issue.condition.IsUnResolved; import org.sonar.api.rules.RuleType; import org.sonar.api.web.UserRole; @@ -42,11 +43,11 @@ public class SetTypeAction extends Action { super(SET_TYPE_KEY); this.issueUpdater = issueUpdater; this.userSession = userSession; - super.setConditions(new IsUnResolved(), issue -> isCurrentUserIssueAdmin(issue.projectUuid())); + super.setConditions(new IsUnResolved(), this::isCurrentUserIssueAdmin); } - private boolean isCurrentUserIssueAdmin(String projectUuid) { - return userSession.hasComponentUuidPermission(UserRole.ISSUE_ADMIN, projectUuid); + private boolean isCurrentUserIssueAdmin(Issue issue) { + return !((DefaultIssue) issue).isFromHotspot() && userSession.hasComponentUuidPermission(UserRole.ISSUE_ADMIN, issue.projectUuid()); } @Override diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Function.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Function.java index c289816ba5e..77d24bc593e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Function.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Function.java @@ -21,6 +21,7 @@ package org.sonar.server.issue.workflow; import javax.annotation.Nullable; import org.sonar.api.issue.Issue; +import org.sonar.api.rules.RuleType; import org.sonar.db.user.UserDto; interface Function { @@ -34,6 +35,8 @@ interface Function { Context setCloseDate(boolean b); Context setLine(@Nullable Integer line); + + Context setType(@Nullable RuleType type); } void execute(Context context); diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/FunctionExecutor.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/FunctionExecutor.java index bb76fad9de2..d92fa575d80 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/FunctionExecutor.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/FunctionExecutor.java @@ -22,6 +22,7 @@ package org.sonar.server.issue.workflow; import javax.annotation.Nullable; import org.sonar.api.ce.ComputeEngineSide; import org.sonar.api.issue.Issue; +import org.sonar.api.rules.RuleType; import org.sonar.api.server.ServerSide; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.IssueChangeContext; @@ -86,5 +87,11 @@ public class FunctionExecutor { updater.setLine(issue, line); return this; } + + @Override + public Function.Context setType(RuleType type) { + updater.setType(issue, type, changeContext); + return this; + } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/HasType.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/HasType.java new file mode 100644 index 00000000000..4767298a0e5 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/HasType.java @@ -0,0 +1,45 @@ +/* + * 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. + */ +package org.sonar.server.issue.workflow; + +import java.util.EnumSet; +import java.util.Set; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.condition.Condition; +import org.sonar.api.rules.RuleType; +import org.sonar.core.issue.DefaultIssue; + +import static java.util.Arrays.asList; + +public class HasType implements Condition { + + private final Set types; + + public HasType(RuleType first, RuleType... others) { + this.types = EnumSet.noneOf(RuleType.class); + this.types.add(first); + this.types.addAll(asList(others)); + } + + @Override + public boolean matches(Issue issue) { + return types.contains(((DefaultIssue) issue).type()); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsManualVulnerability.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsManualVulnerability.java new file mode 100644 index 00000000000..7eadb25831f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsManualVulnerability.java @@ -0,0 +1,37 @@ +/* + * 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. + */ +package org.sonar.server.issue.workflow; + +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.condition.Condition; +import org.sonar.api.rules.RuleType; +import org.sonar.core.issue.DefaultIssue; + +/** + * The vulnerability originally come from a hotspot that was moved to vulnerability by a security auditor. + */ +enum IsManualVulnerability implements Condition { + INSTANCE; + + @Override + public boolean matches(Issue issue) { + return ((DefaultIssue) issue).type() == RuleType.VULNERABILITY && ((DefaultIssue) issue).isFromHotspot(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsNotHotspotNorManualVulnerability.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsNotHotspotNorManualVulnerability.java new file mode 100644 index 00000000000..af9c359247c --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsNotHotspotNorManualVulnerability.java @@ -0,0 +1,34 @@ +/* + * 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. + */ +package org.sonar.server.issue.workflow; + +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.condition.Condition; +import org.sonar.api.rules.RuleType; +import org.sonar.core.issue.DefaultIssue; + +enum IsNotHotspotNorManualVulnerability implements Condition { + INSTANCE; + + @Override + public boolean matches(Issue issue) { + return ((DefaultIssue) issue).type() != RuleType.SECURITY_HOTSPOT && !((DefaultIssue) issue).isFromHotspot(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java index 0c22a8a0935..aec49fbf75e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java @@ -26,6 +26,7 @@ import org.sonar.api.issue.DefaultTransitions; import org.sonar.api.issue.Issue; import org.sonar.api.issue.condition.HasResolution; import org.sonar.api.issue.condition.NotCondition; +import org.sonar.api.rules.RuleType; import org.sonar.api.server.ServerSide; import org.sonar.api.web.UserRole; import org.sonar.core.issue.DefaultIssue; @@ -57,52 +58,64 @@ public class IssueWorkflow implements Startable { buildManualTransitions(builder); buildAutomaticTransitions(builder); + buildSecurityHotspotTransitions(builder); machine = builder.build(); } private static void buildManualTransitions(StateMachine.Builder builder) { - builder.transition(Transition.builder(DefaultTransitions.CONFIRM) - .from(Issue.STATUS_OPEN).to(Issue.STATUS_CONFIRMED) - .functions(new SetResolution(null)) - .build()) + builder + .transition(Transition.builder(DefaultTransitions.CONFIRM) + .from(Issue.STATUS_OPEN).to(Issue.STATUS_CONFIRMED) + .conditions(IsNotHotspotNorManualVulnerability.INSTANCE) + .functions(new SetResolution(null)) + .build()) .transition(Transition.builder(DefaultTransitions.CONFIRM) .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CONFIRMED) + .conditions(IsNotHotspotNorManualVulnerability.INSTANCE) .functions(new SetResolution(null)) .build()) .transition(Transition.builder(DefaultTransitions.UNCONFIRM) .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_REOPENED) + .conditions(IsNotHotspotNorManualVulnerability.INSTANCE) .functions(new SetResolution(null)) .build()) .transition(Transition.builder(DefaultTransitions.RESOLVE) .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED) + .conditions(IsNotHotspotNorManualVulnerability.INSTANCE) .functions(new SetResolution(Issue.RESOLUTION_FIXED)) .build()) .transition(Transition.builder(DefaultTransitions.RESOLVE) .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED) + .conditions(IsNotHotspotNorManualVulnerability.INSTANCE) .functions(new SetResolution(Issue.RESOLUTION_FIXED)) .build()) .transition(Transition.builder(DefaultTransitions.RESOLVE) .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED) + .conditions(IsNotHotspotNorManualVulnerability.INSTANCE) .functions(new SetResolution(Issue.RESOLUTION_FIXED)) .build()) .transition(Transition.builder(DefaultTransitions.REOPEN) .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED) + .conditions(IsNotHotspotNorManualVulnerability.INSTANCE) .functions(new SetResolution(null)) .build()) // resolve as false-positive .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE) .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED) + .conditions(IsNotHotspotNorManualVulnerability.INSTANCE) .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE) .requiredProjectPermission(UserRole.ISSUE_ADMIN) .build()) .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE) .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED) + .conditions(IsNotHotspotNorManualVulnerability.INSTANCE) .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE) .requiredProjectPermission(UserRole.ISSUE_ADMIN) .build()) .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE) .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED) + .conditions(IsNotHotspotNorManualVulnerability.INSTANCE) .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE) .requiredProjectPermission(UserRole.ISSUE_ADMIN) .build()) @@ -110,19 +123,90 @@ public class IssueWorkflow implements Startable { // resolve as won't fix .transition(Transition.builder(DefaultTransitions.WONT_FIX) .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED) + .conditions(IsNotHotspotNorManualVulnerability.INSTANCE) .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE) .requiredProjectPermission(UserRole.ISSUE_ADMIN) .build()) .transition(Transition.builder(DefaultTransitions.WONT_FIX) .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED) + .conditions(IsNotHotspotNorManualVulnerability.INSTANCE) .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE) .requiredProjectPermission(UserRole.ISSUE_ADMIN) .build()) .transition(Transition.builder(DefaultTransitions.WONT_FIX) .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED) + .conditions(IsNotHotspotNorManualVulnerability.INSTANCE) .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE) .requiredProjectPermission(UserRole.ISSUE_ADMIN) .build()); + } + + private static void buildSecurityHotspotTransitions(StateMachine.Builder builder) { + builder + .transition(Transition.builder(DefaultTransitions.DETECT) + .from(Issue.STATUS_OPEN).to(Issue.STATUS_OPEN) + .conditions(new HasType(RuleType.SECURITY_HOTSPOT)) + .functions(new SetType(RuleType.VULNERABILITY)) + .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission + .build()) + .transition(Transition.builder(DefaultTransitions.DETECT) + .from(Issue.STATUS_REOPENED).to(Issue.STATUS_OPEN) + .conditions(new HasType(RuleType.SECURITY_HOTSPOT)) + .functions(new SetType(RuleType.VULNERABILITY)) + .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission + .build()) + .transition(Transition.builder(DefaultTransitions.DETECT) + .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_OPEN) + .conditions(new HasType(RuleType.SECURITY_HOTSPOT), new HasResolution(Issue.RESOLUTION_WONT_FIX)) + .functions(new SetType(RuleType.VULNERABILITY), new SetResolution(null)) + .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission + .build()) + .transition(Transition.builder(DefaultTransitions.DISMISS) + .from(Issue.STATUS_OPEN).to(Issue.STATUS_REOPENED) + .conditions(IsManualVulnerability.INSTANCE) + .functions(new SetType(RuleType.SECURITY_HOTSPOT)) + .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission + .build()) + .transition(Transition.builder(DefaultTransitions.REQUEST_REVIEW) + .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED) + .conditions(IsManualVulnerability.INSTANCE) + .functions(new SetType(RuleType.SECURITY_HOTSPOT), new SetResolution(Issue.RESOLUTION_FIXED)) + .build()) + .transition(Transition.builder(DefaultTransitions.REQUEST_REVIEW) + .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED) + .conditions(IsManualVulnerability.INSTANCE) + .functions(new SetType(RuleType.SECURITY_HOTSPOT), new SetResolution(Issue.RESOLUTION_FIXED)) + .build()) + .transition(Transition.builder(DefaultTransitions.REJECT) + .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED) + .conditions(new HasType(RuleType.SECURITY_HOTSPOT), new HasResolution(Issue.RESOLUTION_FIXED)) + .functions(new SetType(RuleType.VULNERABILITY), new SetResolution(null)) + .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission + .build()) + .transition(Transition.builder(DefaultTransitions.ACCEPT) + .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_RESOLVED) + .conditions(new HasType(RuleType.SECURITY_HOTSPOT), new HasResolution(Issue.RESOLUTION_FIXED)) + .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX)) + .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission + .build()) + .transition(Transition.builder(DefaultTransitions.CLEAR) + .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED) + .conditions(new HasType(RuleType.SECURITY_HOTSPOT)) + .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX)) + .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission + .build()) + .transition(Transition.builder(DefaultTransitions.CLEAR) + .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED) + .conditions(new HasType(RuleType.SECURITY_HOTSPOT)) + .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX)) + .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission + .build()) + .transition(Transition.builder(DefaultTransitions.REOPEN_HOTSPOT) + .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED) + .conditions(new HasType(RuleType.SECURITY_HOTSPOT), new HasResolution(Issue.RESOLUTION_WONT_FIX)) + .functions(new SetResolution(null)) + .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission + .build()); } @@ -157,7 +241,7 @@ public class IssueWorkflow implements Startable { // Reopen issues that are marked as resolved but that are still alive. .transition(Transition.builder("automaticreopen") .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED) - .conditions(new NotCondition(IsBeingClosed.INSTANCE), new HasResolution(Issue.RESOLUTION_FIXED)) + .conditions(new NotCondition(IsBeingClosed.INSTANCE), new HasResolution(Issue.RESOLUTION_FIXED), IsNotHotspotNorManualVulnerability.INSTANCE) .functions(new SetResolution(null), new SetCloseDate(false)) .automatic() .build()); diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetType.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetType.java new file mode 100644 index 00000000000..47f3487c613 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetType.java @@ -0,0 +1,36 @@ +/* + * 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. + */ +package org.sonar.server.issue.workflow; + +import javax.annotation.Nullable; +import org.sonar.api.rules.RuleType; + +public class SetType implements Function { + private final RuleType type; + + public SetType(@Nullable RuleType type) { + this.type = type; + } + + @Override + public void execute(Context context) { + context.setType(type); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java index ce8714515b8..a44ea87e12a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java @@ -26,6 +26,7 @@ import java.util.Optional; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.apache.commons.lang.BooleanUtils; +import org.sonar.api.rules.RuleType; import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; @@ -115,6 +116,7 @@ public class AssignAction implements IssuesWsAction { try (DbSession dbSession = dbClient.openSession(false)) { IssueDto issueDto = issueFinder.getByKey(dbSession, issueKey); DefaultIssue issue = issueDto.toDefaultIssue(); + checkArgument(issue.type() != RuleType.SECURITY_HOTSPOT,"It is not allowed to assign a security hotspot"); UserDto user = getUser(dbSession, login); if (user != null) { checkMembership(dbSession, issueDto, user); diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java index 17c2e938dfb..37d5fda28f2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java @@ -68,11 +68,13 @@ public class DoTransitionAction implements IssuesWsAction { public void define(WebService.NewController controller) { WebService.NewAction action = controller.createAction(ACTION_DO_TRANSITION) .setDescription("Do workflow transition on an issue. Requires authentication and Browse permission on project.
" + - "The transitions '" + DefaultTransitions.WONT_FIX + "' and '" + DefaultTransitions.FALSE_POSITIVE + "' require the permission 'Administer Issues'.") + "The transitions '" + DefaultTransitions.WONT_FIX + "' and '" + DefaultTransitions.FALSE_POSITIVE + "' require the permission 'Administer Issues'.
" + + "The transitions involving security hotspots (except '" + DefaultTransitions.REQUEST_REVIEW + "') require the permission 'Administer Security Hotspot'.") .setSince("3.6") .setChangelog( new Change("6.5", "the database ids of the components are removed from the response"), - new Change("6.5", "the response field components.uuid is deprecated. Use components.key instead.")) + new Change("6.5", "the response field components.uuid is deprecated. Use components.key instead."), + new Change("7.3", "added transitions for security hotspots")) .setHandler(this) .setResponseExample(Resources.getResource(this.getClass(), "do_transition-example.json")) .setPost(true); diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java index 5fe1c04436f..f12eb753de2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java @@ -173,7 +173,8 @@ public class SearchAction implements IssuesWsAction { new Change("5.5", "parameters 'reporters', 'actionPlans' and 'planned' are dropped and therefore ignored (drop of action plan and manual issue features)"), new Change("5.5", "response field 'debt' is renamed 'effort'"), new Change("7.2", "response field 'externalRuleEngine' added to issues that have been imported from an external rule engine"), - new Change("7.2", format("value '%s' in parameter '%s' is deprecated, it won't have any effect", SORT_BY_ASSIGNEE, Param.SORT))) + new Change("7.2", format("value '%s' in parameter '%s' is deprecated, it won't have any effect", SORT_BY_ASSIGNEE, Param.SORT)), + new Change("7.3", "response field 'fromHotspot' added to issues that are security hotspots")) .setResponseExample(getClass().getResource("search-example.json")); action.addPagingParams(100, MAX_LIMIT); diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseData.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseData.java index b5acc861c56..e39cbff71ef 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseData.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseData.java @@ -144,7 +144,7 @@ public class SearchResponseData { } } - public void addActions(String issueKey, List actions) { + public void addActions(String issueKey, Iterable actions) { actionsByIssueKey.putAll(issueKey, actions); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java index 4bfbc23ff8d..2bf22c04931 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java @@ -179,6 +179,7 @@ public class SearchResponseFormat { if (dto.isExternal()) { issueBuilder.setExternalRuleEngine(engineNameFrom(dto.getRuleKey())); } + issueBuilder.setFromHotspot(dto.isFromHotspot()); issueBuilder.setSeverity(Common.Severity.valueOf(dto.getSeverity())); setNullable(data.getUserByUuid(dto.getAssigneeUuid()), assignee -> issueBuilder.setAssignee(assignee.getLogin())); setNullable(emptyToNull(dto.getResolution()), issueBuilder::setResolution); diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java index e66f427cebb..dfe4fea1788 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java @@ -36,6 +36,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nullable; import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.RuleType; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.DbClient; @@ -53,8 +54,8 @@ import org.sonarqube.ws.client.issue.IssuesWsParameters; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.collect.ImmutableSet.copyOf; -import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Sets.difference; +import static com.google.common.collect.Sets.newHashSet; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; @@ -267,20 +268,25 @@ public class SearchResponseLoader { } } - private List listAvailableActions(IssueDto issue, ComponentDto project) { - List availableActions = newArrayList(); + private Set listAvailableActions(IssueDto issue, ComponentDto project) { + Set availableActions = newHashSet(); String login = userSession.getLogin(); if (login == null) { - return Collections.emptyList(); + return Collections.emptySet(); } + RuleType ruleType = RuleType.valueOf(issue.getType()); availableActions.add(COMMENT_KEY); if (issue.getResolution() != null) { return availableActions; } - availableActions.add(ASSIGN_KEY); + if (ruleType != RuleType.SECURITY_HOTSPOT) { + availableActions.add(ASSIGN_KEY); + } availableActions.add("set_tags"); - if (userSession.hasComponentPermission(ISSUE_ADMIN, project)) { + if (!issue.isFromHotspot() && userSession.hasComponentPermission(ISSUE_ADMIN, project)) { availableActions.add(SET_TYPE_KEY); + } + if ((ruleType != RuleType.SECURITY_HOTSPOT && userSession.hasComponentPermission(ISSUE_ADMIN, project))) { availableActions.add(SET_SEVERITY_KEY); } return availableActions; diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java index 241b6be963f..5675ac9b730 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java @@ -106,6 +106,9 @@ public class SetTypeAction implements IssuesWsAction { private SearchResponseData setType(DbSession session, String issueKey, RuleType ruleType) { IssueDto issueDto = issueFinder.getByKey(session, issueKey); DefaultIssue issue = issueDto.toDefaultIssue(); + if (issue.isFromHotspot()) { + throw new IllegalArgumentException("Changing type of a security hotspot is not permitted"); + } userSession.checkComponentUuidPermission(ISSUE_ADMIN, issue.projectUuid()); IssueChangeContext context = IssueChangeContext.createUser(new Date(system2.now()), userSession.getUuid()); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java index bc5aedea1d1..1a02b737dea 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java @@ -27,6 +27,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.issue.Issue; +import org.sonar.api.rules.RuleType; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.FieldDiffs; import org.sonar.core.issue.IssueChangeContext; @@ -102,6 +103,16 @@ public class SetSeverityActionTest { assertThat(action.supports(issue.setResolution(Issue.RESOLUTION_FIXED))).isFalse(); } + @Test + public void doesnt_support_security_hotspots() { + IssueDto issueDto = newIssue().setSeverity(MAJOR); + DefaultIssue issue = issueDto.toDefaultIssue(); + setUserWithBrowseAndAdministerIssuePermission(issueDto); + + assertThat(action.supports(issue.setType(RuleType.CODE_SMELL))).isTrue(); + assertThat(action.supports(issue.setType(RuleType.SECURITY_HOTSPOT))).isFalse(); + } + @Test public void support_only_issues_with_issue_admin_permission() { IssueDto authorizedIssue = newIssue().setSeverity(MAJOR); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AssignActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AssignActionTest.java index e9a1efd5cfe..fa8786037e6 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AssignActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AssignActionTest.java @@ -24,6 +24,7 @@ import javax.annotation.Nullable; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.sonar.api.rules.RuleType; import org.sonar.api.utils.internal.TestSystem2; import org.sonar.db.DbClient; import org.sonar.db.DbSession; @@ -197,6 +198,21 @@ public class AssignActionTest { .execute(); } + @Test + public void fail_when_trying_to_assign_hotspot() { + IssueDto issueDto = db.issues().insertIssue(i -> i.setType(RuleType.SECURITY_HOTSPOT)); + setUserWithBrowsePermission(issueDto); + UserDto arthur = insertUser("arthur"); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("It is not allowed to assign a security hotspot"); + + ws.newRequest() + .setParam("issue", issueDto.getKey()) + .setParam("assignee", "arthur") + .execute(); + } + @Test public void fail_when_assignee_is_disabled() { IssueDto issue = newIssueWithBrowsePermission(); @@ -239,7 +255,7 @@ public class AssignActionTest { @Test public void fail_when_assignee_is_not_member_of_organization_of_project_issue() { OrganizationDto org = db.organizations().insert(organizationDto -> organizationDto.setKey("Organization key")); - IssueDto issueDto = db.issues().insertIssue(org); + IssueDto issueDto = db.issues().insertIssue(org, i -> i.setType(RuleType.CODE_SMELL)); setUserWithBrowsePermission(issueDto); OrganizationDto otherOrganization = db.organizations().insert(); UserDto assignee = db.users().insertUser("arthur"); @@ -261,11 +277,12 @@ public class AssignActionTest { } private IssueDto newIssue(String assignee) { - IssueDto issue = db.issues().insertIssue( + IssueDto issue = db.issues().insertIssue( issueDto -> issueDto .setAssigneeUuid(assignee) .setCreatedAt(PAST).setIssueCreationTime(PAST) - .setUpdatedAt(PAST).setIssueUpdateTime(PAST)); + .setUpdatedAt(PAST).setIssueUpdateTime(PAST) + .setType(RuleType.CODE_SMELL)); return issue; } diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java index d6fe59d9f61..710fa2131ab 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java @@ -64,6 +64,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.sonar.api.rules.RuleType.BUG; import static org.sonar.api.rules.RuleType.CODE_SMELL; +import static org.sonar.api.rules.RuleType.VULNERABILITY; import static org.sonar.api.web.UserRole.ISSUE_ADMIN; import static org.sonar.api.web.UserRole.USER; import static org.sonar.core.util.Protobuf.setNullable; @@ -118,6 +119,18 @@ public class SetTypeActionTest { .containsExactlyInAnyOrder(issueDto.getComponentUuid()); } + @Test + public void prevent_changing_type_security_hotspot() { + long now = 1_999_777_234L; + when(system2.now()).thenReturn(now); + IssueDto issueDto = issueDbTester.insertIssue(newIssue().setType(VULNERABILITY).setIsFromHotspot(true)); + setUserWithBrowseAndAdministerIssuePermission(issueDto); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Changing type of a security hotspot is not permitted"); + call(issueDto.getKey(), BUG.name()); + } + @Test public void insert_entry_in_changelog_when_setting_type() { IssueDto issueDto = issueDbTester.insertIssue(newIssue().setType(CODE_SMELL)); diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index 75ac42d3237..15639fc96c3 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -231,6 +231,7 @@ export interface Issue { fromExternalRule?: boolean; key: string; flows: FlowLocation[][]; + fromHotspot: boolean; line?: number; message: string; organization: string; diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.tsx index 639ab681d9b..549928cb0ea 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.tsx @@ -40,7 +40,8 @@ const issue = { status: '', type: '', secondaryLocations: [], - flows: [] + flows: [], + fromHotspot: false }; it('should render', () => { diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.tsx.snap index ee41d9fd306..744a5418ee6 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.tsx.snap @@ -14,6 +14,7 @@ exports[`should render 1`] = ` "componentUuid": "", "creationDate": "", "flows": Array [], + "fromHotspot": false, "key": "", "message": "", "organization": "", diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx index c9dc513d817..5fd8d5e434a 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx @@ -30,6 +30,7 @@ const issueBase: Issue = { creationDate: '', key: '', flows: [], + fromHotspot: false, message: '', organization: '', project: '', diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx index b4fd9fc6e70..d63bbf1a2be 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx @@ -31,6 +31,7 @@ const issueBase: Issue = { creationDate: '', key: '', flows: [], + fromHotspot: false, message: '', organization: '', project: '', diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.tsx index 7e9c9ec5bbe..bd621904bb9 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.tsx @@ -30,6 +30,7 @@ const issueBase: Issue = { creationDate: '', key: '', flows: [], + fromHotspot: false, message: '', organization: '', project: '', diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap index dc0f5c92200..5546edb6645 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap @@ -53,6 +53,7 @@ exports[`render code 1`] = ` "componentUuid": "", "creationDate": "", "flows": Array [], + "fromHotspot": false, "key": "issue-1", "message": "", "organization": "", @@ -74,6 +75,7 @@ exports[`render code 1`] = ` "componentUuid": "", "creationDate": "", "flows": Array [], + "fromHotspot": false, "key": "issue-2", "message": "", "organization": "", diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesList-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesList-test.tsx.snap index d19a4e96ef0..2fa14dfb1b3 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesList-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesList-test.tsx.snap @@ -15,6 +15,7 @@ exports[`render issues list 1`] = ` "componentUuid": "", "creationDate": "", "flows": Array [], + "fromHotspot": false, "key": "foo", "message": "", "organization": "", @@ -47,6 +48,7 @@ exports[`render issues list 1`] = ` "componentUuid": "", "creationDate": "", "flows": Array [], + "fromHotspot": false, "key": "bar", "message": "", "organization": "", diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.js b/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.js index f12909c0f9a..10d0d20281b 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.js +++ b/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.js @@ -89,6 +89,7 @@ export default class IssueActionsBar extends React.PureComponent { const canAssign = issue.actions.includes('assign'); const canComment = issue.actions.includes('comment'); const canSetSeverity = issue.actions.includes('set_severity'); + const canSetType = issue.actions.includes('set_type'); const canSetTags = issue.actions.includes('set_tags'); const hasTransitions = issue.transitions && issue.transitions.length > 0; @@ -97,8 +98,8 @@ export default class IssueActionsBar extends React.PureComponent {
  • )} + {this.props.manualVulnerability && ( + +
    + {translate('issue.manual_vulnerability')} +
    +
    + )} ); } diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js index 1781e6d637d..3e1bea15210 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js +++ b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js @@ -75,6 +75,7 @@ export default function IssueTitleBar(props /*: Props */) {
    Promise<*>, string) => void, @@ -55,12 +55,12 @@ export default class IssueType extends React.PureComponent { render() { const { issue } = this.props; - if (this.props.canSetSeverity) { + if (this.props.canSetType) { return (
    }>