diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotWsResponseFormatter.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotWsResponseFormatter.java index 99bca6ad5fdf..e204b7c7f1cf 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotWsResponseFormatter.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotWsResponseFormatter.java @@ -114,13 +114,19 @@ private List mapHotspots(SearchResponseData s .setSecurityCategory(sqCategory.getKey()) .setVulnerabilityProbability(sqCategory.getVulnerability().name()) .setRuleKey(hotspot.getRuleKey().toString()); + ofNullable(hotspot.getSeverity()).ifPresent(builder::setSeverity); ofNullable(hotspot.getStatus()).ifPresent(builder::setStatus); builder.setStatusMarkedBy(nullToEmpty(searchResponseData.getStatusMarkedBy(hotspot.getKey()))); ofNullable(hotspot.getResolution()).ifPresent(builder::setResolution); + ofNullable(hotspot.getAssigneeUuid()).ifPresent(builder::setAssignee); + ofNullable(searchResponseData.getUserByUuid(hotspot.getAssigneeUuid())).ifPresent(user -> {String assignedTo = user.getName() != null && !user.getName().isBlank() ? user.getName() : user.getLogin();builder.setAssignedTo(assignedTo); + ofNullable(searchResponseData.getAssignedDate(hotspot.getKey())).ifPresent(date -> builder.setAssignedDate(formatDateTime(new Date(date))));}); + boolean hasActiveException = "REVIEWED".equals(hotspot.getStatus()) && "EXCEPTION".equals(hotspot.getResolution()); + if (hasActiveException) {ofNullable(hotspot.getIssueResolutionExpiresAt()).ifPresent(expiresAt -> builder.setExceptionExpiryDate(formatDateTime(new Date(expiresAt)))); + ofNullable(searchResponseData.getExceptionReason(hotspot.getKey())).ifPresent(reason -> builder.setExceptionReason(reason));} 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())); builder.setUpdateDate(formatDateTime(hotspot.getIssueUpdateDate())); @@ -190,6 +196,8 @@ static final class SearchResponseData { private final Set updatableComments = new HashSet<>(); private final Map usersByUuid = new HashMap<>(); private final Map statusMarkedByByIssueKey = new HashMap<>(); + private final Map exceptionReasonByIssueKey = new HashMap<>(); + private final Map assignedDateByIssueKey = new HashMap<>(); SearchResponseData(Paging paging, List hotspots) { this.paging = paging; @@ -220,6 +228,45 @@ public void addBranches(List branchDtos) { } } + void addExceptionReasons(@Nullable List changes) { + if (changes == null) { + return; + } + changes.stream() + .filter(c -> IssueChangeDto.TYPE_EXCEPTION_REASON.equals(c.getChangeType())) + .forEach(c -> exceptionReasonByIssueKey.merge(c.getIssueKey(), c, + (existing, current) -> + current.getIssueChangeCreationDate() > existing.getIssueChangeCreationDate() + ? current + : existing + )); + } + + @Nullable + String getExceptionReason(String issueKey) { + IssueChangeDto dto = exceptionReasonByIssueKey.get(issueKey); + return dto == null ? null : dto.getChangeData(); + } + + void addAssignedDates(@Nullable List changes) { + if (changes == null) { + return; + } + changes.stream() + .filter(c -> IssueChangeDto.TYPE_FIELD_CHANGE.equals(c.getChangeType())) + .filter(c -> c.getChangeData() != null && c.getChangeData().contains("assignee")) + .forEach(c -> assignedDateByIssueKey.merge( + c.getIssueKey(), + c.getIssueChangeCreationDate(), + Math::max + )); + } + + @Nullable + Long getAssignedDate(String issueKey) { + return assignedDateByIssueKey.get(issueKey); + } + public BranchDto getBranch(String branchUuid) { return branchesByBranchUuid.get(branchUuid); } 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 0a991a7042c9..9dc9d3cdd2d5 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 @@ -209,8 +209,20 @@ private void loadComments( Collector collector,DbSession dbSession, SearchRespon List comments = dbClient.issueChangeDao() .selectByTypeAndIssueKeys(dbSession, collector.getIssueKeys(), IssueChangeDto.TYPE_COMMENT); result.setComments(comments); - comments.stream().filter(c -> c.getUserUuid() != null) + List changes = dbClient.issueChangeDao() + .selectByIssueKeys(dbSession, collector.getIssueKeys()); + result.addExceptionReasons(changes); + result.addAssignedDates(changes); + comments.stream() + .filter(c -> c.getUserUuid() != null) .forEach(comment -> loadComment(collector, result, comment)); + changes.stream() + .filter(c -> c.getUserUuid() != null) + .forEach(change -> collector.addUserUuids(singletonList(change.getUserUuid()))); + result.getHotspots().stream() + .map(IssueDto::getAssigneeUuid) + .filter(Objects::nonNull) + .forEach(uuid -> collector.addUserUuids(singletonList(uuid))); result.addUsers(dbClient.userDao().selectByUuids(dbSession, collector.getUserUuids())); } diff --git a/sonar-ws/src/main/protobuf/ws-hotspots.proto b/sonar-ws/src/main/protobuf/ws-hotspots.proto index 6ae29cd31e9f..e81a59130d0e 100644 --- a/sonar-ws/src/main/protobuf/ws-hotspots.proto +++ b/sonar-ws/src/main/protobuf/ws-hotspots.proto @@ -53,6 +53,11 @@ message SearchWsResponse { optional string cveId = 18; repeated sonarqube.ws.commons.Comment comments = 19; optional string statusMarkedBy = 20; + optional string severity = 21; + optional string exceptionExpiryDate = 22; + optional string exceptionReason = 23; + optional string assignedTo = 24; + optional string assignedDate = 25; } }