aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java2
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/NewCodePeriodResolver.java190
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java162
-rw-r--r--server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java12
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDao.java6
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDto.java4
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodMapper.java3
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodType.java1
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/newcodeperiod/NewCodePeriodMapper.xml8
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/newcodeperiod/NewCodePeriodDtoTest.java8
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java6
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v80/PopulateNewCodePeriodTable.java243
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v80/PopulateNewCodePeriodTableTest.java212
-rw-r--r--server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v80/PopulateNewCodePeriodTableTest/schema.sql61
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/ListAction.java152
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/SetAction.java17
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/ShowAction.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/package-info.java23
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/setting/ws/list_new_code_period-example.json0
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/ListActionTest.java319
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/SetActionTest.java35
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/ShowActionTest.java12
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/UnsetActionTest.java16
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/settings/DeleteNewCodePeriodRequest.java58
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/settings/ShowNewCodePeriodRequest.java58
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/settings/UpdateNewCodePeriodRequest.java86
-rw-r--r--sonar-ws/src/main/protobuf/ws-newcodeperiods.proto21
27 files changed, 1241 insertions, 478 deletions
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java
index f507563326b..92a6746f3f4 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java
@@ -101,6 +101,7 @@ import org.sonar.ce.task.projectanalysis.measure.MeasureToMeasureDto;
import org.sonar.ce.task.projectanalysis.metric.MetricModule;
import org.sonar.ce.task.projectanalysis.notification.NotificationFactory;
import org.sonar.ce.task.projectanalysis.organization.DefaultOrganizationLoader;
+import org.sonar.ce.task.projectanalysis.period.NewCodePeriodResolver;
import org.sonar.ce.task.projectanalysis.period.PeriodHolderImpl;
import org.sonar.ce.task.projectanalysis.qualitygate.EvaluationResultTextConverterImpl;
import org.sonar.ce.task.projectanalysis.qualitygate.QualityGateHolderImpl;
@@ -285,6 +286,7 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop
BranchPersisterImpl.class,
SiblingsIssuesLoader.class,
SiblingsIssueMerger.class,
+ NewCodePeriodResolver.class,
// filemove
ScoreMatrixDumperImpl.class,
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/NewCodePeriodResolver.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/NewCodePeriodResolver.java
new file mode 100644
index 00000000000..7fee57705c8
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/NewCodePeriodResolver.java
@@ -0,0 +1,190 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.ce.task.projectanalysis.period;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Supplier;
+import javax.annotation.Nullable;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.component.SnapshotQuery;
+import org.sonar.db.event.EventDto;
+import org.sonar.db.newcodeperiod.NewCodePeriodDto;
+import org.sonar.db.newcodeperiod.NewCodePeriodParser;
+import org.sonar.db.newcodeperiod.NewCodePeriodType;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.format;
+import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
+import static org.sonar.db.component.SnapshotQuery.SORT_FIELD.BY_DATE;
+import static org.sonar.db.component.SnapshotQuery.SORT_ORDER.ASC;
+
+public class NewCodePeriodResolver {
+ private static final Logger LOG = Loggers.get(NewCodePeriodResolver.class);
+
+ private final DbClient dbClient;
+
+ public NewCodePeriodResolver(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ public Period resolve(DbSession dbSession, String branchUuid, NewCodePeriodDto newCodePeriodDto, long referenceDate, String projectVersion) {
+ return toPeriod(newCodePeriodDto.getType(), newCodePeriodDto.getValue(), dbSession, projectVersion, branchUuid, referenceDate);
+ }
+
+ private Period toPeriod(NewCodePeriodType type, @Nullable String value, DbSession dbSession, String projectVersion, String rootUuid, long referenceDate) {
+ switch (type) {
+ case NUMBER_OF_DAYS:
+ Integer days = NewCodePeriodParser.parseDays(value);
+ checkNotNullValue(value, type);
+ return resolveByDays(dbSession, rootUuid, days, value, referenceDate);
+ case PREVIOUS_VERSION:
+ return resolveByPreviousVersion(dbSession, rootUuid, projectVersion);
+ case SPECIFIC_ANALYSIS:
+ checkNotNullValue(value, type);
+ return resolveBySpecificAnalysis(dbSession, rootUuid, value);
+ default:
+ throw new IllegalStateException("Unexpected type: " + type);
+ }
+ }
+
+ private Period resolveBySpecificAnalysis(DbSession dbSession, String rootUuid, String value) {
+ SnapshotDto baseline = dbClient.snapshotDao().selectByUuid(dbSession, value)
+ .filter(t -> t.getComponentUuid().equals(rootUuid))
+ .orElseThrow(() -> new IllegalStateException("Analysis '" + value + "' of project '" + rootUuid
+ + "' defined as the baseline does not exist"));
+ LOG.debug("Resolving new code period with a specific analysis");
+ return newPeriod(NewCodePeriodType.SPECIFIC_ANALYSIS, value, Instant.ofEpochMilli(baseline.getCreatedAt()));
+ }
+
+ private Period resolveByPreviousVersion(DbSession dbSession, String projectUuid, String projectVersion) {
+ List<EventDto> versions = dbClient.eventDao().selectVersionsByMostRecentFirst(dbSession, projectUuid);
+ if (versions.isEmpty()) {
+ return findOldestAnalysis(dbSession, projectUuid);
+ }
+
+ String mostRecentVersion = Optional.ofNullable(versions.iterator().next().getName())
+ .orElseThrow(() -> new IllegalStateException("selectVersionsByMostRecentFirst returned a DTO which didn't have a name"));
+
+ if (versions.size() == 1) {
+ return findOldestAnalysis(dbSession, projectUuid);
+ }
+ return resolvePreviousVersion(dbSession, projectVersion, versions, mostRecentVersion);
+ }
+
+ private Period resolveByDays(DbSession dbSession, String rootUuid, Integer days, String value, long referenceDate) {
+ checkPeriodProperty(days > 0, value, "number of days is <= 0");
+ List<SnapshotDto> snapshots = dbClient.snapshotDao().selectAnalysesByQuery(dbSession, createCommonQuery(rootUuid)
+ .setCreatedBefore(referenceDate).setSort(BY_DATE, ASC));
+
+ ensureNotOnFirstAnalysis(!snapshots.isEmpty());
+ Instant targetDate = DateUtils.addDays(Instant.ofEpochMilli(referenceDate), -days);
+ LOG.debug("Resolving new code period by {} days: {}", days, supplierToString(() -> logDate(targetDate)));
+ SnapshotDto snapshot = findNearestSnapshotToTargetDate(snapshots, targetDate);
+ return newPeriod(NewCodePeriodType.NUMBER_OF_DAYS, String.valueOf((int) days), Instant.ofEpochMilli(snapshot.getCreatedAt()));
+ }
+
+ private Period resolvePreviousVersion(DbSession dbSession, String currentVersion, List<EventDto> versions, String mostRecentVersion) {
+ EventDto previousVersion = versions.get(currentVersion.equals(mostRecentVersion) ? 1 : 0);
+ LOG.debug("Resolving new code period by previous version: {}", previousVersion.getName());
+ return newPeriod(dbSession, previousVersion);
+ }
+
+ private Period findOldestAnalysis(DbSession dbSession, String projectUuid) {
+ LOG.debug("Resolving first analysis as new code period as there is only one existing version");
+ Optional<Period> period = dbClient.snapshotDao().selectOldestSnapshot(dbSession, projectUuid)
+ .map(dto -> newPeriod(NewCodePeriodType.PREVIOUS_VERSION, null, Instant.ofEpochMilli(dto.getCreatedAt())));
+ ensureNotOnFirstAnalysis(period.isPresent());
+ return period.get();
+ }
+
+ private Period newPeriod(DbSession dbSession, EventDto previousVersion) {
+ Optional<Period> period = dbClient.snapshotDao().selectByUuid(dbSession, previousVersion.getAnalysisUuid())
+ .map(dto -> newPeriod(NewCodePeriodType.PREVIOUS_VERSION, dto.getProjectVersion(), Instant.ofEpochMilli(dto.getCreatedAt())));
+ if (!period.isPresent()) {
+ throw new IllegalStateException(format("Analysis '%s' for version event '%s' has been deleted",
+ previousVersion.getAnalysisUuid(), previousVersion.getName()));
+ }
+ return period.get();
+ }
+
+ private static Period newPeriod(NewCodePeriodType type, @Nullable String value, Instant instant) {
+ return new Period(type.name(), value, instant.toEpochMilli());
+ }
+
+ private static Object supplierToString(Supplier<String> s) {
+ return new Object() {
+ @Override
+ public String toString() {
+ return s.get();
+ }
+ };
+ }
+
+ private static SnapshotQuery createCommonQuery(String projectUuid) {
+ return new SnapshotQuery().setComponentUuid(projectUuid).setStatus(STATUS_PROCESSED);
+ }
+
+ private static SnapshotDto findNearestSnapshotToTargetDate(List<SnapshotDto> snapshots, Instant targetDate) {
+ // FIXME shouldn't this be the first analysis after targetDate?
+ Duration bestDuration = null;
+ SnapshotDto nearest = null;
+ for (SnapshotDto snapshot : snapshots) {
+ Instant createdAt = Instant.ofEpochMilli(snapshot.getCreatedAt());
+ Duration duration = Duration.between(targetDate, createdAt).abs();
+ if (bestDuration == null || duration.compareTo(bestDuration) <= 0) {
+ bestDuration = duration;
+ nearest = snapshot;
+ }
+ }
+ return nearest;
+ }
+
+ private static void checkPeriodProperty(boolean test, String propertyValue, String testDescription, Object... args) {
+ if (!test) {
+ LOG.debug("Invalid code period '{}': {}", propertyValue, supplierToString(() -> format(testDescription, args)));
+ throw MessageException.of(format("Invalid new code period. '%s' is not one of: " +
+ "integer > 0, date before current analysis j, \"previous_version\", or version string that exists in the project' \n" +
+ "Please contact a project administrator to correct this setting", propertyValue));
+ }
+ }
+
+ private static void ensureNotOnFirstAnalysis(boolean expression) {
+ checkState(expression, "Attempting to resolve period while no analysis exist for project");
+ }
+
+ private static void checkNotNullValue(@Nullable String value, NewCodePeriodType type) {
+ checkNotNull(value, "Value can't be null with type %s", type);
+ }
+
+ private static String logDate(Instant instant) {
+ return DateUtils.formatDate(instant.truncatedTo(ChronoUnit.SECONDS));
+ }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java
index d646d03a213..20cae44c073 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java
@@ -19,39 +19,19 @@
*/
package org.sonar.ce.task.projectanalysis.step;
-import java.time.Duration;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
-import javax.annotation.Nullable;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.MessageException;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
+import org.sonar.ce.task.projectanalysis.period.NewCodePeriodResolver;
import org.sonar.ce.task.projectanalysis.period.Period;
import org.sonar.ce.task.projectanalysis.period.PeriodHolder;
import org.sonar.ce.task.projectanalysis.period.PeriodHolderImpl;
import org.sonar.ce.task.step.ComputationStep;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.component.SnapshotQuery;
-import org.sonar.db.event.EventDto;
import org.sonar.db.newcodeperiod.NewCodePeriodDao;
import org.sonar.db.newcodeperiod.NewCodePeriodDto;
-import org.sonar.db.newcodeperiod.NewCodePeriodParser;
-import org.sonar.db.newcodeperiod.NewCodePeriodType;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import static java.lang.String.format;
-import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
-import static org.sonar.db.component.SnapshotQuery.SORT_FIELD.BY_DATE;
-import static org.sonar.db.component.SnapshotQuery.SORT_ORDER.ASC;
/**
* Populates the {@link PeriodHolder}
@@ -62,21 +42,22 @@ import static org.sonar.db.component.SnapshotQuery.SORT_ORDER.ASC;
* - If a snapshot is found, a period is set to the repository, otherwise fail with MessageException
*/
public class LoadPeriodsStep implements ComputationStep {
- private static final Logger LOG = Loggers.get(LoadPeriodsStep.class);
private final AnalysisMetadataHolder analysisMetadataHolder;
private final NewCodePeriodDao newCodePeriodDao;
private final TreeRootHolder treeRootHolder;
private final PeriodHolderImpl periodsHolder;
private final DbClient dbClient;
+ private final NewCodePeriodResolver resolver;
public LoadPeriodsStep(AnalysisMetadataHolder analysisMetadataHolder, NewCodePeriodDao newCodePeriodDao, TreeRootHolder treeRootHolder,
- PeriodHolderImpl periodsHolder, DbClient dbClient) {
+ PeriodHolderImpl periodsHolder, DbClient dbClient, NewCodePeriodResolver resolver) {
this.analysisMetadataHolder = analysisMetadataHolder;
this.newCodePeriodDao = newCodePeriodDao;
this.treeRootHolder = treeRootHolder;
this.periodsHolder = periodsHolder;
this.dbClient = dbClient;
+ this.resolver = resolver;
}
@Override
@@ -101,8 +82,9 @@ public class LoadPeriodsStep implements ComputationStep {
() -> getProjectSetting(dbSession, projectUuid),
() -> getGlobalSetting(dbSession));
- Period period = dto.map(d -> toPeriod(d.getType(), d.getValue(), dbSession, projectVersion, branchUuid))
- .orElseGet(() -> toPeriod(NewCodePeriodType.PREVIOUS_VERSION, null, dbSession, projectVersion, branchUuid));
+ long analysisDate = analysisMetadataHolder.getAnalysisDate();
+ Period period = dto.map(d -> resolver.resolve(dbSession, branchUuid, d, analysisDate, projectVersion))
+ .orElseGet(() -> resolver.resolve(dbSession, branchUuid, NewCodePeriodDto.defaultInstance(), analysisDate, projectVersion));
periodsHolder.setPeriod(period);
}
}
@@ -129,137 +111,7 @@ public class LoadPeriodsStep implements ComputationStep {
return newCodePeriodDao.selectGlobal(dbSession);
}
- private Period toPeriod(NewCodePeriodType type, @Nullable String value, DbSession dbSession, String analysisProjectVersion, String rootUuid) {
- switch (type) {
- case NUMBER_OF_DAYS:
- Integer days = NewCodePeriodParser.parseDays(value);
- checkNotNullValue(value, type);
- return resolveByDays(dbSession, rootUuid, days, value);
- case PREVIOUS_VERSION:
- return resolveByPreviousVersion(dbSession, rootUuid, analysisProjectVersion);
- case SPECIFIC_ANALYSIS:
- checkNotNullValue(value, type);
- return resolveBySpecificAnalysis(dbSession, rootUuid, value);
- default:
- throw new IllegalStateException("Unexpected type: " + type);
- }
- }
-
private String getProjectBranchUuid() {
return analysisMetadataHolder.getProject().getUuid();
}
-
- private Period resolveBySpecificAnalysis(DbSession dbSession, String rootUuid, String value) {
- SnapshotDto baseline = dbClient.snapshotDao().selectByUuid(dbSession, value)
- .filter(t -> t.getComponentUuid().equals(rootUuid))
- .orElseThrow(() -> new IllegalStateException("Analysis '" + value + "' of project '" + rootUuid
- + "' defined as the baseline does not exist"));
- LOG.debug("Resolving new code period with a specific analysis");
- return newPeriod(NewCodePeriodType.SPECIFIC_ANALYSIS, value, Instant.ofEpochMilli(baseline.getCreatedAt()));
- }
-
- private Period resolveByPreviousVersion(DbSession dbSession, String projectUuid, String analysisProjectVersion) {
- List<EventDto> versions = dbClient.eventDao().selectVersionsByMostRecentFirst(dbSession, projectUuid);
- if (versions.isEmpty()) {
- return findOldestAnalysis(dbSession, projectUuid);
- }
-
- String mostRecentVersion = Optional.ofNullable(versions.iterator().next().getName())
- .orElseThrow(() -> new IllegalStateException("selectVersionsByMostRecentFirst returned a DTO which didn't have a name"));
-
- if (versions.size() == 1) {
- return findOldestAnalysis(dbSession, projectUuid);
- }
- return resolvePreviousVersion(dbSession, analysisProjectVersion, versions, mostRecentVersion);
- }
-
- private Period resolveByDays(DbSession dbSession, String rootUuid, Integer days, String propertyValue) {
- checkPeriodProperty(days > 0, propertyValue, "number of days is <= 0");
- long analysisDate = analysisMetadataHolder.getAnalysisDate();
- List<SnapshotDto> snapshots = dbClient.snapshotDao().selectAnalysesByQuery(dbSession, createCommonQuery(rootUuid)
- .setCreatedBefore(analysisDate).setSort(BY_DATE, ASC));
-
- ensureNotOnFirstAnalysis(!snapshots.isEmpty());
- Instant targetDate = DateUtils.addDays(Instant.ofEpochMilli(analysisDate), -days);
- LOG.debug("Resolving new code period by {} days: {}", days, supplierToString(() -> logDate(targetDate)));
- SnapshotDto snapshot = findNearestSnapshotToTargetDate(snapshots, targetDate);
- return newPeriod(NewCodePeriodType.NUMBER_OF_DAYS, String.valueOf((int) days), Instant.ofEpochMilli(snapshot.getCreatedAt()));
- }
-
- private Period resolvePreviousVersion(DbSession dbSession, String currentVersion, List<EventDto> versions, String mostRecentVersion) {
- EventDto previousVersion = versions.get(currentVersion.equals(mostRecentVersion) ? 1 : 0);
- LOG.debug("Resolving new code period by previous version: {}", previousVersion.getName());
- return newPeriod(dbSession, previousVersion);
- }
-
- private Period findOldestAnalysis(DbSession dbSession, String projectUuid) {
- LOG.debug("Resolving first analysis as new code period as there is only one existing version");
- Optional<Period> period = dbClient.snapshotDao().selectOldestSnapshot(dbSession, projectUuid)
- .map(dto -> newPeriod(NewCodePeriodType.PREVIOUS_VERSION, null, Instant.ofEpochMilli(dto.getCreatedAt())));
- ensureNotOnFirstAnalysis(period.isPresent());
- return period.get();
- }
-
- private Period newPeriod(DbSession dbSession, EventDto previousVersion) {
- Optional<Period> period = dbClient.snapshotDao().selectByUuid(dbSession, previousVersion.getAnalysisUuid())
- .map(dto -> newPeriod(NewCodePeriodType.PREVIOUS_VERSION, dto.getProjectVersion(), Instant.ofEpochMilli(dto.getCreatedAt())));
- if (!period.isPresent()) {
- throw new IllegalStateException(format("Analysis '%s' for version event '%s' has been deleted",
- previousVersion.getAnalysisUuid(), previousVersion.getName()));
- }
- return period.get();
- }
-
- private static Period newPeriod(NewCodePeriodType type, @Nullable String value, Instant instant) {
- return new Period(type.name(), value, instant.toEpochMilli());
- }
-
- private static Object supplierToString(Supplier<String> s) {
- return new Object() {
- @Override
- public String toString() {
- return s.get();
- }
- };
- }
-
- private static SnapshotQuery createCommonQuery(String projectUuid) {
- return new SnapshotQuery().setComponentUuid(projectUuid).setStatus(STATUS_PROCESSED);
- }
-
- private static SnapshotDto findNearestSnapshotToTargetDate(List<SnapshotDto> snapshots, Instant targetDate) {
- // FIXME shouldn't this be the first analysis after targetDate?
- Duration bestDuration = null;
- SnapshotDto nearest = null;
- for (SnapshotDto snapshot : snapshots) {
- Instant createdAt = Instant.ofEpochMilli(snapshot.getCreatedAt());
- Duration duration = Duration.between(targetDate, createdAt).abs();
- if (bestDuration == null || duration.compareTo(bestDuration) <= 0) {
- bestDuration = duration;
- nearest = snapshot;
- }
- }
- return nearest;
- }
-
- private static void checkPeriodProperty(boolean test, String propertyValue, String testDescription, Object... args) {
- if (!test) {
- LOG.debug("Invalid code period '{}': {}", propertyValue, supplierToString(() -> format(testDescription, args)));
- throw MessageException.of(format("Invalid new code period. '%s' is not one of: " +
- "integer > 0, date before current analysis j, \"previous_version\", or version string that exists in the project' \n" +
- "Please contact a project administrator to correct this setting", propertyValue));
- }
- }
-
- private static void ensureNotOnFirstAnalysis(boolean expression) {
- checkState(expression, "Attempting to resolve period while no analysis exist for project");
- }
-
- private static void checkNotNullValue(@Nullable String value, NewCodePeriodType type) {
- checkNotNull(value, "Value can't be null with type %s", type);
- }
-
- private static String logDate(Instant instant) {
- return DateUtils.formatDate(instant.truncatedTo(ChronoUnit.SECONDS));
- }
}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java
index 1d253b9e06a..c440cfa96c9 100644
--- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java
+++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java
@@ -44,6 +44,7 @@ import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.ce.task.projectanalysis.component.ReportComponent;
import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.period.NewCodePeriodResolver;
import org.sonar.ce.task.projectanalysis.period.Period;
import org.sonar.ce.task.projectanalysis.period.PeriodHolderImpl;
import org.sonar.ce.task.step.ComputationStep;
@@ -86,9 +87,10 @@ public class LoadPeriodsStepTest extends BaseStepTest {
private PeriodHolderImpl periodsHolder = new PeriodHolderImpl();
private System2 system2Mock = mock(System2.class);
private NewCodePeriodDao dao = new NewCodePeriodDao(system2Mock, new SequenceUuidFactory());
+ private NewCodePeriodResolver newCodePeriodResolver = new NewCodePeriodResolver(dbTester.getDbClient());
private ZonedDateTime analysisDate = ZonedDateTime.of(2019, 3, 20, 5, 30, 40, 0, ZoneId.systemDefault());
- private LoadPeriodsStep underTest = new LoadPeriodsStep(analysisMetadataHolder, dao, treeRootHolder, periodsHolder, dbTester.getDbClient());
+ private LoadPeriodsStep underTest = new LoadPeriodsStep(analysisMetadataHolder, dao, treeRootHolder, periodsHolder, dbTester.getDbClient(), newCodePeriodResolver);
private OrganizationDto organization;
private ComponentDto project;
@@ -373,7 +375,7 @@ public class LoadPeriodsStepTest extends BaseStepTest {
@DataProvider
public static Object[][] zeroOrLess() {
- return new Object[][] {
+ return new Object[][]{
{0},
{-1 - new Random().nextInt(30)}
};
@@ -381,7 +383,7 @@ public class LoadPeriodsStepTest extends BaseStepTest {
@DataProvider
public static Object[][] stringConsideredAsVersions() {
- return new Object[][] {
+ return new Object[][]{
{randomAlphabetic(5)},
{"1,3"},
{"1.3"},
@@ -393,7 +395,7 @@ public class LoadPeriodsStepTest extends BaseStepTest {
@DataProvider
public static Object[][] projectVersionNullOrNot() {
- return new Object[][] {
+ return new Object[][]{
{null},
{randomAlphabetic(15)},
};
@@ -401,7 +403,7 @@ public class LoadPeriodsStepTest extends BaseStepTest {
@DataProvider
public static Object[][] anyValidLeakPeriodSettingValue() {
- return new Object[][] {
+ return new Object[][]{
// days
{"100"},
// previous_version keyword
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDao.java
index 66290c83d1c..a1d9ee6c8d6 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDao.java
@@ -19,6 +19,7 @@
*/
package org.sonar.db.newcodeperiod;
+import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import org.sonar.api.utils.System2;
@@ -76,6 +77,11 @@ public class NewCodePeriodDao implements Dao {
return Optional.ofNullable(mapper(dbSession).selectByProject(projectUuid));
}
+ public List<NewCodePeriodDto> selectAllByProject(DbSession dbSession, String projectUuid) {
+ requireNonNull(projectUuid, "Project uuid must be specified.");
+ return mapper(dbSession).selectAllByProject(projectUuid);
+ }
+
public Optional<NewCodePeriodDto> selectByBranch(DbSession dbSession, String projectUuid, String branchUuid) {
requireNonNull(projectUuid, "Project uuid must be specified.");
requireNonNull(branchUuid, "Branch uuid must be specified.");
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDto.java
index 2ad0382c668..538e33c79eb 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDto.java
@@ -30,6 +30,10 @@ public class NewCodePeriodDto {
private long updatedAt;
private long createdAt;
+ public static NewCodePeriodDto defaultInstance() {
+ return new NewCodePeriodDto().setType(NewCodePeriodType.PREVIOUS_VERSION);
+ }
+
public long getCreatedAt() {
return createdAt;
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodMapper.java
index e692ab95d48..6517b4cad39 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodMapper.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodMapper.java
@@ -19,6 +19,7 @@
*/
package org.sonar.db.newcodeperiod;
+import java.util.List;
import java.util.Optional;
import org.apache.ibatis.annotations.Param;
@@ -39,4 +40,6 @@ public interface NewCodePeriodMapper {
NewCodePeriodDto selectByBranch(@Param("projectUuid") String projectUuid, @Param("branchUuid") String branchUuid);
long countByProjectAnalysis(String projectAnalysisUuid);
+
+ List<NewCodePeriodDto> selectAllByProject(String projectUuid);
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodType.java b/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodType.java
index 9a1721a2287..c02c61be669 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodType.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodType.java
@@ -22,6 +22,5 @@ package org.sonar.db.newcodeperiod;
public enum NewCodePeriodType {
PREVIOUS_VERSION,
NUMBER_OF_DAYS,
- DATE,
SPECIFIC_ANALYSIS
}
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/newcodeperiod/NewCodePeriodMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/newcodeperiod/NewCodePeriodMapper.xml
index 7c7463a9630..8cb4148b2e4 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/newcodeperiod/NewCodePeriodMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/newcodeperiod/NewCodePeriodMapper.xml
@@ -79,6 +79,14 @@
AND ncp.branch_uuid is null
</select>
+ <select id="selectAllByProject" parameterType="map" resultType="org.sonar.db.newcodeperiod.NewCodePeriodDto">
+ SELECT
+ <include refid="newCodePeriodMapperColumns"/>
+ FROM new_code_periods ncp
+ WHERE
+ ncp.project_uuid=#{projectUuid, jdbcType=VARCHAR}
+ </select>
+
<select id="selectByBranch" parameterType="map" resultType="org.sonar.db.newcodeperiod.NewCodePeriodDto">
SELECT
<include refid="newCodePeriodMapperColumns"/>
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/newcodeperiod/NewCodePeriodDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/newcodeperiod/NewCodePeriodDtoTest.java
index ed40ba8968b..ea997ba92a2 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/newcodeperiod/NewCodePeriodDtoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/newcodeperiod/NewCodePeriodDtoTest.java
@@ -39,15 +39,15 @@ public class NewCodePeriodDtoTest {
.setBranchUuid("branchUuid")
.setCreatedAt(currentTime)
.setUpdatedAt(currentTime)
- .setType(NewCodePeriodType.DATE)
- .setValue("2018-01-02");
+ .setType(NewCodePeriodType.NUMBER_OF_DAYS)
+ .setValue("1");
assertThat(newCodePeriodDto.getUuid()).isEqualTo("uuid");
assertThat(newCodePeriodDto.getProjectUuid()).isEqualTo("projectUuid");
assertThat(newCodePeriodDto.getBranchUuid()).isEqualTo("branchUuid");
assertThat(newCodePeriodDto.getCreatedAt()).isEqualTo(currentTime);
assertThat(newCodePeriodDto.getUpdatedAt()).isEqualTo(currentTime);
- assertThat(newCodePeriodDto.getType()).isEqualTo(NewCodePeriodType.DATE);
- assertThat(newCodePeriodDto.getValue()).isEqualTo("2018-01-02");
+ assertThat(newCodePeriodDto.getType()).isEqualTo(NewCodePeriodType.NUMBER_OF_DAYS);
+ assertThat(newCodePeriodDto.getValue()).isEqualTo("1");
}
}
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java
index 85c3d9300ac..01914170265 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java
@@ -563,7 +563,7 @@ public class PurgeCommandsTest {
dbTester.newCodePeriods().insert(NewCodePeriodType.PREVIOUS_VERSION, null);
//project settings
- dbTester.newCodePeriods().insert(project.uuid(), NewCodePeriodType.DATE, "2019-01-01");
+ dbTester.newCodePeriods().insert(project.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, "20");
//branch settings
dbTester.newCodePeriods().insert(project.uuid(), branch.getUuid(), NewCodePeriodType.NUMBER_OF_DAYS, "1");
@@ -586,7 +586,7 @@ public class PurgeCommandsTest {
dbTester.newCodePeriods().insert(NewCodePeriodType.PREVIOUS_VERSION, null);
//project settings
- dbTester.newCodePeriods().insert(project.uuid(), NewCodePeriodType.DATE, "2019-01-01");
+ dbTester.newCodePeriods().insert(project.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, "20");
//branch settings
dbTester.newCodePeriods().insert(project.uuid(), branch.getUuid(), NewCodePeriodType.NUMBER_OF_DAYS, "1");
@@ -609,7 +609,7 @@ public class PurgeCommandsTest {
dbTester.newCodePeriods().insert(NewCodePeriodType.PREVIOUS_VERSION, null);
//project settings
- dbTester.newCodePeriods().insert(project.uuid(), NewCodePeriodType.DATE, "2019-01-01");
+ dbTester.newCodePeriods().insert(project.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, "20");
//branch settings
dbTester.newCodePeriods().insert(project.uuid(), branch.getUuid(), NewCodePeriodType.NUMBER_OF_DAYS, "1");
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v80/PopulateNewCodePeriodTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v80/PopulateNewCodePeriodTable.java
index 48a659808b6..c2fbb0ffcad 100644
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v80/PopulateNewCodePeriodTable.java
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v80/PopulateNewCodePeriodTable.java
@@ -20,14 +20,30 @@
package org.sonar.server.platform.db.migration.version.v80;
import java.sql.SQLException;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
import org.sonar.core.util.UuidFactory;
import org.sonar.db.Database;
import org.sonar.server.platform.db.migration.step.DataChange;
import org.sonar.server.platform.db.migration.step.Upsert;
public class PopulateNewCodePeriodTable extends DataChange {
+ private static final Logger LOG = Loggers.get(PopulateNewCodePeriodTable.class);
+
+ private static final String TYPE_PREVIOUS_VERSION = "PREVIOUS_VERSION";
+ private static final String TYPE_NUMBER_OF_DAYS = "NUMBER_OF_DAYS";
+ private static final String TYPE_SPECIFIC_ANALYSIS = "SPECIFIC_ANALYSIS";
+
+ private static final String LEAK_PERIOD_PROP_KEY = "sonar.leak.period";
private final UuidFactory uuidFactory;
private final System2 system2;
@@ -40,41 +56,132 @@ public class PopulateNewCodePeriodTable extends DataChange {
@Override
protected void execute(Context context) throws SQLException {
- List<ProjectBranchCodePeriod> projectBranchCodePeriods = context.prepareSelect(
- "select pb.uuid, pb.project_uuid, pb.manual_baseline_analysis_uuid from project_branches pb where pb.manual_baseline_analysis_uuid is not null")
+ List<String> branchUuidsAlreadyMigrated = populateFromManualBaselines(context);
+ populateFromSettings(context, branchUuidsAlreadyMigrated);
+ }
+
+ private List<String> populateFromManualBaselines(Context context) throws SQLException {
+ List<ProjectBranchManualBaselineDto> projectBranchManualBaselines = context.prepareSelect(
+ "SELECT pb.uuid, pb.project_uuid, pb.manual_baseline_analysis_uuid FROM project_branches pb WHERE pb.manual_baseline_analysis_uuid IS NOT NULL")
.list(row -> {
String branchUuid = row.getString(1);
String projectUuid = row.getString(2);
String manualBaselineAnalysisUuid = row.getString(3);
- return new ProjectBranchCodePeriod(branchUuid, projectUuid, manualBaselineAnalysisUuid);
+ return new ProjectBranchManualBaselineDto(branchUuid, projectUuid, manualBaselineAnalysisUuid);
+ });
+
+ if (!projectBranchManualBaselines.isEmpty()) {
+ populateWithManualBaselines(context, projectBranchManualBaselines);
+ }
+
+ return projectBranchManualBaselines.stream()
+ .map(pb -> pb.branchUuid)
+ .collect(Collectors.toList());
+ }
+
+ private void populateFromSettings(Context context, List<String> branchUuidsAlreadyMigrated) throws SQLException {
+ // migrate global setting
+ String globalSetting = context
+ .prepareSelect("SELECT props.text_value FROM properties props WHERE props.prop_key = '" + LEAK_PERIOD_PROP_KEY + "' AND props.resource_id IS NULL")
+ .get(r -> r.getString(1));
+ if (globalSetting != null) {
+ populateGlobalSetting(context, globalSetting);
+ }
+
+ List<BranchLeakPeriodPropertyDto> projectLeakPeriodProperties = new ArrayList<>();
+ context.prepareSelect(
+ "SELECT projs.uuid, projs.main_branch_project_uuid, props.text_value "
+ + "FROM properties props INNER JOIN projects projs ON props.resource_id = projs.id "
+ + "WHERE props.prop_key = '" + LEAK_PERIOD_PROP_KEY + "' AND props.resource_id IS NOT NULL ")
+ .scroll(row -> {
+ String branchUuid = row.getString(1);
+ if (branchUuidsAlreadyMigrated.contains(branchUuid)) {
+ return;
+ }
+ String mainBranchUuid = row.getString(2);
+ String value = row.getString(3);
+ if (mainBranchUuid == null) {
+ mainBranchUuid = branchUuid;
+ }
+ projectLeakPeriodProperties.add(new BranchLeakPeriodPropertyDto(branchUuid, mainBranchUuid, value));
});
- if (!projectBranchCodePeriods.isEmpty()) {
- populateProjectBranchCodePeriods(context, projectBranchCodePeriods);
+ populateWithSettings(context, projectLeakPeriodProperties);
+ }
+
+ private void populateGlobalSetting(Context context, String value) {
+ Optional<TypeValuePair> typeValue = tryParse(context, null, value);
+ typeValue.ifPresent(tp -> {
+ try (Upsert upsertQuery = prepareUpsertNewCodePeriodQuery(context)) {
+ long currentTime = system2.now();
+ insert(upsertQuery, null, null, tp.type, tp.value, currentTime);
+ upsertQuery
+ .execute()
+ .commit();
+ } catch (SQLException e) {
+ LOG.warn("Failed to migrate the global property for the new code period", e);
+ }
+ });
+ }
+
+ private void populateWithSettings(Context context, List<BranchLeakPeriodPropertyDto> projectLeakPeriodProperties) throws SQLException {
+ try (Upsert upsertQuery = prepareUpsertNewCodePeriodQuery(context)) {
+ long currentTime = system2.now();
+ boolean commit = false;
+
+ for (BranchLeakPeriodPropertyDto branchLeakPeriodProperty : projectLeakPeriodProperties) {
+ Optional<TypeValuePair> typeValueOpt = tryParse(context, branchLeakPeriodProperty.branchUuid, branchLeakPeriodProperty.value);
+ if (!typeValueOpt.isPresent()) {
+ continue;
+ }
+
+ TypeValuePair typeValue = typeValueOpt.get();
+
+ String branchUuid = null;
+ if (!branchLeakPeriodProperty.isMainBranch() || TYPE_SPECIFIC_ANALYSIS.equals(typeValue.type)) {
+ branchUuid = branchLeakPeriodProperty.branchUuid;
+ }
+
+ insert(upsertQuery, branchLeakPeriodProperty.mainBranchUuid, branchUuid, typeValue.type, typeValue.value, currentTime);
+ commit = true;
+ }
+
+ if (commit) {
+ upsertQuery
+ .execute()
+ .commit();
+ }
}
}
- private void populateProjectBranchCodePeriods(Context context, List<ProjectBranchCodePeriod> projectBranchCodePeriods) throws SQLException {
- Upsert insertQuery = prepareInsertProjectQualityGateQuery(context);
- for (ProjectBranchCodePeriod projectBranchCodePeriod : projectBranchCodePeriods) {
- long currenTime = system2.now();
- insertQuery
- .setString(1, uuidFactory.create())
- .setString(2, projectBranchCodePeriod.projectUuid)
- .setString(3, projectBranchCodePeriod.branchUuid)
- .setString(4, "SPECIFIC_ANALYSIS")
- .setString(5, projectBranchCodePeriod.manualBaselineAnalysisUuid)
- .setLong(6, currenTime)
- .setLong(7, currenTime)
- .addBatch();
+ private void populateWithManualBaselines(Context context, List<ProjectBranchManualBaselineDto> projectBranchManualBaselines) throws SQLException {
+ try (Upsert upsertQuery = prepareUpsertNewCodePeriodQuery(context)) {
+ long currentTime = system2.now();
+
+ for (ProjectBranchManualBaselineDto projectBranchManualBaseline : projectBranchManualBaselines) {
+ insert(upsertQuery, projectBranchManualBaseline.projectUuid, projectBranchManualBaseline.branchUuid, TYPE_SPECIFIC_ANALYSIS,
+ projectBranchManualBaseline.manualBaselineAnalysisUuid, currentTime);
+ }
+ upsertQuery
+ .execute()
+ .commit();
}
- insertQuery
- .execute()
- .commit();
}
- private static Upsert prepareInsertProjectQualityGateQuery(Context context) throws SQLException {
- return context.prepareUpsert("insert into new_code_periods(" +
+ private void insert(Upsert upsert, @Nullable String projectUuid, @Nullable String branchUuid, String type, @Nullable String value, long currentTime) throws SQLException {
+ upsert
+ .setString(1, uuidFactory.create())
+ .setString(2, projectUuid)
+ .setString(3, branchUuid)
+ .setString(4, type)
+ .setString(5, value)
+ .setLong(6, currentTime)
+ .setLong(7, currentTime)
+ .addBatch();
+ }
+
+ private static Upsert prepareUpsertNewCodePeriodQuery(Context context) throws SQLException {
+ return context.prepareUpsert("INSERT INTO new_code_periods(" +
"uuid, " +
"project_uuid," +
"branch_uuid," +
@@ -85,16 +192,102 @@ public class PopulateNewCodePeriodTable extends DataChange {
") VALUES (?, ?, ?, ?, ?, ?, ?)");
}
- private static class ProjectBranchCodePeriod {
+ private static Optional<TypeValuePair> tryParse(Context context, @Nullable String branchUuid, String value) {
+ try {
+ if (value.equals("previous_version")) {
+ return Optional.of(new TypeValuePair(TYPE_PREVIOUS_VERSION, null));
+ }
+
+ try {
+ Integer.parseInt(value);
+ return Optional.of(new TypeValuePair(TYPE_NUMBER_OF_DAYS, value));
+ } catch (NumberFormatException e) {
+ // ignore
+ }
+
+ if (branchUuid == null) {
+ return Optional.empty();
+ }
+
+ try {
+ LocalDate localDate = LocalDate.parse(value);
+ Optional<String> snapshot = findFirstSnapshot(context, branchUuid, localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli());
+ return snapshot.map(uuid -> new TypeValuePair(TYPE_SPECIFIC_ANALYSIS, uuid));
+ } catch (DateTimeParseException e) {
+ // ignore
+ }
+
+ List<EventDto> versions = getVersionsByMostRecentFirst(context, branchUuid);
+ Optional<EventDto> versionMatch = versions.stream().filter(v -> v.version.equals(value)).findFirst();
+ return versionMatch.map(e -> new TypeValuePair(TYPE_SPECIFIC_ANALYSIS, e.analysisUuid));
+ } catch (SQLException e) {
+ LOG.warn("Failed to migrate a property for the new code period", e);
+ return Optional.empty();
+ }
+ }
+
+ private static List<EventDto> getVersionsByMostRecentFirst(Context context, String branchUuid) throws SQLException {
+ // TODO in LoadPeriodsStep we do a join with snapshots and see if the analysis is still unprocessed. Do we need to do it here?
+ return context.prepareSelect("SELECT name, analysis_uuid FROM events "
+ + "WHERE component_uuid = '" + branchUuid + "' AND category = 'Version' "
+ + "ORDER BY created_at DESC")
+ .list(r -> new EventDto(r.getString(1), r.getString(2)));
+ }
+
+ private static Optional<String> findFirstSnapshot(Context context, String branchUuid, long date) throws SQLException {
+ String analysisUuid = context.prepareSelect("SELECT uuid FROM snapshots "
+ + "WHERE component_uuid = '" + branchUuid + "' AND created_at > " + date + " AND status = 'P' "
+ + "ORDER BY created_at ASC")
+ .get(r -> r.getString(1));
+ return Optional.ofNullable(analysisUuid);
+ }
+
+ private static class TypeValuePair {
+ private final String type;
+ @Nullable
+ private final String value;
+
+ private TypeValuePair(String type, @Nullable String value) {
+ this.type = type;
+ this.value = value;
+ }
+ }
+
+ private static class EventDto {
+ private final String version;
+ private final String analysisUuid;
+
+ private EventDto(String version, String analysisUuid) {
+ this.version = version;
+ this.analysisUuid = analysisUuid;
+ }
+ }
+
+ private static class ProjectBranchManualBaselineDto {
private final String branchUuid;
private final String projectUuid;
private final String manualBaselineAnalysisUuid;
- ProjectBranchCodePeriod(String branchUuid, String projectUuid, String manualBaselineAnalysisUuid) {
+ ProjectBranchManualBaselineDto(String branchUuid, String projectUuid, String manualBaselineAnalysisUuid) {
this.branchUuid = branchUuid;
this.projectUuid = projectUuid;
this.manualBaselineAnalysisUuid = manualBaselineAnalysisUuid;
}
}
+ private static class BranchLeakPeriodPropertyDto {
+ private final String branchUuid;
+ private final String mainBranchUuid;
+ private final String value;
+
+ BranchLeakPeriodPropertyDto(String branchUuid, String mainBranchUuid, String value) {
+ this.branchUuid = branchUuid;
+ this.mainBranchUuid = mainBranchUuid;
+ this.value = value;
+ }
+
+ private boolean isMainBranch() {
+ return mainBranchUuid.equals(branchUuid);
+ }
+ }
}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v80/PopulateNewCodePeriodTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v80/PopulateNewCodePeriodTableTest.java
index 913de150842..fe673114962 100644
--- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v80/PopulateNewCodePeriodTableTest.java
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v80/PopulateNewCodePeriodTableTest.java
@@ -20,14 +20,23 @@
package org.sonar.server.platform.db.migration.version.v80;
import java.sql.SQLException;
-import org.junit.Assert;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Map;
+import java.util.Optional;
+import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import org.sonar.api.resources.Scopes;
import org.sonar.api.utils.System2;
import org.sonar.core.util.UuidFactoryImpl;
import org.sonar.db.CoreDbTester;
+import static java.lang.String.valueOf;
+import static org.assertj.core.api.Assertions.assertThat;
+
public class PopulateNewCodePeriodTableTest {
private static final String NEW_CODE_PERIODS_TABLE_NAME = "new_code_periods";
private static final String PROJECT_BRANCHES_TABLE_NAME = "project_branches";
@@ -35,7 +44,6 @@ public class PopulateNewCodePeriodTableTest {
@Rule
public CoreDbTester dbTester = CoreDbTester.createForSchema(PopulateNewCodePeriodTableTest.class, "schema.sql");
-
@Rule
public ExpectedException expectedException = ExpectedException.none();
@@ -43,31 +51,211 @@ public class PopulateNewCodePeriodTableTest {
@Test
public void copy_manual_baseline_analysis_to_new_code_period_table() throws SQLException {
- for (long i = 1; i <= NUMBER_OF_PROJECT_BRANCHES_TO_INSERT; i++) {
- insertProjectBranch(i);
+ for (long i = 0; i < NUMBER_OF_PROJECT_BRANCHES_TO_INSERT; i++) {
+ insertMainBranch(i, true);
}
underTest.execute();
+ assertThat(dbTester.countRowsOfTable(NEW_CODE_PERIODS_TABLE_NAME)).isEqualTo(10);
- int propertiesCount = dbTester.countRowsOfTable(NEW_CODE_PERIODS_TABLE_NAME);
- Assert.assertEquals(10, propertiesCount);
+ for (int i = 0; i < NUMBER_OF_PROJECT_BRANCHES_TO_INSERT; i++) {
+ assertNewCodePeriod(i, "pb-uuid-" + i, "pb-uuid-" + i, "SPECIFIC_ANALYSIS", "mba-uuid" + i);
+ }
//should not fail if executed twice
underTest.execute();
}
- private void insertProjectBranch(long counter) {
+ @Test
+ public void do_nothing_if_cant_find_matching_analysis() throws SQLException {
+ insertMainBranch(0, false);
+ insertMainBranch(1, false);
+
+ insertProperty(0, "2.0");
+ insertProperty(0, "2019-04-05");
+
+ underTest.execute();
+ assertThat(dbTester.countRowsOfTable(NEW_CODE_PERIODS_TABLE_NAME)).isEqualTo(0);
+ }
+
+ @Test
+ public void do_nothing_if_cant_migrate_global_property() throws SQLException {
+ insertProperty(null, "2.0");
+
+ underTest.execute();
+ assertThat(dbTester.countRowsOfTable(NEW_CODE_PERIODS_TABLE_NAME)).isEqualTo(0);
+ }
+
+ @Test
+ public void migrate_project_property_set_to_previous_version() throws SQLException {
+ insertMainBranch(0, true);
+ insertMainBranch(1, false);
+ // no property defined for it
+ insertMainBranch(2, false);
+
+ // doesn't get copied since there is a manual baseline taking precedence
+ insertProperty(0, "20");
+ insertProperty(1, "previous_version");
+ // doesn't exist
+ insertProperty(3, "previous_version");
+
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(NEW_CODE_PERIODS_TABLE_NAME)).isEqualTo(2);
+ assertNewCodePeriod(0, "pb-uuid-" + 0, "pb-uuid-" + 0, "SPECIFIC_ANALYSIS", "mba-uuid" + 0);
+ assertNewCodePeriod(1, "pb-uuid-" + 1, null, "PREVIOUS_VERSION", null);
+ }
+
+ @Test
+ public void migrate_project_property_set_to_number_of_days() throws SQLException {
+ insertMainBranch(0, false);
+ insertBranch(0, 1, false);
+ insertProperty(1, "20");
+
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(NEW_CODE_PERIODS_TABLE_NAME)).isEqualTo(1);
+ assertNewCodePeriod(0, "pb-uuid-" + 0, "pb-uuid-" + 1, "NUMBER_OF_DAYS", "20");
+ }
+
+ @Test
+ public void migrate_branch_property_set_to_number_of_days() throws SQLException {
+ insertMainBranch(0, false);
+ insertProperty(0, "20");
+
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(NEW_CODE_PERIODS_TABLE_NAME)).isEqualTo(1);
+ assertNewCodePeriod(0, "pb-uuid-" + 0, null, "NUMBER_OF_DAYS", "20");
+ }
+
+ @Test
+ public void migrate_global_property_set_to_number_of_days() throws SQLException {
+ insertProperty(null, "20");
+
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(NEW_CODE_PERIODS_TABLE_NAME)).isEqualTo(1);
+ assertNewCodePeriod(0, null, null, "NUMBER_OF_DAYS", "20");
+ }
+
+
+ @Test
+ public void migrate_project_property_set_to_version() throws SQLException {
+ insertMainBranch(0, false);
+ insertProperty(0, "2.0");
+
+ insertVersionEvent(0, 10L, "1.0");
+ insertVersionEvent(0, 20L, "2.0");
+ insertVersionEvent(0, 30L, "3.0");
+ insertVersionEvent(0, 40L, "2.0");
+ insertVersionEvent(0, 50L, "3.0");
+
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(NEW_CODE_PERIODS_TABLE_NAME)).isEqualTo(1);
+ // we don't support specific analysis for projects, so we set it for the branch
+ assertNewCodePeriod(0, "pb-uuid-" + 0, "pb-uuid-" + 0, "SPECIFIC_ANALYSIS", "analysis-40");
+ }
+
+ @Test
+ public void migrate_project_property_set_to_date() throws SQLException {
+ insertMainBranch(0, false);
+ insertProperty(0, "2019-04-05");
+
+ long reference = LocalDate.parse("2019-04-05").atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
+ insertSnapshot(100, 0, reference - 100);
+ insertSnapshot(200, 0, reference + 100);
+ insertSnapshot(300, 0, reference + 200);
+
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(NEW_CODE_PERIODS_TABLE_NAME)).isEqualTo(1);
+ // we don't support specific analysis for projects, so we set it for the branch
+ assertNewCodePeriod(0, "pb-uuid-" + 0, "pb-uuid-" + 0, "SPECIFIC_ANALYSIS", "snapshot-200");
+ }
+
+ private void assertNewCodePeriod(int row, @Nullable String projectUuid, @Nullable String branchUuid, String type, @Nullable String value) {
+ Optional<Map<String, Object>> r = dbTester.select("SELECT PROJECT_UUID, BRANCH_UUID, TYPE, VALUE FROM " + NEW_CODE_PERIODS_TABLE_NAME)
+ .stream()
+ .skip(row)
+ .findFirst();
+
+ assertThat(r).isPresent();
+
+ assertThat(r.get().get("PROJECT_UUID")).isEqualTo(projectUuid);
+ assertThat(r.get().get("BRANCH_UUID")).isEqualTo(branchUuid);
+ assertThat(r.get().get("TYPE")).isEqualTo(type);
+ assertThat(r.get().get("VALUE")).isEqualTo(value);
+ }
+
+ private void insertBranch(long mainBranchUid, long uid, boolean withBaseLine) {
+ String manualBaseline = withBaseLine ? "mba-uuid" + uid : null;
+
+ String mainBranchProjectUuid = mainBranchUid == uid ? null : "pb-uuid-" + mainBranchUid;
+ insertComponent(uid, "pb-uuid-" + uid, mainBranchProjectUuid);
+
dbTester.executeInsert(
PROJECT_BRANCHES_TABLE_NAME,
- "UUID", "pb-uuid-" + counter,
- "PROJECT_UUID", "pb-uuid-" + counter,
- "KEE", "pb-key-" + counter,
+ "UUID", "pb-uuid-" + uid,
+ "PROJECT_UUID", "pb-uuid-" + mainBranchUid,
+ "KEE", "pb-key-" + uid,
"KEY_TYPE", "TSR",
"BRANCH_TYPE", "LONG",
- "MERGE_BRANCH_UUID", "mb-uuid-" + counter,
- "MANUAL_BASELINE_ANALYSIS_UUID", "mba-uuid" + counter,
+ "MERGE_BRANCH_UUID", "mb-uuid-" + mainBranchUid,
+ "MANUAL_BASELINE_ANALYSIS_UUID", manualBaseline,
"CREATED_AT", System2.INSTANCE.now(),
"UPDATED_AT", System2.INSTANCE.now()
);
}
+
+ private void insertSnapshot(int uid, int branchUid, long creationDate) {
+ dbTester.executeInsert(
+ "SNAPSHOTS",
+ "UUID", "snapshot-" + uid,
+ "COMPONENT_UUID", "pb-uuid-" + branchUid,
+ "STATUS", "P",
+ "CREATED_AT", creationDate
+ );
+ }
+
+ private void insertProperty(@Nullable Integer uid, String value) {
+ dbTester.executeInsert(
+ "PROPERTIES",
+ "PROP_KEY", "sonar.leak.period",
+ "RESOURCE_ID", uid,
+ "USER_ID", null,
+ "IS_EMPTY", false,
+ "TEXT_VALUE", value,
+ "CLOB_VALUE", null,
+ "CREATED_AT", System2.INSTANCE.now()
+ );
+ }
+
+ private void insertComponent(long id, String uuid, @Nullable String mainBranchProjectUuid) {
+ dbTester.executeInsert(
+ "projects",
+ "ID", valueOf(id),
+ "UUID", uuid,
+ "ROOT_UUID", uuid,
+ "PROJECT_UUID", uuid,
+ "MAIN_BRANCH_PROJECT_UUID", mainBranchProjectUuid,
+ "SCOPE", Scopes.PROJECT,
+ "QUALIFIER", "TRK",
+ "NAME", "name-" + id);
+ }
+
+ private void insertVersionEvent(long id, long createdAt, String version) {
+ dbTester.executeInsert(
+ "events",
+ "ANALYSIS_UUID", "analysis-" + createdAt,
+ "NAME", version,
+ "COMPONENT_UUID", "pb-uuid-" + id,
+ "CATEGORY", "Version",
+ "CREATED_AT", createdAt);
+ }
+
+ private void insertMainBranch(long uid, boolean withBaseLine) {
+ insertBranch(uid, uid, withBaseLine);
+ }
}
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v80/PopulateNewCodePeriodTableTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v80/PopulateNewCodePeriodTableTest/schema.sql
index 799247fe6d9..d0f89a687c9 100644
--- a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v80/PopulateNewCodePeriodTableTest/schema.sql
+++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v80/PopulateNewCodePeriodTableTest/schema.sql
@@ -19,9 +19,68 @@ CREATE TABLE "NEW_CODE_PERIODS" (
"PROJECT_UUID" VARCHAR(40),
"BRANCH_UUID" VARCHAR(40),
"TYPE" VARCHAR(30) NOT NULL,
- "VALUE" VARCHAR(40) NOT NULL,
+ "VALUE" VARCHAR(40),
"UPDATED_AT" BIGINT NOT NULL,
"CREATED_AT" BIGINT NOT NULL,
CONSTRAINT "PK_NEW_CODE_PERIOD" PRIMARY KEY ("UUID")
);
+
+CREATE TABLE "PROPERTIES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "PROP_KEY" VARCHAR(512) NOT NULL,
+ "RESOURCE_ID" INTEGER,
+ "USER_ID" INTEGER,
+ "IS_EMPTY" BOOLEAN NOT NULL,
+ "TEXT_VALUE" VARCHAR(4000),
+ "CLOB_VALUE" CLOB,
+ "CREATED_AT" BIGINT
+);
+CREATE INDEX "PROPERTIES_KEY" ON "PROPERTIES" ("PROP_KEY");
+
+CREATE TABLE "PROJECTS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "KEE" VARCHAR(400),
+ "UUID" VARCHAR(50) NOT NULL,
+ "ROOT_UUID" VARCHAR(50),
+ "PROJECT_UUID" VARCHAR(50) NOT NULL,
+ "MODULE_UUID" VARCHAR(50),
+ "MODULE_UUID_PATH" VARCHAR(1500),
+ "MAIN_BRANCH_PROJECT_UUID" VARCHAR(50),
+ "NAME" VARCHAR(2000),
+ "TAGS" VARCHAR(500),
+ "ENABLED" BOOLEAN NOT NULL DEFAULT TRUE,
+ "SCOPE" VARCHAR(3),
+ "QUALIFIER" VARCHAR(10)
+);
+CREATE UNIQUE INDEX "PROJECTS_KEE" ON "PROJECTS" ("KEE");
+CREATE INDEX "PROJECTS_ROOT_UUID" ON "PROJECTS" ("ROOT_UUID");
+CREATE UNIQUE INDEX "PROJECTS_UUID" ON "PROJECTS" ("UUID");
+CREATE INDEX "PROJECTS_PROJECT_UUID" ON "PROJECTS" ("PROJECT_UUID");
+CREATE INDEX "PROJECTS_MODULE_UUID" ON "PROJECTS" ("MODULE_UUID");
+CREATE INDEX "PROJECTS_QUALIFIER" ON "PROJECTS" ("QUALIFIER");
+
+CREATE TABLE "EVENTS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "NAME" VARCHAR(400),
+ "ANALYSIS_UUID" VARCHAR(50) NOT NULL,
+ "COMPONENT_UUID" VARCHAR(50) NOT NULL,
+ "CATEGORY" VARCHAR(50),
+ "CREATED_AT" BIGINT NOT NULL,
+ "DESCRIPTION" VARCHAR(4000),
+ "EVENT_DATA" VARCHAR(4000)
+);
+CREATE INDEX "EVENTS_ANALYSIS" ON "EVENTS" ("ANALYSIS_UUID");
+CREATE INDEX "EVENTS_COMPONENT_UUID" ON "EVENTS" ("COMPONENT_UUID");
+
+CREATE TABLE "SNAPSHOTS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "UUID" VARCHAR(50) NOT NULL,
+ "CREATED_AT" BIGINT,
+ "BUILD_DATE" BIGINT,
+ "COMPONENT_UUID" VARCHAR(50) NOT NULL,
+ "STATUS" VARCHAR(4) NOT NULL DEFAULT 'U',
+ "ISLAST" BOOLEAN NOT NULL DEFAULT FALSE
+);
+CREATE INDEX "SNAPSHOT_COMPONENT" ON "SNAPSHOTS" ("COMPONENT_UUID");
+CREATE UNIQUE INDEX "ANALYSES_UUID" ON "SNAPSHOTS" ("UUID");
diff --git a/server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/ListAction.java b/server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/ListAction.java
new file mode 100644
index 00000000000..5e0c15df75c
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/ListAction.java
@@ -0,0 +1,152 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.newcodeperiod.ws;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.newcodeperiod.NewCodePeriodDao;
+import org.sonar.db.newcodeperiod.NewCodePeriodDto;
+import org.sonar.db.newcodeperiod.NewCodePeriodType;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.user.UserSession;
+import org.sonarqube.ws.NewCodePeriods;
+import org.sonarqube.ws.NewCodePeriods.ListWSResponse;
+
+import static org.sonar.core.util.stream.MoreCollectors.toList;
+import static org.sonar.db.component.BranchType.LONG;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+import static org.sonarqube.ws.NewCodePeriods.ShowWSResponse.newBuilder;
+
+public class ListAction implements NewCodePeriodsWsAction {
+ private static final String PARAM_PROJECT = "project";
+
+ private final DbClient dbClient;
+ private final UserSession userSession;
+ private final ComponentFinder componentFinder;
+ private final NewCodePeriodDao newCodePeriodDao;
+
+ public ListAction(DbClient dbClient, UserSession userSession, ComponentFinder componentFinder, NewCodePeriodDao newCodePeriodDao) {
+ this.dbClient = dbClient;
+ this.userSession = userSession;
+ this.componentFinder = componentFinder;
+ this.newCodePeriodDao = newCodePeriodDao;
+ }
+
+ @Override
+ public void define(WebService.NewController context) {
+ WebService.NewAction action = context.createAction("list")
+ .setDescription("List the New Code Periods for all long lived branches in a project.<br>" +
+ "Requires the permission to browse the project")
+ .setSince("8.0")
+ .setResponseExample(getClass().getResource("list-example.json"))
+ .setHandler(this);
+
+ action.createParam(PARAM_PROJECT)
+ .setRequired(true)
+ .setDescription("Project key");
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ String projectKey = request.mandatoryParam(PARAM_PROJECT);
+
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ ComponentDto project = componentFinder.getByKey(dbSession, projectKey);
+ userSession.checkComponentPermission(UserRole.ADMIN, project);
+ Collection<BranchDto> branches = dbClient.branchDao().selectByComponent(dbSession, project).stream()
+ .filter(b -> b.getBranchType() == LONG)
+ .collect(toList());
+ Map<String, InheritedNewCodePeriod> newCodePeriodByBranchUuid = newCodePeriodDao
+ .selectAllByProject(dbSession, project.uuid())
+ .stream()
+ .collect(Collectors.toMap(NewCodePeriodDto::getBranchUuid, dto -> new InheritedNewCodePeriod(dto, dto.getBranchUuid() == null)));
+
+ InheritedNewCodePeriod projectDefault = newCodePeriodByBranchUuid.getOrDefault(null,
+ newCodePeriodDao.selectGlobal(dbSession)
+ .map(dto -> new InheritedNewCodePeriod(dto, true))
+ .orElse(new InheritedNewCodePeriod(NewCodePeriodDto.defaultInstance(), true))
+ );
+
+ ListWSResponse.Builder builder = ListWSResponse.newBuilder();
+ for (BranchDto branch : branches) {
+ InheritedNewCodePeriod inherited = newCodePeriodByBranchUuid.getOrDefault(branch.getUuid(), projectDefault);
+ builder.addNewCodePeriods(
+ build(projectKey, branch.getKey(), inherited.getType(), inherited.getValue(), inherited.inherited));
+ }
+
+ writeProtobuf(builder.build(), request, response);
+ }
+ }
+
+ private NewCodePeriods.ShowWSResponse build(String projectKey, String branchKey, NewCodePeriodType newCodePeriodType, @Nullable String value, boolean inherited) {
+ NewCodePeriods.ShowWSResponse.Builder builder = newBuilder()
+ .setType(convertType(newCodePeriodType))
+ .setInherited(inherited)
+ .setBranchKey(branchKey)
+ .setProjectKey(projectKey);
+
+ if (value != null) {
+ builder.setValue(value);
+ }
+
+ return builder.build();
+ }
+
+ private static NewCodePeriods.NewCodePeriodType convertType(NewCodePeriodType type) {
+ switch (type) {
+ case NUMBER_OF_DAYS:
+ return NewCodePeriods.NewCodePeriodType.NUMBER_OF_DAYS;
+ case PREVIOUS_VERSION:
+ return NewCodePeriods.NewCodePeriodType.PREVIOUS_VERSION;
+ case SPECIFIC_ANALYSIS:
+ return NewCodePeriods.NewCodePeriodType.SPECIFIC_ANALYSIS;
+ default:
+ throw new IllegalStateException("Unexpected type: " + type);
+ }
+ }
+
+ private static class InheritedNewCodePeriod {
+ NewCodePeriodDto newCodePeriod;
+ boolean inherited;
+
+ InheritedNewCodePeriod(NewCodePeriodDto newCodePeriod, boolean inherited) {
+ this.newCodePeriod = newCodePeriod;
+ this.inherited = inherited;
+ }
+
+ NewCodePeriodType getType() {
+ return newCodePeriod.getType();
+ }
+
+ String getValue() {
+ return newCodePeriod.getValue();
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/SetAction.java b/server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/SetAction.java
index d540e41933f..796c929fd23 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/SetAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/SetAction.java
@@ -20,7 +20,7 @@
package org.sonar.server.newcodeperiod.ws;
import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableSet;
+import java.util.EnumSet;
import java.util.Locale;
import java.util.Set;
import javax.annotation.Nullable;
@@ -44,7 +44,6 @@ import org.sonar.server.user.UserSession;
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
-import static org.sonar.db.newcodeperiod.NewCodePeriodType.DATE;
import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS;
import static org.sonar.db.newcodeperiod.NewCodePeriodType.PREVIOUS_VERSION;
import static org.sonar.db.newcodeperiod.NewCodePeriodType.SPECIFIC_ANALYSIS;
@@ -55,9 +54,9 @@ public class SetAction implements NewCodePeriodsWsAction {
private static final String PARAM_PROJECT = "project";
private static final String PARAM_TYPE = "type";
private static final String PARAM_VALUE = "value";
- private static final Set<NewCodePeriodType> OVERALL_TYPES = ImmutableSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS);
- private static final Set<NewCodePeriodType> PROJECT_TYPES = ImmutableSet.of(DATE, PREVIOUS_VERSION, NUMBER_OF_DAYS);
- private static final Set<NewCodePeriodType> BRANCH_TYPES = ImmutableSet.of(DATE, PREVIOUS_VERSION, NUMBER_OF_DAYS, SPECIFIC_ANALYSIS);
+ private static final Set<NewCodePeriodType> OVERALL_TYPES = EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS);
+ private static final Set<NewCodePeriodType> PROJECT_TYPES = EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS);
+ private static final Set<NewCodePeriodType> BRANCH_TYPES = EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS, SPECIFIC_ANALYSIS);
private final DbClient dbClient;
private final UserSession userSession;
@@ -146,14 +145,6 @@ public class SetAction implements NewCodePeriodsWsAction {
throw new IllegalArgumentException("Failed to parse number of days: " + value);
}
break;
- case DATE:
- requireValue(type, value);
- try {
- dto.setValue(NewCodePeriodParser.parseDate(value).toString());
- } catch (Exception e) {
- throw new IllegalArgumentException("Failed to parse date: " + value);
- }
- break;
case SPECIFIC_ANALYSIS:
requireValue(type, value);
SnapshotDto analysis = getAnalysis(dbSession, value, projectBranch, branch);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/ShowAction.java b/server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/ShowAction.java
index 97993f401bf..6dff716e39d 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/ShowAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/ShowAction.java
@@ -37,12 +37,12 @@ import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.NewCodePeriods;
-import org.sonarqube.ws.NewCodePeriods.ShowWSResponse;
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
import static org.sonar.server.component.ComponentFinder.ParamNames.PROJECT_ID_AND_KEY;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
+import static org.sonarqube.ws.NewCodePeriods.*;
public class ShowAction implements NewCodePeriodsWsAction {
private static final String PARAM_BRANCH = "branch";
@@ -154,8 +154,6 @@ public class ShowAction implements NewCodePeriodsWsAction {
switch (type) {
case NUMBER_OF_DAYS:
return NewCodePeriods.NewCodePeriodType.NUMBER_OF_DAYS;
- case DATE:
- return NewCodePeriods.NewCodePeriodType.DATE;
case PREVIOUS_VERSION:
return NewCodePeriods.NewCodePeriodType.PREVIOUS_VERSION;
case SPECIFIC_ANALYSIS:
diff --git a/server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/package-info.java
new file mode 100644
index 00000000000..4162912abbb
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/newcodeperiod/ws/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.newcodeperiod.ws;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/setting/ws/list_new_code_period-example.json b/server/sonar-server/src/main/resources/org/sonar/server/setting/ws/list_new_code_period-example.json
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/server/sonar-server/src/main/resources/org/sonar/server/setting/ws/list_new_code_period-example.json
diff --git a/server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/ListActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/ListActionTest.java
new file mode 100644
index 00000000000..24e15d68b00
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/ListActionTest.java
@@ -0,0 +1,319 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.newcodeperiod.ws;
+
+import java.util.Optional;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.newcodeperiod.NewCodePeriodDao;
+import org.sonar.db.newcodeperiod.NewCodePeriodDbTester;
+import org.sonar.db.newcodeperiod.NewCodePeriodDto;
+import org.sonar.db.newcodeperiod.NewCodePeriodType;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.component.TestComponentFinder;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.NewCodePeriods;
+import org.sonarqube.ws.NewCodePeriods.ListWSResponse;
+import org.sonarqube.ws.NewCodePeriods.ShowWSResponse;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ListActionTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+ @Rule
+ public DbTester db = DbTester.create(System2.INSTANCE);
+
+ private ComponentDbTester componentDb = new ComponentDbTester(db);
+ private DbClient dbClient = db.getDbClient();
+ private ComponentFinder componentFinder = TestComponentFinder.from(db);
+ private NewCodePeriodDao dao = new NewCodePeriodDao(System2.INSTANCE, UuidFactoryFast.getInstance());
+ private NewCodePeriodDbTester tester = new NewCodePeriodDbTester(db);
+ private ListAction underTest = new ListAction(dbClient, userSession, componentFinder, dao);
+ private WsActionTester ws = new WsActionTester(underTest);
+
+ @Test
+ public void test_definition() {
+ WebService.Action definition = ws.getDef();
+
+ assertThat(definition.key()).isEqualTo("list");
+ assertThat(definition.isInternal()).isFalse();
+ assertThat(definition.since()).isEqualTo("8.0");
+ assertThat(definition.isPost()).isFalse();
+
+ assertThat(definition.params()).extracting(WebService.Param::key).containsOnly("project");
+ assertThat(definition.param("project").isRequired()).isTrue();
+ }
+
+ @Test
+ public void throw_NFE_if_project_not_found() {
+ expectedException.expect(NotFoundException.class);
+ expectedException.expectMessage("Component key 'unknown' not found");
+
+ ws.newRequest()
+ .setParam("project", "unknown")
+ .execute();
+ }
+
+ @Test
+ public void throw_FE_if_no_project_permission() {
+ ComponentDto project = componentDb.insertMainBranch();
+ expectedException.expect(ForbiddenException.class);
+ expectedException.expectMessage("Insufficient privileges");
+
+ ws.newRequest()
+ .setParam("project", project.getKey())
+ .execute();
+ }
+
+ @Test
+ public void list_only_LLB() {
+ ComponentDto project = componentDb.insertMainBranch();
+
+ createBranches(project, 5, BranchType.LONG);
+ createBranches(project, 3, BranchType.SHORT);
+
+ logInAsProjectAdministrator(project);
+
+ ListWSResponse response = ws.newRequest()
+ .setParam("project", project.getKey())
+ .executeProtobuf(ListWSResponse.class);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getNewCodePeriodsCount()).isEqualTo(6);
+ assertThat(response.getNewCodePeriodsList()).extracting(ShowWSResponse::getBranchKey)
+ .contains("master", "LONG_0", "LONG_1", "LONG_2", "LONG_3", "LONG_4");
+
+ //check if global default is set
+ assertThat(response.getNewCodePeriodsList()).extracting(ShowWSResponse::getType)
+ .contains(NewCodePeriods.NewCodePeriodType.PREVIOUS_VERSION);
+ }
+
+ @Test
+ public void list_inherited_global_settings() {
+ ComponentDto project = componentDb.insertMainBranch();
+ tester.insert(new NewCodePeriodDto().setType(NewCodePeriodType.SPECIFIC_ANALYSIS).setValue("uuid"));
+
+ createBranches(project, 5, BranchType.LONG);
+
+ logInAsProjectAdministrator(project);
+
+ ListWSResponse response = ws.newRequest()
+ .setParam("project", project.getKey())
+ .executeProtobuf(ListWSResponse.class);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getNewCodePeriodsCount()).isEqualTo(6);
+ assertThat(response.getNewCodePeriodsList()).extracting(ShowWSResponse::getBranchKey)
+ .contains("master", "LONG_0", "LONG_1", "LONG_2", "LONG_3", "LONG_4");
+
+ //check if global default is set
+ assertThat(response.getNewCodePeriodsList()).extracting(ShowWSResponse::getType)
+ .contains(NewCodePeriods.NewCodePeriodType.SPECIFIC_ANALYSIS);
+ assertThat(response.getNewCodePeriodsList()).extracting(ShowWSResponse::getValue)
+ .contains("uuid");
+ assertThat(response.getNewCodePeriodsList()).extracting(ShowWSResponse::getInherited)
+ .contains(true);
+ }
+
+ @Test
+ public void list_inherited_project_settings() {
+ ComponentDto projectWithOwnSettings = componentDb.insertMainBranch();
+ ComponentDto projectWithGlobalSettings = componentDb.insertMainBranch();
+ tester.insert(new NewCodePeriodDto()
+ .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
+ .setValue("global_uuid"));
+ tester.insert(new NewCodePeriodDto()
+ .setProjectUuid(projectWithOwnSettings.uuid())
+ .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
+ .setValue("project_uuid"));
+
+ createBranches(projectWithOwnSettings, 5, BranchType.LONG);
+
+ logInAsProjectAdministrator(projectWithOwnSettings, projectWithGlobalSettings);
+
+ ListWSResponse response = ws.newRequest()
+ .setParam("project", projectWithOwnSettings.getKey())
+ .executeProtobuf(ListWSResponse.class);
+
+ //verify project with project level settings
+ assertThat(response).isNotNull();
+ assertThat(response.getNewCodePeriodsCount()).isEqualTo(6);
+ assertThat(response.getNewCodePeriodsList()).extracting(ShowWSResponse::getBranchKey)
+ .contains("master", "LONG_0", "LONG_1", "LONG_2", "LONG_3", "LONG_4");
+
+ //check if project setting is set
+ assertThat(response.getNewCodePeriodsList()).extracting(ShowWSResponse::getType)
+ .contains(NewCodePeriods.NewCodePeriodType.SPECIFIC_ANALYSIS);
+ assertThat(response.getNewCodePeriodsList()).extracting(ShowWSResponse::getValue)
+ .containsOnly("project_uuid");
+ assertThat(response.getNewCodePeriodsList()).extracting(ShowWSResponse::getInherited)
+ .containsOnly(true);
+
+ //verify project with global level settings
+ response = ws.newRequest()
+ .setParam("project", projectWithGlobalSettings.getKey())
+ .executeProtobuf(ListWSResponse.class);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getNewCodePeriodsCount()).isEqualTo(1);
+ assertThat(response.getNewCodePeriodsList()).extracting(ShowWSResponse::getBranchKey)
+ .containsOnly("master");
+
+ //check if global setting is set
+ assertThat(response.getNewCodePeriodsList()).extracting(ShowWSResponse::getType)
+ .contains(NewCodePeriods.NewCodePeriodType.SPECIFIC_ANALYSIS);
+ assertThat(response.getNewCodePeriodsList()).extracting(ShowWSResponse::getValue)
+ .contains("global_uuid");
+ assertThat(response.getNewCodePeriodsList()).extracting(ShowWSResponse::getInherited)
+ .containsOnly(true);
+ }
+
+ @Test
+ public void list_branch_and_inherited_global_settings() {
+ ComponentDto project = componentDb.insertMainBranch();
+ ComponentDto branchWithOwnSettings = componentDb.insertProjectBranch(project, branchDto -> branchDto.setKey("OWN_SETTINGS"));
+ componentDb.insertProjectBranch(project, branchDto -> branchDto.setKey("GLOBAL_SETTINGS"));
+
+ tester.insert(new NewCodePeriodDto()
+ .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
+ .setValue("global_uuid"));
+
+ tester.insert(new NewCodePeriodDto()
+ .setProjectUuid(project.uuid())
+ .setBranchUuid(branchWithOwnSettings.uuid())
+ .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
+ .setValue("branch_uuid"));
+
+ logInAsProjectAdministrator(project);
+
+ ListWSResponse response = ws.newRequest()
+ .setParam("project", project.getKey())
+ .executeProtobuf(ListWSResponse.class);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getNewCodePeriodsCount()).isEqualTo(3);
+ assertThat(response.getNewCodePeriodsList()).extracting(ShowWSResponse::getBranchKey)
+ .contains("master", "OWN_SETTINGS", "GLOBAL_SETTINGS");
+
+ Optional<ShowWSResponse> ownSettings = response.getNewCodePeriodsList().stream()
+ .filter(s -> !s.getInherited())
+ .findFirst();
+
+ assertThat(ownSettings).isNotNull();
+ assertThat(ownSettings).isNotEmpty();
+ assertThat(ownSettings.get().getProjectKey()).isEqualTo(project.getKey());
+ assertThat(ownSettings.get().getBranchKey()).isEqualTo("OWN_SETTINGS");
+ assertThat(ownSettings.get().getType()).isEqualTo(NewCodePeriods.NewCodePeriodType.SPECIFIC_ANALYSIS);
+ assertThat(ownSettings.get().getValue()).isEqualTo("branch_uuid");
+ assertThat(ownSettings.get().getInherited()).isFalse();
+
+ //check if global default is set
+ assertThat(response.getNewCodePeriodsList())
+ .filteredOn(ShowWSResponse::getInherited)
+ .extracting(ShowWSResponse::getType)
+ .contains(NewCodePeriods.NewCodePeriodType.SPECIFIC_ANALYSIS);
+ assertThat(response.getNewCodePeriodsList())
+ .filteredOn(ShowWSResponse::getInherited)
+ .extracting(ShowWSResponse::getValue)
+ .contains("global_uuid");
+ }
+
+ @Test
+ public void list_branch_and_inherited_project_settings() {
+ ComponentDto project = componentDb.insertMainBranch();
+ ComponentDto branchWithOwnSettings = componentDb.insertProjectBranch(project, branchDto -> branchDto.setKey("OWN_SETTINGS"));
+ componentDb.insertProjectBranch(project, branchDto -> branchDto.setKey("PROJECT_SETTINGS"));
+
+ tester.insert(new NewCodePeriodDto()
+ .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
+ .setValue("global_uuid"));
+
+ tester.insert(new NewCodePeriodDto()
+ .setProjectUuid(project.uuid())
+ .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
+ .setValue("project_uuid"));
+
+ tester.insert(new NewCodePeriodDto()
+ .setProjectUuid(project.uuid())
+ .setBranchUuid(branchWithOwnSettings.uuid())
+ .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
+ .setValue("branch_uuid"));
+
+ logInAsProjectAdministrator(project);
+
+ ListWSResponse response = ws.newRequest()
+ .setParam("project", project.getKey())
+ .executeProtobuf(ListWSResponse.class);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getNewCodePeriodsCount()).isEqualTo(3);
+ assertThat(response.getNewCodePeriodsList()).extracting(ShowWSResponse::getBranchKey)
+ .contains("master", "OWN_SETTINGS", "PROJECT_SETTINGS");
+
+ Optional<ShowWSResponse> ownSettings = response.getNewCodePeriodsList().stream()
+ .filter(s -> !s.getInherited())
+ .findFirst();
+
+ assertThat(ownSettings).isNotNull();
+ assertThat(ownSettings).isNotEmpty();
+ assertThat(ownSettings.get().getProjectKey()).isEqualTo(project.getKey());
+ assertThat(ownSettings.get().getBranchKey()).isEqualTo("OWN_SETTINGS");
+ assertThat(ownSettings.get().getType()).isEqualTo(NewCodePeriods.NewCodePeriodType.SPECIFIC_ANALYSIS);
+ assertThat(ownSettings.get().getValue()).isEqualTo("branch_uuid");
+ assertThat(ownSettings.get().getInherited()).isFalse();
+
+ //check if global default is set
+ assertThat(response.getNewCodePeriodsList())
+ .filteredOn(ShowWSResponse::getInherited)
+ .extracting(ShowWSResponse::getType)
+ .contains(NewCodePeriods.NewCodePeriodType.SPECIFIC_ANALYSIS);
+ assertThat(response.getNewCodePeriodsList())
+ .filteredOn(ShowWSResponse::getInherited)
+ .extracting(ShowWSResponse::getValue)
+ .contains("project_uuid");
+ }
+
+ private void createBranches(ComponentDto project, int numberOfBranches, BranchType branchType) {
+ for (int branchCount = 0; branchCount < numberOfBranches; branchCount++) {
+ String branchKey = String.format("%s_%d", branchType.name(), branchCount);
+ componentDb.insertProjectBranch(project, branchDto -> branchDto.setKey(branchKey).setBranchType(branchType));
+ }
+ }
+
+ private void logInAsProjectAdministrator(ComponentDto... project) {
+ userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/SetActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/SetActionTest.java
index 4fe9ee623b0..e18763e72eb 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/SetActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/SetActionTest.java
@@ -102,10 +102,10 @@ public class SetActionTest {
@Test
public void throw_IAE_if_type_is_invalid_for_global() {
expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Invalid type 'DATE'. Overall setting can only be set with types: [PREVIOUS_VERSION, NUMBER_OF_DAYS]");
+ expectedException.expectMessage("Invalid type 'SPECIFIC_ANALYSIS'. Overall setting can only be set with types: [PREVIOUS_VERSION, NUMBER_OF_DAYS]");
ws.newRequest()
- .setParam("type", "date")
+ .setParam("type", "specific_analysis")
.execute();
}
@@ -115,7 +115,7 @@ public class SetActionTest {
logInAsProjectAdministrator(project);
expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Invalid type 'SPECIFIC_ANALYSIS'. Projects can only be set with types: [DATE, PREVIOUS_VERSION, NUMBER_OF_DAYS]");
+ expectedException.expectMessage("Invalid type 'SPECIFIC_ANALYSIS'. Projects can only be set with types: [PREVIOUS_VERSION, NUMBER_OF_DAYS]");
ws.newRequest()
.setParam("project", project.getKey())
@@ -123,20 +123,6 @@ public class SetActionTest {
.execute();
}
- // validation of value
- @Test
- public void throw_IAE_if_no_value_for_date() {
- ComponentDto project = componentDb.insertPublicProject();
- logInAsProjectAdministrator(project);
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("New Code Period type 'DATE' requires a value");
-
- ws.newRequest()
- .setParam("project", project.getKey())
- .setParam("type", "date")
- .execute();
- }
-
@Test
public void throw_IAE_if_no_value_for_days() {
ComponentDto project = componentDb.insertMainBranch();
@@ -166,21 +152,6 @@ public class SetActionTest {
}
@Test
- public void throw_IAE_if_date_is_invalid() {
- ComponentDto project = componentDb.insertMainBranch();
- logInAsProjectAdministrator(project);
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Failed to parse date: unknown");
-
- ws.newRequest()
- .setParam("project", project.getKey())
- .setParam("type", "date")
- .setParam("branch", "master")
- .setParam("value", "unknown")
- .execute();
- }
-
- @Test
public void throw_IAE_if_days_is_invalid() {
ComponentDto project = componentDb.insertMainBranch();
logInAsProjectAdministrator(project);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/ShowActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/ShowActionTest.java
index 069093cdb89..d57733d02da 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/ShowActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/ShowActionTest.java
@@ -173,15 +173,15 @@ public class ShowActionTest {
tester.insert(new NewCodePeriodDto()
.setProjectUuid(project.uuid())
.setBranchUuid(branch.uuid())
- .setType(NewCodePeriodType.DATE)
- .setValue("2018-04-05"));
+ .setType(NewCodePeriodType.NUMBER_OF_DAYS)
+ .setValue("1"));
ShowWSResponse response = ws.newRequest()
.setParam("project", project.getKey())
.setParam("branch", "branch")
.executeProtobuf(ShowWSResponse.class);
- assertResponse(response, project.getKey(), "branch", NewCodePeriods.NewCodePeriodType.DATE, "2018-04-05", false);
+ assertResponse(response, project.getKey(), "branch", NewCodePeriods.NewCodePeriodType.NUMBER_OF_DAYS, "1", false);
}
@Test
@@ -206,15 +206,15 @@ public class ShowActionTest {
tester.insert(new NewCodePeriodDto()
.setProjectUuid(project.uuid())
- .setType(NewCodePeriodType.DATE)
- .setValue("2018-04-05"));
+ .setType(NewCodePeriodType.NUMBER_OF_DAYS)
+ .setValue("1"));
ShowWSResponse response = ws.newRequest()
.setParam("project", project.getKey())
.setParam("branch", "branch")
.executeProtobuf(ShowWSResponse.class);
- assertResponse(response, project.getKey(), "branch", NewCodePeriods.NewCodePeriodType.DATE, "2018-04-05", true);
+ assertResponse(response, project.getKey(), "branch", NewCodePeriods.NewCodePeriodType.NUMBER_OF_DAYS, "1", true);
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/UnsetActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/UnsetActionTest.java
index c3391fa346f..7475a38d2a5 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/UnsetActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/newcodeperiod/ws/UnsetActionTest.java
@@ -173,20 +173,22 @@ public class UnsetActionTest {
@Test
public void delete_project_period_twice() {
- ComponentDto project = componentDb.insertMainBranch();
- db.newCodePeriods().insert(project.uuid(), null, NewCodePeriodType.SPECIFIC_ANALYSIS, "uuid");
+ ComponentDto project1 = componentDb.insertMainBranch();
+ ComponentDto project2 = componentDb.insertMainBranch();
+ db.newCodePeriods().insert(project1.uuid(), null, NewCodePeriodType.SPECIFIC_ANALYSIS, "uuid1");
+ db.newCodePeriods().insert(project2.uuid(), null, NewCodePeriodType.SPECIFIC_ANALYSIS, "uuid2");
- logInAsProjectAdministrator(project);
+ logInAsProjectAdministrator(project1);
ws.newRequest()
- .setParam("project", project.getKey())
+ .setParam("project", project1.getKey())
.execute();
- assertTableEmpty();
+ assertTableContainsOnly(project2.uuid(), null, NewCodePeriodType.SPECIFIC_ANALYSIS, "uuid2");
ws.newRequest()
- .setParam("project", project.getKey())
+ .setParam("project", project1.getKey())
.execute();
- assertTableEmpty();
+ assertTableContainsOnly(project2.uuid(), null, NewCodePeriodType.SPECIFIC_ANALYSIS, "uuid2");
}
@Test
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/DeleteNewCodePeriodRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/DeleteNewCodePeriodRequest.java
deleted file mode 100644
index 9b81df3ebe7..00000000000
--- a/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/DeleteNewCodePeriodRequest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.sonarqube.ws.client.settings;
-
-import java.util.List;
-import javax.annotation.Generated;
-
-/**
- * This is part of the internal API.
- * This is a POST request.
- * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/settings/delete_new_code_period">Further information about this action online (including a response example)</a>
- * @since 8.0
- */
-@Generated("sonar-ws-generator")
-public class DeleteNewCodePeriodRequest {
-
- private String branch;
- private String project;
-
- /**
- */
- public DeleteNewCodePeriodRequest setBranch(String branch) {
- this.branch = branch;
- return this;
- }
-
- public String getBranch() {
- return branch;
- }
-
- /**
- */
- public DeleteNewCodePeriodRequest setProject(String project) {
- this.project = project;
- return this;
- }
-
- public String getProject() {
- return project;
- }
-}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/ShowNewCodePeriodRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/ShowNewCodePeriodRequest.java
deleted file mode 100644
index 72428c5c742..00000000000
--- a/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/ShowNewCodePeriodRequest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.sonarqube.ws.client.settings;
-
-import java.util.List;
-import javax.annotation.Generated;
-
-/**
- * This is part of the internal API.
- * This is a POST request.
- * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/settings/show_new_code_period">Further information about this action online (including a response example)</a>
- * @since 8.0
- */
-@Generated("sonar-ws-generator")
-public class ShowNewCodePeriodRequest {
-
- private String branch;
- private String project;
-
- /**
- */
- public ShowNewCodePeriodRequest setBranch(String branch) {
- this.branch = branch;
- return this;
- }
-
- public String getBranch() {
- return branch;
- }
-
- /**
- */
- public ShowNewCodePeriodRequest setProject(String project) {
- this.project = project;
- return this;
- }
-
- public String getProject() {
- return project;
- }
-}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/UpdateNewCodePeriodRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/UpdateNewCodePeriodRequest.java
deleted file mode 100644
index 9091527e6e4..00000000000
--- a/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/UpdateNewCodePeriodRequest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.sonarqube.ws.client.settings;
-
-import javax.annotation.Generated;
-
-/**
- * This is part of the internal API.
- * This is a POST request.
- *
- * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/settings/update_new_code_period">Further information about this action online (including a response example)</a>
- * @since 8.0
- */
-@Generated("sonar-ws-generator")
-public class UpdateNewCodePeriodRequest {
-
- private String branch;
- private String project;
- private String type;
- private String value;
-
- /**
- *
- */
- public UpdateNewCodePeriodRequest setBranch(String branch) {
- this.branch = branch;
- return this;
- }
-
- public String getBranch() {
- return branch;
- }
-
- /**
- *
- */
- public UpdateNewCodePeriodRequest setProject(String project) {
- this.project = project;
- return this;
- }
-
- public String getProject() {
- return project;
- }
-
- /**
- * This is a mandatory parameter.
- */
- public UpdateNewCodePeriodRequest setType(String type) {
- this.type = type;
- return this;
- }
-
- public String getType() {
- return type;
- }
-
- /**
- *
- */
- public UpdateNewCodePeriodRequest setValue(String value) {
- this.value = value;
- return this;
- }
-
- public String getValue() {
- return value;
- }
-}
diff --git a/sonar-ws/src/main/protobuf/ws-newcodeperiods.proto b/sonar-ws/src/main/protobuf/ws-newcodeperiods.proto
index bfb3caf5fde..384e4d15399 100644
--- a/sonar-ws/src/main/protobuf/ws-newcodeperiods.proto
+++ b/sonar-ws/src/main/protobuf/ws-newcodeperiods.proto
@@ -16,27 +16,30 @@
// along with this program; if not, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-syntax = "proto2";
+syntax = "proto3";
package sonarqube.ws.batch;
option java_package = "org.sonarqube.ws";
option java_outer_classname = "NewCodePeriods";
-
option optimize_for = SPEED;
// WS api/new_code_periods/show
message ShowWSResponse {
- optional string projectKey = 1;
- optional string branchKey = 2;
- required NewCodePeriodType type = 3;
- optional string value = 4;
- required bool inherited = 5;
+ string projectKey = 1;
+ string branchKey = 2;
+ NewCodePeriodType type = 3;
+ string value = 4;
+ bool inherited = 5;
+}
+
+// WS api/new_code_periods/list
+message ListWSResponse {
+ repeated ShowWSResponse newCodePeriods = 1;
}
enum NewCodePeriodType {
PREVIOUS_VERSION = 0;
NUMBER_OF_DAYS = 1;
- DATE = 2;
- SPECIFIC_ANALYSIS = 3;
+ SPECIFIC_ANALYSIS = 2;
}