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

Commit

Permalink
Feature: mc1arke#135 / mc1arke#96 comments filter and limiter.
Browse files Browse the repository at this point in the history
implemented a filter system with 3 filters (maxamount,severy,type)
added commentfilter to the githubpullrequest decorator
fixed the tests I broke with adding the filter
added test to verify the filter is called for the Github Graphql
wrote test for IssueFilterRunner.
Added test for TypeExclusionFilter and SeverityExclusionFilter
Added tests for TypeComparator
Added tests for SeverityComparator
Added default comparators for IssueFilterRunner for real build.
  • Loading branch information
Lex Felix authored and PiekJ committed Aug 3, 2020
1 parent a606728 commit 980e861
Show file tree
Hide file tree
Showing 23 changed files with 885 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

import com.github.mc1arke.sonarqube.plugin.ce.CommunityBranchEditionProvider;
import com.github.mc1arke.sonarqube.plugin.ce.CommunityReportAnalysisComponentProvider;
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PullRequestPostAnalysisTask;
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.GitlabServerPullRequestDecorator;
import com.github.mc1arke.sonarqube.plugin.scanner.CommunityBranchConfigurationLoader;
import com.github.mc1arke.sonarqube.plugin.scanner.CommunityBranchParamsValidator;
import com.github.mc1arke.sonarqube.plugin.scanner.CommunityProjectBranchesLoader;
Expand Down Expand Up @@ -52,16 +54,21 @@
import org.sonar.api.SonarQubeSide;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleType;
import org.sonar.core.config.PurgeConstants;
import org.sonar.core.extension.CoreExtension;

import java.util.Arrays;

