Browse Source

SONAR-17287 Return optional flow description and type in WS responses

tags/9.7.0.61563
Duarte Meneses 1 year ago
parent
commit
d6abb3ee19

+ 1
- 1
build.gradle View File

@@ -201,7 +201,7 @@ subprojects {
dependency 'org.sonarsource.kotlin:sonar-kotlin-plugin:2.10.0.1456'
dependency 'org.sonarsource.slang:sonar-ruby-plugin:1.10.0.3710'
dependency 'org.sonarsource.slang:sonar-scala-plugin:1.10.0.3710'
dependency 'org.sonarsource.api.plugin:sonar-plugin-api:9.10.0.269'
dependency 'org.sonarsource.api.plugin:sonar-plugin-api:9.11.0.290'
dependency 'org.sonarsource.xml:sonar-xml-plugin:2.5.0.3376'
dependency 'org.sonarsource.iac:sonar-iac-plugin:1.9.2.2279'
dependency 'org.sonarsource.text:sonar-text-plugin:1.1.0.282'

+ 3
- 2
server/sonar-db-dao/src/main/protobuf/db-issues.proto View File

@@ -43,8 +43,9 @@ message Flow {
}

enum FlowType {
DATA = 0;
EXECUTION = 1;
UNDEFINED = 0;
DATA = 1;
EXECUTION = 2;
}

message Location {

+ 3
- 9
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java View File

@@ -199,7 +199,8 @@ public class SearchAction implements HotspotsWsAction {
.setSince("8.1")
.setInternal(true)
.setChangelog(
new Change("9.6", "Added parameters 'pciDss-3.2' and 'pciDss-4.0"));
new Change("9.6", "Added parameters 'pciDss-3.2' and 'pciDss-4.0"),
new Change("9.7", "Hotspot flows in the response may contain a description and a type"));

action.addPagingParams(100);
action.createParam(PARAM_PROJECT_KEY)
@@ -603,14 +604,7 @@ public class SearchAction implements HotspotsWsAction {
}

textRangeFormatter.formatTextRange(locations, hotspotBuilder::setTextRange);

for (DbIssues.Flow flow : locations.getFlowList()) {
Common.Flow.Builder targetFlow = Common.Flow.newBuilder();
for (DbIssues.Location flowLocation : flow.getLocationList()) {
targetFlow.addLocations(textRangeFormatter.formatLocation(flowLocation, hotspotBuilder.getComponent(), data.getComponentsByUuid()));
}
hotspotBuilder.addFlows(targetFlow.build());
}
hotspotBuilder.addAllFlows(textRangeFormatter.formatFlows(locations, hotspotBuilder.getComponent(), data.getComponentsByUuid()));
}

private void formatComponents(SearchResponseData searchResponseData, SearchWsResponse.Builder responseBuilder) {

+ 9
- 14
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ShowAction.java View File

@@ -105,7 +105,8 @@ public class ShowAction implements HotspotsWsAction {
.setDescription("Provides the details of a Security Hotspot.")
.setSince("8.1")
.setChangelog(new Change("9.5", "The fields rule.riskDescription, rule.fixRecommendations, rule.vulnerabilityDescription of the response are deprecated."
+ " /api/rules/show endpoint should be used to fetch rule descriptions."));
+ " /api/rules/show endpoint should be used to fetch rule descriptions."),
new Change("9.7", "Hotspot flows in the response may contain a description and a type"));

action.createParam(PARAM_HOTSPOT_KEY)
.setDescription("Key of the Security Hotspot")
@@ -222,13 +223,7 @@ public class ShowAction implements HotspotsWsAction {
Set<String> componentUuids = readComponentUuidsFromLocations(hotspot, locations);
Map<String, ComponentDto> componentsByUuids = loadComponents(dbSession, componentUuids);

for (DbIssues.Flow flow : locations.getFlowList()) {
Common.Flow.Builder targetFlow = Common.Flow.newBuilder();
for (DbIssues.Location flowLocation : flow.getLocationList()) {
targetFlow.addLocations(textRangeFormatter.formatLocation(flowLocation, hotspotBuilder.getComponent().getKey(), componentsByUuids));
}
hotspotBuilder.addFlows(targetFlow.build());
}
hotspotBuilder.addAllFlows(textRangeFormatter.formatFlows(locations, hotspotBuilder.getComponent().getKey(), componentsByUuids));
}

private static Set<String> readComponentUuidsFromLocations(IssueDto hotspot, Locations locations) {
@@ -246,7 +241,7 @@ public class ShowAction implements HotspotsWsAction {

private Map<String, ComponentDto> loadComponents(DbSession dbSession, Set<String> componentUuids) {
Map<String, ComponentDto> componentsByUuids = dbClient.componentDao().selectSubProjectsByComponentUuids(dbSession,
componentUuids)
componentUuids)
.stream()
.collect(toMap(ComponentDto::uuid, Function.identity(), (componentDto, componentDto2) -> componentDto2));

@@ -278,10 +273,10 @@ public class ShowAction implements HotspotsWsAction {
private void formatUsers(ShowWsResponse.Builder responseBuilder, Users users, FormattingContext formattingContext) {
Common.User.Builder userBuilder = Common.User.newBuilder();
Stream.concat(
Stream.of(users.getAssignee(), users.getAuthor())
.filter(Optional::isPresent)
.map(Optional::get),
formattingContext.getUsers().stream())
Stream.of(users.getAssignee(), users.getAuthor())
.filter(Optional::isPresent)
.map(Optional::get),
formattingContext.getUsers().stream())
.distinct()
.map(user -> userFormatter.formatUser(userBuilder, user))
.forEach(responseBuilder::addUsers);
@@ -302,7 +297,7 @@ public class ShowAction implements HotspotsWsAction {
boolean hotspotOnProject = Objects.equals(project.uuid(), componentUuid);
ComponentDto component = hotspotOnProject ? project
: dbClient.componentDao().selectByUuid(dbSession, componentUuid)
.orElseThrow(() -> new NotFoundException(format("Component with uuid '%s' does not exist", componentUuid)));
.orElseThrow(() -> new NotFoundException(format("Component with uuid '%s' does not exist", componentUuid)));

return new Components(project, component);
}

+ 30
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/TextRangeResponseFormatter.java View File

@@ -19,8 +19,11 @@
*/
package org.sonar.server.issue;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.issue.IssueDto;
import org.sonar.db.protobuf.DbCommons;
@@ -46,6 +49,33 @@ public class TextRangeResponseFormatter {
}
}

public List<Common.Flow> formatFlows(DbIssues.Locations locations, String issueComponent, Map<String, ComponentDto> componentsByUuid) {
return locations.getFlowList().stream().map(flow -> {
Common.Flow.Builder targetFlow = Common.Flow.newBuilder();
for (DbIssues.Location flowLocation : flow.getLocationList()) {
targetFlow.addLocations(formatLocation(flowLocation, issueComponent, componentsByUuid));
}
if (flow.hasDescription()) {
targetFlow.setDescription(flow.getDescription());
}
if (flow.hasType()) {
convertFlowType(flow.getType()).ifPresent(targetFlow::setFlowType);
}
return targetFlow.build();
}).collect(Collectors.toList());
}

private static Optional<Common.FlowType> convertFlowType(DbIssues.FlowType flowType) {
switch (flowType) {
case DATA:
return Optional.of(Common.FlowType.DATA);
case EXECUTION:
return Optional.of(Common.FlowType.EXECUTION);
default:
return Optional.empty();
}
}

public Common.Location formatLocation(DbIssues.Location source, String issueComponent, Map<String, ComponentDto> componentsByUuid) {
Common.Location.Builder target = Common.Location.newBuilder();
if (source.hasMsg()) {

+ 1
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java View File

@@ -193,6 +193,7 @@ public class SearchAction implements IssuesWsAction {
+ "<br/>When issue indexation is in progress returns 503 service unavailable HTTP code.")
.setSince("3.6")
.setChangelog(
new Change("9.7", "Issues flows in the response may contain a description and a type"),
new Change("9.6", "Response field 'fromHotspot' dropped."),
new Change("9.6", "Added facets 'pciDss-3.2' and 'pciDss-4.0"),
new Change("9.6", "Added parameters 'pciDss-3.2' and 'pciDss-4.0"),

+ 1
- 7
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java View File

@@ -226,13 +226,7 @@ public class SearchResponseFormat {
return;
}
textRangeFormatter.formatTextRange(locations, issueBuilder::setTextRange);
for (DbIssues.Flow flow : locations.getFlowList()) {
Common.Flow.Builder targetFlow = Common.Flow.newBuilder();
for (DbIssues.Location flowLocation : flow.getLocationList()) {
targetFlow.addLocations(textRangeFormatter.formatLocation(flowLocation, issueBuilder.getComponent(), data.getComponentsByUuid()));
}
issueBuilder.addFlows(targetFlow.build());
}
issueBuilder.addAllFlows(textRangeFormatter.formatFlows(locations, issueBuilder.getComponent(), data.getComponentsByUuid()));
}

private static Transitions createIssueTransition(SearchResponseData data, IssueDto dto) {

+ 53
- 36
server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/ShowActionTest.java View File

@@ -204,36 +204,50 @@ public class ShowActionTest {
ComponentDto project = dbTester.components().insertPublicProject();
ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
ComponentDto anotherFile = dbTester.components().insertComponent(newFileDto(project));
DbIssues.Locations.Builder locations = DbIssues.Locations.newBuilder().addFlow(DbIssues.Flow.newBuilder().addAllLocation(Arrays.asList(
DbIssues.Location.newBuilder()
.setComponentId(file.uuid())
.setMsg("FLOW MESSAGE")
.setTextRange(DbCommons.TextRange.newBuilder()
.setStartLine(1)
.setEndLine(1)
.setStartOffset(0)
.setEndOffset(12)
.build())
.build(),
DbIssues.Location.newBuilder()
.setComponentId(anotherFile.uuid())
.setMsg("ANOTHER FLOW MESSAGE")
.setTextRange(DbCommons.TextRange.newBuilder()
.setStartLine(1)
.setEndLine(1)
.setStartOffset(0)
.setEndOffset(12)
.build())
.build(),
DbIssues.Location.newBuilder()
.setMsg("FLOW MESSAGE WITHOUT FILE UUID")
.setTextRange(DbCommons.TextRange.newBuilder()
.setStartLine(1)
.setEndLine(1)
.setStartOffset(0)
.setEndOffset(12)
.build())
.build())));
DbIssues.Locations.Builder locations = DbIssues.Locations.newBuilder()
.addFlow(DbIssues.Flow.newBuilder()
.setDescription("FLOW DESCRIPTION")
.setType(DbIssues.FlowType.DATA)
.addAllLocation(Arrays.asList(
DbIssues.Location.newBuilder()
.setComponentId(file.uuid())
.setMsg("FLOW MESSAGE")
.setTextRange(DbCommons.TextRange.newBuilder()
.setStartLine(1)
.setEndLine(1)
.setStartOffset(0)
.setEndOffset(12)
.build())
.build(),
DbIssues.Location.newBuilder()
.setComponentId(anotherFile.uuid())
.setMsg("ANOTHER FLOW MESSAGE")
.setTextRange(DbCommons.TextRange.newBuilder()
.setStartLine(1)
.setEndLine(1)
.setStartOffset(0)
.setEndOffset(12)
.build())
.build(),
DbIssues.Location.newBuilder()
.setMsg("FLOW MESSAGE WITHOUT FILE UUID")
.setTextRange(DbCommons.TextRange.newBuilder()
.setStartLine(1)
.setEndLine(1)
.setStartOffset(0)
.setEndOffset(12)
.build())
.build())))
.addFlow(DbIssues.Flow.newBuilder()
.addAllLocation(Arrays.asList(
DbIssues.Location.newBuilder()
.setComponentId(file.uuid())
.setTextRange(DbCommons.TextRange.newBuilder()
.setStartLine(1)
.setStartOffset(0)
.setEndOffset(12)
.build())
.build())));
RuleDto rule = newRule(SECURITY_HOTSPOT);
var hotspot = dbTester.issues().insertHotspot(rule, project, file, i -> i.setLocations(locations.build()));
mockChangelogAndCommentsFormattingContext();
@@ -244,13 +258,18 @@ public class ShowActionTest {
.executeProtobuf(Hotspots.ShowWsResponse.class);

assertThat(response.getKey()).isEqualTo(hotspot.getKey());
assertThat(response.getFlowsCount()).isEqualTo(1);
assertThat(response.getFlowsCount()).isEqualTo(2);
assertThat(response.getFlows(0).getDescription()).isEqualTo("FLOW DESCRIPTION");
assertThat(response.getFlows(0).getFlowType()).isEqualTo(Common.FlowType.DATA);
assertThat(response.getFlows(0).getLocationsList())
.extracting(Location::getMsg, Location::getComponent)
.containsExactlyInAnyOrder(
tuple("FLOW MESSAGE", file.getKey()),
tuple("ANOTHER FLOW MESSAGE", anotherFile.getKey()),
tuple("FLOW MESSAGE WITHOUT FILE UUID", file.getKey()));

assertThat(response.getFlows(1).getDescription()).isEmpty();
assertThat(response.getFlows(1).hasFlowType()).isFalse();
}

@Test
@@ -435,7 +454,6 @@ public class ShowActionTest {
};
}


@Test
public void dispatch_description_sections_of_advanced_rule_in_relevant_field() {
ComponentDto project = dbTester.components().insertPrivateProject();
@@ -481,7 +499,7 @@ public class ShowActionTest {

RuleDto rule = newRuleWithoutSection(SECURITY_HOTSPOT,
r -> r.addRuleDescriptionSectionDto(introductionSection)
.setDescriptionFormat(HTML));
.setDescriptionFormat(HTML));

IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
mockChangelogAndCommentsFormattingContext();
@@ -559,7 +577,6 @@ public class ShowActionTest {
assertThat(response.getRule().getRiskDescription()).isEqualTo("<h2>Title</h2>&lt;div&gt;line1<br/>line2&lt;/div&gt;");
}


@Test
public void handles_null_description_for_custom_rules() {
ComponentDto project = dbTester.components().insertPrivateProject();
@@ -1037,8 +1054,8 @@ public class ShowActionTest {
.extracting(User::getLogin, User::getName, User::getActive)
.containsExactlyInAnyOrder(
Stream.concat(
Stream.of(author, assignee),
changeLogAndCommentsUsers.stream())
Stream.of(author, assignee),
changeLogAndCommentsUsers.stream())
.map(t -> tuple(t.getLogin(), t.getName(), t.isActive()))
.toArray(Tuple[]::new));
}

+ 3
- 3
sonar-scanner-protocol/src/main/protobuf/scanner_report.proto View File

@@ -241,9 +241,9 @@ message Flow {
}

enum FlowType {
DATA = 0;
EXECUTION = 1;
UNKNOWN = 2;
UNDEFINED = 0;
DATA = 1;
EXECUTION = 2;
}

message Changesets {

+ 8
- 0
sonar-ws/src/main/protobuf/ws-commons.proto View File

@@ -95,6 +95,14 @@ message TextRange {

message Flow {
repeated Location locations = 1;
optional string description = 2;
optional FlowType flowType = 3;
}

enum FlowType {
UNDEFINED = 0;
DATA = 1;
EXECUTION = 2;
}

message Location {

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

@@ -287,4 +287,6 @@ message TaintVulnerabilityLite {

message Flow {
repeated Location locations = 1;
optional string description = 2;
optional sonarqube.ws.commons.FlowType type = 3;
}

Loading…
Cancel
Save