diff options
author | Simon Brandhof <simon.brandhof@gmail.com> | 2013-05-02 14:39:34 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@gmail.com> | 2013-05-02 14:50:19 +0200 |
commit | 1fd5d8abd320428e1e8e4cd1b3ebf793678b10f2 (patch) | |
tree | 73668ba184e89bcadf405ae15e1ba3bbd22054d6 /sonar-core | |
parent | 5f2b527e01b997a551a1a57779a57a80c1ba3411 (diff) | |
download | sonarqube-1fd5d8abd320428e1e8e4cd1b3ebf793678b10f2.tar.gz sonarqube-1fd5d8abd320428e1e8e4cd1b3ebf793678b10f2.zip |
SONAR-3755 implement changelog
Diffstat (limited to 'sonar-core')
33 files changed, 1103 insertions, 343 deletions
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java index bf4dd2ed49b..1d69fe1a059 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java @@ -23,6 +23,7 @@ import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ToStringBuilder; @@ -31,10 +32,12 @@ import org.sonar.api.issue.Issue; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; import java.io.Serializable; import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.Map; public class DefaultIssue implements Issue { @@ -60,7 +63,8 @@ public class DefaultIssue implements Issue { private boolean isAlive = true; private Map<String, String> attributes = null; private String authorLogin = null; - private IssueChange change = null; + private FieldDiffs diffs = null; + private List<IssueComment> newComments = null; public String key() { return key; @@ -239,6 +243,7 @@ public class DefaultIssue implements Issue { return this; } + @CheckForNull public String attribute(String key) { return attributes == null ? null : attributes.get(key); } @@ -259,11 +264,17 @@ public class DefaultIssue implements Issue { return attributes == null ? Collections.<String, String>emptyMap() : ImmutableMap.copyOf(attributes); } - public DefaultIssue setAttributes(Map<String, String> attributes) { - this.attributes = attributes; + public DefaultIssue setAttributes(@Nullable Map<String, String> map) { + if (map != null) { + if (attributes == null) { + attributes = Maps.newHashMap(); + } + attributes.putAll(map); + } return this; } + @CheckForNull public String authorLogin() { return authorLogin; } @@ -273,18 +284,32 @@ public class DefaultIssue implements Issue { return this; } - public DefaultIssue setDiff(String field, @Nullable Serializable oldValue, @Nullable Serializable newValue) { + public DefaultIssue setFieldDiff(IssueChangeContext context, String field, @Nullable Serializable oldValue, @Nullable Serializable newValue) { if (!Objects.equal(oldValue, newValue)) { - if (change == null) { - change = new IssueChange(); + if (diffs == null) { + diffs = new FieldDiffs(); + diffs.setUserLogin(context.login()); } - change.setDiff(field, oldValue, newValue); + diffs.setDiff(field, oldValue, newValue); + } + return this; + } + + @CheckForNull + public FieldDiffs diffs() { + return diffs; + } + + public DefaultIssue addComment(IssueComment comment) { + if (newComments == null) { + newComments = Lists.newArrayList(); } + newComments.add(comment); return this; } - public IssueChange change() { - return change; + public List<IssueComment> newComments() { + return Objects.firstNonNull(newComments, Collections.<IssueComment>emptyList()); } @Override diff --git a/sonar-core/src/main/java/org/sonar/core/issue/FieldDiffs.java b/sonar-core/src/main/java/org/sonar/core/issue/FieldDiffs.java new file mode 100644 index 00000000000..a7004285ecd --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/issue/FieldDiffs.java @@ -0,0 +1,129 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.core.issue; + +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.Maps; + +import javax.annotation.Nullable; +import java.io.Serializable; +import java.util.Map; + +public class FieldDiffs extends IssueChange { + + public static final Splitter FIELDS_SPLITTER = Splitter.on(',').omitEmptyStrings(); + + private final Map<String, Diff> diffs = Maps.newLinkedHashMap(); + + Map<String, Diff> diffs() { + return diffs; + } + + Diff get(String field) { + return diffs.get(field); + } + + @SuppressWarnings("unchecked") + public void setDiff(String field, @Nullable Serializable oldValue, @Nullable Serializable newValue) { + Diff diff = diffs.get(field); + if (diff == null) { + diff = new Diff(oldValue, newValue); + diffs.put(field, diff); + } else { + diff.setNewValue(newValue); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + boolean notFirst = false; + for (Map.Entry<String, Diff> entry : diffs.entrySet()) { + if (notFirst) { + sb.append(','); + } else { + notFirst = true; + } + sb.append(entry.getKey()); + sb.append('='); + sb.append(entry.getValue().toString()); + } + return sb.toString(); + } + + public static FieldDiffs parse(@Nullable String s) { + FieldDiffs diffs = new FieldDiffs(); + if (!Strings.isNullOrEmpty(s)) { + Iterable<String> fields = FIELDS_SPLITTER.split(s); + for (String field : fields) { + String[] keyValues = field.split("="); + if (keyValues.length == 2) { + String[] values = keyValues[1].split("\\|"); + String oldValue = ""; + String newValue = ""; + if (values.length > 0) { + oldValue = Strings.nullToEmpty(values[0]); + } + if (values.length > 1) { + newValue = Strings.nullToEmpty(values[1]); + } + diffs.setDiff(keyValues[0], oldValue, newValue); + } + } + } + return diffs; + } + + public static class Diff<T extends Serializable> implements Serializable { + private T oldValue, newValue; + + public Diff(@Nullable T oldValue, @Nullable T newValue) { + this.oldValue = oldValue; + this.newValue = newValue; + } + + public T oldValue() { + return oldValue; + } + + public T newValue() { + return newValue; + } + + void setNewValue(T t) { + this.newValue = t; + } + + @Override + public String toString() { + //TODO escape , and | characters + StringBuilder sb = new StringBuilder(); + if (oldValue != null) { + sb.append(oldValue.toString()); + } + sb.append('|'); + if (newValue != null) { + sb.append(newValue.toString()); + } + return sb.toString(); + } + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssueChange.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueChange.java index d64c6b98411..527965cb442 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/IssueChange.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/IssueChange.java @@ -19,49 +19,37 @@ */ package org.sonar.core.issue; -import com.google.common.collect.Maps; - -import javax.annotation.Nullable; import java.io.Serializable; -import java.util.Map; - -public class IssueChange implements Serializable { - - public static class Diff<T extends Serializable> implements Serializable { - private T before, after; +import java.util.Date; - public Diff(@Nullable T before, @Nullable T after) { - this.before = before; - this.after = after; - } +public abstract class IssueChange implements Serializable { + private String userLogin; + private Date createdAt, updatedAt; - public T before() { - return before; - } + public String userLogin() { + return userLogin; + } - public T after() { - return after; - } + public IssueChange setUserLogin(String s) { + this.userLogin = s; + return this; + } - void setAfter(T t) { - this.after = t; - } + public Date createdAt() { + return createdAt; } - private Map<String, Diff> diffs = Maps.newLinkedHashMap(); + public IssueChange setCreatedAt(Date d) { + this.createdAt = d; + return this; + } - public Map<String, Diff> diffs() { - return diffs; + public Date updatedAt() { + return updatedAt; } - @SuppressWarnings("unchecked") - public void setDiff(String field, @Nullable Serializable oldValue, @Nullable Serializable newValue) { - Diff diff = diffs.get(field); - if (diff == null) { - diff = new Diff(oldValue, newValue); - diffs.put(field, diff); - } else { - diff.setAfter(newValue); - } + public IssueChange setUpdatedAt(Date d) { + this.updatedAt = d; + return this; } } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java index a8422d6edd9..440fbff092c 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java @@ -28,12 +28,12 @@ public class IssueChangeContext implements Serializable { private String login; private Date date; - private boolean automatic; + private boolean scan; - private IssueChangeContext(@Nullable String login, Date date, boolean automatic) { + private IssueChangeContext(@Nullable String login, Date date, boolean scan) { this.login = login; this.date = date; - this.automatic = automatic; + this.scan = scan; } @CheckForNull @@ -45,8 +45,8 @@ public class IssueChangeContext implements Serializable { return date; } - public boolean automatic() { - return automatic; + public boolean scan() { + return scan; } public static IssueChangeContext createScan(Date date) { diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssueComment.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueComment.java new file mode 100644 index 00000000000..975c9ae5978 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/issue/IssueComment.java @@ -0,0 +1,58 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.core.issue; + +import javax.annotation.Nullable; +import java.util.Date; +import java.util.UUID; + +public class IssueComment extends IssueChange { + + private String key; + private String text; + + public String text() { + return text; + } + + public IssueComment setText(String s) { + this.text = s; + return this; + } + + public String key() { + return key; + } + + public IssueComment setKey(String key) { + this.key = key; + return this; + } + + public static IssueComment create(@Nullable String login, String text) { + IssueComment comment = new IssueComment(); + comment.setKey(UUID.randomUUID().toString()); + Date now = new Date(); + comment.setUserLogin(login); + comment.setText(text); + comment.setCreatedAt(now).setUpdatedAt(now); + return comment; + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java index 58341de0ea3..2d5725dda68 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java @@ -30,18 +30,21 @@ import java.util.Date; public class IssueUpdater implements BatchComponent, ServerComponent { - public boolean setSeverity(DefaultIssue issue, String severity) { + public boolean setSeverity(DefaultIssue issue, String severity, IssueChangeContext context) { + if (issue.manualSeverity()) { + throw new IllegalStateException("Severity can't be changed"); + } if (!Objects.equal(severity, issue.severity())) { - issue.setDiff("severity", issue.severity(), severity); + issue.setFieldDiff(context, "severity", issue.severity(), severity); issue.setSeverity(severity); return true; } return false; } - public boolean setManualSeverity(DefaultIssue issue, String severity) { + public boolean setManualSeverity(DefaultIssue issue, String severity, IssueChangeContext context) { if (!issue.manualSeverity() || !Objects.equal(severity, issue.severity())) { - issue.setDiff("severity", issue.severity(), severity); + issue.setFieldDiff(context, "severity", issue.severity(), severity); issue.setSeverity(severity); issue.setManualSeverity(true); return true; @@ -49,62 +52,56 @@ public class IssueUpdater implements BatchComponent, ServerComponent { return false; } - public boolean assign(DefaultIssue issue, @Nullable String assignee) { + public boolean assign(DefaultIssue issue, @Nullable String assignee, IssueChangeContext context) { String sanitizedAssignee = StringUtils.defaultIfBlank(assignee, null); if (!Objects.equal(sanitizedAssignee, issue.assignee())) { - issue.setDiff("assignee", issue.assignee(), sanitizedAssignee); + issue.setFieldDiff(context, "assignee", issue.assignee(), sanitizedAssignee); issue.setAssignee(sanitizedAssignee); return true; } return false; } - public DefaultIssue setLine(DefaultIssue issue, @Nullable Integer line) { + public boolean setLine(DefaultIssue issue, @Nullable Integer line) { if (!Objects.equal(line, issue.line())) { issue.setLine(line); + return true; } - return issue; + return false; } - public DefaultIssue setResolution(DefaultIssue issue, String resolution) { + public boolean setResolution(DefaultIssue issue, String resolution, IssueChangeContext context) { if (!Objects.equal(resolution, issue.resolution())) { - issue.setDiff("resolution", issue.resolution(), resolution); + issue.setFieldDiff(context, "resolution", issue.resolution(), resolution); issue.setResolution(resolution); + return true; } - return issue; + return false; } - public DefaultIssue setStatus(DefaultIssue issue, String status) { + public boolean setStatus(DefaultIssue issue, String status, IssueChangeContext context) { if (!Objects.equal(status, issue.status())) { - issue.setDiff("status", issue.status(), status); + issue.setFieldDiff(context, "status", issue.status(), status); issue.setStatus(status); + return true; } - return issue; + return false; } - public DefaultIssue setAuthorLogin(DefaultIssue issue, @Nullable String authorLogin) { - if (!Objects.equal(authorLogin, issue.authorLogin())) { - issue.setAuthorLogin(authorLogin); - } - return issue; + public void setAuthorLogin(DefaultIssue issue, @Nullable String authorLogin) { + issue.setAuthorLogin(authorLogin); } - public DefaultIssue setDescription(DefaultIssue issue, @Nullable String description) { - if (!Objects.equal(description, issue.description())) { - if (issue.manual()) { - issue.setDiff("description", issue.description(), description); - } - issue.setDescription(description); - } - return issue; + public void setDescription(DefaultIssue issue, @Nullable String description) { + issue.setDescription(description); } - public DefaultIssue setClosedDate(DefaultIssue issue, @Nullable Date date) { - if (!Objects.equal(date, issue.closedAt())) { - issue.setDiff("closedDate", issue.closedAt(), date); - issue.setClosedAt(date); - } - return issue; + public void addComment(DefaultIssue issue, String text, IssueChangeContext context) { + issue.addComment(IssueComment.create(context.login(), text)); + } + + public void setClosedDate(DefaultIssue issue, @Nullable Date date) { + issue.setClosedAt(date); } // TODO setAttribute diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/ChangeDtoConverter.java b/sonar-core/src/main/java/org/sonar/core/issue/db/ChangeDtoConverter.java new file mode 100644 index 00000000000..9403cd08d1d --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/ChangeDtoConverter.java @@ -0,0 +1,74 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.core.issue.db; + +import com.google.common.collect.Lists; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.FieldDiffs; +import org.sonar.core.issue.IssueChangeContext; +import org.sonar.core.issue.IssueComment; + +import java.util.Date; +import java.util.List; + +class ChangeDtoConverter { + + static final String TYPE_FIELD_CHANGE = "diff"; + static final String TYPE_COMMENT = "comment"; + + static List<IssueChangeDto> toChangeDtos(DefaultIssue issue) { + List<IssueChangeDto> dtos = Lists.newArrayList(); + for (IssueComment comment : issue.newComments()) { + dtos.add(commentToDto(issue.key(), comment)); + } + if (issue.diffs() != null) { + dtos.add(diffsToDto(issue.key(), issue.diffs())); + } + return dtos; + } + + static IssueChangeDto commentToDto(String issueKey, IssueComment comment) { + IssueChangeDto dto = newDto(issueKey); + dto.setKey(comment.key()); + dto.setChangeType(TYPE_COMMENT); + dto.setChangeData(comment.text()); + dto.setUserLogin(comment.userLogin()); + return dto; + } + + static IssueChangeDto diffsToDto(String issueKey, FieldDiffs diffs) { + IssueChangeDto dto = newDto(issueKey); + dto.setChangeType(TYPE_FIELD_CHANGE); + dto.setChangeData(diffs.toString()); + dto.setUserLogin(diffs.userLogin()); + return dto; + } + + private static IssueChangeDto newDto(String issueKey) { + IssueChangeDto dto = new IssueChangeDto(); + dto.setIssueKey(issueKey); + + // technical dates - do not use the context date + Date now = new Date(); + dto.setCreatedAt(now); + dto.setUpdatedAt(new Date()); + return dto; + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeDao.java b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeDao.java index 44b90236ea9..42a213d02e8 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeDao.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeDao.java @@ -25,7 +25,8 @@ import org.sonar.api.BatchComponent; import org.sonar.api.ServerComponent; import org.sonar.core.persistence.MyBatis; -import java.util.Collection; +import javax.annotation.CheckForNull; +import java.util.List; /** * @since 3.6 @@ -38,28 +39,21 @@ public class IssueChangeDao implements BatchComponent, ServerComponent { this.mybatis = mybatis; } - public void insert(IssueChangeDto issueChangeDto) { - SqlSession session = mybatis.openSession(); - IssueChangeMapper mapper = session.getMapper(IssueChangeMapper.class); - try { - mapper.insert(issueChangeDto); - session.commit(); - } finally { - MyBatis.closeQuietly(session); - } - } - - public IssueChangeDto findById(long issueChangeLogId) { + @CheckForNull + public IssueChangeDto selectById(long id) { SqlSession session = mybatis.openSession(); try { IssueChangeMapper mapper = session.getMapper(IssueChangeMapper.class); - return mapper.findById(issueChangeLogId); + return mapper.selectById(id); } finally { MyBatis.closeQuietly(session); } } - public Collection<IssueChangeDto> selectByIssue(String issueKey) { + /** + * Issue changes ordered by descending creation date. + */ + public List<IssueChangeDto> selectByIssue(String issueKey) { SqlSession session = mybatis.openSession(); try { IssueChangeMapper mapper = session.getMapper(IssueChangeMapper.class); diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeDto.java b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeDto.java index db1734c00e3..1ab40848704 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeDto.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeDto.java @@ -22,6 +22,7 @@ package org.sonar.core.issue.db; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; +import javax.annotation.Nullable; import java.util.Date; /** @@ -30,11 +31,11 @@ import java.util.Date; public final class IssueChangeDto { private Long id; + private String key; private String issueKey; private String userLogin; private String changeType; private String changeData; - private String message; private Date createdAt; private Date updatedAt; @@ -47,6 +48,15 @@ public final class IssueChangeDto { return this; } + public String getKey() { + return key; + } + + public IssueChangeDto setKey(String key) { + this.key = key; + return this; + } + public String getIssueKey() { return issueKey; } @@ -60,7 +70,7 @@ public final class IssueChangeDto { return userLogin; } - public IssueChangeDto setUserLogin(String userLogin) { + public IssueChangeDto setUserLogin(@Nullable String userLogin) { this.userLogin = userLogin; return this; } @@ -83,15 +93,6 @@ public final class IssueChangeDto { return this; } - public String getMessage() { - return message; - } - - public IssueChangeDto setMessage(String message) { - this.message = message; - return this; - } - public Date getCreatedAt() { return createdAt; } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeMapper.java b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeMapper.java index 988c6a41924..077d1f37f40 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeMapper.java @@ -20,17 +20,20 @@ package org.sonar.core.issue.db; -import java.util.Collection; +import java.util.List; /** * @since 3.6 */ public interface IssueChangeMapper { - void insert(IssueChangeDto issueChangeDto); + void insert(IssueChangeDto dto); - IssueChangeDto findById(long issueChangeLogId); + IssueChangeDto selectById(long id); - Collection<IssueChangeDto> selectByIssue(String issueUuid); + /** + * Issue changes ordered by descending creation date. + */ + List<IssueChangeDto> selectByIssue(String issueKey); } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDao.java b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDao.java index 75f055a176d..1fb43d57578 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDao.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDao.java @@ -48,35 +48,10 @@ public class IssueDao implements BatchComponent, ServerComponent { public IssueDao(MyBatis mybatis) { this.mybatis = mybatis; + // FIXME this.avalailableSorts = getAvalailableSorts(); } - public void insert(IssueDto issueDto) { - SqlSession session = mybatis.openSession(); - try { - // TODO bulk insert - session.insert("org.sonar.core.issue.db.IssueMapper.insert", issueDto); - session.commit(); - } finally { - MyBatis.closeQuietly(session); - } - } - - public IssueDao update(Collection<IssueDto> issues) { - SqlSession session = mybatis.openBatchSession(); - try { - // TODO bulk update - for (IssueDto issue : issues) { - session.update("org.sonar.core.issue.db.IssueMapper.update", issue); - } - session.commit(); - return this; - - } finally { - MyBatis.closeQuietly(session); - } - } - public IssueDto selectById(long id) { SqlSession session = mybatis.openSession(); try { diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueStorage.java b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueStorage.java new file mode 100644 index 00000000000..25872741a9f --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueStorage.java @@ -0,0 +1,90 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.core.issue.db; + +import com.google.common.collect.Lists; +import org.apache.ibatis.session.SqlSession; +import org.sonar.api.issue.Issue; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.RuleFinder; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.persistence.MyBatis; + +import java.util.Arrays; +import java.util.List; + +public abstract class IssueStorage { + + private static final String MYBATIS_INSERT_CHANGE = "org.sonar.core.issue.db.IssueChangeMapper.insert"; + private static final String MYBATIS_INSERT_ISSUE = "org.sonar.core.issue.db.IssueMapper.insert"; + private static final String MYBATIS_UPDATE_ISSUE = "org.sonar.core.issue.db.IssueMapper.update"; + + private final MyBatis mybatis; + private final RuleFinder ruleFinder; + + protected IssueStorage(MyBatis mybatis, RuleFinder ruleFinder) { + this.mybatis = mybatis; + this.ruleFinder = ruleFinder; + } + + public void save(DefaultIssue issue) { + save(Arrays.asList(issue)); + } + + public void save(Iterable<DefaultIssue> issues) { + SqlSession session = mybatis.openSession(); + try { + List<DefaultIssue> conflicts = Lists.newArrayList(); + for (DefaultIssue issue : issues) { + int ruleId = ruleId(issue); + int componentId = componentId(issue); + + IssueDto dto = IssueDto.toDto(issue, componentId, ruleId); + // TODO set technical created/updated dates + if (issue.isNew()) { + session.insert(MYBATIS_INSERT_ISSUE, dto); + } else /* TODO if hasChanges */ { + // TODO manage condition on update date + int count = session.update(MYBATIS_UPDATE_ISSUE, dto); + if (count < 1) { + conflicts.add(issue); + } + } + for (IssueChangeDto changeDto : ChangeDtoConverter.toChangeDtos(issue)) { + session.insert(MYBATIS_INSERT_CHANGE, changeDto); + } + } + session.commit(); + // TODO log and fix conflicts + } finally { + MyBatis.closeQuietly(session); + } + } + + protected abstract int componentId(DefaultIssue issue); + + private int ruleId(Issue issue) { + Rule rule = ruleFinder.findByKey(issue.ruleKey()); + if (rule == null) { + throw new IllegalStateException("Rule not found: " + issue.ruleKey()); + } + return rule.getId(); + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java index a54badf74cd..097e5cfe5b5 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java @@ -44,7 +44,6 @@ public class FunctionExecutor implements BatchComponent, ServerComponent { } static class FunctionContext implements Function.Context { - private final IssueUpdater updater; private final DefaultIssue issue; private final IssueChangeContext changeContext; @@ -62,7 +61,7 @@ public class FunctionExecutor implements BatchComponent, ServerComponent { @Override public Function.Context setResolution(String s) { - updater.setResolution(issue, s); + updater.setResolution(issue, s, changeContext); return this; } diff --git a/sonar-core/src/main/resources/org/sonar/core/issue/db/IssueChangeMapper.xml b/sonar-core/src/main/resources/org/sonar/core/issue/db/IssueChangeMapper.xml index 9ed85877273..3dd61b273e1 100644 --- a/sonar-core/src/main/resources/org/sonar/core/issue/db/IssueChangeMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/issue/db/IssueChangeMapper.xml @@ -26,39 +26,42 @@ <sql id="issueChangeColumns"> i.id, + i.kee as kee, i.issue_key as issueKey, i.user_login as userLogin, i.change_type as changeType, i.change_data as changeData, - i.message as message, i.created_at as createdAt, i.updated_at as updatedAt </sql> - <insert id="insert" parameterType="IssueChange" useGeneratedKeys="true" keyProperty ="id"> - INSERT INTO issue_changes (issue_key, user_login, change_type, change_data, message, created_at, updated_at) - VALUES (#{issueKey}, #{userLogin}, #{changeType}, #{changeData}, #{message}, #{createdAt}, #{updatedAt}) + <insert id="insert" parameterType="IssueChange" useGeneratedKeys="true" keyProperty="id"> + INSERT INTO issue_changes (kee, issue_key, user_login, change_type, change_data, created_at, updated_at) + VALUES (#{key}, #{issueKey}, #{userLogin}, #{changeType}, #{changeData}, #{createdAt}, #{updatedAt}) </insert> <!-- Oracle --> - <insert id="insert" databaseId="oracle" parameterType="IssueChange" keyColumn="id" useGeneratedKeys="true" keyProperty ="id"> - <selectKey order="BEFORE" resultType="Long" keyProperty="id" > + <insert id="insert" databaseId="oracle" parameterType="IssueChange" keyColumn="id" useGeneratedKeys="true" keyProperty="id"> + <selectKey order="BEFORE" resultType="Long" keyProperty="id"> select issue_changes_seq.NEXTVAL from DUAL </selectKey> - INSERT INTO issue_changes (id, issue_key, user_login, change_type, change_data, message, created_at, updated_at) - VALUES (#{id}, #{issueKey}, #{userLogin}, #{changeType}, #{changeData}, #{message}, #{createdAt}, #{updatedAt}) + INSERT INTO issue_changes (id, kee, issue_key, user_login, change_type, change_data, created_at, updated_at) + VALUES (#{id}, #{key}, #{issueKey}, #{userLogin}, #{changeType}, #{changeData}, #{createdAt}, #{updatedAt}) </insert> - <select id="findById" parameterType="long" resultType="IssueChange"> - select <include refid="issueChangeColumns"/> + <select id="selectById" parameterType="long" resultType="IssueChange"> + select + <include refid="issueChangeColumns"/> from issue_changes i where i.id=#{id} </select> <select id="selectByIssue" parameterType="string" resultType="IssueChange"> - select <include refid="issueChangeColumns"/> + select + <include refid="issueChangeColumns"/> from issue_changes i where i.issue_key=#{issueKey} + order by i.created_at desc </select> </mapper> diff --git a/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl b/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl index 34f3fd55b10..b7b0e86adbe 100644 --- a/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl +++ b/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl @@ -518,7 +518,7 @@ CREATE TABLE "GRAPHS" ( CREATE TABLE "ISSUES" ( "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), - "KEE" VARCHAR(36) NOT NULL, + "KEE" VARCHAR(100) NOT NULL, "RESOURCE_ID" INTEGER NOT NULL, "RULE_ID" INTEGER NOT NULL, "SEVERITY" VARCHAR(10), @@ -541,11 +541,11 @@ CREATE TABLE "ISSUES" ( CREATE TABLE "ISSUE_CHANGES" ( "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), - "ISSUE_KEY" VARCHAR(36) NOT NULL, + "KEE" VARCHAR(100), + "ISSUE_KEY" VARCHAR(100) NOT NULL, "USER_LOGIN" VARCHAR(40), - "CHANGE_TYPE" VARCHAR(50), - "CHANGE_DATA" VARCHAR(4000), - "MESSAGE" VARCHAR(16777215), + "CHANGE_TYPE" VARCHAR(40), + "CHANGE_DATA" VARCHAR(16777215), "CREATED_AT" TIMESTAMP, "UPDATED_AT" TIMESTAMP, ); @@ -677,12 +677,14 @@ CREATE INDEX "MEASURE_FILTER_FAVS_USERID" ON "MEASURE_FILTER_FAVOURITES" ("USER_ CREATE UNIQUE INDEX "GRAPHS_PERSPECTIVES" ON "GRAPHS" ("SNAPSHOT_ID", "PERSPECTIVE"); +CREATE INDEX "ISSUES_KEE" ON "ISSUES" ("KEE"); + CREATE INDEX "ISSUES_RESOURCE_ID" ON "ISSUES" ("RESOURCE_ID"); CREATE INDEX "INDEX_ACTION_PLANS_ISSUES_ON_ACTION_PLAN_ID" ON "ACTION_PLANS_ISSUES" ("ACTION_PLAN_ID"); CREATE INDEX "INDEX_ACTION_PLANS_ISSUES_ON_ISSUE_ID" ON "ACTION_PLANS_ISSUES" ("ISSUE_ID"); ---CREATE INDEX "ISSUES_RULE_ID" ON "ISSUES" ("RULE_ID"); +CREATE INDEX "ISSUE_CHANGES_ISSUE_KEY" ON "ISSUE_CHANGES" ("ISSUE_KEY"); ---CREATE INDEX "SNAPSHOT_DATA_SNAPSHOT_ID" ON "SNAPSHOT_DATA" ("SNAPSHOT_ID"); +CREATE INDEX "ISSUE_CHANGES_KEE" ON "ISSUE_CHANGES" ("KEE"); diff --git a/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java b/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java index 64e5970e8fb..1ac246e0db3 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java @@ -19,11 +19,13 @@ */ package org.sonar.core.issue; +import com.google.common.collect.ImmutableMap; import org.apache.commons.lang.StringUtils; import org.junit.Test; import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.Fail.fail; +import static org.fest.assertions.MapAssert.entry; public class DefaultIssueTest { @@ -41,6 +43,20 @@ public class DefaultIssueTest { } @Test + public void setAttributes_should_not_clear_existing_values() throws Exception { + issue.setAttributes(ImmutableMap.of("1", "one")); + assertThat(issue.attribute("1")).isEqualTo("one"); + + issue.setAttributes(ImmutableMap.of("2", "two")); + assertThat(issue.attributes()).hasSize(2); + assertThat(issue.attributes()).includes(entry("1", "one"), entry("2", "two")); + + issue.setAttributes(null); + assertThat(issue.attributes()).hasSize(2); + assertThat(issue.attributes()).includes(entry("1", "one"), entry("2", "two")); + } + + @Test public void should_fail_on_empty_status() { try { issue.setStatus(""); diff --git a/sonar-core/src/test/java/org/sonar/core/issue/FieldDiffsTest.java b/sonar-core/src/test/java/org/sonar/core/issue/FieldDiffsTest.java new file mode 100644 index 00000000000..08d436c7f36 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/issue/FieldDiffsTest.java @@ -0,0 +1,115 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.core.issue; + +import org.junit.Test; + +import static org.fest.assertions.Assertions.assertThat; + +public class FieldDiffsTest { + + FieldDiffs diffs = new FieldDiffs(); + + @Test + public void diffs_should_be_empty_by_default() throws Exception { + assertThat(diffs.diffs()).isEmpty(); + } + + @Test + public void test_diff() throws Exception { + diffs.setDiff("severity", "BLOCKER", "INFO"); + diffs.setDiff("resolution", "OPEN", "FIXED"); + + assertThat(diffs.diffs()).hasSize(2); + + FieldDiffs.Diff diff = diffs.diffs().get("severity"); + assertThat(diff.oldValue()).isEqualTo("BLOCKER"); + assertThat(diff.newValue()).isEqualTo("INFO"); + + diff = diffs.diffs().get("resolution"); + assertThat(diff.oldValue()).isEqualTo("OPEN"); + assertThat(diff.newValue()).isEqualTo("FIXED"); + } + + @Test + public void should_keep_old_value() throws Exception { + diffs.setDiff("severity", "BLOCKER", "INFO"); + diffs.setDiff("severity", null, "MAJOR"); + FieldDiffs.Diff diff = diffs.diffs().get("severity"); + assertThat(diff.oldValue()).isEqualTo("BLOCKER"); + assertThat(diff.newValue()).isEqualTo("MAJOR"); + } + + @Test + public void test_toString() throws Exception { + diffs.setDiff("severity", "BLOCKER", "INFO"); + diffs.setDiff("resolution", "OPEN", "FIXED"); + + assertThat(diffs.toString()).isEqualTo("severity=BLOCKER|INFO,resolution=OPEN|FIXED"); + } + + @Test + public void test_toString_with_null_values() throws Exception { + diffs.setDiff("severity", null, "INFO"); + diffs.setDiff("resolution", "OPEN", null); + + assertThat(diffs.toString()).isEqualTo("severity=|INFO,resolution=OPEN|"); + } + + @Test + public void test_parse() throws Exception { + diffs = FieldDiffs.parse("severity=BLOCKER|INFO,resolution=OPEN|FIXED"); + assertThat(diffs.diffs()).hasSize(2); + + FieldDiffs.Diff diff = diffs.diffs().get("severity"); + assertThat(diff.oldValue()).isEqualTo("BLOCKER"); + assertThat(diff.newValue()).isEqualTo("INFO"); + + diff = diffs.diffs().get("resolution"); + assertThat(diff.oldValue()).isEqualTo("OPEN"); + assertThat(diff.newValue()).isEqualTo("FIXED"); + } + + @Test + public void test_parse_empty_values() throws Exception { + diffs = FieldDiffs.parse("severity=|INFO,resolution=OPEN|"); + assertThat(diffs.diffs()).hasSize(2); + + FieldDiffs.Diff diff = diffs.diffs().get("severity"); + assertThat(diff.oldValue()).isEqualTo(""); + assertThat(diff.newValue()).isEqualTo("INFO"); + + diff = diffs.diffs().get("resolution"); + assertThat(diff.oldValue()).isEqualTo("OPEN"); + assertThat(diff.newValue()).isEqualTo(""); + } + + @Test + public void test_parse_null() throws Exception { + diffs = FieldDiffs.parse(null); + assertThat(diffs.diffs()).isEmpty(); + } + + @Test + public void test_parse_empty() throws Exception { + diffs = FieldDiffs.parse(""); + assertThat(diffs.diffs()).isEmpty(); + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/issue/IssueChangeContextTest.java b/sonar-core/src/test/java/org/sonar/core/issue/IssueChangeContextTest.java new file mode 100644 index 00000000000..101b5fd1472 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/issue/IssueChangeContextTest.java @@ -0,0 +1,48 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.core.issue; + +import org.junit.Test; + +import java.util.Date; + +import static org.fest.assertions.Assertions.assertThat; + +public class IssueChangeContextTest { + @Test + public void test_scan_context() throws Exception { + Date now = new Date(); + IssueChangeContext context = IssueChangeContext.createScan(now); + + assertThat(context.scan()).isTrue(); + assertThat(context.login()).isNull(); + assertThat(context.date()).isEqualTo(now); + } + + @Test + public void test_end_user_context() throws Exception { + Date now = new Date(); + IssueChangeContext context = IssueChangeContext.createUser(now, "emmerik"); + + assertThat(context.scan()).isFalse(); + assertThat(context.login()).isEqualTo("emmerik"); + assertThat(context.date()).isEqualTo(now); + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/issue/IssueUpdaterTest.java b/sonar-core/src/test/java/org/sonar/core/issue/IssueUpdaterTest.java new file mode 100644 index 00000000000..478121c2e45 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/issue/IssueUpdaterTest.java @@ -0,0 +1,196 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.core.issue; + +import org.junit.Test; + +import java.util.Date; + +import static org.fest.assertions.Assertions.assertThat; + +public class IssueUpdaterTest { + + IssueUpdater updater = new IssueUpdater(); + DefaultIssue issue = new DefaultIssue(); + IssueChangeContext context = IssueChangeContext.createUser(new Date(), "emmerik"); + + @Test + public void should_assign() throws Exception { + boolean updated = updater.assign(issue, "emmerik", context); + assertThat(updated).isTrue(); + assertThat(issue.assignee()).isEqualTo("emmerik"); + FieldDiffs.Diff diff = issue.diffs().get("assignee"); + assertThat(diff.oldValue()).isNull(); + assertThat(diff.newValue()).isEqualTo("emmerik"); + } + + @Test + public void should_unassign() throws Exception { + issue.setAssignee("morgan"); + boolean updated = updater.assign(issue, null, context); + assertThat(updated).isTrue(); + assertThat(issue.assignee()).isNull(); + FieldDiffs.Diff diff = issue.diffs().get("assignee"); + assertThat(diff.oldValue()).isEqualTo("morgan"); + assertThat(diff.newValue()).isNull(); + } + + @Test + public void should_change_assignee() throws Exception { + issue.setAssignee("morgan"); + boolean updated = updater.assign(issue, "emmerik", context); + assertThat(updated).isTrue(); + assertThat(issue.assignee()).isEqualTo("emmerik"); + FieldDiffs.Diff diff = issue.diffs().get("assignee"); + assertThat(diff.oldValue()).isEqualTo("morgan"); + assertThat(diff.newValue()).isEqualTo("emmerik"); + } + + @Test + public void should_not_change_assignee() throws Exception { + issue.setAssignee("morgan"); + boolean updated = updater.assign(issue, "morgan", context); + assertThat(updated).isFalse(); + assertThat(issue.diffs()).isNull(); + } + + + @Test + public void should_set_severity() throws Exception { + boolean updated = updater.setSeverity(issue, "BLOCKER", context); + assertThat(updated).isTrue(); + assertThat(issue.severity()).isEqualTo("BLOCKER"); + assertThat(issue.manualSeverity()).isFalse(); + + FieldDiffs.Diff diff = issue.diffs().get("severity"); + assertThat(diff.oldValue()).isNull(); + assertThat(diff.newValue()).isEqualTo("BLOCKER"); + } + + @Test + public void should_update_severity() throws Exception { + issue.setSeverity("BLOCKER"); + boolean updated = updater.setSeverity(issue, "MINOR", context); + + assertThat(updated).isTrue(); + assertThat(issue.severity()).isEqualTo("MINOR"); + FieldDiffs.Diff diff = issue.diffs().get("severity"); + assertThat(diff.oldValue()).isEqualTo("BLOCKER"); + assertThat(diff.newValue()).isEqualTo("MINOR"); + } + + @Test + public void should_not_change_severity() throws Exception { + issue.setSeverity("MINOR"); + boolean updated = updater.setSeverity(issue, "MINOR", context); + assertThat(updated).isFalse(); + assertThat(issue.diffs()).isNull(); + } + + @Test + public void should_not_revert_manual_severity() throws Exception { + issue.setSeverity("MINOR").setManualSeverity(true); + try { + updater.setSeverity(issue, "MAJOR", context); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("Severity can't be changed"); + } + } + + @Test + public void should_set_manual_severity() throws Exception { + issue.setSeverity("BLOCKER"); + boolean updated = updater.setManualSeverity(issue, "MINOR", context); + + assertThat(updated).isTrue(); + assertThat(issue.severity()).isEqualTo("MINOR"); + assertThat(issue.manualSeverity()).isTrue(); + FieldDiffs.Diff diff = issue.diffs().get("severity"); + assertThat(diff.oldValue()).isEqualTo("BLOCKER"); + assertThat(diff.newValue()).isEqualTo("MINOR"); + } + + @Test + public void should_not_change_manual_severity() throws Exception { + issue.setSeverity("MINOR").setManualSeverity(true); + boolean updated = updater.setManualSeverity(issue, "MINOR", context); + assertThat(updated).isFalse(); + assertThat(issue.diffs()).isNull(); + } + + @Test + public void should_set_line() throws Exception { + boolean updated = updater.setLine(issue, 123); + assertThat(updated).isTrue(); + assertThat(issue.line()).isEqualTo(123); + + // do not save change + assertThat(issue.diffs()).isNull(); + } + + @Test + public void should_not_change_line() throws Exception { + issue.setLine(123); + boolean updated = updater.setLine(issue, 123); + assertThat(updated).isFalse(); + assertThat(issue.line()).isEqualTo(123); + assertThat(issue.diffs()).isNull(); + } + + @Test + public void should_set_resolution() throws Exception { + boolean updated = updater.setResolution(issue, "OPEN", context); + assertThat(updated).isTrue(); + assertThat(issue.resolution()).isEqualTo("OPEN"); + + FieldDiffs.Diff diff = issue.diffs().get("resolution"); + assertThat(diff.oldValue()).isNull(); + assertThat(diff.newValue()).isEqualTo("OPEN"); + } + + @Test + public void should_not_change_resolution() throws Exception { + issue.setResolution("FIXED"); + boolean updated = updater.setResolution(issue, "FIXED", context); + assertThat(updated).isFalse(); + assertThat(issue.resolution()).isEqualTo("FIXED"); + assertThat(issue.diffs()).isNull(); + } + + @Test + public void should_set_status() throws Exception { + boolean updated = updater.setStatus(issue, "OPEN", context); + assertThat(updated).isTrue(); + assertThat(issue.status()).isEqualTo("OPEN"); + + FieldDiffs.Diff diff = issue.diffs().get("status"); + assertThat(diff.oldValue()).isNull(); + assertThat(diff.newValue()).isEqualTo("OPEN"); + } + + @Test + public void should_not_change_status() throws Exception { + issue.setStatus("CLOSED"); + boolean updated = updater.setStatus(issue, "CLOSED", context); + assertThat(updated).isFalse(); + assertThat(issue.status()).isEqualTo("CLOSED"); + assertThat(issue.diffs()).isNull(); + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/issue/db/ChangeDtoConverterTest.java b/sonar-core/src/test/java/org/sonar/core/issue/db/ChangeDtoConverterTest.java new file mode 100644 index 00000000000..dec00ee6cf9 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/issue/db/ChangeDtoConverterTest.java @@ -0,0 +1,78 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.core.issue.db; + +import org.junit.Test; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.FieldDiffs; +import org.sonar.core.issue.IssueChangeContext; +import org.sonar.core.issue.IssueComment; + +import java.util.Date; +import java.util.List; + +import static org.fest.assertions.Assertions.assertThat; + +public class ChangeDtoConverterTest { + @Test + public void testToChangeDtos() throws Exception { + IssueComment comment = IssueComment.create("emmerik", "the comment"); + + IssueChangeDto dto = ChangeDtoConverter.commentToDto("ABCDE", comment); + + assertThat(dto.getChangeData()).isEqualTo("the comment"); + assertThat(dto.getChangeType()).isEqualTo("comment"); + assertThat(dto.getCreatedAt()).isNotNull(); + assertThat(dto.getUpdatedAt()).isNotNull(); + assertThat(dto.getIssueKey()).isEqualTo("ABCDE"); + assertThat(dto.getUserLogin()).isEqualTo("emmerik"); + } + + @Test + public void testToDiffsDtos() throws Exception { + FieldDiffs diffs = new FieldDiffs(); + diffs.setDiff("severity", "INFO", "BLOCKER"); + diffs.setUserLogin("emmerik"); + + IssueChangeDto dto = ChangeDtoConverter.diffsToDto("ABCDE", diffs); + + assertThat(dto.getChangeData()).isEqualTo("severity=INFO|BLOCKER"); + assertThat(dto.getChangeType()).isEqualTo("diff"); + assertThat(dto.getCreatedAt()).isNotNull(); + assertThat(dto.getUpdatedAt()).isNotNull(); + assertThat(dto.getIssueKey()).isEqualTo("ABCDE"); + assertThat(dto.getUserLogin()).isEqualTo("emmerik"); + } + + @Test + public void test_toChangeDtos() throws Exception { + IssueChangeContext context = IssueChangeContext.createUser(new Date(), "emmerik"); + IssueComment comment = IssueComment.create("emmerik", "the comment"); + DefaultIssue issue = new DefaultIssue(); + issue.setFieldDiff(context, "severity", "INFO", "BLOCKER"); + issue.addComment(comment); + + List<IssueChangeDto> changeDtos = ChangeDtoConverter.toChangeDtos(issue); + assertThat(changeDtos).hasSize(2); + assertThat(changeDtos.get(0).getChangeType()).isEqualTo("comment"); + assertThat(changeDtos.get(1).getChangeType()).isEqualTo("diff"); + + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueChangeDaoTest.java b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueChangeDaoTest.java index 437ec98973a..c3c23a7c4a1 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueChangeDaoTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueChangeDaoTest.java @@ -21,12 +21,10 @@ package org.sonar.core.issue.db; import org.junit.Before; import org.junit.Test; -import org.sonar.core.issue.db.IssueChangeDao; -import org.sonar.core.issue.db.IssueChangeDto; import org.sonar.core.persistence.AbstractDaoTestCase; -import java.util.Collection; import java.util.Date; +import java.util.List; import static org.fest.assertions.Assertions.assertThat; @@ -40,46 +38,27 @@ public class IssueChangeDaoTest extends AbstractDaoTestCase { } @Test - public void should_insert() { - setupData("insert"); - - IssueChangeDto dto = new IssueChangeDto(); - dto.setIssueKey("100"); - dto.setUserLogin("arthur"); - dto.setChangeType("type"); - dto.setChangeData("data"); - dto.setMessage("some message"); - - Date today = new Date(); - dto.setCreatedAt(today); - dto.setUpdatedAt(today); - - dao.insert(dto); - - checkTables("insert", new String[]{"id", "created_at", "updated_at"}, "issue_changes"); - } - - @Test - public void should_find_by_id() { + public void should_select_by_id() { setupData("shared"); - IssueChangeDto dto = dao.findById(100L); + IssueChangeDto dto = dao.selectById(100L); assertThat(dto.getId()).isEqualTo(100L); - assertThat(dto.getIssueKey()).isEqualTo("100"); + assertThat(dto.getIssueKey()).isEqualTo("1000"); assertThat(dto.getUserLogin()).isEqualTo("arthur"); - assertThat(dto.getChangeType()).isEqualTo("type"); - assertThat(dto.getChangeData()).isEqualTo("data"); - assertThat(dto.getMessage()).isEqualTo("some message"); - assertThat(dto.getCreatedAt()).isNull(); - assertThat(dto.getUpdatedAt()).isNull(); + assertThat(dto.getChangeType()).isEqualTo("comment"); + assertThat(dto.getChangeData()).isEqualTo("this is a comment"); + assertThat(dto.getCreatedAt()).isNotNull(); + assertThat(dto.getUpdatedAt()).isNotNull(); } @Test public void should_select_by_issue() { setupData("shared"); - Collection<IssueChangeDto> dtoList = dao.selectByIssue("100"); - assertThat(dtoList).hasSize(2); + List<IssueChangeDto> ordered = dao.selectByIssue("1000"); + assertThat(ordered).hasSize(2); + assertThat(ordered.get(0).getId()).isEqualTo(101); + assertThat(ordered.get(1).getId()).isEqualTo(100); } } diff --git a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDaoTest.java b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDaoTest.java index e988b9c2bd9..26975e3251e 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDaoTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDaoTest.java @@ -45,54 +45,6 @@ public class IssueDaoTest extends AbstractDaoTestCase { } @Test - public void should_insert() { - setupData("insert"); - - IssueDto issueDto = new IssueDto(); - issueDto.setKey("100"); - issueDto.setResourceId(400); - issueDto.setRuleId(12); - issueDto.setSeverity("BLOCKER"); - issueDto.setLine(200); - issueDto.setStatus("OPEN"); - issueDto.setAssignee("user"); - issueDto.setDescription("the description"); - issueDto.setCost(10.0); - issueDto.setChecksum("checksum"); - issueDto.setAuthorLogin("arthur"); - - Date today = new Date(); - issueDto.setCreatedAt(today); - issueDto.setUpdatedAt(today); - issueDto.setClosedAt(today); - - dao.insert(issueDto); - - checkTables("insert", new String[]{"id", "created_at", "updated_at", "closed_at"}, "issues"); - } - - @Test - public void update() { - setupData("shared", "update"); - Collection<IssueDto> issues = newArrayList(dao.selectById(100L)); - IssueDto issue = issues.iterator().next(); - issue.setLine(1000); - issue.setResolution("NEW_RESOLUTION"); - issue.setStatus("NEW_STATUS"); - issue.setSeverity("NEW_SEV"); - issue.setAssignee("new_user"); - issue.setManualSeverity(true); - issue.setManualIssue(false); - issue.setCreatedAt(DateUtils.parseDate("2012-05-18")); - issue.setUpdatedAt(DateUtils.parseDate("2012-07-01")); - issue.setAttributes("big=bang"); - - dao.update(issues); - - checkTables("update", "issues"); - } - - @Test public void should_select_by_id() { setupData("shared", "should_select_by_id"); IssueDto issue = dao.selectById(100L); diff --git a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueStorageTest.java b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueStorageTest.java new file mode 100644 index 00000000000..c49b404fbbf --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueStorageTest.java @@ -0,0 +1,121 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.core.issue.db; + +import org.junit.Ignore; +import org.junit.Test; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.RuleFinder; +import org.sonar.api.rules.RuleQuery; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.IssueChangeContext; +import org.sonar.core.issue.IssueComment; +import org.sonar.core.persistence.AbstractDaoTestCase; +import org.sonar.core.persistence.MyBatis; + +import java.util.Collection; +import java.util.Date; + +public class IssueStorageTest extends AbstractDaoTestCase { + @Test + public void should_insert_new_issues() throws Exception { + FakeSaver saver = new FakeSaver(getMyBatis(), new FakeRuleFinder()); + + IssueChangeContext context = IssueChangeContext.createUser(new Date(), "emmerik"); + IssueComment comment = IssueComment.create("emmerik", "the comment"); + // override generated key + comment.setKey("FGHIJ"); + + DefaultIssue issue = new DefaultIssue(); + issue.setKey("ABCDE"); + issue.setRuleKey(RuleKey.of("squid", "AvoidCycle")); + issue.setLine(5000); + issue.setNew(true); + issue.setFieldDiff(context, "severity", "INFO", "BLOCKER"); + issue.setUserLogin("emmerik"); + issue.setResolution("OPEN").setStatus("OPEN").setSeverity("BLOCKER"); + issue.setAttribute("foo", "bar"); + issue.addComment(comment); + + saver.save(issue); + + checkTables("should_insert_new_issues", new String[]{"id", "created_at", "updated_at", "closed_at"}, "issues", "issue_changes"); + } + + @Ignore("TODO") + @Test + public void should_update_issues() throws Exception { + + } + + @Ignore("TODO") + @Test + public void should_fail_if_unknown_rule() throws Exception { + + } + + @Ignore("TODO") + @Test + public void should_fail_if_unknown_component() throws Exception { + + } + + static class FakeSaver extends IssueStorage { + protected FakeSaver(MyBatis mybatis, RuleFinder ruleFinder) { + super(mybatis, ruleFinder); + } + + @Override + protected int componentId(DefaultIssue issue) { + return 100; + } + } + + static class FakeRuleFinder implements RuleFinder { + + @Override + public Rule findById(int ruleId) { + return null; + } + + @Override + public Rule findByKey(String repositoryKey, String key) { + return null; + } + + @Override + public Rule findByKey(RuleKey key) { + Rule rule = new Rule().setRepositoryKey(key.repository()).setKey(key.rule()); + rule.setId(200); + return rule; + } + + @Override + public Rule find(RuleQuery query) { + return null; + } + + @Override + public Collection<Rule> findAll(RuleQuery query) { + return null; + } + } +} diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/insert-result.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/insert-result.xml deleted file mode 100644 index cf0a89b4610..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/insert-result.xml +++ /dev/null @@ -1,11 +0,0 @@ -<dataset> - - <issue_changes - issue_key="100" - user_login="arthur" - change_type="type" - change_data="data" - message="some message" - /> - -</dataset> diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/insert.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/insert.xml deleted file mode 100644 index 871dedcb5e9..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/insert.xml +++ /dev/null @@ -1,3 +0,0 @@ -<dataset> - -</dataset> diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/shared.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/shared.xml index e3e9559f8ca..739f6c802df 100644 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/shared.xml +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueChangeDaoTest/shared.xml @@ -2,24 +2,22 @@ <issue_changes id="100" - issue_key="100" + issue_key="1000" user_login="arthur" - change_type="type" - change_data="data" - message="some message" - created_at="[null]" - updated_at="[null]" + change_type="comment" + change_data="this is a comment" + created_at="2013-01-01" + updated_at="2013-01-01" /> <issue_changes id="101" - issue_key="100" + issue_key="1000" user_login="arthur" - change_type="type" - change_data="data" - message="some message" - created_at="[null]" - updated_at="[null]" + change_type="fields" + change_data="severity:MAJOR,BLOCKER" + created_at="2013-02-02" + updated_at="2013-02-02" /> </dataset> diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueDaoTest/insert-result.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueDaoTest/insert-result.xml deleted file mode 100644 index 5868688a27b..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueDaoTest/insert-result.xml +++ /dev/null @@ -1,22 +0,0 @@ -<dataset> - - <issues - kee="100" - resource_id="400" - rule_id="12" - severity="BLOCKER" - manual_severity="[false]" - manual_issue="[false]" - description="the description" - line="200" - cost="10.0" - status="OPEN" - resolution="[null]" - checksum="checksum" - user_login="[null]" - assignee_login="user" - author_login="arthur" - attributes="[null]" - /> - -</dataset> diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueDaoTest/insert.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueDaoTest/insert.xml deleted file mode 100644 index 871dedcb5e9..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueDaoTest/insert.xml +++ /dev/null @@ -1,3 +0,0 @@ -<dataset> - -</dataset> diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueDaoTest/update-result.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueDaoTest/update-result.xml deleted file mode 100644 index 0574a4c9d88..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueDaoTest/update-result.xml +++ /dev/null @@ -1,26 +0,0 @@ -<dataset> - - <issues - id="100" - kee="100" - resource_id="400" - rule_id="500" - severity="NEW_SEV" - manual_severity="[true]" - manual_issue="[false]" - description="[null]" - line="1000" - cost="[null]" - status="NEW_STATUS" - resolution="NEW_RESOLUTION" - checksum="[null]" - user_login="user" - assignee_login="new_user" - author_login="[null]" - attributes="big=bang" - created_at="2012-05-18" - updated_at="2012-07-01" - closed_at="[null]" - /> - -</dataset> diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueDaoTest/update.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueDaoTest/update.xml deleted file mode 100644 index d701d8d68c3..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueDaoTest/update.xml +++ /dev/null @@ -1,26 +0,0 @@ -<dataset> - - <issues - id="100" - kee="100" - resource_id="400" - rule_id="500" - severity="BLOCKER" - manual_severity="[false]" - manual_issue="[false]" - description="[null]" - line="200" - cost="[null]" - status="OPEN" - resolution="RESOLVE" - checksum="[null]" - user_login="user" - assignee_login="user" - author_login="[null]" - attributes="prop1=foo;prop2=bar" - created_at="[null]" - updated_at="[null]" - closed_at="[null]" - /> - -</dataset> diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_insert_new_issues-result.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_insert_new_issues-result.xml new file mode 100644 index 00000000000..24140048336 --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_insert_new_issues-result.xml @@ -0,0 +1,10 @@ +<dataset> + <issues id="1" kee="ABCDE" resolution="OPEN" status="OPEN" severity="BLOCKER" manual_severity="[false]" + assignee_login="[null]" author_login="[null]" checksum="[null]" closed_at="[null]" + cost="[null]" created_at="[null]" description="[null]" line="5000" + manual_issue="[false]" resource_id="100" rule_id="200" updated_at="[null]" user_login="emmerik" + attributes="foo=bar" /> + + <issue_changes id="1" kee="FGHIJ" issue_key="ABCDE" change_type="comment" user_login="emmerik" change_data="the comment" created_at="[null]" updated_at="[null]" /> + <issue_changes id="2" kee="[null]" issue_key="ABCDE" change_type="diff" user_login="emmerik" change_data="severity=INFO|BLOCKER" created_at="[null]" updated_at="[null]" /> +</dataset>
\ No newline at end of file diff --git a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldDeleteResource.xml b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldDeleteResource.xml index f603e893826..1c4b7929732 100644 --- a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldDeleteResource.xml +++ b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeCommandsTest/shouldDeleteResource.xml @@ -27,7 +27,7 @@ <issues id="1" kee="ABCDE" resource_id="1" status="CLOSED" resolution="[null]" created_at="[null]" line="200" severity="BLOCKER" user_login="perceval" assignee_login="arthur" rule_id="500" manual_issue="[true]" manual_severity="[false]" description="[null]"/> - <issue_changes id="1" issue_key="ABCDE" created_at="[null]" updated_at="[null]" user_login="admin" message="abc"/> + <issue_changes id="1" kee="ABDA" issue_key="ABCDE" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="this is a comment"/> <authors id="1" person_id="1" login="tartanpion" created_at="[null]" updated_at="[null]" /> <authors id="2" person_id="1" login="fanfoue" created_at="[null]" updated_at="[null]" /> diff --git a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/shouldDeleteProject.xml b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/shouldDeleteProject.xml index 3df0470a044..611e020fda0 100644 --- a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/shouldDeleteProject.xml +++ b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/shouldDeleteProject.xml @@ -39,7 +39,7 @@ <issues id="2" kee="ABCDF" resource_id="1" status="CLOSED" resolution="[null]" created_at="[null]" line="200" severity="BLOCKER" user_login="perceval" assignee_login="arthur" rule_id="500" manual_issue="[true]" manual_severity="[false]" description="[null]"/> - <issue_changes id="1" issue_key="ABCDF" created_at="[null]" updated_at="[null]" user_login="admin" message="abc"/> + <issue_changes id="1" kee="[null]" issue_key="ABCDF" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> <!-- modules --> <projects id="2" enabled="[true]" root_id="1" |