diff options
author | lukasz-jarocki-sonarsource <lukasz.jarocki@sonarsource.com> | 2023-12-20 16:02:32 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-01-17 20:02:44 +0000 |
commit | 90af6b13ef8e0f62ba6492fd4cd730643f9c96ec (patch) | |
tree | 561728b9e3a3096c3c49e66ed42d1e7e0638f86c /server/sonar-webserver-es | |
parent | a8d2df8b6546115dd219a28c913f035f63cdcfd0 (diff) | |
download | sonarqube-90af6b13ef8e0f62ba6492fd4cd730643f9c96ec.tar.gz sonarqube-90af6b13ef8e0f62ba6492fd4cd730643f9c96ec.zip |
SONAR-21259 added new param to api/issues/search
Diffstat (limited to 'server/sonar-webserver-es')
5 files changed, 146 insertions, 15 deletions
diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java index 677830a4ba0..df616a13ebc 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java @@ -465,7 +465,7 @@ public class IssueIndex { } // Field Filters - filters.addFilter(FIELD_ISSUE_KEY, new SimpleFieldFilterScope(FIELD_ISSUE_KEY), createTermsFilter(FIELD_ISSUE_KEY, query.issueKeys())); + filters.addFilter(FIELD_ISSUE_KEY, new SimpleFieldFilterScope(FIELD_ISSUE_KEY), createTermsFilterForNullableCollection(FIELD_ISSUE_KEY, query.issueKeys())); filters.addFilter(FIELD_ISSUE_ASSIGNEE_UUID, ASSIGNEES.getFilterScope(), createTermsFilter(FIELD_ISSUE_ASSIGNEE_UUID, query.assignees())); filters.addFilter(FIELD_ISSUE_SCOPE, SCOPES.getFilterScope(), createTermsFilter(FIELD_ISSUE_SCOPE, query.scopes())); filters.addFilter(FIELD_ISSUE_LANGUAGE, LANGUAGES.getFilterScope(), createTermsFilter(FIELD_ISSUE_LANGUAGE, query.languages())); @@ -741,11 +741,24 @@ public class IssueIndex { return FACET_MODE_EFFORT.equals(query.facetMode()); } + /** + * This method is for creating a filter that passes null to the elasticsearch query whenever empty or null collection is passed. + * This means that filter will not filter anything, all the documents (issues) will be returned in this case. + */ @CheckForNull private static QueryBuilder createTermsFilter(String field, Collection<?> values) { return values.isEmpty() ? null : termsQuery(field, values); } + /** + * This method is for creating a filter that passes null to the elasticsearch query only when null collection is passed. + * This ensures that whenever we pass empty collection to the filter, it will filter out all the documents (issues). + */ + @CheckForNull + private static QueryBuilder createTermsFilterForNullableCollection(String field, @Nullable Collection<?> values) { + return values != null ? termsQuery(field, values) : null; + } + @CheckForNull private static QueryBuilder createTermFilter(String field, @Nullable String value) { return value == null ? null : termQuery(field, value); diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java index 0612edb8b1e..5da45db5877 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java @@ -105,7 +105,7 @@ public class IssueQuery { private final Collection<String> cleanCodeAttributesCategories; private IssueQuery(Builder builder) { - this.issueKeys = defaultCollection(builder.issueKeys); + this.issueKeys = nullableDefaultCollection(builder.issueKeys); this.severities = defaultCollection(builder.severities); this.impactSeverities = defaultCollection(builder.impactSeverities); this.impactSoftwareQualities = defaultCollection(builder.impactSoftwareQualities); @@ -679,6 +679,10 @@ public class IssueQuery { return c == null ? Collections.emptyList() : Collections.unmodifiableCollection(c); } + private static <T> Collection<T> nullableDefaultCollection(@Nullable Collection<T> c) { + return c == null ? null : Collections.unmodifiableCollection(c); + } + private static <K, V> Map<K, V> defaultMap(@Nullable Map<K, V> map) { return map == null ? Collections.emptyMap() : Collections.unmodifiableMap(map); } diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java index 8f602db25f4..1222629845f 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java @@ -53,7 +53,9 @@ import org.sonar.db.DbSession; import org.sonar.db.component.BranchDto; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.SnapshotDto; +import org.sonar.db.issue.IssueFixedDto; import org.sonar.db.permission.GlobalPermission; +import org.sonar.db.project.ProjectDto; import org.sonar.db.rule.RuleDto; import org.sonar.server.issue.SearchRequest; import org.sonar.server.issue.index.IssueQuery.PeriodStart; @@ -79,6 +81,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_UUIDS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_IN_LAST; +import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FIXED_IN_PULL_REQUEST; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IN_NEW_CODE_PERIOD; /** @@ -99,7 +102,8 @@ public class IssueQueryFactory { .map(Enum::name) .collect(Collectors.toSet()); private static final ComponentDto UNKNOWN_COMPONENT = new ComponentDto().setUuid(UNKNOWN).setBranchUuid(UNKNOWN); - private static final Set<String> QUALIFIERS_WITHOUT_LEAK_PERIOD = new HashSet<>(Arrays.asList(Qualifiers.APP, Qualifiers.VIEW, Qualifiers.SUBVIEW)); + private static final Set<String> QUALIFIERS_WITHOUT_LEAK_PERIOD = new HashSet<>(Arrays.asList(Qualifiers.APP, Qualifiers.VIEW, + Qualifiers.SUBVIEW)); private final DbClient dbClient; private final Clock clock; private final UserSession userSession; @@ -116,13 +120,14 @@ public class IssueQueryFactory { Collection<RuleDto> ruleDtos = ruleKeysToRuleId(dbSession, request.getRules()); Collection<String> ruleUuids = ruleDtos.stream().map(RuleDto::getUuid).collect(Collectors.toSet()); + Collection<String> issueKeys = collectIssueKeys(dbSession, request); if (request.getRules() != null && request.getRules().stream().collect(Collectors.toSet()).size() != ruleDtos.size()) { ruleUuids.add("non-existing-uuid"); } IssueQuery.Builder builder = IssueQuery.builder() - .issueKeys(request.getIssues()) + .issueKeys(issueKeys) .severities(request.getSeverities()) .cleanCodeAttributesCategories(request.getCleanCodeAttributesCategories()) .impactSoftwareQualities(request.getImpactSoftwareQualities()) @@ -169,6 +174,49 @@ public class IssueQueryFactory { } } + private Collection<String> collectIssueKeys(DbSession dbSession, SearchRequest request) { + Collection<String> issueKeys = null; + if (request.getFixedInPullRequest() != null) { + issueKeys = getIssuesFixedByPullRequest(dbSession, request); + } + if (request.getIssues() != null && !request.getIssues().isEmpty()) { + if (issueKeys == null) { + issueKeys = new ArrayList<>(); + } + issueKeys.addAll(request.getIssues()); + } + + return issueKeys; + } + + private Collection<String> getIssuesFixedByPullRequest(DbSession dbSession, SearchRequest request) { + String fixedInPullRequest = request.getFixedInPullRequest(); + List<String> componentKeys = request.getComponentKeys(); + if (componentKeys == null || componentKeys.size() != 1) { + throw new IllegalArgumentException("Exactly one project needs to be provided in the " + + "'" + PARAM_COMPONENTS + "' param when used together with '" + PARAM_FIXED_IN_PULL_REQUEST + "' param"); + } + String projectKey = componentKeys.get(0); + ProjectDto projectDto = dbClient.projectDao().selectProjectByKey(dbSession, projectKey) + .orElseThrow(() -> new IllegalArgumentException("Project with key '" + projectKey + "' does not exist")); + BranchDto pullRequest = dbClient.branchDao().selectByPullRequestKey(dbSession, projectDto.getUuid(), fixedInPullRequest) + .orElseThrow(() -> new IllegalArgumentException("Pull request with key '" + fixedInPullRequest + "' does not exist for a project " + + projectKey)); + + if (request.getBranch() != null) { + BranchDto targetBranch = dbClient.branchDao().selectByBranchKey(dbSession, projectDto.getUuid(), request.getBranch()) + .orElseThrow(() -> new IllegalArgumentException("Branch with key '" + request.getBranch() + "' does not exist")); + if (!Objects.equals(targetBranch.getUuid(), pullRequest.getMergeBranchUuid())) { + throw new IllegalArgumentException("Pull request with key '" + fixedInPullRequest + "' does not target branch '" + request.getBranch() + "'"); + } + } + return dbClient.issueFixedDao().selectByPullRequest(dbSession, pullRequest.getUuid()) + .stream() + .map(IssueFixedDto::issueKey) + .collect(Collectors.toSet()); + } + + private static Optional<ZoneId> parseTimeZone(@Nullable String timeZone) { if (timeZone == null) { return Optional.empty(); @@ -181,7 +229,8 @@ public class IssueQueryFactory { } } - private void setCreatedAfterFromDates(IssueQuery.Builder builder, @Nullable Date createdAfter, @Nullable String createdInLast, boolean createdAfterInclusive) { + private void setCreatedAfterFromDates(IssueQuery.Builder builder, @Nullable Date createdAfter, @Nullable String createdInLast, + boolean createdAfterInclusive) { Date actualCreatedAfter = createdAfter; if (createdInLast != null) { actualCreatedAfter = Date.from( @@ -192,16 +241,19 @@ public class IssueQueryFactory { builder.createdAfter(actualCreatedAfter, createdAfterInclusive); } - private void setCreatedAfterFromRequest(DbSession dbSession, IssueQuery.Builder builder, SearchRequest request, List<ComponentDto> componentUuids, ZoneId timeZone) { + private void setCreatedAfterFromRequest(DbSession dbSession, IssueQuery.Builder builder, SearchRequest request, + List<ComponentDto> componentUuids, ZoneId timeZone) { Date createdAfter = parseStartingDateOrDateTime(request.getCreatedAfter(), timeZone); String createdInLast = request.getCreatedInLast(); if (notInNewCodePeriod(request)) { - checkArgument(createdAfter == null || createdInLast == null, format("Parameters %s and %s cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_CREATED_IN_LAST)); + checkArgument(createdAfter == null || createdInLast == null, format("Parameters %s and %s cannot be set simultaneously", + PARAM_CREATED_AFTER, PARAM_CREATED_IN_LAST)); setCreatedAfterFromDates(builder, createdAfter, createdInLast, true); } else { // If the filter is on leak period - checkArgument(createdAfter == null, "Parameters '%s' and '%s' cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_IN_NEW_CODE_PERIOD); + checkArgument(createdAfter == null, "Parameters '%s' and '%s' cannot be set simultaneously", PARAM_CREATED_AFTER, + PARAM_IN_NEW_CODE_PERIOD); checkArgument(createdInLast == null, format("Parameters '%s' and '%s' cannot be set simultaneously", PARAM_CREATED_IN_LAST, PARAM_IN_NEW_CODE_PERIOD)); @@ -306,7 +358,8 @@ public class IssueQueryFactory { .collect(Collectors.toSet()); } - private void addComponentsBasedOnQualifier(IssueQuery.Builder builder, DbSession dbSession, List<ComponentDto> components, SearchRequest request) { + private void addComponentsBasedOnQualifier(IssueQuery.Builder builder, DbSession dbSession, List<ComponentDto> components, + SearchRequest request) { if (components.isEmpty()) { return; } @@ -369,7 +422,8 @@ public class IssueQueryFactory { builder.viewUuids(filteredViewUuids); } - private void addApplications(IssueQuery.Builder builder, DbSession dbSession, List<ComponentDto> appBranchComponents, SearchRequest request) { + private void addApplications(IssueQuery.Builder builder, DbSession dbSession, List<ComponentDto> appBranchComponents, + SearchRequest request) { Set<String> authorizedAppBranchUuids = appBranchComponents.stream() .filter(app -> userSession.hasComponentPermission(USER, app) && userSession.hasChildProjectsPermission(USER, app)) .map(ComponentDto::uuid) @@ -379,7 +433,8 @@ public class IssueQueryFactory { addCreatedAfterByProjects(builder, dbSession, request, authorizedAppBranchUuids); } - private void addCreatedAfterByProjects(IssueQuery.Builder builder, DbSession dbSession, SearchRequest request, Set<String> appBranchUuids) { + private void addCreatedAfterByProjects(IssueQuery.Builder builder, DbSession dbSession, SearchRequest request, + Set<String> appBranchUuids) { if (notInNewCodePeriod(request) || request.getPullRequest() != null) { return; } @@ -415,7 +470,8 @@ public class IssueQueryFactory { builder.directories(paths); } - private List<ComponentDto> getComponentsFromKeys(DbSession dbSession, Collection<String> componentKeys, @Nullable String branch, @Nullable String pullRequest) { + private List<ComponentDto> getComponentsFromKeys(DbSession dbSession, Collection<String> componentKeys, @Nullable String branch, + @Nullable String pullRequest) { List<ComponentDto> componentDtos = dbClient.componentDao().selectByKeys(dbSession, componentKeys, branch, pullRequest); if (!componentKeys.isEmpty() && componentDtos.isEmpty()) { return singletonList(UNKNOWN_COMPONENT); diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java index f84916093e5..2d98b68b1fb 100644 --- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java +++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java @@ -136,6 +136,64 @@ public class IssueQueryFactoryTest { } @Test + public void getIssuesFixedByPullRequest_returnIssuesFixedByThePullRequest() { + String ruleAdHocName = "New Name"; + UserDto user = db.users().insertUser(u -> u.setLogin("joanna")); + ProjectData projectData = db.components().insertPrivateProject(); + ComponentDto project = projectData.getMainBranchComponent(); + ComponentDto file = db.components().insertComponent(newFileDto(project)); + + RuleDto rule1 = ruleDbTester.insert(r -> r.setAdHocName(ruleAdHocName)); + RuleDto rule2 = ruleDbTester.insert(r -> r.setAdHocName(ruleAdHocName)); + newRule(RuleKey.of("findbugs", "NullReference")); + SearchRequest request = new SearchRequest() + .setIssues(asList("anIssueKey")) + .setSeverities(asList("MAJOR", "MINOR")) + .setStatuses(asList("CLOSED")) + .setResolutions(asList("FALSE-POSITIVE")) + .setResolved(true) + .setProjectKeys(asList(project.getKey())) + .setDirectories(asList("aDirPath")) + .setFiles(asList(file.uuid())) + .setAssigneesUuid(asList(user.getUuid())) + .setScopes(asList("MAIN", "TEST")) + .setLanguages(asList("xoo")) + .setTags(asList("tag1", "tag2")) + .setAssigned(true) + .setCreatedAfter("2013-04-16T09:08:24+0200") + .setCreatedBefore("2013-04-17T09:08:24+0200") + .setRules(asList(rule1.getKey().toString(), rule2.getKey().toString())) + .setSort("CREATION_DATE") + .setAsc(true) + .setCodeVariants(asList("variant1", "variant2")); + + IssueQuery query = underTest.create(request); + + assertThat(query.issueKeys()).containsOnly("anIssueKey"); + assertThat(query.severities()).containsOnly("MAJOR", "MINOR"); + assertThat(query.statuses()).containsOnly("CLOSED"); + assertThat(query.resolutions()).containsOnly("FALSE-POSITIVE"); + assertThat(query.resolved()).isTrue(); + assertThat(query.projectUuids()).containsOnly(projectData.projectUuid()); + assertThat(query.files()).containsOnly(file.uuid()); + assertThat(query.assignees()).containsOnly(user.getUuid()); + assertThat(query.scopes()).containsOnly("TEST", "MAIN"); + assertThat(query.languages()).containsOnly("xoo"); + assertThat(query.tags()).containsOnly("tag1", "tag2"); + assertThat(query.onComponentOnly()).isFalse(); + assertThat(query.assigned()).isTrue(); + assertThat(query.rules()).hasSize(2); + assertThat(query.ruleUuids()).hasSize(2); + assertThat(query.directories()).containsOnly("aDirPath"); + assertThat(query.createdAfter().date()).isEqualTo(parseDateTime("2013-04-16T09:08:24+0200")); + assertThat(query.createdAfter().inclusive()).isTrue(); + assertThat(query.createdBefore()).isEqualTo(parseDateTime("2013-04-17T09:08:24+0200")); + assertThat(query.sort()).isEqualTo(IssueQuery.SORT_BY_CREATION_DATE); + assertThat(query.asc()).isTrue(); + assertThat(query.codeVariants()).containsOnly("variant1", "variant2"); + } + + @Test public void create_with_rule_key_that_does_not_exist_in_the_db() { db.users().insertUser(u -> u.setLogin("joanna")); ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent(); diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java index fd44cf728a3..d50061f65f8 100644 --- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java +++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java @@ -153,7 +153,7 @@ public class IssueQueryTest { } @Test - public void collection_params_should_not_be_null_but_empty() { + public void collection_params_should_not_be_null_but_empty_except_issue_keys() { IssueQuery query = IssueQuery.builder() .issueKeys(null) .projectUuids(null) @@ -171,7 +171,7 @@ public class IssueQueryTest { .cwe(null) .createdAfterByProjectUuids(null) .build(); - assertThat(query.issueKeys()).isEmpty(); + assertThat(query.issueKeys()).isNull(); assertThat(query.projectUuids()).isEmpty(); assertThat(query.componentUuids()).isEmpty(); assertThat(query.statuses()).isEmpty(); @@ -191,7 +191,6 @@ public class IssueQueryTest { @Test public void test_default_query() { IssueQuery query = IssueQuery.builder().build(); - assertThat(query.issueKeys()).isEmpty(); assertThat(query.projectUuids()).isEmpty(); assertThat(query.componentUuids()).isEmpty(); assertThat(query.statuses()).isEmpty(); @@ -202,6 +201,7 @@ public class IssueQueryTest { assertThat(query.languages()).isEmpty(); assertThat(query.tags()).isEmpty(); assertThat(query.types()).isEmpty(); + assertThat(query.issueKeys()).isNull(); assertThat(query.branchUuid()).isNull(); assertThat(query.assigned()).isNull(); assertThat(query.createdAfter()).isNull(); |