Skip to content
This repository has been archived by the owner on Jan 7, 2021. It is now read-only.

Commit

Permalink
Merge pull request #33 from acopet/issue-7
Browse files Browse the repository at this point in the history
Option for creating a task on the issue comment
  • Loading branch information
acopet committed Feb 1, 2016
2 parents 1f97332 + 461dc3b commit a7e7d07
Show file tree
Hide file tree
Showing 16 changed files with 784 additions and 65 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,19 @@ Go to Stash general settings screen on SonarQube server to fill:

**Stash base URL** (sonar.stash.url): To define Stash instance.

**Stash base user** (sonar.stash.login): To define user to push violations on Stash pull-request. User must have **REPO_READ permission** for the repository.
**Please notice Stash password needs to be provided to sonar-runner through sonar.stash.password in commandline**.
**Stash base user** (sonar.stash.login): To define user to push violations on Stash pull-request. User must have **REPO_READ permission** for the repository. **Please notice Stash password needs to be provided to sonar-runner through sonar.stash.password in commandline**.

**Stash issue threshold** (sonar.stash.issue.threshold): To limit the number of issue pushed to Stash.

**Stash timeout** (sonar.stash.timeout): To timeout when Stash Rest api does not replied with expected.

**Stash reviewer approval** (sonar.stash.reviewer.approval): SonarQube is able to approve the pull-request if there is no new issue introduced by the change.
By default, this feature is deactivated: if activated, **Stash base user must have REPO_WRITE permission for the repositories.**
**Stash reviewer approval** (sonar.stash.reviewer.approval): SonarQube is able to approve the pull-request if there is no new issue introduced by the change. By default, this feature is deactivated: if activated, **Stash base user must have REPO_WRITE permission for the repositories.**

![Screenshot SonarQube plugin](resources/Sonar-plugin-approver.PNG)

**Stash tasks severity threshold** (sonar.stash.task.issue.severity.threshold): SonarQube is able to create tasks for all issues with a severity higher to the threshold. By default, this feature is deactivated (threshold: NONE).

![Screenshot SonarQube plugin](resources/Stash-plugin-task.PNG)

## How to run the plugin?

