ParseReportStep.class,
// Persist data
+ PersistNumberOfDaysSinceLastCommitStep.class,
PersistMeasuresStep.class,
PersistIssuesStep.class,
PersistComponentLinksStep.class,
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
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;
}
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));
+ }
}
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
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();
--- /dev/null
+/*
+ * 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;
+ }
+}
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 {
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);
+ }
}
--- /dev/null
+<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
--- /dev/null
+<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
.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 {