package org.sonar.db.issue;
import java.security.SecureRandom;
-import java.sql.SQLException;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
assertThat(issue.getEffectiveCleanCodeAttribute()).isEqualTo(RULE.getCleanCodeAttribute());
assertThat(issue.parseLocations()).isNull();
assertThat(issue.getImpacts())
- .extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality)
+ .extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality, ImpactDto::isManualSeverity)
.containsExactlyInAnyOrder(
- tuple(MEDIUM, RELIABILITY),
- tuple(LOW, SECURITY));
+ tuple(MEDIUM, RELIABILITY, false),
+ tuple(LOW, SECURITY, false));
assertThat(issue.getRuleDefaultImpacts())
.extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality)
.containsExactlyInAnyOrder(tuple(HIGH, MAINTAINABILITY));
}
@Test
- void scrollIndexationIssues_shouldReturnDto() throws SQLException {
+ void scrollIndexationIssues_shouldReturnDto() {
ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("java").setLanguage("java")
.replaceAllDefaultImpacts(List.of(new ImpactDto()
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));
+ IntStream.range(0, 100).forEach(i -> insertBranchIssueWithManualSeverity(branchA, fileA, rule, "A" + i, STATUS_OPEN, 1_340_000_000_000L));
Cursor<IndexedIssueDto> issues = underTest.scrollIssuesForIndexation(db.getSession(), null, null);
while (iterator.hasNext()) {
IndexedIssueDto next = iterator.next();
assertThat(next.getRuleDefaultImpacts()).hasSize(2)
- .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
+ .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity, ImpactDto::isManualSeverity)
.containsExactlyInAnyOrder(
- tuple(RELIABILITY, LOW),
- tuple(MAINTAINABILITY, MEDIUM));
+ tuple(RELIABILITY, LOW, false),
+ tuple(MAINTAINABILITY, MEDIUM, false));
assertThat(next.getImpacts())
- .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
+ .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity, ImpactDto::isManualSeverity)
.containsExactlyInAnyOrder(
- tuple(MAINTAINABILITY, HIGH));
+ tuple(MAINTAINABILITY, HIGH, true),
+ tuple(RELIABILITY, LOW, false));
issueCount++;
}
assertThat(issueCount).isEqualTo(100);
void insert_shouldInsertBatchIssuesWithImpacts() {
ImpactDto impact1 = new ImpactDto()
.setSoftwareQuality(MAINTAINABILITY)
- .setSeverity(HIGH);
+ .setSeverity(HIGH)
+ .setManualSeverity(false);
ImpactDto impact2 = new ImpactDto()
.setSoftwareQuality(SECURITY)
- .setSeverity(LOW);
+ .setSeverity(LOW)
+ .setManualSeverity(true);
IssueDto issue1 = createIssueWithKey(ISSUE_KEY1)
.addImpact(impact1)
.addImpact(impact2);
.setSeverity(HIGH);
ImpactDto impact2 = new ImpactDto()
.setSoftwareQuality(SECURITY)
- .setSeverity(LOW);
+ .setSeverity(LOW)
+ .setManualSeverity(true);
IssueDto issue1 = createIssueWithKey(ISSUE_KEY1)
.addImpact(impact1)
.addImpact(impact2);
.containsExactlyInAnyOrder(tuple(RELIABILITY, MEDIUM), tuple(SECURITY, LOW));
}
+ @Test
+ void insertIssueImpacts_should_insert_all_values() {
+ IssueDto issueDto = createIssueWithKey(ISSUE_KEY1);
+ ImpactDto impactDto1 = new ImpactDto(MAINTAINABILITY, MEDIUM, true);
+ ImpactDto impactDto2 = new ImpactDto(RELIABILITY, HIGH, false);
+ issueDto.addImpact(impactDto1);
+ issueDto.addImpact(impactDto2);
+ underTest.insert(db.getSession(), issueDto);
+
+ assertThat(underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1).getImpacts()).extracting(ImpactDto::getSoftwareQuality,
+ ImpactDto::getSeverity, ImpactDto::isManualSeverity)
+ .containsExactlyInAnyOrder(tuple(MAINTAINABILITY, MEDIUM, true), tuple(RELIABILITY, HIGH, false));
+ }
+
private static IssueDto createIssueWithKey(String issueKey) {
return createIssueWithKey(issueKey, PROJECT_UUID, FILE_UUID);
}
.setMessageFormattings(MESSAGE_FORMATTING));
}
+ private void insertBranchIssueWithManualSeverity(ComponentDto branch, ComponentDto file, RuleDto rule, String id, String status,
+ Long updateAt) {
+ db.issues().insert(rule, branch, file, i -> i.setKee("issue" + id)
+ .setStatus(status)
+ .setResolution(null)
+ .setUpdatedAt(updateAt)
+ .setType(randomRuleTypeExceptHotspot())
+ .setMessage("message")
+ .setMessageFormattings(MESSAGE_FORMATTING)
+ .replaceAllImpacts(List.of(
+ new ImpactDto()
+ .setSoftwareQuality(MAINTAINABILITY)
+ .setSeverity(HIGH)
+ .setManualSeverity(true),
+ new ImpactDto()
+ .setSoftwareQuality(RELIABILITY)
+ .setSeverity(LOW)
+ .setManualSeverity(false))));
+ }
+
private void insertBranchIssue(ComponentDto branch, ComponentDto file, RuleDto rule, String id, String status, Long updateAt) {
insertBranchIssue(branch, file, rule, id, status, null, updateAt);
}
public class ImpactDto implements Serializable {
private SoftwareQuality softwareQuality;
private Severity severity;
+ private boolean manualSeverity;
public ImpactDto() {
// nothing to do
}
- public ImpactDto(SoftwareQuality softwareQuality, Severity severity) {
+ public ImpactDto(SoftwareQuality softwareQuality, Severity severity, boolean manualSeverity) {
this.softwareQuality = softwareQuality;
this.severity = severity;
+ this.manualSeverity = manualSeverity;
+ }
+
+ public ImpactDto(SoftwareQuality softwareQuality, Severity severity) {
+ this(softwareQuality, severity, false);
}
public SoftwareQuality getSoftwareQuality() {
return this;
}
+ public boolean isManualSeverity() {
+ return manualSeverity;
+ }
+
+ public ImpactDto setManualSeverity(boolean manualSeverity) {
+ this.manualSeverity = manualSeverity;
+ return this;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
ImpactDto impactDto = (ImpactDto) o;
return Objects.equals(softwareQuality, impactDto.softwareQuality)
- && Objects.equals(severity, impactDto.severity);
+ && Objects.equals(severity, impactDto.severity)
+ && Objects.equals(manualSeverity, impactDto.manualSeverity);
}
@Override
public int hashCode() {
- return Objects.hash(softwareQuality, severity);
+ return Objects.hash(softwareQuality, severity, manualSeverity);
}
}
<sql id="issueImpactsColumns">
ii.software_quality as "ii_softwareQuality",
ii.severity as "ii_severity",
+ ii.manual_severity as "ii_manualSeverity",
</sql>
<sql id="ruleDefaultImpactsColumns">
javaType="java.util.Set" ofType="Impact">
<result property="softwareQuality" column="ii_softwareQuality"/>
<result property="severity" column="ii_severity"/>
+ <result property="manualSeverity" column="ii_manualSeverity"/>
</collection>
<collection property="ruleDefaultImpacts" column="rdi_softwareQuality" notNullColumn="rdi_softwareQuality"
javaType="java.util.Set" ofType="Impact">
</insert>
<insert id="insertIssueImpact" parameterType="map" useGeneratedKeys="false">
- INSERT INTO issues_impacts (issue_key, software_quality, severity)
+ INSERT INTO issues_impacts (issue_key, software_quality, severity, manual_severity)
VALUES (
#{issueKey,jdbcType=VARCHAR},
#{dto.softwareQuality,jdbcType=VARCHAR},
- #{dto.severity,jdbcType=VARCHAR})
+ #{dto.severity,jdbcType=VARCHAR},
+ #{dto.manualSeverity,jdbcType=BOOLEAN})
</insert>
<delete id="deleteAsNewCodeOnReferenceBranch" parameterType="String">
javaType="java.util.Set" ofType="Impact">
<result property="softwareQuality" column="ii_softwareQuality"/>
<result property="severity" column="ii_severity"/>
+ <result property="manualSeverity" column="ii_manualSeverity"/>
</collection>
<collection property="ruleDefaultImpacts" column="rdi_softwareQuality" notNullColumn="rdi_softwareQuality"
javaType="java.util.Set" ofType="Impact">
CREATE TABLE "ISSUES_IMPACTS"(
"ISSUE_KEY" CHARACTER VARYING(40) NOT NULL,
"SOFTWARE_QUALITY" CHARACTER VARYING(40) NOT NULL,
- "SEVERITY" CHARACTER VARYING(40) NOT NULL
+ "SEVERITY" CHARACTER VARYING(40) NOT NULL,
+ "MANUAL_SEVERITY" BOOLEAN DEFAULT FALSE NOT NULL
);
ALTER TABLE "ISSUES_IMPACTS" ADD CONSTRAINT "PK_ISSUES_IMPACTS" PRIMARY KEY("ISSUE_KEY", "SOFTWARE_QUALITY");
CREATE UNIQUE NULLS NOT DISTINCT INDEX "UNIQ_ISS_KEY_SOF_QUAL" ON "ISSUES_IMPACTS"("ISSUE_KEY" NULLS FIRST, "SOFTWARE_QUALITY" NULLS FIRST);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.server.platform.db.migration.version.v108;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.sonar.db.MigrationDbTester;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class AddManualSeverityColumnInIssuesImpactsTableTest {
+
+ @RegisterExtension
+ public final MigrationDbTester db = MigrationDbTester.createForMigrationStep(AddManualSeverityColumnInIssuesImpactsTable.class);
+ private final AddManualSeverityColumnInIssuesImpactsTable underTest = new AddManualSeverityColumnInIssuesImpactsTable(db.database());
+
+ @Test
+ void execute_whenColumnDoesNotExist_shouldCreateColumn() throws Exception {
+ db.assertColumnDoesNotExist(AddManualSeverityColumnInIssuesImpactsTable.TABLE_NAME, AddManualSeverityColumnInIssuesImpactsTable.MANUAL_SEVERITY);
+ underTest.execute();
+ db.assertColumnDefinition(AddManualSeverityColumnInIssuesImpactsTable.TABLE_NAME, AddManualSeverityColumnInIssuesImpactsTable.MANUAL_SEVERITY, java.sql.Types.BOOLEAN, null, false);
+ }
+
+ @Test
+ void execute_whenColumnAlreadyExists_shouldNotFail() throws Exception {
+ underTest.execute();
+ assertDoesNotThrow(() -> underTest.execute());
+ }
+
+ @Test
+ void execute_is_reentrant() throws Exception {
+ underTest.execute();
+ underTest.execute();
+ db.assertColumnDefinition(AddManualSeverityColumnInIssuesImpactsTable.TABLE_NAME, AddManualSeverityColumnInIssuesImpactsTable.MANUAL_SEVERITY, java.sql.Types.BOOLEAN, null, false);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.server.platform.db.migration.version.v108;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.def.BooleanColumnDef;
+import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.db.DatabaseUtils.tableColumnExists;
+
+public class AddManualSeverityColumnInIssuesImpactsTable extends DdlChange {
+
+ public static final String TABLE_NAME = "issues_impacts";
+ public static final String MANUAL_SEVERITY = "manual_severity";
+
+
+ public AddManualSeverityColumnInIssuesImpactsTable(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ try (var connection = getDatabase().getDataSource().getConnection()) {
+ if (!tableColumnExists(connection, TABLE_NAME, MANUAL_SEVERITY)) {
+ var manualSeverity = BooleanColumnDef.newBooleanColumnDefBuilder()
+ .setColumnName(MANUAL_SEVERITY)
+ .setIsNullable(false)
+ .setDefaultValue(false)
+ .build();
+
+ context.execute(new AddColumnsBuilder(getDialect(), TABLE_NAME)
+ .addColumn(manualSeverity)
+ .build());
+ }
+ }
+ }
+}
.add(10_8_018, "Make columns 'published_at' and 'last_modified_at' nullable on the 'cves' table", AlterCveColumnsToNullable.class)
.add(10_8_019, "Delete Software Quality ratings from project_measures", DeleteSoftwareQualityRatingFromProjectMeasures.class)
.add(10_8_020, "Create new software quality metrics", CreateNewSoftwareQualityMetrics.class)
- .add(10_8_021, "Migrate deprecated project_measures to replacement metrics", MigrateProjectMeasuresDeprecatedMetrics.class);
+ .add(10_8_021, "Migrate deprecated project_measures to replacement metrics", MigrateProjectMeasuresDeprecatedMetrics.class)
+ .add(10_8_022, "Add 'manual_severity' column in 'issues_impacts' table", AddManualSeverityColumnInIssuesImpactsTable.class);
}
}