From 7f4d7aabc87647aa40386ab2708544e023ca0896 Mon Sep 17 00:00:00 2001 From: lukasz-jarocki-sonarsource <77498856+lukasz-jarocki-sonarsource@users.noreply.github.com> Date: Mon, 28 Nov 2022 17:41:04 +0100 Subject: [PATCH] SONAR-17592 added message formatting to responses in webapi --- .../sonar/server/hotspot/ws/SearchAction.java | 7 ++- .../sonar/server/hotspot/ws/ShowAction.java | 20 ++++--- .../issue/TextRangeResponseFormatter.java | 2 + .../sonar/server/issue/ws/SearchAction.java | 1 + .../server/issue/ws/SearchResponseFormat.java | 2 + ...ullTaintActionProtobufObjectGenerator.java | 2 + .../server/hotspot/ws/search-example.json | 5 +- .../sonar/server/hotspot/ws/show-example.json | 7 +++ .../sonar/server/issue/ws/search-example.json | 19 ++++++- .../server/hotspot/ws/ShowActionTest.java | 17 ++++-- .../server/issue/ws/PullTaintActionTest.java | 5 ++ .../server/issue/ws/SearchActionTest.java | 27 ++++++--- .../server/ws/MessageFormattingUtils.java | 52 ++++++++++++++++++ .../server/ws/MessageFormattingUtilsTest.java | 55 +++++++++++++++++++ sonar-ws/src/main/protobuf/ws-commons.proto | 11 ++++ sonar-ws/src/main/protobuf/ws-hotspots.proto | 2 + sonar-ws/src/main/protobuf/ws-issues.proto | 3 + 17 files changed, 209 insertions(+), 28 deletions(-) create mode 100644 server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/MessageFormattingUtils.java create mode 100644 server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/MessageFormattingUtilsTest.java 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 f3f52032a64..e250d0977f8 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 @@ -65,6 +65,7 @@ import org.sonar.server.issue.index.IssueIndexSyncProgressChecker; import org.sonar.server.issue.index.IssueQuery; import org.sonar.server.security.SecurityStandards; import org.sonar.server.user.UserSession; +import org.sonar.server.ws.MessageFormattingUtils; import org.sonarqube.ws.Common; import org.sonarqube.ws.Hotspots; import org.sonarqube.ws.Hotspots.SearchWsResponse; @@ -234,7 +235,8 @@ public class SearchAction implements HotspotsWsAction { 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"), new Change("9.7", "Hotspot in the response contain the corresponding ruleKey"), - new Change("9.8", "Endpoint visibility change from internal to public")); + new Change("9.8", "Endpoint visibility change from internal to public"), + new Change("9.8", "Add message formatting to issue and locations response")); action.addPagingParams(100); action.createParam(PARAM_PROJECT_KEY) @@ -608,6 +610,7 @@ public class SearchAction implements HotspotsWsAction { ofNullable(hotspot.getResolution()).ifPresent(builder::setResolution); ofNullable(hotspot.getLine()).ifPresent(builder::setLine); builder.setMessage(nullToEmpty(hotspot.getMessage())); + builder.addAllMessageFormattings(MessageFormattingUtils.dbMessageFormattingToWs(hotspot.parseMessageFormattings())); ofNullable(hotspot.getAssigneeUuid()).ifPresent(builder::setAssignee); builder.setAuthor(nullToEmpty(hotspot.getAuthorLogin())); builder.setCreationDate(formatDateTime(hotspot.getIssueCreationDate())); @@ -666,7 +669,7 @@ public class SearchAction implements HotspotsWsAction { private final Set sonarsourceSecurity; private final Set cwe; private final Set files; - + private WsRequest(int page, int index, @Nullable String projectKey, @Nullable String branch, @Nullable String pullRequest, Set hotspotKeys, @Nullable String status, @Nullable String resolution, @Nullable Boolean inNewCodePeriod, @Nullable Boolean onlyMine, 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 7a55dcb9530..0df0216d228 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 @@ -56,6 +56,7 @@ import org.sonar.server.issue.IssueChangeWSSupport.Load; import org.sonar.server.issue.TextRangeResponseFormatter; import org.sonar.server.issue.ws.UserResponseFormatter; import org.sonar.server.security.SecurityStandards; +import org.sonar.server.ws.MessageFormattingUtils; import org.sonarqube.ws.Common; import org.sonarqube.ws.Hotspots; import org.sonarqube.ws.Hotspots.ShowWsResponse; @@ -106,9 +107,11 @@ public class ShowAction implements HotspotsWsAction { .setHandler(this) .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." + .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."), - new Change("9.7", "Hotspot flows in the response may contain a description and a type")); + new Change("9.7", "Hotspot flows in the response may contain a description and a type"), + new Change("9.8", "Add message formatting to issue and locations response")); action.createParam(PARAM_HOTSPOT_KEY) .setDescription("Key of the Security Hotspot") @@ -163,6 +166,7 @@ public class ShowAction implements HotspotsWsAction { ofNullable(hotspot.getLine()).ifPresent(builder::setLine); ofNullable(emptyToNull(hotspot.getChecksum())).ifPresent(builder::setHash); builder.setMessage(nullToEmpty(hotspot.getMessage())); + builder.addAllMessageFormattings(MessageFormattingUtils.dbMessageFormattingToWs(hotspot.parseMessageFormattings())); builder.setCreationDate(formatDateTime(hotspot.getIssueCreationDate())); builder.setUpdateDate(formatDateTime(hotspot.getIssueUpdateDate())); users.getAssignee().map(UserDto::getLogin).ifPresent(builder::setAssignee); @@ -243,7 +247,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)); @@ -275,10 +279,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); @@ -300,7 +304,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, branch); } 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 aec7338ca63..599c9c09239 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 @@ -28,6 +28,7 @@ import org.sonar.db.component.ComponentDto; import org.sonar.db.issue.IssueDto; import org.sonar.db.protobuf.DbCommons; import org.sonar.db.protobuf.DbIssues; +import org.sonar.server.ws.MessageFormattingUtils; import org.sonarqube.ws.Common; import static java.util.Optional.ofNullable; @@ -82,6 +83,7 @@ public class TextRangeResponseFormatter { Common.Location.Builder target = Common.Location.newBuilder(); if (source.hasMsg()) { target.setMsg(source.getMsg()); + target.addAllMsgFormattings(MessageFormattingUtils.dbMessageFormattingListToWs(source.getMsgFormattingList())); } if (source.hasTextRange()) { DbCommons.TextRange sourceRange = source.getTextRange(); 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 a08f06cb34a..fa7b0f14cfc 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 @@ -196,6 +196,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.8", "Add message formatting to issue and locations response"), new Change("9.8", "response fields 'total', 's', 'ps' have been deprecated, please use 'paging' object instead"), new Change("9.7", "Issues flows in the response may contain a description and a type"), new Change("9.6", "Response field 'fromHotspot' dropped."), 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 541788dfe08..7d9fc2bff30 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 @@ -48,6 +48,7 @@ import org.sonar.server.es.Facets; import org.sonar.server.issue.TextRangeResponseFormatter; import org.sonar.server.issue.index.IssueScope; import org.sonar.server.issue.workflow.Transition; +import org.sonar.server.ws.MessageFormattingUtils; import org.sonarqube.ws.Common; import org.sonarqube.ws.Common.Comment; import org.sonarqube.ws.Common.User; @@ -179,6 +180,7 @@ public class SearchResponseFormat { ofNullable(emptyToNull(dto.getResolution())).ifPresent(issueBuilder::setResolution); issueBuilder.setStatus(dto.getStatus()); issueBuilder.setMessage(nullToEmpty(dto.getMessage())); + issueBuilder.addAllMessageFormattings(MessageFormattingUtils.dbMessageFormattingToWs(dto.parseMessageFormattings())); issueBuilder.addAllTags(dto.getTags()); Long effort = dto.getEffort(); if (effort != null) { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/pull/PullTaintActionProtobufObjectGenerator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/pull/PullTaintActionProtobufObjectGenerator.java index 46136cde772..0647cc9880c 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/pull/PullTaintActionProtobufObjectGenerator.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/pull/PullTaintActionProtobufObjectGenerator.java @@ -33,6 +33,7 @@ import org.sonar.db.component.ComponentDto; import org.sonar.db.issue.IssueDto; import org.sonar.db.protobuf.DbIssues; import org.sonar.server.user.UserSession; +import org.sonar.server.ws.MessageFormattingUtils; import org.sonarqube.ws.Common; import org.sonarqube.ws.Issues; @@ -71,6 +72,7 @@ public class PullTaintActionProtobufObjectGenerator implements ProtobufObjectGen Issues.Location.Builder locationBuilder = Issues.Location.newBuilder(); if (issueDto.getMessage() != null) { locationBuilder.setMessage(issueDto.getMessage()); + locationBuilder.addAllMessageFormattings(MessageFormattingUtils.dbMessageFormattingToWs(issueDto.parseMessageFormattings())); } if (issueDto.getFilePath() != null) { locationBuilder.setFilePath(issueDto.getFilePath()); diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/hotspot/ws/search-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/hotspot/ws/search-example.json index f23e1a62e07..0ee090fee84 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/hotspot/ws/search-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/hotspot/ws/search-example.json @@ -14,6 +14,7 @@ "status": "TO_REVIEW", "line": 10, "message": "message-0", + "messageFormattings": [], "assignee": "assignee-uuid", "author": "joe", "creationDate": "2020-01-02T15:43:10+0100", @@ -30,6 +31,7 @@ "status": "TO_REVIEW", "line": 11, "message": "message-1", + "messageFormattings": [], "assignee": "assignee-uuid", "author": "joe", "creationDate": "2020-01-02T15:43:10+0100", @@ -46,6 +48,7 @@ "status": "TO_REVIEW", "line": 12, "message": "message-2", + "messageFormattings": [], "assignee": "assignee-uuid", "author": "joe", "creationDate": "2020-01-02T15:43:10+0100", @@ -69,4 +72,4 @@ "longName": "test-project" } ] -} \ No newline at end of file +} diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/hotspot/ws/show-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/hotspot/ws/show-example.json index b242faae585..feb46663bd0 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/hotspot/ws/show-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/hotspot/ws/show-example.json @@ -23,6 +23,13 @@ "line": 10, "hash": "a227e508d6646b55a086ee11d63b21e9", "message": "message", + "messageFormattings": [ + { + "start": 0, + "end": 4, + "type": "CODE" + } + ], "assignee": "joe", "author": "joe", "creationDate": "2020-01-02T15:43:10+0100", diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/search-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/search-example.json index ba19cc0b7df..3e90745a062 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/search-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/search-example.json @@ -14,6 +14,13 @@ "resolution": "WONTFIX", "severity": "MAJOR", "message": "Remove this unused private \"getKee\" method.", + "messageFormattings": [ + { + "start": 0, + "end": 4, + "type": "CODE" + } + ], "line": 81, "hash": "a227e508d6646b55a086ee11d63b21e9", "author": "Developer 1", @@ -59,7 +66,14 @@ "startOffset": 0, "endOffset": 30 }, - "msg": "Expected position: 5" + "msg": "Expected position: 5", + "msgFormattings": [ + { + "start": 0, + "end": 4, + "type": "CODE" + } + ] } ] }, @@ -72,7 +86,8 @@ "startOffset": 0, "endOffset": 37 }, - "msg": "Expected position: 6" + "msg": "Expected position: 6", + "msgFormattings": [] } ] } 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 e235ffc4398..af5d7c0ba68 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 @@ -102,6 +102,7 @@ import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSe import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.RESOURCES_SECTION_KEY; import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY; import static org.sonar.db.component.ComponentTesting.newFileDto; +import static org.sonar.db.protobuf.DbIssues.MessageFormattingType.CODE; import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY; import static org.sonar.db.rule.RuleDto.Format.HTML; import static org.sonar.db.rule.RuleDto.Format.MARKDOWN; @@ -109,6 +110,7 @@ import static org.sonar.db.rule.RuleDto.Format.MARKDOWN; @RunWith(DataProviderRunner.class) public class ShowActionTest { private static final Random RANDOM = new Random(); + public static final DbIssues.MessageFormatting MESSAGE_FORMATTING = DbIssues.MessageFormatting.newBuilder().setStart(0).setEnd(4).setType(CODE).build(); @Rule public DbTester dbTester = DbTester.create(System2.INSTANCE); @@ -215,6 +217,7 @@ public class ShowActionTest { DbIssues.Location.newBuilder() .setComponentId(file.uuid()) .setMsg("FLOW MESSAGE") + .addMsgFormatting(MESSAGE_FORMATTING) .setTextRange(textRange(1, 1, 0, 12)).build(), DbIssues.Location.newBuilder() .setComponentId(anotherFile.uuid()) @@ -247,11 +250,12 @@ public class ShowActionTest { assertThat(response.getFlows(0).getDescription()).isEqualTo("FLOW DESCRIPTION"); assertThat(response.getFlows(0).getType()).isEqualTo(Common.FlowType.DATA); assertThat(response.getFlows(0).getLocationsList()) - .extracting(Location::getMsg, Location::getComponent) + .extracting(Location::getMsg, Location::getMsgFormattingsList, Location::getComponent) .containsExactlyInAnyOrder( - tuple("FLOW MESSAGE", file.getKey()), - tuple("ANOTHER FLOW MESSAGE", anotherFile.getKey()), - tuple("FLOW MESSAGE WITHOUT FILE UUID", file.getKey())); + tuple("FLOW MESSAGE", List.of(Common.MessageFormatting.newBuilder() + .setStart(0).setEnd(4).setType(Common.MessageFormattingType.CODE).build()), file.getKey()), + tuple("ANOTHER FLOW MESSAGE", List.of(), anotherFile.getKey()), + tuple("FLOW MESSAGE WITHOUT FILE UUID", List.of(), file.getKey())); assertThat(response.getFlows(1).getDescription()).isEmpty(); assertThat(response.getFlows(1).hasType()).isFalse(); @@ -1036,8 +1040,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)); } @@ -1090,6 +1094,7 @@ public class ShowActionTest { .setAssigneeUuid("assignee-uuid") .setAuthorLogin("joe") .setMessage("message") + .setMessageFormattings(DbIssues.MessageFormattings.newBuilder().addMessageFormatting(MESSAGE_FORMATTING).build()) .setLine(10) .setChecksum("a227e508d6646b55a086ee11d63b21e9") .setIssueCreationTime(time) diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/PullTaintActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/PullTaintActionTest.java index 637b5c40910..5a8ad622255 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/PullTaintActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/PullTaintActionTest.java @@ -46,6 +46,7 @@ import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.issue.TaintChecker; import org.sonar.server.issue.ws.pull.PullTaintActionProtobufObjectGenerator; import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.MessageFormattingUtils; import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.TestResponse; import org.sonar.server.ws.WsActionTester; @@ -60,12 +61,14 @@ import static org.mockito.Mockito.when; import static org.sonar.api.web.UserRole.USER; import static org.sonar.db.component.BranchDto.DEFAULT_PROJECT_MAIN_BRANCH_NAME; import static org.sonar.db.component.ComponentTesting.newFileDto; +import static org.sonar.db.protobuf.DbIssues.MessageFormattingType.CODE; public class PullTaintActionTest { private static final long NOW = 10_000_000_000L; private static final long PAST = 1_000_000_000L; private static final String DEFAULT_BRANCH = DEFAULT_PROJECT_MAIN_BRANCH_NAME; + public static final DbIssues.MessageFormatting MESSAGE_FORMATTING = DbIssues.MessageFormatting.newBuilder().setStart(0).setEnd(4).setType(CODE).build(); @Rule public DbTester dbTester = DbTester.create(); @@ -225,6 +228,7 @@ public class PullTaintActionTest { .setAssigneeUuid(userSession.getUuid()) .setManualSeverity(true) .setMessage("message") + .setMessageFormattings(DbIssues.MessageFormattings.newBuilder().addMessageFormatting(MESSAGE_FORMATTING).build()) .setCreatedAt(NOW) .setStatus(Issue.STATUS_OPEN) .setLocations(mainLocation.build()) @@ -250,6 +254,7 @@ public class PullTaintActionTest { Issues.Location location = taintLite.getMainLocation(); assertThat(location.getMessage()).isEqualTo(issueDto.getMessage()); + assertThat(location.getMessageFormattingsList()).isEqualTo(MessageFormattingUtils.dbMessageFormattingListToWs(List.of(MESSAGE_FORMATTING))); Issues.TextRange locationTextRange = location.getTextRange(); assertThat(locationTextRange.getStartLine()).isEqualTo(1); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java index 49cfd31ec09..8302192ca6f 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java @@ -75,6 +75,7 @@ import org.sonar.server.issue.workflow.IssueWorkflow; import org.sonar.server.permission.index.PermissionIndexer; import org.sonar.server.permission.index.WebAuthorizationTypeSupport; import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.MessageFormattingUtils; import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.TestResponse; import org.sonar.server.ws.WsActionTester; @@ -101,6 +102,7 @@ import static org.sonar.api.utils.DateUtils.parseDateTime; import static org.sonar.api.web.UserRole.ISSUE_ADMIN; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.issue.IssueTesting.newDto; +import static org.sonar.db.protobuf.DbIssues.MessageFormattingType.CODE; import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection; import static org.sonar.db.rule.RuleTesting.XOO_X1; import static org.sonar.db.rule.RuleTesting.XOO_X2; @@ -126,6 +128,8 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES; public class SearchActionTest { + public static final DbIssues.MessageFormatting MESSAGE_FORMATTING = DbIssues.MessageFormatting.newBuilder() + .setStart(0).setEnd(11).setType(CODE).build(); private final UuidFactoryFast uuidFactory = UuidFactoryFast.getInstance(); @Rule public UserSessionRule userSession = standalone(); @@ -168,6 +172,7 @@ public class SearchActionTest { .setLine(42) .setChecksum("a227e508d6646b55a086ee11d63b21e9") .setMessage("the message") + .setMessageFormattings(DbIssues.MessageFormattings.newBuilder().addMessageFormatting(MESSAGE_FORMATTING).build()) .setStatus(STATUS_RESOLVED) .setResolution(RESOLUTION_FIXED) .setSeverity("MAJOR") @@ -184,11 +189,12 @@ public class SearchActionTest { assertThat(response.getIssuesList()) .extracting( - Issue::getKey, Issue::getRule, Issue::getSeverity, Issue::getComponent, Issue::getResolution, Issue::getStatus, Issue::getMessage, Issue::getEffort, - Issue::getAssignee, Issue::getAuthor, Issue::getLine, Issue::getHash, Issue::getTagsList, Issue::getCreationDate, Issue::getUpdateDate, + Issue::getKey, Issue::getRule, Issue::getSeverity, Issue::getComponent, Issue::getResolution, Issue::getStatus, Issue::getMessage, Issue::getMessageFormattingsList, + Issue::getEffort, Issue::getAssignee, Issue::getAuthor, Issue::getLine, Issue::getHash, Issue::getTagsList, Issue::getCreationDate, Issue::getUpdateDate, Issue::getQuickFixAvailable) .containsExactlyInAnyOrder( - tuple(issue.getKey(), rule.getKey().toString(), Severity.MAJOR, file.getKey(), RESOLUTION_FIXED, STATUS_RESOLVED, "the message", "10min", + tuple(issue.getKey(), rule.getKey().toString(), Severity.MAJOR, file.getKey(), RESOLUTION_FIXED, STATUS_RESOLVED, "the message", + MessageFormattingUtils.dbMessageFormattingListToWs(List.of(MESSAGE_FORMATTING)), "10min", simon.getLogin(), "John", 42, "a227e508d6646b55a086ee11d63b21e9", asList("bug", "owasp"), formatDateTime(issue.getIssueCreationDate()), formatDateTime(issue.getIssueUpdateDate()), false)); } @@ -309,6 +315,7 @@ public class SearchActionTest { DbIssues.Location.newBuilder() .setComponentId(anotherFile.uuid()) .setMsg("ANOTHER FLOW MESSAGE") + .addMsgFormatting(DbIssues.MessageFormatting.newBuilder().setStart(0).setEnd(20).setType(CODE).build()) .setTextRange(DbCommons.TextRange.newBuilder() .setStartLine(1) .setEndLine(1) @@ -333,11 +340,12 @@ public class SearchActionTest { SearchWsResponse result = ws.newRequest().executeProtobuf(SearchWsResponse.class); assertThat(result.getIssuesCount()).isOne(); - assertThat(result.getIssues(0).getFlows(0).getLocationsList()).extracting(Common.Location::getComponent, Common.Location::getMsg) + assertThat(result.getIssues(0).getFlows(0).getLocationsList()).extracting(Common.Location::getComponent, Common.Location::getMsg, Common.Location::getMsgFormattingsList) .containsExactlyInAnyOrder( - tuple(file.getKey(), "FLOW MESSAGE"), - tuple(anotherFile.getKey(), "ANOTHER FLOW MESSAGE"), - tuple(file.getKey(), "FLOW MESSAGE WITHOUT FILE UUID")); + tuple(file.getKey(), "FLOW MESSAGE", List.of()), + tuple(anotherFile.getKey(), "ANOTHER FLOW MESSAGE", List.of(Common.MessageFormatting.newBuilder() + .setStart(0).setEnd(20).setType(Common.MessageFormattingType.CODE).build())), + tuple(file.getKey(), "FLOW MESSAGE WITHOUT FILE UUID", List.of())); } @Test @@ -903,7 +911,7 @@ public class SearchActionTest { assertThat(ws.newRequest() .setMultiParam("author", singletonList("unknown")) .executeProtobuf(SearchWsResponse.class).getIssuesList()) - .isEmpty(); + .isEmpty(); } @Test @@ -1773,7 +1781,8 @@ public class SearchActionTest { assertThat(def.params()).extracting("key").containsExactlyInAnyOrder( "additionalFields", "asc", "assigned", "assignees", "author", "componentKeys", "branch", "pullRequest", "createdAfter", "createdAt", "createdBefore", "createdInLast", "directories", "facets", "files", "issues", "scopes", "languages", "onComponentOnly", - "p", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "sinceLeakPeriod", "statuses", "tags", "types", "pciDss-3.2", "pciDss-4.0", "owaspAsvs-4.0", "owaspAsvsLevel", "owaspTop10", + "p", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "sinceLeakPeriod", "statuses", "tags", "types", "pciDss-3.2", "pciDss-4.0", "owaspAsvs-4.0", + "owaspAsvsLevel", "owaspTop10", "owaspTop10-2021", "sansTop25", "cwe", "sonarsourceSecurity", "timeZone", "inNewCodePeriod"); WebService.Param branch = def.param(PARAM_BRANCH); diff --git a/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/MessageFormattingUtils.java b/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/MessageFormattingUtils.java new file mode 100644 index 00000000000..14bf34e5fbc --- /dev/null +++ b/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/MessageFormattingUtils.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.ws; + +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.sonar.db.protobuf.DbIssues; +import org.sonarqube.ws.Common; + +public class MessageFormattingUtils { + + private MessageFormattingUtils() { + } + + public static List dbMessageFormattingToWs(@Nullable DbIssues.MessageFormattings dbFormattings) { + if (dbFormattings == null) { + return List.of(); + } + return dbMessageFormattingListToWs(dbFormattings.getMessageFormattingList()); + } + + public static List dbMessageFormattingListToWs(@Nullable List dbFormattings) { + if (dbFormattings == null) { + return List.of(); + } + return dbFormattings.stream() + .map(f -> Common.MessageFormatting.newBuilder() + .setStart(f.getStart()) + .setEnd(f.getEnd()) + .setType(Common.MessageFormattingType.valueOf(f.getType().name())).build()) + .collect(Collectors.toList()); + } + +} diff --git a/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/MessageFormattingUtilsTest.java b/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/MessageFormattingUtilsTest.java new file mode 100644 index 00000000000..03277d1ef41 --- /dev/null +++ b/server/sonar-webserver-ws/src/test/java/org/sonar/server/ws/MessageFormattingUtilsTest.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.ws; + +import org.junit.Test; +import org.sonar.db.protobuf.DbIssues; +import org.sonarqube.ws.Common; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MessageFormattingUtilsTest { + + @Test + public void nullFormattingShouldBeEmptyList() { + assertThat(MessageFormattingUtils.dbMessageFormattingToWs(null)).isEmpty(); + } + + @Test + public void nullFormattingListShouldBeEmptyList() { + assertThat(MessageFormattingUtils.dbMessageFormattingListToWs(null)).isEmpty(); + } + @Test + public void singleEntryShouldBeIdentical() { + DbIssues.MessageFormattings formattings = DbIssues.MessageFormattings.newBuilder() + .addMessageFormatting(DbIssues.MessageFormatting.newBuilder() + .setStart(0) + .setEnd(4) + .setType(DbIssues.MessageFormattingType.CODE) + .build()) + .build(); + + assertThat(MessageFormattingUtils.dbMessageFormattingToWs( + formattings).get(0)) + .extracting(e -> e.getStart(), e -> e.getEnd(), e -> e.getType()) + .containsExactly(0, 4, Common.MessageFormattingType.CODE); + } + +} diff --git a/sonar-ws/src/main/protobuf/ws-commons.proto b/sonar-ws/src/main/protobuf/ws-commons.proto index 08e17ad049f..a8ba062db9e 100644 --- a/sonar-ws/src/main/protobuf/ws-commons.proto +++ b/sonar-ws/src/main/protobuf/ws-commons.proto @@ -111,6 +111,7 @@ message Location { // Only when component is a file. Can be empty for a file if this is an issue global to the file. optional sonarqube.ws.commons.TextRange textRange = 2; optional string msg = 3; + repeated MessageFormatting msgFormattings = 5; } message User { @@ -183,3 +184,13 @@ enum BranchType { PULL_REQUEST = 3; BRANCH = 4; } + +message MessageFormatting { + required int32 start = 1; + required int32 end = 2; + required MessageFormattingType type = 3; +} + +enum MessageFormattingType { + CODE = 0; +} diff --git a/sonar-ws/src/main/protobuf/ws-hotspots.proto b/sonar-ws/src/main/protobuf/ws-hotspots.proto index d725326a1e0..c846f04ad9e 100644 --- a/sonar-ws/src/main/protobuf/ws-hotspots.proto +++ b/sonar-ws/src/main/protobuf/ws-hotspots.proto @@ -49,6 +49,7 @@ message SearchWsResponse { optional sonarqube.ws.commons.TextRange textRange = 14; repeated sonarqube.ws.commons.Flow flows = 15; optional string ruleKey = 16; + repeated sonarqube.ws.commons.MessageFormatting messageFormattings = 17; } } @@ -73,6 +74,7 @@ message ShowWsResponse { repeated sonarqube.ws.commons.User users = 16; optional bool canChangeStatus = 17; repeated sonarqube.ws.commons.Flow flows = 19; + repeated sonarqube.ws.commons.MessageFormatting messageFormattings = 20; } message Component { diff --git a/sonar-ws/src/main/protobuf/ws-issues.proto b/sonar-ws/src/main/protobuf/ws-issues.proto index cfa4d7a84d1..3a99824f6b4 100644 --- a/sonar-ws/src/main/protobuf/ws-issues.proto +++ b/sonar-ws/src/main/protobuf/ws-issues.proto @@ -159,6 +159,8 @@ message Issue { optional bool quickFixAvailable = 36; optional string ruleDescriptionContextKey = 37; + + repeated sonarqube.ws.commons.MessageFormatting messageFormattings = 38; } message Transitions { @@ -253,6 +255,7 @@ message Location { optional string filePath = 1; optional string message = 2; optional TextRange textRange = 3; + repeated sonarqube.ws.commons.MessageFormatting messageFormattings = 4; } message IssueLite { -- 2.39.5