@Override
public IssueHandler.Context setAuthorLogin(@Nullable String login) {
- updater.setAuthorLogin(issue, login);
+ updater.setAuthorLogin(issue, login, changeContext);
return this;
}
public class IssueTracking implements BatchExtension {
- private static final Comparator<LinePair> LINE_PAIR_COMPARATOR = new Comparator<LinePair>() {
- public int compare(LinePair o1, LinePair o2) {
- int weightDiff = o2.weight - o1.weight;
- if (weightDiff != 0) {
- return weightDiff;
- } else {
- return Math.abs(o1.lineA - o1.lineB) - Math.abs(o2.lineA - o2.lineB);
- }
- }
- };
private final LastSnapshots lastSnapshots;
private final SonarIndex index;
this.index = index;
}
- /**
- * @return untracked issues
- */
public IssueTrackingResult track(Resource resource, Collection<IssueDto> dbIssues, Collection<DefaultIssue> newIssues) {
IssueTrackingResult result = new IssueTrackingResult();
return getClass().getSimpleName();
}
- static class LinePair {
+ private static class LinePair {
int lineA;
int lineB;
int weight;
}
}
- static class HashOccurrence {
+ private static class HashOccurrence {
int lineA;
int lineB;
int countA;
int countB;
}
+ private static final Comparator<LinePair> LINE_PAIR_COMPARATOR = new Comparator<LinePair>() {
+ public int compare(LinePair o1, LinePair o2) {
+ int weightDiff = o2.weight - o1.weight;
+ if (weightDiff != 0) {
+ return weightDiff;
+ } else {
+ return Math.abs(o1.lineA - o1.lineB) - Math.abs(o2.lineA - o2.lineB);
+ }
+ }
+ };
+
}
package org.sonar.plugins.core.issue;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import org.sonar.api.batch.Decorator;
import org.sonar.batch.issue.IssueCache;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
import org.sonar.core.issue.db.IssueDto;
import org.sonar.core.issue.workflow.IssueWorkflow;
private final IssueFilters filters;
private final IssueHandlers handlers;
private final IssueWorkflow workflow;
+ private final IssueUpdater updater;
private final IssueChangeContext changeContext;
private final ResourcePerspectives perspectives;
private final RuleFinder ruleFinder;
public IssueTrackingDecorator(IssueCache issueCache, InitialOpenIssuesStack initialOpenIssues, IssueTracking tracking,
IssueFilters filters, IssueHandlers handlers, IssueWorkflow workflow,
+ IssueUpdater updater,
Project project, ResourcePerspectives perspectives,
RuleFinder ruleFinder) {
this.issueCache = issueCache;
this.filters = filters;
this.handlers = handlers;
this.workflow = workflow;
+ this.updater = updater;
this.changeContext = IssueChangeContext.createScan(project.getAnalysisDate());
this.perspectives = perspectives;
this.ruleFinder = ruleFinder;
IssueDto ref = result.matching(issue);
issue.setKey(ref.getKee());
- if (ref.isManualSeverity()) {
- issue.setManualSeverity(true);
- issue.setSeverity(ref.getSeverity());
- } else if (!Objects.equal(ref.getSeverity(), issue.severity())) {
- // TODO register diff
- }
issue.setResolution(ref.getResolution());
issue.setStatus(ref.getStatus());
issue.setNew(false);
issue.setAuthorLogin(ref.getAuthorLogin());
issue.setAssignee(ref.getAssignee());
if (ref.getAttributes() != null) {
- //FIXME do not override new attributes
issue.setAttributes(KeyValueFormat.parse(ref.getAttributes()));
}
- issue.setTechnicalCreationDate(ref.getCreatedAt());
- issue.setTechnicalUpdateDate(ref.getUpdatedAt());
issue.setCreationDate(ref.getIssueCreationDate());
- // FIXME issue.setUpdateDate(project.getAnalysisDate());
+
+ // must be done before the change of severity
+ issue.setUpdateDate(ref.getIssueUpdateDate());
// should be null
issue.setCloseDate(ref.getIssueCloseDate());
+
+ if (ref.isManualSeverity()) {
+ issue.setManualSeverity(true);
+ issue.setSeverity(ref.getSeverity());
+ } else {
+ // Emulate change of severity in the current scan.
+ String severity = issue.severity();
+ issue.setSeverity(ref.getSeverity());
+ updater.setSeverity(issue, severity, changeContext);
+ }
}
}
Rule rule = ruleFinder.findByKey(unmatched.ruleKey());
boolean manualIssue = !Strings.isNullOrEmpty(unmatched.reporter());
- boolean onExistingRule = rule != null && !Rule.STATUS_REMOVED.equals(rule.getStatus());
+ boolean onExistingRule = (rule != null && !Rule.STATUS_REMOVED.equals(rule.getStatus()));
unmatched.setAlive(manualIssue && onExistingRule);
issues.add(unmatched);
import org.sonar.batch.issue.IssueCache;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
import org.sonar.core.issue.db.IssueDto;
import org.sonar.core.issue.workflow.IssueWorkflow;
import org.sonar.core.persistence.AbstractDaoTestCase;
IssueFilters filters = mock(IssueFilters.class);
IssueHandlers handlers = mock(IssueHandlers.class);
IssueWorkflow workflow = mock(IssueWorkflow.class);
+ IssueUpdater updater = mock(IssueUpdater.class);
ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
Date loadedDate = new Date();
RuleFinder ruleFinder = mock(RuleFinder.class);
issueCache,
initialOpenIssues,
tracking,
- filters, handlers, workflow,
+ filters, handlers,
+ workflow,
+ updater,
mock(Project.class),
perspectives,
ruleFinder);
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
-
import java.io.Serializable;
import java.util.Collections;
import java.util.Date;
private String reporter;
private String assignee;
private String checksum;
- private boolean isNew = true;
- private boolean isAlive = true;
private Map<String, String> attributes = null;
private String authorLogin = null;
private FieldDiffs diffs = null;
private Date updateDate;
private Date closeDate;
- // technical dates
- private Date technicalCreationDate;
- private Date technicalUpdateDate;
+ // The following states are used only during scan.
+
+ // true if the the issue did not exist in the previous scan.
+ private boolean isNew = true;
+
+ // true if the the issue did exist in the previous scan but not in the current one. That means
+ // that this issue should be closed.
+ private boolean isAlive = true;
+
+ // true if some fields have been changed since the previous scan
+ private boolean isChanged = false;
public String key() {
return key;
}
- /**
- * The date when issue was physically created
- */
- public DefaultIssue setTechnicalCreationDate(@Nullable Date d) {
- this.technicalCreationDate = d;
- return this;
- }
-
- @CheckForNull
- public Date technicalCreationDate() {
- return technicalCreationDate;
- }
-
- /**
- * The date when issue was physically updated for the last time
- */
-
- public DefaultIssue setTechnicalUpdateDate(@Nullable Date d) {
- this.technicalUpdateDate = d;
- return this;
- }
-
- @CheckForNull
- public Date technicalUpdateDate() {
- return technicalUpdateDate;
- }
-
@CheckForNull
public String getChecksum() {
return checksum;
return this;
}
+ public boolean isChanged() {
+ return isChanged;
+ }
+
+ public DefaultIssue setChanged(boolean b) {
+ isChanged = b;
+ return this;
+ }
+
@CheckForNull
public String attribute(String key) {
return attributes == null ? null : attributes.get(key);
Date now = new Date();
Date date = Objects.firstNonNull(createdDate, now);
- issue.setTechnicalCreationDate(now);
- issue.setTechnicalUpdateDate(now);
issue.setCreationDate(date);
issue.setUpdateDate(date);
issue.setComponentKey(componentKey);
issue.setManualSeverity(false);
issue.setReporter(reporter);
issue.setAttributes(attributes);
- issue.setNew(true);
- issue.setAlive(true);
issue.setResolution(null);
issue.setStatus(Issue.STATUS_OPEN);
return issue;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.BatchComponent;
import org.sonar.api.ServerComponent;
-import org.sonar.api.issue.IssueComment;
import javax.annotation.Nullable;
-
import java.util.Date;
/**
issue.setFieldDiff(context, "severity", issue.severity(), severity);
issue.setSeverity(severity);
issue.setUpdateDate(context.date());
+ issue.setChanged(true);
return true;
}
return false;
issue.setSeverity(severity);
issue.setManualSeverity(true);
issue.setUpdateDate(context.date());
+ issue.setChanged(true);
return true;
}
return false;
issue.setFieldDiff(context, "assignee", issue.assignee(), sanitizedAssignee);
issue.setAssignee(sanitizedAssignee);
issue.setUpdateDate(context.date());
+ issue.setChanged(true);
return true;
}
return false;
public boolean setLine(DefaultIssue issue, @Nullable Integer line) {
if (!Objects.equal(line, issue.line())) {
issue.setLine(line);
+ issue.setChanged(true);
return true;
}
return false;
issue.setFieldDiff(context, "resolution", issue.resolution(), resolution);
issue.setResolution(resolution);
issue.setUpdateDate(context.date());
+ issue.setChanged(true);
return true;
}
return false;
issue.setFieldDiff(context, "status", issue.status(), status);
issue.setStatus(status);
issue.setUpdateDate(context.date());
+ issue.setChanged(true);
return true;
}
return false;
}
- public void setAuthorLogin(DefaultIssue issue, @Nullable String authorLogin) {
- issue.setAuthorLogin(authorLogin);
+ public void setAuthorLogin(DefaultIssue issue, @Nullable String authorLogin, IssueChangeContext context) {
+ if (!Objects.equal(authorLogin, issue.authorLogin())) {
+ issue.setFieldDiff(context, "author", issue.authorLogin(), authorLogin);
+ issue.setAuthorLogin(authorLogin);
+ issue.setUpdateDate(context.date());
+ issue.setChanged(true);
+ }
}
public void setMessage(DefaultIssue issue, @Nullable String s, IssueChangeContext context) {
- issue.setMessage(s);
- issue.setUpdateDate(context.date());
+ if (!Objects.equal(s, issue.message())) {
+ issue.setFieldDiff(context, "message", issue.message(), s);
+ issue.setMessage(s);
+ issue.setUpdateDate(context.date());
+ issue.setChanged(true);
+ }
}
public void addComment(DefaultIssue issue, String text, IssueChangeContext context) {
issue.addComment(DefaultIssueComment.create(issue.key(), context.login(), text));
issue.setUpdateDate(context.date());
+ issue.setChanged(true);
}
- public void setCloseDate(DefaultIssue issue, @Nullable Date d) {
- issue.setCloseDate(d);
+ public void setCloseDate(DefaultIssue issue, @Nullable Date d, IssueChangeContext context) {
+ if (!Objects.equal(d, issue.closeDate())) {
+ issue.setCloseDate(d);
+ issue.setUpdateDate(context.date());
+ issue.setChanged(true);
+ }
}
public void setEffortToFix(DefaultIssue issue, @Nullable Double d, IssueChangeContext context) {
- issue.setEffortToFix(d);
- issue.setUpdateDate(context.date());
+ if (!Objects.equal(d, issue.closeDate())) {
+ issue.setEffortToFix(d);
+ issue.setUpdateDate(context.date());
+ issue.setChanged(true);
+ }
}
public boolean setAttribute(DefaultIssue issue, String key, @Nullable String value, IssueChangeContext context) {
issue.setFieldDiff(context, key, oldValue, value);
issue.setAttribute(key, value);
issue.setUpdateDate(context.date());
+ issue.setChanged(true);
return true;
}
return false;
issue.setFieldDiff(context, "actionPlanKey", issue.actionPlanKey(), sanitizedActionPlanKey);
issue.setActionPlanKey(sanitizedActionPlanKey);
issue.setUpdateDate(context.date());
+ issue.setChanged(true);
return true;
}
return false;
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
- public static IssueDto toDtoForInsert(DefaultIssue issue, Integer componentId, Integer ruleId) {
+ public static IssueDto toDtoForInsert(DefaultIssue issue, Integer componentId, Integer ruleId, Date now) {
return new IssueDto()
.setKee(issue.key())
.setLine(issue.line())
.setActionPlanKey(issue.actionPlanKey())
.setAttributes(issue.attributes() != null ? KeyValueFormat.format(issue.attributes()) : "")
.setAuthorLogin(issue.authorLogin())
- .setCreatedAt(issue.technicalCreationDate())
- .setUpdatedAt(issue.technicalUpdateDate())
.setIssueCreationDate(issue.creationDate())
.setIssueCloseDate(issue.closeDate())
- .setIssueUpdateDate(issue.updateDate());
+ .setIssueUpdateDate(issue.updateDate())
+
+ .setCreatedAt(now)
+ .setUpdatedAt(now);
}
- public static IssueDto toDtoForUpdate(DefaultIssue issue) {
+ public static IssueDto toDtoForUpdate(DefaultIssue issue, Date now) {
// Invariant fields, like key and rule, can't be updated
return new IssueDto()
.setKee(issue.key())
.setActionPlanKey(issue.actionPlanKey())
.setAttributes(issue.attributes() != null ? KeyValueFormat.format(issue.attributes()) : "")
.setAuthorLogin(issue.authorLogin())
- .setUpdatedAt(issue.technicalUpdateDate())
.setIssueCreationDate(issue.creationDate())
.setIssueCloseDate(issue.closeDate())
- .setIssueUpdateDate(issue.updateDate());
+ .setIssueUpdateDate(issue.updateDate())
+ .setUpdatedAt(now);
}
public DefaultIssue toDefaultIssue() {
issue.setActionPlanKey(actionPlanKey);
issue.setAuthorLogin(authorLogin);
issue.setNew(false);
- issue.setTechnicalCreationDate(createdAt);
- issue.setTechnicalUpdateDate(updatedAt);
issue.setCreationDate(issueCreationDate);
issue.setCloseDate(issueCloseDate);
issue.setUpdateDate(issueUpdateDate);
import org.sonar.core.persistence.MyBatis;
import java.util.Arrays;
+import java.util.Date;
import java.util.List;
public abstract class IssueStorage {
SqlSession session = mybatis.openBatchSession();
IssueMapper issueMapper = session.getMapper(IssueMapper.class);
IssueChangeMapper issueChangeMapper = session.getMapper(IssueChangeMapper.class);
+ Date now = new Date();
try {
List<DefaultIssue> conflicts = Lists.newArrayList();
-
for (DefaultIssue issue : issues) {
if (issue.isNew()) {
int componentId = componentId(issue);
int ruleId = ruleId(issue);
- IssueDto dto = IssueDto.toDtoForInsert(issue, componentId, ruleId);
+ IssueDto dto = IssueDto.toDtoForInsert(issue, componentId, ruleId, now);
issueMapper.insert(dto);
- } else /* TODO if hasChanges */ {
- // TODO manage condition on update date
- IssueDto dto = IssueDto.toDtoForUpdate(issue);
+ } else if (issue.isChanged()) {
+ IssueDto dto = IssueDto.toDtoForUpdate(issue, now);
int count = issueMapper.update(dto);
if (count < 1) {
conflicts.add(issue);
@Override
public Function.Context setCloseDate(boolean b) {
- updater.setCloseDate(issue, b ? changeContext.date() : null);
+ updater.setCloseDate(issue, b ? changeContext.date() : null, changeContext);
return null;
}
}
assertThat(issue.ruleKey().repository()).isEqualTo("squid");
assertThat(issue.ruleKey().rule()).isEqualTo("NullDereference");
assertThat(issue.severity()).isEqualTo(Severity.CRITICAL);
- assertThat(issue.technicalCreationDate()).isNotNull();
- assertThat(issue.technicalUpdateDate()).isNotNull();
assertThat(issue.assignee()).isNull();
assertThat(issue.isNew()).isTrue();
assertThat(issue.resolution()).isNull();
comment.setKey("FGHIJ");
Date date = DateUtils.parseDate("2013-05-18");
- DefaultIssue issue = new DefaultIssue();
- issue.setKey("ABCDE");
- issue.setRuleKey(RuleKey.of("squid", "AvoidCycle"));
- issue.setLine(5000);
- issue.setNew(true);
- issue.setReporter("emmerik");
- issue.setResolution("OPEN").setStatus("OPEN").setSeverity("BLOCKER");
- issue.setAttribute("foo", "bar");
- issue.addComment(comment);
- issue.setCreationDate(date);
- issue.setUpdateDate(date);
- issue.setCloseDate(date);
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setNew(true)
+
+ .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
+ .setLine(5000)
+ .setReporter("emmerik")
+ .setResolution("OPEN")
+ .setStatus("OPEN")
+ .setSeverity("BLOCKER")
+ .setAttribute("foo", "bar")
+ .addComment(comment)
+ .setCreationDate(date)
+ .setUpdateDate(date)
+ .setCloseDate(date);
saver.save(issue);
comment.setKey("FGHIJ");
Date date = DateUtils.parseDate("2013-05-18");
- DefaultIssue issue = new DefaultIssue();
- issue.setKey("ABCDE");
- issue.setRuleKey(RuleKey.of("squid", "AvoidCycle"));
- issue.setLine(5000);
- issue.setNew(false);
- issue.setChecksum("FFFFF");
- issue.setAuthorLogin("simon");
- issue.setAssignee("loic");
- issue.setFieldDiff(context, "severity", "INFO", "BLOCKER");
- issue.setReporter("emmerik");
- issue.setResolution("FIXED").setStatus("RESOLVED").setSeverity("BLOCKER");
- issue.setAttribute("foo", "bar");
- issue.addComment(comment);
- issue.setCreationDate(date);
- issue.setUpdateDate(date);
- issue.setCloseDate(date);
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("ABCDE")
+ .setNew(false)
+ .setChanged(true)
+
+ // updated fields
+ .setLine(5000)
+ .setChecksum("FFFFF")
+ .setAuthorLogin("simon")
+ .setAssignee("loic")
+ .setFieldDiff(context, "severity", "INFO", "BLOCKER")
+ .setReporter("emmerik")
+ .setResolution("FIXED")
+ .setStatus("RESOLVED")
+ .setSeverity("BLOCKER")
+ .setAttribute("foo", "bar")
+ .addComment(comment)
+ .setCreationDate(date)
+ .setUpdateDate(date)
+ .setCloseDate(date)
+
+ // unmodifiable fields
+ .setRuleKey(RuleKey.of("xxx", "unknown"))
+ .setComponentKey("not:a:component");
saver.save(issue);
:rule => issue.ruleKey.toString(),
:status => issue.status
}
- hash[:actionPlan] = issue.actionPlanKey if issue.actionPlanKey
hash[:resolution] = issue.resolution if issue.resolution
hash[:severity] = issue.severity if issue.severity
hash[:message] = issue.message if issue.message
hash[:effortToFix] = issue.effortToFix.to_f if issue.effortToFix
hash[:reporter] = issue.reporter if issue.reporter
hash[:assignee] = issue.assignee if issue.assignee
+ hash[:actionPlan] = issue.actionPlanKey if issue.actionPlanKey
hash[:creationDate] = Api::Utils.format_datetime(issue.creationDate) if issue.creationDate
hash[:updateDate] = Api::Utils.format_datetime(issue.updateDate) if issue.updateDate
hash[:closeDate] = Api::Utils.format_datetime(issue.closeDate) if issue.closeDate