From ae791695c0432f6e7d916f081af25ce67d90679d Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Tue, 18 Jun 2019 14:25:46 +0200 Subject: [PATCH] SONAR-12075 return scmAuthor in api/sources/issue_snippets --- .../server/source/ws/IssueSnippetsAction.java | 83 ++++++++++--------- .../source/ws/IssueSnippetsActionTest.java | 57 +++++++++++-- .../issue_snippets_multiple_locations.json | 51 ++++++++---- 3 files changed, 127 insertions(+), 64 deletions(-) diff --git a/server/sonar-server/src/main/java/org/sonar/server/source/ws/IssueSnippetsAction.java b/server/sonar-server/src/main/java/org/sonar/server/source/ws/IssueSnippetsAction.java index 1f0698937a0..80b5114dba8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/source/ws/IssueSnippetsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/source/ws/IssueSnippetsAction.java @@ -20,7 +20,6 @@ package org.sonar.server.source.ws; import com.google.common.io.Resources; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -33,30 +32,35 @@ import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.text.JsonWriter; +import org.sonar.api.web.UserRole; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.SnapshotDto; import org.sonar.db.issue.IssueDto; +import org.sonar.db.organization.OrganizationDto; import org.sonar.db.protobuf.DbCommons; import org.sonar.db.protobuf.DbFileSources; import org.sonar.db.protobuf.DbIssues; import org.sonar.server.component.ws.ComponentViewerJsonWriter; -import org.sonar.server.issue.IssueFinder; +import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.source.SourceService; +import org.sonar.server.user.UserSession; + +import static java.lang.String.format; public class IssueSnippetsAction implements SourcesWsAction { - private final IssueFinder issueFinder; - private final LinesJsonWriter linesJsonWriter; - private final ComponentViewerJsonWriter componentViewerJsonWriter; - private final SourceService sourceService; private final DbClient dbClient; + private final UserSession userSession; + private final SourceService sourceService; + private final ComponentViewerJsonWriter componentViewerJsonWriter; + private final LinesJsonWriter linesJsonWriter; - public IssueSnippetsAction(SourceService sourceService, DbClient dbClient, IssueFinder issueFinder, LinesJsonWriter linesJsonWriter, - ComponentViewerJsonWriter componentViewerJsonWriter) { + public IssueSnippetsAction(DbClient dbClient, UserSession userSession, SourceService sourceService, LinesJsonWriter linesJsonWriter, + ComponentViewerJsonWriter componentViewerJsonWriter) { this.sourceService = sourceService; this.dbClient = dbClient; - this.issueFinder = issueFinder; + this.userSession = userSession; this.linesJsonWriter = linesJsonWriter; this.componentViewerJsonWriter = componentViewerJsonWriter; } @@ -81,60 +85,61 @@ public class IssueSnippetsAction implements SourcesWsAction { public void handle(Request request, Response response) throws Exception { String issueKey = request.mandatoryParam("issueKey"); try (DbSession dbSession = dbClient.openSession(false)) { - IssueDto issueDto = issueFinder.getByKey(dbSession, issueKey); - - Map> linesPerComponent; - Map componentsByUuid; + IssueDto issueDto = dbClient.issueDao().selectByKey(dbSession, issueKey) + .orElseThrow(() -> new NotFoundException(format("Issue with key '%s' does not exist", issueKey))); + ComponentDto project = dbClient.componentDao().selectByUuid(dbSession, issueDto.getProjectUuid()) + .orElseThrow(() -> new NotFoundException(format("Project with uuid '%s' does not exist", issueDto.getProjectUuid()))); + userSession.checkComponentPermission(UserRole.USER, project); DbIssues.Locations locations = issueDto.parseLocations(); - if (locations != null && issueDto.getComponentUuid() != null) { - linesPerComponent = getLinesPerComponent(issueDto.getComponentUuid(), locations); - componentsByUuid = dbClient.componentDao().selectByUuids(dbSession, linesPerComponent.keySet()) - .stream().collect(Collectors.toMap(ComponentDto::uuid, c -> c)); + String componentUuid = issueDto.getComponentUuid(); + if (locations == null || componentUuid == null) { + response.noContent(); } else { - componentsByUuid = Collections.emptyMap(); - linesPerComponent = Collections.emptyMap(); - } - - try (JsonWriter jsonWriter = response.newJsonWriter()) { - jsonWriter.beginObject(); - - for (Map.Entry> e : linesPerComponent.entrySet()) { - ComponentDto componentDto = componentsByUuid.get(e.getKey()); - if (componentDto != null) { - processComponent(dbSession, jsonWriter, componentDto, e.getKey(), e.getValue()); + Map> linesPerComponent = getLinesPerComponent(componentUuid, locations); + Map componentsByUuid = dbClient.componentDao().selectByUuids(dbSession, linesPerComponent.keySet()) + .stream().collect(Collectors.toMap(ComponentDto::uuid, c -> c)); + try (JsonWriter jsonWriter = response.newJsonWriter()) { + jsonWriter.beginObject(); + + boolean showScmAuthors = userSession.hasMembership(new OrganizationDto().setUuid(project.getOrganizationUuid())); + for (Map.Entry> e : linesPerComponent.entrySet()) { + ComponentDto componentDto = componentsByUuid.get(e.getKey()); + if (componentDto != null) { + writeSnippet(dbSession, jsonWriter, componentDto, e.getValue(), showScmAuthors); + } } - } - jsonWriter.endObject(); + jsonWriter.endObject(); + } } } } - private void processComponent(DbSession dbSession, JsonWriter writer, ComponentDto componentDto, String fileUuid, Set lines) { - Optional> lineSourcesOpt = sourceService.getLines(dbSession, fileUuid, lines); + private void writeSnippet(DbSession dbSession, JsonWriter writer, ComponentDto fileDto, Set lines, boolean showScmAuthors) { + Optional> lineSourcesOpt = sourceService.getLines(dbSession, fileDto.uuid(), lines); if (!lineSourcesOpt.isPresent()) { return; } Supplier> periodDateSupplier = () -> dbClient.snapshotDao() - .selectLastAnalysisByComponentUuid(dbSession, componentDto.projectUuid()) + .selectLastAnalysisByComponentUuid(dbSession, fileDto.projectUuid()) .map(SnapshotDto::getPeriodDate); Iterable lineSources = lineSourcesOpt.get(); - writer.name(componentDto.getKey()).beginObject(); + writer.name(fileDto.getKey()).beginObject(); writer.name("component").beginObject(); - componentViewerJsonWriter.writeComponentWithoutFav(writer, componentDto, dbSession, false); - componentViewerJsonWriter.writeMeasures(writer, componentDto, dbSession); + componentViewerJsonWriter.writeComponentWithoutFav(writer, fileDto, dbSession, false); + componentViewerJsonWriter.writeMeasures(writer, fileDto, dbSession); writer.endObject(); - linesJsonWriter.writeSource(lineSources, writer, false, periodDateSupplier); + linesJsonWriter.writeSource(lineSources, writer, showScmAuthors, periodDateSupplier); writer.endObject(); } - private Map> getLinesPerComponent(String componentUuid, DbIssues.Locations locations) { + private static Map> getLinesPerComponent(String componentUuid, DbIssues.Locations locations) { Map> linesPerComponent = new HashMap<>(); if (locations.hasTextRange()) { @@ -155,7 +160,7 @@ public class IssueSnippetsAction implements SourcesWsAction { } private static void addTextRange(Map> linesPerComponent, String componentUuid, - DbCommons.TextRange textRange, int numLinesAfterIssue) { + DbCommons.TextRange textRange, int numLinesAfterIssue) { int start = textRange.getStartLine() - 2; int end = textRange.getEndLine() + numLinesAfterIssue; diff --git a/server/sonar-server/src/test/java/org/sonar/server/source/ws/IssueSnippetsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/source/ws/IssueSnippetsActionTest.java index 2dd813daab1..742cce0823d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/source/ws/IssueSnippetsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/source/ws/IssueSnippetsActionTest.java @@ -19,6 +19,7 @@ */ package org.sonar.server.source.ws; +import java.net.URL; import java.util.Arrays; import org.junit.Before; import org.junit.Rule; @@ -40,14 +41,15 @@ import org.sonar.db.source.FileSourceTester; import org.sonar.server.component.ws.ComponentViewerJsonWriter; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; -import org.sonar.server.issue.IssueFinder; import org.sonar.server.source.HtmlSourceDecorator; import org.sonar.server.source.SourceService; import org.sonar.server.source.index.FileSourceTesting; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.TestResponse; import org.sonar.server.ws.WsActionTester; +import org.sonar.test.JsonAssert; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -61,6 +63,8 @@ import static org.sonar.api.web.UserRole.USER; import static org.sonar.db.component.ComponentTesting.newFileDto; public class IssueSnippetsActionTest { + private static final String SCM_AUTHOR_JSON_FIELD = "scmAuthor"; + @Rule public ExpectedException expectedException = ExpectedException.none(); @Rule @@ -70,22 +74,22 @@ public class IssueSnippetsActionTest { private DbClient dbClient = db.getDbClient(); private FileSourceTester fileSourceTester = new FileSourceTester(db); + private OrganizationDto organization; private ComponentDto project; private WsActionTester actionTester; @Before public void setUp() { - OrganizationDto organization = db.organizations().insert(); + organization = db.organizations().insert(); project = db.components().insertPrivateProject(organization, "projectUuid"); HtmlSourceDecorator htmlSourceDecorator = mock(HtmlSourceDecorator.class); - when(htmlSourceDecorator.getDecoratedSourceAsHtml(anyString(), anyString(), anyString())).then((Answer) - invocationOnMock -> "

" + invocationOnMock.getArguments()[0] + "

"); + when(htmlSourceDecorator.getDecoratedSourceAsHtml(anyString(), anyString(), anyString())) + .then((Answer) invocationOnMock -> "

" + invocationOnMock.getArguments()[0] + "

"); LinesJsonWriter linesJsonWriter = new LinesJsonWriter(htmlSourceDecorator); - IssueFinder issueFinder = new IssueFinder(dbClient, userSession); ComponentViewerJsonWriter componentViewerJsonWriter = new ComponentViewerJsonWriter(dbClient); SourceService sourceService = new SourceService(dbClient, htmlSourceDecorator); - actionTester = new WsActionTester(new IssueSnippetsAction(sourceService, dbClient, issueFinder, linesJsonWriter, componentViewerJsonWriter)); + actionTester = new WsActionTester(new IssueSnippetsAction(dbClient, userSession, sourceService, linesJsonWriter, componentViewerJsonWriter)); } @Test @@ -196,7 +200,10 @@ public class IssueSnippetsActionTest { newLocation(file1.uuid(), 9, 9), newLocation(file2.uuid(), 1, 5)); TestResponse response = actionTester.newRequest().setParam("issueKey", issueKey1).execute(); - response.assertJson(getClass(), "issue_snippets_multiple_locations.json"); + JsonAssert.assertJson(response.getInput()) + .ignoreFields(SCM_AUTHOR_JSON_FIELD) + .isSimilarTo(toUrl("issue_snippets_multiple_locations.json")); + assertThat(response.getInput()).doesNotContain(SCM_AUTHOR_JSON_FIELD); } @Test @@ -213,7 +220,31 @@ public class IssueSnippetsActionTest { newLocation(file1.uuid(), 12, 12)); TestResponse response = actionTester.newRequest().setParam("issueKey", issueKey1).execute(); - response.assertJson(getClass(), "issue_snippets_close_to_each_other.json"); + JsonAssert.assertJson(response.getInput()) + .ignoreFields(SCM_AUTHOR_JSON_FIELD) + .isSimilarTo(toUrl("issue_snippets_close_to_each_other.json")); + assertThat(response.getInput()).doesNotContain(SCM_AUTHOR_JSON_FIELD); + } + + @Test + public void returns_scmAuthors_if_user_belongs_to_organization_of_project_of_issue() { + ComponentDto file1 = insertFile(project, "file1"); + ComponentDto file2 = insertFile(project, "file2"); + + DbFileSources.Data fileSources = FileSourceTesting.newFakeData(10).build(); + fileSourceTester.insertFileSource(file1, 10, dto -> dto.setSourceData(fileSources)); + fileSourceTester.insertFileSource(file2, 10, dto -> dto.setSourceData(fileSources)); + + userSession.logIn() + .addProjectPermission(USER, project, file1, file2) + .addMembership(organization); + + String issueKey1 = insertIssue(file1, newLocation(file1.uuid(), 5, 5), + newLocation(file1.uuid(), 9, 9), newLocation(file2.uuid(), 1, 5)); + + TestResponse response = actionTester.newRequest().setParam("issueKey", issueKey1).execute(); + JsonAssert.assertJson(response.getInput()) + .isSimilarTo(toUrl("issue_snippets_multiple_locations.json")); } private DbIssues.Location newLocation(String fileUuid, int startLine, int endLine) { @@ -243,4 +274,14 @@ public class IssueSnippetsActionTest { return issue.getKey(); } + private URL toUrl(String fileName) { + Class clazz = getClass(); + String path = clazz.getSimpleName() + "/" + fileName; + URL url = clazz.getResource(path); + if (url == null) { + throw new IllegalStateException("Cannot find " + path); + } + return url; + } + } diff --git a/server/sonar-server/src/test/resources/org/sonar/server/source/ws/IssueSnippetsActionTest/issue_snippets_multiple_locations.json b/server/sonar-server/src/test/resources/org/sonar/server/source/ws/IssueSnippetsActionTest/issue_snippets_multiple_locations.json index f4fe2d2bb53..d27edae9477 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/source/ws/IssueSnippetsActionTest/issue_snippets_multiple_locations.json +++ b/server/sonar-server/src/test/resources/org/sonar/server/source/ws/IssueSnippetsActionTest/issue_snippets_multiple_locations.json @@ -8,14 +8,16 @@ "longName": "null/NAME_file2", "q": "FIL", "project": "KEY_projectUuid", - "projectName": "LONG_NAME_projectUuid" + "projectName": "LONG_NAME_projectUuid", + "measures": {} }, "sources": [ { "line": 1, "code": "

SOURCE_1

", "scmRevision": "REVISION_1", - "scmDate": "1974-10-02T21:40:00-0500", + "scmAuthor": "AUTHOR_1", + "scmDate": "1974-10-03T03:40:00+0100", "utLineHits": 1, "lineHits": 1, "utConditions": 2, @@ -29,7 +31,8 @@ "line": 2, "code": "

SOURCE_2

", "scmRevision": "REVISION_2", - "scmDate": "1974-10-02T21:40:00-0500", + "scmAuthor": "AUTHOR_2", + "scmDate": "1974-10-03T03:40:00+0100", "utLineHits": 2, "lineHits": 2, "utConditions": 3, @@ -43,7 +46,8 @@ "line": 3, "code": "

SOURCE_3

", "scmRevision": "REVISION_3", - "scmDate": "1974-10-02T21:40:00-0500", + "scmAuthor": "AUTHOR_3", + "scmDate": "1974-10-03T03:40:00+0100", "utLineHits": 3, "lineHits": 3, "utConditions": 4, @@ -57,7 +61,8 @@ "line": 4, "code": "

SOURCE_4

", "scmRevision": "REVISION_4", - "scmDate": "1974-10-02T21:40:00-0500", + "scmAuthor": "AUTHOR_4", + "scmDate": "1974-10-03T03:40:00+0100", "utLineHits": 4, "lineHits": 4, "utConditions": 5, @@ -71,7 +76,8 @@ "line": 5, "code": "

SOURCE_5

", "scmRevision": "REVISION_5", - "scmDate": "1974-10-02T21:40:00-0500", + "scmAuthor": "AUTHOR_5", + "scmDate": "1974-10-03T03:40:00+0100", "utLineHits": 5, "lineHits": 5, "utConditions": 6, @@ -85,7 +91,8 @@ "line": 6, "code": "

SOURCE_6

", "scmRevision": "REVISION_6", - "scmDate": "1974-10-02T21:40:00-0500", + "scmAuthor": "AUTHOR_6", + "scmDate": "1974-10-03T03:40:00+0100", "utLineHits": 6, "lineHits": 6, "utConditions": 7, @@ -99,7 +106,8 @@ "line": 7, "code": "

SOURCE_7

", "scmRevision": "REVISION_7", - "scmDate": "1974-10-02T21:40:00-0500", + "scmAuthor": "AUTHOR_7", + "scmDate": "1974-10-03T03:40:00+0100", "utLineHits": 7, "lineHits": 7, "utConditions": 8, @@ -120,14 +128,16 @@ "longName": "null/NAME_file1", "q": "FIL", "project": "KEY_projectUuid", - "projectName": "LONG_NAME_projectUuid" + "projectName": "LONG_NAME_projectUuid", + "measures": {} }, "sources": [ { "line": 3, "code": "

SOURCE_3

", "scmRevision": "REVISION_3", - "scmDate": "1974-10-02T21:40:00-0500", + "scmAuthor": "AUTHOR_3", + "scmDate": "1974-10-03T03:40:00+0100", "utLineHits": 3, "lineHits": 3, "utConditions": 4, @@ -141,7 +151,8 @@ "line": 4, "code": "

SOURCE_4

", "scmRevision": "REVISION_4", - "scmDate": "1974-10-02T21:40:00-0500", + "scmAuthor": "AUTHOR_4", + "scmDate": "1974-10-03T03:40:00+0100", "utLineHits": 4, "lineHits": 4, "utConditions": 5, @@ -155,7 +166,8 @@ "line": 5, "code": "

SOURCE_5

", "scmRevision": "REVISION_5", - "scmDate": "1974-10-02T21:40:00-0500", + "scmAuthor": "AUTHOR_5", + "scmDate": "1974-10-03T03:40:00+0100", "utLineHits": 5, "lineHits": 5, "utConditions": 6, @@ -169,7 +181,8 @@ "line": 6, "code": "

SOURCE_6

", "scmRevision": "REVISION_6", - "scmDate": "1974-10-02T21:40:00-0500", + "scmAuthor": "AUTHOR_6", + "scmDate": "1974-10-03T03:40:00+0100", "utLineHits": 6, "lineHits": 6, "utConditions": 7, @@ -183,7 +196,8 @@ "line": 7, "code": "

SOURCE_7

", "scmRevision": "REVISION_7", - "scmDate": "1974-10-02T21:40:00-0500", + "scmAuthor": "AUTHOR_7", + "scmDate": "1974-10-03T03:40:00+0100", "utLineHits": 7, "lineHits": 7, "utConditions": 8, @@ -197,7 +211,8 @@ "line": 8, "code": "

SOURCE_8

", "scmRevision": "REVISION_8", - "scmDate": "1974-10-02T21:40:00-0500", + "scmAuthor": "AUTHOR_8", + "scmDate": "1974-10-03T03:40:00+0100", "utLineHits": 8, "lineHits": 8, "utConditions": 9, @@ -211,7 +226,8 @@ "line": 9, "code": "

SOURCE_9

", "scmRevision": "REVISION_9", - "scmDate": "1974-10-02T21:40:00-0500", + "scmAuthor": "AUTHOR_9", + "scmDate": "1974-10-03T03:40:00+0100", "utLineHits": 9, "lineHits": 9, "utConditions": 10, @@ -225,7 +241,8 @@ "line": 10, "code": "

SOURCE_10

", "scmRevision": "REVISION_10", - "scmDate": "1974-10-02T21:40:00-0500", + "scmAuthor": "AUTHOR_10", + "scmDate": "1974-10-03T03:40:00+0100", "utLineHits": 10, "lineHits": 10, "utConditions": 11, -- 2.39.5