aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/DefaultIssueHandlerContext.java86
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java83
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java6
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java4
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueHandlersTest.java15
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java16
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java11
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/issue/DeprecatedViolations.java26
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssues.java5
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java94
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssuesTest.java7
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java28
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java16
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/IssueChange.java67
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java (renamed from sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosedAt.java)41
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java110
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java2
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/workflow/Function.java10
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java75
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java39
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosedDate.java (renamed from sonar-core/src/main/java/org/sonar/core/issue/workflow/UnsetClosedAt.java)12
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/workflow/SetResolution.java10
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/workflow/Transition.java9
-rw-r--r--sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueBuilderTest.java4
-rw-r--r--sonar-core/src/test/java/org/sonar/core/issue/db/IssueDtoTest.java3
-rw-r--r--sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java14
-rw-r--r--sonar-core/src/test/java/org/sonar/core/issue/workflow/SetResolutionTest.java47
-rw-r--r--sonar-core/src/test/java/org/sonar/core/issue/workflow/TransitionTest.java17
-rw-r--r--sonar-core/src/test/java/org/sonar/core/issue/workflow/UnsetClosedAtTest.java38
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueHandler.java20
-rw-r--r--sonar-server/src/main/java/org/sonar/server/issue/JRubyInternalIssues.java30
-rw-r--r--sonar-server/src/main/java/org/sonar/server/issue/ServerIssueActions.java82
-rw-r--r--sonar-server/src/main/java/org/sonar/server/platform/Platform.java4
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb44
34 files changed, 671 insertions, 404 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/DefaultIssueHandlerContext.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/DefaultIssueHandlerContext.java
deleted file mode 100644
index c9810f76603..00000000000
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/DefaultIssueHandlerContext.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.plugins.core.issue;
-
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.IssueHandler;
-import org.sonar.core.issue.DefaultIssue;
-
-import javax.annotation.Nullable;
-
-class DefaultIssueHandlerContext implements IssueHandler.IssueContext {
-
- private final DefaultIssue issue;
-
- DefaultIssueHandlerContext(DefaultIssue issue) {
- this.issue = issue;
- }
-
- @Override
- public Issue issue() {
- return issue;
- }
-
- @Override
- public boolean isNew() {
- return issue.isNew();
- }
-
- @Override
- public boolean isAlive() {
- return issue.isAlive();
- }
-
- @Override
- public IssueHandler.IssueContext setLine(@Nullable Integer line) {
- issue.setLine(line);
- return this;
- }
-
- @Override
- public IssueHandler.IssueContext setDescription(String description) {
- issue.setDescription(description);
- return this;
- }
-
- @Override
- public IssueHandler.IssueContext setSeverity(String severity) {
- issue.setSeverity(severity);
- return this;
- }
-
- @Override
- public IssueHandler.IssueContext setAuthorLogin(@Nullable String login) {
- issue.setAuthorLogin(login);
- return this;
- }
-
- @Override
- public IssueHandler.IssueContext setAttribute(String key, @Nullable String value) {
- issue.setAttribute(key, value);
- return this;
- }
-
- @Override
- public IssueHandler.IssueContext assignTo(@Nullable String login) {
- issue.setAssignee(login);
- return this;
- }
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java
index 3e496edcb69..96be94d7966 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java
@@ -20,25 +20,100 @@
package org.sonar.plugins.core.issue;
import org.sonar.api.BatchExtension;
+import org.sonar.api.issue.Issue;
import org.sonar.api.issue.IssueHandler;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueUpdater;
+
+import javax.annotation.Nullable;
public class IssueHandlers implements BatchExtension {
private final IssueHandler[] handlers;
+ private final DefaultContext context;
- public IssueHandlers(IssueHandler[] handlers) {
+ public IssueHandlers(IssueUpdater updater, IssueHandler[] handlers) {
this.handlers = handlers;
+ this.context = new DefaultContext(updater);
}
- public IssueHandlers() {
- this(new IssueHandler[0]);
+ public IssueHandlers(IssueUpdater updater) {
+ this(updater, new IssueHandler[0]);
}
public void execute(DefaultIssue issue) {
- DefaultIssueHandlerContext context = new DefaultIssueHandlerContext(issue);
+ context.reset(issue);
for (IssueHandler handler : handlers) {
handler.onIssue(context);
}
}
+ static class DefaultContext implements IssueHandler.Context {
+ private final IssueUpdater updater;
+ private DefaultIssue issue;
+
+
+ private DefaultContext(IssueUpdater updater) {
+ this.updater = updater;
+ }
+
+ private void reset(DefaultIssue i) {
+ this.issue = i;
+ }
+
+ @Override
+ public Issue issue() {
+ return issue;
+ }
+
+ @Override
+ public boolean isNew() {
+ return issue.isNew();
+ }
+
+ @Override
+ public boolean isAlive() {
+ return issue.isAlive();
+ }
+
+ @Override
+ public IssueHandler.Context setLine(@Nullable Integer line) {
+ updater.setLine(issue, line);
+ return this;
+ }
+
+ @Override
+ public IssueHandler.Context setDescription(@Nullable String description) {
+ updater.setDescription(issue, description);
+ return this;
+ }
+
+ @Override
+ public IssueHandler.Context setSeverity(String severity) {
+ updater.setSeverity(issue, severity);
+ return this;
+ }
+
+ @Override
+ public IssueHandler.Context setAuthorLogin(@Nullable String login) {
+ updater.setAuthorLogin(issue, login);
+ return this;
+ }
+
+ @Override
+ public IssueHandler.Context setAttribute(String key, @Nullable String value) {
+ throw new UnsupportedOperationException("TODO");
+ }
+
+ @Override
+ public IssueHandler.Context assign(@Nullable String assignee) {
+ updater.assign(issue, assignee);
+ return this;
+ }
+
+ @Override
+ public IssueHandler.Context comment(String message) {
+ return null;
+ }
+ }
+
}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java
index 2c1f78e570e..65485967058 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java
@@ -349,6 +349,8 @@ public class IssueTracking implements BatchExtension {
if (pastIssue.isManualSeverity()) {
newIssue.setManualSeverity(true);
newIssue.setSeverity(pastIssue.getSeverity());
+ } else if (!Objects.equal(pastIssue.getSeverity(), newIssue.severity())) {
+ newIssue.setDiff("severity", pastIssue.getSeverity(), newIssue.severity());
}
newIssue.setResolution(pastIssue.getResolution());
newIssue.setStatus(pastIssue.getStatus());
@@ -358,15 +360,13 @@ public class IssueTracking implements BatchExtension {
newIssue.setAlive(true);
newIssue.setAuthorLogin(pastIssue.getAuthorLogin());
if (pastIssue.getAttributes() != null) {
+ //TODO do not loose new attributes
newIssue.setAttributes(KeyValueFormat.parse(pastIssue.getAttributes()));
}
lastIssuesByRule.remove(getRuleId(newIssue), pastIssue);
issueMap.put(newIssue, pastIssue);
unmappedLastIssues.remove(pastIssue);
- } else {
- newIssue.setNew(true);
- newIssue.setCreatedAt(project.getAnalysisDate());
}
}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java
index 7ef5af13ac7..f6c8b833d69 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java
@@ -31,6 +31,7 @@ import org.sonar.api.resources.ResourceUtils;
import org.sonar.api.resources.Scopes;
import org.sonar.batch.issue.ScanIssues;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.issue.db.IssueDto;
import org.sonar.core.issue.workflow.IssueWorkflow;
@@ -83,8 +84,9 @@ public class IssueTrackingDecorator implements Decorator {
addDead(issues);
}
+ IssueChangeContext changeContext = IssueChangeContext.createScan(context.getProject().getAnalysisDate());
for (DefaultIssue issue : issues) {
- workflow.doAutomaticTransition(issue);
+ workflow.doAutomaticTransition(issue, changeContext);
handlers.execute(issue);
scanIssues.addOrUpdate(issue);
}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueHandlersTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueHandlersTest.java
index dbc5e7bec7e..684f6431770 100644
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueHandlersTest.java
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueHandlersTest.java
@@ -23,33 +23,38 @@ import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.sonar.api.issue.IssueHandler;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
public class IssueHandlersTest {
@Test
public void should_execute_handlers() throws Exception {
IssueHandler h1 = mock(IssueHandler.class);
IssueHandler h2 = mock(IssueHandler.class);
+ IssueUpdater updater = mock(IssueUpdater.class);
- IssueHandlers handlers = new IssueHandlers(new IssueHandler[]{h1, h2});
+ IssueHandlers handlers = new IssueHandlers(updater, new IssueHandler[]{h1, h2});
final DefaultIssue issue = new DefaultIssue();
handlers.execute(issue);
- verify(h1).onIssue(argThat(new ArgumentMatcher<IssueHandler.IssueContext>() {
+ verify(h1).onIssue(argThat(new ArgumentMatcher<IssueHandler.Context>() {
@Override
public boolean matches(Object o) {
- return ((IssueHandler.IssueContext) o).issue() == issue;
+ return ((IssueHandler.Context) o).issue() == issue;
}
}));
}
@Test
public void test_no_handlers() {
- IssueHandlers handlers = new IssueHandlers();
+ IssueUpdater updater = mock(IssueUpdater.class);
+ IssueHandlers handlers = new IssueHandlers(updater);
handlers.execute(new DefaultIssue());
- // do not fail
+ verifyZeroInteractions(updater);
}
}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java
index 72d74f21186..3eae474dd84 100644
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java
@@ -23,12 +23,14 @@ import com.google.common.collect.Sets;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
+import org.mockito.Mockito;
import org.sonar.api.batch.DecoratorContext;
import org.sonar.api.resources.File;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.batch.issue.ScanIssues;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.issue.db.IssueDto;
import org.sonar.core.issue.workflow.IssueWorkflow;
import org.sonar.core.persistence.AbstractDaoTestCase;
@@ -88,7 +90,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
List<IssueDto> dbIssues = Collections.emptyList();
when(initialOpenIssues.selectAndRemove(123)).thenReturn(dbIssues);
- decorator.decorate(file, mock(DecoratorContext.class));
+ decorator.decorate(file, mock(DecoratorContext.class, Mockito.RETURNS_MOCKS));
// Apply filters, track, apply transitions, notify extensions then update cache
verify(filters).accept(issue);
@@ -99,8 +101,8 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
return issues.size() == 1 && issues.get(0) == issue;
}
}));
- verify(workflow).doAutomaticTransition(issue);
- verify(handlers).execute(issue);
+ verify(workflow).doAutomaticTransition(eq(issue), any(IssueChangeContext.class));
+ verify(handlers).execute(eq(issue));
verify(scanIssues).addOrUpdate(issue);
}
@@ -117,9 +119,9 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
List<IssueDto> unmatchedIssues = Arrays.asList(unmatchedIssue);
when(tracking.track(eq(file), anyCollection(), anyCollection())).thenReturn(Sets.newHashSet(unmatchedIssues));
- decorator.decorate(file, mock(DecoratorContext.class));
+ decorator.decorate(file, mock(DecoratorContext.class, Mockito.RETURNS_MOCKS));
- verify(workflow, times(2)).doAutomaticTransition(any(DefaultIssue.class));
+ verify(workflow, times(2)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
verify(handlers, times(2)).execute(any(DefaultIssue.class));
verify(scanIssues, times(2)).addOrUpdate(any(DefaultIssue.class));
@@ -141,10 +143,10 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
IssueDto deadIssue = new IssueDto().setKey("ABCDE").setResolution("OPEN").setStatus("OPEN").setRuleKey_unit_test_only("squid", "AvoidCycle");
when(initialOpenIssues.getAllIssues()).thenReturn(Arrays.asList(deadIssue));
- decorator.decorate(project, mock(DecoratorContext.class));
+ decorator.decorate(project, mock(DecoratorContext.class, Mockito.RETURNS_MOCKS));
// the dead issue must be closed -> apply automatic transition, notify handlers and add to cache
- verify(workflow, times(2)).doAutomaticTransition(any(DefaultIssue.class));
+ verify(workflow, times(2)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
verify(handlers, times(2)).execute(any(DefaultIssue.class));
verify(scanIssues, times(2)).addOrUpdate(any(DefaultIssue.class));
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java
index ad4a625b2f3..82eeebdbd5a 100644
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java
@@ -207,17 +207,6 @@ public class IssueTrackingTest {
}
@Test
- public void should_set_date_of_new_issues() {
- DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum");
- assertThat(newIssue.createdAt()).isNull();
-
- Map<DefaultIssue, IssueDto> mapping = tracking.mapIssues(newArrayList(newIssue), Lists.<IssueDto>newArrayList());
- assertThat(mapping.size()).isEqualTo(0);
- assertThat(newIssue.createdAt()).isEqualTo(analysisDate);
- assertThat(newIssue.isNew()).isTrue();
- }
-
- @Test
public void should_set_severity_if_severity_has_been_changed_by_user() {
DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum").setSeverity("MAJOR");
IssueDto referenceIssue = newReferenceIssue("message", 1, 1, "checksum").setSeverity("MINOR").setManualSeverity(true);
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/DeprecatedViolations.java b/sonar-batch/src/main/java/org/sonar/batch/issue/DeprecatedViolations.java
index 28d69f309c4..c7e421bb335 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/issue/DeprecatedViolations.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/issue/DeprecatedViolations.java
@@ -20,14 +20,13 @@
package org.sonar.batch.issue;
import org.sonar.api.BatchComponent;
-import org.sonar.api.issue.Issue;
import org.sonar.api.resources.Resource;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.Violation;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.DefaultIssueBuilder;
import java.util.Collection;
-import java.util.UUID;
public class DeprecatedViolations implements BatchComponent {
@@ -47,21 +46,12 @@ public class DeprecatedViolations implements BatchComponent {
}
DefaultIssue toIssue(Violation violation) {
- DefaultIssue issue = new DefaultIssue()
- .setComponentKey(violation.getResource().getEffectiveKey())
- .setKey(UUID.randomUUID().toString())
- .setRuleKey(RuleKey.of(violation.getRule().getRepositoryKey(), violation.getRule().getKey()))
- .setCost(violation.getCost())
- .setLine(violation.getLineId())
- .setDescription(violation.getMessage())
- .setResolution(Issue.RESOLUTION_OPEN)
- .setStatus(Issue.STATUS_OPEN)
- .setManualSeverity(false)
- .setManual(false)
- .setSeverity(violation.getSeverity() != null ? violation.getSeverity().name() : null);
-
- // FIXME
- //issue.setPerson(violation.getPersonId());
- return issue;
+ return (DefaultIssue) new DefaultIssueBuilder(violation.getResource().getEffectiveKey())
+ .ruleKey(RuleKey.of(violation.getRule().getRepositoryKey(), violation.getRule().getKey()))
+ .cost(violation.getCost())
+ .line(violation.getLineId())
+ .description(violation.getMessage())
+ .severity(violation.getSeverity() != null ? violation.getSeverity().name() : null)
+ .build();
}
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssues.java b/sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssues.java
index b1dcdaf2993..4719c96353d 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssues.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssues.java
@@ -28,7 +28,6 @@ import org.sonar.api.rules.ActiveRule;
import org.sonar.core.issue.DefaultIssue;
import java.util.Collection;
-import java.util.UUID;
/**
* Central component to manage issues
@@ -65,10 +64,8 @@ public class ScanIssues {
// rule does not exist or is not enabled -> ignore the issue
return false;
}
- String key = UUID.randomUUID().toString();
- Preconditions.checkState(!Strings.isNullOrEmpty(key), "Fail to generate issue key");
- issue.setKey(key);
issue.setCreatedAt(project.getAnalysisDate());
+ issue.setUpdatedAt(project.getAnalysisDate());
if (issue.severity() == null) {
issue.setSeverity(activeRule.getSeverity().name());
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
index e73daf25d5b..6fd278c0e05 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
@@ -30,24 +30,8 @@ import org.sonar.batch.DefaultFileLinesContextFactory;
import org.sonar.batch.DefaultResourceCreationLock;
import org.sonar.batch.ProjectConfigurator;
import org.sonar.batch.ProjectTree;
-import org.sonar.batch.bootstrap.BatchSettings;
-import org.sonar.batch.bootstrap.ExtensionInstaller;
-import org.sonar.batch.bootstrap.ExtensionMatcher;
-import org.sonar.batch.bootstrap.ExtensionUtils;
-import org.sonar.batch.bootstrap.MetricProvider;
-import org.sonar.batch.index.Caches;
-import org.sonar.batch.index.ComponentDataCache;
-import org.sonar.batch.index.ComponentDataPersister;
-import org.sonar.batch.index.DefaultIndex;
-import org.sonar.batch.index.DefaultPersistenceManager;
-import org.sonar.batch.index.DefaultResourcePersister;
-import org.sonar.batch.index.DependencyPersister;
-import org.sonar.batch.index.EventPersister;
-import org.sonar.batch.index.LinkPersister;
-import org.sonar.batch.index.MeasurePersister;
-import org.sonar.batch.index.MemoryOptimizer;
-import org.sonar.batch.index.SnapshotCache;
-import org.sonar.batch.index.SourcePersister;
+import org.sonar.batch.bootstrap.*;
+import org.sonar.batch.index.*;
import org.sonar.batch.issue.DeprecatedViolations;
import org.sonar.batch.issue.IssueCache;
import org.sonar.batch.issue.IssuePersister;
@@ -58,6 +42,8 @@ import org.sonar.batch.scan.maven.MavenPluginExecutor;
import org.sonar.batch.scan.source.HighlightableBuilder;
import org.sonar.batch.scan.source.SymbolPerspectiveBuilder;
import org.sonar.core.component.ScanGraph;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.core.issue.workflow.FunctionExecutor;
import org.sonar.core.issue.workflow.IssueWorkflow;
import org.sonar.core.notification.DefaultNotificationManager;
import org.sonar.core.test.TestPlanBuilder;
@@ -83,44 +69,46 @@ public class ProjectScanContainer extends ComponentContainer {
private void addBatchComponents() {
add(
- DefaultResourceCreationLock.class,
- DefaultPersistenceManager.class,
- DependencyPersister.class,
- EventPersister.class,
- LinkPersister.class,
- MeasurePersister.class,
- MemoryOptimizer.class,
- DefaultResourcePersister.class,
- SourcePersister.class,
- DefaultNotificationManager.class,
- MetricProvider.class,
- ProjectConfigurator.class,
- DefaultIndex.class,
- DefaultFileLinesContextFactory.class,
- ProjectLock.class,
- LastSnapshots.class,
- Caches.class,
- SnapshotCache.class,
- ComponentDataCache.class,
- ComponentDataPersister.class,
+ DefaultResourceCreationLock.class,
+ DefaultPersistenceManager.class,
+ DependencyPersister.class,
+ EventPersister.class,
+ LinkPersister.class,
+ MeasurePersister.class,
+ MemoryOptimizer.class,
+ DefaultResourcePersister.class,
+ SourcePersister.class,
+ DefaultNotificationManager.class,
+ MetricProvider.class,
+ ProjectConfigurator.class,
+ DefaultIndex.class,
+ DefaultFileLinesContextFactory.class,
+ ProjectLock.class,
+ LastSnapshots.class,
+ Caches.class,
+ SnapshotCache.class,
+ ComponentDataCache.class,
+ ComponentDataPersister.class,
- // issues
- IssueWorkflow.class,
- DeprecatedViolations.class,
- IssueCache.class,
- IssuePersister.class,
+ // issues
+ IssueUpdater.class,
+ FunctionExecutor.class,
+ IssueWorkflow.class,
+ DeprecatedViolations.class,
+ IssueCache.class,
+ IssuePersister.class,
- // tests
- TestPlanPerspectiveLoader.class,
- TestablePerspectiveLoader.class,
- TestPlanBuilder.class,
- TestableBuilder.class,
- ScanGraph.create(),
- GraphPersister.class,
+ // tests
+ TestPlanPerspectiveLoader.class,
+ TestablePerspectiveLoader.class,
+ TestPlanBuilder.class,
+ TestableBuilder.class,
+ ScanGraph.create(),
+ GraphPersister.class,
- // lang
- HighlightableBuilder.class,
- SymbolPerspectiveBuilder.class);
+ // lang
+ HighlightableBuilder.class,
+ SymbolPerspectiveBuilder.class);
}
private void fixMavenExecutor() {
diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssuesTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssuesTest.java
index 963f0618ffc..695d2292f97 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssuesTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssuesTest.java
@@ -84,13 +84,15 @@ public class ScanIssuesTest {
Date analysisDate = new Date();
when(project.getAnalysisDate()).thenReturn(analysisDate);
- DefaultIssue issue = new DefaultIssue().setRuleKey(RuleKey.of("squid", "AvoidCycle")).setSeverity(Severity.CRITICAL);
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
+ .setSeverity(Severity.CRITICAL);
boolean added = scanIssues.initAndAddIssue(issue);
assertThat(added).isTrue();
ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
verify(cache).put(argument.capture());
- assertThat(argument.getValue().key()).isNotNull();
assertThat(argument.getValue().severity()).isEqualTo(Severity.CRITICAL);
assertThat(argument.getValue().createdAt()).isEqualTo(analysisDate);
}
@@ -111,7 +113,6 @@ public class ScanIssuesTest {
ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
verify(cache).put(argument.capture());
- assertThat(argument.getValue().key()).isNotNull();
assertThat(argument.getValue().severity()).isEqualTo(Severity.INFO);
assertThat(argument.getValue().createdAt()).isEqualTo(analysisDate);
}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
index bb6fe07a4c7..23d3dfd82b3 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
@@ -19,10 +19,10 @@
*/
package org.sonar.core.issue;
+import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
@@ -36,9 +36,8 @@ import java.io.Serializable;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
-import java.util.Set;
-public class DefaultIssue implements Issue, Serializable {
+public class DefaultIssue implements Issue {
private String key;
private String componentKey;
@@ -60,8 +59,8 @@ public class DefaultIssue implements Issue, Serializable {
private boolean isNew = true;
private boolean isAlive = true;
private Map<String, String> attributes = null;
-
- private String authorLogin;
+ private String authorLogin = null;
+ private IssueChange change = null;
public String key() {
return key;
@@ -100,7 +99,7 @@ public class DefaultIssue implements Issue, Serializable {
return this;
}
- public boolean isManualSeverity() {
+ public boolean manualSeverity() {
return manualSeverity;
}
@@ -198,6 +197,7 @@ public class DefaultIssue implements Issue, Serializable {
return closedAt;
}
+ // TODO rename setClosedDate
public DefaultIssue setClosedAt(@Nullable Date d) {
this.closedAt = d;
return this;
@@ -273,6 +273,20 @@ public class DefaultIssue implements Issue, Serializable {
return this;
}
+ public DefaultIssue setDiff(String field, @Nullable Serializable oldValue, @Nullable Serializable newValue) {
+ if (!Objects.equal(oldValue, newValue)) {
+ if (change == null) {
+ change = new IssueChange();
+ }
+ change.setDiff(field, oldValue, newValue);
+ }
+ return this;
+ }
+
+ public IssueChange change() {
+ return change;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -297,6 +311,4 @@ public class DefaultIssue implements Issue, Serializable {
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
-
-
}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java
index eb80c617351..3ed6d326c56 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java
@@ -19,13 +19,18 @@
*/
package org.sonar.core.issue;
+import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import org.sonar.api.issue.Issuable;
import org.sonar.api.issue.Issue;
import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.Severity;
+import java.util.Date;
import java.util.Map;
+import java.util.UUID;
public class DefaultIssueBuilder implements Issuable.IssueBuilder {
@@ -88,18 +93,25 @@ public class DefaultIssueBuilder implements Issuable.IssueBuilder {
}
@Override
- public Issue build() {
+ public DefaultIssue build() {
Preconditions.checkNotNull(componentKey, "Component key must be set");
Preconditions.checkNotNull(ruleKey, "Rule key must be set");
DefaultIssue issue = new DefaultIssue();
+ String key = UUID.randomUUID().toString();
+ Preconditions.checkState(!Strings.isNullOrEmpty(key), "Fail to generate issue key");
+ issue.setKey(key);
+ Date now = new Date();
+ issue.setCreatedAt(now);
+ issue.setUpdatedAt(now);
issue.setComponentKey(componentKey);
issue.setRuleKey(ruleKey);
issue.setDescription(description);
- issue.setSeverity(severity);
+ issue.setSeverity(Objects.firstNonNull(severity, Severity.MAJOR));
issue.setCost(cost);
issue.setLine(line);
issue.setManual(manual);
+ issue.setManualSeverity(manual);
issue.setAttributes(attributes);
issue.setNew(true);
issue.setAlive(true);
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssueChange.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueChange.java
new file mode 100644
index 00000000000..d64c6b98411
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/issue/IssueChange.java
@@ -0,0 +1,67 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.core.issue;
+
+import com.google.common.collect.Maps;
+
+import javax.annotation.Nullable;
+import java.io.Serializable;
+import java.util.Map;
+
+public class IssueChange implements Serializable {
+
+ public static class Diff<T extends Serializable> implements Serializable {
+ private T before, after;
+
+ public Diff(@Nullable T before, @Nullable T after) {
+ this.before = before;
+ this.after = after;
+ }
+
+ public T before() {
+ return before;
+ }
+
+ public T after() {
+ return after;
+ }
+
+ void setAfter(T t) {
+ this.after = t;
+ }
+ }
+
+ private Map<String, Diff> diffs = Maps.newLinkedHashMap();
+
+ public Map<String, Diff> diffs() {
+ return diffs;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void setDiff(String field, @Nullable Serializable oldValue, @Nullable Serializable newValue) {
+ Diff diff = diffs.get(field);
+ if (diff == null) {
+ diff = new Diff(oldValue, newValue);
+ diffs.put(field, diff);
+ } else {
+ diff.setAfter(newValue);
+ }
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosedAt.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java
index ca2f47c268d..a8422d6edd9 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosedAt.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java
@@ -17,20 +17,43 @@
* 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.core.issue.workflow;
-
-import org.sonar.core.issue.DefaultIssue;
+package org.sonar.core.issue;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import java.io.Serializable;
import java.util.Date;
-class SetClosedAt implements Function {
- static final SetClosedAt CLOSED_AT = new SetClosedAt();
+public class IssueChangeContext implements Serializable {
+
+ private String login;
+ private Date date;
+ private boolean automatic;
+
+ private IssueChangeContext(@Nullable String login, Date date, boolean automatic) {
+ this.login = login;
+ this.date = date;
+ this.automatic = automatic;
+ }
+
+ @CheckForNull
+ public String login() {
+ return login;
+ }
+
+ public Date date() {
+ return date;
+ }
+
+ public boolean automatic() {
+ return automatic;
+ }
- private SetClosedAt() {
+ public static IssueChangeContext createScan(Date date) {
+ return new IssueChangeContext(null, date, true);
}
- @Override
- public void execute(DefaultIssue issue) {
- issue.setClosedAt(new Date());
+ public static IssueChangeContext createUser(Date date, String login) {
+ return new IssueChangeContext(login, date, false);
}
}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java
new file mode 100644
index 00000000000..013e4b24f7f
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java
@@ -0,0 +1,110 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.core.issue;
+
+import com.google.common.base.Objects;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.ServerComponent;
+
+import javax.annotation.Nullable;
+import java.util.Date;
+
+public class IssueUpdater implements BatchComponent, ServerComponent {
+
+ public boolean setSeverity(DefaultIssue issue, String severity) {
+ if (!Objects.equal(severity, issue.severity())) {
+ issue.setDiff("severity", issue.severity(), severity);
+ issue.setSeverity(severity);
+ return true;
+ }
+ return false;
+ }
+
+ public DefaultIssue setManualSeverity(DefaultIssue issue, String severity) {
+ if (!issue.manualSeverity() || !Objects.equal(severity, issue.severity())) {
+ issue.setDiff("severity", issue.severity(), severity);
+ issue.setSeverity(severity);
+ issue.setManualSeverity(true);
+ }
+ return issue;
+ }
+
+ public boolean assign(DefaultIssue issue, @Nullable String assignee) {
+ String sanitizedAssignee = StringUtils.defaultIfBlank(assignee, null);
+ if (!Objects.equal(sanitizedAssignee, issue.assignee())) {
+ issue.setDiff("assignee", issue.assignee(), sanitizedAssignee);
+ issue.setAssignee(sanitizedAssignee);
+ return true;
+ }
+ return false;
+ }
+
+ public DefaultIssue setLine(DefaultIssue issue, @Nullable Integer line) {
+ if (!Objects.equal(line, issue.line())) {
+ issue.setLine(line);
+ }
+ return issue;
+ }
+
+ public DefaultIssue setResolution(DefaultIssue issue, String resolution) {
+ if (!Objects.equal(resolution, issue.resolution())) {
+ issue.setDiff("resolution", issue.resolution(), resolution);
+ issue.setResolution(resolution);
+ }
+ return issue;
+ }
+
+ public DefaultIssue setStatus(DefaultIssue issue, String status) {
+ if (!Objects.equal(status, issue.status())) {
+ issue.setDiff("status", issue.status(), status);
+ issue.setStatus(status);
+ }
+ return issue;
+ }
+
+ public DefaultIssue setAuthorLogin(DefaultIssue issue, @Nullable String authorLogin) {
+ if (!Objects.equal(authorLogin, issue.authorLogin())) {
+ issue.setAuthorLogin(authorLogin);
+ }
+ return issue;
+ }
+
+ public DefaultIssue setDescription(DefaultIssue issue, @Nullable String description) {
+ if (!Objects.equal(description, issue.description())) {
+ if (issue.manual()) {
+ issue.setDiff("description", issue.description(), description);
+ }
+ issue.setDescription(description);
+ }
+ return issue;
+ }
+
+ public DefaultIssue setClosedDate(DefaultIssue issue, @Nullable Date date) {
+ if (!Objects.equal(date, issue.closedAt())) {
+ issue.setDiff("closedDate", issue.closedAt(), date);
+ issue.setClosedAt(date);
+ }
+ return issue;
+ }
+
+ // TODO setAttribute
+ // TODO comment
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java
index 4c5385c390b..40ea1e57411 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java
@@ -308,7 +308,7 @@ public final class IssueDto {
.setSeverity(issue.severity())
.setChecksum(issue.getChecksum())
.setManualIssue(issue.manual())
- .setManualSeverity(issue.isManualSeverity())
+ .setManualSeverity(issue.manualSeverity())
.setUserLogin(issue.userLogin())
.setAssignee(issue.assignee())
.setCreatedAt(issue.createdAt())
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/Function.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/Function.java
index a45a595acbd..82b79dc1ec9 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/Function.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/workflow/Function.java
@@ -19,8 +19,14 @@
*/
package org.sonar.core.issue.workflow;
-import org.sonar.core.issue.DefaultIssue;
+import org.sonar.api.issue.Issue;
interface Function {
- void execute(DefaultIssue issue);
+ interface Context {
+ Issue issue();
+ Context setResolution(String s);
+ Context setClosedDate(boolean b);
+ }
+
+ void execute(Context context);
}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java
new file mode 100644
index 00000000000..a54badf74cd
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java
@@ -0,0 +1,75 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.core.issue.workflow;
+
+import org.sonar.api.BatchComponent;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.issue.Issue;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
+
+public class FunctionExecutor implements BatchComponent, ServerComponent {
+
+ private final IssueUpdater updater;
+
+ public FunctionExecutor(IssueUpdater updater) {
+ this.updater = updater;
+ }
+
+ public void execute(Function[] functions, DefaultIssue issue, IssueChangeContext changeContext) {
+ if (functions.length > 0) {
+ FunctionContext functionContext = new FunctionContext(updater, issue, changeContext);
+ for (Function function : functions) {
+ function.execute(functionContext);
+ }
+ }
+ }
+
+ static class FunctionContext implements Function.Context {
+
+ private final IssueUpdater updater;
+ private final DefaultIssue issue;
+ private final IssueChangeContext changeContext;
+
+ FunctionContext(IssueUpdater updater, DefaultIssue issue, IssueChangeContext changeContext) {
+ this.updater = updater;
+ this.issue = issue;
+ this.changeContext = changeContext;
+ }
+
+ @Override
+ public Issue issue() {
+ return issue;
+ }
+
+ @Override
+ public Function.Context setResolution(String s) {
+ updater.setResolution(issue, s);
+ return this;
+ }
+
+ @Override
+ public Function.Context setClosedDate(boolean b) {
+ updater.setClosedDate(issue, b ? changeContext.date() : null);
+ return null;
+ }
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java
index 3d630bc4f4c..f536d2c534c 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java
@@ -25,30 +25,23 @@ import org.sonar.api.ServerComponent;
import org.sonar.api.issue.DefaultTransitions;
import org.sonar.api.issue.Issue;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
import java.util.List;
public class IssueWorkflow implements BatchComponent, ServerComponent, Startable {
private StateMachine machine;
+ private final FunctionExecutor functionExecutor;
+
+ public IssueWorkflow(FunctionExecutor functionExecutor) {
+ this.functionExecutor = functionExecutor;
+ }
@Override
public void start() {
machine = StateMachine.builder()
.states(Issue.STATUS_OPEN, Issue.STATUS_REOPENED, Issue.STATUS_RESOLVED, Issue.STATUS_CLOSED)
- .transition(Transition.builder(DefaultTransitions.CLOSE)
- .from(Issue.STATUS_OPEN).to(Issue.STATUS_CLOSED)
- .functions(new SetResolution(Issue.RESOLUTION_FIXED), SetClosedAt.CLOSED_AT)
- .build())
- .transition(Transition.builder(DefaultTransitions.CLOSE)
- .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_CLOSED)
- .functions(SetClosedAt.CLOSED_AT)
- .build())
- .transition(Transition.builder(DefaultTransitions.CLOSE)
- .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CLOSED)
- .functions(SetClosedAt.CLOSED_AT)
- .functions(new SetResolution(Issue.RESOLUTION_FIXED))
- .build())
.transition(Transition.builder(DefaultTransitions.RESOLVE)
.from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
.functions(new SetResolution(Issue.RESOLUTION_FIXED))
@@ -63,7 +56,7 @@ public class IssueWorkflow implements BatchComponent, ServerComponent, Startable
.build())
.transition(Transition.builder(DefaultTransitions.REOPEN)
.from(Issue.STATUS_CLOSED).to(Issue.STATUS_REOPENED)
- .functions(new SetResolution(Issue.RESOLUTION_OPEN), new UnsetClosedAt())
+ .functions(new SetResolution(Issue.RESOLUTION_OPEN), new SetClosedDate(false))
.build())
.transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
.from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
@@ -82,13 +75,13 @@ public class IssueWorkflow implements BatchComponent, ServerComponent, Startable
.transition(Transition.builder("automaticclose")
.from(Issue.STATUS_OPEN).to(Issue.STATUS_CLOSED)
.conditions(new IsAlive(false), new IsManual(false))
- .functions(new SetResolution(Issue.RESOLUTION_FIXED), SetClosedAt.CLOSED_AT)
+ .functions(new SetResolution(Issue.RESOLUTION_FIXED), new SetClosedDate(true))
.automatic()
.build())
.transition(Transition.builder("automaticclose")
.from(Issue.STATUS_REOPENED).to(Issue.STATUS_CLOSED)
.conditions(new IsAlive(false))
- .functions(new SetResolution(Issue.RESOLUTION_FIXED), SetClosedAt.CLOSED_AT)
+ .functions(new SetResolution(Issue.RESOLUTION_FIXED), new SetClosedDate(true))
.automatic()
.build())
// Close the issues marked as resolved and that do not exist anymore.
@@ -96,7 +89,7 @@ public class IssueWorkflow implements BatchComponent, ServerComponent, Startable
.transition(Transition.builder("automaticclose")
.from(Issue.STATUS_RESOLVED).to(Issue.STATUS_CLOSED)
.conditions(new IsAlive(false))
- .functions(SetClosedAt.CLOSED_AT)
+ .functions(new SetClosedDate(true))
.automatic()
.build())
.transition(Transition.builder("automaticreopen")
@@ -112,24 +105,26 @@ public class IssueWorkflow implements BatchComponent, ServerComponent, Startable
public void stop() {
}
- public boolean doManualTransition(DefaultIssue issue, String transitionKey) {
+ public boolean doTransition(DefaultIssue issue, String transitionKey, IssueChangeContext issueChangeContext) {
Transition transition = machine.state(issue.status()).transition(transitionKey);
if (transition != null && !transition.automatic()) {
- transition.execute(issue);
+ functionExecutor.execute(transition.functions(), issue, issueChangeContext);
+ issue.setStatus(transition.to());
return true;
}
return false;
}
- public List<Transition> outManualTransitions(Issue issue) {
+ public List<Transition> outTransitions(Issue issue) {
return machine.state(issue.status()).outManualTransitions(issue);
}
- public void doAutomaticTransition(DefaultIssue issue) {
+ public void doAutomaticTransition(DefaultIssue issue, IssueChangeContext issueChangeContext) {
Transition transition = machine.state(issue.status()).outAutomaticTransition(issue);
if (transition != null) {
- transition.execute(issue);
+ functionExecutor.execute(transition.functions(), issue, issueChangeContext);
+ issue.setStatus(transition.to());
}
}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/UnsetClosedAt.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosedDate.java
index 87540a123c4..203cc473d74 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/UnsetClosedAt.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosedDate.java
@@ -19,13 +19,15 @@
*/
package org.sonar.core.issue.workflow;
-import org.sonar.core.issue.DefaultIssue;
+public class SetClosedDate implements Function {
+ private final boolean set;
-class UnsetClosedAt implements Function {
+ public SetClosedDate(boolean set) {
+ this.set = set;
+ }
@Override
- public void execute(DefaultIssue issue) {
- issue.setClosedAt(null);
+ public void execute(Context context) {
+ context.setClosedDate(set);
}
-
}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetResolution.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetResolution.java
index c18bbc5493f..e5147d4bf99 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetResolution.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetResolution.java
@@ -21,19 +21,17 @@ package org.sonar.core.issue.workflow;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
-import org.sonar.core.issue.DefaultIssue;
-class SetResolution implements Function {
+public class SetResolution implements Function {
private final String resolution;
- SetResolution(String resolution) {
+ public SetResolution(String resolution) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(resolution), "Resolution must be set");
this.resolution = resolution;
}
@Override
- public void execute(DefaultIssue issue) {
- issue.setResolution(resolution);
+ public void execute(Context context) {
+ context.setResolution(resolution);
}
-
}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/Transition.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/Transition.java
index 632b944f711..ce4b4aecdb2 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/Transition.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/workflow/Transition.java
@@ -24,10 +24,8 @@ import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.issue.Issue;
-import org.sonar.core.issue.DefaultIssue;
import java.util.Arrays;
-import java.util.Date;
import java.util.List;
public class Transition {
@@ -79,13 +77,6 @@ public class Transition {
return true;
}
- public void execute(DefaultIssue issue) {
- for (Function function : functions) {
- function.execute(issue);
- }
- issue.setStatus(to);
- issue.setUpdatedAt(new Date());
- }
public static TransitionBuilder builder(String key) {
return new TransitionBuilder(key);
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueBuilderTest.java b/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueBuilderTest.java
index 4a0834f8fad..e625470bedc 100644
--- a/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueBuilderTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueBuilderTest.java
@@ -42,7 +42,7 @@ public class DefaultIssueBuilderTest {
.build();
assertThat(issue).isNotNull();
- assertThat(issue.key()).isNull();
+ assertThat(issue.key()).isNotNull();
assertThat(issue.cost()).isEqualTo(10000.0);
assertThat(issue.componentKey()).isEqualTo(componentKey);
assertThat(issue.description()).isEqualTo("the desc");
@@ -50,7 +50,7 @@ public class DefaultIssueBuilderTest {
assertThat(issue.ruleKey().repository()).isEqualTo("squid");
assertThat(issue.ruleKey().rule()).isEqualTo("NullDereference");
assertThat(issue.severity()).isEqualTo(Severity.CRITICAL);
- assertThat(issue.updatedAt()).isNull();
+ assertThat(issue.updatedAt()).isNotNull();
assertThat(issue.closedAt()).isNull();
assertThat(issue.assignee()).isNull();
assertThat(issue.isNew()).isTrue();
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDtoTest.java b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDtoTest.java
index 8600e27ba99..89aeb4fbd5a 100644
--- a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDtoTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDtoTest.java
@@ -25,7 +25,6 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.issue.Issue;
import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.db.IssueDto;
import java.util.Date;
@@ -86,7 +85,7 @@ public class IssueDtoTest {
assertThat(issue.line()).isEqualTo(6);
assertThat(issue.severity()).isEqualTo("BLOCKER");
assertThat(issue.description()).isEqualTo("message");
- assertThat(issue.isManualSeverity()).isTrue();
+ assertThat(issue.manualSeverity()).isTrue();
assertThat(issue.manual()).isTrue();
assertThat(issue.userLogin()).isEqualTo("arthur");
assertThat(issue.assignee()).isEqualTo("perceval");
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java
index 77abe017209..19d07e6c213 100644
--- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java
@@ -24,15 +24,19 @@ import com.google.common.collect.Collections2;
import org.junit.Test;
import org.sonar.api.issue.Issue;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
import javax.annotation.Nullable;
import java.util.Collection;
+import java.util.Date;
import java.util.List;
import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
public class IssueWorkflowTest {
- IssueWorkflow workflow = new IssueWorkflow();
+ IssueWorkflow workflow = new IssueWorkflow(new FunctionExecutor(new IssueUpdater()));
@Test
public void should_init_state_machine() throws Exception {
@@ -51,9 +55,9 @@ public class IssueWorkflowTest {
workflow.start();
DefaultIssue issue = new DefaultIssue().setStatus(Issue.STATUS_OPEN);
- List<Transition> transitions = workflow.outManualTransitions(issue);
- assertThat(transitions).hasSize(3);
- assertThat(keys(transitions)).containsOnly("close", "falsepositive", "resolve");
+ List<Transition> transitions = workflow.outTransitions(issue);
+ assertThat(transitions).hasSize(2);
+ assertThat(keys(transitions)).containsOnly("falsepositive", "resolve");
}
@Test
@@ -65,7 +69,7 @@ public class IssueWorkflowTest {
.setStatus(Issue.STATUS_RESOLVED)
.setNew(false)
.setAlive(false);
- workflow.doAutomaticTransition(issue);
+ workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(new Date()));
assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FIXED);
assertThat(issue.status()).isEqualTo(Issue.STATUS_CLOSED);
assertThat(issue.closedAt()).isNotNull();
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetResolutionTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetResolutionTest.java
deleted file mode 100644
index 2ab06515ada..00000000000
--- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetResolutionTest.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.core.issue.workflow;
-
-import org.junit.Test;
-import org.sonar.api.issue.Issue;
-import org.sonar.core.issue.DefaultIssue;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.fest.assertions.Fail.fail;
-
-public class SetResolutionTest {
- @Test
- public void should_set_resolution() throws Exception {
- DefaultIssue issue = new DefaultIssue();
- SetResolution function = new SetResolution(Issue.RESOLUTION_FIXED);
- function.execute(issue);
- assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FIXED);
- }
-
- @Test
- public void resolution_should_not_be_empty() throws Exception {
- try {
- new SetResolution("");
- fail();
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessage("Resolution must be set");
- }
- }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/TransitionTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/TransitionTest.java
index 4f068afe6cc..caf383720f4 100644
--- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/TransitionTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/issue/workflow/TransitionTest.java
@@ -25,7 +25,6 @@ import org.sonar.core.issue.DefaultIssue;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class TransitionTest {
@@ -102,22 +101,6 @@ public class TransitionTest {
}
@Test
- public void should_execute_functions() throws Exception {
- DefaultIssue issue = new DefaultIssue();
- Transition transition = Transition.builder("close")
- .from("OPEN").to("CLOSED")
- .conditions(condition1, condition2)
- .functions(function1, function2)
- .build();
- transition.execute(issue);
-
- assertThat(issue.status()).isEqualTo("CLOSED");
- assertThat(issue.updatedAt()).isNotNull();
- verify(function1).execute(issue);
- verify(function2).execute(issue);
- }
-
- @Test
public void should_verify_conditions() throws Exception {
DefaultIssue issue = new DefaultIssue();
Transition transition = Transition.builder("close")
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/UnsetClosedAtTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/UnsetClosedAtTest.java
deleted file mode 100644
index e74f78b9a1d..00000000000
--- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/UnsetClosedAtTest.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.core.issue.workflow;
-
-import org.junit.Test;
-import org.sonar.core.issue.DefaultIssue;
-
-import java.util.Date;
-
-import static org.fest.assertions.Assertions.assertThat;
-
-public class UnsetClosedAtTest {
- @Test
- public void should_remove_date() throws Exception {
- UnsetClosedAt function = new UnsetClosedAt();
- DefaultIssue issue = new DefaultIssue().setCreatedAt(new Date()).setCreatedAt(new Date());
- function.execute(issue);
- assertThat(issue.closedAt()).isNull();
- assertThat(issue.createdAt()).isNotNull();
- }
-}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueHandler.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueHandler.java
index 6ba7c0edb7c..8b7a6aadcc4 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueHandler.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueHandler.java
@@ -28,31 +28,29 @@ import javax.annotation.Nullable;
*/
public interface IssueHandler extends BatchExtension {
- interface IssueContext {
+ interface Context {
Issue issue();
boolean isNew();
boolean isAlive();
- IssueContext setLine(@Nullable Integer line);
+ Context setLine(@Nullable Integer line);
- IssueContext setDescription(String description);
+ Context setDescription(@Nullable String description);
- // set manual severity ?
- IssueContext setSeverity(String severity);
+ Context setSeverity(String severity);
- // TODO rename to setScmLogin ?
- IssueContext setAuthorLogin(@Nullable String login);
+ Context setAuthorLogin(@Nullable String login);
- IssueContext setAttribute(String key, @Nullable String value);
+ Context setAttribute(String key, @Nullable String value);
- IssueContext assignTo(@Nullable String login);
+ Context assign(@Nullable String login);
- //TODO IssueContext comment(String comment);
+ Context comment(String message);
}
- void onIssue(IssueContext context);
+ void onIssue(Context context);
}
diff --git a/sonar-server/src/main/java/org/sonar/server/issue/JRubyInternalIssues.java b/sonar-server/src/main/java/org/sonar/server/issue/JRubyInternalIssues.java
index fab07960dcd..a529f1206fd 100644
--- a/sonar-server/src/main/java/org/sonar/server/issue/JRubyInternalIssues.java
+++ b/sonar-server/src/main/java/org/sonar/server/issue/JRubyInternalIssues.java
@@ -21,10 +21,14 @@ package org.sonar.server.issue;
import org.sonar.api.ServerComponent;
import org.sonar.api.issue.Issue;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.DefaultIssueBuilder;
import org.sonar.core.issue.workflow.Transition;
import org.sonar.server.platform.UserSession;
import java.util.List;
+import java.util.Map;
/**
* All the issue features that are not published to public API
@@ -38,10 +42,34 @@ public class JRubyInternalIssues implements ServerComponent {
}
public List<Transition> listTransitions(String issueKey) {
- return actions.listTransitions(issueKey, UserSession.get().userId());
+ return actions.listTransitions(issueKey, UserSession.get());
}
public Issue doTransition(String issueKey, String transitionKey) {
return actions.doTransition(issueKey, transitionKey, UserSession.get());
}
+
+ public Issue assign(String issueKey, String transitionKey) {
+ return actions.assign(issueKey, transitionKey, UserSession.get());
+ }
+
+ public Issue setSeverity(String issueKey, String severity) {
+ return actions.setSeverity(issueKey, severity, UserSession.get());
+ }
+
+ public Issue create(Map<String, String> parameters) {
+ String componentKey = parameters.get("component");
+ // TODO verify authorization
+ // TODO check existence of component
+ DefaultIssueBuilder builder = new DefaultIssueBuilder(componentKey);
+ String line = parameters.get("line");
+ builder.line(line != null ? Integer.parseInt(line) : null);
+ builder.description(parameters.get("description"));
+ builder.severity(parameters.get("severity"));
+ // TODO verify existence of rule
+ builder.ruleKey(RuleKey.parse(parameters.get("rule")));
+ builder.manual(true);
+ Issue issue = builder.build();
+ return actions.create((DefaultIssue) issue, UserSession.get());
+ }
}
diff --git a/sonar-server/src/main/java/org/sonar/server/issue/ServerIssueActions.java b/sonar-server/src/main/java/org/sonar/server/issue/ServerIssueActions.java
index cb902736e47..e56f966fc32 100644
--- a/sonar-server/src/main/java/org/sonar/server/issue/ServerIssueActions.java
+++ b/sonar-server/src/main/java/org/sonar/server/issue/ServerIssueActions.java
@@ -21,19 +21,25 @@ package org.sonar.server.issue;
import org.sonar.api.ServerComponent;
import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.IssueFinder;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
import org.sonar.core.issue.db.IssueDao;
import org.sonar.core.issue.db.IssueDto;
import org.sonar.core.issue.workflow.IssueWorkflow;
import org.sonar.core.issue.workflow.Transition;
+import org.sonar.core.resource.ResourceDao;
+import org.sonar.core.resource.ResourceDto;
+import org.sonar.core.resource.ResourceQuery;
import org.sonar.core.user.AuthorizationDao;
import org.sonar.server.platform.UserSession;
import javax.annotation.Nullable;
import java.util.Arrays;
-import java.util.Collections;
+import java.util.Date;
import java.util.List;
/**
@@ -42,27 +48,73 @@ import java.util.List;
public class ServerIssueActions implements ServerComponent {
private final IssueWorkflow workflow;
- private final IssueFinder finder;
private final IssueDao issueDao;
private final AuthorizationDao authorizationDao;
+ private final ResourceDao resourceDao;
+ private final RuleFinder ruleFinder;
+ private final IssueUpdater issueUpdater;
- public ServerIssueActions(IssueWorkflow workflow, IssueFinder finder, IssueDao issueDao, AuthorizationDao authorizationDao) {
+ public ServerIssueActions(IssueWorkflow workflow, IssueDao issueDao,
+ AuthorizationDao authorizationDao, ResourceDao resourceDao, RuleFinder ruleFinder, IssueUpdater issueUpdater) {
this.workflow = workflow;
- this.finder = finder;
this.issueDao = issueDao;
this.authorizationDao = authorizationDao;
+ this.resourceDao = resourceDao;
+ this.ruleFinder = ruleFinder;
+ this.issueUpdater = issueUpdater;
}
- public List<Transition> listTransitions(String issueKey, @Nullable Integer userId) {
- Issue issue = finder.findByKey(issueKey /*, userId */);
- // TODO check authorization
- if (issue == null) {
- return Collections.emptyList();
- }
- return workflow.outManualTransitions(issue);
+ public List<Transition> listTransitions(String issueKey, UserSession userSession) {
+ IssueDto dto = loadDto(issueKey, userSession);
+ DefaultIssue issue = dto.toDefaultIssue();
+ return workflow.outTransitions(issue);
}
public Issue doTransition(String issueKey, String transition, UserSession userSession) {
+ IssueDto dto = loadDto(issueKey, userSession);
+ DefaultIssue issue = dto.toDefaultIssue();
+ IssueChangeContext context = IssueChangeContext.createUser(new Date(), userSession.login());
+ if (workflow.doTransition(issue, transition, context)) {
+ issueDao.update(Arrays.asList(IssueDto.toDto(issue, dto.getResourceId(), dto.getRuleId())));
+ }
+ return issue;
+ }
+
+ public Issue assign(String issueKey, @Nullable String assigneeLogin, UserSession userSession) {
+ IssueDto dto = loadDto(issueKey, userSession);
+ DefaultIssue issue = dto.toDefaultIssue();
+
+ // TODO check that assignee exists
+ if (issueUpdater.assign(issue, assigneeLogin)) {
+ issueDao.update(Arrays.asList(IssueDto.toDto(issue, dto.getResourceId(), dto.getRuleId())));
+ }
+ return issue;
+ }
+
+ public Issue setSeverity(String issueKey, String severity, UserSession userSession) {
+ IssueDto dto = loadDto(issueKey, userSession);
+ DefaultIssue issue = dto.toDefaultIssue();
+
+ if (issueUpdater.setSeverity(issue, severity)) {
+ issueDao.update(Arrays.asList(IssueDto.toDto(issue, dto.getResourceId(), dto.getRuleId())));
+ }
+ return issue;
+ }
+
+ public Issue create(DefaultIssue issue, UserSession userSession) {
+ issue.setManual(true);
+ issue.setUserLogin(userSession.login());
+
+ // TODO check that rule and component exist
+ Rule rule = ruleFinder.findByKey(issue.ruleKey());
+ ResourceDto resourceDto = resourceDao.getResource(ResourceQuery.create().setKey(issue.componentKey()));
+ IssueDto dto = IssueDto.toDto(issue, resourceDto.getId().intValue(), rule.getId());
+ issueDao.insert(dto);
+
+ return issue;
+ }
+
+ private IssueDto loadDto(String issueKey, UserSession userSession) {
if (!userSession.isLoggedIn()) {
// must be logged
throw new IllegalStateException("User is not logged in");
@@ -75,10 +127,6 @@ public class ServerIssueActions implements ServerComponent {
if (!authorizationDao.isAuthorizedComponentId(dto.getResourceId(), userSession.userId(), requiredRole)) {
throw new IllegalStateException("User does not have the role " + requiredRole + " required to change the issue: " + issueKey);
}
- DefaultIssue issue = dto.toDefaultIssue();
- if (workflow.doManualTransition(issue, transition)) {
- issueDao.update(Arrays.asList(IssueDto.toDto(issue, dto.getResourceId(), dto.getRuleId())));
- }
- return issue;
+ return dto;
}
}
diff --git a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
index c02d1a7e1cd..896ddb28287 100644
--- a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
+++ b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
@@ -40,6 +40,8 @@ import org.sonar.core.config.Logback;
import org.sonar.core.i18n.GwtI18n;
import org.sonar.core.i18n.I18nManager;
import org.sonar.core.i18n.RuleI18nManager;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.core.issue.workflow.FunctionExecutor;
import org.sonar.core.issue.workflow.IssueWorkflow;
import org.sonar.core.measure.MeasureFilterEngine;
import org.sonar.core.measure.MeasureFilterExecutor;
@@ -240,6 +242,8 @@ public final class Platform {
servicesContainer.addSingleton(Periods.class);
// issues
+ servicesContainer.addSingleton(IssueUpdater.class);
+ servicesContainer.addSingleton(FunctionExecutor.class);
servicesContainer.addSingleton(IssueWorkflow.class);
servicesContainer.addSingleton(ServerIssueActions.class);
servicesContainer.addSingleton(ServerIssueFinder.class);
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb
index 4886e1e8df7..f473065fc85 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb
@@ -54,20 +54,54 @@ class Api::IssuesController < Api::ApiController
issue = Internal.issues.doTransition(params[:issue], params[:transition])
if issue
render :json => jsonp({
- :issue => issue_to_json(issue)
- })
+ :issue => issue_to_json(issue)
+ })
else
render :status => 400
end
end
- # POST /api/issues/create?severity=xxx>&<resolution=xxx>&component=<component key>
+ # POST /api/issues/add_comment?issue=<key>&text=<text>
+ # Note that the text can also be set in the post body
+ def add_comment
+ verify_post_request
+ require_parameters :issue, :text
+
+ text = Api::Utils.read_post_request_param(:text)
+ Internal.issues.addComment(params[:issue], text)
+ # TODO add more response data ?
+ render :json => jsonp({})
+ end
+
+ # POST /api/issues/assign?issue=<key>&assignee=<optional assignee>
+ # A nil assignee will remove the assignee.
+ def assign
+ verify_post_request
+ require_parameters :issue
+
+ Internal.issues.assign(params[:issue], params[:assignee])
+ # TODO return the assignee
+ render :json => jsonp({})
+ end
+
+ # POST /api/issues/create
+ #
+ # Mandatory parameters
+ # 'component' is the component key
+ # 'rule' includes the repository key and the rule key, for example 'squid:AvoidCycle'
+ #
+ # Optional parameters
+ # 'severity' is in BLOCKER, CRITICAL, ... INFO. Default value is MAJOR.
+ # 'line' starts at 1
+ # 'description' is the plain-text description
+ #
def create
verify_post_request
access_denied unless logged_in?
+ require_parameters :component, :rule
- # TODO
- render :json => jsonp({})
+ issue = Internal.issues.create(params)
+ render :json => jsonp({:issue => issue_to_json(issue)})
end
private