diff --git a/src/main/java/org/sonar/plugins/stash/StashIssueReportingPostJob.java b/src/main/java/org/sonar/plugins/stash/StashIssueReportingPostJob.java index 43d9cb2f..34365968 100644 --- a/src/main/java/org/sonar/plugins/stash/StashIssueReportingPostJob.java +++ b/src/main/java/org/sonar/plugins/stash/StashIssueReportingPostJob.java @@ -145,6 +145,12 @@ private void postInfoAndPRsActions( stashRequestFacade.resetPullRequestApproval(pr, stashClient); } } + + if (config.canMarkPullRequestNeedsWork()) { + if (!shouldApprovePullRequest(config.getApprovalSeverityThreshold(), issueReport)) { + stashRequestFacade.markPullRequestNeedsWork(pr, stashClient); + } + } } static boolean shouldApprovePullRequest(Optional approvalSeverityThreshold, List report) { diff --git a/src/main/java/org/sonar/plugins/stash/StashPlugin.java b/src/main/java/org/sonar/plugins/stash/StashPlugin.java index 706e7094..22f4d345 100644 --- a/src/main/java/org/sonar/plugins/stash/StashPlugin.java +++ b/src/main/java/org/sonar/plugins/stash/StashPlugin.java @@ -69,6 +69,7 @@ public enum IssueType { public static final String STASH_PASSWORD = "sonar.stash.password"; public static final String STASH_PASSWORD_ENVIRONMENT_VARIABLE = "sonar.stash.password.variable"; public static final String STASH_REVIEWER_APPROVAL = "sonar.stash.reviewer.approval"; + public static final String STASH_REVIEWER_MARK_NEEDS_WORK = "sonar.stash.reviewer.mark.needs.work"; public static final String STASH_REVIEWER_APPROVAL_SEVERITY_THRESHOLD = "sonar.stash.reviewer.approval.severity.threshold"; public static final String STASH_ISSUE_THRESHOLD = "sonar.stash.issue.threshold"; public static final String STASH_ISSUE_SEVERITY_THRESHOLD = "sonar.stash.issue.severity.threshold"; @@ -191,7 +192,14 @@ public void define(Context context) { .type(PropertyType.STRING) .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) .onQualifiers(Qualifiers.PROJECT) - .defaultValue(DEFAULT_STASH_EXCLUDE_RULES).build() + .defaultValue(DEFAULT_STASH_EXCLUDE_RULES).build(), + PropertyDefinition.builder(STASH_REVIEWER_MARK_NEEDS_WORK) + .name("Stash reviewer marking NEEDS WORK") + .description("Does SonarQube mark the pull-request NEEDS WORK if there are new issues?") + .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) + .onQualifiers(Qualifiers.PROJECT) + .type(PropertyType.BOOLEAN) + .defaultValue("false").build() ); } } diff --git a/src/main/java/org/sonar/plugins/stash/StashPluginConfiguration.java b/src/main/java/org/sonar/plugins/stash/StashPluginConfiguration.java index 86d405b5..cf0451be 100644 --- a/src/main/java/org/sonar/plugins/stash/StashPluginConfiguration.java +++ b/src/main/java/org/sonar/plugins/stash/StashPluginConfiguration.java @@ -89,6 +89,9 @@ public int getStashTimeout() { public boolean canApprovePullRequest() { return settings.getBoolean(StashPlugin.STASH_REVIEWER_APPROVAL); } + public boolean canMarkPullRequestNeedsWork() { + return settings.getBoolean(StashPlugin.STASH_REVIEWER_MARK_NEEDS_WORK); + } public boolean resetComments() { return settings.getBoolean(StashPlugin.STASH_RESET_COMMENTS); diff --git a/src/main/java/org/sonar/plugins/stash/StashRequestFacade.java b/src/main/java/org/sonar/plugins/stash/StashRequestFacade.java index d03dd51d..094de910 100644 --- a/src/main/java/org/sonar/plugins/stash/StashRequestFacade.java +++ b/src/main/java/org/sonar/plugins/stash/StashRequestFacade.java @@ -93,6 +93,24 @@ public void approvePullRequest(PullRequestRef pr, StashClient stashClient) { } } + /** + * Mark pull-request needs work + */ + public void markPullRequestNeedsWork(PullRequestRef pr, StashClient stashClient) { + try { + stashClient.markPullRequestNeedsWork(pr); + + // squid:S2629 : no evaluation required if the logging level is not activated + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Pull-request {} ({}/{}) marked NEEDS WORK by user \"{}\"", + pr.pullRequestId(), pr.project(), pr.repository(), stashClient.getLogin()); + } + + } catch (StashClientException e) { + LOGGER.error("Unable to mark pull-request NEEDS WORK", e); + } + } + /** * Reset pull-request approval */ diff --git a/src/main/java/org/sonar/plugins/stash/client/StashClient.java b/src/main/java/org/sonar/plugins/stash/client/StashClient.java index a46260f2..a925c2b3 100644 --- a/src/main/java/org/sonar/plugins/stash/client/StashClient.java +++ b/src/main/java/org/sonar/plugins/stash/client/StashClient.java @@ -64,10 +64,12 @@ public class StashClient implements AutoCloseable { private static final String API_ONE_PR_ALL_COMMENTS = API_ONE_PR + "/comments"; private static final String API_ONE_PR_DIFF = API_ONE_PR + "/diff?withComments=true"; private static final String API_ONE_PR_APPROVAL = API_ONE_PR + "/approve"; + private static final String API_ONE_PR_NEEDS_WORK = API_ONE_PR + "/participants/{4}"; private static final String API_ONE_PR_COMMENT_PATH = API_ONE_PR + "/comments?path={4}&start={5,number,#}"; private static final String API_ONE_PR_ONE_COMMENT = API_ONE_PR_ALL_COMMENTS + "/{4}?version={5}"; + private static final String PULL_REQUEST_MARK_NEEDS_WORK_PUT_ERROR_MESSAGE = "Unable to set NEEDS WORK status of pull-request {0}"; private static final String PULL_REQUEST_APPROVAL_POST_ERROR_MESSAGE = "Unable to change status of pull-request {0}" + " #{1,number,#}."; private static final String PULL_REQUEST_GET_ERROR_MESSAGE = "Unable to retrieve pull-request {0} #{1,number,#}."; @@ -278,6 +280,21 @@ public void resetPullRequestApproval(PullRequestRef pr) throws StashClientExcept MessageFormat.format(PULL_REQUEST_APPROVAL_POST_ERROR_MESSAGE, pr.repository(), pr.pullRequestId())); } + public void markPullRequestNeedsWork(PullRequestRef pr ) throws StashClientException { + String request = MessageFormat.format(API_ONE_PR_NEEDS_WORK, + baseUrl, + pr.project(), + pr.repository(), + pr.pullRequestId(), + credentials.getUserSlug()); + JsonObject json = new JsonObject(); + json.put("status", "NEEDS_WORK"); + put(request, + json, + MessageFormat + .format(PULL_REQUEST_MARK_NEEDS_WORK_PUT_ERROR_MESSAGE, pr.repository(), pr.pullRequestId())); + } + public void postTaskOnComment(String message, Long commentId) throws StashClientException { String request = baseUrl + TASKS_API; diff --git a/src/main/java/org/sonar/plugins/stash/issue/MarkdownPrinter.java b/src/main/java/org/sonar/plugins/stash/issue/MarkdownPrinter.java index 5aa27121..20b9e454 100644 --- a/src/main/java/org/sonar/plugins/stash/issue/MarkdownPrinter.java +++ b/src/main/java/org/sonar/plugins/stash/issue/MarkdownPrinter.java @@ -147,7 +147,13 @@ private String fileNameList(List issues) { issues.sort(issueFormatComparator); for (PostJobIssue issue: issues.subList(0, Math.min(includeFilesInOverview, issues.size()))) { - names.add(String.format("%s:%s", issuePathResolver.getIssuePath(issue), issue.line())); + String path = issuePathResolver.getIssuePath(issue); + Integer line = issue.line(); + if (line == null) { + names.add(path); + } else { + names.add(String.format("%s:%s", path, line)); + } } if (issues.size() > includeFilesInOverview) { names.add("..."); @@ -171,7 +177,8 @@ private static String link(String title, String target) { .comparing(i -> issuePathResolver.getIssuePath(i).length()); private Comparator issueLine = Comparator - .comparing(PostJobIssue::line); + // -1 sorts before all issues with lines + .comparing(issue -> firstNonNull(issue.line(), -1)); private Comparator fileNameLexical = Comparator .comparing(i -> issuePathResolver.getIssuePath(i)); @@ -179,5 +186,16 @@ private static String link(String title, String target) { private Comparator issueFormatComparator = fileNameLength .thenComparing(issueLine) - .thenComparing(fileNameLexical); + .thenComparing(fileNameLexical) + ; + + @SafeVarargs + private static T firstNonNull(T... args) { + for (T t: args) { + if (t != null) { + return t; + } + } + throw new IllegalStateException("At least one of the arguments should have been non-null"); + } } diff --git a/src/test/java/org/sonar/plugins/stash/StashRequestFacadeTest.java b/src/test/java/org/sonar/plugins/stash/StashRequestFacadeTest.java index 1f291fb1..c29ade67 100755 --- a/src/test/java/org/sonar/plugins/stash/StashRequestFacadeTest.java +++ b/src/test/java/org/sonar/plugins/stash/StashRequestFacadeTest.java @@ -771,6 +771,12 @@ public void testApprovePullRequest() throws Exception { verify(stashClient, times(1)).approvePullRequest(pr); } + @Test + public void testmarkPullRequestNeedsWork() throws Exception { + + myFacade.markPullRequestNeedsWork(pr, stashClient); + verify(stashClient, times(1)).markPullRequestNeedsWork(pr); + } @Test public void testApprovePullRequestException() throws Exception { diff --git a/src/test/java/org/sonar/plugins/stash/issue/MarkdownPrinterTest.java b/src/test/java/org/sonar/plugins/stash/issue/MarkdownPrinterTest.java index 384ea9c0..2c66a09d 100755 --- a/src/test/java/org/sonar/plugins/stash/issue/MarkdownPrinterTest.java +++ b/src/test/java/org/sonar/plugins/stash/issue/MarkdownPrinterTest.java @@ -157,6 +157,38 @@ public void testPrintReportMarkdownWithIssueLimitation() { assertEquals(reportString, issueReportMarkdown); } + @Test + public void testPrintReportMarkdownWithFileWideIssues() { + PostJobIssue issueWithoutLine = new DefaultIssue().setKey("key36") + .setSeverity(Severity.CRITICAL) + .setMessage("messageCritical") + .setRuleKey(RuleKey.of("RepoCritical", "RuleCritical")) + .setInputComponent(new DefaultInputFile("foo2", "scripts/file2.example")) + .setLine(null); + report.add(issueWithoutLine); + printer = new MarkdownPrinter(100, SONAR_URL, 100, new DummyIssuePathResolver()); + String issueReportMarkdown = printer.printReportMarkdown(report); + String reportString = "## SonarQube analysis Overview\n" + + "| Total New Issues | 6 |\n" + + "|-----------------|------|\n" + + "| BLOCKER | 1 |\n" + + "| CRITICAL | 2 |\n" + + "| MAJOR | 3 |\n" + + "| MINOR | 0 |\n" + + "| INFO | 0 |\n\n\n" + + "| Issues list |\n" + + "|-------------|\n" + + "| *BLOCKER* - messageBlocker [[RepoBlocker:RuleBlocker](sonarqube/URL/coding_rules#rule_key=RepoBlocker:RuleBlocker)] |\n" + + "|    *Files: scripts/file1.example:1* |\n" + + "| *CRITICAL* - messageCritical [[RepoCritical:RuleCritical](sonarqube/URL/coding_rules#rule_key=RepoCritical:RuleCritical)] |\n" + + "|    *Files: scripts/file2.example, scripts/file2.example:1* |\n" + + "| *MAJOR* - messageMajor [[RepoMajor:RuleMajor](sonarqube/URL/coding_rules#rule_key=RepoMajor:RuleMajor)] |\n" + + "|    *Files: scripts/file3.example:1, scripts/file3.example:15, scripts/tests/file3.example:5* |\n"; + + assertEquals(reportString, issueReportMarkdown); + + } + @Test public void testPrintEmptyReportMarkdown() { report = new ArrayList<>();