]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3526 Display the author (SCM account) of an issue if exists
authorJulien HENRY <julien.henry@sonarsource.com>
Tue, 9 Jul 2013 16:39:47 +0000 (18:39 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Tue, 9 Jul 2013 16:40:18 +0000 (18:40 +0200)
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java
plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java
sonar-server/src/main/webapp/WEB-INF/app/views/issue/_issue.html.erb

index b433614c6300ccff32b77beb818969278e77ed38..57b461727bcac70c490e40f6dffb2de05808818e 100644 (file)
@@ -29,24 +29,41 @@ import com.google.common.collect.Multimap;
 import org.sonar.api.BatchExtension;
 import org.sonar.api.batch.SonarIndex;
 import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.FileLinesContext;
+import org.sonar.api.measures.FileLinesContextFactory;
 import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.batch.scan.LastSnapshots;
 import org.sonar.core.issue.db.IssueDto;
-import org.sonar.plugins.core.issue.tracking.*;
+import org.sonar.plugins.core.issue.tracking.HashedSequence;
+import org.sonar.plugins.core.issue.tracking.HashedSequenceComparator;
+import org.sonar.plugins.core.issue.tracking.RollingHashSequence;
+import org.sonar.plugins.core.issue.tracking.RollingHashSequenceComparator;
+import org.sonar.plugins.core.issue.tracking.SourceChecksum;
+import org.sonar.plugins.core.issue.tracking.StringText;
+import org.sonar.plugins.core.issue.tracking.StringTextComparator;
+import org.sonar.plugins.core.issue.tracking.ViolationTrackingBlocksRecognizer;
 
 import javax.annotation.Nullable;
 
-import java.util.*;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
 
 public class IssueTracking implements BatchExtension {
 
   private final LastSnapshots lastSnapshots;
   private final SonarIndex index;
+  private FileLinesContextFactory fileLineContextFactory;
 
-  public IssueTracking(LastSnapshots lastSnapshots, SonarIndex index) {
+  public IssueTracking(LastSnapshots lastSnapshots, SonarIndex index, FileLinesContextFactory fileLineContextFactory) {
     this.lastSnapshots = lastSnapshots;
     this.index = index;
+    this.fileLineContextFactory = fileLineContextFactory;
   }
 
   public IssueTrackingResult track(Resource resource, Collection<IssueDto> dbIssues, Collection<DefaultIssue> newIssues) {
@@ -55,11 +72,29 @@ public class IssueTracking implements BatchExtension {
     String source = index.getSource(resource);
     setChecksumOnNewIssues(newIssues, source);
 
+    setScmAuthorOnNewIssues(resource, newIssues);
+
     // Map new issues with old ones
     mapIssues(newIssues, dbIssues, source, resource, result);
     return result;
   }
 
+  @VisibleForTesting
+  void setScmAuthorOnNewIssues(Resource resource, Collection<DefaultIssue> newIssues) {
+    if (ResourceUtils.isFile(resource)) {
+      FileLinesContext fileLineContext = fileLineContextFactory.createFor(resource);
+      for (DefaultIssue issue : newIssues) {
+        if (issue.line() != null) {
+          // TODO When issue is on line 0 then who is the author?
+          String scmAuthorLogin = fileLineContext.getStringValue(CoreMetrics.SCM_AUTHORS_BY_LINE_KEY, issue.line());
+          if (scmAuthorLogin != null) {
+            issue.setAuthorLogin(scmAuthorLogin);
+          }
+        }
+      }
+    }
+  }
+
   private void setChecksumOnNewIssues(Collection<DefaultIssue> issues, String source) {
     List<String> checksums = SourceChecksum.lineChecksumsOfFile(source);
     for (DefaultIssue issue : issues) {
@@ -102,9 +137,9 @@ public class IssueTracking implements BatchExtension {
     for (DefaultIssue newIssue : newIssues) {
       if (isNotAlreadyMapped(newIssue, result)) {
         mapIssue(
-          newIssue,
-          findLastIssueWithSameLineAndChecksum(newIssue, result.unmatchedForRule(newIssue.ruleKey())),
-          result);
+            newIssue,
+            findLastIssueWithSameLineAndChecksum(newIssue, result.unmatchedForRule(newIssue.ruleKey())),
+            result);
       }
     }
   }
@@ -179,9 +214,9 @@ public class IssueTracking implements BatchExtension {
     for (DefaultIssue newIssue : newIssues) {
       if (isNotAlreadyMapped(newIssue, result)) {
         mapIssue(
-          newIssue,
-          findLastIssueWithSameChecksumAndMessage(newIssue, result.unmatchedForRule(newIssue.ruleKey())),
-          result);
+            newIssue,
+            findLastIssueWithSameChecksumAndMessage(newIssue, result.unmatchedForRule(newIssue.ruleKey())),
+            result);
       }
     }
 
@@ -189,9 +224,9 @@ public class IssueTracking implements BatchExtension {
     for (DefaultIssue newIssue : newIssues) {
       if (isNotAlreadyMapped(newIssue, result)) {
         mapIssue(
-          newIssue,
-          findLastIssueWithSameLineAndMessage(newIssue, result.unmatchedForRule(newIssue.ruleKey())),
-          result);
+            newIssue,
+            findLastIssueWithSameLineAndMessage(newIssue, result.unmatchedForRule(newIssue.ruleKey())),
+            result);
       }
     }
 
@@ -200,9 +235,9 @@ public class IssueTracking implements BatchExtension {
     for (DefaultIssue newIssue : newIssues) {
       if (isNotAlreadyMapped(newIssue, result)) {
         mapIssue(
-          newIssue,
-          findLastIssueWithSameChecksum(newIssue, result.unmatchedForRule(newIssue.ruleKey())),
-          result);
+            newIssue,
+            findLastIssueWithSameChecksum(newIssue, result.unmatchedForRule(newIssue.ruleKey())),
+            result);
       }
     }
   }
index aca30d943bae5e13bf28372ef5cac3ac1460c4b1..c5a4b3ef0ab1072e5586f04a86195d8f143f4afc 100644 (file)
@@ -505,6 +505,7 @@ issue.manual.missing_rule=Missing rule
 issue.manual.no_rules.admin=Manual rules must be defined before manual issues can be created.
 issue.manual.no_rules.non_admin=At least one manual rule must exist before manual issues can be created. Please contact your project administrator.
 issue.reported_by=Reported by
+issue.authorLogin=Author:
 issue.component_deleted=Removed
 issue.changelog.changed_to={0} changed to {1}
 issue.changelog.was=was {0}
index cd2867c0d2f62a0580353190ef15edebf5ea2db3..349229e05b651853169f2968e916ff0e3585f1d8 100644 (file)
@@ -26,8 +26,11 @@ import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.measures.FileLinesContext;
+import org.sonar.api.measures.FileLinesContextFactory;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.Scopes;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.batch.scan.LastSnapshots;
 import org.sonar.core.issue.db.IssueDto;
@@ -46,13 +49,15 @@ public class IssueTrackingTest {
   Resource project;
   LastSnapshots lastSnapshots;
   long violationId = 0;
+  private FileLinesContextFactory fileLineContextFactory;
 
   @Before
   public void before() {
     lastSnapshots = mock(LastSnapshots.class);
 
     project = mock(Project.class);
-    tracking = new IssueTracking(lastSnapshots, null);
+    fileLineContextFactory = mock(FileLinesContextFactory.class);
+    tracking = new IssueTracking(lastSnapshots, null, fileLineContextFactory);
   }
 
   @Test
@@ -267,9 +272,9 @@ public class IssueTrackingTest {
 
     IssueTrackingResult result = new IssueTrackingResult();
     tracking.mapIssues(
-      Arrays.asList(newIssue1, newIssue2, newIssue3),
-      Arrays.asList(referenceIssue1),
-      source, project, result);
+        Arrays.asList(newIssue1, newIssue2, newIssue3),
+        Arrays.asList(referenceIssue1),
+        source, project, result);
 
     assertThat(result.matching(newIssue1)).isNull();
     assertThat(result.matching(newIssue2)).isSameAs(referenceIssue1);
@@ -283,24 +288,27 @@ public class IssueTrackingTest {
 
     IssueDto referenceIssue1 = newReferenceIssue("Avoid unused local variables such as 'j'.", 6, "squid", "AvoidCycle", "63c11570fc0a76434156be5f8138fa03");
     IssueDto referenceIssue2 = newReferenceIssue("Avoid unused private methods such as 'myMethod()'.", 13, "squid", "NullDeref", "ef23288705d1ef1e512448ace287586e");
-    IssueDto referenceIssue3 = newReferenceIssue("Method 'avoidUtilityClass' is not designed for extension - needs to be abstract, final or empty.", 9, "pmd", "UnusedLocalVariable", "ed5cdd046fda82727d6fedd1d8e3a310");
+    IssueDto referenceIssue3 = newReferenceIssue("Method 'avoidUtilityClass' is not designed for extension - needs to be abstract, final or empty.", 9, "pmd",
+        "UnusedLocalVariable", "ed5cdd046fda82727d6fedd1d8e3a310");
 
     // New issue
     DefaultIssue newIssue1 = newDefaultIssue("Avoid unused local variables such as 'msg'.", 18, RuleKey.of("squid", "AvoidCycle"), "a24254126be2bf1a9b9a8db43f633733");
     // Same as referenceIssue2
     DefaultIssue newIssue2 = newDefaultIssue("Avoid unused private methods such as 'myMethod()'.", 13, RuleKey.of("squid", "NullDeref"), "ef23288705d1ef1e512448ace287586e");
     // Same as referenceIssue3
-    DefaultIssue newIssue3 = newDefaultIssue("Method 'avoidUtilityClass' is not designed for extension - needs to be abstract, final or empty.", 9, RuleKey.of("pmd", "UnusedLocalVariable"), "ed5cdd046fda82727d6fedd1d8e3a310");
+    DefaultIssue newIssue3 = newDefaultIssue("Method 'avoidUtilityClass' is not designed for extension - needs to be abstract, final or empty.", 9,
+        RuleKey.of("pmd", "UnusedLocalVariable"), "ed5cdd046fda82727d6fedd1d8e3a310");
     // New issue
-    DefaultIssue newIssue4 = newDefaultIssue("Method 'newViolation' is not designed for extension - needs to be abstract, final or empty.", 17, RuleKey.of("pmd", "UnusedLocalVariable"), "7d58ac9040c27e4ca2f11a0269e251e2");
+    DefaultIssue newIssue4 = newDefaultIssue("Method 'newViolation' is not designed for extension - needs to be abstract, final or empty.", 17,
+        RuleKey.of("pmd", "UnusedLocalVariable"), "7d58ac9040c27e4ca2f11a0269e251e2");
     // Same as referenceIssue1
     DefaultIssue newIssue5 = newDefaultIssue("Avoid unused local variables such as 'j'.", 6, RuleKey.of("squid", "AvoidCycle"), "4432a2675ec3e1620daefe38386b51ef");
 
     IssueTrackingResult result = new IssueTrackingResult();
     tracking.mapIssues(
-      Arrays.asList(newIssue1, newIssue2, newIssue3, newIssue4, newIssue5),
-      Arrays.asList(referenceIssue1, referenceIssue2, referenceIssue3),
-      source, project, result);
+        Arrays.asList(newIssue1, newIssue2, newIssue3, newIssue4, newIssue5),
+        Arrays.asList(referenceIssue1, referenceIssue2, referenceIssue3),
+        source, project, result);
 
     assertThat(result.matching(newIssue1)).isNull();
     assertThat(result.matching(newIssue2)).isSameAs(referenceIssue2);
@@ -309,6 +317,59 @@ public class IssueTrackingTest {
     assertThat(result.matching(newIssue5)).isSameAs(referenceIssue1);
   }
 
+  @Test
+  public void should_update_scm_author_on_new_issues() {
+    Resource resource = mock(Resource.class);
+    FileLinesContext context = mock(FileLinesContext.class);
+    when(context.getStringValue("authors_by_line", 1)).thenReturn("julien");
+    when(fileLineContextFactory.createFor(resource)).thenReturn(context);
+
+    DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
+
+    when(resource.getScope()).thenReturn(Scopes.FILE);
+    tracking.setScmAuthorOnNewIssues(resource, Arrays.asList(newIssue));
+    assertThat(newIssue.authorLogin()).isEqualTo("julien");
+  }
+
+  @Test
+  public void should_not_update_scm_author_when_resource_is_not_a_file() {
+    Resource resource = mock(Resource.class);
+
+    DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
+
+    when(resource.getScope()).thenReturn(Scopes.PROJECT);
+    tracking.setScmAuthorOnNewIssues(resource, Arrays.asList(newIssue));
+    assertThat(newIssue.authorLogin()).isNull();
+  }
+
+  @Test
+  public void should_not_update_scm_author_when_issue_is_on_line_0() {
+    Resource resource = mock(Resource.class);
+    FileLinesContext context = mock(FileLinesContext.class);
+    when(context.getStringValue("authors_by_line", 1)).thenReturn("julien");
+    when(fileLineContextFactory.createFor(resource)).thenReturn(context);
+
+    DefaultIssue newIssue = newDefaultIssue("message", null, RuleKey.of("squid", "AvoidCycle"), "checksum1");
+
+    when(resource.getScope()).thenReturn(Scopes.FILE);
+    tracking.setScmAuthorOnNewIssues(resource, Arrays.asList(newIssue));
+    assertThat(newIssue.authorLogin()).isNull();
+  }
+
+  @Test
+  public void should_not_update_scm_author_when_unknow_scm_author() {
+    Resource resource = mock(Resource.class);
+    FileLinesContext context = mock(FileLinesContext.class);
+    when(context.getStringValue("authors_by_line", 1)).thenReturn(null);
+    when(fileLineContextFactory.createFor(resource)).thenReturn(context);
+
+    DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
+
+    when(resource.getScope()).thenReturn(Scopes.FILE);
+    tracking.setScmAuthorOnNewIssues(resource, Arrays.asList(newIssue));
+    assertThat(newIssue.authorLogin()).isNull();
+  }
+
   private static String load(String name) throws IOException {
     return Resources.toString(IssueTrackingTest.class.getResource("IssueTrackingTest/" + name + ".txt"), Charsets.UTF_8);
   }
index ffdf06a369364fce4bda25a527f13ab45b4ed192..ff010384541d99f3f25628cb53317b73fe7be70a 100644 (file)
       <span><%= message('issue.reported_by') -%> <%= @issue_results.user(issue.reporter).name -%></span>
       &nbsp;
     <% end %>
+    <% if issue.authorLogin %>
+      <img src="<%= ApplicationController.root_context -%>/images/sep12.png"/>
+      &nbsp;
+      <span><%= message('issue.authorLogin') -%> <%= issue.authorLogin -%></span>
+      &nbsp;
+    <% end %>
   </div>
 
   <div class="issue-rule rule_detail" style="display: none"></div>
       <% end %>
     </div>
   <% end %>
-</div>
\ No newline at end of file
+</div>