import javax.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.sonar.api.issue.Issue;
+import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.server.issue.workflow.IssueWorkflow;
import static java.util.Objects.requireNonNull;
+import static java.util.Optional.ofNullable;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByScanBuilder;
/**
issue.setEffort(debtCalculator.calculate(issue));
setType(issue, rule);
setStatus(issue, rule);
+ setCleanCodeAttribute(issue, rule);
+ }
+
+ private static void setCleanCodeAttribute(DefaultIssue issue, Rule rule) {
+ issue.setCleanCodeAttribute(ofNullable(rule.cleanCodeAttribute()).orElse(CleanCodeAttribute.defaultCleanCodeAttribute()));
}
private static void setType(DefaultIssue issue, Rule rule) {
to.setManualSeverity(true);
to.setSeverity(from.severity());
}
+ to.setCleanCodeAttribute(from.getCleanCodeAttribute());
copyChangesOfIssueFromOtherBranch(to, from);
}
raw.setChanged(true);
}
setType(raw, rule);
+ setCleanCodeAttribute(raw, rule);
copyFields(raw, base);
base.changes().forEach(raw::addChange);
updater.setPastEffort(raw, base.effort(), changeContext);
updater.setCodeVariants(raw, requireNonNull(base.codeVariants()), changeContext);
updater.setImpacts(raw, base.impacts(), changeContext);
+ updater.setCleanCodeAttribute(raw, base.getCleanCodeAttribute(), changeContext);
}
public void doAutomaticTransition(DefaultIssue issue) {
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.System2;
defaultIssue.setSelectedAt(next.hasSelectedAt() ? next.getSelectedAt() : null);
defaultIssue.setQuickFixAvailable(next.getQuickFixAvailable());
defaultIssue.setIsNoLongerNewCodeReferenceIssue(next.getIsNoLongerNewCodeReferenceIssue());
+ defaultIssue.setCleanCodeAttribute(next.hasCleanCodeAttribute() ? CleanCodeAttribute.valueOf(next.getCleanCodeAttribute()) : null);
if (next.hasAnticipatedTransitionUuid()) {
defaultIssue.setAnticipatedTransitionUuid(next.getAnticipatedTransitionUuid());
}
builder.clear();
builder.setKey(defaultIssue.key());
builder.setRuleType(defaultIssue.type().getDbConstant());
+ ofNullable(defaultIssue.getCleanCodeAttribute()).ifPresent(value -> builder.setCleanCodeAttribute(value.name()));
ofNullable(defaultIssue.componentUuid()).ifPresent(builder::setComponentUuid);
builder.setComponentKey(defaultIssue.componentKey());
builder.setProjectUuid(defaultIssue.projectUuid());
optional string assigneeLogin = 47;
optional string anticipatedTransitionUuid = 48;
repeated Impact impacts = 49;
+ optional string cleanCodeAttribute = 50;
}
message Comment {
import org.junit.Test;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
+import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.Duration;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
assertThat(issue.effort()).isEqualTo(DEFAULT_DURATION);
assertThat(issue.isNew()).isTrue();
assertThat(issue.isCopied()).isFalse();
+ assertThat(issue.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CONVENTIONAL);
}
@Test
.setIsNewCodeReferenceIssue(true);
fromShort.setResolution("resolution");
fromShort.setStatus("status");
+ fromShort.setCleanCodeAttribute(CleanCodeAttribute.COMPLETE);
Date commentDate = new Date();
fromShort.addComment(new DefaultIssueComment()
assertThat(raw.resolution()).isEqualTo("resolution");
assertThat(raw.status()).isEqualTo("status");
+ assertThat(raw.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.COMPLETE);
assertThat(raw.defaultIssueComments())
.extracting(DefaultIssueComment::issueKey, DefaultIssueComment::createdAt, DefaultIssueComment::userUuid, DefaultIssueComment::markdownText)
.containsOnly(tuple("raw", commentDate, "user_uuid", "A comment"));
.setKey("short");
fromShort.setResolution("resolution");
fromShort.setStatus("status");
+ fromShort.setCleanCodeAttribute(CleanCodeAttribute.DISTINCT);
Date commentDate = new Date();
fromShort.addComment(new DefaultIssueComment()
assertThat(raw.resolution()).isEqualTo("resolution");
assertThat(raw.status()).isEqualTo("status");
+ assertThat(raw.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.DISTINCT);
assertThat(raw.defaultIssueComments())
.extracting(DefaultIssueComment::issueKey, DefaultIssueComment::createdAt, DefaultIssueComment::userUuid, DefaultIssueComment::markdownText)
.containsOnly(tuple("raw", commentDate, "user_uuid", "A comment"));
.setCreationDate(parseDate("2015-01-01"))
.setUpdateDate(parseDate("2015-01-02"))
.setCloseDate(parseDate("2015-01-03"))
+ .setCleanCodeAttribute(CleanCodeAttribute.FOCUSED)
.setResolution(RESOLUTION_FIXED)
.setStatus(STATUS_CLOSED)
.setSeverity(BLOCKER)
assertThat(raw.isNew()).isFalse();
assertThat(raw.isCopied()).isTrue();
+ assertThat(raw.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.FOCUSED);
assertThat(raw.key()).isNotNull();
assertThat(raw.key()).isNotEqualTo(base.key());
assertThat(raw.creationDate()).isEqualTo(base.creationDate());
.setKey("RAW_KEY")
.setRuleKey(XOO_X1)
.setRuleDescriptionContextKey("spring")
+ .setCleanCodeAttribute(CleanCodeAttribute.IDENTIFIABLE)
.setCodeVariants(Set.of("foo", "bar"))
.addImpact(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)
.setCreationDate(parseDate("2015-10-01"))
.setKey("BASE_KEY")
.setCreationDate(parseDate("2015-01-01"))
.setUpdateDate(parseDate("2015-01-02"))
+ .setCleanCodeAttribute(CleanCodeAttribute.FOCUSED)
.setResolution(RESOLUTION_FALSE_POSITIVE)
.setStatus(STATUS_RESOLVED)
.setSeverity(BLOCKER)
verify(updater).setPastMessage(raw, "message with code", messageFormattings, issueChangeContext);
verify(updater).setPastEffort(raw, Duration.create(15L), issueChangeContext);
verify(updater).setPastLocations(raw, issueLocations);
+ verify(updater).setCleanCodeAttribute(raw, CleanCodeAttribute.FOCUSED, issueChangeContext);
}
@Test
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.core.issue.DefaultIssue;
assertThat(defaultIssue.impacts()).containsExactlyInAnyOrderEntriesOf(Map.of(SoftwareQuality.MAINTAINABILITY, Severity.HIGH, SoftwareQuality.RELIABILITY, Severity.LOW));
}
+ @Test
+ public void toDefaultIssue_whenCleanCodeAttributeIsSet_shouldSetItInDefaultIssue() {
+ IssueCache.Issue issue = prepareIssueWithCompulsoryFields()
+ .setCleanCodeAttribute(CleanCodeAttribute.FOCUSED.name())
+ .build();
+
+ DefaultIssue defaultIssue = ProtobufIssueDiskCache.toDefaultIssue(issue);
+
+ assertThat(defaultIssue.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.FOCUSED);
+ }
+
@Test
public void toProto_whenRuleDescriptionContextKeySet_shouldCopyToIssueProto() {
DefaultIssue defaultIssue = createDefaultIssueWithMandatoryFields();
);
}
+ @Test
+ public void toProto_whenCleanCodeAttributeIsSet_shouldCopyToIssueProto() {
+ DefaultIssue defaultIssue = createDefaultIssueWithMandatoryFields();
+ defaultIssue.setCleanCodeAttribute(CleanCodeAttribute.FOCUSED);
+
+ IssueCache.Issue issue = ProtobufIssueDiskCache.toProto(IssueCache.Issue.newBuilder(), defaultIssue);
+
+ assertThat(issue.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.FOCUSED.name());
+ }
+
private IssueCache.Impact toImpact(SoftwareQuality softwareQuality, Severity severity) {
return IssueCache.Impact.newBuilder().setSoftwareQuality(softwareQuality.name()).setSeverity(severity.name()).build();
}
.setManualSeverity(false)
.setMessage("the message")
.setRuleDescriptionContextKey(TEST_CONTEXT_KEY)
+ .setRuleCleanCodeAttribute(RULE.getCleanCodeAttribute())
.setLine(500)
.setEffort(10L)
.setGap(3.14)
assertThat(issue.getIssueCloseDate()).isNotNull();
assertThat(issue.getRuleRepo()).isEqualTo(RULE.getRepositoryKey());
assertThat(issue.getRule()).isEqualTo(RULE.getRuleKey());
- assertThat(issue.getCleanCodeAttribute()).isEqualTo(RULE.getCleanCodeAttribute());
+ assertThat(issue.getEffectiveCleanCodeAttribute()).isEqualTo(RULE.getCleanCodeAttribute());
assertThat(issue.parseLocations()).isNull();
assertThat(issue.getImpacts())
.extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality)
private Integer line = null;
private String resolution = null;
private String cleanCodeAttribute = null;
+ private String ruleCleanCodeAttribute = null;
private String severity = null;
private String status = null;
private Long effort = null;
}
public String getCleanCodeAttribute() {
- return cleanCodeAttribute;
+ if (cleanCodeAttribute != null) {
+ return cleanCodeAttribute;
+ }
+ return ruleCleanCodeAttribute;
}
public IndexedIssueDto setCleanCodeAttribute(String cleanCodeAttribute) {
this.cleanCodeAttribute = cleanCodeAttribute;
return this;
}
+
+ public String getRuleCleanCodeAttribute() {
+ return ruleCleanCodeAttribute;
+ }
+
+ public IndexedIssueDto setRuleCleanCodeAttribute(String ruleCleanCodeAttribute) {
+ this.ruleCleanCodeAttribute = ruleCleanCodeAttribute;
+ return this;
+ }
}
//non-persisted fields
private Set<ImpactDto> ruleDefaultImpacts = new HashSet<>();
private CleanCodeAttribute cleanCodeAttribute;
+ private CleanCodeAttribute ruleCleanCodeAttribute;
public IssueDto() {
// nothing to do
.setIsNewCodeReferenceIssue(issue.isNewCodeReferenceIssue())
.setCodeVariants(issue.codeVariants())
.replaceAllImpacts(mapToImpactDto(issue.impacts()))
+ .setCleanCodeAttribute(issue.getCleanCodeAttribute())
// technical dates
.setCreatedAt(now)
.setUpdatedAt(now);
.setIsNewCodeReferenceIssue(issue.isNewCodeReferenceIssue())
.setCodeVariants(issue.codeVariants())
.replaceAllImpacts(mapToImpactDto(issue.impacts()))
+ .setCleanCodeAttribute(issue.getCleanCodeAttribute())
// technical date
.setUpdatedAt(now);
}
}
@CheckForNull
- public CleanCodeAttribute getCleanCodeAttribute() {
- return cleanCodeAttribute;
+ public CleanCodeAttribute getEffectiveCleanCodeAttribute() {
+ if (cleanCodeAttribute != null) {
+ return cleanCodeAttribute;
+ }
+ return ruleCleanCodeAttribute;
}
public IssueDto setCleanCodeAttribute(CleanCodeAttribute cleanCodeAttribute) {
return this;
}
+ public IssueDto setRuleCleanCodeAttribute(CleanCodeAttribute ruleCleanCodeAttribute) {
+ this.ruleCleanCodeAttribute = ruleCleanCodeAttribute;
+ return this;
+ }
+
public Optional<String> getClosedChangeData() {
return Optional.ofNullable(closedChangeData);
}
issue.setQuickFixAvailable(quickFixAvailable);
issue.setIsNewCodeReferenceIssue(isNewCodeReferenceIssue);
issue.setCodeVariants(getCodeVariants());
+ issue.setCleanCodeAttribute(cleanCodeAttribute);
impacts.forEach(i -> issue.addImpact(i.getSoftwareQuality(), i.getSeverity()));
return issue;
}
r.plugin_name as ruleRepo,
r.language as language,
r.security_standards as securityStandards,
- r.clean_code_attribute as cleanCodeAttribute,
+ i.clean_code_attribute as cleanCodeAttribute,
+ r.clean_code_attribute as ruleCleanCodeAttribute,
p.kee as componentKey,
i.component_uuid as componentUuid,
p.path as filePath,
i.project_uuid,
i.issue_type,
i.quick_fix_available,
- i.code_variants
+ i.code_variants,
+ i.clean_code_attribute
</sql>
<sql id="isNewCodeReferenceIssue" databaseId="mssql">
INSERT INTO issues (kee, rule_uuid, severity, manual_severity,
message, message_formattings, line, locations, gap, effort, status, tags, rule_description_context_key,
resolution, checksum, assignee, author_login, issue_creation_date, issue_update_date,
- issue_close_date, created_at, updated_at, component_uuid, project_uuid, issue_type, quick_fix_available, code_variants)
+ issue_close_date, created_at, updated_at, component_uuid, project_uuid, issue_type, quick_fix_available, code_variants,
+ clean_code_attribute)
VALUES (
#{kee,jdbcType=VARCHAR},
#{ruleUuid,jdbcType=VARCHAR},
#{createdAt,jdbcType=BIGINT}, #{updatedAt,jdbcType=BIGINT},
#{componentUuid,jdbcType=VARCHAR}, #{projectUuid,jdbcType=VARCHAR}, #{type,jdbcType=INTEGER},
#{quickFixAvailable, jdbcType=BOOLEAN},
- #{codeVariantsString,jdbcType=VARCHAR})
+ #{codeVariantsString,jdbcType=VARCHAR},
+ #{effectiveCleanCodeAttribute,jdbcType=VARCHAR})
</insert>
<insert id="insertAsNewCodeOnReferenceBranch" parameterType="NewCodeReferenceIssue" useGeneratedKeys="false">
issue_close_date=#{issueCloseTime,jdbcType=BIGINT},
updated_at=#{updatedAt,jdbcType=BIGINT},
issue_type=#{type,jdbcType=INTEGER},
- code_variants=#{codeVariantsString,jdbcType=VARCHAR}
+ code_variants=#{codeVariantsString,jdbcType=VARCHAR},
+ clean_code_attribute=#{effectiveCleanCodeAttribute,jdbcType=VARCHAR}
where kee = #{kee}
</update>
issue_close_date=#{issueCloseTime,jdbcType=BIGINT},
updated_at=#{updatedAt,jdbcType=BIGINT},
issue_type=#{type,jdbcType=INTEGER},
- code_variants=#{codeVariantsString,jdbcType=VARCHAR}
+ code_variants=#{codeVariantsString,jdbcType=VARCHAR},
+ clean_code_attribute=#{effectiveCleanCodeAttribute,jdbcType=VARCHAR}
where kee = #{kee} and updated_at <= #{selectedAt}
</update>
i.issue_update_date as issueUpdateDate,
r.uuid as ruleUuid,
r.language as language,
- r.clean_code_attribute as cleanCodeAttribute,
+ i.clean_code_attribute as cleanCodeAttribute,
+ r.clean_code_attribute as ruleCleanCodeAttribute,
c.uuid as componentUuid,
c.path,
c.scope,
r.rule_type as ruleType,
r.plugin_name as ruleRepo,
r.plugin_rule_key as ruleKey,
- r.clean_code_attribute as cleanCodeAttribute,
i.message as message,
i.message_formattings as messageFormattings,
i.severity as severity,
i.rule_description_context_key as ruleDescriptionContextKey,
<include refid="issueImpactsColumns"/>
<include refid="ruleDefaultImpactsColumns"/>
- r.clean_code_attribute as cleanCodeAttribute
+ i.clean_code_attribute as cleanCodeAttribute,
+ r.clean_code_attribute as ruleCleanCodeAttribute
</sql>
<select id="selectByBranch" parameterType="map" resultMap="issueResultMap" resultOrdered="true">
"QUICK_FIX_AVAILABLE" BOOLEAN,
"RULE_DESCRIPTION_CONTEXT_KEY" CHARACTER VARYING(50),
"MESSAGE_FORMATTINGS" BINARY LARGE OBJECT,
- "CODE_VARIANTS" CHARACTER VARYING(4000)
+ "CODE_VARIANTS" CHARACTER VARYING(4000),
+ "CLEAN_CODE_ATTRIBUTE" CHARACTER VARYING(40)
);
ALTER TABLE "ISSUES" ADD CONSTRAINT "PK_ISSUES" PRIMARY KEY("KEE");
CREATE INDEX "ISSUES_ASSIGNEE" ON "ISSUES"("ASSIGNEE" NULLS FIRST);
.setStatus("status")
.setNewCodeReferenceIssue(true)
.setCleanCodeAttribute("cleanCodeAttribute")
+ .setRuleCleanCodeAttribute("ruleCleanCodeAttribute")
.setCodeVariants("codeVariants")
.setSecurityStandards("securityStandards")
.setComponentUuid("componentUuid")
assertThat(indexedIssueDto)
.extracting(IndexedIssueDto::getIssueKey, IndexedIssueDto::getAssignee, IndexedIssueDto::getAuthorLogin, IndexedIssueDto::getStatus,
- IndexedIssueDto::isNewCodeReferenceIssue, IndexedIssueDto::getCleanCodeAttribute, IndexedIssueDto::getCodeVariants,
+ IndexedIssueDto::isNewCodeReferenceIssue, IndexedIssueDto::getCleanCodeAttribute, IndexedIssueDto::getRuleCleanCodeAttribute, 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",
+ .containsExactly("issueKey", "assignee", "authorLogin", "status", true, "cleanCodeAttribute", "ruleCleanCodeAttribute", "codeVariants", "securityStandards",
"componentUuid", 1L, 2L, 3L, 4L, true, "language", 5, "path", "projectUuid", "qualifier", "resolution", "ruleUuid",
"scope", "severity", "tags", 6, "branchUuid");
assertThat(dto.getRuleRepo()).isEqualTo("java");
assertThat(dto.getRule()).isEqualTo("AvoidCycle");
assertThat(dto.getRuleKey()).hasToString("java:AvoidCycle");
- assertThat(dto.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR);
+ assertThat(dto.getEffectiveCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR);
assertThat(dto.getLanguage()).isEqualTo("xoo");
assertThat(dto.isExternal()).isTrue();
}
--- /dev/null
+/*
+ * 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.server.platform.db.migration.version.v103;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.DatabaseUtils;
+import org.sonar.server.platform.db.migration.def.ColumnDef;
+import org.sonar.server.platform.db.migration.def.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class AddCleanCodeAttributeColumnInIssuesTable extends DdlChange {
+ private static final String TABLE_NAME = "issues";
+ private static final String COLUMN_NAME = "clean_code_attribute";
+ private static final int NEW_COLUMN_SIZE = 40;
+
+
+ public AddCleanCodeAttributeColumnInIssuesTable(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ try (Connection connection = getDatabase().getDataSource().getConnection()) {
+ if (!DatabaseUtils.tableColumnExists(connection, TABLE_NAME, COLUMN_NAME)) {
+ ColumnDef columnDef = VarcharColumnDef.newVarcharColumnDefBuilder()
+ .setColumnName(COLUMN_NAME)
+ .setLimit(NEW_COLUMN_SIZE)
+ .setIsNullable(true)
+ .build();
+ context.execute(new AddColumnsBuilder(getDialect(), TABLE_NAME).addColumn(columnDef).build());
+ }
+ }
+ }
+}
.add(10_3_001, "Add table 'github_perms_mapping'", CreateGithubPermissionsMappingTable.class)
.add(10_3_002, "Create unique index on 'github_perms_mapping'", CreateUniqueIndexForGithubPermissionsMappingTable.class)
.add(10_3_003, "Add default mappings to 'github_perms_mapping'", PopulateGithubPermissionsMapping.class)
+ .add(10_3_004, "Add 'clean_code_attribute' column in 'issues' table", AddCleanCodeAttributeColumnInIssuesTable.class);
;
}
}
--- /dev/null
+/*
+ * 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.server.platform.db.migration.version.v103;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+public class AddCleanCodeAttributeColumnInIssuesTableTest {
+ private static final String TABLE_NAME = "issues";
+ private static final String COLUMN_NAME = "clean_code_attribute";
+
+ @Rule
+ public final CoreDbTester db = CoreDbTester.createForSchema(AddCleanCodeAttributeColumnInIssuesTableTest.class, "schema.sql");
+
+ private final AddCleanCodeAttributeColumnInIssuesTable underTest = new AddCleanCodeAttributeColumnInIssuesTable(db.database());
+
+ @Test
+ public void execute_whenColumnDoesNotExist_shouldCreateColumn() throws SQLException {
+ db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME);
+ underTest.execute();
+ db.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, Types.VARCHAR, 40, true);
+ }
+
+ @Test
+ public void execute_whenColumnsAlreadyExists_shouldNotFail() throws SQLException {
+ underTest.execute();
+ assertThatCode(underTest::execute).doesNotThrowAnyException();
+ }
+}
--- /dev/null
+CREATE TABLE "ISSUES"(
+ "KEE" CHARACTER VARYING(50) NOT NULL,
+ "RULE_UUID" CHARACTER VARYING(40),
+ "SEVERITY" CHARACTER VARYING(10),
+ "MANUAL_SEVERITY" BOOLEAN NOT NULL,
+ "MESSAGE" CHARACTER VARYING(4000),
+ "LINE" INTEGER,
+ "GAP" DOUBLE PRECISION,
+ "STATUS" CHARACTER VARYING(20),
+ "RESOLUTION" CHARACTER VARYING(20),
+ "CHECKSUM" CHARACTER VARYING(1000),
+ "ASSIGNEE" CHARACTER VARYING(255),
+ "AUTHOR_LOGIN" CHARACTER VARYING(255),
+ "EFFORT" INTEGER,
+ "CREATED_AT" BIGINT,
+ "UPDATED_AT" BIGINT,
+ "ISSUE_CREATION_DATE" BIGINT,
+ "ISSUE_UPDATE_DATE" BIGINT,
+ "ISSUE_CLOSE_DATE" BIGINT,
+ "TAGS" CHARACTER VARYING(4000),
+ "COMPONENT_UUID" CHARACTER VARYING(50),
+ "PROJECT_UUID" CHARACTER VARYING(50),
+ "LOCATIONS" BINARY LARGE OBJECT,
+ "ISSUE_TYPE" TINYINT,
+ "FROM_HOTSPOT" BOOLEAN,
+ "QUICK_FIX_AVAILABLE" BOOLEAN,
+ "RULE_DESCRIPTION_CONTEXT_KEY" CHARACTER VARYING(50),
+ "MESSAGE_FORMATTINGS" BINARY LARGE OBJECT,
+ "CODE_VARIANTS" CHARACTER VARYING(4000)
+);
+ALTER TABLE "ISSUES" ADD CONSTRAINT "PK_ISSUES" PRIMARY KEY("KEE");
+CREATE INDEX "ISSUES_ASSIGNEE" ON "ISSUES"("ASSIGNEE" NULLS FIRST);
+CREATE INDEX "ISSUES_COMPONENT_UUID" ON "ISSUES"("COMPONENT_UUID" NULLS FIRST);
+CREATE INDEX "ISSUES_CREATION_DATE" ON "ISSUES"("ISSUE_CREATION_DATE" NULLS FIRST);
+CREATE INDEX "ISSUES_PROJECT_UUID" ON "ISSUES"("PROJECT_UUID" NULLS FIRST);
+CREATE INDEX "ISSUES_RESOLUTION" ON "ISSUES"("RESOLUTION" NULLS FIRST);
+CREATE INDEX "ISSUES_UPDATED_AT" ON "ISSUES"("UPDATED_AT" NULLS FIRST);
+CREATE INDEX "ISSUES_RULE_UUID" ON "ISSUES"("RULE_UUID" NULLS FIRST);
import org.sonar.api.ce.ComputeEngineSide;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
+import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ServerSide;
import org.sonar.api.server.rule.RuleTagFormat;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;
+import static java.util.Objects.requireNonNull;
/**
* Updates issue fields and chooses if changes must be kept in history.
public static final String UNUSED = "";
public static final String SEVERITY = "severity";
public static final String TYPE = "type";
+ public static final String CLEAN_CODE_ATTRIBUTE = "cleanCodeAttribute";
public static final String ASSIGNEE = "assignee";
public static final String RESOLUTION = "resolution";
public static final String STATUS = "status";
for (int i = 0; i < l1c.getMessageFormattingCount(); i++) {
if (l1c.getMessageFormatting(i).getStart() != l2.getMessageFormatting(i).getStart()
- || l1c.getMessageFormatting(i).getEnd() != l2.getMessageFormatting(i).getEnd()
- || l1c.getMessageFormatting(i).getType() != l2.getMessageFormatting(i).getType()) {
+ || l1c.getMessageFormatting(i).getEnd() != l2.getMessageFormatting(i).getEnd()
+ || l1c.getMessageFormatting(i).getType() != l2.getMessageFormatting(i).getType()) {
return false;
}
}
return false;
}
+ public boolean setCleanCodeAttribute(DefaultIssue raw, @Nullable CleanCodeAttribute previousCleanCodeAttribute, IssueChangeContext changeContext) {
+ CleanCodeAttribute newCleanCodeAttribute = requireNonNull(raw.getCleanCodeAttribute());
+ if (Objects.equals(previousCleanCodeAttribute, newCleanCodeAttribute)) {
+ return false;
+ }
+ raw.setFieldChange(changeContext, CLEAN_CODE_ATTRIBUTE, previousCleanCodeAttribute, newCleanCodeAttribute.name());
+ raw.setCleanCodeAttribute(newCleanCodeAttribute);
+ raw.setUpdateDate(changeContext.date());
+ raw.setChanged(true);
+ return true;
+
+ }
+
private static Set<String> getNewCodeVariants(DefaultIssue issue) {
Set<String> issueCodeVariants = issue.codeVariants();
if (issueCodeVariants == null) {
doc.setSeverity(indexedIssueDto.getSeverity());
String cleanCodeAttributeCategory = Optional.ofNullable(indexedIssueDto.getCleanCodeAttribute())
.map(CleanCodeAttribute::valueOf)
- .map(cleanCodeAttribute -> cleanCodeAttribute.getAttributeCategory().name())
+ .map(CleanCodeAttribute::getAttributeCategory)
+ .map(Enum::name)
.orElse(null);
- //TODO SONAR-20073 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());
import org.junit.Test;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
+import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.Duration;
import org.sonar.core.issue.DefaultIssue;
assertThat(updated).isTrue();
assertThat(issue.getRuleDescriptionContextKey()).contains(DEFAULT_RULE_DESCRIPTION_CONTEXT_KEY);
}
+
+ @Test
+ public void setCleanCodeAttribute_whenCleanCodeAttributeChanged_shouldUpdateIssue() {
+ issue.setCleanCodeAttribute(CleanCodeAttribute.CLEAR);
+ boolean updated = underTest.setCleanCodeAttribute(issue, CleanCodeAttribute.COMPLETE, context);
+
+ assertThat(updated).isTrue();
+ assertThat(issue.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR);
+ assertThat(issue.currentChange().get("cleanCodeAttribute"))
+ .extracting(FieldDiffs.Diff::oldValue, FieldDiffs.Diff::newValue)
+ .containsExactly(CleanCodeAttribute.COMPLETE, CleanCodeAttribute.CLEAR.name());
+ }
+
+ @Test
+ public void setCleanCodeAttribute_whenCleanCodeAttributeNotChanged_shouldNotUpdateIssue() {
+ issue.setCleanCodeAttribute(CleanCodeAttribute.CLEAR);
+ boolean updated = underTest.setCleanCodeAttribute(issue, CleanCodeAttribute.CLEAR, context);
+
+ assertThat(updated).isFalse();
+ assertThat(issue.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR);
+ }
}
assertThat(createdIssues).hasSize(1);
- assertThat(createdIssues.iterator().next().getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR);
+ assertThat(createdIssues.iterator().next().getEffectiveCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR);
assertThat(createdIssues.iterator().next().getEffectiveImpacts()).isEqualTo(Map.of(SoftwareQuality.MAINTAINABILITY, Severity.HIGH));
assertThat(db.countRowsOfTable("issues")).isOne();
Collection<IssueDto> updatedIssues = underTest.save(db.getSession(), singletonList(issue));
assertThat(updatedIssues).hasSize(1);
- assertThat(updatedIssues.iterator().next().getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR);
+ assertThat(updatedIssues.iterator().next().getEffectiveCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR);
assertThat(updatedIssues.iterator().next().getEffectiveImpacts()).isEqualTo(Map.of(SoftwareQuality.MAINTAINABILITY, Severity.HIGH));
assertThat(db.countRowsOfTable("issues")).isOne();
assertThat(taintLite.getType()).isEqualTo(Common.RuleType.forNumber(issueDto.getType()));
assertThat(taintLite.getAssignedToSubscribedUser()).isTrue();
assertThat(taintLite.getCleanCodeAttribute())
- .isEqualTo(Common.CleanCodeAttribute.valueOf(issueDto.getCleanCodeAttribute().name()));
+ .isEqualTo(Common.CleanCodeAttribute.valueOf(issueDto.getEffectiveCleanCodeAttribute().name()));
assertThat(taintLite.getCleanCodeAttributeCategory())
- .isEqualTo(Common.CleanCodeAttributeCategory.valueOf(issueDto.getCleanCodeAttribute().getAttributeCategory().name()));
+ .isEqualTo(Common.CleanCodeAttributeCategory.valueOf(issueDto.getEffectiveCleanCodeAttribute().getAttributeCategory().name()));
assertThat(taintLite.getImpactsList())
.extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
issueBuilder.setKey(dto.getKey());
issueBuilder.setType(Common.RuleType.forNumber(dto.getType()));
- CleanCodeAttribute cleanCodeAttribute = dto.getCleanCodeAttribute();
+ CleanCodeAttribute cleanCodeAttribute = dto.getEffectiveCleanCodeAttribute();
if (cleanCodeAttribute != null) {
issueBuilder.setCleanCodeAttribute(Common.CleanCodeAttribute.valueOf(cleanCodeAttribute.name()));
issueBuilder.setCleanCodeAttributeCategory(Common.CleanCodeAttributeCategory.valueOf(cleanCodeAttribute.getAttributeCategory().name()));
taintBuilder.setSeverity(Common.Severity.valueOf(issueDto.getSeverity()));
}
taintBuilder.setType(Common.RuleType.forNumber(issueDto.getType()));
- CleanCodeAttribute cleanCodeAttribute = issueDto.getCleanCodeAttribute();
+ CleanCodeAttribute cleanCodeAttribute = issueDto.getEffectiveCleanCodeAttribute();
String cleanCodeAttributeString = cleanCodeAttribute != null ? cleanCodeAttribute.name() : null;
String cleanCodeAttributeCategoryString = cleanCodeAttribute != null ? cleanCodeAttribute.getAttributeCategory().name() : null;
if (cleanCodeAttributeString != null) {
private void assertIssueEqualsIssueDto(Issue issue, IssueDto issueDto) {
assertThat(issue.getKey()).isEqualTo(issueDto.getKey());
- assertThat(issue.getCleanCodeAttribute()).isEqualTo(Common.CleanCodeAttribute.valueOf(issueDto.getCleanCodeAttribute().name()));
- assertThat(issue.getCleanCodeAttributeCategory()).isEqualTo(Common.CleanCodeAttributeCategory.valueOf(issueDto.getCleanCodeAttribute().getAttributeCategory().name()));
+ assertThat(issue.getCleanCodeAttribute()).isEqualTo(Common.CleanCodeAttribute.valueOf(issueDto.getEffectiveCleanCodeAttribute().name()));
+ assertThat(issue.getCleanCodeAttributeCategory()).isEqualTo(Common.CleanCodeAttributeCategory.valueOf(issueDto.getEffectiveCleanCodeAttribute().getAttributeCategory().name()));
assertThat(issue.getType().getNumber()).isEqualTo(issueDto.getType());
assertThat(issue.getComponent()).isEqualTo(issueDto.getComponentKey());
assertThat(issue.getRule()).isEqualTo(issueDto.getRuleKey().toString());
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.Duration;
import org.sonar.core.issue.tracking.Trackable;
// all changes
// -- contains only current change (if any) on CE side unless reopening a closed issue or copying issue from base branch
- // when analyzing a branch from the first time
+ // when analyzing a branch from the first time
private List<FieldDiffs> changes = null;
// true if the issue did not exist in the previous scan.
private String anticipatedTransitionUuid = null;
private Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> impacts = new EnumMap<>(SoftwareQuality.class);
+ private CleanCodeAttribute cleanCodeAttribute = null;
@Override
public String key() {
return this;
}
-
public DefaultIssue setNew(boolean b) {
isNew = b;
return this;
return this;
}
+ public DefaultIssue setCleanCodeAttribute(@Nullable CleanCodeAttribute cleanCodeAttribute) {
+ this.cleanCodeAttribute = cleanCodeAttribute;
+ return this;
+ }
+
+ @Nullable
+ public CleanCodeAttribute getCleanCodeAttribute() {
+ return cleanCodeAttribute;
+ }
+
@Override
public Integer getLine() {
return line;
public Date getUpdateDate() {
return updateDate;
}
-
}