Kaynağa Gözat

SONAR-12719 add changelog to response of WS api/hotspots/show

tags/8.2.0.32929
Sébastien Lesaint 4 yıl önce
ebeveyn
işleme
333daf6a6f

+ 18
- 1
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ShowAction.java Dosyayı Görüntüle

@@ -19,7 +19,9 @@
*/
package org.sonar.server.hotspot.ws;

import com.google.common.collect.ImmutableSet;
import java.util.Objects;
import java.util.Set;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.Request;
@@ -33,6 +35,8 @@ import org.sonar.db.component.ComponentDto;
import org.sonar.db.issue.IssueDto;
import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.issue.IssueChangelog;
import org.sonar.server.issue.IssueChangelog.ChangelogLoadingContext;
import org.sonar.server.issue.TextRangeResponseFormatter;
import org.sonar.server.security.SecurityStandards;
import org.sonar.server.user.UserSession;
@@ -54,12 +58,15 @@ public class ShowAction implements HotspotsWsAction {
private final UserSession userSession;
private final HotspotWsResponseFormatter responseFormatter;
private final TextRangeResponseFormatter textRangeFormatter;
private final IssueChangelog issueChangelog;

public ShowAction(DbClient dbClient, UserSession userSession, HotspotWsResponseFormatter responseFormatter, TextRangeResponseFormatter textRangeFormatter) {
public ShowAction(DbClient dbClient, UserSession userSession, HotspotWsResponseFormatter responseFormatter,
TextRangeResponseFormatter textRangeFormatter, IssueChangelog issueChangelog) {
this.dbClient = dbClient;
this.userSession = userSession;
this.responseFormatter = responseFormatter;
this.textRangeFormatter = textRangeFormatter;
this.issueChangelog = issueChangelog;
}

@Override
@@ -95,6 +102,7 @@ public class ShowAction implements HotspotsWsAction {
formatComponents(components, responseBuilder);
formatRule(responseBuilder, rule);
formatTextRange(hotspot, responseBuilder);
formatChangelog(dbSession, hotspot, components, responseBuilder);

writeProtobuf(responseBuilder.build(), request, response);
}
@@ -140,6 +148,15 @@ public class ShowAction implements HotspotsWsAction {
textRangeFormatter.formatTextRange(hotspot, responseBuilder::setTextRange);
}

private void formatChangelog(DbSession dbSession, IssueDto hotspot, Components components, ShowWsResponse.Builder responseBuilder) {
Set<ComponentDto> preloadedComponents = ImmutableSet.of(components.project, components.component);
ChangelogLoadingContext changelogLoadingContext = issueChangelog
.newChangelogLoadingContext(dbSession, hotspot, ImmutableSet.of(), preloadedComponents);

issueChangelog.formatChangelog(dbSession, changelogLoadingContext)
.forEach(responseBuilder::addChangelog);
}

private RuleDefinitionDto loadRule(DbSession dbSession, IssueDto hotspot) {
RuleKey ruleKey = hotspot.getRuleKey();
return dbClient.ruleDao().selectDefinitionByKey(dbSession, ruleKey)

+ 237
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/IssueChangelog.java Dosyayı Görüntüle

@@ -0,0 +1,237 @@
/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info 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;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.issue.IssueDto;
import org.sonar.db.user.UserDto;
import org.sonarqube.ws.Common;

import static com.google.common.base.Strings.emptyToNull;
import static java.util.Collections.emptyMap;
import static java.util.Optional.ofNullable;
import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.core.util.stream.MoreCollectors.toSet;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
import static org.sonar.server.issue.IssueFieldsSetter.FILE;
import static org.sonar.server.issue.IssueFieldsSetter.TECHNICAL_DEBT;

public class IssueChangelog {
private static final String EFFORT_CHANGELOG_KEY = "effort";

private final DbClient dbClient;
private final AvatarResolver avatarFactory;

public IssueChangelog(DbClient dbClient, AvatarResolver avatarFactory) {
this.dbClient = dbClient;
this.avatarFactory = avatarFactory;
}

public ChangelogLoadingContext newChangelogLoadingContext(DbSession dbSession, IssueDto dto) {
return newChangelogLoadingContext(dbSession, dto, ImmutableSet.of(), ImmutableSet.of());
}

public ChangelogLoadingContext newChangelogLoadingContext(DbSession dbSession, IssueDto dto, Set<UserDto> preloadedUsers, Set<ComponentDto> preloadedComponents) {
List<FieldDiffs> changes = dbClient.issueChangeDao().selectChangelogByIssue(dbSession, dto.getKey());
return new ChangelogLoadingContextImpl(changes, preloadedUsers, preloadedComponents);
}

public Stream<Common.Changelog> formatChangelog(DbSession dbSession, ChangelogLoadingContext loadingContext) {
Map<String, UserDto> usersByUuid = loadUsers(dbSession, loadingContext);
Map<String, ComponentDto> filesByUuid = loadFiles(dbSession, loadingContext);
FormatableChangeLog changeLogResults = new FormatableChangeLog(loadingContext.getChanges(), usersByUuid, filesByUuid);

return changeLogResults.changes.stream()
.map(toWsChangelog(changeLogResults));
}

private Map<String, UserDto> loadUsers(DbSession dbSession, ChangelogLoadingContext loadingContext) {
List<FieldDiffs> changes = loadingContext.getChanges();
if (changes.isEmpty()) {
return emptyMap();
}

Set<UserDto> usersByUuid = loadingContext.getPreloadedUsers();

Set<String> userUuids = changes.stream()
.filter(change -> change.userUuid() != null)
.map(FieldDiffs::userUuid)
.collect(toSet());
if (userUuids.isEmpty()) {
return emptyMap();
}

Set<String> missingUsersUuids = Sets.difference(userUuids, usersByUuid).immutableCopy();
if (missingUsersUuids.isEmpty()) {
return usersByUuid.stream()
.filter(t -> userUuids.contains(t.getUuid()))
.collect(uniqueIndex(UserDto::getUuid, userUuids.size()));
}

return Stream.concat(
usersByUuid.stream(),
dbClient.userDao().selectByUuids(dbSession, missingUsersUuids).stream())
.filter(t -> userUuids.contains(t.getUuid()))
.collect(uniqueIndex(UserDto::getUuid, userUuids.size()));
}

private Map<String, ComponentDto> loadFiles(DbSession dbSession, ChangelogLoadingContext loadingContext) {
List<FieldDiffs> changes = loadingContext.getChanges();
if (changes.isEmpty()) {
return emptyMap();
}

Set<String> fileUuids = changes.stream()
.filter(diffs -> diffs.diffs().containsKey(FILE))
.flatMap(diffs -> Stream.of(diffs.get(FILE).newValue().toString(), diffs.get(FILE).oldValue().toString()))
.collect(toSet());
if (fileUuids.isEmpty()) {
return emptyMap();
}

Set<ComponentDto> preloadedComponents = loadingContext.getPreloadedComponents();
Set<String> preloadedComponentUuids = preloadedComponents.stream()
.map(ComponentDto::uuid)
.collect(toSet(preloadedComponents.size()));
Set<String> missingFileUuids = Sets.difference(fileUuids, preloadedComponentUuids).immutableCopy();
if (missingFileUuids.isEmpty()) {
return preloadedComponents.stream()
.filter(t -> fileUuids.contains(t.uuid()))
.collect(uniqueIndex(ComponentDto::uuid, fileUuids.size()));
}

return Stream.concat(
preloadedComponents.stream(),
dbClient.componentDao().selectByUuids(dbSession, missingFileUuids).stream())
.filter(t -> fileUuids.contains(t.uuid()))
.collect(uniqueIndex(ComponentDto::uuid, fileUuids.size()));
}

public interface ChangelogLoadingContext {
List<FieldDiffs> getChanges();

Set<UserDto> getPreloadedUsers();

Set<ComponentDto> getPreloadedComponents();
}

@Immutable
public static final class ChangelogLoadingContextImpl implements ChangelogLoadingContext {
private final List<FieldDiffs> changes;
private final Set<UserDto> preloadedUsers;
private final Set<ComponentDto> preloadedComponents;

private ChangelogLoadingContextImpl(List<FieldDiffs> changes, Set<UserDto> preloadedUsers, Set<ComponentDto> preloadedComponents) {
this.changes = ImmutableList.copyOf(changes);
this.preloadedUsers = ImmutableSet.copyOf(preloadedUsers);
this.preloadedComponents = ImmutableSet.copyOf(preloadedComponents);
}

@Override
public List<FieldDiffs> getChanges() {
return changes;
}

@Override
public Set<UserDto> getPreloadedUsers() {
return preloadedUsers;
}

@Override
public Set<ComponentDto> getPreloadedComponents() {
return preloadedComponents;
}
}

private Function<FieldDiffs, Common.Changelog> toWsChangelog(FormatableChangeLog results) {
return change -> {
String userUUuid = change.userUuid();
Common.Changelog.Builder changelogBuilder = Common.Changelog.newBuilder();
changelogBuilder.setCreationDate(formatDateTime(change.creationDate()));
UserDto user = userUUuid == null ? null : results.users.get(userUUuid);
if (user != null) {
changelogBuilder.setUser(user.getLogin());
changelogBuilder.setIsUserActive(user.isActive());
ofNullable(user.getName()).ifPresent(changelogBuilder::setUserName);
ofNullable(emptyToNull(user.getEmail())).ifPresent(email -> changelogBuilder.setAvatar(avatarFactory.create(user)));
}
change.diffs().entrySet().stream()
.map(toWsDiff(results))
.forEach(changelogBuilder::addDiffs);
return changelogBuilder.build();
};
}

private static Function<Map.Entry<String, FieldDiffs.Diff>, Common.Changelog.Diff> toWsDiff(FormatableChangeLog results) {
return diff -> {
FieldDiffs.Diff value = diff.getValue();
Common.Changelog.Diff.Builder diffBuilder = Common.Changelog.Diff.newBuilder();
String key = diff.getKey();
String oldValue = value.oldValue() != null ? value.oldValue().toString() : null;
String newValue = value.newValue() != null ? value.newValue().toString() : null;
if (key.equals(FILE)) {
diffBuilder.setKey(key);
ofNullable(results.getFileLongName(emptyToNull(newValue))).ifPresent(diffBuilder::setNewValue);
ofNullable(results.getFileLongName(emptyToNull(oldValue))).ifPresent(diffBuilder::setOldValue);
} else {
diffBuilder.setKey(key.equals(TECHNICAL_DEBT) ? EFFORT_CHANGELOG_KEY : key);
ofNullable(emptyToNull(newValue)).ifPresent(diffBuilder::setNewValue);
ofNullable(emptyToNull(oldValue)).ifPresent(diffBuilder::setOldValue);
}
return diffBuilder.build();
};
}

private static final class FormatableChangeLog {
private final List<FieldDiffs> changes;
private final Map<String, UserDto> users;
private final Map<String, ComponentDto> files;

private FormatableChangeLog(List<FieldDiffs> changes, Map<String, UserDto> users, Map<String, ComponentDto> files) {
this.changes = changes;
this.users = users;
this.files = files;
}

@CheckForNull
String getFileLongName(@Nullable String fileUuid) {
if (fileUuid == null) {
return null;
}
ComponentDto file = files.get(fileUuid);
return file == null ? null : file.longName();
}

}
}

+ 23
- 125
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/ChangelogAction.java Dosyayı Görüntüle

@@ -19,61 +19,40 @@
*/
package org.sonar.server.issue.ws;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Resources;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.server.ws.Change;
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.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.issue.IssueDto;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.user.UserDto;
import org.sonar.server.issue.AvatarResolver;
import org.sonar.server.issue.IssueChangelog;
import org.sonar.server.issue.IssueFinder;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.Common.Changelog;
import org.sonarqube.ws.Issues.ChangelogWsResponse;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.emptyToNull;
import static java.util.Optional.ofNullable;
import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
import static org.sonar.server.issue.IssueFieldsSetter.FILE;
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.ACTION_CHANGELOG;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE;

public class ChangelogAction implements IssuesWsAction {

private static final String EFFORT_CHANGELOG_KEY = "effort";

private final DbClient dbClient;
private final IssueFinder issueFinder;
private final AvatarResolver avatarFactory;
private final UserSession userSession;
private final IssueChangelog issueChangelog;

public ChangelogAction(DbClient dbClient, IssueFinder issueFinder, AvatarResolver avatarFactory, UserSession userSession) {
public ChangelogAction(DbClient dbClient, IssueFinder issueFinder, UserSession userSession, IssueChangelog issueChangelog) {
this.dbClient = dbClient;
this.issueFinder = issueFinder;
this.avatarFactory = avatarFactory;
this.userSession = userSession;
this.issueChangelog = issueChangelog;
}

@Override
@@ -95,112 +74,31 @@ public class ChangelogAction implements IssuesWsAction {
@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(MoreCollectors.toOneElement());
writeProtobuf(wsResponse, request, response);
}
}

private Function<Request, ChangeLogResults> searchChangelog(DbSession dbSession) {
return request -> new ChangeLogResults(dbSession, request.mandatoryParam(PARAM_ISSUE));
}

private Function<ChangeLogResults, ChangelogWsResponse> buildResponse() {
return result -> Stream.of(ChangelogWsResponse.newBuilder())
.peek(addChanges(result))
.map(ChangelogWsResponse.Builder::build)
.collect(MoreCollectors.toOneElement());
}

private Consumer<ChangelogWsResponse.Builder> addChanges(ChangeLogResults results) {
return response -> results.changes.stream()
.map(toWsChangelog(results))
.forEach(response::addChangelog);
}
IssueDto issue = issueFinder.getByKey(dbSession, request.mandatoryParam(PARAM_ISSUE));

private Function<FieldDiffs, Changelog> toWsChangelog(ChangeLogResults results) {
return change -> {
String userUUuid = change.userUuid();
Changelog.Builder changelogBuilder = Changelog.newBuilder();
changelogBuilder.setCreationDate(formatDateTime(change.creationDate()));
UserDto user = userUUuid == null ? null : results.users.get(userUUuid);
if (user != null) {
changelogBuilder.setUser(user.getLogin());
changelogBuilder.setIsUserActive(user.isActive());
ofNullable(user.getName()).ifPresent(changelogBuilder::setUserName);
ofNullable(emptyToNull(user.getEmail())).ifPresent(email -> changelogBuilder.setAvatar(avatarFactory.create(user)));
}
change.diffs().entrySet().stream()
.map(toWsDiff(results))
.forEach(changelogBuilder::addDiffs);
return changelogBuilder.build();
};
}

private static Function<Map.Entry<String, FieldDiffs.Diff>, Changelog.Diff> toWsDiff(ChangeLogResults results) {
return diff -> {
FieldDiffs.Diff value = diff.getValue();
Changelog.Diff.Builder diffBuilder = Changelog.Diff.newBuilder();
String key = diff.getKey();
String oldValue = value.oldValue() != null ? value.oldValue().toString() : null;
String newValue = value.newValue() != null ? value.newValue().toString() : null;
if (key.equals(FILE)) {
diffBuilder.setKey(key);
ofNullable(results.getFileLongName(emptyToNull(newValue))).ifPresent(diffBuilder::setNewValue);
ofNullable(results.getFileLongName(emptyToNull(oldValue))).ifPresent(diffBuilder::setOldValue);
} else {
diffBuilder.setKey(key.equals(TECHNICAL_DEBT) ? EFFORT_CHANGELOG_KEY : key);
ofNullable(emptyToNull(newValue)).ifPresent(diffBuilder::setNewValue);
ofNullable(emptyToNull(oldValue)).ifPresent(diffBuilder::setOldValue);
}
return diffBuilder.build();
};
}

private class ChangeLogResults {
private final List<FieldDiffs> changes;
private final Map<String, UserDto> users;
private final Map<String, ComponentDto> files;

ChangeLogResults(DbSession dbSession, String issueKey) {
IssueDto issue = issueFinder.getByKey(dbSession, issueKey);
if (isMember(dbSession, issue)) {
this.changes = dbClient.issueChangeDao().selectChangelogByIssue(dbSession, issue.getKey());
List<String> userUuids = changes.stream().filter(change -> change.userUuid() != null).map(FieldDiffs::userUuid).collect(MoreCollectors.toList());
this.users = dbClient.userDao().selectByUuids(dbSession, userUuids).stream().collect(MoreCollectors.uniqueIndex(UserDto::getUuid));
this.files = dbClient.componentDao().selectByUuids(dbSession, getFileUuids(changes)).stream().collect(MoreCollectors.uniqueIndex(ComponentDto::uuid, Function.identity()));
} else {
changes = ImmutableList.of();
users = ImmutableMap.of();
files = ImmutableMap.of();
}
ChangelogWsResponse build = handle(dbSession, issue);
writeProtobuf(build, request, response);
}
}

private boolean isMember(DbSession dbSession, IssueDto issue) {
Optional<ComponentDto> project = dbClient.componentDao().selectByUuid(dbSession, issue.getProjectUuid());
checkState(project.isPresent(), "Cannot find the project with uuid %s from issue.id %s", issue.getProjectUuid(), issue.getId());
Optional<OrganizationDto> organization = dbClient.organizationDao().selectByUuid(dbSession, project.get().getOrganizationUuid());
checkState(organization.isPresent(), "Cannot find the organization with uuid %s from issue.id %s", project.get().getOrganizationUuid(), issue.getId());
return userSession.hasMembership(organization.get());
public ChangelogWsResponse handle(DbSession dbSession, IssueDto issue) {
if (!isMember(dbSession, issue)) {
return ChangelogWsResponse.newBuilder().build();
}

private Set<String> getFileUuids(List<FieldDiffs> changes) {
return changes.stream()
.filter(diffs -> diffs.diffs().containsKey(FILE))
.flatMap(diffs -> Stream.of(diffs.get(FILE).newValue().toString(), diffs.get(FILE).oldValue().toString()))
.collect(MoreCollectors.toSet());
}
IssueChangelog.ChangelogLoadingContext loadingContext = issueChangelog.newChangelogLoadingContext(dbSession, issue);

@CheckForNull
String getFileLongName(@Nullable String fileUuid) {
if (fileUuid == null) {
return null;
}
ComponentDto file = files.get(fileUuid);
return file == null ? null : file.longName();
}
ChangelogWsResponse.Builder builder = ChangelogWsResponse.newBuilder();
issueChangelog.formatChangelog(dbSession, loadingContext)
.forEach(builder::addChangelog);
return builder.build();
}

private boolean isMember(DbSession dbSession, IssueDto issue) {
Optional<ComponentDto> project = dbClient.componentDao().selectByUuid(dbSession, issue.getProjectUuid());
checkState(project.isPresent(), "Cannot find the project with uuid %s from issue.id %s", issue.getProjectUuid(), issue.getId());
Optional<OrganizationDto> organization = dbClient.organizationDao().selectByUuid(dbSession, project.get().getOrganizationUuid());
checkState(organization.isPresent(), "Cannot find the organization with uuid %s from issue.id %s", project.get().getOrganizationUuid(), issue.getId());
return userSession.hasMembership(organization.get());
}
}

+ 3
- 1
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java Dosyayı Görüntüle

@@ -21,9 +21,10 @@ package org.sonar.server.issue.ws;

import org.sonar.core.platform.Module;
import org.sonar.server.issue.AvatarResolverImpl;
import org.sonar.server.issue.TextRangeResponseFormatter;
import org.sonar.server.issue.IssueChangelog;
import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.IssueFinder;
import org.sonar.server.issue.TextRangeResponseFormatter;
import org.sonar.server.issue.TransitionService;
import org.sonar.server.issue.WebIssueStorage;
import org.sonar.server.issue.index.IssueQueryFactory;
@@ -45,6 +46,7 @@ public class IssueWsModule extends Module {
IssueQueryFactory.class,
IssuesWs.class,
AvatarResolverImpl.class,
IssueChangelog.class,
SearchResponseLoader.class,
TextRangeResponseFormatter.class,
SearchResponseFormat.class,

+ 58
- 1
server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/ShowActionTest.java Dosyayı Görüntüle

@@ -25,20 +25,25 @@ import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
import org.sonar.api.issue.Issue;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.System2;
import org.sonar.api.web.UserRole;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.issue.IssueDto;
@@ -51,6 +56,8 @@ import org.sonar.server.issue.TextRangeResponseFormatter;
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.issue.IssueChangelog;
import org.sonar.server.issue.IssueChangelog.ChangelogLoadingContext;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
import org.sonar.server.security.SecurityStandards;
import org.sonar.server.security.SecurityStandards.SQCategory;
@@ -63,6 +70,12 @@ import org.sonarqube.ws.Hotspots;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
import static org.sonar.db.component.ComponentTesting.newFileDto;

@@ -82,8 +95,9 @@ public class ShowActionTest {

private TextRangeResponseFormatter commonFormatter = new TextRangeResponseFormatter();
private HotspotWsResponseFormatter responseFormatter = new HotspotWsResponseFormatter(defaultOrganizationProvider);
private IssueChangelog issueChangelog = Mockito.mock(IssueChangelog.class);

private ShowAction underTest = new ShowAction(dbClient, userSessionRule, responseFormatter, commonFormatter);
private ShowAction underTest = new ShowAction(dbClient, userSessionRule, responseFormatter, commonFormatter, issueChangelog);
private WsActionTester actionTester = new WsActionTester(underTest);

@Test
@@ -353,6 +367,36 @@ public class ShowActionTest {
verifyComponent(response.getComponent(), project);
}

@Test
public void returns_hotspot_changelog() {
ComponentDto project = dbTester.components().insertPublicProject();
userSessionRule.registerComponents(project);
RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
IssueDto hotspot = dbTester.issues().insertIssue(newHotspot(project, file, rule)
.setLocations(DbIssues.Locations.newBuilder()
.setTextRange(DbCommons.TextRange.newBuilder().build())
.build()));
ChangelogLoadingContext changelogLoadingContext = Mockito.mock(ChangelogLoadingContext.class);
List<Common.Changelog> changelog = IntStream.range(0, 1 + new Random().nextInt(12))
.mapToObj(i -> Common.Changelog.newBuilder().setUser("u" + i).build())
.collect(Collectors.toList());
when(issueChangelog.newChangelogLoadingContext(any(), any(), anySet(), anySet())).thenReturn(changelogLoadingContext);
when(issueChangelog.formatChangelog(any(), eq(changelogLoadingContext)))
.thenReturn(changelog.stream());

Hotspots.ShowWsResponse response = newRequest(hotspot)
.executeProtobuf(Hotspots.ShowWsResponse.class);

assertThat(response.getChangelogList())
.extracting(Common.Changelog::getUser)
.containsExactly(changelog.stream().map(Common.Changelog::getUser).toArray(String[]::new));
verify(issueChangelog).newChangelogLoadingContext(any(DbSession.class),
argThat(new IssueDtoArgumentMatcher(hotspot)),
eq(Collections.emptySet()), eq(ImmutableSet.of(project, file)));
verify(issueChangelog).formatChangelog(any(DbSession.class), eq(changelogLoadingContext));
}

public void verifyRule(Hotspots.Rule wsRule, RuleDefinitionDto dto) {
assertThat(wsRule.getKey()).isEqualTo(dto.getKey().toString());
assertThat(wsRule.getName()).isEqualTo(dto.getName());
@@ -394,4 +438,17 @@ public class ShowActionTest {
return ruleDefinition;
}

private static class IssueDtoArgumentMatcher implements ArgumentMatcher<IssueDto> {
private final IssueDto expected;

private IssueDtoArgumentMatcher(IssueDto expected) {
this.expected = expected;
}

@Override
public boolean matches(IssueDto argument) {
return argument != null && argument.getKey().equals(expected.getKey());
}
}

}

+ 5
- 1
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/ChangelogActionTest.java Dosyayı Görüntüle

@@ -38,6 +38,7 @@ import org.sonar.db.user.UserDto;
import org.sonar.db.user.UserTesting;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.issue.AvatarResolverImpl;
import org.sonar.server.issue.IssueChangelog;
import org.sonar.server.issue.IssueFinder;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
@@ -69,7 +70,10 @@ public class ChangelogActionTest {

private ComponentDto project;
private ComponentDto file;
private WsActionTester tester = new WsActionTester(new ChangelogAction(db.getDbClient(), new IssueFinder(db.getDbClient(), userSession), new AvatarResolverImpl(), userSession));
private IssueFinder issueFinder = new IssueFinder(db.getDbClient(), userSession);
private IssueChangelog issueChangelog = new IssueChangelog(db.getDbClient(), new AvatarResolverImpl());
private ChangelogAction underTest = new ChangelogAction(db.getDbClient(), issueFinder, userSession, issueChangelog);
private WsActionTester tester = new WsActionTester(underTest);

@Before
public void setUp() {

+ 1
- 1
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/IssueWsModuleTest.java Dosyayı Görüntüle

@@ -30,7 +30,7 @@ public class IssueWsModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new IssueWsModule().configure(container);
assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 29);
assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 30);
}
}


+ 1
- 0
sonar-ws/src/main/protobuf/ws-hotspots.proto Dosyayı Görüntüle

@@ -66,6 +66,7 @@ message ShowWsResponse {
optional string creationDate = 11;
optional string updateDate = 12;
optional sonarqube.ws.commons.TextRange textRange = 13;
repeated sonarqube.ws.commons.Changelog changelog = 14;
}

message Component {

Loading…
İptal
Kaydet