]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10577 return if an issue is from an external analyser in issue search
authorGuillaume Jambet <guillaume.jambet@sonarsource.com>
Fri, 13 Apr 2018 09:25:59 +0000 (11:25 +0200)
committerSonarTech <sonartech@sonarsource.com>
Thu, 26 Apr 2018 18:20:51 +0000 (20:20 +0200)
19 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDtoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleTesting.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/commonrule/CommonRule.java
server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
server/sonar-server/src/main/java/org/sonar/server/rule/ExternalRuleCreator.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueAssignerTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/TransitionActionTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/ws/BulkChangeActionTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java
server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/response_contains_all_fields_except_additional_fields.json
sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java
sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueBuilderTest.java
sonar-ws/src/main/protobuf/ws-issues.proto

index a603618f441d595cc07107806c5df45d2be41e1a..0213e3106e5adcc44548b720dd977ca203086c1d 100644 (file)
@@ -91,6 +91,7 @@ public final class IssueDto implements Serializable {
   // joins
   private String ruleKey;
   private String ruleRepo;
+  private boolean isExternal;
   private String language;
   private String componentKey;
   private String moduleUuid;
@@ -119,6 +120,7 @@ public final class IssueDto implements Serializable {
       .setAssignee(issue.assignee())
       .setRuleId(ruleId)
       .setRuleKey(issue.ruleKey().repository(), issue.ruleKey().rule())
+      .setExternal(issue.isFromExternalRuleEngine())
       .setTags(issue.tags())
       .setComponentUuid(issue.componentUuid())
       .setComponentKey(issue.componentKey())
@@ -166,6 +168,7 @@ public final class IssueDto implements Serializable {
       .setIssueAttributes(KeyValueFormat.format(issue.attributes()))
       .setAuthorLogin(issue.authorLogin())
       .setRuleKey(issue.ruleKey().repository(), issue.ruleKey().rule())
+      .setExternal(issue.isFromExternalRuleEngine())
       .setTags(issue.tags())
       .setComponentUuid(issue.componentUuid())
       .setComponentKey(issue.componentKey())
@@ -185,6 +188,7 @@ public final class IssueDto implements Serializable {
   public static IssueDto createFor(Project project, RuleDto rule) {
     return new IssueDto()
       .setProjectUuid(project.getUuid())
+      .setExternal(rule.isExternal())
       .setRuleId(rule.getId())
       .setKee(Uuids.create());
   }
@@ -455,6 +459,7 @@ public final class IssueDto implements Serializable {
     this.ruleKey = rule.getRuleKey();
     this.ruleRepo = rule.getRepositoryKey();
     this.language = rule.getLanguage();
+    this.isExternal = rule.isExternal();
     return this;
   }
 
@@ -480,6 +485,15 @@ public final class IssueDto implements Serializable {
     return this;
   }
 
+  public boolean isExternal() {
+    return isExternal;
+  }
+
+  public IssueDto setExternal(boolean external) {
+    isExternal = external;
+    return this;
+  }
+
   public String getComponentKey() {
     return componentKey;
   }
@@ -719,6 +733,7 @@ public final class IssueDto implements Serializable {
     issue.setUpdateDate(longToDate(issueUpdateDate));
     issue.setSelectedAt(selectedAt);
     issue.setLocations(parseLocations());
+    issue.setFromExternalRuleEngine(isExternal);
     return issue;
   }
 }
index a077a1e17caa42d435c3b3190500829e8b2badc2..5f2e0f482cbb4840077f00d861457319185af188 100644 (file)
@@ -27,6 +27,7 @@
     i.issue_close_date as issueCloseTime,
     i.created_at as createdAt,
     i.updated_at as updatedAt,
+    r.is_external as "isExternal",
     r.plugin_rule_key as ruleKey,
     r.plugin_name as ruleRepo,
     r.language as language,
@@ -84,6 +85,7 @@
     i.issue_close_date as "issueCloseDate",
     i.issue_creation_date as "issueCreationDate",
     i.issue_update_date as "issueUpdateDate",
+    r.is_external as "isExternal",
     r.plugin_name as "pluginName",
     r.plugin_rule_key as "pluginRuleKey",
     r.language,
index 9791628fc34d99e2d33f2cb74664344d12ae24d3..a96d88a90314616af9239306f72da8d2a2ec9fba 100644 (file)
@@ -100,6 +100,7 @@ public class IssueDaoTest {
     assertThat(issue.getProjectKey()).isEqualTo(PROJECT_KEY);
     assertThat(issue.getLocations()).isNull();
     assertThat(issue.parseLocations()).isNull();
+    assertThat(issue.isExternal()).isTrue();
   }
 
   @Test
@@ -304,7 +305,7 @@ public class IssueDaoTest {
   }
 
   private void prepareTables() {
-    db.rules().insertRule(RULE);
+    db.rules().insertRule(RULE.setIsExternal(true));
     OrganizationDto organizationDto = db.organizations().insert();
     ComponentDto projectDto = db.components().insertPrivateProject(organizationDto, (t) -> t.setUuid(PROJECT_UUID).setDbKey(PROJECT_KEY));
     db.components().insertComponent(newFileDto(projectDto).setUuid(FILE_UUID).setDbKey(FILE_KEY));
index c9ef120abdecf1a2b728ceb30215af2b71327b5a..cd323991d89c0eb355e4c2c9715bb2c0ba1e3d9f 100644 (file)
@@ -116,7 +116,7 @@ public class IssueDtoTest {
   public void set_rule() {
     IssueDto dto = new IssueDto()
       .setKee("100")
-      .setRule(new RuleDefinitionDto().setId(1).setRuleKey("AvoidCycle").setRepositoryKey("squid"))
+      .setRule(new RuleDefinitionDto().setId(1).setRuleKey("AvoidCycle").setRepositoryKey("squid").setIsExternal(true))
       .setLanguage("xoo");
 
     assertThat(dto.getRuleId()).isEqualTo(1);
@@ -124,6 +124,7 @@ public class IssueDtoTest {
     assertThat(dto.getRule()).isEqualTo("AvoidCycle");
     assertThat(dto.getRuleKey().toString()).isEqualTo("squid:AvoidCycle");
     assertThat(dto.getLanguage()).isEqualTo("xoo");
+    assertThat(dto.isExternal()).isEqualTo(true);
   }
 
   @Test
index 77b70a3ecffde54487fc6fdb14e6abbed4eb4cc2..4a90d86466d79ee3d4169826679405f47289b592 100644 (file)
@@ -41,12 +41,14 @@ import static java.util.Objects.requireNonNull;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.apache.commons.lang.math.RandomUtils.nextInt;
+import static org.sonar.api.rule.RuleKey.EXTERNAL_RULE_REPO_PREFIX;
 
 /**
  * Utility class for tests involving rules
  */
 public class RuleTesting {
 
+  public static final RuleKey EXTERNAL_XOO = RuleKey.of(EXTERNAL_RULE_REPO_PREFIX + "xoo", "x1");
   public static final RuleKey XOO_X1 = RuleKey.of("xoo", "x1");
   public static final RuleKey XOO_X2 = RuleKey.of("xoo", "x2");
   public static final RuleKey XOO_X3 = RuleKey.of("xoo", "x3");
index 0b56e4b21dfcb9c7426a4f09bde44e7f71ad0eeb..9f1a84bafaaf2a00ccff9bf1c77d4497ceb471cc 100644 (file)
@@ -55,6 +55,7 @@ public abstract class CommonRule {
         issue.setSeverity(activeRule.get().getSeverity());
         issue.setLine(null);
         issue.setChecksum("");
+        issue.setFromExternalRuleEngine(false);
       }
     }
     return issue;
index 444b105017b644b92bde6f6f2b1995e41010a16d..f61f8c12d1eaa4d79d29895c5de90efd2c739bb1 100644 (file)
@@ -21,9 +21,12 @@ package org.sonar.server.issue;
 
 import java.util.Collection;
 import java.util.Map;
+
+import com.google.common.base.Preconditions;
 import org.sonar.api.server.ServerSide;
 import org.sonar.core.issue.DefaultIssue;
 import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.issue.workflow.Transition;
 import org.sonar.server.user.UserSession;
 
@@ -62,6 +65,9 @@ public class TransitionAction extends Action {
   }
 
   private boolean canExecuteTransition(DefaultIssue issue, String transitionKey) {
+
+    checkArgument(!issue.isFromExternalRuleEngine(), "No transition allowed on issue from externally define rule");
+
     return transitionService.listTransitions(issue)
       .stream()
       .map(Transition::key)
index 3bd129ba174f49d813af5436ccc92ee46ea85c03..893880956fbe7ac3d0f28e53d3e20cc058bfe8a9 100644 (file)
@@ -162,8 +162,9 @@ public class SearchAction implements IssuesWsAction {
         new Change("6.3", "response field 'email' is renamed 'avatar'"),
         new Change("5.5", "response fields 'reporter' and 'actionPlan' are removed (drop of action plan and manual issue features)"),
         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'"))
-      .setResponseExample(getClass().getResource("search-example.json"));
+        new Change("5.5", "response field 'debt' is renamed 'effort'"),
+        new Change("7.2", "response 'issue' now indicates if issue has been created by an externally defined rule engine and his name through the optional 'externalRuleEngine' field")
+      ).setResponseExample(getClass().getResource("search-example.json"));
 
     action.addPagingParams(100, MAX_LIMIT);
     action.createParam(Param.FACETS)
index ff6dea2e454e580d78f6cbee15de5e0f09741611..e55422e4b7d2ba251e05bf869a59aaa8bcf7c60b 100644 (file)
@@ -30,6 +30,7 @@ import java.util.Set;
 import javax.annotation.Nullable;
 import org.sonar.api.resources.Language;
 import org.sonar.api.resources.Languages;
+import org.sonar.api.rule.RuleKey;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.api.utils.Duration;
 import org.sonar.api.utils.Durations;
@@ -59,8 +60,10 @@ import org.sonarqube.ws.Issues.SearchWsResponse;
 import org.sonarqube.ws.Issues.Transitions;
 import org.sonarqube.ws.Issues.Users;
 
+import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.base.Strings.emptyToNull;
 import static com.google.common.base.Strings.nullToEmpty;
+import static org.sonar.api.rule.RuleKey.EXTERNAL_RULE_REPO_PREFIX;
 import static org.sonar.core.util.Protobuf.setNullable;
 
 public class SearchResponseFormat {
@@ -171,6 +174,9 @@ public class SearchResponseFormat {
       }
     }
     issueBuilder.setRule(dto.getRuleKey().toString());
+    if (dto.isExternal()) {
+      issueBuilder.setExternalRuleEngine(engineNameFrom(dto.getRuleKey()));
+    }
     issueBuilder.setSeverity(Common.Severity.valueOf(dto.getSeverity()));
     setNullable(emptyToNull(dto.getAssignee()), issueBuilder::setAssignee);
     setNullable(emptyToNull(dto.getResolution()), issueBuilder::setResolution);
@@ -192,6 +198,11 @@ public class SearchResponseFormat {
     setNullable(dto.getIssueCloseDate(), issueBuilder::setCloseDate, DateUtils::formatDateTime);
   }
 
+  private String engineNameFrom(RuleKey ruleKey) {
+    checkState(ruleKey.repository().startsWith(EXTERNAL_RULE_REPO_PREFIX));
+    return ruleKey.repository().replace(EXTERNAL_RULE_REPO_PREFIX, "");
+  }
+
   private static void completeIssueLocations(IssueDto dto, Issue.Builder issueBuilder, SearchResponseData data) {
     DbIssues.Locations locations = dto.parseLocations();
     if (locations == null) {
index 6b4234af11e6fe73ffb1b3867b3d55f0c22c1a76..4b8c27d026185bc58f8ff2c4849aaab64d8cbc1d 100644 (file)
@@ -30,6 +30,7 @@ import org.sonar.server.computation.task.projectanalysis.issue.Rule;
 import org.sonar.server.computation.task.projectanalysis.issue.RuleImpl;
 import org.sonar.server.rule.index.RuleIndexer;
 
+import static org.sonar.api.rule.RuleStatus.READY;
 import static org.sonar.db.rule.RuleDto.Scope.ALL;
 
 public class ExternalRuleCreator {
@@ -59,6 +60,7 @@ public class ExternalRuleCreator {
       .setScope(ALL)
       .setStatus(RuleStatus.READY)
       .setSeverity(external.getSeverity())
+      .setStatus(READY)
       .setCreatedAt(system2.now())
       .setUpdatedAt(system2.now()));
 
index 67a3613e930714caa356efa0cb828de743cd4256..5362e2e0fb1ede16e90059a21338fec4ddff4b68 100644 (file)
@@ -213,7 +213,7 @@ public class IssueAssignerTest {
         "moduleUuid=<null>,moduleUuidPath=<null>,projectUuid=<null>,projectKey=<null>,ruleKey=<null>,language=<null>,severity=<null>," +
         "manualSeverity=false,message=<null>,line=2,gap=<null>,effort=<null>,status=<null>,resolution=<null>," +
         "assignee=<null>,checksum=<null>,attributes=<null>,authorLogin=<null>,comments=<null>,tags=<null>," +
-        "locations=<null>,creationDate=<null>,updateDate=<null>,closeDate=<null>,currentChange=<null>,changes=<null>,isNew=true,isCopied=false," +
+        "locations=<null>,isFromExternalRuleEngine=false,creationDate=<null>,updateDate=<null>,closeDate=<null>,currentChange=<null>,changes=<null>,isNew=true,isCopied=false," +
         "beingClosed=false,onDisabledRule=false,isChanged=false,sendNotifications=false,selectedAt=<null>]");
   }
 
index 558684b3fd1202ea03f35d03c5eb6b9c84fecea9..6ada52d8ace36c5e7c2adbfcd418688afe107a72 100644 (file)
@@ -21,7 +21,9 @@ package org.sonar.server.issue;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
+
 import java.util.Date;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -42,6 +44,7 @@ import static java.util.Collections.emptyList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.sonar.api.issue.Issue.STATUS_CLOSED;
 import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.issue.IssueTesting.newDto;
@@ -86,11 +89,11 @@ public class TransitionActionTest {
   @Test
   public void does_not_execute_if_transition_is_not_available() {
     loginAndAddProjectPermission("john", ISSUE_ADMIN);
-    issue.setStatus(Issue.STATUS_CLOSED);
+    issue.setStatus(STATUS_CLOSED);
 
     action.execute(ImmutableMap.of("transition", "reopen"), context);
 
-    assertThat(issue.status()).isEqualTo(Issue.STATUS_CLOSED);
+    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
   }
 
   @Test
@@ -106,6 +109,21 @@ public class TransitionActionTest {
     action.verify(ImmutableMap.of("unknwown", "reopen"), Lists.newArrayList(), userSession);
   }
 
+  @Test
+  public void do_not_allow_transitions_for_issues_from_external_rule_engine() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("No transition allowed on issue from externally define rule");
+
+    loginAndAddProjectPermission("john", ISSUE_ADMIN);
+
+    context.issue()
+      .setFromExternalRuleEngine(true)
+      .setStatus(STATUS_CLOSED);
+
+    action.execute(ImmutableMap.of("transition", "close"), context);
+
+  }
+
   @Test
   public void should_support_all_issues() {
     assertThat(action.supports(new DefaultIssue().setResolution(null))).isTrue();
index b8575618c25308cdc4f3822f881f649e7d4f6adb..9b1aac04b67cff1d8cc47d1e45282740ef1c7312 100644 (file)
@@ -35,13 +35,18 @@ import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDbTester;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.issue.IssueChangeDto;
+import org.sonar.db.issue.IssueDbTester;
 import org.sonar.db.issue.IssueDto;
 import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.rule.RuleDbTester;
+import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.user.UserDto;
 import org.sonar.server.es.EsTester;
+import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.UnauthorizedException;
 import org.sonar.server.issue.Action;
 import org.sonar.server.issue.IssueFieldsSetter;
@@ -86,6 +91,7 @@ import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.issue.IssueChangeDto.TYPE_COMMENT;
 import static org.sonar.db.issue.IssueTesting.newDto;
+import static org.sonar.db.rule.RuleTesting.newRule;
 import static org.sonar.db.rule.RuleTesting.newRuleDto;
 
 public class BulkChangeActionTest {
@@ -114,6 +120,9 @@ public class BulkChangeActionTest {
   private TestIssueChangePostProcessor issueChangePostProcessor = new TestIssueChangePostProcessor();
   private List<Action> actions = new ArrayList<>();
 
+  private RuleDbTester ruleDbTester = new RuleDbTester(db);
+  private IssueDbTester issueDbTester = new IssueDbTester(db);
+
   private RuleDto rule;
   private OrganizationDto organization;
   private ComponentDto project;
@@ -500,6 +509,39 @@ public class BulkChangeActionTest {
       .build());
   }
 
+  @Test
+  public void fail_when_requesting_transition_on_issue_from_external_rules_engine(){
+
+    setUserProjectPermissions(USER, ISSUE_ADMIN);
+    UserDto userToAssign = db.users().insertUser("arthur");
+    db.organizations().addMember(organization, user);
+    db.organizations().addMember(organization, userToAssign);
+
+    RuleDefinitionDto rule = ruleDbTester.insert(newRule()
+      .setIsExternal(true));
+    IssueDto issue1 = issueDbTester.insertIssue(newIssue()
+      .setStatus(STATUS_OPEN)
+      .setResolution(null)
+      .setRuleId(rule.getId())
+      .setRuleKey(rule.getRuleKey(), rule.getRepositoryKey())
+      .setAssignee(user.getLogin())
+      .setType(BUG)
+      .setSeverity(MINOR));
+
+    IssueDto issue2 = issueDbTester.insertIssue(newIssue()
+      .setStatus(STATUS_OPEN)
+      .setResolution(null)
+      .setRuleId(rule.getId())
+      .setRuleKey(rule.getRuleKey(), rule.getRepositoryKey())
+      .setAssignee(user.getLogin())
+      .setType(BUG)
+      .setSeverity(MAJOR));
+
+    BulkChangeWsResponse confirm = call(builder().setIssues(asList(issue1.getKey(), issue2.getKey())).setDoTransition("confirm").build());
+
+    assertThat(confirm.getFailures()).isEqualTo(2);
+  }
+
   @Test
   public void fail_when_number_of_issues_is_more_than_500() {
     userSession.logIn("john");
@@ -591,6 +633,11 @@ public class BulkChangeActionTest {
     return newDto(rule, file, project).setStatus(STATUS_CLOSED).setResolution(RESOLUTION_FIXED);
   }
 
+  private IssueDto newIssue() {
+    RuleDto rule = ruleDbTester.insertRule(newRuleDto());
+    return newDto(rule, file, project);
+  }
+
   private void addActions() {
     actions.add(new org.sonar.server.issue.AssignAction(db.getDbClient(), issueFieldsSetter));
     actions.add(new org.sonar.server.issue.SetSeverityAction(issueFieldsSetter, userSession));
index ffbe2089fa6b0deb7c3741718b5fa801f645a58a..8a866b9d327477ce31d78308c7799d49dd1c332d 100644 (file)
@@ -824,6 +824,16 @@ public class SearchActionTest {
     return rule;
   }
 
+  private RuleDto newExternalRule() {
+    RuleDto rule = RuleTesting.newDto(RuleTesting.EXTERNAL_XOO).setLanguage("xoo")
+      .setName("Rule name")
+      .setDescription("Rule desc")
+      .setIsExternal(true)
+      .setStatus(RuleStatus.READY);
+    db.rules().insert(rule.getDefinition());
+    return rule;
+  }
+
   private void indexPermissions() {
     permissionIndexer.indexOnStartup(permissionIndexer.getIndexTypes());
   }
index f7656e728d26b2af9f478d298e6e939a46745648..b008f9bff742e363d6269cd85b4921a557d16b1f 100644 (file)
@@ -3,7 +3,7 @@
     {
       "organization": "my-org-2",
       "key": "82fd47d4-b650-4037-80bc-7b112bd4eac2",
-      "rule": "xoo:x1",
+      "rule": "external_xoo:x1",
       "severity": "MAJOR",
       "component": "FILE_KEY",
       "resolution": "FIXED",
@@ -19,7 +19,8 @@
         "owasp"
       ],
       "creationDate": "2014-09-04T01:00:00+0200",
-      "updateDate": "2017-12-04T00:00:00+0100"
+      "updateDate": "2017-12-04T00:00:00+0100",
+      "externalRuleEngine" : "xoo"
     }
   ]
 }
index 3192a5304bc5734fe0e30311d9f94e6b60372b7d..0add84d5a82164701bce0c95f7a53fa4bb106810 100644 (file)
@@ -81,6 +81,8 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure.
   private Set<String> tags = null;
   // temporarily an Object as long as DefaultIssue is used by sonar-batch
   private Object locations = null;
+
+  private boolean isFromExternalRuleEngine;
   // FUNCTIONAL DATES
   private Date creationDate;
   private Date updateDate;
@@ -239,6 +241,15 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure.
     return this;
   }
 
+  public boolean isFromExternalRuleEngine() {
+    return isFromExternalRuleEngine;
+  }
+
+  public DefaultIssue setFromExternalRuleEngine(boolean fromExternalRuleEngine) {
+    isFromExternalRuleEngine = fromExternalRuleEngine;
+    return this;
+  }
+
   @Override
   @CheckForNull
   public String message() {
index b436500c041128d8169f24da54a2fb940242eff2..e302c0bd3e321e11382e0b9f521b8ae7bb33647f 100644 (file)
@@ -44,6 +44,7 @@ public class DefaultIssueBuilder implements Issuable.IssueBuilder {
   private String assignee;
   private RuleType type;
   private Map<String, String> attributes;
+  private boolean isFromExternalRuleEngine;
 
   public DefaultIssueBuilder componentKey(String componentKey) {
     this.componentKey = componentKey;
@@ -55,6 +56,11 @@ public class DefaultIssueBuilder implements Issuable.IssueBuilder {
     return this;
   }
 
+  public DefaultIssueBuilder fromExternalRuleEngine(boolean fromExternalRuleEngine) {
+    isFromExternalRuleEngine = fromExternalRuleEngine;
+    return this;
+  }
+
   @Override
   public DefaultIssueBuilder ruleKey(RuleKey ruleKey) {
     this.ruleKey = ruleKey;
@@ -164,6 +170,7 @@ public class DefaultIssueBuilder implements Issuable.IssueBuilder {
     issue.setCopied(false);
     issue.setBeingClosed(false);
     issue.setOnDisabledRule(false);
+    issue.setFromExternalRuleEngine(isFromExternalRuleEngine);
     return issue;
   }
 }
index 76d3001eaa99128f8b1ac284443e3d806b321e17..8c214f8987382c6d2f9cea646fb39759770e460e 100644 (file)
@@ -39,6 +39,7 @@ public class DefaultIssueBuilderTest {
       .line(123)
       .effortToFix(10000.0)
       .ruleKey(RuleKey.of("squid", "NullDereference"))
+      .fromExternalRuleEngine(false)
       .severity(Severity.CRITICAL)
       .attribute("JIRA", "FOO-123")
       .attribute("YOUTRACK", "YT-123")
@@ -53,6 +54,7 @@ public class DefaultIssueBuilderTest {
     assertThat(issue.line()).isEqualTo(123);
     assertThat(issue.ruleKey().repository()).isEqualTo("squid");
     assertThat(issue.ruleKey().rule()).isEqualTo("NullDereference");
+    assertThat(issue.isFromExternalRuleEngine()).isFalse();
     assertThat(issue.severity()).isEqualTo(Severity.CRITICAL);
     assertThat(issue.assignee()).isNull();
     assertThat(issue.isNew()).isTrue();
index 93b99eb4a3c1d58a85d14cbbc45161deb1f929c2..e55bd9864db93c787b290bca92b0055e5ebc0e31 100644 (file)
@@ -157,6 +157,8 @@ message Issue {
   optional string organization = 29;
   optional string branch = 30;
   optional string pullRequest = 32;
+
+  optional string externalRuleEngine = 33;
 }
 
 message Transitions {