From: Teryk Bellahsene Date: Tue, 7 Apr 2015 13:25:03 +0000 (+0200) Subject: 'Days since last commit' metric in platform out of the box - SONAR-6353 X-Git-Tag: 5.2-RC1~2338 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=a750f694dc5197ce886ea18a30b4548a89bc40fa;p=sonarqube.git 'Days since last commit' metric in platform out of the box - SONAR-6353 --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java index bfba1b718d0..793374b6f02 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java @@ -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 index 00000000000..68cecbd8858 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistNumberOfDaysSinceLastCommitStep.java @@ -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); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/source/index/SourceLineIndex.java b/server/sonar-server/src/main/java/org/sonar/server/source/index/SourceLineIndex.java index 51b0c142e9d..78bba2af156 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/source/index/SourceLineIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/source/index/SourceLineIndex.java @@ -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)); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java index a795572cf34..479b64a49dd 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java @@ -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 diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistDuplicationMeasuresStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistDuplicationMeasuresStepTest.java index e5037e303e8..a482a0dd532 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistDuplicationMeasuresStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistDuplicationMeasuresStepTest.java @@ -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 index 00000000000..7bf6a9a8561 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistNumberOfDaysSinceLastCommitStepTest.java @@ -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; + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/source/index/SourceLineIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/source/index/SourceLineIndexTest.java index 288148c98c3..5a27cd939df 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/source/index/SourceLineIndexTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/source/index/SourceLineIndexTest.java @@ -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 index 00000000000..644a0bcd53e --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistNumberOfDaysSinceLastCommitStepTest/insert-from-index-result.xml @@ -0,0 +1,7 @@ + + + \ 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 index 00000000000..1262c51c16f --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/step/PersistNumberOfDaysSinceLastCommitStepTest/insert-from-report-result.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java index bebf743c01c..3427c5a8461 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java @@ -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 METRICS; static {