From d6abb3ee19911c06bd8c01f4e57bcf8aaff445f9 Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Thu, 8 Sep 2022 08:50:47 -0500 Subject: SONAR-17287 Return optional flow description and type in WS responses --- build.gradle | 2 +- .../sonar-db-dao/src/main/protobuf/db-issues.proto | 5 +- .../org/sonar/server/hotspot/ws/SearchAction.java | 12 +-- .../org/sonar/server/hotspot/ws/ShowAction.java | 23 +++--- .../server/issue/TextRangeResponseFormatter.java | 30 ++++++++ .../org/sonar/server/issue/ws/SearchAction.java | 1 + .../server/issue/ws/SearchResponseFormat.java | 8 +- .../sonar/server/hotspot/ws/ShowActionTest.java | 89 +++++++++++++--------- .../src/main/protobuf/scanner_report.proto | 6 +- sonar-ws/src/main/protobuf/ws-commons.proto | 8 ++ sonar-ws/src/main/protobuf/ws-issues.proto | 2 + 11 files changed, 114 insertions(+), 72 deletions(-) diff --git a/build.gradle b/build.gradle index e353e1501b3..ef529ad5313 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/server/sonar-db-dao/src/main/protobuf/db-issues.proto b/server/sonar-db-dao/src/main/protobuf/db-issues.proto index 3138260372d..a094ea162ca 100644 --- a/server/sonar-db-dao/src/main/protobuf/db-issues.proto +++ b/server/sonar-db-dao/src/main/protobuf/db-issues.proto @@ -43,8 +43,9 @@ message Flow { } enum FlowType { - DATA = 0; - EXECUTION = 1; + UNDEFINED = 0; + DATA = 1; + EXECUTION = 2; } message Location { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java index 7a6b742820a..6abb2b5bc43 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java @@ -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) { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ShowAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ShowAction.java index 9cadb2e3035..39d3a514d8c 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ShowAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ShowAction.java @@ -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 componentUuids = readComponentUuidsFromLocations(hotspot, locations); Map 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 readComponentUuidsFromLocations(IssueDto hotspot, Locations locations) { @@ -246,7 +241,7 @@ public class ShowAction implements HotspotsWsAction { private Map loadComponents(DbSession dbSession, Set componentUuids) { Map 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); } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/TextRangeResponseFormatter.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/TextRangeResponseFormatter.java index 9bbb4bda6f9..d105c4e533c 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/TextRangeResponseFormatter.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/TextRangeResponseFormatter.java @@ -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 formatFlows(DbIssues.Locations locations, String issueComponent, Map 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 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 componentsByUuid) { Common.Location.Builder target = Common.Location.newBuilder(); if (source.hasMsg()) { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java index 51b9ac1bbd4..36aa53d81cb 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java @@ -193,6 +193,7 @@ public class SearchAction implements IssuesWsAction { + "
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"), diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java index 40ff06f7f74..f62a2077ecb 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java @@ -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) { diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/ShowActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/ShowActionTest.java index 2d1e0e8ae55..cfcced32b9c 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/ShowActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/ShowActionTest.java @@ -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("

Title

<div>line1
line2</div>"); } - @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)); } diff --git a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto index da7c32d0cfc..8e2be64f50f 100644 --- a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto +++ b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto @@ -241,9 +241,9 @@ message Flow { } enum FlowType { - DATA = 0; - EXECUTION = 1; - UNKNOWN = 2; + UNDEFINED = 0; + DATA = 1; + EXECUTION = 2; } message Changesets { diff --git a/sonar-ws/src/main/protobuf/ws-commons.proto b/sonar-ws/src/main/protobuf/ws-commons.proto index 7a49f67ccb1..71a564212a0 100644 --- a/sonar-ws/src/main/protobuf/ws-commons.proto +++ b/sonar-ws/src/main/protobuf/ws-commons.proto @@ -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 { diff --git a/sonar-ws/src/main/protobuf/ws-issues.proto b/sonar-ws/src/main/protobuf/ws-issues.proto index 15735d26244..ca76506ec2c 100644 --- a/sonar-ws/src/main/protobuf/ws-issues.proto +++ b/sonar-ws/src/main/protobuf/ws-issues.proto @@ -287,4 +287,6 @@ message TaintVulnerabilityLite { message Flow { repeated Location locations = 1; + optional string description = 2; + optional sonarqube.ws.commons.FlowType type = 3; } -- cgit v1.2.3