/**
* @author Michael Clarke
*/
public class CommunityBranchPlugin implements Plugin, CoreExtension {

public static final String IMAGE_URL_BASE = "com.github.mc1arke.sonarqube.plugin.branch.image-url-base";

private static final String PULL_REQUEST_CATEGORY_LABEL = "Pull Request Decoration Filters";
private static final String PULL_REQUEST_DECORATIONS_LABEL = "Filters";
@Override
public String getName() {
return "Community Branch Plugin";
Expand All @@ -74,46 +81,57 @@ public void load(CoreExtension.Context context) {
} else if (SonarQubeSide.SERVER == context.getRuntime().getSonarQubeSide()) {
context.addExtensions(CommunityBranchFeatureExtension.class, CommunityBranchSupportDelegate.class,

AlmSettingsWs.class, CountBindingAction.class, DeleteAction.class,
DeleteBindingAction.class, ListAction.class, ListDefinitionsAction.class,
GetBindingAction.class,
AlmSettingsWs.class, CountBindingAction.class, DeleteAction.class,
DeleteBindingAction.class, ListAction.class, ListDefinitionsAction.class,
GetBindingAction.class,

CreateGithubAction.class, SetGithubBindingAction.class, UpdateGithubAction.class,
CreateGithubAction.class, SetGithubBindingAction.class, UpdateGithubAction.class,

CreateAzureAction.class, SetAzureBindingAction.class, UpdateAzureAction.class,
CreateAzureAction.class, SetAzureBindingAction.class, UpdateAzureAction.class,

CreateBitbucketAction.class, SetBitbucketBindingAction.class,
UpdateBitbucketAction.class,
CreateBitbucketAction.class, SetBitbucketBindingAction.class,
UpdateBitbucketAction.class,

CreateGitlabAction.class, SetGitlabBindingAction.class, UpdateGitlabAction.class,
CreateGitlabAction.class, SetGitlabBindingAction.class, UpdateGitlabAction.class,

/* org.sonar.db.purge.PurgeConfiguration uses the value for the this property if it's configured, so it only
needs to be specified here, but doesn't need any additional classes to perform the relevant purge/cleanup
*/
PropertyDefinition
.builder(PurgeConstants.DAYS_BEFORE_DELETING_INACTIVE_BRANCHES_AND_PRS)
.name("Number of days before purging inactive short living branches")
.description(
"Short living branches are permanently deleted when there are no analysis for the configured number of days.")
.category(CoreProperties.CATEGORY_HOUSEKEEPING)
.subCategory(CoreProperties.SUBCATEGORY_GENERAL).defaultValue("30")
.type(PropertyType.INTEGER).build()
PropertyDefinition
.builder(PurgeConstants.DAYS_BEFORE_DELETING_INACTIVE_BRANCHES_AND_PRS)
.name("Number of days before purging inactive short living branches")
.description(
"Short living branches are permanently deleted when there are no analysis for the configured number of days.")
.category(CoreProperties.CATEGORY_HOUSEKEEPING)
.subCategory(CoreProperties.SUBCATEGORY_GENERAL).defaultValue("30")
.type(PropertyType.INTEGER).build()


);
);

}

if (SonarQubeSide.COMPUTE_ENGINE == context.getRuntime().getSonarQubeSide() ||
SonarQubeSide.SERVER == context.getRuntime().getSonarQubeSide()) {
SonarQubeSide.SERVER == context.getRuntime().getSonarQubeSide()) {
context.addExtensions(PropertyDefinition.builder(IMAGE_URL_BASE)
.category(CoreProperties.CATEGORY_GENERAL)
.subCategory(CoreProperties.SUBCATEGORY_GENERAL)
.onQualifiers(Qualifiers.APP)
.name("Images base URL")
.description("Base URL used to load the images for the PR comments (please use this only if images are not displayed properly).")
.type(PropertyType.STRING)
.build());
.category(CoreProperties.CATEGORY_GENERAL)
.subCategory(CoreProperties.SUBCATEGORY_GENERAL)
.onQualifiers(Qualifiers.APP)
.name("Images base URL")
.description("Base URL used to load the images for the PR comments (please use this only if images are not displayed properly).")
.type(PropertyType.STRING)
.build(),
PropertyDefinition.builder(PullRequestPostAnalysisTask.PULLREQUEST_FILTER_TYPE_EXCLUSION).category(PULL_REQUEST_CATEGORY_LABEL).subCategory(PULL_REQUEST_DECORATIONS_LABEL)
.onQualifiers(Qualifiers.PROJECT).name("RuleType Exclusions").description("Comma-separated list of ruletypes you want to exclude, possible values: CODE_SMELL, BUG, VULNERABILITY, SECURITY_HOTSPOT")
.type(PropertyType.STRING).options(RuleType.BUG.name(), RuleType.CODE_SMELL.name(), RuleType.VULNERABILITY.name(),RuleType.SECURITY_HOTSPOT.name()).build(),
PropertyDefinition.builder(PullRequestPostAnalysisTask.PULLREQUEST_FILTER_SEVERITY_EXCLUSION).category(PULL_REQUEST_CATEGORY_LABEL).subCategory(PULL_REQUEST_DECORATIONS_LABEL)
.onQualifiers(Qualifiers.PROJECT).name("Severity Exclusions").description("Comma-separated list of severity levels you want to exclude, possible values: INFO, MINOR, MAJOR, CRITICAL, BLOCKER")
.type(PropertyType.STRING).options(Severity.ALL).build(),
PropertyDefinition.builder(PullRequestPostAnalysisTask.PULLREQUEST_FILTER_MAXAMOUNT).category(PULL_REQUEST_CATEGORY_LABEL).subCategory(PULL_REQUEST_DECORATIONS_LABEL)
.onQualifiers(Qualifiers.PROJECT).name("Max amount").description("Max amount of comments to be added to the pull request, must be > 0")
.type(PropertyType.INTEGER).build()

);

}
}
Expand All @@ -122,8 +140,8 @@ public void load(CoreExtension.Context context) {
public void define(Plugin.Context context) {
if (SonarQubeSide.SCANNER == context.getRuntime().getSonarQubeSide()) {
context.addExtensions(CommunityProjectBranchesLoader.class, CommunityProjectPullRequestsLoader.class,
CommunityBranchConfigurationLoader.class, CommunityBranchParamsValidator.class,
ScannerPullRequestPropertySensor.class);
CommunityBranchConfigurationLoader.class, CommunityBranchParamsValidator.class,
ScannerPullRequestPropertySensor.class);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,18 @@
*/
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest;

import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.commentfilter.IssueFilterRunner;
import org.sonar.db.alm.setting.ALM;
import org.sonar.db.alm.setting.AlmSettingDto;
import org.sonar.db.alm.setting.ProjectAlmSettingDto;

public interface PullRequestBuildStatusDecorator {

DecorationResult decorateQualityGateStatus(AnalysisDetails analysisDetails, AlmSettingDto almSettingDto,
ProjectAlmSettingDto projectAlmSettingDto);
ProjectAlmSettingDto projectAlmSettingDto);

DecorationResult decorateQualityGateStatus(AnalysisDetails analysisDetails, AlmSettingDto almSettingDto,
ProjectAlmSettingDto projectAlmSettingDto, IssueFilterRunner issueFilterRunner);

ALM alm();
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
*/
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest;

import org.sonar.api.ce.posttask.Analysis;
import org.sonar.api.ce.posttask.Branch;
import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
import org.sonar.api.ce.posttask.QualityGate;
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.commentfilter.IssueFilterRunner;
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.commentfilter.SeverityExclusionFilter;
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.commentfilter.TypeExclusionFilter;
import org.checkerframework.checker.nullness.Opt;
import org.sonar.api.ce.posttask.*;
import org.sonar.api.config.Configuration;
import org.sonar.api.platform.Server;
import org.sonar.api.utils.log.Logger;
Expand All @@ -36,14 +37,20 @@
import org.sonar.db.alm.setting.ProjectAlmSettingDto;
import org.sonar.db.component.BranchDao;
import org.sonar.db.component.BranchDto;
import org.sonar.db.property.PropertyDto;
import org.sonar.db.protobuf.DbProjectBranches;
import org.sonar.server.setting.ws.Setting;

import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Stream;

public class PullRequestPostAnalysisTask implements PostProjectAnalysisTask {

private static final Logger LOGGER = Loggers.get(PullRequestPostAnalysisTask.class);
public static final String PULLREQUEST_FILTER_SEVERITY_EXCLUSION = "sonar.pullrequest.comment.filter.severity.exclusions";
public static final String PULLREQUEST_FILTER_TYPE_EXCLUSION = "sonar.pullrequest.comment.filter.type.exclusions";
public static final String PULLREQUEST_FILTER_MAXAMOUNT = "sonar.pullrequest.comment.filter.maxamount";

private final List<PullRequestBuildStatusDecorator> pullRequestDecorators;
private final Server server;
Expand Down Expand Up @@ -78,6 +85,7 @@ public String getDescription() {
@Override
public void finished(Context context) {
ProjectAnalysis projectAnalysis = context.getProjectAnalysis();

LOGGER.debug("found " + pullRequestDecorators.size() + " pull request decorators");
Optional<Branch> optionalPullRequest =
projectAnalysis.getBranch().filter(branch -> Branch.Type.PULL_REQUEST == branch.getType());
Expand All @@ -94,11 +102,14 @@ public void finished(Context context) {

ProjectAlmSettingDto projectAlmSettingDto;
Optional<AlmSettingDto> optionalAlmSettingDto;
List<PropertyDto> projectProperties;
try (DbSession dbSession = dbClient.openSession(false)) {

Optional<ProjectAlmSettingDto> optionalProjectAlmSettingDto =
dbClient.projectAlmSettingDao().selectByProject(dbSession, projectAnalysis.getProject().getUuid());

projectProperties = dbClient.propertiesDao().selectProjectProperties(dbSession, projectAnalysis.getProject().getKey());

if (!optionalProjectAlmSettingDto.isPresent()) {
LOGGER.debug("No ALM has been set on the current project");
return;
Expand Down Expand Up @@ -145,23 +156,63 @@ public void finished(Context context) {
return;
}


String commitId = revision.get();

AnalysisDetails analysisDetails =
new AnalysisDetails(new AnalysisDetails.BranchDetails(optionalBranchName.get(), commitId),
postAnalysisIssueVisitor, qualityGate,
new AnalysisDetails.MeasuresHolder(metricRepository, measureRepository,
treeRootHolder), analysis,
projectAnalysis.getProject(), configuration, server.getPublicRootUrl(),
projectAnalysis.getScannerContext());
postAnalysisIssueVisitor, qualityGate,
new AnalysisDetails.MeasuresHolder(metricRepository, measureRepository,
treeRootHolder), analysis,
projectAnalysis.getProject(), configuration, server.getPublicRootUrl(),
projectAnalysis.getScannerContext());

PullRequestBuildStatusDecorator pullRequestDecorator = optionalPullRequestDecorator.get();
LOGGER.info("using pull request decorator " + pullRequestDecorator.alm().getId());
DecorationResult decorationResult = pullRequestDecorator.decorateQualityGateStatus(analysisDetails, almSettingDto, projectAlmSettingDto);

Optional<IssueFilterRunner> optionalIssueFilterRunner = getIssueFilterList(projectProperties);

DecorationResult decorationResult;
if (optionalIssueFilterRunner.isPresent())
decorationResult = pullRequestDecorator.decorateQualityGateStatus(analysisDetails, almSettingDto, projectAlmSettingDto, optionalIssueFilterRunner.get());
else
decorationResult = pullRequestDecorator.decorateQualityGateStatus(analysisDetails, almSettingDto, projectAlmSettingDto);

decorationResult.getPullRequestUrl().ifPresent(pullRequestUrl -> persistPullRequestUrl(pullRequestUrl, projectAnalysis, optionalBranchName.get()));
}

private Optional<IssueFilterRunner> getIssueFilterList(List<PropertyDto> projectProperties) {
List<Predicate<PostAnalysisIssueVisitor.ComponentIssue>> filterList = new ArrayList<>();


Optional<String> optionalSeverityExclusion = projectProperties.stream().filter(propertyDto -> propertyDto.getKey().equals(PULLREQUEST_FILTER_SEVERITY_EXCLUSION))
.map(PropertyDto::getValue)
.filter(Objects::nonNull)
.findAny();
Optional<String> optionalTypeExclusion = projectProperties.stream().filter(propertyDto -> propertyDto.getKey().equals(PULLREQUEST_FILTER_TYPE_EXCLUSION))
.map(PropertyDto::getValue)
.filter(Objects::nonNull)
.findAny();
Optional<Integer> optionalMaxAmount = projectProperties.stream().filter(propertyDto -> propertyDto.getKey().equals(PULLREQUEST_FILTER_MAXAMOUNT))
.map(PropertyDto::getValue)
.filter(Objects::nonNull)
.map(Integer::parseInt)
.findAny();

optionalSeverityExclusion = Optional.ofNullable(optionalSeverityExclusion.orElseGet(() -> configuration.get(PULLREQUEST_FILTER_SEVERITY_EXCLUSION).orElse(null)));
optionalTypeExclusion = Optional.ofNullable(optionalTypeExclusion.orElseGet(() -> configuration.get(PULLREQUEST_FILTER_TYPE_EXCLUSION).orElse(null)));
optionalMaxAmount = Optional.ofNullable(optionalMaxAmount.orElseGet(() -> configuration.getInt(PULLREQUEST_FILTER_MAXAMOUNT).orElse(null)));

optionalSeverityExclusion.ifPresent(severityString -> filterList.add(new SeverityExclusionFilter(severityString)));
optionalTypeExclusion.ifPresent(typeString -> filterList.add(new TypeExclusionFilter(typeString)));

if (filterList.isEmpty() && !optionalMaxAmount.isPresent()) {
return Optional.empty();
} else {
return Optional.of(new IssueFilterRunner(filterList, optionalMaxAmount.orElse(null)));
}
}


private static Optional<PullRequestBuildStatusDecorator> findCurrentPullRequestStatusDecorator(
AlmSettingDto almSetting, List<PullRequestBuildStatusDecorator> pullRequestDecorators) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.DataValue;
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.ReportData;
import com.google.common.annotations.VisibleForTesting;
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.commentfilter.IssueFilterRunner;
import org.sonar.api.ce.posttask.QualityGate;
import org.sonar.api.issue.Issue;
import org.sonar.api.measures.CoreMetrics;
Expand Down Expand Up @@ -70,6 +71,11 @@ public class BitbucketPullRequestDecorator implements PullRequestBuildStatusDeco

@Override
public DecorationResult decorateQualityGateStatus(AnalysisDetails analysisDetails, AlmSettingDto almSettingDto, ProjectAlmSettingDto projectAlmSettingDto) {
return decorateQualityGateStatus(analysisDetails, almSettingDto, projectAlmSettingDto, null);
}

@Override
public DecorationResult decorateQualityGateStatus(AnalysisDetails analysisDetails, AlmSettingDto almSettingDto, ProjectAlmSettingDto projectAlmSettingDto, IssueFilterRunner issueFilterRunner) {
String project = projectAlmSettingDto.getAlmRepo();
String repo = projectAlmSettingDto.getAlmSlug();
String url = almSettingDto.getUrl();
Expand Down

0 comments on commit 980e861

Please sign in to comment.