@@ -0,0 +1,147 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.server.issue.ws; | |||
import com.google.common.io.Resources; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.function.Consumer; | |||
import java.util.function.Function; | |||
import java.util.stream.Stream; | |||
import org.sonar.api.server.ws.Request; | |||
import org.sonar.api.server.ws.Response; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.core.util.stream.Collectors; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.issue.IssueDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.issue.IssueFinder; | |||
import org.sonarqube.ws.Issues.ChangelogWsResponse; | |||
import org.sonarqube.ws.Issues.ChangelogWsResponse.Changelog; | |||
import static com.google.common.base.Strings.emptyToNull; | |||
import static org.sonar.api.utils.DateUtils.formatDateTime; | |||
import static org.sonar.core.util.Protobuf.setNullable; | |||
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01; | |||
import static org.sonar.server.issue.IssueFieldsSetter.TECHNICAL_DEBT; | |||
import static org.sonar.server.ws.WsUtils.writeProtobuf; | |||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE; | |||
public class ChangelogAction implements IssuesWsAction { | |||
private static final String ACTION_CHANGELOG = "changelog"; | |||
private static final String EFFORT_CHANGELOG_KEY = "effort"; | |||
private final DbClient dbClient; | |||
private final IssueFinder issueFinder; | |||
public ChangelogAction(DbClient dbClient, IssueFinder issueFinder) { | |||
this.dbClient = dbClient; | |||
this.issueFinder = issueFinder; | |||
} | |||
@Override | |||
public void define(WebService.NewController context) { | |||
WebService.NewAction action = context.createAction(ACTION_CHANGELOG) | |||
.setDescription("Display changelog of an issue<br/>." + | |||
"Require the 'Browse' permission on the project of the specified issue<br/>.") | |||
.setSince("4.1") | |||
.setHandler(this) | |||
.setResponseExample(Resources.getResource(IssuesWs.class, "example-changelog.json")); | |||
action.createParam(PARAM_ISSUE) | |||
.setDescription("Issue key") | |||
.setRequired(true) | |||
.setExampleValue(UUID_EXAMPLE_01); | |||
} | |||
@Override | |||
public void handle(Request request, Response response) throws Exception { | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
ChangelogWsResponse wsResponse = Stream.of(request) | |||
.map(searchChangelog(dbSession)) | |||
.map(buildResponse()) | |||
.collect(Collectors.toOneElement()); | |||
writeProtobuf(wsResponse, | |||
request, response); | |||
} | |||
} | |||
private Function<Request, ChangeLogResults> searchChangelog(DbSession dbSession) { | |||
return request -> new ChangeLogResults(dbSession, request.mandatoryParam(PARAM_ISSUE)); | |||
} | |||
private static Function<ChangeLogResults, ChangelogWsResponse> buildResponse() { | |||
return result -> Stream.of(ChangelogWsResponse.newBuilder()) | |||
.peek(addChanges(result)) | |||
.map(ChangelogWsResponse.Builder::build) | |||
.collect(Collectors.toOneElement()); | |||
} | |||
private static Consumer<ChangelogWsResponse.Builder> addChanges(ChangeLogResults results) { | |||
return response -> results.changes.stream() | |||
.map(toWsChangelog(results)) | |||
.forEach(response::addChangelog); | |||
} | |||
private static Function<FieldDiffs, Changelog> toWsChangelog(ChangeLogResults results) { | |||
return change -> { | |||
String userLogin = change.userLogin(); | |||
Changelog.Builder changelogBuilder = Changelog.newBuilder(); | |||
changelogBuilder.setCreationDate(formatDateTime(change.creationDate())); | |||
UserDto user = userLogin == null ? null : results.users.get(userLogin); | |||
if (user != null) { | |||
changelogBuilder.setUser(user.getLogin()); | |||
changelogBuilder.setUserName(user.getName()); | |||
setNullable(emptyToNull(user.getEmail()), changelogBuilder::setEmail); | |||
} | |||
change.diffs().entrySet().stream() | |||
.map(toWsDiff()) | |||
.forEach(changelogBuilder::addDiffs); | |||
return changelogBuilder.build(); | |||
}; | |||
} | |||
private static Function<Map.Entry<String, FieldDiffs.Diff>, Changelog.Diff> toWsDiff() { | |||
return diff -> { | |||
FieldDiffs.Diff value = diff.getValue(); | |||
Changelog.Diff.Builder diffBuilder = Changelog.Diff.newBuilder(); | |||
String key = diff.getKey(); | |||
diffBuilder.setKey(key.equals(TECHNICAL_DEBT) ? EFFORT_CHANGELOG_KEY : key); | |||
setNullable(emptyToNull(value.newValue().toString()), diffBuilder::setNewValue); | |||
setNullable(emptyToNull(value.oldValue().toString()), diffBuilder::setOldValue); | |||
return diffBuilder.build(); | |||
}; | |||
} | |||
private class ChangeLogResults { | |||
private final List<FieldDiffs> changes; | |||
private final Map<String, UserDto> users; | |||
ChangeLogResults(DbSession dbSession, String issueKey) { | |||
IssueDto dbIssue = issueFinder.getByKey(dbSession, issueKey); | |||
this.changes = dbClient.issueChangeDao().selectChangelogByIssue(dbSession, dbIssue.getKey()); | |||
List<String> logins = changes.stream().filter(change -> change.userLogin() != null).map(FieldDiffs::userLogin).collect(Collectors.toList()); | |||
this.users = dbClient.userDao().selectByLogins(dbSession, logins).stream().collect(Collectors.uniqueIndex(UserDto::getLogin)); | |||
} | |||
} | |||
} |
@@ -65,6 +65,7 @@ public class IssueWsModule extends Module { | |||
SetTagsAction.class, | |||
SetTypeAction.class, | |||
ComponentTagsAction.class, | |||
AuthorsAction.class); | |||
AuthorsAction.class, | |||
ChangelogAction.class); | |||
} | |||
} |
@@ -19,7 +19,6 @@ | |||
*/ | |||
package org.sonar.server.issue.ws; | |||
import com.google.common.io.Resources; | |||
import org.sonar.api.issue.DefaultTransitions; | |||
import org.sonar.api.rule.Severity; | |||
import org.sonar.api.rules.RuleType; | |||
@@ -30,7 +29,6 @@ public class IssuesWs implements WebService { | |||
public static final String API_ENDPOINT = "api/issues"; | |||
public static final String CHANGELOG_ACTION = "changelog"; | |||
public static final String ADD_COMMENT_ACTION = "add_comment"; | |||
public static final String DELETE_COMMENT_ACTION = "delete_comment"; | |||
public static final String EDIT_COMMENT_ACTION = "edit_comment"; | |||
@@ -55,27 +53,12 @@ public class IssuesWs implements WebService { | |||
} | |||
private static void defineRailsActions(NewController controller) { | |||
defineChangelogAction(controller); | |||
defineAddCommentAction(controller); | |||
defineDeleteCommentAction(controller); | |||
defineEditCommentAction(controller); | |||
defineBulkChangeAction(controller); | |||
} | |||
private static void defineChangelogAction(NewController controller) { | |||
WebService.NewAction action = controller.createAction(CHANGELOG_ACTION) | |||
.setDescription("Display changelog of an issue") | |||
.setSince("4.1") | |||
.setHandler(RailsHandler.INSTANCE) | |||
.setResponseExample(Resources.getResource(IssuesWs.class, "example-changelog.json")); | |||
action.createParam("issue") | |||
.setDescription("Key of the issue") | |||
.setRequired(true) | |||
.setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef"); | |||
RailsHandler.addFormatParam(action); | |||
} | |||
private static void defineAddCommentAction(NewController controller) { | |||
WebService.NewAction action = controller.createAction(ADD_COMMENT_ACTION) | |||
.setDescription("Add a comment. Requires authentication and Browse permission on project") |
@@ -3,14 +3,15 @@ | |||
{ | |||
"user": "john.smith", | |||
"userName": "John Smith", | |||
"email": "john@smith.com", | |||
"creationDate": "2014-03-04T23:03:44+0100", | |||
"diffs": [ | |||
{ | |||
"key": "effort", | |||
"newValue": "2min" | |||
"key": "severity", | |||
"newValue": "BLOCKER", | |||
"oldValue": "MAJOR" | |||
} | |||
] | |||
} | |||
] | |||
} |
@@ -27,6 +27,7 @@ import org.sonar.api.utils.System2; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDbTester; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.issue.IssueDbTester; | |||
import org.sonar.db.issue.IssueDto; | |||
import org.sonar.db.rule.RuleDbTester; | |||
import org.sonar.db.rule.RuleDto; |
@@ -34,6 +34,7 @@ import org.sonar.db.DbClient; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDbTester; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.issue.IssueDbTester; | |||
import org.sonar.db.issue.IssueDto; | |||
import org.sonar.db.rule.RuleDbTester; | |||
import org.sonar.db.rule.RuleDto; |
@@ -0,0 +1,263 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.server.issue.ws; | |||
import com.google.common.base.Throwables; | |||
import java.io.IOException; | |||
import javax.annotation.Nullable; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.api.utils.DateUtils; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.issue.IssueDto; | |||
import org.sonar.db.rule.RuleDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.db.user.UserTesting; | |||
import org.sonar.server.exceptions.ForbiddenException; | |||
import org.sonar.server.issue.IssueFinder; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.ws.TestRequest; | |||
import org.sonar.server.ws.WsActionTester; | |||
import org.sonarqube.ws.Issues.ChangelogWsResponse; | |||
import org.sonarqube.ws.Issues.ChangelogWsResponse.Changelog.Diff; | |||
import org.sonarqube.ws.MediaTypes; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.groups.Tuple.tuple; | |||
import static org.sonar.api.web.UserRole.CODEVIEWER; | |||
import static org.sonar.api.web.UserRole.USER; | |||
import static org.sonar.core.util.Protobuf.setNullable; | |||
import static org.sonar.db.component.ComponentTesting.newFileDto; | |||
import static org.sonar.db.rule.RuleTesting.newRuleDto; | |||
import static org.sonar.db.user.UserTesting.newUserDto; | |||
import static org.sonar.server.issue.IssueTesting.newDto; | |||
import static org.sonar.test.JsonAssert.assertJson; | |||
public class ChangelogActionTest { | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
@Rule | |||
public DbTester db = DbTester.create(System2.INSTANCE); | |||
@Rule | |||
public UserSessionRule userSession = UserSessionRule.standalone(); | |||
private WsActionTester tester = new WsActionTester(new ChangelogAction(db.getDbClient(), new IssueFinder(db.getDbClient(), userSession))); | |||
@Test | |||
public void return_changelog() throws Exception { | |||
UserDto user = db.users().insertUser(); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.login("john").addProjectUuidPermissions(USER, issueDto.getProjectUuid()); | |||
db.issues().insertIssueChanges(issueDto, new FieldDiffs().setUserLogin(user.getLogin()).setDiff("severity", "MAJOR", "BLOCKER")); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
assertThat(result.getChangelogList()).hasSize(1); | |||
assertThat(result.getChangelogList().get(0).getUser()).isNotNull().isEqualTo(user.getLogin()); | |||
assertThat(result.getChangelogList().get(0).getUserName()).isNotNull().isEqualTo(user.getName()); | |||
assertThat(result.getChangelogList().get(0).getEmail()).isNotNull().isEqualTo(user.getEmail()); | |||
assertThat(result.getChangelogList().get(0).getCreationDate()).isNotEmpty(); | |||
assertThat(result.getChangelogList().get(0).getDiffsList()).extracting(Diff::getKey, Diff::getOldValue, Diff::getNewValue).containsOnly(tuple("severity", "MAJOR", "BLOCKER")); | |||
} | |||
@Test | |||
public void return_changelog_on_user_without_email() throws Exception { | |||
UserDto user = db.users().insertUser(UserTesting.newUserDto("john", "John", null)); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.login("john").addProjectUuidPermissions(USER, issueDto.getProjectUuid()); | |||
db.issues().insertIssueChanges(issueDto, new FieldDiffs().setUserLogin(user.getLogin()).setDiff("severity", "MAJOR", "BLOCKER")); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
assertThat(result.getChangelogList()).hasSize(1); | |||
assertThat(result.getChangelogList().get(0).getUser()).isNotNull().isEqualTo(user.getLogin()); | |||
assertThat(result.getChangelogList().get(0).getUserName()).isNotNull().isEqualTo(user.getName()); | |||
assertThat(result.getChangelogList().get(0).hasEmail()).isFalse(); | |||
} | |||
@Test | |||
public void return_changelog_not_having_user() throws Exception { | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.login("john").addProjectUuidPermissions(USER, issueDto.getProjectUuid()); | |||
db.issues().insertIssueChanges(issueDto, new FieldDiffs().setUserLogin(null).setDiff("severity", "MAJOR", "BLOCKER")); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
assertThat(result.getChangelogList()).hasSize(1); | |||
assertThat(result.getChangelogList().get(0).hasUser()).isFalse(); | |||
assertThat(result.getChangelogList().get(0).hasUserName()).isFalse(); | |||
assertThat(result.getChangelogList().get(0).hasEmail()).isFalse(); | |||
assertThat(result.getChangelogList().get(0).getDiffsList()).isNotEmpty(); | |||
} | |||
@Test | |||
public void return_changelog_on_none_existing_user() throws Exception { | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.login("john").addProjectUuidPermissions(USER, issueDto.getProjectUuid()); | |||
db.issues().insertIssueChanges(issueDto, new FieldDiffs().setUserLogin("UNKNOWN").setDiff("severity", "MAJOR", "BLOCKER")); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
assertThat(result.getChangelogList()).hasSize(1); | |||
assertThat(result.getChangelogList().get(0).hasUser()).isFalse(); | |||
assertThat(result.getChangelogList().get(0).hasUserName()).isFalse(); | |||
assertThat(result.getChangelogList().get(0).hasEmail()).isFalse(); | |||
assertThat(result.getChangelogList().get(0).getDiffsList()).isNotEmpty(); | |||
} | |||
@Test | |||
public void return_multiple_diffs() throws Exception { | |||
UserDto user = db.users().insertUser(); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.login("john").addProjectUuidPermissions(USER, issueDto.getProjectUuid()); | |||
db.issues().insertIssueChanges(issueDto, new FieldDiffs().setUserLogin(user.getLogin()).setDiff("severity", "MAJOR", "BLOCKER").setDiff("status", "RESOLVED", "CLOSED")); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
assertThat(result.getChangelogList()).hasSize(1); | |||
assertThat(result.getChangelogList().get(0).getDiffsList()).extracting(Diff::getKey, Diff::getOldValue, Diff::getNewValue) | |||
.containsOnly(tuple("severity", "MAJOR", "BLOCKER"), tuple("status", "RESOLVED", "CLOSED")); | |||
} | |||
@Test | |||
public void return_changelog_when_no_old_value() throws Exception { | |||
UserDto user = db.users().insertUser(); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.login("john").addProjectUuidPermissions(USER, issueDto.getProjectUuid()); | |||
db.issues().insertIssueChanges(issueDto, new FieldDiffs().setUserLogin(user.getLogin()).setDiff("severity", null, "BLOCKER")); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
assertThat(result.getChangelogList()).hasSize(1); | |||
assertThat(result.getChangelogList().get(0).getDiffsList().get(0).hasOldValue()).isFalse(); | |||
} | |||
@Test | |||
public void return_changelog_when_no_new_value() throws Exception { | |||
UserDto user = db.users().insertUser(); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.login("john").addProjectUuidPermissions(USER, issueDto.getProjectUuid()); | |||
db.issues().insertIssueChanges(issueDto, new FieldDiffs().setUserLogin(user.getLogin()).setDiff("severity", "MAJOR", null)); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
assertThat(result.getChangelogList()).hasSize(1); | |||
assertThat(result.getChangelogList().get(0).getDiffsList().get(0).hasNewValue()).isFalse(); | |||
} | |||
@Test | |||
public void return_many_changelog() throws Exception { | |||
UserDto user = db.users().insertUser(); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.login("john").addProjectUuidPermissions(USER, issueDto.getProjectUuid()); | |||
db.issues().insertIssueChanges(issueDto, | |||
new FieldDiffs().setUserLogin(user.getLogin()).setDiff("severity", "MAJOR", "BLOCKER"), | |||
new FieldDiffs().setDiff("status", "RESOLVED", "CLOSED")); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
assertThat(result.getChangelogList()).hasSize(2); | |||
} | |||
@Test | |||
public void replace_technical_debt_key_by_effort() throws Exception { | |||
UserDto user = db.users().insertUser(); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.login("john").addProjectUuidPermissions(USER, issueDto.getProjectUuid()); | |||
db.issues().insertIssueChanges(issueDto, new FieldDiffs().setUserLogin(user.getLogin()).setDiff("technicalDebt", "10", "20")); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
assertThat(result.getChangelogList()).hasSize(1); | |||
assertThat(result.getChangelogList().get(0).getDiffsList()).extracting(Diff::getKey, Diff::getOldValue, Diff::getNewValue).containsOnly(tuple("effort", "10", "20")); | |||
} | |||
@Test | |||
public void return_empty_changelog_when_no_changes_on_issue() { | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.login("john").addProjectUuidPermissions(USER, issueDto.getProjectUuid()); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
assertThat(result.getChangelogList()).isEmpty(); | |||
} | |||
@Test | |||
public void fail_when_not_enough_permission() { | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.login("john").addProjectUuidPermissions(CODEVIEWER, issueDto.getProjectUuid()); | |||
expectedException.expect(ForbiddenException.class); | |||
call(issueDto.getKey()); | |||
} | |||
@Test | |||
public void test_example() throws Exception { | |||
UserDto user = db.users().insertUser(newUserDto("john.smith", "John Smith", "john@smith.com")); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.login("john").addProjectUuidPermissions(USER, issueDto.getProjectUuid()); | |||
db.issues().insertIssueChanges(issueDto, new FieldDiffs() | |||
.setUserLogin(user.getLogin()) | |||
.setDiff("severity", "MAJOR", "BLOCKER") | |||
.setCreationDate(DateUtils.parseDateTime("2014-03-04T23:03:44+0100"))); | |||
String result = tester.newRequest().setParam("issue", issueDto.getKey()).execute().getInput(); | |||
assertJson(result).isSimilarTo(getClass().getResource("example-changelog.json")); | |||
} | |||
@Test | |||
public void test_definition() { | |||
WebService.Action action = tester.getDef(); | |||
assertThat(action.key()).isEqualTo("changelog"); | |||
assertThat(action.isPost()).isFalse(); | |||
assertThat(action.isInternal()).isFalse(); | |||
assertThat(action.params()).hasSize(1); | |||
assertThat(action.responseExample()).isNotNull(); | |||
} | |||
private ChangelogWsResponse call(@Nullable String issueKey) { | |||
TestRequest request = tester.newRequest() | |||
.setMediaType(MediaTypes.PROTOBUF); | |||
setNullable(issueKey, e -> request.setParam("issue", e)); | |||
try { | |||
return ChangelogWsResponse.parseFrom(request.execute().getInputStream()); | |||
} catch (IOException e) { | |||
throw Throwables.propagate(e); | |||
} | |||
} | |||
private IssueDto newIssue() { | |||
RuleDto rule = db.rules().insertRule(newRuleDto()); | |||
ComponentDto project = db.components().insertProject(); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project)); | |||
return newDto(rule, file, project); | |||
} | |||
} |
@@ -32,6 +32,7 @@ import org.sonar.db.DbClient; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDbTester; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.issue.IssueDbTester; | |||
import org.sonar.db.issue.IssueDto; | |||
import org.sonar.db.rule.RuleDbTester; | |||
import org.sonar.db.rule.RuleDto; | |||
@@ -39,7 +40,6 @@ import org.sonar.server.es.EsTester; | |||
import org.sonar.server.exceptions.ForbiddenException; | |||
import org.sonar.server.exceptions.NotFoundException; | |||
import org.sonar.server.exceptions.UnauthorizedException; | |||
import org.sonar.server.issue.IssueDbTester; | |||
import org.sonar.server.issue.IssueFieldsSetter; | |||
import org.sonar.server.issue.IssueFinder; | |||
import org.sonar.server.issue.IssueUpdater; |
@@ -25,11 +25,12 @@ import java.util.List; | |||
import javax.annotation.CheckForNull; | |||
import org.sonar.core.issue.DefaultIssueComment; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.core.util.stream.Collectors; | |||
import org.sonar.db.Dao; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.MyBatis; | |||
import static java.util.Arrays.asList; | |||
import static java.util.Collections.singletonList; | |||
import static org.sonar.db.DatabaseUtils.executeLargeInputs; | |||
public class IssueChangeDao implements Dao { | |||
@@ -48,17 +49,11 @@ public class IssueChangeDao implements Dao { | |||
return comments; | |||
} | |||
public List<FieldDiffs> selectChangelogByIssue(String issueKey) { | |||
DbSession session = mybatis.openSession(false); | |||
try { | |||
List<FieldDiffs> result = Lists.newArrayList(); | |||
for (IssueChangeDto dto : selectByTypeAndIssueKeys(session, asList(issueKey), IssueChangeDto.TYPE_FIELD_CHANGE)) { | |||
result.add(dto.toFieldDiffs()); | |||
} | |||
return result; | |||
} finally { | |||
MyBatis.closeQuietly(session); | |||
} | |||
public List<FieldDiffs> selectChangelogByIssue(DbSession session, String issueKey) { | |||
return selectByTypeAndIssueKeys(session, singletonList(issueKey), IssueChangeDto.TYPE_FIELD_CHANGE) | |||
.stream() | |||
.map(IssueChangeDto::toFieldDiffs) | |||
.collect(Collectors.toList()); | |||
} | |||
public List<IssueChangeDto> selectChangelogOfNonClosedIssuesByComponent(String componentUuid) { |
@@ -65,11 +65,13 @@ import org.sonar.api.utils.log.Loggers; | |||
import org.sonar.core.util.SequenceUuidFactory; | |||
import org.sonar.db.component.ComponentDbTester; | |||
import org.sonar.db.event.EventDbTester; | |||
import org.sonar.db.issue.IssueDbTester; | |||
import org.sonar.db.organization.OrganizationDbTester; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.organization.OrganizationTesting; | |||
import org.sonar.db.permission.template.PermissionTemplateDbTester; | |||
import org.sonar.db.qualitygate.QualityGateDbTester; | |||
import org.sonar.db.rule.RuleDbTester; | |||
import org.sonar.db.user.RootFlagAssertions; | |||
import org.sonar.db.user.UserDbTester; | |||
@@ -104,6 +106,8 @@ public class DbTester extends ExternalResource { | |||
private final OrganizationDbTester organizationTester; | |||
private final PermissionTemplateDbTester permissionTemplateTester; | |||
private final QualityGateDbTester qualityGateDbTester; | |||
private final IssueDbTester issueDbTester; | |||
private final RuleDbTester ruleDbTester; | |||
private final RootFlagAssertions rootFlagAssertions; | |||
private DbTester(System2 system2, @Nullable String schemaPath) { | |||
@@ -116,6 +120,8 @@ public class DbTester extends ExternalResource { | |||
this.organizationTester = new OrganizationDbTester(this); | |||
this.permissionTemplateTester = new PermissionTemplateDbTester(this); | |||
this.qualityGateDbTester = new QualityGateDbTester(this); | |||
this.issueDbTester = new IssueDbTester(this); | |||
this.ruleDbTester = new RuleDbTester(this); | |||
this.rootFlagAssertions = new RootFlagAssertions(this); | |||
} | |||
@@ -204,6 +210,14 @@ public class DbTester extends ExternalResource { | |||
return rootFlagAssertions; | |||
} | |||
public IssueDbTester issues() { | |||
return issueDbTester; | |||
} | |||
public RuleDbTester rules() { | |||
return ruleDbTester; | |||
} | |||
@Override | |||
protected void after() { | |||
if (session != null) { |
@@ -39,15 +39,15 @@ import static org.mockito.Mockito.mock; | |||
public class IssueChangeDaoTest { | |||
@Rule | |||
public DbTester dbTester = DbTester.create(System2.INSTANCE); | |||
public DbTester db = DbTester.create(System2.INSTANCE); | |||
IssueChangeDao dao = dbTester.getDbClient().issueChangeDao(); | |||
private IssueChangeDao underTest = db.getDbClient().issueChangeDao(); | |||
@Test | |||
public void select_comments_by_issues() { | |||
dbTester.prepareDbUnit(getClass(), "shared.xml"); | |||
db.prepareDbUnit(getClass(), "shared.xml"); | |||
List<DefaultIssueComment> comments = dao.selectCommentsByIssues(dbTester.getSession(), Arrays.asList("1000")); | |||
List<DefaultIssueComment> comments = underTest.selectCommentsByIssues(db.getSession(), Arrays.asList("1000")); | |||
assertThat(comments).hasSize(2); | |||
// chronological order | |||
@@ -62,13 +62,13 @@ public class IssueChangeDaoTest { | |||
@Test | |||
public void select_comments_by_issues_on_huge_number_of_issues() { | |||
dbTester.prepareDbUnit(getClass(), "shared.xml"); | |||
db.prepareDbUnit(getClass(), "shared.xml"); | |||
List<String> hugeNbOfIssues = newArrayList(); | |||
for (int i = 0; i < 4500; i++) { | |||
hugeNbOfIssues.add("ABCD" + i); | |||
} | |||
List<DefaultIssueComment> comments = dao.selectCommentsByIssues(dbTester.getSession(), hugeNbOfIssues); | |||
List<DefaultIssueComment> comments = underTest.selectCommentsByIssues(db.getSession(), hugeNbOfIssues); | |||
// The goal of this test is only to check that the query do no fail, not to check the number of results | |||
assertThat(comments).isEmpty(); | |||
@@ -76,22 +76,22 @@ public class IssueChangeDaoTest { | |||
@Test | |||
public void select_comment_by_key() { | |||
dbTester.prepareDbUnit(getClass(), "shared.xml"); | |||
db.prepareDbUnit(getClass(), "shared.xml"); | |||
DefaultIssueComment comment = dao.selectCommentByKey("FGHIJ"); | |||
DefaultIssueComment comment = underTest.selectCommentByKey("FGHIJ"); | |||
assertThat(comment).isNotNull(); | |||
assertThat(comment.key()).isEqualTo("FGHIJ"); | |||
assertThat(comment.key()).isEqualTo("FGHIJ"); | |||
assertThat(comment.userLogin()).isEqualTo("arthur"); | |||
assertThat(dao.selectCommentByKey("UNKNOWN")).isNull(); | |||
assertThat(underTest.selectCommentByKey("UNKNOWN")).isNull(); | |||
} | |||
@Test | |||
public void select_issue_changelog_from_issue_key() { | |||
dbTester.prepareDbUnit(getClass(), "shared.xml"); | |||
db.prepareDbUnit(getClass(), "shared.xml"); | |||
List<FieldDiffs> changelog = dao.selectChangelogByIssue("1000"); | |||
List<FieldDiffs> changelog = underTest.selectChangelogByIssue(db.getSession(), "1000"); | |||
assertThat(changelog).hasSize(1); | |||
assertThat(changelog.get(0).diffs()).hasSize(1); | |||
assertThat(changelog.get(0).diffs().get("severity").newValue()).isEqualTo("BLOCKER"); | |||
@@ -100,9 +100,9 @@ public class IssueChangeDaoTest { | |||
@Test | |||
public void selectChangelogOfNonClosedIssuesByComponent() { | |||
dbTester.prepareDbUnit(getClass(), "selectChangelogOfNonClosedIssuesByComponent.xml"); | |||
db.prepareDbUnit(getClass(), "selectChangelogOfNonClosedIssuesByComponent.xml"); | |||
List<IssueChangeDto> dtos = dao.selectChangelogOfNonClosedIssuesByComponent("FILE_1"); | |||
List<IssueChangeDto> dtos = underTest.selectChangelogOfNonClosedIssuesByComponent("FILE_1"); | |||
// no need to have ordered results (see NewDebtCalculator) | |||
assertThat(dtos).extracting("id").containsOnly(100L, 103L); | |||
} | |||
@@ -111,25 +111,25 @@ public class IssueChangeDaoTest { | |||
public void select_comments_by_issues_empty_input() { | |||
// no need to connect to db | |||
DbSession session = mock(DbSession.class); | |||
List<DefaultIssueComment> comments = dao.selectCommentsByIssues(session, Collections.<String>emptyList()); | |||
List<DefaultIssueComment> comments = underTest.selectCommentsByIssues(session, Collections.<String>emptyList()); | |||
assertThat(comments).isEmpty(); | |||
} | |||
@Test | |||
public void delete() { | |||
dbTester.prepareDbUnit(getClass(), "delete.xml"); | |||
db.prepareDbUnit(getClass(), "delete.xml"); | |||
assertThat(dao.delete("COMMENT-2")).isTrue(); | |||
assertThat(underTest.delete("COMMENT-2")).isTrue(); | |||
dbTester.assertDbUnit(getClass(), "delete-result.xml", "issue_changes"); | |||
db.assertDbUnit(getClass(), "delete-result.xml", "issue_changes"); | |||
} | |||
@Test | |||
public void delete_unknown_key() { | |||
dbTester.prepareDbUnit(getClass(), "delete.xml"); | |||
db.prepareDbUnit(getClass(), "delete.xml"); | |||
assertThat(dao.delete("UNKNOWN")).isFalse(); | |||
assertThat(underTest.delete("UNKNOWN")).isFalse(); | |||
} | |||
@Test | |||
@@ -144,15 +144,15 @@ public class IssueChangeDaoTest { | |||
.setUpdatedAt(1_501_000_000_000L) | |||
.setIssueChangeCreationDate(1_502_000_000_000L); | |||
dao.insert(dbTester.getSession(), changeDto); | |||
dbTester.getSession().commit(); | |||
underTest.insert(db.getSession(), changeDto); | |||
db.getSession().commit(); | |||
dbTester.assertDbUnit(getClass(), "insert-result.xml", new String[]{"id"}, "issue_changes"); | |||
db.assertDbUnit(getClass(), "insert-result.xml", new String[]{"id"}, "issue_changes"); | |||
} | |||
@Test | |||
public void update() { | |||
dbTester.prepareDbUnit(getClass(), "update.xml"); | |||
db.prepareDbUnit(getClass(), "update.xml"); | |||
IssueChangeDto change = new IssueChangeDto(); | |||
change.setKey("COMMENT-2"); | |||
@@ -161,14 +161,14 @@ public class IssueChangeDaoTest { | |||
change.setChangeData("new comment"); | |||
change.setUpdatedAt(1_500_000_000_000L); | |||
assertThat(dao.update(change)).isTrue(); | |||
assertThat(underTest.update(change)).isTrue(); | |||
dbTester.assertDbUnit(getClass(), "update-result.xml", "issue_changes"); | |||
db.assertDbUnit(getClass(), "update-result.xml", "issue_changes"); | |||
} | |||
@Test | |||
public void update_unknown_key() { | |||
dbTester.prepareDbUnit(getClass(), "update.xml"); | |||
db.prepareDbUnit(getClass(), "update.xml"); | |||
IssueChangeDto change = new IssueChangeDto(); | |||
change.setKey("UNKNOWN"); | |||
@@ -177,6 +177,6 @@ public class IssueChangeDaoTest { | |||
change.setChangeData("new comment"); | |||
change.setUpdatedAt(DateUtils.parseDate("2013-06-30").getTime()); | |||
assertThat(dao.update(change)).isFalse(); | |||
assertThat(underTest.update(change)).isFalse(); | |||
} | |||
} |
@@ -18,10 +18,11 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.server.issue; | |||
package org.sonar.db.issue; | |||
import java.util.Arrays; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.issue.IssueDto; | |||
public class IssueDbTester { | |||
@@ -36,4 +37,9 @@ public class IssueDbTester { | |||
db.commit(); | |||
return issueDto; | |||
} | |||
public void insertIssueChanges(IssueDto issueDto, FieldDiffs... diffs) { | |||
Arrays.stream(diffs).forEach(diff -> db.getDbClient().issueChangeDao().insert(db.getSession(), IssueChangeDto.of(issueDto.getKey(), diff))); | |||
db.commit(); | |||
} | |||
} |
@@ -19,6 +19,8 @@ | |||
*/ | |||
package org.sonar.db.user; | |||
import javax.annotation.Nullable; | |||
import static java.util.Collections.singletonList; | |||
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; | |||
import static org.apache.commons.lang.math.RandomUtils.nextLong; | |||
@@ -29,7 +31,7 @@ public class UserTesting { | |||
return newUserDto(randomAlphanumeric(30), randomAlphanumeric(30), randomAlphanumeric(30)); | |||
} | |||
public static UserDto newUserDto(String login, String name, String email) { | |||
public static UserDto newUserDto(String login, String name, @Nullable String email) { | |||
return new UserDto() | |||
.setActive(true) | |||
.setLocal(true) | |||
@@ -45,7 +47,7 @@ public class UserTesting { | |||
.setUpdatedAt(nextLong()); | |||
} | |||
public static UserDto newLocalUser(String login, String name, String email) { | |||
public static UserDto newLocalUser(String login, String name, @Nullable String email) { | |||
return new UserDto() | |||
.setActive(true) | |||
.setLocal(true) | |||
@@ -61,7 +63,7 @@ public class UserTesting { | |||
.setUpdatedAt(nextLong()); | |||
} | |||
public static UserDto newExternalUser(String login, String name, String email) { | |||
public static UserDto newExternalUser(String login, String name, @Nullable String email) { | |||
return new UserDto() | |||
.setActive(true) | |||
.setLocal(false) |
@@ -29,6 +29,8 @@ import java.util.List; | |||
*/ | |||
public class IssuesWsParameters { | |||
public static final String PARAM_ISSUE = "issue"; | |||
public static final String ISSUES = "issues"; | |||
public static final String SEVERITIES = "severities"; | |||
public static final String STATUSES = "statuses"; |
@@ -181,3 +181,24 @@ message Component { | |||
optional int64 projectId = 9; | |||
optional int64 subProjectId = 10; | |||
} | |||
// Response of GET api/issues/changelog | |||
message ChangelogWsResponse { | |||
repeated Changelog changelog = 1; | |||
message Changelog { | |||
optional string user = 1; | |||
optional string userName = 2; | |||
optional string email = 3; | |||
optional string creationDate = 4; | |||
repeated Diff diffs = 5; | |||
message Diff { | |||
optional string key = 1; | |||
optional string newValue = 2; | |||
optional string oldValue = 3; | |||
} | |||
} | |||
} | |||