diff options
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; } |