- Refactor issue indexer to use MyBatis mapping with cursortags/10.2.0.77647
@@ -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; | |||
@@ -198,6 +200,40 @@ public class IssueDaoIT { | |||
tuple(Severity.LOW, SoftwareQuality.SECURITY)); | |||
} | |||
@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 |
@@ -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; | |||
} | |||
} |
@@ -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)); |
@@ -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); |
@@ -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 |
@@ -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); | |||
} | |||
} |
@@ -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 |
@@ -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 |
@@ -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); |
@@ -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(); |
@@ -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(); | |||
} | |||
} |
@@ -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; |
@@ -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), | |||
@@ -444,6 +464,10 @@ public class IssueIndex { | |||
filters.addFilter(FIELD_ISSUE_LANGUAGE, LANGUAGES.getFilterScope(), createTermsFilter(FIELD_ISSUE_LANGUAGE, query.languages())); | |||
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())); | |||
@@ -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); | |||
} | |||
} |
@@ -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) { |
@@ -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); |
@@ -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)); | |||
} |
@@ -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"; |