Expand All @@ -65,4 +66,4 @@ sonar-runner -Dsonar.analysis.mode=incremental -Dsonar.stash.notification -Dsona
If needed, you can reset comments published during the previous SonarQube analysis of your pull-request. Please add **sonar.stash.comments.reset** option to your SonarQube analysis. Please notice only comments linked to the **sonar.stash.login** user will be deleted. This reset will be the first action performed by the plugin.
```
sonar-runner -Dsonar.analysis.mode=incremental -Dsonar.stash.notification -Dsonar.stash.comments.reset -Dsonar.stash.project=<PROJECT> -Dsonar.stash.repository=<REPO> -Dsonar.stash.pullrequest.id=<PR_ID> -Dsonar.stash.password=<STASH_PASSWORD>...
```
```
Binary file modified resources/Sonar-plugin-configuration.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/Stash-plugin-task.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 24 additions & 10 deletions src/main/java/org/sonar/plugins/stash/StashPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import java.util.Arrays;
import java.util.List;

import org.apache.commons.collections.ListUtils;
import org.sonar.api.Properties;
import org.sonar.api.Property;
import org.sonar.api.PropertyType;
import org.sonar.api.SonarPlugin;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.rule.Severity;

@Properties({
@Property(key = StashPlugin.STASH_NOTIFICATION, name = "Stash Notification", defaultValue = "false", description = "Analysis result will be issued in Stash pull request", global = false),
Expand All @@ -21,10 +23,13 @@ public class StashPlugin extends SonarPlugin {
private static final String DEFAULT_STASH_TIMEOUT_VALUE = "10000";
private static final String DEFAULT_STASH_THRESHOLD_VALUE = "100";

private static final String CONFIG_PAGE_SUB_CATEGORY_GENERAL = "General";
private static final String CONFIG_PAGE_SUB_CATEGORY_STASH = "Stash";

public static final String SEVERITY_NONE = "NONE";

// INFO, MINOR, MAJOR, CRITICAL, BLOCKER
protected static final List<String> SEVERITY_LIST = Severity.ALL;

public static final String INCREMENTAL_MODE = "incremental";

public static final String CONTEXT_ISSUE_TYPE = "CONTEXT";
public static final String REMOVED_ISSUE_TYPE = "REMOVED";
public static final String ADDED_ISSUE_TYPE = "ADDED";
Expand All @@ -41,6 +46,7 @@ public class StashPlugin extends SonarPlugin {
public static final String STASH_ISSUE_THRESHOLD = "sonar.stash.issue.threshold";
public static final String STASH_TIMEOUT = "sonar.stash.timeout";
public static final String SONARQUBE_URL = "sonar.host.url";
public static final String STASH_TASK_SEVERITY_THRESHOLD = "sonar.stash.task.issue.severity.threshold";

@Override
public List getExtensions() {
Expand All @@ -54,32 +60,40 @@ public List getExtensions() {
PropertyDefinition.builder(STASH_URL)
.name("Stash base URL")
.description("HTTP URL of Stash instance, such as http://yourhost.yourdomain/stash")
.subCategory(CONFIG_PAGE_SUB_CATEGORY_GENERAL)
.subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH)
.onQualifiers(Qualifiers.PROJECT).build(),
PropertyDefinition.builder(STASH_LOGIN)
.name("Stash base User")
.description("User to push data on Stash instance")
.subCategory(CONFIG_PAGE_SUB_CATEGORY_GENERAL)
.subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH)
.onQualifiers(Qualifiers.PROJECT).build(),
PropertyDefinition.builder(STASH_TIMEOUT)
.name("Stash issue Timeout")
.description("Timeout when pushing a new issue to Stash (in ms)")
.subCategory(CONFIG_PAGE_SUB_CATEGORY_GENERAL)
.subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH)
.onQualifiers(Qualifiers.PROJECT)
.defaultValue(DEFAULT_STASH_TIMEOUT_VALUE).build(),
PropertyDefinition.builder(STASH_REVIEWER_APPROVAL)
.name("Stash reviewer approval")
.description("Does SonarQube approve the pull-request if there is no new issues?")
.subCategory(CONFIG_PAGE_SUB_CATEGORY_GENERAL)
.subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH)
.onQualifiers(Qualifiers.PROJECT)
.type(PropertyType.BOOLEAN)
.defaultValue("false").build(),
PropertyDefinition.builder(STASH_ISSUE_THRESHOLD)
.name("Stash issue Threshold")
.description("Threshold to limit the number of issues pushed to Stash server")
.subCategory(CONFIG_PAGE_SUB_CATEGORY_GENERAL)
.subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH)
.onQualifiers(Qualifiers.PROJECT)
.defaultValue(DEFAULT_STASH_THRESHOLD_VALUE).build());
.defaultValue(DEFAULT_STASH_THRESHOLD_VALUE).build(),
PropertyDefinition.builder(STASH_TASK_SEVERITY_THRESHOLD)
.name("Stash tasks severity threshold")
.description("Only create tasks for issues with the same or higher severity")
.type(PropertyType.SINGLE_SELECT_LIST)
.subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH)
.onQualifiers(Qualifiers.PROJECT)
.defaultValue(SEVERITY_NONE)
.options(ListUtils.sum(Arrays.asList(SEVERITY_NONE), SEVERITY_LIST)).build());
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,8 @@ public boolean canApprovePullRequest() {
public boolean resetComments() {
return settings.getBoolean(StashPlugin.STASH_RESET_COMMENTS);
}

public String getTaskIssueSeverityThreshold() {
return settings.getString(StashPlugin.STASH_TASK_SEVERITY_THRESHOLD);
}
}
65 changes: 57 additions & 8 deletions src/main/java/org/sonar/plugins/stash/StashRequestFacade.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.BatchComponent;
Expand All @@ -21,6 +23,7 @@
import org.sonar.plugins.stash.issue.StashCommentReport;
import org.sonar.plugins.stash.issue.StashDiffReport;
import org.sonar.plugins.stash.issue.StashPullRequest;
import org.sonar.plugins.stash.issue.StashTask;
import org.sonar.plugins.stash.issue.StashUser;
import org.sonar.plugins.stash.issue.collector.SonarQubeCollector;

Expand Down Expand Up @@ -134,6 +137,9 @@ public void postCommentPerIssue(String project, String repository, String pullRe
}
}

// Severity available to create a task
List<String> taskSeverities = getReportedSeverities();

for (SonarQubeIssue issue : issueReport.getIssues()) {
StashCommentReport comments = commentsByFile.get(issue.getPath());

Expand All @@ -151,15 +157,22 @@ public void postCommentPerIssue(String project, String repository, String pullRe

long line = diffReport.getLine(issue.getPath(), issue.getLine());

stashClient.postCommentLineOnPullRequest(project,
repository,
pullRequestId,
MarkdownPrinter.printIssueMarkdown(issue, sonarQubeURL),
issue.getPath(),
line,
type);
StashComment comment = stashClient.postCommentLineOnPullRequest(project,
repository,
pullRequestId,
MarkdownPrinter.printIssueMarkdown(issue, sonarQubeURL),
issue.getPath(),
line,
type);

LOGGER.debug("Comment \"{}\" has been created ({}) on file {} ({})", issue.getRule(), type, issue.getPath(), line);

// Create task linked to the comment if configured
if (taskSeverities.contains(issue.getSeverity())) {
stashClient.postTaskOnComment(issue.getMessage(), comment.getId());

LOGGER.debug("Comment \"{}\" has been linked to a Stash task", comment.getId());
}
}
}
}
Expand Down Expand Up @@ -289,7 +302,19 @@ public void resetComments(String project, String repository, String pullRequestI

// delete comment if published by the current SQ user
if (sonarUser.getId() == comment.getAuthor().getId()) {
stashClient.deletePullRequestComment(project, repository, pullRequestId, comment);

// comment contains tasks which cannot be deleted => do nothing
if (comment.containsPermanentTasks()) {
LOGGER.debug("Comment \"{}\" (path:\"{}\", line:\"{}\") CANNOT be deleted because one of its tasks is not deletable.", comment.getId(), comment.getPath(), comment.getLine());
} else {

// delete tasks linked to the current comment
for (StashTask task : comment.getTasks()) {
stashClient.deleteTaskOnComment(task);
}

stashClient.deletePullRequestComment(project, repository, pullRequestId, comment);
}
}
}

Expand All @@ -300,4 +325,28 @@ public void resetComments(String project, String repository, String pullRequestI
LOGGER.debug("Exception stack trace", e);
}
}

/**
* Get reported severities to create a task.
*/
public List<String> getReportedSeverities() {
List<String> result = new ArrayList<>();
String threshold = config.getTaskIssueSeverityThreshold();

// threshold == NONE, no severities reported
if (! StringUtils.equals(threshold, StashPlugin.SEVERITY_NONE)) {

// INFO, MINOR, MAJOR, CRITICAL, BLOCKER
boolean hit = false;
for (String severity : StashPlugin.SEVERITY_LIST) {

if (hit || StringUtils.equals(severity, threshold)) {
result.add(severity);
hit = true;
}
}
}

return result;
}
}
67 changes: 62 additions & 5 deletions src/main/java/org/sonar/plugins/stash/client/StashClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
import org.sonar.plugins.stash.exceptions.StashReportExtractionException;
import org.sonar.plugins.stash.issue.StashComment;
import org.sonar.plugins.stash.issue.StashCommentReport;
import org.sonar.plugins.stash.issue.StashDiffReport;
import org.sonar.plugins.stash.issue.StashPullRequest;
import org.sonar.plugins.stash.issue.StashDiffReport;
import org.sonar.plugins.stash.issue.StashPullRequest;
import org.sonar.plugins.stash.issue.StashTask;
import org.sonar.plugins.stash.issue.StashUser;
import org.sonar.plugins.stash.issue.collector.StashCollector;

Expand All @@ -43,6 +44,7 @@ public class StashClient {
private static final String COMMENT_PULL_REQUEST_API = COMMENTS_PULL_REQUEST_API + "/{4}?version={5}";
private static final String DIFF_PULL_REQUEST_API = PULL_REQUEST_API + "/diff";
private static final String APPROVAL_PULL_REQUEST_API = PULL_REQUEST_API + "/approve";
private static final String TASKS_API = REST_API + "tasks";

private static final String PULL_REQUEST_APPROVAL_POST_ERROR_MESSAGE = "Unable to change status of pull-request {0} #{1}. Received {2} with message {3}.";
private static final String PULL_REQUEST_GET_ERROR_MESSAGE = "Unable to retrieve pull-request {0} #{1}. Received {2} with message {3}.";
Expand All @@ -51,6 +53,8 @@ public class StashClient {
private static final String COMMENT_POST_ERROR_MESSAGE = "Unable to post a comment to {0} #{1}. Received {2} with message {3}.";
private static final String COMMENT_GET_ERROR_MESSAGE = "Unable to get comment linked to {0} #{1}. Received {2} with message {3}.";
private static final String COMMENT_DELETION_ERROR_MESSAGE = "Unable to delete comment {0} from pull-request {1} #{2}. Received {3} with message {4}.";
private static final String TASK_POST_ERROR_MESSAGE = "Unable to post a task on comment {0}. Received {1} with message {2}.";
private static final String TASK_DELETION_ERROR_MESSAGE = "Unable to delete task {0}. Received {1} with message {2}.";

public StashClient(String url, StashCredentials credentials, int stashTimeout) {
this.baseUrl = url;
Expand Down Expand Up @@ -171,9 +175,10 @@ public StashDiffReport getPullRequestDiffs(String project, String repository, St
return result;
}

public void postCommentLineOnPullRequest(String project, String repository, String pullRequestId, String message, String path, long line, String type)
public StashComment postCommentLineOnPullRequest(String project, String repository, String pullRequestId, String message, String path, long line, String type)
throws StashClientException {

StashComment result = null;

String request = MessageFormat.format(COMMENTS_PULL_REQUEST_API, baseUrl + REST_API, project, repository,
pullRequestId);

Expand Down Expand Up @@ -204,11 +209,17 @@ public void postCommentLineOnPullRequest(String project, String repository, Stri
String responseMessage = response.getStatusText();
throw new StashClientException(MessageFormat.format(COMMENT_POST_ERROR_MESSAGE, repository, pullRequestId, responseCode, responseMessage));
}
} catch (ExecutionException | TimeoutException | IOException | InterruptedException e) {

// get generated comment
result = StashCollector.extractComment(response.getResponseBody(), path, line);

} catch (ExecutionException | TimeoutException | IOException | InterruptedException | StashReportExtractionException e) {
throw new StashClientException(e);
} finally{
httpClient.close();
}

return result;
}

public StashUser getUser(String userSlug)
Expand Down Expand Up @@ -339,7 +350,53 @@ public void resetPullRequestApproval(String project, String repository, String p
httpClient.close();
}
}

public void postTaskOnComment(String message, Long commentId) throws StashClientException {
String request = baseUrl + TASKS_API;

JSONObject anchor = new JSONObject();
anchor.put("id", commentId);
anchor.put("type", "COMMENT");

JSONObject json = new JSONObject();
json.put("anchor", anchor);
json.put("text", message);

try (AsyncHttpClient httpClient = createHttpClient()) {

BoundRequestBuilder requestBuilder = httpClient.preparePost(request);
requestBuilder.setBody(json.toString());

Response response = executeRequest(requestBuilder);
int responseCode = response.getStatusCode();
if (responseCode != HttpURLConnection.HTTP_CREATED) {
String responseMessage = response.getStatusText();
throw new StashClientException(MessageFormat.format(TASK_POST_ERROR_MESSAGE, commentId, responseCode, responseMessage));
}
} catch (ExecutionException | TimeoutException | IOException | InterruptedException e) {
throw new StashClientException(e);
}
}

public void deleteTaskOnComment(StashTask task) throws StashClientException {
String request = baseUrl + TASKS_API + "/" + task.getId();

AsyncHttpClient httpClient = createHttpClient();
BoundRequestBuilder requestBuilder = httpClient.prepareDelete(request);

try {
Response response = executeRequest(requestBuilder);
int responseCode = response.getStatusCode();
if (responseCode != HttpURLConnection.HTTP_NO_CONTENT) {
String responseMessage = response.getStatusText();
throw new StashClientException(MessageFormat.format(TASK_DELETION_ERROR_MESSAGE, task.getId(), responseCode, responseMessage));
}
} catch (ExecutionException | TimeoutException | InterruptedException | IOException e) {
throw new StashClientException(e);
} finally{
httpClient.close();
}
}

Response executeRequest(final BoundRequestBuilder requestBuilder) throws InterruptedException, IOException,
ExecutionException, TimeoutException {
Expand Down
Loading

0 comments on commit a7e7d07

Please sign in to comment.