Browse Source

SONAR-3755 complete the ability to change issue

tags/3.6
Simon Brandhof 11 years ago
parent
commit
fb93878ddb

+ 2
- 25
sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssueChanges.java View File

@@ -22,10 +22,10 @@ package org.sonar.batch.issue;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.IssueChange;
import org.sonar.api.issue.IssueChanges;
import org.sonar.core.issue.ApplyIssueChange;
import org.sonar.core.issue.DefaultIssue;

import java.util.Date;
import java.util.Map;

public class ScanIssueChanges implements IssueChanges {

@@ -41,7 +41,7 @@ public class ScanIssueChanges implements IssueChanges {
return issue;
}
DefaultIssue reloaded = reload(issue);
doChange(reloaded, change);
ApplyIssueChange.apply(reloaded, change);
// TODO set the date of loading of issues
reloaded.setUpdatedAt(new Date());
cache.addOrUpdate(reloaded);
@@ -49,29 +49,6 @@ public class ScanIssueChanges implements IssueChanges {
return reloaded;
}

private void doChange(DefaultIssue issue, IssueChange change) {
if (change.isCostChanged()) {
issue.setCost(change.cost());
}
if (change.manualSeverity() != null) {
change.setManualSeverity(change.manualSeverity());
}
if (change.severity() != null) {
issue.setSeverity(change.severity());
}
if (change.isAssigneeChanged()) {
issue.setAssignee(change.assignee());
}
if (change.resolution() != null) {
issue.setResolution(change.resolution());
}
if (change.isLineChanged()) {
issue.setLine(change.line());
}
for (Map.Entry<String, String> entry : change.attributes().entrySet()) {
issue.setAttribute(entry.getKey(), entry.getValue());
}
}

private DefaultIssue reload(Issue issue) {
DefaultIssue reloaded = (DefaultIssue) cache.componentIssue(issue.componentKey(), issue.key());

+ 58
- 0
sonar-core/src/main/java/org/sonar/core/issue/ApplyIssueChange.java View File

@@ -0,0 +1,58 @@
/*
* Sonar, open source software quality management tool.
* Copyright (C) 2008-2012 SonarSource
* mailto:contact AT sonarsource DOT com
*
* Sonar 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.
*
* Sonar 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 Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.core.issue;

import org.sonar.api.issue.IssueChange;

import java.util.Map;

public class ApplyIssueChange {

public static void apply(DefaultIssue issue, IssueChange change) {
if (change.description() != null) {
issue.setDescription(change.description());
}
if (change.manualSeverity() != null) {
change.setManualSeverity(change.manualSeverity());
}
if (change.severity() != null) {
issue.setSeverity(change.severity());
}
if (change.title() != null) {
issue.setTitle(change.title());
}
if (change.isAssigneeChanged()) {
issue.setAssignee(change.assignee());
}
if (change.resolution() != null) {
issue.setResolution(change.resolution());
}
if (change.isLineChanged()) {
issue.setLine(change.line());
}
if (change.isCostChanged()) {
issue.setCost(change.cost());
}
for (Map.Entry<String, String> entry : change.attributes().entrySet()) {
issue.setAttribute(entry.getKey(), entry.getValue());
}
}

}

+ 1
- 0
sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java View File

@@ -292,4 +292,5 @@ public class DefaultIssue implements Issue, Serializable {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}


}

+ 3
- 4
sonar-core/src/main/java/org/sonar/core/issue/IssueDto.java View File

@@ -27,7 +27,6 @@ import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.KeyValueFormat;

import javax.annotation.Nullable;

import java.util.Date;

/**
@@ -58,7 +57,7 @@ public final class IssueDto {
private Date closedAt;

// joins
private String rule;
private String ruleKey;

// This fields are not persited in db
private transient String ruleRepo;
@@ -272,7 +271,7 @@ public final class IssueDto {
*/
public IssueDto setRuleKey_unit_test_only(String repo, String rule) {
this.ruleRepo = repo;
this.rule = rule;
this.ruleKey = rule;
return this;
}

@@ -353,7 +352,7 @@ public final class IssueDto {
issue.setComponentKey(componentKey);
issue.setManual(manualIssue);
issue.setManualSeverity(manualSeverity);
issue.setRuleKey(RuleKey.of(ruleRepo, rule));
issue.setRuleKey(RuleKey.of(ruleRepo, ruleKey));
// TODO personId
return issue;
}

+ 1
- 1
sonar-core/src/main/resources/org/sonar/core/issue/IssueMapper.xml View File

@@ -26,7 +26,7 @@
i.created_at as createdAt,
i.updated_at as updatedAt,
i.closed_at as closedAt,
r.plugin_rule_key as rule,
r.plugin_rule_key as ruleKey,
r.plugin_name as ruleRepo,
p.kee as componentKey
</sql>

+ 84
- 0
sonar-core/src/test/java/org/sonar/core/issue/ApplyIssueChangeTest.java View File

@@ -0,0 +1,84 @@
/*
* Sonar, open source software quality management tool.
* Copyright (C) 2008-2012 SonarSource
* mailto:contact AT sonarsource DOT com
*
* Sonar 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.
*
* Sonar 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 Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.core.issue;

import org.junit.Test;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.IssueChange;
import org.sonar.api.rule.Severity;

import static org.fest.assertions.Assertions.assertThat;

public class ApplyIssueChangeTest {

@Test
public void should_change_fields() throws Exception {
DefaultIssue issue = new DefaultIssue().setComponentKey("org/struts/Action.java").setKey("ABCDE");
ApplyIssueChange.apply(issue, IssueChange.create()
.setLine(200)
.setResolution(Issue.RESOLUTION_FALSE_POSITIVE)
.setAttribute("JIRA", "FOO-123")
.setManualSeverity(true)
.setSeverity(Severity.CRITICAL)
.setAssignee("arthur")
.setTitle("new title")
.setDescription("new desc")
.setCost(4.2)
);
assertThat(issue.line()).isEqualTo(200);
assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FALSE_POSITIVE);
assertThat(issue.title()).isEqualTo("new title");
assertThat(issue.description()).isEqualTo("new desc");
assertThat(issue.attribute("JIRA")).isEqualTo("FOO-123");
assertThat(issue.severity()).isEqualTo(Severity.CRITICAL);
assertThat(issue.assignee()).isEqualTo("arthur");
assertThat(issue.cost()).isEqualTo(4.2);
}

@Test
public void should_not_touch_fields() throws Exception {
DefaultIssue issue = new DefaultIssue()
.setComponentKey("org/struts/Action.java")
.setKey("ABCDE")
.setLine(123)
.setTitle("the title")
.setDescription("the desc")
.setAssignee("karadoc")
.setCost(4.2)
.setAttribute("JIRA", "FOO-123")
.setManualSeverity(true)
.setSeverity("BLOCKER")
.setStatus("CLOSED")
.setResolution("FIXED");
ApplyIssueChange.apply(issue, IssueChange.create());

assertThat(issue.componentKey()).isEqualTo("org/struts/Action.java");
assertThat(issue.key()).isEqualTo("ABCDE");
assertThat(issue.line()).isEqualTo(123);
assertThat(issue.resolution()).isEqualTo("FIXED");
assertThat(issue.attribute("JIRA")).isEqualTo("FOO-123");
assertThat(issue.severity()).isEqualTo("BLOCKER");
assertThat(issue.assignee()).isEqualTo("karadoc");
assertThat(issue.cost()).isEqualTo(4.2);
assertThat(issue.isManualSeverity()).isTrue();
assertThat(issue.description()).isEqualTo("the desc");
assertThat(issue.title()).isEqualTo("the title");
}
}

+ 10
- 0
sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueChange.java View File

@@ -41,6 +41,7 @@ public class IssueChange {
private String resolution = null;
private boolean assigneeChanged = false;
private String assignee = null;
private String title = null;
private Map<String, String> attributes = null;

private IssueChange() {
@@ -75,6 +76,11 @@ public class IssueChange {
return this;
}

public IssueChange setTitle(String s) {
this.title = s;
return this;
}

public IssueChange setDescription(String s) {
this.description = s;
return this;
@@ -131,6 +137,10 @@ public class IssueChange {
return description;
}

public String title() {
return title;
}

public Integer line() {
return line;
}

+ 52
- 4
sonar-server/src/main/java/org/sonar/server/issue/DefaultJRubyIssues.java View File

@@ -23,6 +23,7 @@ import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import org.sonar.api.issue.IssueChange;
import org.sonar.api.issue.IssueFinder;
import org.sonar.api.issue.IssueQuery;
import org.sonar.api.issue.JRubyIssues;
@@ -45,20 +46,28 @@ import java.util.Map;
public class DefaultJRubyIssues implements JRubyIssues {

private final IssueFinder finder;
private final ServerIssueChanges changes;

public DefaultJRubyIssues(IssueFinder f) {
public DefaultJRubyIssues(IssueFinder f, ServerIssueChanges changes) {
this.finder = f;
this.changes = changes;
JRubyFacades.setIssues(this);
}

/**
* Requires the role {@link org.sonar.api.web.UserRole#CODEVIEWER}
*/
public IssueFinder.Results find(Map<String, Object> params, Integer currentUserId) {
return finder.find(newQuery(params), currentUserId, UserRole.CODEVIEWER);
public IssueFinder.Results find(Map<String, Object> params, @Nullable Integer currentUserId) {
// TODO move the role to IssueFinder
return finder.find(toQuery(params), currentUserId, UserRole.CODEVIEWER);
}

IssueQuery newQuery(Map<String, Object> props) {
public void change(Map<String, Object> params, @Nullable Integer currentUserId) {
String issueKey = (String) params.get("key");
changes.change(issueKey, toChange(params), currentUserId);
}

IssueQuery toQuery(Map<String, Object> props) {
IssueQuery.Builder builder = IssueQuery.builder();
builder.keys(toStrings(props.get("keys")));
builder.severities(toStrings(props.get("severities")));
@@ -76,6 +85,34 @@ public class DefaultJRubyIssues implements JRubyIssues {
return builder.build();
}

IssueChange toChange(Map<String, Object> props) {
IssueChange change = IssueChange.create();
if (props.containsKey("newSeverity")) {
change.setSeverity((String) props.get("newSeverity"));
}
if (props.containsKey("newDesc")) {
change.setDescription((String) props.get("newDesc"));
}
if (props.containsKey("newCost")) {
change.setCost(toDouble(props.get("newCost")));
}
if (props.containsKey("newLine")) {
change.setLine(toInteger(props.get("newLine")));
}
if (props.containsKey("newAssignee")) {
change.setAssignee((String) props.get("newAssignee"));
}
if (props.containsKey("newResolution")) {
change.setResolution((String) props.get("newResolution"));
}
if (props.containsKey("newTitle")) {
change.setTitle((String) props.get("newTitle"));
}

// TODO set attribute + comment
return change;
}

@SuppressWarnings("unchecked")
static Collection<RuleKey> toRules(Object o) {
Collection<RuleKey> result = null;
@@ -122,6 +159,17 @@ public class DefaultJRubyIssues implements JRubyIssues {
return null;
}

Double toDouble(Object o) {
if (o instanceof Double) {
return (Double) o;
}
if (o instanceof String) {
return Double.parseDouble((String) o);
}
return null;
}


Date toDate(Object o) {
if (o instanceof Date) {
return (Date) o;

+ 2
- 3
sonar-server/src/main/java/org/sonar/server/issue/ServerIssueChanges.java View File

@@ -22,6 +22,7 @@ package org.sonar.server.issue;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.IssueChange;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.ApplyIssueChange;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueDao;
import org.sonar.core.issue.IssueDto;
@@ -58,11 +59,9 @@ public class ServerIssueChanges {
}
DefaultIssue issue = dto.toDefaultIssue();
if (change.hasChanges()) {
// TODO apply changes
ApplyIssueChange.apply(issue, change);
issueDao.update(Arrays.asList(IssueDto.toDto(issue, dto.getResourceId(), dto.getRuleId())));
}


return issue;
}
}

sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueFinder.java → sonar-server/src/main/java/org/sonar/server/issue/ServerIssueFinder.java View File

@@ -17,8 +17,7 @@
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/

package org.sonar.core.issue;
package org.sonar.server.issue;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@@ -28,6 +27,8 @@ import org.slf4j.LoggerFactory;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.IssueFinder;
import org.sonar.api.issue.IssueQuery;
import org.sonar.core.issue.IssueDao;
import org.sonar.core.issue.IssueDto;
import org.sonar.core.persistence.MyBatis;
import org.sonar.core.user.AuthorizationDao;

@@ -38,16 +39,16 @@ import java.util.Set;
/**
* @since 3.6
*/
public class DefaultIssueFinder implements IssueFinder {
public class ServerIssueFinder implements IssueFinder {

private static final Logger LOG = LoggerFactory.getLogger(DefaultIssueFinder.class);
private static final Logger LOG = LoggerFactory.getLogger(ServerIssueFinder.class);

private final MyBatis myBatis;
private final IssueDao issueDao;
private final AuthorizationDao authorizationDao;


public DefaultIssueFinder(MyBatis myBatis, IssueDao issueDao, AuthorizationDao authorizationDao) {
public ServerIssueFinder(MyBatis myBatis, IssueDao issueDao, AuthorizationDao authorizationDao) {
this.myBatis = myBatis;
this.issueDao = issueDao;
this.authorizationDao = authorizationDao;

+ 23
- 0
sonar-server/src/main/java/org/sonar/server/issue/package-info.java View File

@@ -0,0 +1,23 @@
/*
* Sonar, open source software quality management tool.
* Copyright (C) 2008-2012 SonarSource
* mailto:contact AT sonarsource DOT com
*
* Sonar 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.
*
* Sonar 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 Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
@ParametersAreNonnullByDefault
package org.sonar.server.issue;

import javax.annotation.ParametersAreNonnullByDefault;

+ 7
- 39
sonar-server/src/main/java/org/sonar/server/platform/Platform.java View File

@@ -40,20 +40,13 @@ 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.DefaultIssueFinder;
import org.sonar.server.issue.ServerIssueFinder;
import org.sonar.core.measure.MeasureFilterEngine;
import org.sonar.core.measure.MeasureFilterExecutor;
import org.sonar.core.measure.MeasureFilterFactory;
import org.sonar.core.metric.DefaultMetricFinder;
import org.sonar.core.notification.DefaultNotificationManager;
import org.sonar.core.persistence.DaoUtils;
import org.sonar.core.persistence.DatabaseMigrator;
import org.sonar.core.persistence.DatabaseVersion;
import org.sonar.core.persistence.DefaultDatabase;
import org.sonar.core.persistence.DryRunDatabaseFactory;
import org.sonar.core.persistence.MyBatis;
import org.sonar.core.persistence.SemaphoreUpdater;
import org.sonar.core.persistence.SemaphoresImpl;
import org.sonar.core.persistence.*;
import org.sonar.core.purge.PurgeProfiler;
import org.sonar.core.qualitymodel.DefaultModelFinder;
import org.sonar.core.resource.DefaultResourcePermissions;
@@ -82,37 +75,12 @@ import org.sonar.server.macro.MacroInterpreter;
import org.sonar.server.notifications.NotificationCenter;
import org.sonar.server.notifications.NotificationService;
import org.sonar.server.notifications.reviews.ReviewsNotificationManager;
import org.sonar.server.plugins.ApplicationDeployer;
import org.sonar.server.plugins.DefaultServerPluginRepository;
import org.sonar.server.plugins.InstalledPluginReferentialFactory;
import org.sonar.server.plugins.PluginDeployer;
import org.sonar.server.plugins.PluginDownloader;
import org.sonar.server.plugins.ServerExtensionInstaller;
import org.sonar.server.plugins.UpdateCenterClient;
import org.sonar.server.plugins.UpdateCenterMatrixFactory;
import org.sonar.server.plugins.*;
import org.sonar.server.qualitymodel.DefaultModelManager;
import org.sonar.server.rules.ProfilesConsole;
import org.sonar.server.rules.RulesConsole;
import org.sonar.server.startup.DeleteDeprecatedMeasures;
import org.sonar.server.startup.GenerateBootstrapIndex;
import org.sonar.server.startup.GeneratePluginIndex;
import org.sonar.server.startup.GwtPublisher;
import org.sonar.server.startup.JdbcDriverDeployer;
import org.sonar.server.startup.LogServerId;
import org.sonar.server.startup.RegisterMetrics;
import org.sonar.server.startup.RegisterNewDashboards;
import org.sonar.server.startup.RegisterNewMeasureFilters;
import org.sonar.server.startup.RegisterNewProfiles;
import org.sonar.server.startup.RegisterQualityModels;
import org.sonar.server.startup.RegisterRules;
import org.sonar.server.startup.RegisterServletFilters;
import org.sonar.server.startup.RenameDeprecatedPropertyKeys;
import org.sonar.server.startup.ServerMetadataPersister;
import org.sonar.server.ui.CodeColorizers;
import org.sonar.server.ui.JRubyI18n;
import org.sonar.server.ui.PageDecorations;
import org.sonar.server.ui.SecurityRealmFactory;
import org.sonar.server.ui.Views;
import org.sonar.server.startup.*;
import org.sonar.server.ui.*;

import javax.servlet.ServletContext;

@@ -267,9 +235,9 @@ public final class Platform {
servicesContainer.addSingleton(DryRunDatabaseFactory.class);
servicesContainer.addSingleton(DefaultResourcePermissions.class);
servicesContainer.addSingleton(Periods.class);
servicesContainer.addSingleton(DefaultIssueFinder.class);
servicesContainer.addSingleton(DefaultJRubyIssues.class);
servicesContainer.addSingleton(ServerIssueChanges.class);
servicesContainer.addSingleton(ServerIssueFinder.class);
servicesContainer.addSingleton(DefaultJRubyIssues.class);

// Notifications
servicesContainer.addSingleton(EmailSettings.class);

+ 4
- 3
sonar-server/src/test/java/org/sonar/server/issue/DefaultJRubyIssuesTest.java View File

@@ -42,7 +42,8 @@ import static org.mockito.Mockito.*;
public class DefaultJRubyIssuesTest {

IssueFinder finder = mock(IssueFinder.class);
DefaultJRubyIssues facade = new DefaultJRubyIssues(finder);
ServerIssueChanges changes = mock(ServerIssueChanges.class);
DefaultJRubyIssues facade = new DefaultJRubyIssues(finder, changes);

@Test
public void test_find() throws Exception {
@@ -72,7 +73,7 @@ public class DefaultJRubyIssuesTest {
map.put("limit", 10);
map.put("offset", 50);

IssueQuery query = new DefaultJRubyIssues(finder).newQuery(map);
IssueQuery query = new DefaultJRubyIssues(finder, changes).toQuery(map);
assertThat(query.keys()).containsOnly("ABCDE1234");
assertThat(query.severities()).containsOnly("MAJOR", "MINOR");
assertThat(query.statuses()).containsOnly("CLOSED");
@@ -103,7 +104,7 @@ public class DefaultJRubyIssuesTest {
assertThat(DefaultJRubyIssues.toStrings("")).isEmpty();
assertThat(DefaultJRubyIssues.toStrings("foo")).containsOnly("foo");
assertThat(DefaultJRubyIssues.toStrings("foo,bar")).containsOnly("foo", "bar");
assertThat(DefaultJRubyIssues.toStrings(asList("foo","bar"))).containsOnly("foo", "bar");
assertThat(DefaultJRubyIssues.toStrings(asList("foo", "bar"))).containsOnly("foo", "bar");

}


sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueFinderTest.java → sonar-server/src/test/java/org/sonar/server/issue/ServerIssueFinderTest.java View File

@@ -17,7 +17,7 @@
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.core.issue;
package org.sonar.server.issue;

import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
@@ -28,6 +28,8 @@ import org.sonar.api.issue.Issue;
import org.sonar.api.issue.IssueFinder;
import org.sonar.api.issue.IssueQuery;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.IssueDao;
import org.sonar.core.issue.IssueDto;
import org.sonar.core.persistence.MyBatis;
import org.sonar.core.user.AuthorizationDao;

@@ -39,10 +41,10 @@ import static org.mockito.Matchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class DefaultIssueFinderTest {
public class ServerIssueFinderTest {

MyBatis mybatis;
DefaultIssueFinder finder;
ServerIssueFinder finder;
IssueDao issueDao;
AuthorizationDao authorizationDao;

@@ -51,7 +53,7 @@ public class DefaultIssueFinderTest {
mybatis = mock(MyBatis.class);
issueDao = mock(IssueDao.class);
authorizationDao = mock(AuthorizationDao.class);
finder = new DefaultIssueFinder(mybatis, issueDao, authorizationDao);
finder = new ServerIssueFinder(mybatis, issueDao, authorizationDao);
}

@Test

+ 5
- 0
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueChange.java View File

@@ -70,6 +70,11 @@ public class IssueChange {
return this;
}

public IssueChange description(String s) {
params.put("newDescription", s);
return this;
}

public IssueChange attribute(String key, @Nullable String value) {
if (value == null) {
params.put("newAttr[" + key + "]", "");

Loading…
Cancel
Save