]> source.dussan.org Git - sonarqube.git/commitdiff
'Days since last commit' metric in platform out of the box - SONAR-6353 200/head
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Tue, 7 Apr 2015 13:25:03 +0000 (15:25 +0200)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Wed, 8 Apr 2015 14:27:20 +0000 (16:27 +0200)
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistNumberOfDaysSinceLastCommitStep.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/source/index/SourceLineIndex.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistDuplicationMeasuresStepTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistNumberOfDaysSinceLastCommitStepTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/source/index/SourceLineIndexTest.java
server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistNumberOfDaysSinceLastCommitStepTest/insert-from-index-result.xml [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistNumberOfDaysSinceLastCommitStepTest/insert-from-report-result.xml [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java

index bfba1b718d089d78227b384972babd8643347e4a..793374b6f026138bb7273deb1f520ff6cd53d8c2 100644 (file)
@@ -41,6 +41,7 @@ public class ComputationSteps {
       ParseReportStep.class,
 
       // Persist data
+      PersistNumberOfDaysSinceLastCommitStep.class,
       PersistMeasuresStep.class,
       PersistIssuesStep.class,
       PersistComponentLinksStep.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistNumberOfDaysSinceLastCommitStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistNumberOfDaysSinceLastCommitStep.java
new file mode 100644 (file)
index 0000000..68cecbd
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.server.computation.step;
+
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.utils.System2;
+import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.batch.protocol.output.BatchReportReader;
+import org.sonar.core.measure.db.MeasureDto;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.server.computation.ComputationContext;
+import org.sonar.server.computation.measure.MetricCache;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.source.index.SourceLineIndex;
+
+import javax.annotation.Nullable;
+
+import static com.google.common.base.Preconditions.checkState;
+
+public class PersistNumberOfDaysSinceLastCommitStep implements ComputationStep {
+
+  private static final long MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24L;
+
+  private final DbClient dbClient;
+  private final SourceLineIndex sourceLineIndex;
+  private final MetricCache metricCache;
+  private final System2 system;
+
+  private long lastCommitTimestamp = 0L;
+
+  public PersistNumberOfDaysSinceLastCommitStep(System2 system, DbClient dbClient, SourceLineIndex sourceLineIndex, MetricCache metricCache) {
+    this.dbClient = dbClient;
+    this.sourceLineIndex = sourceLineIndex;
+    this.metricCache = metricCache;
+    this.system = system;
+  }
+
+  @Override
+  public String[] supportedProjectQualifiers() {
+    return new String[] {Qualifiers.PROJECT};
+  }
+
+  @Override
+  public String getDescription() {
+    return "Compute and persist the number of days since last commit";
+  }
+
+  @Override
+  public void execute(ComputationContext context) {
+    int rootComponentRef = context.getReportMetadata().getRootComponentRef();
+    recursivelyProcessComponent(context, rootComponentRef);
+
+    if (lastCommitTimestamp == 0L) {
+      lastCommitTimestamp = lastCommitFromIndex(context.getProject().uuid());
+    }
+
+    persistNumberOfDaysSinceLastCommit(context);
+  }
+
+  private void recursivelyProcessComponent(ComputationContext context, int componentRef) {
+    BatchReportReader reportReader = context.getReportReader();
+    BatchReport.Component component = reportReader.readComponent(componentRef);
+    BatchReport.Scm scm = reportReader.readComponentScm(componentRef);
+    processScm(scm);
+
+    for (Integer childRef : component.getChildRefList()) {
+      recursivelyProcessComponent(context, childRef);
+    }
+  }
+
+  private void processScm(@Nullable BatchReport.Scm scm) {
+    if (scm == null) {
+      return;
+    }
+
+    for (BatchReport.Scm.Changeset changeset : scm.getChangesetList()) {
+      if (changeset.hasDate() && changeset.getDate() > lastCommitTimestamp) {
+        lastCommitTimestamp = changeset.getDate();
+      }
+    }
+  }
+
+  private long lastCommitFromIndex(String projectUuid) {
+    return sourceLineIndex.lastCommitedDateOnProject(projectUuid).getTime();
+  }
+
+  private void persistNumberOfDaysSinceLastCommit(ComputationContext context) {
+    checkState(lastCommitTimestamp != 0, "The last commit time should exist");
+
+    long numberOfDaysSinceLastCommit = (system.now() - lastCommitTimestamp) / MILLISECONDS_PER_DAY;
+    DbSession dbSession = dbClient.openSession(true);
+    try {
+      dbClient.measureDao().insert(dbSession, new MeasureDto()
+        .setValue((double) numberOfDaysSinceLastCommit)
+        .setMetricId(metricCache.get(CoreMetrics.DAYS_SINCE_LAST_COMMIT_KEY).getId())
+        .setSnapshotId(context.getReportMetadata().getSnapshotId()));
+      dbSession.commit();
+    } finally {
+      MyBatis.closeQuietly(dbSession);
+    }
+  }
+}
index 51b0c142e9da79e9601ba0064dedcbc3c7d5489a..78bba2af156b09e3cef810c38599aa6d4b376c3a 100644 (file)
@@ -28,6 +28,7 @@ import org.sonar.server.es.BaseIndex;
 import org.sonar.server.es.EsClient;
 import org.sonar.server.exceptions.NotFoundException;
 
+import java.util.Date;
 import java.util.List;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -112,4 +113,21 @@ public class SourceLineIndex extends BaseIndex {
     }
     throw new NotFoundException(String.format("No source found on line %s for file '%s'", line, fileUuid));
   }
+
+  public Date lastCommitedDateOnProject(String projectUuid) {
+    SearchRequestBuilder request = getClient().prepareSearch(SourceLineIndexDefinition.INDEX)
+      .setTypes(SourceLineIndexDefinition.TYPE)
+      .setSize(1)
+      .setQuery(QueryBuilders.boolQuery()
+        .must(QueryBuilders.termQuery(SourceLineIndexDefinition.FIELD_PROJECT_UUID, projectUuid)))
+      .setFetchSource(new String[]{SourceLineIndexDefinition.FIELD_SCM_DATE}, null)
+      .addSort(SourceLineIndexDefinition.FIELD_SCM_DATE, SortOrder.DESC);
+
+    SearchHit[] result = request.get().getHits().getHits();
+    if (result.length > 0) {
+      return new SourceLineDoc(result[0].sourceAsMap()).scmDate();
+    }
+
+    throw new NotFoundException(String.format("No source found on project '%s'", projectUuid));
+  }
 }
index a795572cf348b5748d5ad3e6574c31f6dfc99952..479b64a49ddd8d228030bbaa340fb355e6848333 100644 (file)
@@ -46,12 +46,13 @@ public class ComputationStepsTest {
       mock(PersistComponentLinksStep.class),
       mock(PersistMeasuresStep.class),
       mock(PersistEventsStep.class),
-      mock(PersistDuplicationMeasuresStep.class)
+      mock(PersistDuplicationMeasuresStep.class),
+      mock(PersistNumberOfDaysSinceLastCommitStep.class)
       );
 
-    assertThat(registry.orderedSteps()).hasSize(15);
+    assertThat(registry.orderedSteps()).hasSize(16);
     assertThat(registry.orderedSteps().get(0)).isInstanceOf(ParseReportStep.class);
-    assertThat(registry.orderedSteps().get(14)).isInstanceOf(SendIssueNotificationsStep.class);
+    assertThat(registry.orderedSteps().get(15)).isInstanceOf(SendIssueNotificationsStep.class);
   }
 
   @Test
