]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12075 return scmAuthor in api/sources/issue_snippets
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Tue, 18 Jun 2019 12:25:46 +0000 (14:25 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 28 Jun 2019 06:45:40 +0000 (08:45 +0200)
server/sonar-server/src/main/java/org/sonar/server/source/ws/IssueSnippetsAction.java
server/sonar-server/src/test/java/org/sonar/server/source/ws/IssueSnippetsActionTest.java
server/sonar-server/src/test/resources/org/sonar/server/source/ws/IssueSnippetsActionTest/issue_snippets_multiple_locations.json

index 1f0698937a09007f8a030208ddcfd97173ade726..80b5114dba837505969277db4a463263f31ac60d 100644 (file)
@@ -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<String, TreeSet<Integer>> linesPerComponent;
-      Map<String, ComponentDto> 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<String, TreeSet<Integer>> e : linesPerComponent.entrySet()) {
-          ComponentDto componentDto = componentsByUuid.get(e.getKey());
-          if (componentDto != null) {
-            processComponent(dbSession, jsonWriter, componentDto, e.getKey(), e.getValue());
+        Map<String, TreeSet<Integer>> linesPerComponent = getLinesPerComponent(componentUuid, locations);
+        Map<String, ComponentDto> 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<String, TreeSet<Integer>> 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<Integer> lines) {
-    Optional<Iterable<DbFileSources.Line>> lineSourcesOpt = sourceService.getLines(dbSession, fileUuid, lines);
+  private void writeSnippet(DbSession dbSession, JsonWriter writer, ComponentDto fileDto, Set<Integer> lines, boolean showScmAuthors) {
+    Optional<Iterable<DbFileSources.Line>> lineSourcesOpt = sourceService.getLines(dbSession, fileDto.uuid(), lines);
     if (!lineSourcesOpt.isPresent()) {
       return;
     }
 
     Supplier<Optional<Long>> periodDateSupplier = () -> dbClient.snapshotDao()
-      .selectLastAnalysisByComponentUuid(dbSession, componentDto.projectUuid())
+      .selectLastAnalysisByComponentUuid(dbSession, fileDto.projectUuid())
       .map(SnapshotDto::getPeriodDate);
 
     Iterable<DbFileSources.Line> 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<String, TreeSet<Integer>> getLinesPerComponent(String componentUuid, DbIssues.Locations locations) {
+  private static Map<String, TreeSet<Integer>> getLinesPerComponent(String componentUuid, DbIssues.Locations locations) {
     Map<String, TreeSet<Integer>> linesPerComponent = new HashMap<>();
 
     if (locations.hasTextRange()) {
@@ -155,7 +160,7 @@ public class IssueSnippetsAction implements SourcesWsAction {
   }
 
   private static void addTextRange(Map<String, TreeSet<Integer>> linesPerComponent, String componentUuid,
-    DbCommons.TextRange textRange, int numLinesAfterIssue) {
+                                   DbCommons.TextRange textRange, int numLinesAfterIssue) {
     int start = textRange.getStartLine() - 2;
     int end = textRange.getEndLine() + numLinesAfterIssue;
 
index 2dd813daab14e99864321478d750c9ffa24047bd..742cce0823d952a21efe28bdd3ae80d77524098f 100644 (file)
@@ -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<String>)
-      invocationOnMock -> "<p>" + invocationOnMock.getArguments()[0] + "</p>");
+    when(htmlSourceDecorator.getDecoratedSourceAsHtml(anyString(), anyString(), anyString()))
+      .then((Answer<String>) invocationOnMock -> "<p>" + invocationOnMock.getArguments()[0] + "</p>");
     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;
+  }
+
 }
index f4fe2d2bb53047bae54877447bc6a772529ed606..d27edae9477ceea00427e5f40d96dffad84db109 100644 (file)
@@ -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": "<p>SOURCE_1</p>",
         "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": "<p>SOURCE_2</p>",
         "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": "<p>SOURCE_3</p>",
         "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": "<p>SOURCE_4</p>",
         "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": "<p>SOURCE_5</p>",
         "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": "<p>SOURCE_6</p>",
         "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,
         "line": 7,
         "code": "<p>SOURCE_7</p>",
         "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,
       "longName": "null/NAME_file1",
       "q": "FIL",
       "project": "KEY_projectUuid",
-      "projectName": "LONG_NAME_projectUuid"
+      "projectName": "LONG_NAME_projectUuid",
+      "measures": {}
     },
     "sources": [
       {
         "line": 3,
         "code": "<p>SOURCE_3</p>",
         "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,
         "line": 4,
         "code": "<p>SOURCE_4</p>",
         "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,
         "line": 5,
         "code": "<p>SOURCE_5</p>",
         "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,
         "line": 6,
         "code": "<p>SOURCE_6</p>",
         "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,
         "line": 7,
         "code": "<p>SOURCE_7</p>",
         "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,
         "line": 8,
         "code": "<p>SOURCE_8</p>",
         "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,
         "line": 9,
         "code": "<p>SOURCE_9</p>",
         "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,
         "line": 10,
         "code": "<p>SOURCE_10</p>",
         "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,