Browse Source

SONAR-7290 Rewrite api/issues/changelog in Java

tags/6.3-RC1
Julien Lancelot 7 years ago
parent
commit
1593d587b7

+ 147
- 0
server/sonar-server/src/main/java/org/sonar/server/issue/ws/ChangelogAction.java View File

@@ -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));
}
}
}

+ 2
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java View File

@@ -65,6 +65,7 @@ public class IssueWsModule extends Module {
SetTagsAction.class,
SetTypeAction.class,
ComponentTagsAction.class,
AuthorsAction.class);
AuthorsAction.class,
ChangelogAction.class);
}
}

+ 0
- 17
server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssuesWs.java View File

@@ -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")

+ 4
- 3
server/sonar-server/src/main/resources/org/sonar/server/issue/ws/example-changelog.json View File

@@ -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"
}
]
}
]

}

+ 1
- 0
server/sonar-server/src/test/java/org/sonar/server/issue/IssueFinderTest.java View File

@@ -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;

+ 1
- 0
server/sonar-server/src/test/java/org/sonar/server/issue/IssueUpdaterTest.java View File

@@ -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;

+ 263
- 0
server/sonar-server/src/test/java/org/sonar/server/issue/ws/ChangelogActionTest.java View File

@@ -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);
}

}

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/issue/ws/DoTransitionActionTest.java View File

@@ -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;

+ 7
- 12
sonar-db/src/main/java/org/sonar/db/issue/IssueChangeDao.java View File

@@ -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) {

+ 14
- 0
sonar-db/src/test/java/org/sonar/db/DbTester.java View File

@@ -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) {

+ 27
- 27
sonar-db/src/test/java/org/sonar/db/issue/IssueChangeDaoTest.java View File

@@ -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();
}
}

server/sonar-server/src/test/java/org/sonar/server/issue/IssueDbTester.java → sonar-db/src/test/java/org/sonar/db/issue/IssueDbTester.java View File

@@ -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();
}
}

+ 5
- 3
sonar-db/src/test/java/org/sonar/db/user/UserTesting.java View File

@@ -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)

+ 2
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java View File

@@ -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";

+ 21
- 0
sonar-ws/src/main/protobuf/ws-issues.proto View File

@@ -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;
}
}
}



Loading…
Cancel
Save