Co-authored-by: Nolwenn Cadic <nolwenn.cadic@sonarsource.com>tags/10.1.0.73491
@@ -29,10 +29,13 @@ import org.sonar.alm.client.gitlab.GitlabHttpClient; | |||
import org.sonar.alm.client.gitlab.Project; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.core.i18n.I18n; | |||
import org.sonar.core.platform.EditionProvider; | |||
import org.sonar.core.platform.PlatformEditionProvider; | |||
import org.sonar.core.util.SequenceUuidFactory; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.alm.setting.AlmSettingDto; | |||
import org.sonar.db.component.BranchDto; | |||
import org.sonar.db.newcodeperiod.NewCodePeriodDto; | |||
import org.sonar.db.project.ProjectDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.almintegration.ws.ImportHelper; | |||
@@ -40,6 +43,7 @@ import org.sonar.server.almintegration.ws.ProjectKeyGenerator; | |||
import org.sonar.server.component.ComponentUpdater; | |||
import org.sonar.server.es.TestProjectIndexers; | |||
import org.sonar.server.favorite.FavoriteUpdater; | |||
import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; | |||
import org.sonar.server.permission.PermissionTemplateService; | |||
import org.sonar.server.project.DefaultBranchNameResolver; | |||
import org.sonar.server.project.ProjectDefaultVisibility; | |||
@@ -58,8 +62,11 @@ import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.when; | |||
import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME; | |||
import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS; | |||
import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; | |||
import static org.sonar.server.tester.UserSessionRule.standalone; | |||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_TYPE; | |||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_VALUE; | |||
public class ImportGitLabProjectActionIT { | |||
@@ -83,8 +90,11 @@ public class ImportGitLabProjectActionIT { | |||
private final ImportHelper importHelper = new ImportHelper(db.getDbClient(), userSession); | |||
private final ProjectDefaultVisibility projectDefaultVisibility = mock(ProjectDefaultVisibility.class); | |||
private final ProjectKeyGenerator projectKeyGenerator = mock(ProjectKeyGenerator.class); | |||
private PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class); | |||
private NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider); | |||
private final ImportGitLabProjectAction importGitLabProjectAction = new ImportGitLabProjectAction( | |||
db.getDbClient(), userSession, projectDefaultVisibility, gitlabHttpClient, componentUpdater, importHelper, projectKeyGenerator); | |||
db.getDbClient(), userSession, projectDefaultVisibility, gitlabHttpClient, componentUpdater, importHelper, projectKeyGenerator, newCodeDefinitionResolver, | |||
defaultBranchNameResolver); | |||
private final WsActionTester ws = new WsActionTester(importGitLabProjectAction); | |||
@Before | |||
@@ -94,7 +104,9 @@ public class ImportGitLabProjectActionIT { | |||
} | |||
@Test | |||
public void import_project() { | |||
public void import_project_developer_edition() { | |||
when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER)); | |||
UserDto user = db.users().insertUser(); | |||
userSession.logIn(user).addPermission(PROVISION_PROJECTS); | |||
AlmSettingDto almSetting = db.almSettings().insertGitlabAlmSetting(); | |||
@@ -111,6 +123,8 @@ public class ImportGitLabProjectActionIT { | |||
Projects.CreateWsResponse response = ws.newRequest() | |||
.setParam("almSetting", almSetting.getKey()) | |||
.setParam("gitlabProjectId", "12345") | |||
.setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "NUMBER_OF_DAYS") | |||
.setParam(PARAM_NEW_CODE_DEFINITION_VALUE, "30") | |||
.executeProtobuf(Projects.CreateWsResponse.class); | |||
verify(gitlabHttpClient).getProject(almSetting.getUrl(), "PAT", 12345L); | |||
@@ -122,6 +136,48 @@ public class ImportGitLabProjectActionIT { | |||
Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); | |||
assertThat(projectDto).isPresent(); | |||
assertThat(db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto.get())).isPresent(); | |||
assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(), projectDto.get().getUuid())) | |||
.isPresent() | |||
.get() | |||
.extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue, NewCodePeriodDto::getBranchUuid) | |||
.containsExactly(NUMBER_OF_DAYS, "30", null); | |||
} | |||
@Test | |||
public void import_project_community_edition() { | |||
when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.COMMUNITY)); | |||
UserDto user = db.users().insertUser(); | |||
userSession.logIn(user).addPermission(PROVISION_PROJECTS); | |||
AlmSettingDto almSetting = db.almSettings().insertGitlabAlmSetting(); | |||
db.almPats().insert(dto -> { | |||
dto.setAlmSettingUuid(almSetting.getUuid()); | |||
dto.setUserUuid(user.getUuid()); | |||
dto.setPersonalAccessToken("PAT"); | |||
}); | |||
Project project = getGitlabProject(); | |||
when(gitlabHttpClient.getProject(any(), any(), any())).thenReturn(project); | |||
when(gitlabHttpClient.getBranches(any(), any(), any())).thenReturn(singletonList(new GitLabBranch("master", true))); | |||
when(projectKeyGenerator.generateUniqueProjectKey(project.getPathWithNamespace())).thenReturn(PROJECT_KEY_NAME); | |||
Projects.CreateWsResponse response = ws.newRequest() | |||
.setParam("almSetting", almSetting.getKey()) | |||
.setParam("gitlabProjectId", "12345") | |||
.setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "NUMBER_OF_DAYS") | |||
.setParam(PARAM_NEW_CODE_DEFINITION_VALUE, "30") | |||
.executeProtobuf(Projects.CreateWsResponse.class); | |||
Projects.CreateWsResponse.Project result = response.getProject(); | |||
Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); | |||
String projectUuid = projectDto.get().getUuid(); | |||
assertThat(db.getDbClient().newCodePeriodDao().selectByBranch(db.getSession(), projectUuid, projectUuid)) | |||
.isPresent() | |||
.get() | |||
.extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue, NewCodePeriodDto::getBranchUuid) | |||
.containsExactly(NUMBER_OF_DAYS, "30", projectUuid); | |||
} | |||
@Test | |||
@@ -197,6 +253,38 @@ public class ImportGitLabProjectActionIT { | |||
} | |||
@Test | |||
public void import_project_without_NCD() { | |||
UserDto user = db.users().insertUser(); | |||
userSession.logIn(user).addPermission(PROVISION_PROJECTS); | |||
AlmSettingDto almSetting = db.almSettings().insertGitlabAlmSetting(); | |||
db.almPats().insert(dto -> { | |||
dto.setAlmSettingUuid(almSetting.getUuid()); | |||
dto.setUserUuid(user.getUuid()); | |||
dto.setPersonalAccessToken("PAT"); | |||
}); | |||
Project project = getGitlabProject(); | |||
when(gitlabHttpClient.getProject(any(), any(), any())).thenReturn(project); | |||
when(gitlabHttpClient.getBranches(any(), any(), any())).thenReturn(singletonList(new GitLabBranch("master", true))); | |||
when(projectKeyGenerator.generateUniqueProjectKey(project.getPathWithNamespace())).thenReturn(PROJECT_KEY_NAME); | |||
Projects.CreateWsResponse response = ws.newRequest() | |||
.setParam("almSetting", almSetting.getKey()) | |||
.setParam("gitlabProjectId", "12345") | |||
.executeProtobuf(Projects.CreateWsResponse.class); | |||
verify(gitlabHttpClient).getProject(almSetting.getUrl(), "PAT", 12345L); | |||
Projects.CreateWsResponse.Project result = response.getProject(); | |||
assertThat(result.getKey()).isEqualTo(PROJECT_KEY_NAME); | |||
assertThat(result.getName()).isEqualTo(project.getName()); | |||
Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); | |||
assertThat(projectDto).isPresent(); | |||
assertThat(db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto.get())).isPresent(); | |||
} | |||
private Project getGitlabProject() { | |||
return new Project(randomAlphanumeric(5), randomAlphanumeric(5)); | |||
} |
@@ -0,0 +1,148 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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; | |||
import java.util.Optional; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.core.platform.PlatformEditionProvider; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDbTester; | |||
import org.sonar.db.newcodeperiod.NewCodePeriodDto; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.assertThatNoException; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
import static org.mockito.Mockito.mock; | |||
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.REFERENCE_BRANCH; | |||
import static org.sonar.db.newcodeperiod.NewCodePeriodType.SPECIFIC_ANALYSIS; | |||
public class NewCodeDefinitionResolverTest { | |||
@Rule | |||
public DbTester db = DbTester.create(System2.INSTANCE, true); | |||
private static final String DEFAULT_PROJECT_ID = "12345"; | |||
private static final String MAIN_BRANCH = "main"; | |||
private ComponentDbTester componentDb = new ComponentDbTester(db); | |||
private DbSession dbSession = db.getSession(); | |||
private DbClient dbClient = db.getDbClient(); | |||
private PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class); | |||
private NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider); | |||
@Test | |||
public void createNewCodeDefinition_throw_IAE_if_no_valid_type() { | |||
assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH, "nonValid", null)) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("Invalid type: nonValid"); | |||
} | |||
@Test | |||
public void createNewCodeDefinition_throw_IAE_if_type_is_not_allowed() { | |||
assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH, SPECIFIC_ANALYSIS.name(), null)) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("Invalid type 'SPECIFIC_ANALYSIS'. `newCodeDefinitionType` can only be set with types: [PREVIOUS_VERSION, NUMBER_OF_DAYS, REFERENCE_BRANCH]"); | |||
} | |||
@Test | |||
public void createNewCodeDefinition_throw_IAE_if_no_value_for_days() { | |||
assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH, NUMBER_OF_DAYS.name(), null)) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("New code definition type 'NUMBER_OF_DAYS' requires a newCodeDefinitionValue"); | |||
} | |||
@Test | |||
public void createNewCodeDefinition_throw_IAE_if_days_is_invalid() { | |||
assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH, NUMBER_OF_DAYS.name(), "unknown")) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("Failed to parse number of days: unknown"); | |||
} | |||
@Test | |||
public void createNewCodeDefinition_throw_IAE_if_value_is_set_for_reference_branch() { | |||
assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH, REFERENCE_BRANCH.name(), "feature/zw")) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("Unexpected value for newCodeDefinitionType 'REFERENCE_BRANCH'"); | |||
} | |||
@Test | |||
public void createNewCodeDefinition_throw_IAE_if_previous_version_type_and_value_provided() { | |||
assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH, PREVIOUS_VERSION.name(), "10.2.3")) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("Unexpected value for newCodeDefinitionType 'PREVIOUS_VERSION'"); | |||
} | |||
@Test | |||
public void createNewCodeDefinition_persist_previous_version_type() { | |||
newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH, PREVIOUS_VERSION.name(), null); | |||
Optional<NewCodePeriodDto> newCodePeriodDto = dbClient.newCodePeriodDao().selectByProject(dbSession, DEFAULT_PROJECT_ID); | |||
assertThat(newCodePeriodDto).map(NewCodePeriodDto::getType).hasValue(PREVIOUS_VERSION); | |||
} | |||
@Test | |||
public void createNewCodeDefinition_return_days_value_for_number_of_days_type() { | |||
String numberOfDays = "30"; | |||
newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH, NUMBER_OF_DAYS.name(), numberOfDays); | |||
Optional<NewCodePeriodDto> newCodePeriodDto = dbClient.newCodePeriodDao().selectByProject(dbSession, DEFAULT_PROJECT_ID); | |||
assertThat(newCodePeriodDto) | |||
.isPresent() | |||
.get() | |||
.extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue) | |||
.containsExactly(NUMBER_OF_DAYS, numberOfDays); | |||
} | |||
@Test | |||
public void createNewCodeDefinition_return_branch_value_for_reference_branch_type() { | |||
newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH, REFERENCE_BRANCH.name(), null); | |||
Optional<NewCodePeriodDto> newCodePeriodDto = dbClient.newCodePeriodDao().selectByProject(dbSession, DEFAULT_PROJECT_ID); | |||
assertThat(newCodePeriodDto) | |||
.isPresent() | |||
.get() | |||
.extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue) | |||
.containsExactly(REFERENCE_BRANCH, MAIN_BRANCH); | |||
} | |||
@Test | |||
public void checkNewCodeDefinitionParam_throw_IAE_if_newCodeDefinitionValue_is_provided_without_newCodeDefinitionType() { | |||
assertThatThrownBy(() -> newCodeDefinitionResolver.checkNewCodeDefinitionParam(null, "anyvalue")) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("New code definition type is required when new code definition value is provided"); | |||
} | |||
@Test | |||
public void checkNewCodeDefinitionParam_do_not_throw_when_both_value_and_type_are_provided() { | |||
assertThatNoException() | |||
.isThrownBy(() -> newCodeDefinitionResolver.checkNewCodeDefinitionParam("PREVIOUS_VERSION", "anyvalue")); | |||
} | |||
} |
@@ -1,217 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.BranchDto; | |||
import org.sonar.db.component.ComponentDbTester; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.component.SnapshotDto; | |||
import org.sonar.db.project.ProjectDto; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat; | |||
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.REFERENCE_BRANCH; | |||
import static org.sonar.db.newcodeperiod.NewCodePeriodType.SPECIFIC_ANALYSIS; | |||
import static org.sonar.server.newcodeperiod.NewCodePeriodUtils.getNewCodeDefinitionValue; | |||
import static org.sonar.server.newcodeperiod.NewCodePeriodUtils.getNewCodeDefinitionValueProjectCreation; | |||
import static org.sonar.server.newcodeperiod.NewCodePeriodUtils.validateType; | |||
public class NewCodePeriodUtilsTest { | |||
@Rule | |||
public DbTester db = DbTester.create(System2.INSTANCE, true); | |||
private static final String MAIN_BRANCH = "main"; | |||
private ComponentDbTester componentDb = new ComponentDbTester(db); | |||
private DbSession dbSession = db.getSession(); | |||
private DbClient dbClient = db.getDbClient(); | |||
@Test | |||
public void validateType_throw_IAE_if_no_valid_type() { | |||
assertThatThrownBy(() -> validateType("nonValid", false, false)) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("Invalid type: nonValid"); | |||
} | |||
@Test | |||
public void validateType_throw_IAE_if_type_is_invalid_for_global() { | |||
assertThatThrownBy(() -> validateType("SPECIFIC_ANALYSIS", true, false)) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("Invalid type 'SPECIFIC_ANALYSIS'. Overall setting can only be set with types: [PREVIOUS_VERSION, NUMBER_OF_DAYS]"); | |||
} | |||
@Test | |||
public void validateType_throw_IAE_if_type_is_invalid_for_project() { | |||
assertThatThrownBy(() -> validateType("SPECIFIC_ANALYSIS", false, false)) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("Invalid type 'SPECIFIC_ANALYSIS'. Projects can only be set with types: [PREVIOUS_VERSION, NUMBER_OF_DAYS, REFERENCE_BRANCH]"); | |||
} | |||
@Test | |||
public void validateType_return_type_for_branch() { | |||
assertThat(validateType("REFERENCE_BRANCH", false, true)).isEqualTo(REFERENCE_BRANCH); | |||
} | |||
@Test | |||
public void validateType_return_type_for_project() { | |||
assertThat(validateType("REFERENCE_BRANCH", false, false)).isEqualTo(REFERENCE_BRANCH); | |||
} | |||
@Test | |||
public void validateType_return_type_for_overall() { | |||
assertThat(validateType("PREVIOUS_VERSION", true, false)).isEqualTo(PREVIOUS_VERSION); | |||
} | |||
@Test | |||
public void getNCDValue_throw_IAE_if_no_value_for_days() { | |||
assertThatThrownBy(() -> getNewCodeDefinitionValue(dbSession, dbClient, NUMBER_OF_DAYS, null, null, null)) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("New code definition type 'NUMBER_OF_DAYS' requires a value"); | |||
} | |||
@Test | |||
public void getNCDValue_throw_IAE_if_no_value_for_reference_branch() { | |||
assertThatThrownBy(() -> getNewCodeDefinitionValue(dbSession, dbClient, REFERENCE_BRANCH, null, null, null)) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("New code definition type 'REFERENCE_BRANCH' requires a value"); | |||
} | |||
@Test | |||
public void getNCDValue_throw_IAE_if_no_value_for_analysis() { | |||
assertThatThrownBy(() -> getNewCodeDefinitionValue(dbSession, dbClient, SPECIFIC_ANALYSIS, null, null, null)) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("New code definition type 'SPECIFIC_ANALYSIS' requires a value"); | |||
} | |||
@Test | |||
public void getNCDValue_throw_IAE_if_days_is_invalid() { | |||
assertThatThrownBy(() -> getNewCodeDefinitionValue(dbSession, dbClient, NUMBER_OF_DAYS, null, null, "unknown")) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("Failed to parse number of days: unknown"); | |||
} | |||
@Test | |||
public void getNCDValue_throw_IAE_if_previous_version_type_and_value_provided() { | |||
assertThatThrownBy(() -> getNewCodeDefinitionValue(dbSession, dbClient, PREVIOUS_VERSION, null, null, "someValue")) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("Unexpected value for type 'PREVIOUS_VERSION'"); | |||
} | |||
@Test | |||
public void getNCDValue_return_empty_for_previous_version_type() { | |||
assertThat(getNewCodeDefinitionValue(dbSession, dbClient, PREVIOUS_VERSION, null, null, null)).isEmpty(); | |||
} | |||
@Test | |||
public void getNCDValue_return_days_value_for_number_of_days_type() { | |||
String numberOfDays = "30"; | |||
assertThat(getNewCodeDefinitionValue(dbSession, dbClient, NUMBER_OF_DAYS, null, null, numberOfDays)) | |||
.isPresent() | |||
.get() | |||
.isEqualTo(numberOfDays); | |||
} | |||
@Test | |||
public void getNCDValue_return_specific_analysis_uuid_for_specific_analysis_type() { | |||
ComponentDto project = componentDb.insertPublicProject().getMainBranchComponent(); | |||
SnapshotDto analysisMaster = db.components().insertSnapshot(project); | |||
ProjectDto projectDto = new ProjectDto().setUuid(project.uuid()); | |||
BranchDto branchDto = new BranchDto().setUuid(project.uuid()); | |||
String numberOfDays = "30"; | |||
assertThat(getNewCodeDefinitionValue(dbSession, dbClient, SPECIFIC_ANALYSIS, projectDto, branchDto, analysisMaster.getUuid())) | |||
.isPresent() | |||
.get() | |||
.isEqualTo(analysisMaster.getUuid()); | |||
} | |||
@Test | |||
public void getNCDValue_return_branch_value_for_reference_branch_type() { | |||
String branchKey = "main"; | |||
assertThat(getNewCodeDefinitionValue(dbSession, dbClient, REFERENCE_BRANCH, null, null, branchKey)) | |||
.isPresent() | |||
.get() | |||
.isEqualTo(branchKey); | |||
} | |||
@Test | |||
public void getNCDValueProjectCreation_throw_IAE_if_no_value_for_days() { | |||
assertThatThrownBy(() -> getNewCodeDefinitionValueProjectCreation(NUMBER_OF_DAYS, null, MAIN_BRANCH)) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("New code definition type 'NUMBER_OF_DAYS' requires a value"); | |||
} | |||
@Test | |||
public void getNCDValueProjectCreation_throw_IAE_if_days_is_invalid() { | |||
assertThatThrownBy(() -> getNewCodeDefinitionValueProjectCreation(NUMBER_OF_DAYS, "unknown", MAIN_BRANCH)) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("Failed to parse number of days: unknown"); | |||
} | |||
@Test | |||
public void getNCDValueProjectCreation_throw_IAE_if_previous_version_type_and_value_provided() { | |||
assertThatThrownBy(() -> getNewCodeDefinitionValueProjectCreation(PREVIOUS_VERSION, "someValue", MAIN_BRANCH)) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("Unexpected value for type 'PREVIOUS_VERSION'"); | |||
} | |||
@Test | |||
public void getNCDValueProjectCreation_return_empty_for_previous_version_type() { | |||
assertThat(getNewCodeDefinitionValueProjectCreation(PREVIOUS_VERSION, null, MAIN_BRANCH)).isEmpty(); | |||
} | |||
@Test | |||
public void getNCDValueProjectCreation_return_days_value_for_number_of_days_type() { | |||
String numberOfDays = "30"; | |||
assertThat(getNewCodeDefinitionValueProjectCreation(NUMBER_OF_DAYS, numberOfDays, MAIN_BRANCH)) | |||
.isPresent() | |||
.get() | |||
.isEqualTo(numberOfDays); | |||
} | |||
@Test | |||
public void getNCDValueProjectCreation_return_branch_value_for_reference_branch_type() { | |||
String branchKey = "main"; | |||
assertThat(getNewCodeDefinitionValueProjectCreation(REFERENCE_BRANCH, null, branchKey)) | |||
.isPresent() | |||
.get() | |||
.isEqualTo(branchKey); | |||
} | |||
@Test | |||
public void getNCDValueProjectCreation_throw_IAE_fiif_reference_branch_type_and_value_provided() { | |||
assertThatThrownBy(() -> getNewCodeDefinitionValueProjectCreation(REFERENCE_BRANCH, "someValue", MAIN_BRANCH)) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessageContaining("Unexpected value for type 'REFERENCE_BRANCH'"); | |||
} | |||
} |
@@ -43,6 +43,7 @@ import org.sonar.server.exceptions.BadRequestException; | |||
import org.sonar.server.exceptions.ForbiddenException; | |||
import org.sonar.server.favorite.FavoriteUpdater; | |||
import org.sonar.server.l18n.I18nRule; | |||
import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; | |||
import org.sonar.server.permission.PermissionTemplateService; | |||
import org.sonar.server.project.DefaultBranchNameResolver; | |||
import org.sonar.server.project.ProjectDefaultVisibility; | |||
@@ -66,8 +67,8 @@ import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME; | |||
import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS; | |||
import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH; | |||
import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; | |||
import static org.sonar.server.newcodeperiod.NewCodePeriodUtils.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; | |||
import static org.sonar.server.newcodeperiod.NewCodePeriodUtils.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; | |||
import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; | |||
import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; | |||
import static org.sonar.server.project.Visibility.PRIVATE; | |||
import static org.sonar.test.JsonAssert.assertJson; | |||
import static org.sonarqube.ws.client.WsRequest.Method.POST; | |||
@@ -99,12 +100,14 @@ public class CreateActionIT { | |||
private final PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class); | |||
private PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class); | |||
private NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider); | |||
private final WsActionTester ws = new WsActionTester( | |||
new CreateAction( | |||
db.getDbClient(), userSession, | |||
new ComponentUpdater(db.getDbClient(), i18n, system2, permissionTemplateService, new FavoriteUpdater(db.getDbClient()), | |||
projectIndexers, new SequenceUuidFactory(), defaultBranchNameResolver, true), | |||
projectDefaultVisibility, editionProvider, defaultBranchNameResolver)); | |||
projectDefaultVisibility, defaultBranchNameResolver, newCodeDefinitionResolver)); | |||
@Before | |||
public void before() { |
@@ -21,6 +21,7 @@ package org.sonar.server.almintegration.ws.gitlab; | |||
import java.util.Optional; | |||
import javax.annotation.Nullable; | |||
import javax.inject.Inject; | |||
import org.sonar.alm.client.gitlab.GitLabBranch; | |||
import org.sonar.alm.client.gitlab.GitlabHttpClient; | |||
import org.sonar.alm.client.gitlab.Project; | |||
@@ -38,6 +39,8 @@ import org.sonar.server.almintegration.ws.ImportHelper; | |||
import org.sonar.server.almintegration.ws.ProjectKeyGenerator; | |||
import org.sonar.server.component.ComponentCreationData; | |||
import org.sonar.server.component.ComponentUpdater; | |||
import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; | |||
import org.sonar.server.project.DefaultBranchNameResolver; | |||
import org.sonar.server.project.ProjectDefaultVisibility; | |||
import org.sonar.server.user.UserSession; | |||
import org.sonarqube.ws.Projects.CreateWsResponse; | |||
@@ -45,7 +48,12 @@ import org.sonarqube.ws.Projects.CreateWsResponse; | |||
import static java.util.Objects.requireNonNull; | |||
import static org.sonar.api.resources.Qualifiers.PROJECT; | |||
import static org.sonar.server.component.NewComponent.newComponentBuilder; | |||
import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; | |||
import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; | |||
import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; | |||
import static org.sonar.server.ws.WsUtils.writeProtobuf; | |||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_TYPE; | |||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_VALUE; | |||
public class ImportGitLabProjectAction implements AlmIntegrationsWsAction { | |||
@@ -58,10 +66,14 @@ public class ImportGitLabProjectAction implements AlmIntegrationsWsAction { | |||
private final ComponentUpdater componentUpdater; | |||
private final ImportHelper importHelper; | |||
private final ProjectKeyGenerator projectKeyGenerator; | |||
private final NewCodeDefinitionResolver newCodeDefinitionResolver; | |||
private final DefaultBranchNameResolver defaultBranchNameResolver; | |||
@Inject | |||
public ImportGitLabProjectAction(DbClient dbClient, UserSession userSession, | |||
ProjectDefaultVisibility projectDefaultVisibility, GitlabHttpClient gitlabHttpClient, | |||
ComponentUpdater componentUpdater, ImportHelper importHelper, ProjectKeyGenerator projectKeyGenerator) { | |||
ComponentUpdater componentUpdater, ImportHelper importHelper, ProjectKeyGenerator projectKeyGenerator, NewCodeDefinitionResolver newCodeDefinitionResolver, | |||
DefaultBranchNameResolver defaultBranchNameResolver) { | |||
this.dbClient = dbClient; | |||
this.userSession = userSession; | |||
this.projectDefaultVisibility = projectDefaultVisibility; | |||
@@ -69,6 +81,8 @@ public class ImportGitLabProjectAction implements AlmIntegrationsWsAction { | |||
this.componentUpdater = componentUpdater; | |||
this.importHelper = importHelper; | |||
this.projectKeyGenerator = projectKeyGenerator; | |||
this.newCodeDefinitionResolver = newCodeDefinitionResolver; | |||
this.defaultBranchNameResolver = defaultBranchNameResolver; | |||
} | |||
@Override | |||
@@ -86,6 +100,14 @@ public class ImportGitLabProjectAction implements AlmIntegrationsWsAction { | |||
action.createParam(PARAM_GITLAB_PROJECT_ID) | |||
.setRequired(true) | |||
.setDescription("GitLab project ID"); | |||
action.createParam(PARAM_NEW_CODE_DEFINITION_TYPE) | |||
.setDescription(NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION) | |||
.setSince("10.1"); | |||
action.createParam(PARAM_NEW_CODE_DEFINITION_VALUE) | |||
.setDescription(NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION) | |||
.setSince("10.1"); | |||
} | |||
@Override | |||
@@ -96,12 +118,13 @@ public class ImportGitLabProjectAction implements AlmIntegrationsWsAction { | |||
private CreateWsResponse doHandle(Request request) { | |||
importHelper.checkProvisionProjectPermission(); | |||
AlmSettingDto almSettingDto = importHelper.getAlmSetting(request); | |||
String userUuid = importHelper.getUserUuid(); | |||
String newCodeDefinitionType = request.param(PARAM_NEW_CODE_DEFINITION_TYPE); | |||
String newCodeDefinitionValue = request.param(PARAM_NEW_CODE_DEFINITION_VALUE); | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
Optional<AlmPatDto> almPatDto = dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto); | |||
String pat = almPatDto.map(AlmPatDto::getPersonalAccessToken) | |||
.orElseThrow(() -> new IllegalArgumentException(String.format("personal access token for '%s' is missing", almSettingDto.getKey()))); | |||
AlmSettingDto almSettingDto = importHelper.getAlmSetting(request); | |||
String pat = getPat(dbSession, almSettingDto); | |||
long gitlabProjectId = request.mandatoryParamAsLong(PARAM_GITLAB_PROJECT_ID); | |||
@@ -109,15 +132,31 @@ public class ImportGitLabProjectAction implements AlmIntegrationsWsAction { | |||
Project gitlabProject = gitlabHttpClient.getProject(gitlabUrl, pat, gitlabProjectId); | |||
Optional<String> almMainBranchName = getAlmDefaultBranch(pat, gitlabProjectId, gitlabUrl); | |||
ComponentCreationData componentCreationData = createProject(dbSession, gitlabProject, almMainBranchName.orElse(null)); | |||
ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); | |||
populateMRSetting(dbSession, gitlabProjectId, projectDto, almSettingDto); | |||
checkNewCodeDefinitionParam(newCodeDefinitionType, newCodeDefinitionValue); | |||
if (newCodeDefinitionType != null) { | |||
newCodeDefinitionResolver.createNewCodeDefinition(dbSession, projectDto.getUuid(), almMainBranchName.orElse(defaultBranchNameResolver.getEffectiveMainBranchName()), | |||
newCodeDefinitionType, newCodeDefinitionValue); | |||
} | |||
componentUpdater.commitAndIndex(dbSession, componentCreationData.mainBranchComponent()); | |||
return ImportHelper.toCreateResponse(projectDto); | |||
} | |||
} | |||
private String getPat(DbSession dbSession, AlmSettingDto almSettingDto) { | |||
String userUuid = importHelper.getUserUuid(); | |||
Optional<AlmPatDto> almPatDto = dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto); | |||
return almPatDto.map(AlmPatDto::getPersonalAccessToken) | |||
.orElseThrow(() -> new IllegalArgumentException(String.format("personal access token for '%s' is missing", almSettingDto.getKey()))); | |||
} | |||
private Optional<String> getAlmDefaultBranch(String pat, long gitlabProjectId, String gitlabUrl) { | |||
Optional<GitLabBranch> almMainBranch = gitlabHttpClient.getBranches(gitlabUrl, pat, gitlabProjectId).stream().filter(GitLabBranch::isDefault).findFirst(); | |||
return almMainBranch.map(GitLabBranch::getName); |
@@ -0,0 +1,148 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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; | |||
import com.google.common.base.Preconditions; | |||
import java.util.EnumSet; | |||
import java.util.Locale; | |||
import java.util.Optional; | |||
import javax.annotation.Nullable; | |||
import org.sonar.core.platform.EditionProvider; | |||
import org.sonar.core.platform.PlatformEditionProvider; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.newcodeperiod.NewCodePeriodDto; | |||
import org.sonar.db.newcodeperiod.NewCodePeriodParser; | |||
import org.sonar.db.newcodeperiod.NewCodePeriodType; | |||
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.REFERENCE_BRANCH; | |||
public class NewCodeDefinitionResolver { | |||
private static final String BEGIN_LIST = "<ul>"; | |||
private static final String END_LIST = "</ul>"; | |||
private static final String BEGIN_ITEM_LIST = "<li>"; | |||
private static final String END_ITEM_LIST = "</li>"; | |||
public static final String NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION = "Type<br/>" + | |||
"New code definitions of the following types are allowed:" + | |||
BEGIN_LIST + | |||
BEGIN_ITEM_LIST + PREVIOUS_VERSION.name() + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + NUMBER_OF_DAYS.name() + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + REFERENCE_BRANCH.name() + " - will default to the main branch." + END_ITEM_LIST + | |||
END_LIST; | |||
public static final String NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION = "Value<br/>" + | |||
"For each new code definition type, a different value is expected:" + | |||
BEGIN_LIST + | |||
BEGIN_ITEM_LIST + "no value, when the new code definition type is " + PREVIOUS_VERSION.name() + " and " + REFERENCE_BRANCH.name() + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + "a number between 1 and 90, when the new code definition type is " + NUMBER_OF_DAYS.name() + END_ITEM_LIST + | |||
END_LIST; | |||
private static final String UNEXPECTED_VALUE_ERROR_MESSAGE = "Unexpected value for newCodeDefinitionType '%s'"; | |||
private static final EnumSet<NewCodePeriodType> projectCreationNCDTypes = EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS, REFERENCE_BRANCH); | |||
private final DbClient dbClient; | |||
private final PlatformEditionProvider editionProvider; | |||
public NewCodeDefinitionResolver(DbClient dbClient, PlatformEditionProvider editionProvider) { | |||
this.dbClient = dbClient; | |||
this.editionProvider = editionProvider; | |||
} | |||
public void createNewCodeDefinition(DbSession dbSession, String projectUuid, String defaultBranchName, String newCodeDefinitionType, String newCodeDefinitionValue) { | |||
boolean isCommunityEdition = editionProvider.get().filter(EditionProvider.Edition.COMMUNITY::equals).isPresent(); | |||
NewCodePeriodType newCodePeriodType = parseNewCodeDefinitionType(newCodeDefinitionType); | |||
NewCodePeriodDto dto = new NewCodePeriodDto(); | |||
dto.setType(newCodePeriodType); | |||
dto.setProjectUuid(projectUuid); | |||
if (isCommunityEdition) { | |||
dto.setBranchUuid(projectUuid); | |||
} | |||
getNewCodeDefinitionValueProjectCreation(newCodePeriodType, newCodeDefinitionValue, defaultBranchName).ifPresent(dto::setValue); | |||
if (!CaycUtils.isNewCodePeriodCompliant(dto.getType(), dto.getValue())) { | |||
throw new IllegalArgumentException("Failed to set the New Code Definition. The given value is not compatible with the Clean as You Code methodology. " | |||
+ "Please refer to the documentation for compliant options."); | |||
} | |||
dbClient.newCodePeriodDao().insert(dbSession, dto); | |||
} | |||
public static void checkNewCodeDefinitionParam(String newCodeDefinitionType, String newCodeDefinitionValue) { | |||
if (newCodeDefinitionType == null && newCodeDefinitionValue != null) { | |||
throw new IllegalArgumentException("New code definition type is required when new code definition value is provided"); | |||
} | |||
} | |||
private static Optional<String> getNewCodeDefinitionValueProjectCreation(NewCodePeriodType type, @Nullable String value, String defaultBranchName) { | |||
return switch (type) { | |||
case PREVIOUS_VERSION -> { | |||
Preconditions.checkArgument(value == null, UNEXPECTED_VALUE_ERROR_MESSAGE, type); | |||
yield Optional.empty(); | |||
} | |||
case NUMBER_OF_DAYS -> { | |||
requireValue(type, value); | |||
yield Optional.of(parseDays(value)); | |||
} | |||
case REFERENCE_BRANCH -> { | |||
Preconditions.checkArgument(value == null, UNEXPECTED_VALUE_ERROR_MESSAGE, type); | |||
yield Optional.of(defaultBranchName); | |||
} | |||
default -> throw new IllegalStateException("Unexpected type: " + type); | |||
}; | |||
} | |||
private static String parseDays(String value) { | |||
try { | |||
return Integer.toString(NewCodePeriodParser.parseDays(value)); | |||
} catch (Exception e) { | |||
throw new IllegalArgumentException("Failed to parse number of days: " + value); | |||
} | |||
} | |||
private static void requireValue(NewCodePeriodType type, @Nullable String value) { | |||
Preconditions.checkArgument(value != null, "New code definition type '%s' requires a newCodeDefinitionValue", type); | |||
} | |||
private static NewCodePeriodType parseNewCodeDefinitionType(String typeStr) { | |||
NewCodePeriodType type; | |||
try { | |||
type = NewCodePeriodType.valueOf(typeStr.toUpperCase(Locale.US)); | |||
} catch (IllegalArgumentException e) { | |||
throw new IllegalArgumentException("Invalid type: " + typeStr); | |||
} | |||
validateType(type); | |||
return type; | |||
} | |||
private static void validateType(NewCodePeriodType type) { | |||
Preconditions.checkArgument(projectCreationNCDTypes.contains(type), "Invalid type '%s'. `newCodeDefinitionType` can only be set with types: %s", | |||
type, projectCreationNCDTypes); | |||
} | |||
} |
@@ -1,190 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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; | |||
import com.google.common.base.Preconditions; | |||
import java.util.EnumSet; | |||
import java.util.Locale; | |||
import java.util.Optional; | |||
import java.util.Set; | |||
import javax.annotation.Nullable; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.component.BranchDto; | |||
import org.sonar.db.component.SnapshotDto; | |||
import org.sonar.db.newcodeperiod.NewCodePeriodParser; | |||
import org.sonar.db.newcodeperiod.NewCodePeriodType; | |||
import org.sonar.db.project.ProjectDto; | |||
import org.sonar.server.exceptions.NotFoundException; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
import static java.lang.String.format; | |||
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.REFERENCE_BRANCH; | |||
import static org.sonar.db.newcodeperiod.NewCodePeriodType.SPECIFIC_ANALYSIS; | |||
public interface NewCodePeriodUtils { | |||
String BEGIN_LIST = "<ul>"; | |||
String END_LIST = "</ul>"; | |||
String BEGIN_ITEM_LIST = "<li>"; | |||
String END_ITEM_LIST = "</li>"; | |||
String NEW_CODE_PERIOD_TYPE_DESCRIPTION = "Type<br/>" + | |||
"New code definitions of the following types are allowed:" + | |||
BEGIN_LIST + | |||
BEGIN_ITEM_LIST + SPECIFIC_ANALYSIS.name() + " - can be set at branch level only" + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + PREVIOUS_VERSION.name() + " - can be set at any level (global, project, branch)" + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + NUMBER_OF_DAYS.name() + " - can be set at any level (global, project, branch)" + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + REFERENCE_BRANCH.name() + " - can only be set for projects and branches" + END_ITEM_LIST + | |||
END_LIST; | |||
String NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION = "Type<br/>" + | |||
"New code definitions of the following types are allowed:" + | |||
BEGIN_LIST + | |||
BEGIN_ITEM_LIST + PREVIOUS_VERSION.name() + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + NUMBER_OF_DAYS.name() + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + REFERENCE_BRANCH.name() + " - will default to the main branch. A new code definition should be set for the branch itself. " + | |||
"Until you do so, this branch will use itself as a reference branch and no code will be considered new for this branch" + END_ITEM_LIST + | |||
END_LIST; | |||
String NEW_CODE_PERIOD_VALUE_DESCRIPTION = "Value<br/>" + | |||
"For each type, a different value is expected:" + | |||
BEGIN_LIST + | |||
BEGIN_ITEM_LIST + "the uuid of an analysis, when type is " + SPECIFIC_ANALYSIS.name() + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + "no value, when type is " + PREVIOUS_VERSION.name() + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + "a number between 1 and 90, when type is " + NUMBER_OF_DAYS.name() + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + "a string, when type is " + REFERENCE_BRANCH.name() + END_ITEM_LIST + | |||
END_LIST; | |||
String NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION = "Value<br/>" + | |||
"For each type, a different value is expected:" + | |||
BEGIN_LIST + | |||
BEGIN_ITEM_LIST + "no value, when type is " + PREVIOUS_VERSION.name() + " and " + REFERENCE_BRANCH.name() + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + "a number between 1 and 90, when type is " + NUMBER_OF_DAYS.name() + END_ITEM_LIST + | |||
END_LIST; | |||
String UNEXPECTED_VALUE_ERROR_MESSAGE = "Unexpected value for type '%s'"; | |||
static Optional<String> getNewCodeDefinitionValue(DbSession dbSession, DbClient dbClient, NewCodePeriodType type, @Nullable ProjectDto project, | |||
@Nullable BranchDto branch, @Nullable String value) { | |||
switch (type) { | |||
case PREVIOUS_VERSION: | |||
Preconditions.checkArgument(value == null, UNEXPECTED_VALUE_ERROR_MESSAGE, type); | |||
return Optional.empty(); | |||
case NUMBER_OF_DAYS: | |||
requireValue(type, value); | |||
return Optional.of(parseDays(value)); | |||
case SPECIFIC_ANALYSIS: | |||
requireValue(type, value); | |||
requireBranch(type, branch); | |||
SnapshotDto analysis = getAnalysis(dbSession, value, project, branch, dbClient); | |||
return Optional.of(analysis.getUuid()); | |||
case REFERENCE_BRANCH: | |||
requireValue(type, value); | |||
return Optional.of(value); | |||
default: | |||
throw new IllegalStateException("Unexpected type: " + type); | |||
} | |||
} | |||
static Optional<String> getNewCodeDefinitionValueProjectCreation(NewCodePeriodType type, @Nullable String value, String defaultBranchName) { | |||
switch (type) { | |||
case PREVIOUS_VERSION: | |||
Preconditions.checkArgument(value == null, UNEXPECTED_VALUE_ERROR_MESSAGE, type); | |||
return Optional.empty(); | |||
case NUMBER_OF_DAYS: | |||
requireValue(type, value); | |||
return Optional.of(parseDays(value)); | |||
case REFERENCE_BRANCH: | |||
Preconditions.checkArgument(value == null, UNEXPECTED_VALUE_ERROR_MESSAGE, type); | |||
return Optional.of(defaultBranchName); | |||
default: | |||
throw new IllegalStateException("Unexpected type: " + type); | |||
} | |||
} | |||
private static String parseDays(String value) { | |||
try { | |||
return Integer.toString(NewCodePeriodParser.parseDays(value)); | |||
} catch (Exception e) { | |||
throw new IllegalArgumentException("Failed to parse number of days: " + value); | |||
} | |||
} | |||
private static void requireValue(NewCodePeriodType type, @Nullable String value) { | |||
Preconditions.checkArgument(value != null, "New code definition type '%s' requires a value", type); | |||
} | |||
private static void requireBranch(NewCodePeriodType type, @Nullable BranchDto branch) { | |||
Preconditions.checkArgument(branch != null, "New code definition type '%s' requires a branch", type); | |||
} | |||
private static Set<NewCodePeriodType> getInstanceTypes() { | |||
return EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS); | |||
} | |||
private static Set<NewCodePeriodType> getProjectTypes() { | |||
return EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS, REFERENCE_BRANCH); | |||
} | |||
private static Set<NewCodePeriodType> getBranchTypes() { | |||
return EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS, SPECIFIC_ANALYSIS, REFERENCE_BRANCH); | |||
} | |||
static NewCodePeriodType validateType(String typeStr, boolean isOverall, boolean isBranch) { | |||
NewCodePeriodType type; | |||
try { | |||
type = NewCodePeriodType.valueOf(typeStr.toUpperCase(Locale.US)); | |||
} catch (IllegalArgumentException e) { | |||
throw new IllegalArgumentException("Invalid type: " + typeStr); | |||
} | |||
if (isOverall) { | |||
checkType("Overall setting", getInstanceTypes(), type); | |||
} else if (isBranch) { | |||
checkType("Branches", getBranchTypes(), type); | |||
} else { | |||
checkType("Projects", getProjectTypes(), type); | |||
} | |||
return type; | |||
} | |||
private static SnapshotDto getAnalysis(DbSession dbSession, String analysisUuid, ProjectDto project, BranchDto branch, DbClient dbClient) { | |||
SnapshotDto snapshotDto = dbClient.snapshotDao().selectByUuid(dbSession, analysisUuid) | |||
.orElseThrow(() -> new NotFoundException(format("Analysis '%s' is not found", analysisUuid))); | |||
checkAnalysis(dbSession, project, branch, snapshotDto, dbClient); | |||
return snapshotDto; | |||
} | |||
private static void checkAnalysis(DbSession dbSession, ProjectDto project, BranchDto branch, SnapshotDto analysis, DbClient dbClient) { | |||
BranchDto analysisBranch = dbClient.branchDao().selectByUuid(dbSession, analysis.getComponentUuid()).orElse(null); | |||
boolean analysisMatchesProjectBranch = analysisBranch != null && analysisBranch.getUuid().equals(branch.getUuid()); | |||
checkArgument(analysisMatchesProjectBranch, | |||
"Analysis '%s' does not belong to branch '%s' of project '%s'", | |||
analysis.getUuid(), branch.getKey(), project.getKey()); | |||
} | |||
private static void checkType(String name, Set<NewCodePeriodType> validTypes, NewCodePeriodType type) { | |||
if (!validTypes.contains(type)) { | |||
throw new IllegalArgumentException(String.format("Invalid type '%s'. %s can only be set with types: %s", type, name, validTypes)); | |||
} | |||
} | |||
} |
@@ -19,8 +19,11 @@ | |||
*/ | |||
package org.sonar.server.newcodeperiod.ws; | |||
import com.google.common.base.Preconditions; | |||
import java.util.EnumSet; | |||
import java.util.Locale; | |||
import java.util.Set; | |||
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; | |||
@@ -31,8 +34,10 @@ import org.sonar.core.platform.PlatformEditionProvider; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.component.BranchDto; | |||
import org.sonar.db.component.SnapshotDto; | |||
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 org.sonar.db.project.ProjectDto; | |||
import org.sonar.server.component.ComponentFinder; | |||
@@ -40,15 +45,12 @@ import org.sonar.server.exceptions.NotFoundException; | |||
import org.sonar.server.newcodeperiod.CaycUtils; | |||
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.NUMBER_OF_DAYS; | |||
import static org.sonar.db.newcodeperiod.NewCodePeriodType.PREVIOUS_VERSION; | |||
import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH; | |||
import static org.sonar.db.newcodeperiod.NewCodePeriodType.SPECIFIC_ANALYSIS; | |||
import static org.sonar.server.newcodeperiod.NewCodePeriodUtils.NEW_CODE_PERIOD_TYPE_DESCRIPTION; | |||
import static org.sonar.server.newcodeperiod.NewCodePeriodUtils.NEW_CODE_PERIOD_VALUE_DESCRIPTION; | |||
import static org.sonar.server.newcodeperiod.NewCodePeriodUtils.getNewCodeDefinitionValue; | |||
import static org.sonar.server.newcodeperiod.NewCodePeriodUtils.validateType; | |||
import static org.sonar.server.ws.WsUtils.createHtmlExternalLink; | |||
public class SetAction implements NewCodePeriodsWsAction { | |||
@@ -63,7 +65,8 @@ public class SetAction implements NewCodePeriodsWsAction { | |||
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, REFERENCE_BRANCH); | |||
private static final Set<NewCodePeriodType> BRANCH_TYPES = EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS, SPECIFIC_ANALYSIS, REFERENCE_BRANCH); | |||
private static final Set<NewCodePeriodType> BRANCH_TYPES = EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS, SPECIFIC_ANALYSIS, | |||
REFERENCE_BRANCH); | |||
private final DbClient dbClient; | |||
private final UserSession userSession; | |||
@@ -93,7 +96,6 @@ public class SetAction implements NewCodePeriodsWsAction { | |||
"Existing projects or branches having a specific new code definition will not be impacted" + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + "Project key must be provided to update the value for a project" + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + "Both project and branch keys must be provided to update the value for a branch" + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + "New setting must be compliant with the Clean as You Code methodology" + END_ITEM_LIST + | |||
END_LIST + | |||
"Requires one of the following permissions: " + | |||
BEGIN_LIST + | |||
@@ -109,9 +111,25 @@ public class SetAction implements NewCodePeriodsWsAction { | |||
.setDescription("Branch key"); | |||
action.createParam(PARAM_TYPE) | |||
.setRequired(true) | |||
.setDescription(NEW_CODE_PERIOD_VALUE_DESCRIPTION); | |||
.setDescription("Type<br/>" + | |||
"New code definitions of the following types are allowed:" + | |||
BEGIN_LIST + | |||
BEGIN_ITEM_LIST + SPECIFIC_ANALYSIS.name() + " - can be set at branch level only" + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + PREVIOUS_VERSION.name() + " - can be set at any level (global, project, branch)" + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + NUMBER_OF_DAYS.name() + " - can be set at any level (global, project, branch)" + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + REFERENCE_BRANCH.name() + " - can only be set for projects and branches" + END_ITEM_LIST + | |||
END_LIST | |||
); | |||
action.createParam(PARAM_VALUE) | |||
.setDescription(NEW_CODE_PERIOD_TYPE_DESCRIPTION); | |||
.setDescription("Value<br/>" + | |||
"For each type, a different value is expected:" + | |||
BEGIN_LIST + | |||
BEGIN_ITEM_LIST + "the uuid of an analysis, when type is " + SPECIFIC_ANALYSIS.name() + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + "no value, when type is " + PREVIOUS_VERSION.name() + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + "a number between 1 and 90, when type is " + NUMBER_OF_DAYS.name() + END_ITEM_LIST + | |||
BEGIN_ITEM_LIST + "a string, when type is " + REFERENCE_BRANCH.name() + END_ITEM_LIST + | |||
END_LIST | |||
); | |||
} | |||
@Override | |||
@@ -154,11 +172,11 @@ public class SetAction implements NewCodePeriodsWsAction { | |||
userSession.checkIsSystemAdministrator(); | |||
} | |||
getNewCodeDefinitionValue(dbSession, dbClient, type, project, branch, valueStr).ifPresent(dto::setValue); | |||
setValue(dbSession, dto, type, project, branch, valueStr); | |||
if (!CaycUtils.isNewCodePeriodCompliant(dto.getType(), dto.getValue())) { | |||
throw new IllegalArgumentException("Failed to set the New Code Definition. The given value is not compatible with the Clean as You Code methodology. " | |||
+ "Please refer to the documentation for compliant options."); | |||
throw new IllegalArgumentException("Failed to set the New Code Definition. The given value is not compatible with the Clean as " + | |||
"You Code methodology. Please refer to the documentation for compliant options."); | |||
} | |||
newCodePeriodDao.upsert(dbSession, dto); | |||
@@ -166,6 +184,52 @@ public class SetAction implements NewCodePeriodsWsAction { | |||
} | |||
} | |||
private void setValue(DbSession dbSession, NewCodePeriodDto dto, NewCodePeriodType type, @Nullable ProjectDto project, | |||
@Nullable BranchDto branch, @Nullable String value) { | |||
switch (type) { | |||
case PREVIOUS_VERSION -> Preconditions.checkArgument(value == null, "Unexpected value for type '%s'", type); | |||
case NUMBER_OF_DAYS -> { | |||
requireValue(type, value); | |||
dto.setValue(parseDays(value)); | |||
} | |||
case SPECIFIC_ANALYSIS -> { | |||
checkValuesForSpecificBranch(type, value, project, branch); | |||
dto.setValue(getAnalysis(dbSession, value, project, branch).getUuid()); | |||
} | |||
case REFERENCE_BRANCH -> { | |||
requireValue(type, value); | |||
dto.setValue(value); | |||
} | |||
default -> throw new IllegalStateException("Unexpected type: " + type); | |||
} | |||
} | |||
private static void checkValuesForSpecificBranch(NewCodePeriodType type, @Nullable String value, @Nullable ProjectDto project, @Nullable BranchDto branch) { | |||
requireValue(type, value); | |||
requireProject(type, project); | |||
requireBranch(type, branch); | |||
} | |||
private static String parseDays(String value) { | |||
try { | |||
return Integer.toString(NewCodePeriodParser.parseDays(value)); | |||
} catch (Exception e) { | |||
throw new IllegalArgumentException("Failed to parse number of days: " + value); | |||
} | |||
} | |||
private static void requireValue(NewCodePeriodType type, @Nullable String value) { | |||
Preconditions.checkArgument(value != null, "New code definition type '%s' requires a value", type); | |||
} | |||
private static void requireBranch(NewCodePeriodType type, @Nullable BranchDto branch) { | |||
Preconditions.checkArgument(branch != null, "New code definition type '%s' requires a branch", type); | |||
} | |||
private static void requireProject(NewCodePeriodType type, @Nullable ProjectDto project) { | |||
Preconditions.checkArgument(project != null, "New code definition type '%s' requires a project", type); | |||
} | |||
private BranchDto getBranch(DbSession dbSession, ProjectDto project, String branchKey) { | |||
return dbClient.branchDao().selectByBranchKey(dbSession, project.getUuid(), branchKey) | |||
.orElseThrow(() -> new NotFoundException(format("Branch '%s' in project '%s' not found", branchKey, project.getKey()))); | |||
@@ -181,4 +245,44 @@ public class SetAction implements NewCodePeriodsWsAction { | |||
.findFirst() | |||
.orElseThrow(() -> new NotFoundException(format("Main branch in project '%s' is not found", project.getKey()))); | |||
} | |||
private static NewCodePeriodType validateType(String typeStr, boolean isOverall, boolean isBranch) { | |||
NewCodePeriodType type; | |||
try { | |||
type = NewCodePeriodType.valueOf(typeStr.toUpperCase(Locale.US)); | |||
} catch (IllegalArgumentException e) { | |||
throw new IllegalArgumentException("Invalid type: " + typeStr); | |||
} | |||
if (isOverall) { | |||
checkType("Overall setting", OVERALL_TYPES, type); | |||
} else if (isBranch) { | |||
checkType("Branches", BRANCH_TYPES, type); | |||
} else { | |||
checkType("Projects", PROJECT_TYPES, type); | |||
} | |||
return type; | |||
} | |||
private SnapshotDto getAnalysis(DbSession dbSession, String analysisUuid, ProjectDto project, BranchDto branch) { | |||
SnapshotDto snapshotDto = dbClient.snapshotDao().selectByUuid(dbSession, analysisUuid) | |||
.orElseThrow(() -> new NotFoundException(format("Analysis '%s' is not found", analysisUuid))); | |||
checkAnalysis(dbSession, project, branch, snapshotDto); | |||
return snapshotDto; | |||
} | |||
private void checkAnalysis(DbSession dbSession, ProjectDto project, BranchDto branch, SnapshotDto analysis) { | |||
BranchDto analysisBranch = dbClient.branchDao().selectByUuid(dbSession, analysis.getComponentUuid()).orElse(null); | |||
boolean analysisMatchesProjectBranch = analysisBranch != null && analysisBranch.getUuid().equals(branch.getUuid()); | |||
checkArgument(analysisMatchesProjectBranch, | |||
"Analysis '%s' does not belong to branch '%s' of project '%s'", | |||
analysis.getUuid(), branch.getKey(), project.getKey()); | |||
} | |||
private static void checkType(String name, Set<NewCodePeriodType> validTypes, NewCodePeriodType type) { | |||
if (!validTypes.contains(type)) { | |||
throw new IllegalArgumentException(String.format("Invalid type '%s'. %s can only be set with types: %s", type, name, validTypes)); | |||
} | |||
} | |||
} |
@@ -26,15 +26,11 @@ import org.sonar.api.server.ws.Change; | |||
import org.sonar.api.server.ws.Request; | |||
import org.sonar.api.server.ws.Response; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.core.platform.EditionProvider; | |||
import org.sonar.core.platform.PlatformEditionProvider; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.newcodeperiod.NewCodePeriodDto; | |||
import org.sonar.db.newcodeperiod.NewCodePeriodType; | |||
import org.sonar.server.component.ComponentUpdater; | |||
import org.sonar.server.newcodeperiod.CaycUtils; | |||
import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; | |||
import org.sonar.server.project.DefaultBranchNameResolver; | |||
import org.sonar.server.project.ProjectDefaultVisibility; | |||
import org.sonar.server.project.Visibility; | |||
@@ -48,10 +44,9 @@ import static org.sonar.core.component.ComponentKeys.MAX_COMPONENT_KEY_LENGTH; | |||
import static org.sonar.db.component.ComponentValidator.MAX_COMPONENT_NAME_LENGTH; | |||
import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; | |||
import static org.sonar.server.component.NewComponent.newComponentBuilder; | |||
import static org.sonar.server.newcodeperiod.NewCodePeriodUtils.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; | |||
import static org.sonar.server.newcodeperiod.NewCodePeriodUtils.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; | |||
import static org.sonar.server.newcodeperiod.NewCodePeriodUtils.getNewCodeDefinitionValueProjectCreation; | |||
import static org.sonar.server.newcodeperiod.NewCodePeriodUtils.validateType; | |||
import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; | |||
import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; | |||
import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; | |||
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; | |||
import static org.sonar.server.ws.WsUtils.writeProtobuf; | |||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_CREATE; | |||
@@ -68,18 +63,18 @@ public class CreateAction implements ProjectsWsAction { | |||
private final UserSession userSession; | |||
private final ComponentUpdater componentUpdater; | |||
private final ProjectDefaultVisibility projectDefaultVisibility; | |||
private final PlatformEditionProvider editionProvider; | |||
private final DefaultBranchNameResolver defaultBranchNameResolver; | |||
private final NewCodeDefinitionResolver newCodeDefinitionResolver; | |||
public CreateAction(DbClient dbClient, UserSession userSession, ComponentUpdater componentUpdater, | |||
ProjectDefaultVisibility projectDefaultVisibility, PlatformEditionProvider editionProvider, | |||
DefaultBranchNameResolver defaultBranchNameResolver) { | |||
ProjectDefaultVisibility projectDefaultVisibility, DefaultBranchNameResolver defaultBranchNameResolver, NewCodeDefinitionResolver newCodeDefinitionResolver) { | |||
this.dbClient = dbClient; | |||
this.userSession = userSession; | |||
this.componentUpdater = componentUpdater; | |||
this.projectDefaultVisibility = projectDefaultVisibility; | |||
this.editionProvider = editionProvider; | |||
this.defaultBranchNameResolver = defaultBranchNameResolver; | |||
this.newCodeDefinitionResolver = newCodeDefinitionResolver; | |||
} | |||
@Override | |||
@@ -125,7 +120,6 @@ public class CreateAction implements ProjectsWsAction { | |||
action.createParam(PARAM_NEW_CODE_DEFINITION_VALUE) | |||
.setDescription(NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION) | |||
.setSince("10.1"); | |||
} | |||
@Override | |||
@@ -137,21 +131,18 @@ public class CreateAction implements ProjectsWsAction { | |||
private CreateWsResponse doHandle(CreateRequest request) { | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
userSession.checkPermission(PROVISION_PROJECTS); | |||
checkNewCodeDefinitionParam(request); | |||
checkNewCodeDefinitionParam(request.getNewCodeDefinitionType(), request.getNewCodeDefinitionValue()); | |||
ComponentDto componentDto = createProject(request, dbSession); | |||
if(request.getNewCodeDefinitionType() != null) { | |||
createNewCodeDefinition(dbSession, request, componentDto.uuid()); | |||
if (request.getNewCodeDefinitionType() != null) { | |||
String defaultBranchName = Optional.ofNullable(request.getMainBranchKey()).orElse(defaultBranchNameResolver.getEffectiveMainBranchName()); | |||
newCodeDefinitionResolver.createNewCodeDefinition(dbSession, componentDto.uuid(), defaultBranchName, request.getNewCodeDefinitionType(), | |||
request.getNewCodeDefinitionValue()); | |||
} | |||
componentUpdater.commitAndIndex(dbSession, componentDto); | |||
return toCreateResponse(componentDto); | |||
} | |||
} | |||
private static void checkNewCodeDefinitionParam(CreateRequest request) { | |||
if (request.getNewCodeDefinitionType() == null && request.getNewCodeDefinitionValue() != null) { | |||
throw new IllegalArgumentException("New code definition type is required when new code definition value is provided"); | |||
} | |||
} | |||
private ComponentDto createProject(CreateRequest request, DbSession dbSession) { | |||
String visibility = request.getVisibility(); | |||
boolean changeToPrivate = visibility == null ? projectDefaultVisibility.get(dbSession).isPrivate() : "private".equals(visibility); | |||
@@ -164,32 +155,9 @@ public class CreateAction implements ProjectsWsAction { | |||
.build(), | |||
userSession.isLoggedIn() ? userSession.getUuid() : null, | |||
userSession.isLoggedIn() ? userSession.getLogin() : null, | |||
request.getMainBranchKey(), s -> {}).mainBranchComponent(); | |||
} | |||
private void createNewCodeDefinition(DbSession dbSession, CreateRequest request, String projectUuid) { | |||
boolean isCommunityEdition = editionProvider.get().filter(EditionProvider.Edition.COMMUNITY::equals).isPresent(); | |||
NewCodePeriodType newCodePeriodType = validateType(request.getNewCodeDefinitionType(), false, isCommunityEdition); | |||
String newCodePeriodValue = request.getNewCodeDefinitionValue(); | |||
String defaultBranchName = Optional.ofNullable(request.getMainBranchKey()).orElse(defaultBranchNameResolver.getEffectiveMainBranchName()); | |||
NewCodePeriodDto dto = new NewCodePeriodDto(); | |||
dto.setType(newCodePeriodType); | |||
dto.setProjectUuid(projectUuid); | |||
if (isCommunityEdition) { | |||
dto.setBranchUuid(projectUuid); | |||
} | |||
getNewCodeDefinitionValueProjectCreation(newCodePeriodType, newCodePeriodValue, defaultBranchName).ifPresent(dto::setValue); | |||
if (!CaycUtils.isNewCodePeriodCompliant(dto.getType(), dto.getValue())) { | |||
throw new IllegalArgumentException("Failed to set the New Code Definition. The given value is not compatible with the Clean as You Code methodology. " | |||
+ "Please refer to the documentation for compliant options."); | |||
} | |||
request.getMainBranchKey(), s -> { | |||
}).mainBranchComponent(); | |||
dbClient.newCodePeriodDao().insert(dbSession, dto); | |||
} | |||
private static CreateRequest toCreateRequest(Request request) { |
@@ -146,6 +146,7 @@ import org.sonar.server.monitoring.devops.AzureMetricsTask; | |||
import org.sonar.server.monitoring.devops.BitbucketMetricsTask; | |||
import org.sonar.server.monitoring.devops.GithubMetricsTask; | |||
import org.sonar.server.monitoring.devops.GitlabMetricsTask; | |||
import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; | |||
import org.sonar.server.newcodeperiod.ws.NewCodePeriodsWsModule; | |||
import org.sonar.server.notification.NotificationModule; | |||
import org.sonar.server.notification.ws.NotificationWsModule; | |||
@@ -508,6 +509,7 @@ public class PlatformLevel4 extends PlatformLevel { | |||
// New Code Periods | |||
new NewCodePeriodsWsModule(), | |||
NewCodeDefinitionResolver.class, | |||
// Project Links | |||
new ProjectLinksModule(), |