index e5037e303e81781abb1f3939e3b8e49fbbe242b7..a482a0dd5324d4d4d82412b9718ff6b705f97abc 100644 (file)
@@ -47,7 +47,8 @@ import static com.google.common.collect.Lists.newArrayList;
 import static org.assertj.core.api.Assertions.assertThat;
 
 @Category(DbTests.class)
-public class PersistDuplicationMeasuresStepTest extends BaseStepTest {
+public class
+  PersistDuplicationMeasuresStepTest extends BaseStepTest {
 
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistNumberOfDaysSinceLastCommitStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistNumberOfDaysSinceLastCommitStepTest.java
new file mode 100644 (file)
index 0000000..7bf6a9a
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.server.computation.step;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.batch.protocol.Constants;
+import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.batch.protocol.output.BatchReportReader;
+import org.sonar.batch.protocol.output.BatchReportWriter;
+import org.sonar.core.measure.db.MetricDto;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.server.component.ComponentTesting;
+import org.sonar.server.computation.ComputationContext;
+import org.sonar.server.computation.measure.MetricCache;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.measure.persistence.MeasureDao;
+import org.sonar.server.source.index.SourceLineIndex;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Date;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PersistNumberOfDaysSinceLastCommitStepTest extends BaseStepTest {
+
+  @ClassRule
+  public static DbTester db = new DbTester();
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+  File dir;
+
+  PersistNumberOfDaysSinceLastCommitStep sut;
+
+  DbClient dbClient;
+  SourceLineIndex sourceLineIndex;
+  MetricCache metricCache;
+
+  @Before
+  public void setUp() throws Exception {
+    dbClient = new DbClient(db.database(), db.myBatis(), new MeasureDao());
+    sourceLineIndex = mock(SourceLineIndex.class);
+    metricCache = mock(MetricCache.class);
+    when(metricCache.get(anyString())).thenReturn(new MetricDto().setId(10));
+    dir = temp.newFolder();
+    db.truncateTables();
+
+    sut = new PersistNumberOfDaysSinceLastCommitStep(System2.INSTANCE, dbClient, sourceLineIndex, metricCache);
+  }
+
+  @Override
+  protected ComputationStep step() throws IOException {
+    return sut;
+  }
+
+  @Test
+  public void persist_number_of_days_since_last_commit_from_report() throws Exception {
+    long threeDaysAgo = DateUtils.addDays(new Date(), -3).getTime();
+    BatchReportWriter reportWriter = initReportWithProjectAndFile();
+    reportWriter.writeComponentScm(
+      BatchReport.Scm.newBuilder()
+        .setComponentRef(2)
+        .addChangeset(
+          BatchReport.Scm.Changeset.newBuilder()
+            .setDate(threeDaysAgo)
+        )
+        .build()
+      );
+    ComputationContext context = new ComputationContext(new BatchReportReader(dir), ComponentTesting.newProjectDto("project-uuid"));
+
+    sut.execute(context);
+
+    db.assertDbUnit(getClass(), "insert-from-report-result.xml", new String[] {"id"}, "project_measures");
+  }
+
+  @Test
+  public void persist_number_of_days_since_last_commit_from_index() throws Exception {
+    Date sixDaysAgo = DateUtils.addDays(new Date(), -6);
+    when(sourceLineIndex.lastCommitedDateOnProject("project-uuid")).thenReturn(sixDaysAgo);
+    initReportWithProjectAndFile();
+    ComputationContext context = new ComputationContext(new BatchReportReader(dir), ComponentTesting.newProjectDto("project-uuid"));
+
+    sut.execute(context);
+
+    db.assertDbUnit(getClass(), "insert-from-index-result.xml", new String[] {"id"}, "project_measures");
+  }
+
+  private BatchReportWriter initReportWithProjectAndFile() throws IOException {
+    BatchReportWriter writer = new BatchReportWriter(dir);
+    writer.writeMetadata(BatchReport.Metadata.newBuilder()
+      .setRootComponentRef(1)
+      .setSnapshotId(1000)
+      .build());
+
+    writer.writeComponent(BatchReport.Component.newBuilder()
+      .setRef(1)
+      .setType(Constants.ComponentType.PROJECT)
+      .setKey("project-key")
+      .setUuid("project-uuid")
+      .setSnapshotId(10L)
+      .addChildRef(2)
+      .build());
+    writer.writeComponent(BatchReport.Component.newBuilder()
+      .setRef(2)
+      .setType(Constants.ComponentType.FILE)
+      .setSnapshotId(11L)
+      .build());
+
+    return writer;
+  }
+}
index 288148c98c3ee8c1f4a167db3d7193480847f03d..5a27cd939df57d5833d202f11aab20cf801c1b9c 100644 (file)
@@ -23,9 +23,12 @@ import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.sonar.api.config.Settings;
+import org.sonar.api.utils.DateUtils;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.NotFoundException;
 
+import java.util.Date;
+
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class SourceLineIndexTest {
@@ -109,4 +112,24 @@ public class SourceLineIndexTest {
       assertThat(e).isInstanceOf(NotFoundException.class).hasMessage("No source found on line 1 for file 'file1'");
     }
   }
+
+  @Test
+  public void last_commit_of_the_whole_project() throws Exception {
+    Date now = new Date();
+    es.putDocuments(SourceLineIndexDefinition.INDEX, SourceLineIndexDefinition.TYPE,
+      new SourceLineDoc()
+        .setProjectUuid("project-uuid")
+        .setScmDate(now)
+        .setLine(25)
+        .setFileUuid("file-uuid"),
+      new SourceLineDoc()
+        .setProjectUuid("project-uuid")
+        .setScmDate(DateUtils.addDays(now, -1))
+        .setLine(24)
+        .setFileUuid("file-uuid"));
+
+    Date returnedDate = index.lastCommitedDateOnProject("project-uuid");
+
+    assertThat(returnedDate).isEqualTo(now);
+  }
 }
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistNumberOfDaysSinceLastCommitStepTest/insert-from-index-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistNumberOfDaysSinceLastCommitStepTest/insert-from-index-result.xml
new file mode 100644 (file)
index 0000000..644a0bc
--- /dev/null
@@ -0,0 +1,7 @@
+<dataset>
+  <project_measures snapshot_id="1000" metric_id="10" value="6.0" project_id="[null]" text_value="[null]"
+                    measure_data="[null]" variation_value_1="[null]" variation_value_2="[null]"
+                    variation_value_3="[null]" variation_value_4="[null]" variation_value_5="[null]"
+                    alert_status="[null]" alert_text="[null]" characteristic_id="[null]" description="[null]" measure_date="[null]" person_id="[null]"
+                    rule_id="[null]" rule_priority="[null]" rules_category_id="[null]" tendency="[null]" url="[null]" />
+</dataset>
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistNumberOfDaysSinceLastCommitStepTest/insert-from-report-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistNumberOfDaysSinceLastCommitStepTest/insert-from-report-result.xml
new file mode 100644 (file)
index 0000000..1262c51
--- /dev/null
@@ -0,0 +1,7 @@
+<dataset>
+  <project_measures snapshot_id="1000" metric_id="10" value="3.0" project_id="[null]" text_value="[null]"
+                    measure_data="[null]" variation_value_1="[null]" variation_value_2="[null]"
+                    variation_value_3="[null]" variation_value_4="[null]" variation_value_5="[null]"
+                    alert_status="[null]" alert_text="[null]" characteristic_id="[null]" description="[null]" measure_date="[null]" person_id="[null]"
+                    rule_id="[null]" rule_priority="[null]" rules_category_id="[null]" tendency="[null]" url="[null]" />
+</dataset>
\ No newline at end of file
index bebf743c01c83baa84d764cf6d236702d23681d8..3427c5a8461165a464f898d0e335744994fda320 100644 (file)
@@ -2211,6 +2211,18 @@ public final class CoreMetrics {
     .setHidden(true)
     .create();
 
+  /**
+   * @since 5.2 – was computed in the dev cockpit plugin previously
+   */
+  public static final String DAYS_SINCE_LAST_COMMIT_KEY = "days_since_last_commit";
+
+  /**
+   * @since 5.2 – was computed in the dev cockpit plugin previously
+   */
+  public static final Metric DAYS_SINCE_LAST_COMMIT = new Metric.Builder(DAYS_SINCE_LAST_COMMIT_KEY, "Days since last commit", Metric.ValueType.INT)
+    .setDomain(CoreMetrics.DOMAIN_SCM)
+    .create();
+
   private static final List<Metric> METRICS;
 
   static {