aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJacek Poreda <jacek.poreda@sonarsource.com>2023-08-07 14:53:59 +0200
committersonartech <sonartech@sonarsource.com>2023-08-18 20:02:49 +0000
commitc5f6c08551a945e6cc7c2450b0fb7ae4e795ba90 (patch)
treed7ae90734d56c8776563ad408f8573b71c7db32f
parent09a45741162362db5fcf1cdf55fbd43b89785523 (diff)
downloadsonarqube-c5f6c08551a945e6cc7c2450b0fb7ae4e795ba90.tar.gz
sonarqube-c5f6c08551a945e6cc7c2450b0fb7ae4e795ba90.zip
SONAR-20021 Add filter and facets for Software Quality, Severity and Clean Code Attribute Category
- Refactor issue indexer to use MyBatis mapping with cursor
-rw-r--r--server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java36
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/issue/IndexedIssueDto.java316
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java8
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java3
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml69
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/issue/IndexedIssueDtoTest.java89
-rw-r--r--server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIndexerIT.java9
-rw-r--r--server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java1
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java32
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java11
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java282
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresDoc.java1
-rw-r--r--server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java131
-rw-r--r--server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java36
-rw-r--r--server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java178
-rw-r--r--server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java113
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java5
17 files changed, 1083 insertions, 237 deletions
diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java
index d9f8ac85ee9..0a4a79a5e86 100644
--- a/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java
+++ b/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java
@@ -22,12 +22,14 @@ package org.sonar.db.issue;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
+import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javax.annotation.Nullable;
+import org.apache.ibatis.cursor.Cursor;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -199,6 +201,40 @@ public class IssueDaoIT {
}
@Test
+ public void scrollIndexationIssues_shouldReturnDto() {
+ ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
+ RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("java").setLanguage("java")
+ .addDefaultImpact(new ImpactDto()
+ .setUuid(UuidFactoryFast.getInstance().create())
+ .setSoftwareQuality(SoftwareQuality.RELIABILITY)
+ .setSeverity(Severity.MEDIUM)));
+
+ ComponentDto branchA = db.components().insertProjectBranch(project, b -> b.setKey("branchA"));
+ ComponentDto fileA = db.components().insertComponent(newFileDto(branchA));
+
+ IntStream.range(0, 100).forEach(i -> insertBranchIssue(branchA, fileA, rule, "A" + i, STATUS_OPEN, 1_340_000_000_000L));
+
+ Cursor<IndexedIssueDto> issues = underTest.scrollIssuesForIndexation(db.getSession(), null, null);
+
+ Iterator<IndexedIssueDto> iterator = issues.iterator();
+ int issueCount = 0;
+ while (iterator.hasNext()) {
+ IndexedIssueDto next = iterator.next();
+ assertThat(next.getRuleDefaultImpacts()).hasSize(2)
+ .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
+ .containsExactlyInAnyOrder(
+ tuple(SoftwareQuality.RELIABILITY, Severity.MEDIUM),
+ tuple(SoftwareQuality.MAINTAINABILITY, Severity.HIGH));
+ assertThat(next.getImpacts())
+ .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
+ .containsExactlyInAnyOrder(
+ tuple(SoftwareQuality.MAINTAINABILITY, Severity.HIGH));
+ issueCount++;
+ }
+ assertThat(issueCount).isEqualTo(100);
+ }
+
+ @Test
public void selectIssueKeysByComponentUuid() {
// contains I1 and I2
prepareTables();
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IndexedIssueDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IndexedIssueDto.java
new file mode 100644
index 00000000000..dc502006d56
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IndexedIssueDto.java
@@ -0,0 +1,316 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * 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
+ * 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.
+ */
+package org.sonar.db.issue;
+
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+
+public final class IndexedIssueDto {
+ private String issueKey = null;
+ private String assignee = null;
+ private Integer line = null;
+ private String resolution = null;
+ private String cleanCodeAttribute = null;
+ private String severity = null;
+ private String status = null;
+ private Long effort = null;
+ private String authorLogin = null;
+ private Long issueCloseDate = null;
+ private Long issueCreationDate = null;
+ private Long issueUpdateDate = null;
+ private String ruleUuid = null;
+ private String language = null;
+ private String componentUuid = null;
+ private String path = null;
+ private String scope = null;
+ private String branchUuid = null;
+ private boolean isMain = false;
+ private String projectUuid = null;
+ private String tags = null;
+ private Integer issueType = null;
+ private String securityStandards = null;
+ private String qualifier = null;
+ private boolean isNewCodeReferenceIssue = false;
+ private String codeVariants = null;
+
+ private Set<ImpactDto> impacts = new HashSet<>();
+ private Set<ImpactDto> ruleDefaultImpacts = new HashSet<>();
+
+ public IndexedIssueDto() {
+ // empty constructor
+ }
+
+ public String getIssueKey() {
+ return issueKey;
+ }
+
+ public IndexedIssueDto setIssueKey(String issueKey) {
+ this.issueKey = issueKey;
+ return this;
+ }
+
+ public String getAssignee() {
+ return assignee;
+ }
+
+ public IndexedIssueDto setAssignee(String assignee) {
+ this.assignee = assignee;
+ return this;
+ }
+
+ public Integer getLine() {
+ return line;
+ }
+
+ public IndexedIssueDto setLine(Integer line) {
+ this.line = line;
+ return this;
+ }
+
+ public String getResolution() {
+ return resolution;
+ }
+
+ public IndexedIssueDto setResolution(String resolution) {
+ this.resolution = resolution;
+ return this;
+ }
+
+ public String getSeverity() {
+ return severity;
+ }
+
+ public IndexedIssueDto setSeverity(String severity) {
+ this.severity = severity;
+ return this;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public IndexedIssueDto setStatus(String status) {
+ this.status = status;
+ return this;
+ }
+
+ public Long getEffort() {
+ return effort;
+ }
+
+ public IndexedIssueDto setEffort(Long effort) {
+ this.effort = effort;
+ return this;
+ }
+
+ public String getAuthorLogin() {
+ return authorLogin;
+ }
+
+ public IndexedIssueDto setAuthorLogin(String authorLogin) {
+ this.authorLogin = authorLogin;
+ return this;
+ }
+
+ public Long getIssueCloseDate() {
+ return issueCloseDate;
+ }
+
+ public IndexedIssueDto setIssueCloseDate(Long issueCloseDate) {
+ this.issueCloseDate = issueCloseDate;
+ return this;
+ }
+
+ public Long getIssueCreationDate() {
+ return issueCreationDate;
+ }
+
+ public IndexedIssueDto setIssueCreationDate(Long issueCreationDate) {
+ this.issueCreationDate = issueCreationDate;
+ return this;
+ }
+
+ public Long getIssueUpdateDate() {
+ return issueUpdateDate;
+ }
+
+ public IndexedIssueDto setIssueUpdateDate(Long issueUpdateDate) {
+ this.issueUpdateDate = issueUpdateDate;
+ return this;
+ }
+
+ public String getRuleUuid() {
+ return ruleUuid;
+ }
+
+ public IndexedIssueDto setRuleUuid(String ruleUuid) {
+ this.ruleUuid = ruleUuid;
+ return this;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public IndexedIssueDto setLanguage(String language) {
+ this.language = language;
+ return this;
+ }
+
+ public String getComponentUuid() {
+ return componentUuid;
+ }
+
+ public IndexedIssueDto setComponentUuid(String componentUuid) {
+ this.componentUuid = componentUuid;
+ return this;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public IndexedIssueDto setPath(String path) {
+ this.path = path;
+ return this;
+ }
+
+ public String getScope() {
+ return scope;
+ }
+
+ public IndexedIssueDto setScope(String scope) {
+ this.scope = scope;
+ return this;
+ }
+
+ public String getBranchUuid() {
+ return branchUuid;
+ }
+
+ public IndexedIssueDto setBranchUuid(String branchUuid) {
+ this.branchUuid = branchUuid;
+ return this;
+ }
+
+ public boolean isMain() {
+ return isMain;
+ }
+
+ public IndexedIssueDto setIsMain(boolean isMain) {
+ this.isMain = isMain;
+ return this;
+ }
+
+ public String getProjectUuid() {
+ return projectUuid;
+ }
+
+ public IndexedIssueDto setProjectUuid(String projectUuid) {
+ this.projectUuid = projectUuid;
+ return this;
+ }
+
+ public String getTags() {
+ return tags;
+ }
+
+ public IndexedIssueDto setTags(String tags) {
+ this.tags = tags;
+ return this;
+ }
+
+ @Deprecated
+ public Integer getIssueType() {
+ return issueType;
+ }
+
+ @Deprecated
+ public IndexedIssueDto setIssueType(Integer issueType) {
+ this.issueType = issueType;
+ return this;
+ }
+
+ public String getSecurityStandards() {
+ return securityStandards;
+ }
+
+ public IndexedIssueDto setSecurityStandards(String securityStandards) {
+ this.securityStandards = securityStandards;
+ return this;
+ }
+
+ public String getQualifier() {
+ return qualifier;
+ }
+
+ public IndexedIssueDto setQualifier(String qualifier) {
+ this.qualifier = qualifier;
+ return this;
+ }
+
+ public boolean isNewCodeReferenceIssue() {
+ return isNewCodeReferenceIssue;
+ }
+
+ public IndexedIssueDto setNewCodeReferenceIssue(boolean newCodeReferenceIssue) {
+ isNewCodeReferenceIssue = newCodeReferenceIssue;
+ return this;
+ }
+
+ public String getCodeVariants() {
+ return codeVariants;
+ }
+
+ public IndexedIssueDto setCodeVariants(String codeVariants) {
+ this.codeVariants = codeVariants;
+ return this;
+ }
+
+ public Set<ImpactDto> getImpacts() {
+ return impacts;
+ }
+
+ public Set<ImpactDto> getRuleDefaultImpacts() {
+ return ruleDefaultImpacts;
+ }
+
+
+ public Map<SoftwareQuality, Severity> getEffectiveImpacts() {
+ EnumMap<SoftwareQuality, Severity> effectiveImpacts = new EnumMap<>(SoftwareQuality.class);
+ ruleDefaultImpacts.forEach(impact -> effectiveImpacts.put(impact.getSoftwareQuality(), impact.getSeverity()));
+ impacts.forEach(impact -> effectiveImpacts.put(impact.getSoftwareQuality(), impact.getSeverity()));
+ return Collections.unmodifiableMap(effectiveImpacts);
+ }
+
+ public String getCleanCodeAttribute() {
+ return cleanCodeAttribute;
+ }
+
+ public IndexedIssueDto setCleanCodeAttribute(String cleanCodeAttribute) {
+ this.cleanCodeAttribute = cleanCodeAttribute;
+ return this;
+ }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
index 6efd896f392..af95135fb9f 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
@@ -23,6 +23,9 @@ import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import javax.annotation.Nullable;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.cursor.Cursor;
import org.sonar.db.Dao;
import org.sonar.db.DbSession;
import org.sonar.db.Pagination;
@@ -91,6 +94,11 @@ public class IssueDao implements Dao {
return mapper(dbSession).selectIssueGroupsByComponent(component, leakPeriodBeginningDate);
}
+ public Cursor<IndexedIssueDto> scrollIssuesForIndexation(DbSession dbSession, @Nullable @Param("branchUuid") String branchUuid,
+ @Nullable @Param("issueKeys") Collection<String> issueKeys) {
+ return mapper(dbSession).scrollIssuesForIndexation(branchUuid, issueKeys);
+ }
+
public void insert(DbSession session, IssueDto dto) {
mapper(session).insert(dto);
updateIssueImpacts(dto, mapper(session));
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java
index 579c69c13f6..93951e438e6 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java
@@ -24,6 +24,7 @@ import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.session.ResultHandler;
import org.sonar.db.Pagination;
import org.sonar.db.component.ComponentDto;
@@ -72,6 +73,8 @@ public interface IssueMapper {
void scrollClosedByComponentUuid(@Param("componentUuid") String componentUuid, @Param("closeDateAfter") long closeDateAfter, ResultHandler<IssueDto> handler);
+ Cursor<IndexedIssueDto> scrollIssuesForIndexation(@Nullable @Param("branchUuid") String branchUuid, @Nullable @Param("issueKeys") Collection<String> issueKeys);
+
Collection<IssueGroupDto> selectIssueGroupsByComponent(@Param("component") ComponentDto component, @Param("leakPeriodBeginningDate") long leakPeriodBeginningDate);
List<IssueDto> selectByBranch(@Param("keys") Set<String> keys, @Nullable @Param("changedSince") Long changedSince);
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
index e1966bf9aa6..17889d58ee3 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
@@ -317,6 +317,75 @@
i.kee, ic.issue_change_creation_date desc
</select>
+ <resultMap id="indexedIssueResultMap" type="org.sonar.db.issue.IndexedIssueDto" autoMapping="true">
+ <id property="issueKey" column="issueKey"/>
+
+ <collection property="impacts" column="ii_uuid" notNullColumn="ii_uuid"
+ javaType="java.util.Set" ofType="Impact">
+ <id property="uuid" column="ii_uuid"/>
+ <result property="softwareQuality" column="ii_softwareQuality"/>
+ <result property="severity" column="ii_severity"/>
+ </collection>
+ <collection property="ruleDefaultImpacts" column="rdi_uuid" notNullColumn="rdi_uuid"
+ javaType="java.util.Set" ofType="Impact">
+ <id property="uuid" column="rdi_uuid"/>
+ <result property="softwareQuality" column="rdi_softwareQuality"/>
+ <result property="severity" column="rdi_severity"/>
+ </collection>
+ </resultMap>
+
+ <select id="scrollIssuesForIndexation" parameterType="map" resultMap="indexedIssueResultMap" fetchSize="${_scrollFetchSize}"
+ resultSetType="FORWARD_ONLY" resultOrdered="true">
+ select
+ i.kee as issueKey,
+ i.assignee,
+ i.line,
+ i.resolution,
+ i.severity,
+ i.status,
+ i.effort,
+ i.author_login as authorLogin,
+ i.issue_close_date as issueCloseDate,
+ i.issue_creation_date as issueCreationDate,
+ i.issue_update_date as issueUpdateDate,
+ r.uuid as ruleUuid,
+ r.language as language,
+ r.clean_code_attribute as cleanCodeAttribute,
+ c.uuid as componentUuid,
+ c.path,
+ c.scope,
+ c.branch_uuid as branchUuid,
+ pb.is_main as isMain,
+ pb.project_uuid as projectUuid,
+ i.tags,
+ i.issue_type as issueType,
+ r.security_standards as securityStandards,
+ c.qualifier,
+ i.code_variants as codeVariants,
+ <include refid="issueImpactsColumns"/>
+ <include refid="ruleDefaultImpactsColumns"/>
+ <include refid="isNewCodeReferenceIssue"/>
+ from issues i
+ inner join rules r on r.uuid = i.rule_uuid
+ inner join components c on c.uuid = i.component_uuid
+ inner join project_branches pb on c.branch_uuid = pb.uuid
+ left join new_code_reference_issues n on n.issue_key = i.kee
+ left outer join issues_impacts ii on i.kee = ii.issue_key
+ left outer join rules_default_impacts rdi on r.uuid = rdi.rule_uuid
+ <where>
+ <if test="branchUuid != null">
+ and c.branch_uuid = #{branchUuid,jdbcType=VARCHAR} and i.project_uuid = #{branchUuid,jdbcType=VARCHAR}
+ </if>
+ <if test="issueKeys != null">
+ and i.kee in
+ <foreach collection="issueKeys" open="(" close=")" item="key" separator=",">
+ #{key,jdbcType=VARCHAR}
+ </foreach>
+ </if>
+ </where>
+ order by i.kee
+ </select>
+
<select id="selectComponentUuidsOfOpenIssuesForProjectUuid" parameterType="string" resultType="string">
select distinct(i.component_uuid)
from issues i
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IndexedIssueDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IndexedIssueDtoTest.java
new file mode 100644
index 00000000000..24ba25aed7f
--- /dev/null
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IndexedIssueDtoTest.java
@@ -0,0 +1,89 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * 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
+ * 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.
+ */
+package org.sonar.db.issue;
+
+import org.junit.Test;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
+
+public class IndexedIssueDtoTest {
+
+ @Test
+ public void settersGetters_shouldSetAndGetValues() {
+ IndexedIssueDto indexedIssueDto = new IndexedIssueDto()
+ .setIssueKey("issueKey")
+ .setAssignee("assignee")
+ .setAuthorLogin("authorLogin")
+ .setStatus("status")
+ .setNewCodeReferenceIssue(true)
+ .setCleanCodeAttribute("cleanCodeAttribute")
+ .setCodeVariants("codeVariants")
+ .setSecurityStandards("securityStandards")
+ .setComponentUuid("componentUuid")
+ .setIssueCloseDate(1L)
+ .setIssueCreationDate(2L)
+ .setIssueUpdateDate(3L)
+ .setEffort(4L)
+ .setIsMain(true)
+ .setLanguage("language")
+ .setLine(5)
+ .setPath("path")
+ .setProjectUuid("projectUuid")
+ .setQualifier("qualifier")
+ .setResolution("resolution")
+ .setRuleUuid("ruleUuid")
+ .setScope("scope")
+ .setSeverity("severity")
+ .setTags("tags")
+ .setIssueType(6)
+ .setBranchUuid("branchUuid");
+
+ indexedIssueDto.getImpacts().add(new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.HIGH));
+ indexedIssueDto.getRuleDefaultImpacts().add(new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.MEDIUM));
+
+ assertThat(indexedIssueDto)
+ .extracting(IndexedIssueDto::getIssueKey, IndexedIssueDto::getAssignee, IndexedIssueDto::getAuthorLogin, IndexedIssueDto::getStatus,
+ IndexedIssueDto::isNewCodeReferenceIssue, IndexedIssueDto::getCleanCodeAttribute, IndexedIssueDto::getCodeVariants,
+ IndexedIssueDto::getSecurityStandards, IndexedIssueDto::getComponentUuid, IndexedIssueDto::getIssueCloseDate, IndexedIssueDto::getIssueCreationDate,
+ IndexedIssueDto::getIssueUpdateDate, IndexedIssueDto::getEffort, IndexedIssueDto::isMain, IndexedIssueDto::getLanguage, IndexedIssueDto::getLine,
+ IndexedIssueDto::getPath, IndexedIssueDto::getProjectUuid, IndexedIssueDto::getQualifier, IndexedIssueDto::getResolution,
+ IndexedIssueDto::getRuleUuid, IndexedIssueDto::getScope, IndexedIssueDto::getSeverity, IndexedIssueDto::getTags, IndexedIssueDto::getIssueType,
+ IndexedIssueDto::getBranchUuid)
+ .containsExactly("issueKey", "assignee", "authorLogin", "status", true, "cleanCodeAttribute", "codeVariants", "securityStandards",
+ "componentUuid", 1L, 2L, 3L, 4L, true, "language", 5, "path", "projectUuid", "qualifier", "resolution", "ruleUuid",
+ "scope", "severity", "tags", 6, "branchUuid");
+
+ assertThat(indexedIssueDto.getImpacts())
+ .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
+ .containsExactly(tuple(SoftwareQuality.SECURITY, Severity.HIGH));
+
+ assertThat(indexedIssueDto.getRuleDefaultImpacts())
+ .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
+ .containsExactly(tuple(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM));
+
+ assertThat(indexedIssueDto.getEffectiveImpacts())
+ .containsEntry(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM)
+ .containsEntry(SoftwareQuality.SECURITY, Severity.HIGH);
+ }
+
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIndexerIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIndexerIT.java
index 54e6ab950fa..5e2db299839 100644
--- a/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIndexerIT.java
+++ b/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIndexerIT.java
@@ -24,6 +24,7 @@ import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import org.assertj.core.api.Assertions;
@@ -31,6 +32,8 @@ import org.elasticsearch.search.SearchHit;
import org.junit.Rule;
import org.junit.Test;
import org.slf4j.event.Level;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.testfixtures.log.LogTester;
import org.sonar.db.DbSession;
@@ -68,6 +71,8 @@ import static org.sonar.server.es.Indexers.BranchEvent.DELETION;
import static org.sonar.server.es.Indexers.EntityEvent.PROJECT_KEY_UPDATE;
import static org.sonar.server.es.Indexers.EntityEvent.PROJECT_TAGS_UPDATE;
import static org.sonar.server.issue.IssueDocTesting.newDoc;
+import static org.sonar.server.issue.index.IssueIndexDefinition.SUB_FIELD_SOFTWARE_QUALITY;
+import static org.sonar.server.issue.index.IssueIndexDefinition.SUB_FIELD_SEVERITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEFENSES;
@@ -144,6 +149,10 @@ public class IssueIndexerIT {
assertThat(doc.getSansTop25()).isEmpty();
assertThat(doc.getSonarSourceSecurityCategory()).isEqualTo(SQCategory.OTHERS);
assertThat(doc.getVulnerabilityProbability()).isEqualTo(VulnerabilityProbability.LOW);
+ assertThat(doc.impacts())
+ .containsExactlyInAnyOrder(Map.of(
+ SUB_FIELD_SOFTWARE_QUALITY, SoftwareQuality.MAINTAINABILITY.name(),
+ SUB_FIELD_SEVERITY, Severity.HIGH.name()));
}
@Test
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java
index 2a4e1052b8e..15291533dc1 100644
--- a/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java
+++ b/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java
@@ -94,6 +94,7 @@ public class IssueIteratorFactoryIT {
assertThat(issue.effort().toMinutes()).isPositive();
assertThat(issue.type().getDbConstant()).isEqualTo(2);
assertThat(issue.getCodeVariants()).containsOnly("variant1", "variant2");
+ assertThat(issue.cleanCodeAttributeCategory()).isEqualTo("INTENTIONAL");
}
@Test
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java
index c02ff1f7782..f2e8f5986f4 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java
@@ -22,9 +22,11 @@ package org.sonar.server.issue.index;
import com.google.common.collect.Maps;
import java.util.Collection;
import java.util.Date;
+import java.util.List;
import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.Duration;
@@ -33,6 +35,8 @@ import org.sonar.server.permission.index.AuthorizationDoc;
import org.sonar.server.security.SecurityStandards;
import org.sonar.server.security.SecurityStandards.VulnerabilityProbability;
+import static org.sonar.server.issue.index.IssueIndexDefinition.SUB_FIELD_SOFTWARE_QUALITY;
+import static org.sonar.server.issue.index.IssueIndexDefinition.SUB_FIELD_SEVERITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
public class IssueDoc extends BaseDoc {
@@ -86,6 +90,14 @@ public class IssueDoc extends BaseDoc {
return getField(IssueIndexDefinition.FIELD_ISSUE_SEVERITY);
}
+ public String cleanCodeAttributeCategory() {
+ return getField(IssueIndexDefinition.FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY);
+ }
+
+ public Collection<Map<String, String>> impacts() {
+ return getField(IssueIndexDefinition.FIELD_ISSUE_IMPACTS);
+ }
+
@CheckForNull
public Integer line() {
return getNullableField(IssueIndexDefinition.FIELD_ISSUE_LINE);
@@ -129,6 +141,7 @@ public class IssueDoc extends BaseDoc {
return getNullableField(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN);
}
+ @Deprecated
public RuleType type() {
return RuleType.valueOf(getField(IssueIndexDefinition.FIELD_ISSUE_TYPE));
}
@@ -190,12 +203,18 @@ public class IssueDoc extends BaseDoc {
return this;
}
+ @Deprecated
public IssueDoc setSeverity(@Nullable String s) {
setField(IssueIndexDefinition.FIELD_ISSUE_SEVERITY, s);
setField(IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE, Severity.ALL.indexOf(s));
return this;
}
+ public IssueDoc setCleanCodeAttributeCategory(@Nullable String s) {
+ setField(IssueIndexDefinition.FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY, s);
+ return this;
+ }
+
public IssueDoc setLine(@Nullable Integer i) {
setField(IssueIndexDefinition.FIELD_ISSUE_LINE, i);
return this;
@@ -261,11 +280,24 @@ public class IssueDoc extends BaseDoc {
return this;
}
+ @Deprecated
public IssueDoc setType(RuleType type) {
setField(IssueIndexDefinition.FIELD_ISSUE_TYPE, type.toString());
return this;
}
+ public IssueDoc setImpacts(Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> softwareQualities) {
+ List<Map<String, String>> convertedMap = softwareQualities
+ .entrySet()
+ .stream()
+ .map(entry -> Map.of(
+ SUB_FIELD_SOFTWARE_QUALITY, entry.getKey().name(),
+ SUB_FIELD_SEVERITY, entry.getValue().name()))
+ .toList();
+ setField(IssueIndexDefinition.FIELD_ISSUE_IMPACTS, convertedMap);
+ return this;
+ }
+
@CheckForNull
public Collection<String> getPciDss32() {
return getNullableField(IssueIndexDefinition.FIELD_ISSUE_PCI_DSS_32);
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java
index fdfaba17578..5c760d638bf 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java
@@ -104,6 +104,12 @@ public class IssueIndexDefinition implements IndexDefinition {
* Whether issue is new code for a branch using the reference branch new code definition.
*/
public static final String FIELD_ISSUE_NEW_CODE_REFERENCE = "isNewCodeReference";
+ public static final String FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY = "cleanCodeAttributeCategory";
+ public static final String FIELD_ISSUE_IMPACTS = "impacts";
+ public static final String SUB_FIELD_SOFTWARE_QUALITY = "softwareQuality";
+ public static final String SUB_FIELD_SEVERITY = "severity";
+ public static final String FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY = FIELD_ISSUE_IMPACTS + "." + SUB_FIELD_SOFTWARE_QUALITY;
+ public static final String FIELD_ISSUE_IMPACT_SEVERITY = FIELD_ISSUE_IMPACTS + "." + SUB_FIELD_SEVERITY;
private final Configuration config;
private final boolean enableSource;
@@ -157,6 +163,11 @@ public class IssueIndexDefinition implements IndexDefinition {
mapping.keywordFieldBuilder(FIELD_ISSUE_RULE_UUID).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_SEVERITY).disableNorms().build();
mapping.createByteField(FIELD_ISSUE_SEVERITY_VALUE);
+ mapping.keywordFieldBuilder(FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY).disableNorms().build();
+ mapping.nestedFieldBuilder(FIELD_ISSUE_IMPACTS)
+ .addKeywordField(SUB_FIELD_SOFTWARE_QUALITY)
+ .addKeywordField(SUB_FIELD_SEVERITY)
+ .build();
mapping.keywordFieldBuilder(FIELD_ISSUE_STATUS).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
mapping.keywordFieldBuilder(FIELD_ISSUE_TAGS).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_TYPE).disableNorms().build();
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java
index 86d2196a20e..deec19dbba9 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java
@@ -21,29 +21,25 @@ package org.sonar.server.issue.index;
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
+import java.util.Iterator;
+import java.util.Optional;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
-import org.apache.commons.lang.StringUtils;
+import org.apache.ibatis.cursor.Cursor;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Scopes;
+import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.db.DatabaseUtils;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
-import org.sonar.db.ResultSetIterator;
+import org.sonar.db.issue.IndexedIssueDto;
import org.sonar.server.security.SecurityStandards;
import static com.google.common.base.Preconditions.checkArgument;
-import static org.elasticsearch.common.Strings.isNullOrEmpty;
import static org.sonar.api.utils.DateUtils.longToDate;
-import static org.sonar.db.DatabaseUtils.getLong;
import static org.sonar.db.rule.RuleDto.deserializeSecurityStandardsString;
import static org.sonar.server.security.SecurityStandards.fromSecurityStandards;
@@ -53,85 +49,25 @@ import static org.sonar.server.security.SecurityStandards.fromSecurityStandards;
*/
class IssueIteratorForSingleChunk implements IssueIterator {
- private static final String[] FIELDS = {
- "i.kee",
- "i.assignee",
- "i.line",
- "i.resolution",
- "i.severity",
- "i.status",
- "i.effort",
- "i.author_login",
- "i.issue_close_date",
- "i.issue_creation_date",
- "i.issue_update_date",
- "r.uuid",
- "r.language",
- "c.uuid",
- "c.path",
- "c.scope",
- "c.branch_uuid",
- "pb.is_main",
- "pb.project_uuid",
- "i.tags",
- "i.issue_type",
- "r.security_standards",
- "c.qualifier",
- "n.uuid",
- "i.code_variants"
- };
-
- private static final String SQL_ALL = "select " + StringUtils.join(FIELDS, ",") + " from issues i " +
- "inner join rules r on r.uuid = i.rule_uuid " +
- "inner join components c on c.uuid = i.component_uuid " +
- "inner join project_branches pb on c.branch_uuid = pb.uuid ";
-
- private static final String SQL_NEW_CODE_JOIN = "left join new_code_reference_issues n on n.issue_key = i.kee ";
-
- private static final String BRANCH_FILTER = " and c.branch_uuid = ? and i.project_uuid = ? ";
- private static final String ISSUE_KEY_FILTER_PREFIX = " and i.kee in (";
- private static final String ISSUE_KEY_FILTER_SUFFIX = ") ";
-
static final Splitter STRING_LIST_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
private final DbSession session;
- @CheckForNull
- private final String branchUuid;
-
- @CheckForNull
- private final Collection<String> issueKeys;
-
- private final PreparedStatement stmt;
- private final ResultSetIterator<IssueDoc> iterator;
+ private final Iterator<IndexedIssueDto> iterator;
IssueIteratorForSingleChunk(DbClient dbClient, @Nullable String branchUuid, @Nullable Collection<String> issueKeys) {
checkArgument(issueKeys == null || issueKeys.size() <= DatabaseUtils.PARTITION_SIZE_FOR_ORACLE,
"Cannot search for more than " + DatabaseUtils.PARTITION_SIZE_FOR_ORACLE + " issue keys at once. Please provide the keys in smaller chunks.");
- this.branchUuid = branchUuid;
- this.issueKeys = issueKeys;
this.session = dbClient.openSession(false);
-
try {
- String sql = createSql();
- stmt = dbClient.getMyBatis().newScrollingSelectStatement(session, sql);
- iterator = createIterator();
+ Cursor<IndexedIssueDto> indexCursor = dbClient.issueDao().scrollIssuesForIndexation(session, branchUuid, issueKeys);
+ iterator = indexCursor.iterator();
} catch (Exception e) {
session.close();
throw new IllegalStateException("Fail to prepare SQL request to select all issues", e);
}
}
- private IssueIteratorInternal createIterator() {
- try {
- setParameters(stmt);
- return new IssueIteratorInternal(stmt);
- } catch (SQLException e) {
- DatabaseUtils.closeQuietly(stmt);
- throw new IllegalStateException("Fail to prepare SQL request to select all issues", e);
- }
- }
-
@Override
public boolean hasNext() {
return iterator.hasNext();
@@ -139,133 +75,101 @@ class IssueIteratorForSingleChunk implements IssueIterator {
@Override
public IssueDoc next() {
- return iterator.next();
+ return toIssueDoc(iterator.next());
}
- private String createSql() {
- String sql = SQL_ALL;
- sql += branchUuid == null ? "" : BRANCH_FILTER;
- if (issueKeys != null && !issueKeys.isEmpty()) {
- sql += ISSUE_KEY_FILTER_PREFIX;
- sql += IntStream.range(0, issueKeys.size()).mapToObj(i -> "?").collect(Collectors.joining(","));
- sql += ISSUE_KEY_FILTER_SUFFIX;
- }
- sql += SQL_NEW_CODE_JOIN;
- return sql;
+ private static IssueDoc toIssueDoc(IndexedIssueDto indexedIssueDto) {
+ IssueDoc doc = new IssueDoc(new HashMap<>(30));
+
+ String key = indexedIssueDto.getIssueKey();
+
+ // all the fields must be present, even if value is null
+ doc.setKey(key);
+ doc.setAssigneeUuid(indexedIssueDto.getAssignee());
+ doc.setLine(indexedIssueDto.getLine());
+ doc.setResolution(indexedIssueDto.getResolution());
+ doc.setSeverity(indexedIssueDto.getSeverity());
+ String cleanCodeAttributeCategory = Optional.ofNullable(indexedIssueDto.getCleanCodeAttribute())
+ .map(CleanCodeAttribute::valueOf)
+ .map(cleanCodeAttribute -> cleanCodeAttribute.getAttributeCategory().name())
+ .orElse(null);
+ //TODO:: uncomment once clean code attribute is set to not-null
+ //.orElseThrow(() -> new IllegalStateException("Clean Code Attribute is missing for issue " + key));
+ doc.setCleanCodeAttributeCategory(cleanCodeAttributeCategory);
+ doc.setStatus(indexedIssueDto.getStatus());
+ doc.setEffort(indexedIssueDto.getEffort());
+ doc.setAuthorLogin(indexedIssueDto.getAuthorLogin());
+
+ doc.setFuncCloseDate(longToDate(indexedIssueDto.getIssueCloseDate()));
+ doc.setFuncCreationDate(longToDate(indexedIssueDto.getIssueCreationDate()));
+ doc.setFuncUpdateDate(longToDate(indexedIssueDto.getIssueUpdateDate()));
+
+ doc.setRuleUuid(indexedIssueDto.getRuleUuid());
+ doc.setLanguage(indexedIssueDto.getLanguage());
+ doc.setComponentUuid(indexedIssueDto.getComponentUuid());
+ String scope = indexedIssueDto.getScope();
+ String filePath = extractFilePath(indexedIssueDto.getPath(), scope);
+ doc.setFilePath(filePath);
+ doc.setDirectoryPath(extractDirPath(doc.filePath(), scope));
+ String branchUuid = indexedIssueDto.getBranchUuid();
+ boolean isMainBranch = indexedIssueDto.isMain();
+ String projectUuid = indexedIssueDto.getProjectUuid();
+ doc.setBranchUuid(branchUuid);
+ doc.setIsMainBranch(isMainBranch);
+ doc.setProjectUuid(projectUuid);
+ String tags = indexedIssueDto.getTags();
+ doc.setTags(STRING_LIST_SPLITTER.splitToList(tags == null ? "" : tags));
+ doc.setType(RuleType.valueOf(indexedIssueDto.getIssueType()));
+ doc.setImpacts(indexedIssueDto.getEffectiveImpacts());
+ SecurityStandards securityStandards = fromSecurityStandards(deserializeSecurityStandardsString(indexedIssueDto.getSecurityStandards()));
+ SecurityStandards.SQCategory sqCategory = securityStandards.getSqCategory();
+ doc.setOwaspTop10(securityStandards.getOwaspTop10());
+ doc.setOwaspTop10For2021(securityStandards.getOwaspTop10For2021());
+ doc.setPciDss32(securityStandards.getPciDss32());
+ doc.setPciDss40(securityStandards.getPciDss40());
+ doc.setOwaspAsvs40(securityStandards.getOwaspAsvs40());
+ doc.setCwe(securityStandards.getCwe());
+ doc.setSansTop25(securityStandards.getSansTop25());
+ doc.setSonarSourceSecurityCategory(sqCategory);
+ doc.setVulnerabilityProbability(sqCategory.getVulnerability());
+
+ doc.setScope(Qualifiers.UNIT_TEST_FILE.equals(indexedIssueDto.getQualifier()) ? IssueScope.TEST : IssueScope.MAIN);
+ doc.setIsNewCodeReference(indexedIssueDto.isNewCodeReferenceIssue());
+ String codeVariants = indexedIssueDto.getCodeVariants();
+ doc.setCodeVariants(STRING_LIST_SPLITTER.splitToList(codeVariants == null ? "" : codeVariants));
+ return doc;
+
}
- private void setParameters(PreparedStatement stmt) throws SQLException {
- int index = 1;
- if (branchUuid != null) {
- stmt.setString(index, branchUuid);
- index++;
- stmt.setString(index, branchUuid);
- index++;
- }
- if (issueKeys != null) {
- for (String key : issueKeys) {
- stmt.setString(index, key);
- index++;
+ @CheckForNull
+ private static String extractDirPath(@Nullable String filePath, String scope) {
+ if (filePath != null) {
+ if (Scopes.DIRECTORY.equals(scope)) {
+ return filePath;
+ }
+ int lastSlashIndex = CharMatcher.anyOf("/").lastIndexIn(filePath);
+ if (lastSlashIndex > 0) {
+ return filePath.substring(0, lastSlashIndex);
}
+ return "/";
}
+ return null;
}
- @Override
- public void close() {
- try {
- iterator.close();
- } finally {
- DatabaseUtils.closeQuietly(stmt);
- session.close();
+ @CheckForNull
+ private static String extractFilePath(@Nullable String filePath, String scope) {
+ // On modules, the path contains the relative path of the module starting from its parent, and in E/S we're only interested in the
+ // path
+ // of files and directories.
+ // That's why the file path should be null on modules and projects.
+ if (filePath != null && !Scopes.PROJECT.equals(scope)) {
+ return filePath;
}
+ return null;
}
- private static final class IssueIteratorInternal extends ResultSetIterator<IssueDoc> {
-
- public IssueIteratorInternal(PreparedStatement stmt) throws SQLException {
- super(stmt);
- }
-
- @Override
- protected IssueDoc read(ResultSet rs) throws SQLException {
- IssueDoc doc = new IssueDoc(new HashMap<>(30));
-
- String key = rs.getString(1);
-
- // all the fields must be present, even if value is null
- doc.setKey(key);
- doc.setAssigneeUuid(rs.getString(2));
- doc.setLine(DatabaseUtils.getInt(rs, 3));
- doc.setResolution(rs.getString(4));
- doc.setSeverity(rs.getString(5));
- doc.setStatus(rs.getString(6));
- doc.setEffort(getLong(rs, 7));
- doc.setAuthorLogin(rs.getString(8));
- doc.setFuncCloseDate(longToDate(getLong(rs, 9)));
- doc.setFuncCreationDate(longToDate(getLong(rs, 10)));
- doc.setFuncUpdateDate(longToDate(getLong(rs, 11)));
- doc.setRuleUuid(rs.getString(12));
- doc.setLanguage(rs.getString(13));
- doc.setComponentUuid(rs.getString(14));
- String scope = rs.getString(16);
- String filePath = extractFilePath(rs.getString(15), scope);
- doc.setFilePath(filePath);
- doc.setDirectoryPath(extractDirPath(doc.filePath(), scope));
- String branchUuid = rs.getString(17);
- boolean isMainBranch = rs.getBoolean( 18);
- String projectUuid = rs.getString(19);
- doc.setBranchUuid(branchUuid);
- doc.setIsMainBranch(isMainBranch);
- doc.setProjectUuid(projectUuid);
- String tags = rs.getString(20);
- doc.setTags(STRING_LIST_SPLITTER.splitToList(tags == null ? "" : tags));
- doc.setType(RuleType.valueOf(rs.getInt(21)));
-
- SecurityStandards securityStandards = fromSecurityStandards(deserializeSecurityStandardsString(rs.getString(22)));
- SecurityStandards.SQCategory sqCategory = securityStandards.getSqCategory();
- doc.setOwaspTop10(securityStandards.getOwaspTop10());
- doc.setOwaspTop10For2021(securityStandards.getOwaspTop10For2021());
- doc.setPciDss32(securityStandards.getPciDss32());
- doc.setPciDss40(securityStandards.getPciDss40());
- doc.setOwaspAsvs40(securityStandards.getOwaspAsvs40());
- doc.setCwe(securityStandards.getCwe());
- doc.setSansTop25(securityStandards.getSansTop25());
- doc.setSonarSourceSecurityCategory(sqCategory);
- doc.setVulnerabilityProbability(sqCategory.getVulnerability());
-
- doc.setScope(Qualifiers.UNIT_TEST_FILE.equals(rs.getString(23)) ? IssueScope.TEST : IssueScope.MAIN);
- doc.setIsNewCodeReference(!isNullOrEmpty(rs.getString(24)));
- String codeVariants = rs.getString(25);
- doc.setCodeVariants(STRING_LIST_SPLITTER.splitToList(codeVariants == null ? "" : codeVariants));
- return doc;
- }
-
- @CheckForNull
- private static String extractDirPath(@Nullable String filePath, String scope) {
- if (filePath != null) {
- if (Scopes.DIRECTORY.equals(scope)) {
- return filePath;
- }
- int lastSlashIndex = CharMatcher.anyOf("/").lastIndexIn(filePath);
- if (lastSlashIndex > 0) {
- return filePath.substring(0, lastSlashIndex);
- }
- return "/";
- }
- return null;
- }
-
- @CheckForNull
- private static String extractFilePath(@Nullable String filePath, String scope) {
- // On modules, the path contains the relative path of the module starting from its parent, and in E/S we're only interested in the
- // path
- // of files and directories.
- // That's why the file path should be null on modules and projects.
- if (filePath != null && !Scopes.PROJECT.equals(scope)) {
- return filePath;
- }
- return null;
- }
-
+ @Override
+ public void close() {
+ session.close();
}
}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresDoc.java b/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresDoc.java
index 0b90f2486c5..92874fc6ced 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresDoc.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresDoc.java
@@ -19,7 +19,6 @@
*/
package org.sonar.server.measure.index;
-import com.google.common.collect.ImmutableMap;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
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 9d30e3e5ec0..a8991a87577 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
@@ -40,6 +40,7 @@ import java.util.stream.Stream;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
+import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
@@ -51,6 +52,7 @@ import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.HasAggregations;
import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
+import org.elasticsearch.search.aggregations.bucket.filter.FiltersAggregator;
import org.elasticsearch.search.aggregations.bucket.filter.ParsedFilter;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
import org.elasticsearch.search.aggregations.bucket.histogram.LongBounds;
@@ -66,7 +68,9 @@ import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.joda.time.Duration;
import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.CleanCodeAttributeCategory;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version;
@@ -101,10 +105,12 @@ import static java.util.stream.Collectors.toCollection;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.existsQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.elasticsearch.index.query.QueryBuilders.nestedQuery;
import static org.elasticsearch.index.query.QueryBuilders.prefixQuery;
import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
+import static org.elasticsearch.search.aggregations.AggregationBuilders.filters;
import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
import static org.sonar.api.rules.RuleType.VULNERABILITY;
import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars;
@@ -116,6 +122,7 @@ import static org.sonar.server.es.searchrequest.TopAggregationHelper.NO_OTHER_SU
import static org.sonar.server.issue.index.IssueIndex.Facet.ASSIGNED_TO_ME;
import static org.sonar.server.issue.index.IssueIndex.Facet.ASSIGNEES;
import static org.sonar.server.issue.index.IssueIndex.Facet.AUTHOR;
+import static org.sonar.server.issue.index.IssueIndex.Facet.CLEAN_CODE_ATTRIBUTE_CATEGORY;
import static org.sonar.server.issue.index.IssueIndex.Facet.CODE_VARIANTS;
import static org.sonar.server.issue.index.IssueIndex.Facet.CREATED_AT;
import static org.sonar.server.issue.index.IssueIndex.Facet.CWE;
@@ -133,6 +140,8 @@ import static org.sonar.server.issue.index.IssueIndex.Facet.RULES;
import static org.sonar.server.issue.index.IssueIndex.Facet.SANS_TOP_25;
import static org.sonar.server.issue.index.IssueIndex.Facet.SCOPES;
import static org.sonar.server.issue.index.IssueIndex.Facet.SEVERITIES;
+import static org.sonar.server.issue.index.IssueIndex.Facet.IMPACT_SOFTWARE_QUALITY;
+import static org.sonar.server.issue.index.IssueIndex.Facet.IMPACT_SEVERITY;
import static org.sonar.server.issue.index.IssueIndex.Facet.SONARSOURCE_SECURITY;
import static org.sonar.server.issue.index.IssueIndex.Facet.STATUSES;
import static org.sonar.server.issue.index.IssueIndex.Facet.TAGS;
@@ -140,6 +149,7 @@ import static org.sonar.server.issue.index.IssueIndex.Facet.TYPES;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID;
+import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_CODE_VARIANTS;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_CWE;
@@ -166,6 +176,9 @@ import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SANS
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SCOPE;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE;
+import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACTS;
+import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY;
+import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACT_SEVERITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SQ_SECURITY_CATEGORY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_STATUS;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_TAGS;
@@ -180,6 +193,7 @@ import static org.sonar.server.view.index.ViewIndexDefinition.TYPE_VIEW;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_AUTHOR;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CODE_VARIANTS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AT;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CWE;
@@ -196,6 +210,8 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SCOPES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SOFTWARE_QUALITIES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SOFTWARE_QUALITIES_SEVERTIIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SONARSOURCE_SECURITY;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS;
@@ -239,6 +255,10 @@ public class IssueIndex {
public enum Facet {
SEVERITIES(PARAM_SEVERITIES, FIELD_ISSUE_SEVERITY, STICKY, Severity.ALL.size()),
+ IMPACT_SOFTWARE_QUALITY(PARAM_SOFTWARE_QUALITIES, FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, STICKY, SoftwareQuality.values().length),
+ IMPACT_SEVERITY(PARAM_SOFTWARE_QUALITIES_SEVERTIIES, FIELD_ISSUE_IMPACT_SEVERITY, STICKY,
+ org.sonar.api.issue.impact.Severity.values().length),
+ CLEAN_CODE_ATTRIBUTE_CATEGORY(PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES, FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY, STICKY, CleanCodeAttributeCategory.values().length),
STATUSES(PARAM_STATUSES, FIELD_ISSUE_STATUS, STICKY, Issue.STATUSES.size()),
// Resolutions facet returns one more element than the number of resolutions to take into account unresolved issues
RESOLUTIONS(PARAM_RESOLUTIONS, FIELD_ISSUE_RESOLUTION, STICKY, Issue.RESOLUTIONS.size() + 1),
@@ -445,6 +465,10 @@ public class IssueIndex {
filters.addFilter(FIELD_ISSUE_TAGS, TAGS.getFilterScope(), createTermsFilter(FIELD_ISSUE_TAGS, query.tags()));
filters.addFilter(FIELD_ISSUE_TYPE, TYPES.getFilterScope(), createTermsFilter(FIELD_ISSUE_TYPE, query.types()));
filters.addFilter(
+ FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY,
+ CLEAN_CODE_ATTRIBUTE_CATEGORY.getFilterScope(),
+ createTermsFilter(FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY, query.cleanCodeAttributesCategories()));
+ filters.addFilter(
FIELD_ISSUE_RESOLUTION, RESOLUTIONS.getFilterScope(),
createTermsFilter(FIELD_ISSUE_RESOLUTION, query.resolutions()));
filters.addFilter(
@@ -468,7 +492,7 @@ public class IssueIndex {
addSecurityCategoryFilter(FIELD_ISSUE_SQ_SECURITY_CATEGORY, SONARSOURCE_SECURITY, query.sonarsourceSecurity(), filters);
addSeverityFilter(query, filters);
-
+ addImpactFilters(query, filters);
addComponentRelatedFilters(query, filters);
addDatesFilter(filters, query);
addCreatedAfterByProjectsFilter(filters, query);
@@ -490,7 +514,6 @@ public class IssueIndex {
}
}
-
private static Set<String> calculateRequirementsForOwaspAsvs40Params(IssueQuery query) {
int level = query.getOwaspAsvsLevel().orElse(3);
List<String> levelRequirements = OWASP_ASVS_40_REQUIREMENTS_BY_LEVEL.get(level);
@@ -573,6 +596,31 @@ public class IssueIndex {
}
}
+ private static void addImpactFilters(IssueQuery query, AllFilters allFilters) {
+ if (query.impactSoftwareQualities().isEmpty() && query.impactSeverities().isEmpty()) {
+ return;
+ }
+ if (!query.impactSoftwareQualities().isEmpty()) {
+ allFilters.addFilter(
+ FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY,
+ IMPACT_SOFTWARE_QUALITY.getFilterScope(),
+ nestedQuery(
+ FIELD_ISSUE_IMPACTS,
+ termsQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, query.impactSoftwareQualities()),
+ ScoreMode.Avg));
+ }
+
+ if (!query.impactSeverities().isEmpty()) {
+ allFilters.addFilter(
+ FIELD_ISSUE_IMPACT_SEVERITY,
+ IMPACT_SEVERITY.getFilterScope(),
+ nestedQuery(
+ FIELD_ISSUE_IMPACTS,
+ termsQuery(FIELD_ISSUE_IMPACT_SEVERITY, query.impactSeverities()),
+ ScoreMode.Avg));
+ }
+ }
+
private static void addComponentRelatedFilters(IssueQuery query, AllFilters filters) {
addCommonComponentRelatedFilters(query, filters);
if (query.viewUuids().isEmpty()) {
@@ -647,11 +695,11 @@ public class IssueIndex {
private static RequestFiltersComputer newFilterComputer(SearchOptions options, AllFilters allFilters) {
Collection<String> facetNames = options.getFacets();
Set<TopAggregationDefinition<?>> facets = Stream.concat(
- Stream.of(EFFORT_TOP_AGGREGATION),
- facetNames.stream()
- .map(FACETS_BY_NAME::get)
- .filter(Objects::nonNull)
- .map(Facet::getTopAggregationDef))
+ Stream.of(EFFORT_TOP_AGGREGATION),
+ facetNames.stream()
+ .map(FACETS_BY_NAME::get)
+ .filter(Objects::nonNull)
+ .map(Facet::getTopAggregationDef))
.collect(Collectors.toSet());
return new RequestFiltersComputer(allFilters, facets);
@@ -789,6 +837,7 @@ public class IssueIndex {
addFacetIfNeeded(options, aggregationHelper, esRequest, TAGS, query.tags().toArray());
addFacetIfNeeded(options, aggregationHelper, esRequest, TYPES, query.types().toArray());
addFacetIfNeeded(options, aggregationHelper, esRequest, CODE_VARIANTS, query.codeVariants().toArray());
+ addFacetIfNeeded(options, aggregationHelper, esRequest, CLEAN_CODE_ATTRIBUTE_CATEGORY, query.cleanCodeAttributesCategories().toArray());
addSecurityCategoryFacetIfNeeded(PARAM_PCI_DSS_32, PCI_DSS_32, options, aggregationHelper, esRequest, query.pciDss32().toArray());
addSecurityCategoryFacetIfNeeded(PARAM_PCI_DSS_40, PCI_DSS_40, options, aggregationHelper, esRequest, query.pciDss40().toArray());
@@ -800,6 +849,8 @@ public class IssueIndex {
addSecurityCategoryFacetIfNeeded(PARAM_SONARSOURCE_SECURITY, SONARSOURCE_SECURITY, options, aggregationHelper, esRequest, query.sonarsourceSecurity().toArray());
addSeverityFacetIfNeeded(options, aggregationHelper, esRequest);
+ addImpactSoftwareQualityFacetIfNeeded(options, query, aggregationHelper, esRequest);
+ addImpactSeverityFacetIfNeeded(options, query, aggregationHelper, esRequest);
addResolutionFacetIfNeeded(options, query, aggregationHelper, esRequest);
addAssigneesFacetIfNeeded(options, query, aggregationHelper, esRequest);
addCreatedAtFacetIfNeeded(options, query, aggregationHelper, queryFilters, esRequest);
@@ -848,6 +899,54 @@ public class IssueIndex {
esRequest.aggregation(aggregation);
}
+ private static void addImpactSoftwareQualityFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
+ if (!options.getFacets().contains(PARAM_SOFTWARE_QUALITIES)) {
+ return;
+ }
+
+ Function<SoftwareQuality, BoolQueryBuilder> mainQuery = softwareQuality -> boolQuery()
+ .filter(termQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, softwareQuality.name()));
+
+ FiltersAggregator.KeyedFilter[] keyedFilters = Arrays.stream(SoftwareQuality.values())
+ .map(softwareQuality -> new FiltersAggregator.KeyedFilter(softwareQuality.name(),
+ query.impactSeverities().isEmpty() ? mainQuery.apply(softwareQuality)
+ : mainQuery.apply(softwareQuality)
+ .filter(termsQuery(FIELD_ISSUE_IMPACT_SEVERITY, query.impactSeverities()))))
+ .toArray(FiltersAggregator.KeyedFilter[]::new);
+
+ AggregationBuilder aggregation = aggregationHelper.buildTopAggregation(
+ IMPACT_SOFTWARE_QUALITY.getName(), IMPACT_SOFTWARE_QUALITY.getTopAggregationDef(),
+ NO_EXTRA_FILTER,
+ t -> t.subAggregation(AggregationBuilders.nested("nested_" + IMPACT_SOFTWARE_QUALITY.getName(), FIELD_ISSUE_IMPACTS)
+ .subAggregation(filters(IMPACT_SOFTWARE_QUALITY.getName(), keyedFilters))));
+
+ esRequest.aggregation(aggregation);
+ }
+
+ private static void addImpactSeverityFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
+ if (!options.getFacets().contains(PARAM_SOFTWARE_QUALITIES_SEVERTIIES)) {
+ return;
+ }
+
+ Function<org.sonar.api.issue.impact.Severity, BoolQueryBuilder> mainQuery = softwareQuality -> boolQuery()
+ .filter(termQuery(FIELD_ISSUE_IMPACT_SEVERITY, softwareQuality.name()));
+
+ FiltersAggregator.KeyedFilter[] keyedFilters = Arrays.stream(org.sonar.api.issue.impact.Severity.values())
+ .map(severity -> new FiltersAggregator.KeyedFilter(severity.name(),
+ query.impactSoftwareQualities().isEmpty() ? mainQuery.apply(severity)
+ : mainQuery.apply(severity)
+ .filter(termsQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, query.impactSoftwareQualities()))))
+ .toArray(FiltersAggregator.KeyedFilter[]::new);
+
+ AggregationBuilder aggregation = aggregationHelper.buildTopAggregation(
+ IMPACT_SEVERITY.getName(), IMPACT_SEVERITY.getTopAggregationDef(),
+ NO_EXTRA_FILTER,
+ t -> t.subAggregation(AggregationBuilders.nested("nested_" + IMPACT_SEVERITY.getName(), FIELD_ISSUE_IMPACTS)
+ .subAggregation(filters(IMPACT_SEVERITY.getName(),
+ keyedFilters))));
+ esRequest.aggregation(aggregation);
+ }
+
private static void addResolutionFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
if (!options.getFacets().contains(PARAM_RESOLUTIONS)) {
return;
@@ -857,11 +956,11 @@ public class IssueIndex {
RESOLUTIONS.getName(), RESOLUTIONS.getTopAggregationDef(), RESOLUTIONS.getNumberOfTerms(),
NO_EXTRA_FILTER,
t ->
- // add aggregation of type "missing" to return count of unresolved issues in the facet
- t.subAggregation(
- addEffortAggregationIfNeeded(query, AggregationBuilders
- .missing(RESOLUTIONS.getName() + FACET_SUFFIX_MISSING)
- .field(RESOLUTIONS.getFieldName()))));
+ // add aggregation of type "missing" to return count of unresolved issues in the facet
+ t.subAggregation(
+ addEffortAggregationIfNeeded(query, AggregationBuilders
+ .missing(RESOLUTIONS.getName() + FACET_SUFFIX_MISSING)
+ .field(RESOLUTIONS.getFieldName()))));
esRequest.aggregation(aggregation);
}
@@ -980,10 +1079,10 @@ public class IssueIndex {
ASSIGNED_TO_ME.getTopAggregationDef(),
NO_EXTRA_FILTER,
t ->
- // add sub-aggregation to return issue count for current user
- aggregationHelper.getSubAggregationHelper()
- .buildSelectedItemsAggregation(ASSIGNED_TO_ME.getName(), ASSIGNED_TO_ME.getTopAggregationDef(), new String[]{uuid})
- .ifPresent(t::subAggregation));
+ // add sub-aggregation to return issue count for current user
+ aggregationHelper.getSubAggregationHelper()
+ .buildSelectedItemsAggregation(ASSIGNED_TO_ME.getName(), ASSIGNED_TO_ME.getTopAggregationDef(), new String[] {uuid})
+ .ifPresent(t::subAggregation));
esRequest.aggregation(aggregation);
}
}
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 a6c367cc8c4..11cd3614c1f 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
@@ -59,6 +59,8 @@ public class IssueQuery {
private final Collection<String> issueKeys;
private final Collection<String> severities;
+ private final Collection<String> impactSeverities;
+ private final Collection<String> impactSoftwareQualities;
private final Collection<String> statuses;
private final Collection<String> resolutions;
private final Collection<String> components;
@@ -99,10 +101,13 @@ public class IssueQuery {
private final Boolean newCodeOnReference;
private final Collection<String> newCodeOnReferenceByProjectUuids;
private final Collection<String> codeVariants;
+ private Collection<String> cleanCodeAttributesCategories;
private IssueQuery(Builder builder) {
this.issueKeys = defaultCollection(builder.issueKeys);
this.severities = defaultCollection(builder.severities);
+ this.impactSeverities = defaultCollection(builder.impactSeverities);
+ this.impactSoftwareQualities = defaultCollection(builder.impactSoftwareQualities);
this.statuses = defaultCollection(builder.statuses);
this.resolutions = defaultCollection(builder.resolutions);
this.components = defaultCollection(builder.components);
@@ -143,6 +148,7 @@ public class IssueQuery {
this.newCodeOnReference = builder.newCodeOnReference;
this.newCodeOnReferenceByProjectUuids = defaultCollection(builder.newCodeOnReferenceByProjectUuids);
this.codeVariants = defaultCollection(builder.codeVariants);
+ this.cleanCodeAttributesCategories = defaultCollection(builder.cleanCodeAttributesCategories);
}
public Collection<String> issueKeys() {
@@ -153,6 +159,14 @@ public class IssueQuery {
return severities;
}
+ public Collection<String> impactSeverities() {
+ return impactSeverities;
+ }
+
+ public Collection<String> impactSoftwareQualities() {
+ return impactSoftwareQualities;
+ }
+
public Collection<String> statuses() {
return statuses;
}
@@ -338,9 +352,15 @@ public class IssueQuery {
return codeVariants;
}
+ public Collection<String> cleanCodeAttributesCategories() {
+ return cleanCodeAttributesCategories;
+ }
+
public static class Builder {
private Collection<String> issueKeys;
private Collection<String> severities;
+ private Collection<String> impactSeverities;
+ private Collection<String> impactSoftwareQualities;
private Collection<String> statuses;
private Collection<String> resolutions;
private Collection<String> components;
@@ -381,6 +401,7 @@ public class IssueQuery {
private Boolean newCodeOnReference = null;
private Collection<String> newCodeOnReferenceByProjectUuids;
private Collection<String> codeVariants;
+ private Collection<String> cleanCodeAttributesCategories;
private Builder() {
@@ -421,6 +442,16 @@ public class IssueQuery {
return this;
}
+ public Builder impactSeverities(@Nullable Collection<String> l) {
+ this.impactSeverities = l;
+ return this;
+ }
+
+ public Builder impactSoftwareQualities(@Nullable Collection<String> l) {
+ this.impactSoftwareQualities = l;
+ return this;
+ }
+
public Builder files(@Nullable Collection<String> l) {
this.files = l;
return this;
@@ -626,6 +657,11 @@ public class IssueQuery {
this.codeVariants = codeVariants;
return this;
}
+
+ public Builder cleanCodeAttributesCategories(@Nullable Collection<String> cleanCodeAttributesCategories) {
+ this.cleanCodeAttributesCategories = cleanCodeAttributesCategories;
+ return this;
+ }
}
private static <T> Collection<T> defaultCollection(@Nullable Collection<T> c) {
diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java
index 00046aa6012..cc9ede60838 100644
--- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java
+++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java
@@ -23,8 +23,10 @@ import java.time.ZoneId;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
+import java.util.Set;
import org.elasticsearch.action.search.SearchResponse;
import org.junit.Test;
+import org.sonar.api.issue.impact.Severity;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.rule.RulesDefinition.OwaspAsvsVersion;
import org.sonar.db.component.ComponentDto;
@@ -47,11 +49,17 @@ import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
import static org.sonar.api.issue.Issue.STATUS_OPEN;
import static org.sonar.api.issue.Issue.STATUS_REOPENED;
import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
+import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY;
+import static org.sonar.api.issue.impact.SoftwareQuality.RELIABILITY;
import static org.sonar.api.rule.Severity.BLOCKER;
import static org.sonar.api.rule.Severity.CRITICAL;
import static org.sonar.api.rule.Severity.INFO;
import static org.sonar.api.rule.Severity.MAJOR;
import static org.sonar.api.rule.Severity.MINOR;
+import static org.sonar.api.rules.CleanCodeAttributeCategory.ADAPTABLE;
+import static org.sonar.api.rules.CleanCodeAttributeCategory.CONSISTENT;
+import static org.sonar.api.rules.CleanCodeAttributeCategory.INTENTIONAL;
+import static org.sonar.api.rules.CleanCodeAttributeCategory.RESPONSIBLE;
import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2017;
import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2021;
import static org.sonar.api.server.rule.RulesDefinition.PciDssVersion.V3_2;
@@ -137,7 +145,8 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon {
@Test
public void facet_on_directories_return_100_entries_plus_selected_values() {
ComponentDto project = newPrivateProjectDto();
- indexIssues(rangeClosed(1, 110).mapToObj(i -> newDoc(newFileDto(project, newDirectory(project, "dir" + i)), project.uuid()).setDirectoryPath("a" + i)).toArray(IssueDoc[]::new));
+ indexIssues(
+ rangeClosed(1, 110).mapToObj(i -> newDoc(newFileDto(project, newDirectory(project, "dir" + i)), project.uuid()).setDirectoryPath("a" + i)).toArray(IssueDoc[]::new));
IssueDoc issue1 = newDoc(newFileDto(project, newDirectory(project, "path1")), project.uuid()).setDirectoryPath("directory1");
IssueDoc issue2 = newDoc(newFileDto(project, newDirectory(project, "path2")), project.uuid()).setDirectoryPath("directory2");
indexIssues(issue1, issue2);
@@ -539,8 +548,8 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon {
SearchOptions options = fixtureForCreatedAtFacet();
SearchResponse result = underTest.search(IssueQuery.builder()
- .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
- .createdBefore(parseDateTime("2014-09-21T00:00:00+0100")).build(),
+ .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
+ .createdBefore(parseDateTime("2014-09-21T00:00:00+0100")).build(),
options);
Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
assertThat(createdAt).containsOnly(
@@ -555,8 +564,8 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon {
SearchOptions options = fixtureForCreatedAtFacet();
SearchResponse result = underTest.search(IssueQuery.builder()
- .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
- .createdBefore(parseDateTime("2015-01-19T00:00:00+0100")).build(),
+ .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
+ .createdBefore(parseDateTime("2015-01-19T00:00:00+0100")).build(),
options);
Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
assertThat(createdAt).containsOnly(
@@ -573,8 +582,8 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon {
SearchOptions options = fixtureForCreatedAtFacet();
SearchResponse result = underTest.search(IssueQuery.builder()
- .createdAfter(parseDateTime("2011-01-01T00:00:00+0100"))
- .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
+ .createdAfter(parseDateTime("2011-01-01T00:00:00+0100"))
+ .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
options);
Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
assertThat(createdAt).containsOnly(
@@ -591,8 +600,8 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon {
SearchOptions options = fixtureForCreatedAtFacet();
SearchResponse result = underTest.search(IssueQuery.builder()
- .createdAfter(parseDateTime("2014-09-01T00:00:00-0100"))
- .createdBefore(parseDateTime("2014-09-02T00:00:00-0100")).build(),
+ .createdAfter(parseDateTime("2014-09-01T00:00:00-0100"))
+ .createdBefore(parseDateTime("2014-09-02T00:00:00-0100")).build(),
options);
Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
assertThat(createdAt).containsOnly(
@@ -624,7 +633,7 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon {
SearchOptions searchOptions = fixtureForCreatedAtFacet();
SearchResponse result = underTest.search(IssueQuery.builder()
- .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
+ .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
searchOptions);
Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
assertThat(createdAt).containsOnly(
@@ -661,6 +670,155 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon {
entry("variant3", 1L));
}
+ @Test
+ public void search_shouldReturnSoftwareQualityFacet() {
+ ComponentDto project = newPrivateProjectDto();
+ ComponentDto file = newFileDto(project);
+
+ indexIssues(
+ newDoc("I1", project.uuid(), file).setImpacts(Map.of(
+ MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH,
+ RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM)),
+ newDoc("I2", project.uuid(), file).setImpacts(Map.of(
+ MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)),
+ newDoc("I3", project.uuid(), file).setImpacts(Map.of(
+ RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)),
+ newDoc("I4", project.uuid(), file).setImpacts(Map.of(
+ MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));
+
+ assertThatFacetHasOnly(IssueQuery.builder(), "softwareQualities",
+ entry("MAINTAINABILITY", 3L),
+ entry("RELIABILITY", 2L),
+ entry("SECURITY", 0L));
+ }
+
+ @Test
+ public void search_whenFilteredOnSeverity_shouldReturnSoftwareQualityFacet() {
+ ComponentDto project = newPrivateProjectDto();
+ ComponentDto file = newFileDto(project);
+
+ indexIssues(
+ newDoc("I1", project.uuid(), file).setImpacts(Map.of(
+ MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH,
+ RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM))
+ .setTags(singletonList("my-tag")),
+ newDoc("I2", project.uuid(), file).setImpacts(Map.of(
+ MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)),
+ newDoc("I3", project.uuid(), file).setImpacts(Map.of(
+ RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)),
+ newDoc("I4", project.uuid(), file).setImpacts(Map.of(
+ MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));
+
+ assertThatFacetHasOnly(IssueQuery.builder().impactSoftwareQualities(Set.of(MAINTAINABILITY.name())).impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.LOW.name())),
+ "softwareQualities",
+ entry("MAINTAINABILITY", 2L),
+ entry("RELIABILITY", 0L),
+ entry("SECURITY", 0L));
+
+ assertThatFacetHasOnly(IssueQuery.builder().impactSeverities(Set.of(Severity.MEDIUM.name())), "softwareQualities",
+ entry("MAINTAINABILITY", 0L),
+ entry("RELIABILITY", 1L),
+ entry("SECURITY", 0L));
+
+ assertThatFacetHasOnly(IssueQuery.builder().impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())), "softwareQualities",
+ entry("MAINTAINABILITY", 1L),
+ entry("RELIABILITY", 1L),
+ entry("SECURITY", 0L));
+
+ assertThatFacetHasOnly(IssueQuery.builder()
+ .tags(singletonList("my-tag"))
+ .impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())), "softwareQualities",
+ entry("MAINTAINABILITY", 1L),
+ entry("RELIABILITY", 0L),
+ entry("SECURITY", 0L));
+ }
+
+ @Test
+ public void search_shouldReturnSoftwareQualitySeverityFacet() {
+ ComponentDto project = newPrivateProjectDto();
+ ComponentDto file = newFileDto(project);
+
+ indexIssues(
+ newDoc("I1", project.uuid(), file).setImpacts(Map.of(
+ MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH,
+ RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM)),
+ newDoc("I2", project.uuid(), file).setImpacts(Map.of(
+ MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)),
+ newDoc("I3", project.uuid(), file).setImpacts(Map.of(
+ RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)),
+ newDoc("I4", project.uuid(), file).setImpacts(Map.of(
+ MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));
+
+ assertThatFacetHasOnly(IssueQuery.builder(), "softwareQualitiesSeverities",
+ entry("HIGH", 2L),
+ entry("MEDIUM", 1L),
+ entry("LOW", 2L));
+ }
+
+ @Test
+ public void search_whenFilteredOnSoftwareQuality_shouldReturnSoftwareQualitySeverityFacet() {
+ ComponentDto project = newPrivateProjectDto();
+ ComponentDto file = newFileDto(project);
+
+ indexIssues(
+ newDoc("I1", project.uuid(), file).setImpacts(Map.of(
+ MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH,
+ RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM)),
+ newDoc("I2", project.uuid(), file).setImpacts(Map.of(
+ MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)),
+ newDoc("I3", project.uuid(), file).setImpacts(Map.of(
+ RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)),
+ newDoc("I4", project.uuid(), file).setImpacts(Map.of(
+ MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));
+
+ assertThatFacetHasOnly(IssueQuery.builder().impactSoftwareQualities(Set.of(MAINTAINABILITY.name())), "softwareQualitiesSeverities",
+ entry("HIGH", 1L),
+ entry("MEDIUM", 0L),
+ entry("LOW", 2L));
+ }
+
+ @Test
+ public void search_shouldReturnCleanCodeAttributeCategoryFacet() {
+ ComponentDto project = newPrivateProjectDto();
+ ComponentDto file = newFileDto(project);
+
+ indexIssues(
+ newDoc("I1", project.uuid(), file).setCleanCodeAttributeCategory(ADAPTABLE.name()),
+ newDoc("I2", project.uuid(), file).setCleanCodeAttributeCategory(ADAPTABLE.name()),
+ newDoc("I3", project.uuid(), file).setCleanCodeAttributeCategory(CONSISTENT.name()),
+ newDoc("I4", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
+ newDoc("I5", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
+ newDoc("I6", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
+ newDoc("I7", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
+ newDoc("I8", project.uuid(), file).setCleanCodeAttributeCategory(RESPONSIBLE.name()));
+
+ assertThatFacetHasOnly(IssueQuery.builder(), "cleanCodeAttributeCategories",
+ entry("INTENTIONAL", 4L),
+ entry("ADAPTABLE", 2L),
+ entry("CONSISTENT", 1L),
+ entry("RESPONSIBLE", 1L));
+ }
+
+ @Test
+ public void search_whenFilteredByTags_shouldReturnCleanCodeAttributeCategoryFacet() {
+ ComponentDto project = newPrivateProjectDto();
+ ComponentDto file = newFileDto(project);
+
+ indexIssues(
+ newDoc("I1", project.uuid(), file).setCleanCodeAttributeCategory(ADAPTABLE.name()).setTags(singletonList("tag-1")),
+ newDoc("I2", project.uuid(), file).setCleanCodeAttributeCategory(ADAPTABLE.name()).setTags(singletonList("tag-1")),
+ newDoc("I3", project.uuid(), file).setCleanCodeAttributeCategory(CONSISTENT.name()),
+ newDoc("I4", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()).setTags(singletonList("tag-1")),
+ newDoc("I5", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
+ newDoc("I6", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
+ newDoc("I7", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
+ newDoc("I8", project.uuid(), file).setCleanCodeAttributeCategory(RESPONSIBLE.name()).setTags(singletonList("tag-3")));
+
+ assertThatFacetHasOnly(IssueQuery.builder().tags(singletonList("tag-1")), "cleanCodeAttributeCategories",
+ entry("INTENTIONAL", 1L),
+ entry("ADAPTABLE", 2L));
+ }
+
private SearchOptions fixtureForCreatedAtFacet() {
ComponentDto project = newPrivateProjectDto();
ComponentDto file = newFileDto(project);
diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java
index 6ad32771478..d6f0c5a7519 100644
--- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java
+++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java
@@ -19,9 +19,9 @@
*/
package org.sonar.server.issue.index;
-import com.google.common.collect.ImmutableMap;
import java.util.Date;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.assertj.core.api.Fail;
import org.junit.Test;
@@ -39,7 +39,14 @@ import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY;
+import static org.sonar.api.issue.impact.SoftwareQuality.RELIABILITY;
+import static org.sonar.api.issue.impact.SoftwareQuality.SECURITY;
import static org.sonar.api.resources.Qualifiers.APP;
+import static org.sonar.api.rules.CleanCodeAttributeCategory.ADAPTABLE;
+import static org.sonar.api.rules.CleanCodeAttributeCategory.CONSISTENT;
+import static org.sonar.api.rules.CleanCodeAttributeCategory.INTENTIONAL;
+import static org.sonar.api.rules.CleanCodeAttributeCategory.RESPONSIBLE;
import static org.sonar.api.utils.DateUtils.addDays;
import static org.sonar.api.utils.DateUtils.parseDate;
import static org.sonar.api.utils.DateUtils.parseDateTime;
@@ -347,25 +354,25 @@ public class IssueIndexFiltersTest extends IssueIndexTestCommon {
// Search for issues of project 1 having less than 15 days
assertThatSearchReturnsOnly(IssueQuery.builder()
- .createdAfterByProjectUuids(ImmutableMap.of(project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -15), true))),
+ .createdAfterByProjectUuids(Map.of(project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -15), true))),
project1Issue1.key());
// Search for issues of project 1 having less than 14 days and project 2 having less then 25 days
assertThatSearchReturnsOnly(IssueQuery.builder()
- .createdAfterByProjectUuids(ImmutableMap.of(
- project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -14), true),
- project2.uuid(), new IssueQuery.PeriodStart(addDays(now, -25), true))),
+ .createdAfterByProjectUuids(Map.of(
+ project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -14), true),
+ project2.uuid(), new IssueQuery.PeriodStart(addDays(now, -25), true))),
project1Issue1.key(), project2Issue1.key());
// Search for issues of project 1 having less than 30 days
assertThatSearchReturnsOnly(IssueQuery.builder()
- .createdAfterByProjectUuids(ImmutableMap.of(
- project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -30), true))),
+ .createdAfterByProjectUuids(Map.of(
+ project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -30), true))),
project1Issue1.key(), project1Issue2.key());
// Search for issues of project 1 and project 2 having less than 5 days
assertThatSearchReturnsOnly(IssueQuery.builder()
- .createdAfterByProjectUuids(ImmutableMap.of(
+ .createdAfterByProjectUuids(Map.of(
project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -5), true),
project2.uuid(), new IssueQuery.PeriodStart(addDays(now, -5), true))));
}
@@ -396,29 +403,29 @@ public class IssueIndexFiltersTest extends IssueIndexTestCommon {
// Search for issues of project 1 branch 1 having less than 15 days
assertThatSearchReturnsOnly(IssueQuery.builder()
- .mainBranch(false)
- .createdAfterByProjectUuids(ImmutableMap.of(project1Branch1.uuid(), new IssueQuery.PeriodStart(addDays(now, -15), true))),
+ .mainBranch(false)
+ .createdAfterByProjectUuids(Map.of(project1Branch1.uuid(), new IssueQuery.PeriodStart(addDays(now, -15), true))),
project1Branch1Issue1.key());
// Search for issues of project 1 branch 1 having less than 14 days and project 2 branch 1 having less then 25 days
assertThatSearchReturnsOnly(IssueQuery.builder()
- .mainBranch(false)
- .createdAfterByProjectUuids(ImmutableMap.of(
- project1Branch1.uuid(), new IssueQuery.PeriodStart(addDays(now, -14), true),
- project2Branch1.uuid(), new IssueQuery.PeriodStart(addDays(now, -25), true))),
+ .mainBranch(false)
+ .createdAfterByProjectUuids(Map.of(
+ project1Branch1.uuid(), new IssueQuery.PeriodStart(addDays(now, -14), true),
+ project2Branch1.uuid(), new IssueQuery.PeriodStart(addDays(now, -25), true))),
project1Branch1Issue1.key(), project2Branch1Issue1.key());
// Search for issues of project 1 branch 1 having less than 30 days
assertThatSearchReturnsOnly(IssueQuery.builder()
- .mainBranch(false)
- .createdAfterByProjectUuids(ImmutableMap.of(
- project1Branch1.uuid(), new IssueQuery.PeriodStart(addDays(now, -30), true))),
+ .mainBranch(false)
+ .createdAfterByProjectUuids(Map.of(
+ project1Branch1.uuid(), new IssueQuery.PeriodStart(addDays(now, -30), true))),
project1Branch1Issue1.key(), project1Branch1Issue2.key());
// Search for issues of project 1 branch 1 and project 2 branch 2 having less than 5 days
assertThatSearchReturnsOnly(IssueQuery.builder()
.mainBranch(false)
- .createdAfterByProjectUuids(ImmutableMap.of(
+ .createdAfterByProjectUuids(Map.of(
project1Branch1.uuid(), new IssueQuery.PeriodStart(addDays(now, -5), true),
project2Branch1.uuid(), new IssueQuery.PeriodStart(addDays(now, -5), true))));
}
@@ -435,7 +442,7 @@ public class IssueIndexFiltersTest extends IssueIndexTestCommon {
// Search for issues of project 1 and project 2 that are new code on a branch using reference for new code
assertThatSearchReturnsOnly(IssueQuery.builder()
- .newCodeOnReferenceByProjectUuids(Set.of(project1.uuid(), project2.uuid())),
+ .newCodeOnReferenceByProjectUuids(Set.of(project1.uuid(), project2.uuid())),
project1Issue1.key(), project2Issue2.key());
}
@@ -463,8 +470,8 @@ public class IssueIndexFiltersTest extends IssueIndexTestCommon {
// Search for issues of project 1 branch 1 and project 2 branch 1 that are new code on a branch using reference for new code
assertThatSearchReturnsOnly(IssueQuery.builder()
- .mainBranch(false)
- .newCodeOnReferenceByProjectUuids(Set.of(project1Branch1.uuid(), project2Branch1.uuid())),
+ .mainBranch(false)
+ .newCodeOnReferenceByProjectUuids(Set.of(project1Branch1.uuid(), project2Branch1.uuid())),
project1Branch1Issue2.key(), project2Branch1Issue2.key());
}
@@ -852,6 +859,70 @@ public class IssueIndexFiltersTest extends IssueIndexTestCommon {
assertThatSearchReturnsOnly(IssueQuery.builder().codeVariants(singletonList("variant2")), "I1", "I2");
}
+ @Test
+ public void search_whenFilteringBySoftwareQualities_shouldReturnRelevantIssues() {
+ ComponentDto project = newPrivateProjectDto();
+ ComponentDto file = newFileDto(project);
+
+ indexIssues(
+ newDoc("I1", project.uuid(), file).setImpacts(Map.of(
+ MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH,
+ SECURITY, org.sonar.api.issue.impact.Severity.LOW,
+ RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM)),
+
+ newDoc("I2", project.uuid(), file).setImpacts(Map.of(
+ MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW,
+ SECURITY, org.sonar.api.issue.impact.Severity.LOW)),
+ newDoc("I3", project.uuid(), file).setImpacts(Map.of(
+ RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)),
+ newDoc("I4", project.uuid(), file).setImpacts(Map.of(
+ MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));
+
+ assertThatSearchReturnsOnly(IssueQuery.builder().impactSoftwareQualities(Set.of(MAINTAINABILITY.name())),
+ "I1", "I2", "I4");
+
+ assertThatSearchReturnsOnly(IssueQuery.builder().impactSoftwareQualities(Set.of(MAINTAINABILITY.name(), RELIABILITY.name())),
+ "I1", "I2", "I3", "I4");
+
+ assertThatSearchReturnsOnly(IssueQuery.builder().impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())),
+ "I1", "I3");
+
+ assertThatSearchReturnsOnly(IssueQuery.builder().impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.LOW.name(), org.sonar.api.issue.impact.Severity.MEDIUM.name())),
+ "I1", "I2", "I4");
+
+ assertThatSearchReturnsOnly(IssueQuery.builder()
+ .impactSoftwareQualities(Set.of(MAINTAINABILITY.name()))
+ .impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())),
+ "I1");
+
+ }
+
+ @Test
+ public void search_whenFilteringByCleanCodeAttributeCategory_shouldReturnRelevantIssues() {
+ ComponentDto project = newPrivateProjectDto();
+ ComponentDto file = newFileDto(project);
+
+ indexIssues(
+ newDoc("I1", project.uuid(), file).setCleanCodeAttributeCategory(ADAPTABLE.name()),
+ newDoc("I2", project.uuid(), file).setCleanCodeAttributeCategory(ADAPTABLE.name()),
+ newDoc("I3", project.uuid(), file).setCleanCodeAttributeCategory(CONSISTENT.name()),
+ newDoc("I4", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
+ newDoc("I5", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
+ newDoc("I6", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
+ newDoc("I7", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
+ newDoc("I8", project.uuid(), file).setCleanCodeAttributeCategory(RESPONSIBLE.name()));
+
+ assertThatSearchReturnsOnly(IssueQuery.builder().cleanCodeAttributesCategories(Set.of(ADAPTABLE.name())),
+ "I1", "I2");
+
+ assertThatSearchReturnsOnly(IssueQuery.builder().cleanCodeAttributesCategories(Set.of(CONSISTENT.name(), INTENTIONAL.name())),
+ "I3", "I4", "I5", "I6", "I7");
+
+ assertThatSearchReturnsOnly(IssueQuery.builder().cleanCodeAttributesCategories(
+ Set.of(CONSISTENT.name(), INTENTIONAL.name(), RESPONSIBLE.name(), ADAPTABLE.name())),
+ "I1", "I2", "I3", "I4", "I5", "I6", "I7", "I8");
+ }
+
private void indexView(String viewUuid, List<String> projectBranchUuids) {
viewIndexer.index(new ViewDoc().setUuid(viewUuid).setProjectBranchUuids(projectBranchUuids));
}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java
index 1284b1461b8..176b205b349 100644
--- a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java
@@ -54,6 +54,11 @@ public class IssuesWsParameters {
public static final String PARAM_TYPE = "type";
public static final String PARAM_ISSUES = "issues";
public static final String PARAM_SEVERITIES = "severities";
+ public static final String PARAM_SOFTWARE_QUALITIES = "softwareQualities";
+
+ //TODO: To be discussed for the naming
+ public static final String PARAM_SOFTWARE_QUALITIES_SEVERTIIES = "softwareQualitiesSeverities";
+ public static final String PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES = "cleanCodeAttributeCategories";
public static final String PARAM_STATUSES = "statuses";
public static final String PARAM_RESOLUTIONS = "resolutions";
public static final String PARAM_RESOLVED = "resolved";