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;
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;
}
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()) {
}
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;
*/
package org.sonar.server.source.ws;
+import java.net.URL;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Rule;
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;
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
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
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
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) {
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;
+ }
+
}
"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,
"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,
"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,
"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,