diff --git a/.gitignore b/.gitignore index 1190b5945..fec6c5b73 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,8 @@ *.iml #Project libs -/sonarqube-lib/ \ No newline at end of file +/sonarqube-lib/ +*.classpath +*.project +.settings/ +bin/ diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPlugin.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPlugin.java index adcf2f739..8007db13b 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPlugin.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPlugin.java @@ -29,6 +29,10 @@ import com.github.mc1arke.sonarqube.plugin.scanner.CommunityProjectPullRequestsLoader; import com.github.mc1arke.sonarqube.plugin.server.CommunityBranchFeatureExtension; import com.github.mc1arke.sonarqube.plugin.server.CommunityBranchSupportDelegate; + +import java.util.Arrays; +import java.util.stream.Collectors; + import org.sonar.api.CoreProperties; import org.sonar.api.Plugin; import org.sonar.api.PropertyType; @@ -37,6 +41,7 @@ import org.sonar.api.resources.Qualifiers; import org.sonar.core.config.PurgeConstants; import org.sonar.core.extension.CoreExtension; +import org.sonarqube.ws.Common.Severity; /** * @author Michael Clarke @@ -121,10 +126,18 @@ public void load(CoreExtension.Context context) { PropertyDefinition.builder(PullRequestBuildStatusDecorator.PULL_REQUEST_FILE_COMMENT_ENABLED).category(PULL_REQUEST_CATEGORY_LABEL).subCategory(GENERAL) .onQualifiers(Qualifiers.PROJECT).name("Enable file comment").description("This enables commenting (if implemented).").type(PropertyType.BOOLEAN) .defaultValue("true").build(), + + PropertyDefinition.builder(PullRequestBuildStatusDecorator.PULL_REQUEST_COMMENTS_MIN_SEVERITY).category(PULL_REQUEST_CATEGORY_LABEL).subCategory(GENERAL) + .onQualifiers(Qualifiers.PROJECT).name("Min Comment Severity").description("Issues below this level are not attached as file comments.") + .type(PropertyType.SINGLE_SELECT_LIST).options(Arrays.stream(Severity.values()).map(Severity::name).collect(Collectors.toList())).build(), PropertyDefinition.builder(PullRequestBuildStatusDecorator.PULL_REQUEST_DELETE_COMMENTS_ENABLED).category(PULL_REQUEST_CATEGORY_LABEL).subCategory(GENERAL) .onQualifiers(Qualifiers.PROJECT).name("Enable deleting comments").description("This cleans up the comments from previous runs (if implemented).") .type(PropertyType.BOOLEAN).defaultValue("false").build(), + + PropertyDefinition.builder(PullRequestBuildStatusDecorator.PULL_REQUEST_COMPACT_COMMENTS_ENABLED).category(PULL_REQUEST_CATEGORY_LABEL).subCategory(GENERAL) + .onQualifiers(Qualifiers.PROJECT).name("Use compact file comments").description("Uses a compact form of the file comments.").type(PropertyType.BOOLEAN) + .defaultValue("true").build(), PropertyDefinition.builder(BitbucketServerPullRequestDecorator.PULL_REQUEST_BITBUCKET_URL).category(PULL_REQUEST_CATEGORY_LABEL).subCategory(BITBUCKET_INTEGRATION_SUBCATEGORY_LABEL) .onQualifiers(Qualifiers.PROJECT).name("URL for Bitbucket (Server or Cloud) instance").description("Example: http://bitbucket.local").type(PropertyType.STRING).build(), @@ -176,7 +189,12 @@ public void load(CoreExtension.Context context) { .name("Repository Slug for the Gitlab (Server or Cloud) instance") .description("The repository slug can be either in the form of user/repo or it can be the Project ID") .type(PropertyType.STRING) - .build() + .build(), + + PropertyDefinition.builder(GitlabServerPullRequestDecorator.PULLREQUEST_CAN_FAIL_PIPELINE_ENABLED).category(PULL_REQUEST_CATEGORY_LABEL).subCategory(GITLAB_INTEGRATION_SUBCATEGORY_LABEL) + .onQualifiers(Qualifiers.PROJECT).name("Fail pipeline if gate not reached").description("Fail the pipeline if the Qualitiy Gate passed succesfully.").type(PropertyType.BOOLEAN) + .defaultValue("true").build() + ); } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetails.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetails.java index d5c619416..8d3fbd3cf 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetails.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetails.java @@ -184,25 +184,35 @@ public String createAnalysisSummary(FormatterFactory formatterFactory) { return formatterFactory.documentFormatter().format(document, formatterFactory); } - public String createAnalysisIssueSummary(PostAnalysisIssueVisitor.ComponentIssue componentIssue, FormatterFactory formatterFactory) { + public String createAnalysisIssueSummary(PostAnalysisIssueVisitor.ComponentIssue componentIssue, FormatterFactory formatterFactory, boolean compact) { final DefaultIssue issue = componentIssue.getIssue(); String baseImageUrl = getBaseImageUrl(); - Long effort = issue.effortInMinutes(); - Node effortNode = (null == effort ? new Text("") : new Paragraph(new Text(String.format("**Duration (min):** %s", effort)))); - - String resolution = issue.resolution(); - Node resolutionNode = (StringUtils.isBlank(resolution) ? new Text("") : new Paragraph(new Text(String.format("**Resolution:** %s ", resolution)))); - - Document document = new Document( - new Paragraph(new Text(String.format("**Type:** %s ", issue.type().name())), new Image(issue.type().name(), String.format("%s/checks/IssueType/%s.svg?sanitize=true", baseImageUrl, issue.type().name().toLowerCase()))), - new Paragraph(new Text(String.format("**Severity:** %s ", issue.severity())), new Image(issue.severity(), String.format("%s/checks/Severity/%s.svg?sanitize=true", baseImageUrl, issue.severity().toLowerCase()))), - new Paragraph(new Text(String.format("**Message:** %s", issue.getMessage()))), - effortNode, - resolutionNode, - new Link(publicRootURL + "/project/issues?id=" + URLEncoder.encode(project.getKey()) + "&pullRequest=" + branchDetails.getBranchName() + "&issues=" + issue.key() + "&open=" + issue.key(), new Text("View in SonarQube")) - ); + Document document; + if (compact) { + document = new Document( + new Image(issue.type().name(), String.format("%s/checks/IssueType/%s.svg?sanitize=true", baseImageUrl, issue.type().name().toLowerCase())), + new Image(issue.severity(), String.format("%s/checks/Severity/%s.svg?sanitize=true", baseImageUrl, issue.severity().toLowerCase())), + new Link(publicRootURL + "/project/issues?id=" + URLEncoder.encode(project.getKey()) + "&pullRequest=" + branchDetails.getBranchName() + "&issues=" + issue.key() + "&open=" + issue.key(), new Text(issue.getRuleKey().rule())), + new Text(String.format(": %s", issue.getMessage())) + ); + + } else { + Long effort = issue.effortInMinutes(); + Node effortNode = (null == effort ? new Text("") : new Paragraph(new Text(String.format("**Duration (min):** %s", effort)))); + + String resolution = issue.resolution(); + Node resolutionNode = (StringUtils.isBlank(resolution) ? new Text("") : new Paragraph(new Text(String.format("**Resolution:** %s ", resolution)))); + document = new Document( + new Paragraph(new Text(String.format("**Type:** %s ", issue.type().name())), new Image(issue.type().name(), String.format("%s/checks/IssueType/%s.svg?sanitize=true", baseImageUrl, issue.type().name().toLowerCase()))), + new Paragraph(new Text(String.format("**Severity:** %s ", issue.severity())), new Image(issue.severity(), String.format("%s/checks/Severity/%s.svg?sanitize=true", baseImageUrl, issue.severity().toLowerCase()))), + new Text(String.format("**Message:** %s %s", issue.getRuleKey(), issue.getMessage())), + effortNode, + resolutionNode, + new Link(publicRootURL + "/project/issues?id=" + URLEncoder.encode(project.getKey()) + "&pullRequest=" + branchDetails.getBranchName() + "&issues=" + issue.key() + "&open=" + issue.key(), new Text("View in SonarQube")) + ); + } return formatterFactory.documentFormatter().format(document, formatterFactory); } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PullRequestBuildStatusDecorator.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PullRequestBuildStatusDecorator.java index 3b11e1d46..d646f7d28 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PullRequestBuildStatusDecorator.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PullRequestBuildStatusDecorator.java @@ -26,6 +26,10 @@ public interface PullRequestBuildStatusDecorator { String PULL_REQUEST_DELETE_COMMENTS_ENABLED = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.delete.comments.enabled"; + String PULL_REQUEST_COMMENTS_MIN_SEVERITY = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.comment.minSeverity"; + + String PULL_REQUEST_COMPACT_COMMENTS_ENABLED = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.comments.compact"; + String name(); void decorateQualityGateStatus(AnalysisDetails analysisDetails); diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/server/BitbucketServerPullRequestDecorator.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/server/BitbucketServerPullRequestDecorator.java index 1d5f3a4b6..e6a9018fa 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/server/BitbucketServerPullRequestDecorator.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/server/BitbucketServerPullRequestDecorator.java @@ -123,6 +123,7 @@ public void decorateQualityGateStatus(AnalysisDetails analysisDetails) { final boolean summaryCommentEnabled = Boolean.parseBoolean(getMandatoryProperty(PULL_REQUEST_COMMENT_SUMMARY_ENABLED, configuration)); final boolean fileCommentEnabled = Boolean.parseBoolean(getMandatoryProperty(PULL_REQUEST_FILE_COMMENT_ENABLED, configuration)); final boolean deleteCommentsEnabled = Boolean.parseBoolean(getMandatoryProperty(PULL_REQUEST_DELETE_COMMENTS_ENABLED, configuration)); + final boolean compactCommentsEnabled = Boolean.parseBoolean(getMandatoryProperty(PULL_REQUEST_COMPACT_COMMENTS_ENABLED, configuration)); final String commentUrl; final String activityUrl; @@ -155,7 +156,7 @@ public void decorateQualityGateStatus(AnalysisDetails analysisDetails) { List componentIssues = analysisDetails.getPostAnalysisIssueVisitor().getIssues().stream().filter(i -> OPEN_ISSUE_STATUSES.contains(i.getIssue().status())).collect(Collectors.toList()); for (PostAnalysisIssueVisitor.ComponentIssue componentIssue : componentIssues) { final DefaultIssue issue = componentIssue.getIssue(); - String analysisIssueSummary = analysisDetails.createAnalysisIssueSummary(componentIssue, new MarkdownFormatterFactory()); + String analysisIssueSummary = analysisDetails.createAnalysisIssueSummary(componentIssue, new MarkdownFormatterFactory(), compactCommentsEnabled); String issuePath = analysisDetails.getSCMPathForIssue(componentIssue).orElse(StringUtils.EMPTY); int issueLine = issue.getLine() != null ? issue.getLine() : 0; String issueType = getIssueType(diffPage, issuePath, issueLine); diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecorator.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecorator.java index 6ba69581b..48173a395 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecorator.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecorator.java @@ -19,6 +19,7 @@ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -39,9 +40,11 @@ import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PostAnalysisIssueVisitor; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PullRequestBuildStatusDecorator; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.response.Commit; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.response.DiffRefs; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.response.Discussion; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.response.MergeRequest; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.response.Note; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.response.Position; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.response.User; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.markup.MarkdownFormatterFactory; import org.apache.commons.io.IOUtils; @@ -53,6 +56,7 @@ import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; @@ -63,9 +67,12 @@ import org.sonar.api.platform.Server; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; +import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.ConfigurationRepository; import org.sonar.ce.task.projectanalysis.scm.Changeset; +import org.sonar.ce.task.projectanalysis.scm.ScmInfo; import org.sonar.ce.task.projectanalysis.scm.ScmInfoRepository; +import org.sonarqube.ws.Common.Severity; public class GitlabServerPullRequestDecorator implements PullRequestBuildStatusDecorator { @@ -76,7 +83,7 @@ public class GitlabServerPullRequestDecorator implements PullRequestBuildStatusD public static final String PULLREQUEST_GITLAB_URL = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.gitlab.url"; public static final String PULLREQUEST_GITLAB_TOKEN = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.gitlab.token"; public static final String PULLREQUEST_GITLAB_REPOSITORY_SLUG = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.gitlab.repositorySlug"; - + public static final String PULLREQUEST_CAN_FAIL_PIPELINE_ENABLED = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.gitlab.canFailPipeline"; private final ConfigurationRepository configurationRepository; private final Server server; @@ -91,108 +98,47 @@ public GitlabServerPullRequestDecorator(Server server, ConfigurationRepository c @Override public void decorateQualityGateStatus(AnalysisDetails analysis) { - LOGGER.info("starting to analyze with " + analysis.toString()); - String revision = analysis.getCommitSha(); + LOGGER.info("starting to analyze with {}", analysis); try { - Configuration configuration = configurationRepository.getConfiguration(); - final String hostURL = getMandatoryProperty(PULLREQUEST_GITLAB_URL, configuration); - final String apiToken = getMandatoryProperty(PULLREQUEST_GITLAB_TOKEN, configuration); - final String repositorySlug = getMandatoryProperty(PULLREQUEST_GITLAB_REPOSITORY_SLUG, configuration); - final String pullRequestId = analysis.getBranchName(); - - final boolean summaryCommentEnabled = Boolean.parseBoolean(getMandatoryProperty(PULL_REQUEST_COMMENT_SUMMARY_ENABLED, configuration)); - final boolean fileCommentEnabled = Boolean.parseBoolean(getMandatoryProperty(PULL_REQUEST_FILE_COMMENT_ENABLED, configuration)); - final boolean deleteCommentsEnabled = Boolean.parseBoolean(getMandatoryProperty(PULL_REQUEST_DELETE_COMMENTS_ENABLED, configuration)); - - final String restURL = String.format("%s/api/v4", hostURL); - final String userURL = restURL + "/user"; - final String projectURL = restURL + String.format("/projects/%s", URLEncoder.encode(repositorySlug, StandardCharsets.UTF_8.name())); - final String statusUrl = projectURL + String.format("/statuses/%s", revision); - final String mergeRequestURl = projectURL + String.format("/merge_requests/%s", pullRequestId); - final String prCommitsURL = mergeRequestURl + "/commits"; - final String mergeRequestDiscussionURL = mergeRequestURl + "/discussions"; - - - LOGGER.info(String.format("Status url is: %s ", statusUrl)); - LOGGER.info(String.format("PR commits url is: %s ", prCommitsURL)); - LOGGER.info(String.format("MR discussion url is: %s ", mergeRequestDiscussionURL)); - LOGGER.info(String.format("User url is: %s ", userURL)); - - Map headers = new HashMap<>(); - headers.put("PRIVATE-TOKEN", apiToken); - headers.put("Accept", "application/json"); - - User user = getSingle(userURL, headers, User.class); - LOGGER.info(String.format("Using user: %s ", user.getUsername())); - - List commits = getPagedList(prCommitsURL, headers, true, new TypeReference>() { - }).stream().map(Commit::getId).collect(Collectors.toList()); - MergeRequest mergeRequest = getSingle(mergeRequestURl, headers, MergeRequest.class); - - List discussions = getPagedList(mergeRequestDiscussionURL, headers, deleteCommentsEnabled, new TypeReference>() { - }); - - LOGGER.info(String.format("Discussions in MR: %s ", discussions - .stream() - .map(Discussion::getId) - .collect(Collectors.joining(", ")))); - + final String pullRequestId = analysis.getBranchName(); + User user = getUser(); + List discussions = getOwnDiscussions(user, pullRequestId); + final boolean deleteCommentsEnabled = getMandatoryBooleanProperty(PULL_REQUEST_DELETE_COMMENTS_ENABLED); + + Note summaryComment = null; + Map lineComments = new HashMap<>(); for (Discussion discussion : discussions) { for (Note note : discussion.getNotes()) { - if (!note.isSystem() && note.getAuthor() != null && note.getAuthor().getUsername().equals(user.getUsername())) { - //delete only our own comments - deleteCommitDiscussionNote(mergeRequestDiscussionURL + String.format("/%s/notes/%s", - discussion.getId(), - note.getId()), - headers, deleteCommentsEnabled); + Position position = note.getPosition(); + if ("DiffNote".equals(note.getType()) && position!=null && position.getNewLine()!=null && position.getNewLine().trim().length() > 0) { + lineComments.put(position.getNewLine(), note); + } else if ("DiscussionNote".equals(note.getType())) { + if (summaryComment==null) { + summaryComment = note; + } else if (note.getUpdatedAt()==null || note.getUpdatedAt().before(summaryComment.getUpdatedAt())) { + if (deleteCommentsEnabled) { + deleteCommitDiscussionNote(pullRequestId, discussion.getId(), note.getId()); + } + } else { + summaryComment = note; + if (deleteCommentsEnabled) { + deleteCommitDiscussionNote(pullRequestId, discussion.getId(), summaryComment.getId()); + } + } + + } else if (deleteCommentsEnabled) { + deleteCommitDiscussionNote(pullRequestId, discussion.getId(), note.getId()); } - } + } } - - QualityGate.Condition newCoverageCondition = analysis.findQualityGateCondition(CoreMetrics.NEW_COVERAGE_KEY) - .orElseThrow(() -> new IllegalStateException("Could not find New Coverage Condition in analysis")); - String coverageValue = newCoverageCondition.getStatus().equals(QualityGate.EvaluationStatus.NO_VALUE) ? "0" : newCoverageCondition.getValue(); - - - List openIssues = analysis.getPostAnalysisIssueVisitor().getIssues().stream().filter(i -> OPEN_ISSUE_STATUSES.contains(i.getIssue().getStatus())).collect(Collectors.toList()); - - String summaryComment = analysis.createAnalysisSummary(new MarkdownFormatterFactory()); - List summaryContentParams = Collections.singletonList(new BasicNameValuePair("body", summaryComment)); - - postStatus(statusUrl, headers, analysis, coverageValue, true); - - postCommitComment(mergeRequestDiscussionURL, headers, summaryContentParams, summaryCommentEnabled); - - for (PostAnalysisIssueVisitor.ComponentIssue issue : openIssues) { - String path = analysis.getSCMPathForIssue(issue).orElse(null); - if (path != null && issue.getIssue().getLine() != null) { - //only if we have a path and line number - String fileComment = analysis.createAnalysisIssueSummary(issue, new MarkdownFormatterFactory()); - - if (scmInfoRepository.getScmInfo(issue.getComponent()) - .filter(i -> i.hasChangesetForLine(issue.getIssue().getLine())) - .map(i -> i.getChangesetForLine(issue.getIssue().getLine())) - .map(Changeset::getRevision) - .filter(commits::contains) - .isPresent()) { - //only if the change is on a commit, that belongs to this MR - - List fileContentParams = Arrays.asList( - new BasicNameValuePair("body", fileComment), - new BasicNameValuePair("position[base_sha]", mergeRequest.getDiffRefs().getBaseSha()), - new BasicNameValuePair("position[start_sha]", mergeRequest.getDiffRefs().getStartSha()), - new BasicNameValuePair("position[head_sha]", mergeRequest.getDiffRefs().getHeadSha()), - new BasicNameValuePair("position[old_path]", path), - new BasicNameValuePair("position[new_path]", path), - new BasicNameValuePair("position[new_line]", String.valueOf(issue.getIssue().getLine())), - new BasicNameValuePair("position[position_type]", "text")); - - postCommitComment(mergeRequestDiscussionURL, headers, fileContentParams, fileCommentEnabled); - } else { - LOGGER.info(String.format("Skipping %s:%d since the commit does not belong to the MR", path, issue.getIssue().getLine())); - } - } + doPipeline(analysis); + doSummary(analysis, summaryComment); + doFileComments(analysis, lineComments); + if (deleteCommentsEnabled) { + for( Note note: lineComments.values()) { + deleteCommitDiscussionNote(pullRequestId, note.getDiscussionId(), note.getId()); + } } } catch (IOException ex) { throw new IllegalStateException("Could not decorate Pull Request on Gitlab Server", ex); @@ -200,8 +146,165 @@ public void decorateQualityGateStatus(AnalysisDetails analysis) { } - private X getSingle(String userURL, Map headers, Class type) throws IOException { - HttpGet httpGet = new HttpGet(userURL); + + protected void doFileComments(AnalysisDetails analysis, Map lineNotes) throws IOException { + // Issues + final boolean fileCommentEnabled = getMandatoryBooleanProperty(PULL_REQUEST_FILE_COMMENT_ENABLED); + final boolean compactCommentsEnabled = getMandatoryBooleanProperty(PULL_REQUEST_COMPACT_COMMENTS_ENABLED); + if (fileCommentEnabled) { + Map headers = getHeaders(); + final String pullRequestId = analysis.getBranchName(); + final String mergeRequestDiscussionURL = getMergeRequestDiskussionsURL(pullRequestId); + MergeRequest mergeRequest = getMergeRequest(pullRequestId); + DiffRefs diffRefs = mergeRequest.getDiffRefs(); + List commits = getCommits(pullRequestId); + List openIssues = analysis.getPostAnalysisIssueVisitor().getIssues().stream().filter(i -> OPEN_ISSUE_STATUSES.contains(i.getIssue().getStatus())).collect(Collectors.toList()); + for (PostAnalysisIssueVisitor.ComponentIssue issue : openIssues) { + String path = analysis.getSCMPathForIssue(issue).orElse(null); + if (path != null && issue.getIssue().getLine() != null && isPrinted(issue.getIssue().severity())) { + //only if we have a path and line number + String fileComment = analysis.createAnalysisIssueSummary(issue, new MarkdownFormatterFactory(), compactCommentsEnabled); + + if (getScmInfoForComponent(issue.getComponent()) + .filter(i -> i.hasChangesetForLine(issue.getIssue().getLine())) + .map(i -> i.getChangesetForLine(issue.getIssue().getLine())) + .map(Changeset::getRevision) + .filter(commits::contains) + .isPresent()) { + //only if the change is on a commit, that belongs to this MR + String lineNr = String.valueOf(issue.getIssue().getLine()); + Note existingNote = lineNotes.get(lineNr); + if (existingNote!=null) { + try { + updateCommitComment(mergeRequestDiscussionURL, existingNote.getDiscussionId(), existingNote.getId(), headers, fileComment); + } catch (IOException ex) { + LOGGER.error("Can't update issue comment on line '{}' to '{}'.", issue.getIssue().getLine(), mergeRequestDiscussionURL); + } + lineNotes.remove(lineNr); + } else { + List fileContentParams = Arrays.asList( + new BasicNameValuePair("body", fileComment), + new BasicNameValuePair("position[base_sha]", diffRefs.getBaseSha()), + new BasicNameValuePair("position[start_sha]", diffRefs.getStartSha()), + new BasicNameValuePair("position[head_sha]", diffRefs.getHeadSha()), + new BasicNameValuePair("position[old_path]", path), + new BasicNameValuePair("position[new_path]", path), + new BasicNameValuePair("position[new_line]", lineNr), + new BasicNameValuePair("position[position_type]", "text")); + + try { + postCommitComment(mergeRequestDiscussionURL, headers, fileContentParams); + } catch (IOException ex) { + LOGGER.error("Can't post issue comment on line '{}' to '{}'.", issue.getIssue().getLine(), mergeRequestDiscussionURL); + } + } + + } else { + LOGGER.info(String.format("Skipping %s:%d since the commit does not belong to the MR", path, issue.getIssue().getLine())); + } + } + } + } + } + + protected void doSummary(AnalysisDetails analysis, Note summaryComment) throws UnsupportedEncodingException { + final boolean summaryCommentEnabled = getMandatoryBooleanProperty(PULL_REQUEST_COMMENT_SUMMARY_ENABLED); + if (summaryCommentEnabled) { + final String pullRequestId = analysis.getBranchName(); + String mergeRequestDiscussionURL = getMergeRequestDiskussionsURL(pullRequestId); + String summaryCommentBody = analysis.createAnalysisSummary(new MarkdownFormatterFactory()); + Map headers = getHeaders(); + if (summaryComment!=null) { + try { + updateCommitComment(mergeRequestDiscussionURL, summaryComment.getDiscussionId(), summaryComment.getId(), headers, summaryCommentBody); + } catch (IOException ex) { + LOGGER.error("Can't update summary comment to '{}'.", mergeRequestDiscussionURL); + } + } else { + List summaryContentParams = Collections.singletonList(new BasicNameValuePair("body", summaryCommentBody)); + try { + postCommitComment(mergeRequestDiscussionURL, headers, summaryContentParams); + } catch (IOException ex) { + LOGGER.error("Can't post summary comment to '{}'.", mergeRequestDiscussionURL); + } + } + boolean approved = analysis.getQualityGateStatus() == QualityGate.Status.OK; + if (approved) { + // TODO resolve by post to /merge_requests/1114/discussions/c4bbff952a9d3f5250f432e9cfeaf24bfe9ebb2a/resolve + } + } + } + + private void doPipeline(AnalysisDetails analysis) throws UnsupportedEncodingException { + final boolean canFailPipeline = getMandatoryBooleanProperty(PULLREQUEST_CAN_FAIL_PIPELINE_ENABLED); + Map headers = getHeaders(); + String revision = analysis.getCommitSha(); + final String statusUrl = getStatusURL(revision); + LOGGER.info(String.format("Status url is: %s ", statusUrl)); + String coverageValue = analysis.findQualityGateCondition(CoreMetrics.NEW_COVERAGE_KEY) + .filter(condition -> condition.getStatus() != QualityGate.EvaluationStatus.NO_VALUE) + .map(QualityGate.Condition::getValue) + .orElse("0"); + + try { + postStatus(statusUrl, headers, analysis, coverageValue, canFailPipeline); + } catch (IOException ex) { + LOGGER.error("Can't post status to '{}'.", statusUrl); + } + } + + private List getOwnDiscussions(User user, String pullRequestId) + throws IOException { + Map headers = getHeaders(); + final String mergeRequestDiscussionURL = getMergeRequestDiskussionsURL(pullRequestId); + List discussions = getPagedList(mergeRequestDiscussionURL, headers, new TypeReference>() { + }); + List result = new ArrayList<>(); + for(Discussion discussion: discussions) { + if (discussion.getNotes()!=null && discussion.getNotes().size()>0) { + Note firstNote = discussion.getNotes().get(0); + if (!firstNote.isSystem() + && firstNote.getAuthor() != null && firstNote.getAuthor().getUsername().equals(user.getUsername()) + && ("DiffNote".equals(firstNote.getType()) || "DiscussionNote".equals(firstNote.getType()))) { + firstNote.setDiscussionId(discussion.getId()); + result.add(discussion); + } + } + } + LOGGER.info(String.format("Discussions in MR: %s ", result + .stream() + .map(Discussion::getId) + .collect(Collectors.joining(", ")))); + return result; + } + + private MergeRequest getMergeRequest(final String pullRequestId) throws IOException { + Map headers = getHeaders(); + final String mergeRequestURL = getMergeRequestURL(pullRequestId); + return getSingle(mergeRequestURL, headers, MergeRequest.class); + } + + + private List getCommits(final String pullRequestId) throws IOException { + Map headers = getHeaders(); + final String prCommitsURL = getMergeRequestCommitsURL(pullRequestId); + List result = getPagedList(prCommitsURL, headers, new TypeReference>() { + }).stream().map(Commit::getId).collect(Collectors.toList()); + return result; + } + + private User getUser() throws IOException { + final String hostURL = getMandatoryProperty(PULLREQUEST_GITLAB_URL); + final String userURL = getUserURL(hostURL); + LOGGER.info(String.format("User url is: %s ", userURL)); + Map headers = getHeaders(); + User result = getSingle(userURL, headers, User.class); + LOGGER.info(String.format("Using user: %s ", result.getUsername())); + return result; + } + + private X getSingle(String url, Map headers, Class type) throws IOException { + HttpGet httpGet = new HttpGet(url); for (Map.Entry entry : headers.entrySet()) { httpGet.addHeader(entry.getKey(), entry.getValue()); } @@ -213,91 +316,105 @@ private X getSingle(String userURL, Map headers, Class ty } else if (null != httpResponse) { LOGGER.debug(httpResponse.toString()); HttpEntity entity = httpResponse.getEntity(); - X user = new ObjectMapper() + X result = new ObjectMapper() .configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true) .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .readValue(IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8), type); LOGGER.info(type + " received"); - - return user; + return result; } else { throw new IOException("No response reveived"); } } - private List getPagedList(String commitDiscussionURL, Map headers, boolean sendRequest, TypeReference> typeRef) throws IOException { - HttpGet httpGet = new HttpGet(commitDiscussionURL); + private List getPagedList(String url, Map headers, TypeReference> type) throws IOException { + HttpGet httpGet = new HttpGet(url); for (Map.Entry entry : headers.entrySet()) { httpGet.addHeader(entry.getKey(), entry.getValue()); } - List discussions = new ArrayList<>(); - - if (sendRequest) { - HttpResponse httpResponse = HttpClients.createDefault().execute(httpGet); - if (null != httpResponse && httpResponse.getStatusLine().getStatusCode() != 200) { - LOGGER.error(httpResponse.toString()); - LOGGER.error(EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8)); - throw new IllegalStateException("An error was returned in the response from the Gitlab API. See the previous log messages for details"); - } else if (null != httpResponse) { - LOGGER.debug(httpResponse.toString()); - HttpEntity entity = httpResponse.getEntity(); - List pagedDiscussions = new ObjectMapper() - .configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true) - .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .readValue(IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8), typeRef); - discussions.addAll(pagedDiscussions); - LOGGER.info("MR discussions received"); - Optional nextURL = getNextUrl(httpResponse); - if (nextURL.isPresent()) { - LOGGER.info("Getting next page"); - discussions.addAll(getPagedList(nextURL.get(), headers, sendRequest, typeRef)); - } + List result = new ArrayList<>(); + + HttpResponse httpResponse = HttpClients.createDefault().execute(httpGet); + if (null != httpResponse && httpResponse.getStatusLine().getStatusCode() != 200) { + LOGGER.error(httpResponse.toString()); + LOGGER.error(EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8)); + throw new IllegalStateException("An error was returned in the response from the Gitlab API. See the previous log messages for details"); + } else if (null != httpResponse) { + LOGGER.debug(httpResponse.toString()); + HttpEntity entity = httpResponse.getEntity(); + List pagedResults = new ObjectMapper() + .configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true) + .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .readValue(IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8), type); + result.addAll(pagedResults); + Optional nextURL = getNextUrl(httpResponse); + if (nextURL.isPresent()) { + LOGGER.info("Getting next page"); + result.addAll(getPagedList(nextURL.get(), headers, type)); } + LOGGER.info(type + " received"); } - return discussions; + return result; } - private void deleteCommitDiscussionNote(String commitDiscussionNoteURL, Map headers, boolean sendRequest) throws IOException { + private void deleteCommitDiscussionNote(String pullRequestId, String discussionId, long noteId) { //https://docs.gitlab.com/ee/api/discussions.html#delete-a-commit-thread-note - HttpDelete httpDelete = new HttpDelete(commitDiscussionNoteURL); - for (Map.Entry entry : headers.entrySet()) { - httpDelete.addHeader(entry.getKey(), entry.getValue()); - } - - if (sendRequest) { - LOGGER.info("Deleting {} with headers {}", commitDiscussionNoteURL, headers); - + String commitDiscussionNoteURL = null; + try { + commitDiscussionNoteURL = getNoteUrl(pullRequestId, discussionId, noteId); + Map headers = getHeaders(); + HttpDelete httpDelete = new HttpDelete(commitDiscussionNoteURL); + for (Map.Entry entry : headers.entrySet()) { + httpDelete.addHeader(entry.getKey(), entry.getValue()); + } + LOGGER.info("Deleting {} with headers {}", commitDiscussionNoteURL, headers); HttpResponse httpResponse = HttpClients.createDefault().execute(httpDelete); validateGitlabResponse(httpResponse, 204, "Commit discussions note deleted"); + } catch (IOException ex) { + LOGGER.error("Can't delete note '{}'", commitDiscussionNoteURL); } } - private void postCommitComment(String commitCommentUrl, Map headers, List params, boolean sendRequest) throws IOException { + private void postCommitComment(String commitCommentUrl, Map headers, List params) throws IOException { //https://docs.gitlab.com/ee/api/commits.html#post-comment-to-commit HttpPost httpPost = new HttpPost(commitCommentUrl); for (Map.Entry entry : headers.entrySet()) { httpPost.addHeader(entry.getKey(), entry.getValue()); } httpPost.setEntity(new UrlEncodedFormEntity(params)); + LOGGER.info("Posting {} with headers {} to {}", params, headers, commitCommentUrl); - if (sendRequest) { - LOGGER.info("Posting {} with headers {} to {}", params, headers, commitCommentUrl); + HttpResponse httpResponse = HttpClients.createDefault().execute(httpPost); + validateGitlabResponse(httpResponse, 201, "Comment posted"); + } + + + private void updateCommitComment(String commitCommentUrl, String discussionId, long noteId, Map headers, String body) throws IOException { + //https://docs.gitlab.com/ee/api/notes.html#modify-existing-merge-request-note + String commitCommentModificationUrl = commitCommentUrl + "/" + discussionId + "/notes/" + noteId; - HttpResponse httpResponse = HttpClients.createDefault().execute(httpPost); - validateGitlabResponse(httpResponse, 201, "Comment posted"); + HttpPut httpPut = new HttpPut(commitCommentModificationUrl); + for (Map.Entry entry : headers.entrySet()) { + httpPut.addHeader(entry.getKey(), entry.getValue()); } - } + httpPut.setEntity(new UrlEncodedFormEntity(Collections.singletonList(new BasicNameValuePair("body", body)))); + LOGGER.info("Posting {} with headers {} to {}", body, headers, commitCommentModificationUrl); + + HttpResponse httpResponse = HttpClients.createDefault().execute(httpPut); + validateGitlabResponse(httpResponse, 200, null); + + } - private void postStatus(String statusPostUrl, Map headers, AnalysisDetails analysis, String coverage, boolean sendRequest) throws IOException{ + private void postStatus(String statusPostUrl, Map headers, AnalysisDetails analysis, String coverage, boolean canFailPipeline) throws IOException{ //See https://docs.gitlab.com/ee/api/commits.html#post-the-build-status-to-a-commit statusPostUrl += "?name=SonarQube"; - String status = (analysis.getQualityGateStatus() == QualityGate.Status.OK ? "success" : "failed"); + String status = (!canFailPipeline || analysis.getQualityGateStatus() == QualityGate.Status.OK ? "success" : "failed"); statusPostUrl += "&state=" + status; - statusPostUrl += "&target_url=" + URLEncoder.encode(String.format("%s/dashboard?id=%s&pullRequest=%s", server.getPublicRootUrl(), + statusPostUrl += "&target_url=" + URLEncoder.encode(String.format("%s/dashboard?id=%s&pullRequest=%s", getServerPublicRootUrl(), URLEncoder.encode(analysis.getAnalysisProjectKey(), StandardCharsets.UTF_8.name()), URLEncoder .encode(analysis.getBranchName(), @@ -310,17 +427,16 @@ private void postStatus(String statusPostUrl, Map headers, Analy for (Map.Entry entry : headers.entrySet()) { httpPost.addHeader(entry.getKey(), entry.getValue()); } - if (sendRequest) { - HttpResponse httpResponse = HttpClients.createDefault().execute(httpPost); - if (null != httpResponse && httpResponse.toString().contains("Cannot transition status")) { - // Workaround for https://gitlab.com/gitlab-org/gitlab-ce/issues/25807 - LOGGER.debug("Transition status is already {}", status); - } else { - validateGitlabResponse(httpResponse, 201, "Comment posted"); - } + HttpResponse httpResponse = HttpClients.createDefault().execute(httpPost); + if (null != httpResponse && httpResponse.toString().contains("Cannot transition status")) { + // Workaround for https://gitlab.com/gitlab-org/gitlab-ce/issues/25807 + LOGGER.debug("Transition status is already {}", status); + } else { + validateGitlabResponse(httpResponse, 201, "Comment posted"); } } + private void validateGitlabResponse(HttpResponse httpResponse, int expectedStatus, String successLogMessage) throws IOException { if (null != httpResponse && httpResponse.getStatusLine().getStatusCode() != expectedStatus) { LOGGER.error(httpResponse.toString()); @@ -332,7 +448,15 @@ private void validateGitlabResponse(HttpResponse httpResponse, int expectedStatu } } - private static String getMandatoryProperty(String propertyName, Configuration configuration) { + protected boolean getMandatoryBooleanProperty(String propertyName) { + return Boolean.parseBoolean(getMandatoryProperty(propertyName)); + } + + protected String getMandatoryProperty(String propertyName) { + return getMandatoryProperty(propertyName, getConfiguration()); + } + + protected static String getMandatoryProperty(String propertyName, Configuration configuration) { return configuration.get(propertyName).orElseThrow(() -> new IllegalStateException( String.format("%s must be specified in the project configuration", propertyName))); } @@ -350,6 +474,85 @@ private static Optional getNextUrl(HttpResponse httpResponse) { } return Optional.empty(); } + + protected Map getHeaders() { + Map headers = new HashMap<>(); + final String apiToken = getMandatoryProperty(PULLREQUEST_GITLAB_TOKEN); + headers.put("PRIVATE-TOKEN", apiToken); + headers.put("Accept", "application/json"); + return headers; + } + + protected boolean isPrinted(String severity) { + final String minSeverityString = getMandatoryProperty(PULL_REQUEST_COMMENTS_MIN_SEVERITY); + Severity minSeverity = Severity.INFO; + if (minSeverityString!=null && minSeverityString.trim().length()>0) { + minSeverity = Severity.valueOf(minSeverityString); + } + if (severity==null || severity.trim().length() == 0) { + return true; + } + return Severity.valueOf(severity).getNumber() >= minSeverity.getNumber(); + } + + + protected Optional getScmInfoForComponent(Component component) { + return scmInfoRepository.getScmInfo(component); + } + + protected Configuration getConfiguration() { + return configurationRepository.getConfiguration(); + } + + protected String getServerPublicRootUrl() { + return server.getPublicRootUrl(); + } + + protected String getRestURL(final String hostURL) { + return String.format("%s/api/v4", hostURL); + } + + protected String getMergeRequestDiskussionsURL(final String pullRequestId) throws UnsupportedEncodingException { + final String mergeRequestURl = getMergeRequestURL(pullRequestId); + String result = mergeRequestURl + "/discussions"; + LOGGER.info(String.format("MR discussion url is: %s ", result)); + return result; + } + + protected String getMergeRequestCommitsURL(final String pullRequestId) throws UnsupportedEncodingException { + final String mergeRequestURL = getMergeRequestURL(pullRequestId); + String result = mergeRequestURL + "/commits"; + LOGGER.info(String.format("PR commits url is: %s ", result)); + return result; + } + + protected String getMergeRequestURL(final String pullRequestId) throws UnsupportedEncodingException { + final String projectURL = getProjectURL(); + return projectURL + String.format("/merge_requests/%s", pullRequestId); + } + + protected String getNoteUrl(String pullRequestId, String discussionId, long noteId) throws UnsupportedEncodingException{ + String mergeRequestDiscussionURL = getMergeRequestDiskussionsURL(pullRequestId); + return mergeRequestDiscussionURL + String.format("/%s/notes/%s", discussionId, noteId); + } + + protected String getStatusURL(String revision) throws UnsupportedEncodingException { + final String projectURL = getProjectURL(); + return projectURL + String.format("/statuses/%s", revision); + } + + protected String getProjectURL() throws UnsupportedEncodingException { + final String hostURL = getMandatoryProperty(PULLREQUEST_GITLAB_URL); + final String repositorySlug = getMandatoryProperty(PULLREQUEST_GITLAB_REPOSITORY_SLUG); + final String restURL = getRestURL(hostURL); + return restURL + String.format("/projects/%s", URLEncoder.encode(repositorySlug, StandardCharsets.UTF_8.name())); + } + + protected String getUserURL(final String hostURL) { + final String restURL = getRestURL(hostURL); + return restURL + "/user"; + } + @Override public String name() { diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Commit.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Commit.java index 5d54c799d..99e6857b7 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Commit.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Commit.java @@ -18,18 +18,93 @@ */ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.response; +import java.util.Calendar; + import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; public class Commit { private final String id; + private final String shortId; + private final Calendar createdAt; + private final String title; + private final String message; + private final String authorName; + private final String authorEmail; + private final Calendar authoredDate; + private final String committerName; + private final String committerEmail; + private final Calendar committedDate; + + @JsonCreator + public Commit( + @JsonProperty("id") String id, + @JsonProperty("short_id") String shortId, + @JsonProperty("created_at") Calendar createdAt, + @JsonProperty("title") String title, + @JsonProperty("message") String message, + @JsonProperty("author_name") String authorName, + @JsonProperty("author_email") String authorEmail, + @JsonProperty("authored_date") Calendar authoredDate, + @JsonProperty("committer_name") String committerName, + @JsonProperty("committer_email") String committerEmail, + @JsonProperty("committed_date") Calendar committedDate) { + super(); + this.id = id; + this.shortId = shortId; + this.createdAt = createdAt; + this.title = title; + this.message = message; + this.authorName = authorName; + this.authorEmail = authorEmail; + this.authoredDate = authoredDate; + this.committerName = committerName; + this.committerEmail = committerEmail; + this.committedDate = committedDate; + } + + public String getId() { + return id; + } + + public String getShortId() { + return shortId; + } + + public Calendar getCreatedAt() { + return createdAt; + } + + public String getTitle() { + return title; + } + + public String getMessage() { + return message; + } + + public String getAuthorName() { + return authorName; + } + + public String getAuthorEmail() { + return authorEmail; + } + + public Calendar getAuthoredDate() { + return authoredDate; + } + + public String getCommitterName() { + return committerName; + } + + public String getCommitterEmail() { + return committerEmail; + } - @JsonCreator - public Commit(@JsonProperty("id") String id) { - this.id = id; - } + public Calendar getCommittedDate() { + return committedDate; + } - public String getId() { - return id; - } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/DetailedStatus.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/DetailedStatus.java new file mode 100644 index 000000000..f44314c55 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/DetailedStatus.java @@ -0,0 +1,68 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.response; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class DetailedStatus { + private final String icon; + private final String text; + private final String label; + private final String group; + private final String tooltip; + private final boolean hasDetails; + private final String detailsPath; + private final String favicon; + + @JsonCreator + public DetailedStatus( + @JsonProperty("icon") String icon, + @JsonProperty("text") String text, + @JsonProperty("label") String label, + @JsonProperty("group") String group, + @JsonProperty("tooltip") String tooltip, + @JsonProperty("has_details") boolean hasDetails, + @JsonProperty("details_path") String detailsPath, + @JsonProperty("favicon") String favicon) { + this.icon = icon; + this.text = text; + this.label = label; + this.group = group; + this.tooltip = tooltip; + this.hasDetails = hasDetails; + this.detailsPath = detailsPath; + this.favicon = favicon; + } + + public String getIcon() { + return icon; + } + + public String getText() { + return text; + } + + public String getLabel() { + return label; + } + + public String getGroup() { + return group; + } + + public String getTooltip() { + return tooltip; + } + + public boolean isHasDetails() { + return hasDetails; + } + + public String getDetailsPath() { + return detailsPath; + } + + public String getFavicon() { + return favicon; + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/DiffRefs.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/DiffRefs.java index ac8292023..4e538d49e 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/DiffRefs.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/DiffRefs.java @@ -7,7 +7,10 @@ public class DiffRefs { private final String headSha; private final String startSha; - public DiffRefs(@JsonProperty("base_sha") String baseSha, @JsonProperty("head_sha") String headSha, @JsonProperty("start_sha") String startSha) { + public DiffRefs( + @JsonProperty("base_sha") String baseSha, + @JsonProperty("head_sha") String headSha, + @JsonProperty("start_sha") String startSha) { this.baseSha = baseSha; this.headSha = headSha; this.startSha = startSha; diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Discussion.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Discussion.java index 17b487f1e..b32a2930e 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Discussion.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Discussion.java @@ -25,20 +25,28 @@ public class Discussion { private final String id; - + private final boolean individualNote; private final List notes; @JsonCreator - public Discussion(@JsonProperty("id") String id, @JsonProperty("notes") List notes) { + public Discussion( + @JsonProperty("id") String id, + @JsonProperty("individual_note") boolean individualNote, + @JsonProperty("notes") List notes) { this.id = id; + this.individualNote = individualNote; this.notes = notes; } public String getId() { return id; } + + public boolean isIndividualNote() { + return individualNote; + } - public List getNotes() { + public List getNotes() { return notes; } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/MergeRequest.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/MergeRequest.java index fa6908828..94a25e1c3 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/MergeRequest.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/MergeRequest.java @@ -1,27 +1,287 @@ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.response; +import java.util.Calendar; + +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; public class MergeRequest { - private final String id; - private final String iid; + private final long id; + private final long iid; + private final long projectId; + private final String title; + private final String description; + private final String state; + private final String createdAt; + private final String updatedAt; + private final User mergedBy; + private final Calendar mergedAt; + private final User closedBy; + private final Calendar closedAt; + private final String targetBranch; + private final String sourceBranch; + private final Long userNotesCount; + private final Long upvotes; + private final Long downvotes; + private final User author; + private final User assignee; + private final Long sourceProjectId; + private final Long targetProjectId; + private final boolean workInProgress; + private final boolean mergeWhenPipelineSucceeds; + private final String mergeStatus; + private final String sha; + private final boolean forceRemoveSourceBranch; + private final String reference; + private final String webUrl; + private final TimeStats timeStats; + private final boolean squash; + private final boolean subscribed; + private final String changesCount; + private final Calendar latestBuildStartedAt; + private final Calendar latestBuildFinishedAt; + private final Calendar firstDeployedToProductionAt; + private final Pipeline pipeline; + private final Pipeline headPipeline; private final DiffRefs diffRefs; + private final Long approvalsBeforMerge; + + @JsonCreator + public MergeRequest( + @JsonProperty("id") long id, + @JsonProperty("iid") long iid, + @JsonProperty("project_id") long projectId, + @JsonProperty("title") String title, + @JsonProperty("description") String description, + @JsonProperty("state") String state, + @JsonProperty("created_at") String createdAt, + @JsonProperty("updated_at") String updatedAt, + @JsonProperty("merged_by") User mergedBy, + @JsonProperty("merged_at") Calendar mergedAt, + @JsonProperty("closed_by") User closedBy, + @JsonProperty("closed_at") Calendar closedAt, + @JsonProperty("target_branch") String targetBranch, + @JsonProperty("source_branch") String sourceBranch, + @JsonProperty("user_notes_count") Long userNotesCount, + @JsonProperty("upvotes") Long upvotes, + @JsonProperty("downvotes") Long downvotes, + @JsonProperty("author") User author, + @JsonProperty("assignee") User assignee, + @JsonProperty("source_project_id") Long sourceProjectId, + @JsonProperty("target_project_id") Long targetProjectId, + @JsonProperty("work_in_progress") boolean workInProgress, + @JsonProperty("merge_when_pipeline_succeeds") boolean mergeWhenPipelineSucceeds, + @JsonProperty("merge_status") String mergeStatus, + @JsonProperty("sha") String sha, + @JsonProperty("force_remove_source_branch") boolean forceRemoveSourceBranch, + @JsonProperty("reference") String reference, + @JsonProperty("web_url") String webUrl, + @JsonProperty("time_stats") TimeStats timeStats, + @JsonProperty("squash") boolean squash, + @JsonProperty("subscribed") boolean subscribed, + @JsonProperty("changes_count") String changesCount, + @JsonProperty("latest_build_started_at") Calendar latestBuildStartedAt, + @JsonProperty("latest_build_finished_at") Calendar latestBuildFinishedAt, + @JsonProperty("first_deployed_to_production_at") Calendar firstDeployedToProductionAt, + @JsonProperty("pipeline") Pipeline pipeline, + @JsonProperty("head_pipeline") Pipeline headPipeline, + @JsonProperty("diff_refs") DiffRefs diffRefs, + @JsonProperty("approvals_before_merge") Long approvalsBeforMerge) { + this.id = id; + this.iid = iid; + this.projectId = projectId; + this.title = title; + this.description = description; + this.state = state; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + this.mergedBy = mergedBy; + this.mergedAt = mergedAt; + this.closedBy = closedBy; + this.closedAt = closedAt; + this.targetBranch = targetBranch; + this.sourceBranch = sourceBranch; + this.userNotesCount = userNotesCount; + this.upvotes = upvotes; + this.downvotes = downvotes; + this.author = author; + this.assignee = assignee; + this.sourceProjectId = sourceProjectId; + this.targetProjectId = targetProjectId; + this.workInProgress = workInProgress; + this.mergeWhenPipelineSucceeds = mergeWhenPipelineSucceeds; + this.mergeStatus = mergeStatus; + this.sha = sha; + this.forceRemoveSourceBranch = forceRemoveSourceBranch; + this.reference = reference; + this.webUrl = webUrl; + this.timeStats = timeStats; + this.squash = squash; + this.subscribed = subscribed; + this.changesCount = changesCount; + this.latestBuildStartedAt = latestBuildStartedAt; + this.latestBuildFinishedAt = latestBuildFinishedAt; + this.firstDeployedToProductionAt = firstDeployedToProductionAt; + this.pipeline = pipeline; + this.headPipeline = headPipeline; + this.diffRefs = diffRefs; + this.approvalsBeforMerge = approvalsBeforMerge; + } + + public long getId() { + return id; + } + + public long getIid() { + return iid; + } + + public long getProjectId() { + return projectId; + } + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + public String getState() { + return state; + } + + public String getCreatedAt() { + return createdAt; + } + + public String getUpdatedAt() { + return updatedAt; + } + + public User getMergedBy() { + return mergedBy; + } + + public Calendar getMergedAt() { + return mergedAt; + } + + public User getClosedBy() { + return closedBy; + } + + public Calendar getClosedAt() { + return closedAt; + } + + public String getTargetBranch() { + return targetBranch; + } + + public String getSourceBranch() { + return sourceBranch; + } + + public Long getUserNotesCount() { + return userNotesCount; + } + + public Long getUpvotes() { + return upvotes; + } + + public Long getDownvotes() { + return downvotes; + } + + public User getAuthor() { + return author; + } + + public User getAssignee() { + return assignee; + } + + public Long getSourceProjectId() { + return sourceProjectId; + } + + public Long getTargetProjectId() { + return targetProjectId; + } + + public boolean isWorkInProgress() { + return workInProgress; + } + + public boolean isMergeWhenPipelineSucceeds() { + return mergeWhenPipelineSucceeds; + } + + public String getMergeStatus() { + return mergeStatus; + } + + public String getSha() { + return sha; + } + + public boolean isForceRemoveSourceBranch() { + return forceRemoveSourceBranch; + } + + public String getReference() { + return reference; + } + + public String getWebUrl() { + return webUrl; + } + + public TimeStats getTimeStats() { + return timeStats; + } + + public boolean isSquash() { + return squash; + } + + public boolean isSubscribed() { + return subscribed; + } + + public String getChangesCount() { + return changesCount; + } + + public Calendar getLatestBuildStartedAt() { + return latestBuildStartedAt; + } + + public Calendar getLatestBuildFinishedAt() { + return latestBuildFinishedAt; + } + + public Calendar getFirstDeployedToProductionAt() { + return firstDeployedToProductionAt; + } + + public Pipeline getPipeline() { + return pipeline; + } - public MergeRequest(@JsonProperty("id") String id, @JsonProperty("iid") String iid, @JsonProperty("diff_refs") DiffRefs diffRefs) { - this.id = id; - this.iid = iid; - this.diffRefs = diffRefs; - } + public Pipeline getHeadPipeline() { + return headPipeline; + } - public String getId() { - return id; - } + public DiffRefs getDiffRefs() { + return diffRefs; + } - public String getIid() { - return iid; - } + public Long getApprovalsBeforMerge() { + return approvalsBeforMerge; + } - public DiffRefs getDiffRefs() { - return diffRefs; - } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Note.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Note.java index edb22c9f9..ee161d10b 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Note.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Note.java @@ -18,32 +18,124 @@ */ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.response; +import java.util.Calendar; + import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; public class Note { private final long id; - + private final String type; + private final String body; + private final User author; + private final Calendar createdAt; + private final Calendar updatedAt; private final boolean system; + private final Long noteableId; + private final Long noteableIid; + private final String noteableType; + private final Position position; + private final boolean resolvable; + private final boolean resolved; + private final User resolvedBy; + + // Reference to the discussion, set during reading notes + private String discussionId; - private final User author; @JsonCreator - public Note(@JsonProperty("id") long id, @JsonProperty("system") boolean system, @JsonProperty("author") User author) { + public Note(@JsonProperty("id") long id, + @JsonProperty("type") String type, + @JsonProperty("body") String body, + @JsonProperty("author") User author, + @JsonProperty("created_at") Calendar createdAt, + @JsonProperty("updated_at") Calendar updatedAt, + @JsonProperty("system") boolean system, + @JsonProperty("noteable_id") Long noteableId, + @JsonProperty("noteable_iid") Long noteableIid, + @JsonProperty("noteable_type") String noteableType, + @JsonProperty("position") Position position, + @JsonProperty("resolvable") boolean resolvable, + @JsonProperty("resolved") boolean resolved, + @JsonProperty("resolved_by") User resolvedBy) { this.id = id; - this.system = system; + this.body = body; + this.type = type; this.author = author; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + this.system = system; + this.noteableId = noteableId; + this.noteableIid = noteableIid; + this.noteableType = noteableType; + this.position = position; + this.resolvable = resolvable; + this.resolved = resolved; + this.resolvedBy = resolvedBy; } public long getId() { return id; } - public boolean isSystem() { - return system; - } + public String getType() { + return type; + } - public User getAuthor() { - return author; - } + public String getBody() { + return body; + } + + public User getAuthor() { + return author; + } + + public Calendar getCreatedAt() { + return createdAt; + } + + public Calendar getUpdatedAt() { + return updatedAt; + } + + public boolean isSystem() { + return system; + } + + public Long getNoteableId() { + return noteableId; + } + + public Long getNoteableIid() { + return noteableIid; + } + + public String getNoteableType() { + return noteableType; + } + + public Position getPosition() { + return position; + } + + public boolean isResolvable() { + return resolvable; + } + + public boolean isResolved() { + return resolved; + } + + public User getResolvedBy() { + return resolvedBy; + } + + public String getDiscussionId() { + return discussionId; + } + + public void setDiscussionId(String discussionId) { + this.discussionId = discussionId; + } + } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Pipeline.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Pipeline.java new file mode 100644 index 000000000..94082845f --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Pipeline.java @@ -0,0 +1,118 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.response; + +import java.util.Calendar; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Pipeline { + + private final long id; + private final String sha; + private final String ref; + private final String status; + private final String webUrl; + private final String beforeSha; + private final boolean tag; + private final User user; + private final Calendar createdAt; + private final Calendar updatedAt; + private final Calendar startedAt; + private final Calendar finishedAt; + private final Calendar committedAt; + private final String coverage; + private final DetailedStatus detailedStatus; + + public Pipeline( + @JsonProperty("id") long id, + @JsonProperty("sha") String sha, + @JsonProperty("ref") String ref, + @JsonProperty("status") String status, + @JsonProperty("web_url") String webUrl, + @JsonProperty("before_sha") String beforeSha, + @JsonProperty("tag") boolean tag, + @JsonProperty("user") User user, + @JsonProperty("created_at") Calendar createdAt, + @JsonProperty("updated_at") Calendar updatedAt, + @JsonProperty("started_at") Calendar startedAt, + @JsonProperty("finished_at") Calendar finishedAt, + @JsonProperty("committed_at") Calendar committedAt, + @JsonProperty("coverage") String coverage, + @JsonProperty("detailed_status") DetailedStatus detailedStatus) { + this.id = id; + this.sha = sha; + this.ref = ref; + this.status = status; + this.webUrl = webUrl; + this.beforeSha = beforeSha; + this.tag = tag; + this.user = user; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + this.startedAt = startedAt; + this.finishedAt = finishedAt; + this.committedAt = committedAt; + this.coverage = coverage; + this.detailedStatus = detailedStatus; + } + + public long getId() { + return id; + } + + public String getSha() { + return sha; + } + + public String getRef() { + return ref; + } + + public String getStatus() { + return status; + } + + public String getWebUrl() { + return webUrl; + } + + public String getBeforeSha() { + return beforeSha; + } + + public boolean isTag() { + return tag; + } + + public User getUser() { + return user; + } + + public Calendar getCreatedAt() { + return createdAt; + } + + public Calendar getUpdatedAt() { + return updatedAt; + } + + public Calendar getStartedAt() { + return startedAt; + } + + public Calendar getFinishedAt() { + return finishedAt; + } + + public Calendar getCommittedAt() { + return committedAt; + } + + public String getCoverage() { + return coverage; + } + + public DetailedStatus getDetailedStatus() { + return detailedStatus; + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Position.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Position.java new file mode 100644 index 000000000..a4666a2e0 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/Position.java @@ -0,0 +1,70 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.response; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Position { + + private final String baseSha; + private final String startSha; + private final String headSha; + private final String oldPath; + private final String newPath; + private final String positionType; + private final String oldLine; + private final String newLine; + + @JsonCreator + public Position( + @JsonProperty("base_sha") String baseSha, + @JsonProperty("start_sha") String startSha, + @JsonProperty("head_sha") String headSha, + @JsonProperty("old_path") String oldPath, + @JsonProperty("new_path") String newPath, + @JsonProperty("position_type") String positionType, + @JsonProperty("old_line") String oldLine, + @JsonProperty("new_line") String newLine) { + this.baseSha = baseSha; + this.startSha = startSha; + this.headSha = headSha; + this.oldPath = oldPath; + this.newPath = newPath; + this.positionType = positionType; + this.oldLine = oldLine; + this.newLine = newLine; + } + + public String getBaseSha() { + return baseSha; + } + + public String getStartSha() { + return startSha; + } + + public String getHeadSha() { + return headSha; + } + + public String getOldPath() { + return oldPath; + } + + public String getNewPath() { + return newPath; + } + + public String getPositionType() { + return positionType; + } + + public String getOldLine() { + return oldLine; + } + + public String getNewLine() { + return newLine; + } + +} + diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/TimeStats.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/TimeStats.java new file mode 100644 index 000000000..50a031d18 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/TimeStats.java @@ -0,0 +1,29 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.response; + +import java.math.BigDecimal; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class TimeStats { + + private final BigDecimal timeEstimate; + private final BigDecimal totalTimeSpent; + + @JsonCreator + public TimeStats( + @JsonProperty("time_estimate") BigDecimal timeEstimate, + @JsonProperty("total_time_spent") BigDecimal totalTimeSpent) { + this.timeEstimate = timeEstimate; + this.totalTimeSpent = totalTimeSpent; + } + + public BigDecimal getTimeEstimate() { + return timeEstimate; + } + + public BigDecimal getTotalTimeSpent() { + return totalTimeSpent; + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/User.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/User.java index c96d0ebf9..a0a9c0138 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/User.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/response/User.java @@ -8,28 +8,200 @@ * * 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 + * 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. + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.response; +import java.util.Calendar; + import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; public class User { - private final String username; + private final long id; + private final String name; + private final String username; + private final String state; + private final String avatarUrl; + private final String webUrl; + private final Calendar createdAt; + private final String publicEmail; + private final String skype; + private final String linkedin; + private final String twitter; + private final String websiteUrl; + private final Calendar lastSignInAt; + private final Calendar confirmedAt; + private final Calendar lastActivityOn; + private final String email; + private final Integer themeId; + private final Integer colorSchemeId; + private final Integer projectsLimit; + private final Calendar currentSignInAt; + private final boolean canCreateGroup; + private final boolean canCreateProject; + private final boolean twoFactorEnabled; + private final boolean external; + private final boolean isAdmin; + + @JsonCreator + public User( + @JsonProperty("id") long id, + @JsonProperty("name") String name, + @JsonProperty("username") String username, + @JsonProperty("state") String state, + @JsonProperty("avatar_url") String avatarUrl, + @JsonProperty("web_url") String webUrl, + @JsonProperty("created_at") Calendar createdAt, + @JsonProperty("public_email") String publicEmail, + @JsonProperty("skype") String skype, + @JsonProperty("linkedin") String linkedin, + @JsonProperty("twitter") String twitter, + @JsonProperty("website_url") String websiteUrl, + @JsonProperty("last_sign_in_at") Calendar lastSignInAt, + @JsonProperty("confirmed_at") Calendar confirmedAt, + @JsonProperty("last_activity_on") Calendar lastActivityOn, + @JsonProperty("email") String email, + @JsonProperty("theme_id") Integer themeId, + @JsonProperty("color_scheme_id") Integer colorSchemeId, + @JsonProperty("projects_limit") Integer projectsLimit, + @JsonProperty("current_sign_in_at") Calendar currentSignInAt, + @JsonProperty("can_create_group") boolean canCreateGroup, + @JsonProperty("can_create_project") boolean canCreateProject, + @JsonProperty("two_factor_enabled") boolean twoFactorEnabled, + @JsonProperty("external") boolean external, + @JsonProperty("is_admin") boolean isAdmin) { + this.id = id; + this.name = name; + this.username = username; + this.state = state; + this.avatarUrl = avatarUrl; + this.webUrl = webUrl; + this.createdAt = createdAt; + this.publicEmail = publicEmail; + this.skype = skype; + this.linkedin = linkedin; + this.twitter = twitter; + this.websiteUrl = websiteUrl; + this.lastSignInAt = lastSignInAt; + this.confirmedAt = confirmedAt; + this.lastActivityOn = lastActivityOn; + this.email = email; + this.themeId = themeId; + this.colorSchemeId = colorSchemeId; + this.projectsLimit = projectsLimit; + this.currentSignInAt = currentSignInAt; + this.canCreateGroup = canCreateGroup; + this.canCreateProject = canCreateProject; + this.twoFactorEnabled = twoFactorEnabled; + this.external = external; + this.isAdmin = isAdmin; + } - @JsonCreator - public User(@JsonProperty("username") String username) { - this.username = username; - } + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getUsername() { + return username; + } + + public String getState() { + return state; + } + + public String getAvatarUrl() { + return avatarUrl; + } + + public String getWebUrl() { + return webUrl; + } + + public Calendar getCreatedAt() { + return createdAt; + } + + public String getPublicEmail() { + return publicEmail; + } + + public String getSkype() { + return skype; + } + + public String getLinkedin() { + return linkedin; + } + + public String getTwitter() { + return twitter; + } + + public String getWebsiteUrl() { + return websiteUrl; + } + + public Calendar getLastSignInAt() { + return lastSignInAt; + } + + public Calendar getConfirmedAt() { + return confirmedAt; + } + + public Calendar getLastActivityOn() { + return lastActivityOn; + } + + public String getEmail() { + return email; + } + + public Integer getThemeId() { + return themeId; + } + + public Integer getColorSchemeId() { + return colorSchemeId; + } + + public Integer getProjectsLimit() { + return projectsLimit; + } + + public Calendar getCurrentSignInAt() { + return currentSignInAt; + } + + public boolean isCanCreateGroup() { + return canCreateGroup; + } + + public boolean isCanCreateProject() { + return canCreateProject; + } + + public boolean isTwoFactorEnabled() { + return twoFactorEnabled; + } + + public boolean isExternal() { + return external; + } + + public boolean isAdmin() { + return isAdmin; + } - public String getUsername() { - return username; - } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfigurationLoader.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfigurationLoader.java index d43d168be..916666d2c 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfigurationLoader.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfigurationLoader.java @@ -21,6 +21,7 @@ import org.apache.commons.lang.StringUtils; import org.sonar.api.CoreProperties; import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.System2; import org.sonar.core.config.ScannerProperties; import org.sonar.scanner.scan.branch.BranchConfiguration; import org.sonar.scanner.scan.branch.BranchConfigurationLoader; @@ -31,8 +32,11 @@ import org.sonar.scanner.scan.branch.ProjectPullRequests; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.Supplier; @@ -50,6 +54,12 @@ public class CommunityBranchConfigurationLoader implements BranchConfigurationLo private static final Set PULL_REQUEST_ANALYSIS_PARAMETERS = new HashSet<>( Arrays.asList(ScannerProperties.PULL_REQUEST_BRANCH, ScannerProperties.PULL_REQUEST_KEY, ScannerProperties.PULL_REQUEST_BASE)); + private final System2 system2; + + public CommunityBranchConfigurationLoader(System2 system2) { + this.system2 = system2; + } + @Override public BranchConfiguration load(Map localSettings, Supplier> supplier, @@ -82,6 +92,8 @@ public BranchConfiguration load(Map localSettings, Supplier localSettings, ProjectBranches projectBranches, ProjectPullRequests pullRequests) { + localSettings = autoConfigure(localSettings); + if (projectBranches.isEmpty()) { if (isTargetingDefaultBranch(localSettings)) { return new DefaultBranchConfiguration(); @@ -107,6 +119,27 @@ public BranchConfiguration load(Map localSettings, ProjectBranch return new DefaultBranchConfiguration(); } + private Map autoConfigure(Map localSettings) { + Map mutableLocalSettings=new HashMap<>(localSettings); + if (Boolean.parseBoolean(system2.envVariable("GITLAB_CI"))) { + //GitLab CI auto configuration + if (system2.envVariable("CI_MERGE_REQUEST_IID") != null) { + // we are inside a merge request + Optional.ofNullable(system2.envVariable("CI_MERGE_REQUEST_IID")).ifPresent( + v -> mutableLocalSettings.putIfAbsent(ScannerProperties.PULL_REQUEST_KEY, v)); + Optional.ofNullable(system2.envVariable("CI_MERGE_REQUEST_SOURCE_BRANCH_NAME")).ifPresent( + v -> mutableLocalSettings.putIfAbsent(ScannerProperties.PULL_REQUEST_BRANCH, v)); + Optional.ofNullable(system2.envVariable("CI_MERGE_REQUEST_TARGET_BRANCH_NAME")).ifPresent( + v -> mutableLocalSettings.putIfAbsent(ScannerProperties.PULL_REQUEST_BASE, v)); + } else { + // branch or tag + Optional.ofNullable(system2.envVariable("CI_COMMIT_REF_NAME")).ifPresent( + v -> mutableLocalSettings.putIfAbsent(ScannerProperties.BRANCH_NAME, v)); + } + } + return Collections.unmodifiableMap(mutableLocalSettings); + } + private static boolean isTargetingDefaultBranch(Map localSettings) { String name = StringUtils.trimToNull(localSettings.get(ScannerProperties.BRANCH_NAME)); String target = StringUtils.trimToNull(localSettings.get(ScannerProperties.BRANCH_TARGET)); diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPluginTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPluginTest.java index b77039317..68048085e 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPluginTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPluginTest.java @@ -36,6 +36,7 @@ import org.sonar.core.extension.CoreExtension; import java.util.Arrays; +import java.util.List; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -119,7 +120,7 @@ public void testServerSideLoad() { final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Object.class); verify(context, times(2)).addExtensions(argumentCaptor.capture(), argumentCaptor.capture()); - assertEquals(22, argumentCaptor.getAllValues().size()); + assertEquals(24, argumentCaptor.getAllValues().size()); assertEquals(Arrays.asList(CommunityBranchFeatureExtension.class, CommunityBranchSupportDelegate.class), argumentCaptor.getAllValues().subList(0, 2)); diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecoratorTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecoratorTest.java index a40b4b9aa..f6933227d 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecoratorTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecoratorTest.java @@ -63,9 +63,11 @@ public void decorateQualityGateStatus() { when(configuration.get(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_URL)).thenReturn(Optional.of(wireMockRule.baseUrl())); when(configuration.get(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_TOKEN)).thenReturn(Optional.of("token")); when(configuration.get(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_REPOSITORY_SLUG)).thenReturn(Optional.of(repositorySlug)); + when(configuration.get(GitlabServerPullRequestDecorator.PULLREQUEST_CAN_FAIL_PIPELINE_ENABLED)).thenReturn(Optional.of("true")); when(configuration.get(PullRequestBuildStatusDecorator.PULL_REQUEST_COMMENT_SUMMARY_ENABLED)).thenReturn(Optional.of("true")); when(configuration.get(PullRequestBuildStatusDecorator.PULL_REQUEST_DELETE_COMMENTS_ENABLED)).thenReturn(Optional.of("true")); when(configuration.get(PullRequestBuildStatusDecorator.PULL_REQUEST_FILE_COMMENT_ENABLED)).thenReturn(Optional.of("true")); + when(configuration.get(PullRequestBuildStatusDecorator.PULL_REQUEST_COMPACT_COMMENTS_ENABLED)).thenReturn(Optional.of("true")); QualityGate.Condition coverage = mock(QualityGate.Condition.class); when(coverage.getStatus()).thenReturn(QualityGate.EvaluationStatus.OK); @@ -87,7 +89,7 @@ public void decorateQualityGateStatus() { when(issueVisitor.getIssues()).thenReturn(Collections.singletonList(componentIssue)); when(analysisDetails.getPostAnalysisIssueVisitor()).thenReturn(issueVisitor); when(analysisDetails.createAnalysisSummary(Mockito.any())).thenReturn("summary"); - when(analysisDetails.createAnalysisIssueSummary(Mockito.any(), Mockito.any())).thenReturn("issue"); + when(analysisDetails.createAnalysisIssueSummary(Mockito.any(), Mockito.any(), Mockito.anyBoolean())).thenReturn("issue"); when(analysisDetails.getSCMPathForIssue(componentIssue)).thenReturn(Optional.of(filePath)); ScmInfoRepository scmInfoRepository = mock(ScmInfoRepository.class); diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfigurationLoaderTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfigurationLoaderTest.java index f484f34e5..c771714d5 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfigurationLoaderTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfigurationLoaderTest.java @@ -25,6 +25,7 @@ import org.junit.rules.ExpectedException; import org.sonar.api.CoreProperties; import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.System2; import org.sonar.scanner.scan.branch.BranchConfiguration; import org.sonar.scanner.scan.branch.BranchInfo; import org.sonar.scanner.scan.branch.BranchType; @@ -59,7 +60,7 @@ public ExpectedException expectedException() { @Test public void testExceptionWhenNoExistingBranchAndBranchParamsPresent() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); ProjectBranches branchInfo = mock(ProjectBranches.class); when(branchInfo.isEmpty()).thenReturn(true); @@ -75,7 +76,7 @@ public void testExceptionWhenNoExistingBranchAndBranchParamsPresent() { @Test public void testDefaultConfigWhenNoExistingBranchAndBranchNameParamMaster() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); ProjectBranches branchInfo = mock(ProjectBranches.class); when(branchInfo.isEmpty()).thenReturn(true); @@ -88,7 +89,7 @@ public void testDefaultConfigWhenNoExistingBranchAndBranchNameParamMaster() { @Test public void testErrorWhenNoExistingBranchAndBranchTargetMasterButNoSourceBranch() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); ProjectBranches branchInfo = mock(ProjectBranches.class); when(branchInfo.isEmpty()).thenReturn(true); @@ -107,7 +108,7 @@ public void testErrorWhenNoExistingBranchAndBranchTargetMasterButNoSourceBranch( @Test public void testDefaultConfigWhenNoExistingBranchAndBranchParamsAllMaster() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); ProjectBranches branchInfo = mock(ProjectBranches.class); when(branchInfo.isEmpty()).thenReturn(true); @@ -121,7 +122,7 @@ public void testDefaultConfigWhenNoExistingBranchAndBranchParamsAllMaster() { @Test public void testExceptionWhenNoExistingBranchAndPullRequestAndBranchParametersPresent() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); ProjectBranches branchInfo = mock(ProjectBranches.class); when(branchInfo.isEmpty()).thenReturn(true); @@ -140,7 +141,7 @@ public void testExceptionWhenNoExistingBranchAndPullRequestAndBranchParametersPr @Test public void testDefaultBranchInfoWhenNoBranchParametersSpecifiedAndNoBranchesExist() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); ProjectBranches branchInfo = mock(ProjectBranches.class); when(branchInfo.isEmpty()).thenReturn(true); @@ -155,14 +156,14 @@ public void testDefaultBranchInfoWhenNoBranchParametersSpecifiedAndNoBranchesExi @Test public void testDefaultBranchInfoWhenNoParametersSpecified() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); assertEquals(DefaultBranchConfiguration.class, testCase.load(new HashMap<>(), mock(ProjectBranches.class), mock(ProjectPullRequests.class)).getClass()); } @Test public void testValidBranchInfoWhenAllBranchParametersSpecified() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.branch.name", "feature/shortLivedFeatureBranch"); parameters.put("sonar.branch.target", "master"); @@ -190,7 +191,7 @@ public void testValidBranchInfoWhenAllBranchParametersSpecified() { @Test public void testValidBranchInfoWhenOnlySourceBranchSpecifiedAndMasterExists() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.branch.name", "feature/shortLivedBranch"); @@ -212,7 +213,7 @@ public void testValidBranchInfoWhenOnlySourceBranchSpecifiedAndMasterExists() { @Test public void testValidBranchInfoWhenOnlySourceBranchSpecifiedAndMasterExists2() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.branch.name", "feature/shortLivedBranch"); parameters.put("sonar.branch.target", ""); @@ -235,7 +236,7 @@ public void testValidBranchInfoWhenOnlySourceBranchSpecifiedAndMasterExists2() { @Test public void testExceptionWhenOnlySourceBranchSpecifiedAndNoMasterExists() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.branch.name", "feature/shortLivedBranch"); @@ -253,36 +254,10 @@ public void testExceptionWhenOnlySourceBranchSpecifiedAndNoMasterExists() { } - @Test - public void testShortLivedBranchInvalidTarget() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - parameters.put("sonar.branch.target", "feature/otherShortLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("feature/otherShortLivedBranch"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.SHORT); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("feature/otherShortLivedBranch")).thenReturn(mockTargetBranchInfo); - - expectedException.expect(MessageException.class); - expectedException.expectMessage(IsEqual.equalTo("Could not target requested branch")); - expectedException.expectCause(new CustomMatcher("Cause checker") { - @Override - public boolean matches(Object item) { - return item instanceof IllegalStateException && - ((IllegalStateException) item).getMessage().equals("Expected branch type of LONG but got SHORT"); - } - }); - - testCase.load(parameters, projectBranches, mock(ProjectPullRequests.class)); - } @Test public void testUnknownTargetBranch() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.branch.name", "feature/shortLivedBranch"); parameters.put("sonar.branch.target", "feature/otherShortLivedBranch"); @@ -303,66 +278,11 @@ public boolean matches(Object item) { } - @Test - public void testShortLivedBranchExistingSourceAllParametersCorrect() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - parameters.put("sonar.branch.target", "longLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("longLivedBranch"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - BranchInfo mockSourceBranchInfo = mock(BranchInfo.class); - when(mockSourceBranchInfo.name()).thenReturn("shortLivedBranch"); - when(mockSourceBranchInfo.type()).thenReturn(BranchType.SHORT); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("longLivedBranch")).thenReturn(mockTargetBranchInfo); - when(projectBranches.get("feature/shortLivedBranch")).thenReturn(mockSourceBranchInfo); - - BranchConfiguration result = testCase.load(parameters, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("longLivedBranch", result.targetBranchName()); - assertEquals("feature/shortLivedBranch", result.branchName()); - assertEquals("longLivedBranch", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - } - - @Test - public void testExistingShortLivedBranchOnlySourceParametersRetargetMaster() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("longLivedBranch"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - BranchInfo mockSourceBranchInfo = mock(BranchInfo.class); - when(mockSourceBranchInfo.name()).thenReturn("shortLivedBranch"); - when(mockSourceBranchInfo.branchTargetName()).thenReturn("otherLongLivedBranch"); - when(mockSourceBranchInfo.type()).thenReturn(BranchType.SHORT); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("master")).thenReturn(mockTargetBranchInfo); - when(projectBranches.get("feature/shortLivedBranch")).thenReturn(mockSourceBranchInfo); - when(projectBranches.defaultBranchName()).thenReturn("master"); - BranchConfiguration result = testCase.load(parameters, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("master", result.targetBranchName()); - assertEquals("feature/shortLivedBranch", result.branchName()); - assertEquals("master", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - } @Test - public void testExistingLongLivedBranchOnlySourceParameters() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + public void testExistingBranchOnlySourceParameters() { + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.branch.name", "longLivedBranch"); @@ -384,7 +304,7 @@ public void testExistingLongLivedBranchOnlySourceParameters() { @Test public void testPullRequestAllParameters() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.pullrequest.branch", "feature/sourceBranch"); parameters.put("sonar.pullrequest.base", "target"); @@ -410,7 +330,7 @@ public void testPullRequestAllParameters() { @Test public void testPullRequestMandatoryParameters() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.pullrequest.branch", "feature/sourceBranch"); parameters.put("sonar.pullrequest.key", "pr-key"); @@ -434,7 +354,7 @@ public void testPullRequestMandatoryParameters() { @Test public void testPullRequestMandatoryParameters2() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.pullrequest.branch", "feature/sourceBranch"); parameters.put("sonar.pullrequest.key", "pr-key"); @@ -460,7 +380,7 @@ public void testPullRequestMandatoryParameters2() { @Test public void testPullRequestNoSuchTarget() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.pullrequest.branch", "feature/sourceBranch"); parameters.put("sonar.pullrequest.base", "missingTarget"); @@ -482,518 +402,4 @@ public boolean matches(Object item) { testCase.load(parameters, projectBranches, mock(ProjectPullRequests.class)); } - @Test - public void testComputeBranchType() { - BranchInfo branchInfo = mock(BranchInfo.class); - when(branchInfo.type()).thenReturn(BranchType.LONG); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.defaultBranchName()).thenReturn("master"); - when(projectBranches.get(eq("master"))).thenReturn(branchInfo); - - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "release/1.2"); - parameters.put(CoreProperties.LONG_LIVED_BRANCHES_REGEX, "(master|release/.+)"); - - assertEquals(BranchType.LONG, new CommunityBranchConfigurationLoader() - .load(parameters, projectBranches, mock(ProjectPullRequests.class)).branchType()); - - parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "master-dummy"); - - assertEquals(BranchType.SHORT, new CommunityBranchConfigurationLoader() - .load(parameters, projectBranches, mock(ProjectPullRequests.class)).branchType()); - - } - - - @Test - public void testExceptionWhenNoExistingBranchAndBranchParamsPresentPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - ProjectBranches branchInfo = mock(ProjectBranches.class); - when(branchInfo.isEmpty()).thenReturn(true); - - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "dummy"); - - expectedException.expect(MessageException.class); - expectedException.expectMessage(IsEqual.equalTo( - "No branches currently exist in this project. Please scan the main branch without passing any branch parameters.")); - - testCase.load(parameters, supplier, branchInfo, mock(ProjectPullRequests.class)); - } - - @Test - public void testDefaultConfigWhenNoExistingBranchAndBranchNameParamMasterPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - ProjectBranches branchInfo = mock(ProjectBranches.class); - when(branchInfo.isEmpty()).thenReturn(true); - - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "master"); - - assertEquals(DefaultBranchConfiguration.class, - testCase.load(parameters, supplier, branchInfo, mock(ProjectPullRequests.class)).getClass()); - } - - @Test - public void testErrorWhenNoExistingBranchAndBranchTargetMasterButNoSourceBranchPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - ProjectBranches branchInfo = mock(ProjectBranches.class); - when(branchInfo.isEmpty()).thenReturn(true); - - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.source", null); - parameters.put("sonar.branch.target", "master"); - - - expectedException.expect(MessageException.class); - expectedException.expectMessage(IsEqual.equalTo( - "No branches currently exist in this project. Please scan the main branch without passing any branch parameters.")); - - testCase.load(parameters, supplier, branchInfo, mock(ProjectPullRequests.class)); - } - - - @Test - public void testDefaultConfigWhenNoExistingBranchAndBranchParamsAllMasterPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - ProjectBranches branchInfo = mock(ProjectBranches.class); - when(branchInfo.isEmpty()).thenReturn(true); - - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "master"); - parameters.put("sonar.branch.target", "master"); - - assertEquals(DefaultBranchConfiguration.class, - testCase.load(parameters, supplier, branchInfo, mock(ProjectPullRequests.class)).getClass()); - } - - @Test - public void testExceptionWhenNoExistingBranchAndPullRequestAndBranchParametersPresentPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - ProjectBranches branchInfo = mock(ProjectBranches.class); - when(branchInfo.isEmpty()).thenReturn(true); - - - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "dummy"); - parameters.put("sonar.pullrequest.branch", "dummy2"); - - - expectedException.expect(MessageException.class); - expectedException.expectMessage(IsEqual.equalTo( - "No branches currently exist in this project. Please scan the main branch without passing any branch parameters.")); - - testCase.load(parameters, supplier, branchInfo, mock(ProjectPullRequests.class)); - } - - @Test - public void testDefaultBranchInfoWhenNoBranchParametersSpecifiedAndNoBranchesExistPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - - ProjectBranches branchInfo = mock(ProjectBranches.class); - when(branchInfo.isEmpty()).thenReturn(true); - - Map parameters = new HashMap<>(); - parameters.put("dummy", "dummy"); - - - assertEquals(DefaultBranchConfiguration.class, - testCase.load(parameters, supplier, branchInfo, mock(ProjectPullRequests.class)).getClass()); - } - - @Test - public void testDefaultBranchInfoWhenNoParametersSpecifiedPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - assertEquals(DefaultBranchConfiguration.class, - testCase.load(new HashMap<>(), supplier, mock(ProjectBranches.class), - mock(ProjectPullRequests.class)).getClass()); - } - - @Test - public void testValidBranchInfoWhenAllBranchParametersSpecifiedPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedFeatureBranch"); - parameters.put("sonar.branch.target", "master"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("masterBranchInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("master")).thenReturn(mockTargetBranchInfo); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("master", result.targetBranchName()); - assertEquals("feature/shortLivedFeatureBranch", result.branchName()); - assertEquals("masterBranchInfo", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - - expectedException - .expectMessage(IsEqual.equalTo("Only a branch of type PULL_REQUEST can have a Pull Request key")); - expectedException.expect(IllegalStateException.class); - - result.pullRequestKey(); - } - - @Test - public void testValidBranchInfoWhenOnlySourceBranchSpecifiedAndMasterExistsPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("defaultBranchInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("masterxxx")).thenReturn(mockTargetBranchInfo); - when(projectBranches.defaultBranchName()).thenReturn("masterxxx"); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("masterxxx", result.targetBranchName()); - assertEquals("feature/shortLivedBranch", result.branchName()); - assertEquals("defaultBranchInfo", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - } - - @Test - public void testValidBranchInfoWhenOnlySourceBranchSpecifiedAndMasterExists2Pre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - parameters.put("sonar.branch.target", ""); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("defaultBranchInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("masterxxx")).thenReturn(mockTargetBranchInfo); - when(projectBranches.defaultBranchName()).thenReturn("masterxxx"); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("masterxxx", result.targetBranchName()); - assertEquals("feature/shortLivedBranch", result.branchName()); - assertEquals("defaultBranchInfo", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - } - - @Test - public void testExceptionWhenOnlySourceBranchSpecifiedAndNoMasterExistsPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("defaultBranchInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - - when(supplier.get()).thenReturn(new HashMap<>()); - - expectedException.expect(MessageException.class); - expectedException.expectMessage(IsEqual.equalTo("Could not target requested branch")); - - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - } - - - @Test - public void testShortLivedBranchInvalidTargetPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - parameters.put("sonar.branch.target", "feature/otherShortLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("feature/otherShortLivedBranch"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.SHORT); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("feature/otherShortLivedBranch")).thenReturn(mockTargetBranchInfo); - - when(supplier.get()).thenReturn(new HashMap<>()); - - expectedException.expect(MessageException.class); - expectedException.expectMessage(IsEqual.equalTo("Could not target requested branch")); - expectedException.expectCause(new CustomMatcher("Cause checker") { - @Override - public boolean matches(Object item) { - return item instanceof IllegalStateException && - ((IllegalStateException) item).getMessage().equals("Expected branch type of LONG but got SHORT"); - } - }); - - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - } - - @Test - public void testUnknownTargetBranchPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - parameters.put("sonar.branch.target", "feature/otherShortLivedBranch"); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - - when(supplier.get()).thenReturn(new HashMap<>()); - - expectedException.expect(MessageException.class); - expectedException.expectMessage(IsEqual.equalTo("Could not target requested branch")); - expectedException.expectCause(new CustomMatcher("Cause checker") { - @Override - public boolean matches(Object item) { - return item instanceof IllegalStateException && ((IllegalStateException) item).getMessage() - .equals("Target branch 'feature/otherShortLivedBranch' does not exist"); - } - }); - - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - } - - - @Test - public void testShortLivedBranchExistingSourceAllParametersCorrectPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - parameters.put("sonar.branch.target", "longLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("longLivedBranch"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - BranchInfo mockSourceBranchInfo = mock(BranchInfo.class); - when(mockSourceBranchInfo.name()).thenReturn("shortLivedBranch"); - when(mockSourceBranchInfo.type()).thenReturn(BranchType.SHORT); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("longLivedBranch")).thenReturn(mockTargetBranchInfo); - when(projectBranches.get("feature/shortLivedBranch")).thenReturn(mockSourceBranchInfo); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("longLivedBranch", result.targetBranchName()); - assertEquals("feature/shortLivedBranch", result.branchName()); - assertEquals("longLivedBranch", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - } - - @Test - public void testExistingShortLivedBranchOnlySourceParametersRetargetMasterPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("longLivedBranch"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - BranchInfo mockSourceBranchInfo = mock(BranchInfo.class); - when(mockSourceBranchInfo.name()).thenReturn("shortLivedBranch"); - when(mockSourceBranchInfo.branchTargetName()).thenReturn("otherLongLivedBranch"); - when(mockSourceBranchInfo.type()).thenReturn(BranchType.SHORT); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("master")).thenReturn(mockTargetBranchInfo); - when(projectBranches.get("feature/shortLivedBranch")).thenReturn(mockSourceBranchInfo); - when(projectBranches.defaultBranchName()).thenReturn("master"); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("master", result.targetBranchName()); - assertEquals("feature/shortLivedBranch", result.branchName()); - assertEquals("master", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - } - - @Test - public void testExistingLongLivedBranchOnlySourceParametersPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "longLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("longLivedBranch"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("longLivedBranch")).thenReturn(mockTargetBranchInfo); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertNull(result.targetBranchName()); - assertEquals("longLivedBranch", result.branchName()); - assertEquals("longLivedBranch", result.longLivingSonarReferenceBranch()); - assertFalse(result.isShortOrPullRequest()); - } - - @Test - public void testPullRequestAllParametersPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.pullrequest.branch", "feature/sourceBranch"); - parameters.put("sonar.pullrequest.base", "target"); - parameters.put("sonar.pullrequest.key", "pr-key"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("targetInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("target")).thenReturn(mockTargetBranchInfo); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("target", result.targetBranchName()); - assertEquals("feature/sourceBranch", result.branchName()); - assertEquals("target", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - assertEquals("pr-key", result.pullRequestKey()); - } - - - @Test - public void testPullRequestMandatoryParametersPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.pullrequest.branch", "feature/sourceBranch"); - parameters.put("sonar.pullrequest.key", "pr-key"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("masterInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("master")).thenReturn(mockTargetBranchInfo); - when(projectBranches.defaultBranchName()).thenReturn("master"); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("master", result.targetBranchName()); - assertEquals("feature/sourceBranch", result.branchName()); - assertEquals("master", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - } - - @Test - public void testPullRequestMandatoryParameters2Pre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.pullrequest.branch", "feature/sourceBranch"); - parameters.put("sonar.pullrequest.key", "pr-key"); - parameters.put("sonar.pullrequest.base", ""); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("masterInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("master")).thenReturn(mockTargetBranchInfo); - when(projectBranches.defaultBranchName()).thenReturn("master"); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("master", result.targetBranchName()); - assertEquals("feature/sourceBranch", result.branchName()); - assertEquals("master", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - } - - - @Test - public void testPullRequestNoSuchTargetPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.pullrequest.branch", "feature/sourceBranch"); - parameters.put("sonar.pullrequest.base", "missingTarget"); - parameters.put("sonar.pullrequest.key", "pr-key"); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - - when(supplier.get()).thenReturn(new HashMap<>()); - - expectedException.expect(MessageException.class); - expectedException.expectMessage(IsEqual.equalTo("Could not target requested branch")); - expectedException.expectCause(new CustomMatcher("Cause checker") { - @Override - public boolean matches(Object item) { - return item instanceof IllegalStateException && ((IllegalStateException) item).getMessage() - .equals("Target branch 'missingTarget' does not exist"); - } - }); - - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - } - - @Test - public void testComputeBranchTypePre79() { - Map settings = new HashMap<>(); - settings.put(CoreProperties.LONG_LIVED_BRANCHES_REGEX, "(master|release/.+)"); - when(supplier.get()).thenReturn(settings); - - BranchInfo branchInfo = mock(BranchInfo.class); - when(branchInfo.type()).thenReturn(BranchType.LONG); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.defaultBranchName()).thenReturn("master"); - when(projectBranches.get(eq("master"))).thenReturn(branchInfo); - - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "release/1.2"); - - assertEquals(BranchType.LONG, new CommunityBranchConfigurationLoader() - .load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)).branchType()); - - parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "master-dummy"); - - assertEquals(BranchType.SHORT, new CommunityBranchConfigurationLoader() - .load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)).branchType()); - - } - - }