@@ -107,6 +107,23 @@ public class ProjectAlmSettingDaoIT { | |||
.containsExactlyInAnyOrder(githubProject1, githubProject2); | |||
} | |||
@Test | |||
public void selectByProjectUuidsAndAlm_whenGivenGithubAndProjectUuids_shouldOnlyReturnThose() { | |||
AlmSettingDto githubSetting = db.almSettings().insertGitHubAlmSetting(); | |||
ProjectAlmSettingDto githubProject = createAlmProject(githubSetting); | |||
createAlmProject(githubSetting); | |||
AlmSettingDto gitlabSetting = db.almSettings().insertGitlabAlmSetting(); | |||
ProjectAlmSettingDto gitlabProject = createAlmProject(gitlabSetting); | |||
List<ProjectAlmSettingDto> projectAlmSettingDtos = | |||
underTest.selectByProjectUuidsAndAlm(dbSession, Set.of(githubProject.getProjectUuid(), gitlabProject.getProjectUuid()), ALM.GITHUB); | |||
assertThat(projectAlmSettingDtos) | |||
.usingRecursiveFieldByFieldElementComparator() | |||
.containsExactlyInAnyOrder(githubProject); | |||
} | |||
private ProjectAlmSettingDto createAlmProject(AlmSettingDto almSettingsDto) { | |||
ProjectDto project = db.components().insertPrivateProject().getProjectDto(); | |||
when(uuidFactory.create()).thenReturn(project.getUuid() + "_forSetting"); |
@@ -107,6 +107,10 @@ public class ProjectAlmSettingDao implements Dao { | |||
return getMapper(dbSession).selectByAlm(alm.getId().toLowerCase(Locale.ROOT)); | |||
} | |||
public List<ProjectAlmSettingDto> selectByProjectUuidsAndAlm(DbSession dbSession, Set<String> projectUuids, ALM alm) { | |||
return getMapper(dbSession).selectByProjectUuidsAndAlm(projectUuids, alm.getId().toLowerCase(Locale.ROOT)); | |||
} | |||
public List<ProjectAlmKeyAndProject> selectAlmTypeAndUrlByProject(DbSession dbSession) { | |||
return getMapper(dbSession).selectAlmTypeAndUrlByProject(); | |||
} |
@@ -20,6 +20,7 @@ | |||
package org.sonar.db.alm.setting; | |||
import java.util.List; | |||
import java.util.Set; | |||
import javax.annotation.CheckForNull; | |||
import org.apache.ibatis.annotations.Param; | |||
@@ -43,5 +44,7 @@ public interface ProjectAlmSettingMapper { | |||
List<ProjectAlmSettingDto> selectByAlm(@Param("alm") String alm); | |||
List<ProjectAlmSettingDto> selectByProjectUuidsAndAlm(@Param("projectUuids") Set<String> projectUuids, @Param("alm") String alm); | |||
List<ProjectAlmKeyAndProject> selectAlmTypeAndUrlByProject(); | |||
} |
@@ -58,7 +58,7 @@ | |||
</foreach> | |||
</select> | |||
<select id="selectByAlm" parameterType="string" resultType="org.sonar.db.alm.setting.ProjectAlmSettingDto"> | |||
<sql id="selectByAlmSql"> | |||
select <include refid="sqlColumns"/> | |||
from | |||
project_alm_settings p | |||
@@ -66,6 +66,18 @@ | |||
alm_settings alm_settings on p.alm_setting_uuid = alm_settings.uuid | |||
where | |||
alm_settings.alm_id=#{alm, jdbcType=VARCHAR} | |||
</sql> | |||
<select id="selectByAlm" parameterType="string" resultType="org.sonar.db.alm.setting.ProjectAlmSettingDto"> | |||
<include refid="selectByAlmSql"/> | |||
</select> | |||
<select id="selectByProjectUuidsAndAlm" parameterType="map" resultType="org.sonar.db.alm.setting.ProjectAlmSettingDto"> | |||
<include refid="selectByAlmSql"/> | |||
and p.project_uuid in | |||
<foreach collection="projectUuids" open="(" close=")" item="projectUuid" separator=","> | |||
#{projectUuid, jdbcType=VARCHAR} | |||
</foreach> | |||
</select> | |||
<insert id="insert" parameterType="Map" useGeneratedKeys="false"> |
@@ -110,6 +110,13 @@ public class DelegatingManagedServices implements ManagedInstanceService, Manage | |||
return resourcesUuid.stream().collect(toMap(identity(), any -> false)); | |||
} | |||
@Override | |||
public Map<String, Boolean> getProjectUuidToManaged(DbSession dbSession, Set<String> projectUuids) { | |||
return findManagedProjectService() | |||
.map(managedProjectService -> managedProjectService.getProjectUuidToManaged(dbSession, projectUuids)) | |||
.orElse(returnNonManagedForAll(projectUuids)); | |||
} | |||
@Override | |||
public boolean isProjectManaged(DbSession dbSession, String projectUuid) { | |||
return findManagedProjectService() |
@@ -19,10 +19,14 @@ | |||
*/ | |||
package org.sonar.server.management; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import org.sonar.db.DbSession; | |||
public interface ManagedProjectService { | |||
Map<String, Boolean> getProjectUuidToManaged(DbSession dbSession, Set<String> projectUuids); | |||
boolean isProjectManaged(DbSession dbSession, String projectUuid); | |||
} |
@@ -31,8 +31,10 @@ import static java.util.Collections.emptySet; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
import static org.mockito.Mockito.doReturn; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
import static org.mockito.Mockito.withSettings; | |||
@RunWith(MockitoJUnitRunner.class) | |||
public class DelegatingManagedServicesTest { | |||
@@ -216,6 +218,36 @@ public class DelegatingManagedServicesTest { | |||
return anotherManagedInstanceService; | |||
} | |||
@Test | |||
public void getProjectUuidToManaged_whenNoDelegates_setAllProjectsAsNonManaged() { | |||
Set<String> projectUuids = Set.of("a", "b"); | |||
DelegatingManagedServices managedInstanceService = NO_MANAGED_SERVICES; | |||
Map<String, Boolean> projectUuidToManaged = managedInstanceService.getProjectUuidToManaged(dbSession, projectUuids); | |||
assertThat(projectUuidToManaged).containsExactlyInAnyOrderEntriesOf(Map.of("a", false, "b", false)); | |||
} | |||
@Test | |||
public void getProjectUuidToManaged_delegatesToRightService_andPropagateAnswer() { | |||
Set<String> projectUuids = Set.of("a", "b"); | |||
Map<String, Boolean> serviceResponse = Map.of("a", false, "b", true); | |||
ManagedInstanceService anotherManagedProjectService = getManagedProjectService(projectUuids, serviceResponse); | |||
DelegatingManagedServices managedInstanceService = new DelegatingManagedServices(Set.of(new NeverManagedInstanceService(), anotherManagedProjectService)); | |||
Map<String, Boolean> projectUuidToManaged = managedInstanceService.getProjectUuidToManaged(dbSession, projectUuids); | |||
assertThat(projectUuidToManaged).containsExactlyInAnyOrderEntriesOf(serviceResponse); | |||
} | |||
private ManagedInstanceService getManagedProjectService(Set<String> projectUuids, Map<String, Boolean> uuidsToManaged) { | |||
ManagedInstanceService anotherManagedProjectService = mock(ManagedInstanceService.class, withSettings().extraInterfaces(ManagedProjectService.class)); | |||
when(anotherManagedProjectService.isInstanceExternallyManaged()).thenReturn(true); | |||
doReturn(uuidsToManaged).when((ManagedProjectService) anotherManagedProjectService).getProjectUuidToManaged(dbSession, projectUuids); | |||
return anotherManagedProjectService; | |||
} | |||
@Test | |||
public void isProjectManaged_whenManagedInstanceServices_shouldDelegatesToRightService() { | |||
DelegatingManagedServices managedInstanceService = new DelegatingManagedServices(Set.of(new NeverManagedInstanceService(), new AlwaysManagedInstanceService())); | |||
@@ -272,6 +304,11 @@ public class DelegatingManagedServicesTest { | |||
return false; | |||
} | |||
@Override | |||
public Map<String, Boolean> getProjectUuidToManaged(DbSession dbSession, Set<String> projectUuids) { | |||
return null; | |||
} | |||
@Override | |||
public boolean isProjectManaged(DbSession dbSession, String projectUuid) { | |||
return false; | |||
@@ -320,6 +357,11 @@ public class DelegatingManagedServicesTest { | |||
return true; | |||
} | |||
@Override | |||
public Map<String, Boolean> getProjectUuidToManaged(DbSession dbSession, Set<String> projectUuids) { | |||
return null; | |||
} | |||
@Override | |||
public boolean isProjectManaged(DbSession dbSession, String projectUuid) { | |||
return true; |
@@ -26,6 +26,8 @@ import java.util.Arrays; | |||
import java.util.Collections; | |||
import java.util.Date; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.api.server.ws.WebService; | |||
@@ -37,6 +39,7 @@ import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.component.ProjectData; | |||
import org.sonar.db.project.ProjectDto; | |||
import org.sonar.server.exceptions.ForbiddenException; | |||
import org.sonar.server.management.ManagedProjectService; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.ws.TestRequest; | |||
import org.sonar.server.ws.WsActionTester; | |||
@@ -51,6 +54,10 @@ import static java.util.Optional.ofNullable; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
import static org.assertj.core.api.Assertions.tuple; | |||
import static org.mockito.ArgumentMatchers.any; | |||
import static org.mockito.Mockito.eq; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
import static org.sonar.api.server.ws.WebService.Param.PAGE; | |||
import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; | |||
import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY; | |||
@@ -79,7 +86,9 @@ public class SearchActionIT { | |||
@Rule | |||
public final DbTester db = DbTester.create(); | |||
private final WsActionTester ws = new WsActionTester(new SearchAction(db.getDbClient(), userSession)); | |||
private final ManagedProjectService managedProjectService = mock(ManagedProjectService.class); | |||
private final WsActionTester ws = new WsActionTester(new SearchAction(db.getDbClient(), userSession, managedProjectService)); | |||
@Test | |||
public void search_by_key_query_with_partial_match_case_insensitive() { | |||
@@ -203,6 +212,26 @@ public class SearchActionIT { | |||
assertThat(result.getComponentsList()).isEmpty(); | |||
} | |||
@Test | |||
public void search_return_manage_status() { | |||
userSession.addPermission(ADMINISTER); | |||
ProjectData managedProject = db.components().insertPrivateProject(); | |||
ProjectData notManagedProject = db.components().insertPrivateProject(); | |||
when(managedProjectService.getProjectUuidToManaged(any(), eq(Set.of(managedProject.projectUuid(), notManagedProject.projectUuid())))) | |||
.thenReturn(Map.of( | |||
managedProject.projectUuid(), true, | |||
notManagedProject.projectUuid(), false | |||
)); | |||
SearchWsResponse result = call(SearchRequest.builder().build()); | |||
assertThat(result.getComponentsList()) | |||
.extracting(Component::getKey, Component::getManaged) | |||
.containsExactlyInAnyOrder( | |||
tuple(managedProject.projectKey(), true), | |||
tuple(notManagedProject.projectKey(), false)); | |||
} | |||
private static String toStringAtUTC(Date d) { | |||
OffsetDateTime offsetTime = d.toInstant().atOffset(ZoneOffset.UTC); | |||
return DateUtils.formatDateTime(offsetTime); | |||
@@ -346,7 +375,7 @@ public class SearchActionIT { | |||
public void json_example() { | |||
userSession.addPermission(ADMINISTER); | |||
ProjectData publicProject = db.components().insertPublicProject("project-uuid-1", p -> p.setName("Project Name 1").setKey("project-key-1").setPrivate(false)); | |||
ProjectData privateProject = db.components().insertPrivateProject("project-uuid-2", p -> p.setName("Project Name 1").setKey("project-key-2")); | |||
ProjectData privateProject = db.components().insertPrivateProject("project-uuid-2", p -> p.setName("Project Name 2").setKey("project-key-2")); | |||
db.getDbClient().snapshotDao().insert(db.getSession(), newAnalysis(publicProject.getMainBranchDto()) | |||
.setCreatedAt(parseDateTime("2017-03-01T11:39:03+0300").getTime()) | |||
.setRevision("cfb82f55c6ef32e61828c4cb3db2da12795fd767")); |
@@ -40,12 +40,14 @@ import org.sonar.db.component.ComponentQuery; | |||
import org.sonar.db.component.ProjectLastAnalysisDateDto; | |||
import org.sonar.db.component.SnapshotDto; | |||
import org.sonar.db.permission.GlobalPermission; | |||
import org.sonar.server.management.ManagedProjectService; | |||
import org.sonar.server.project.Visibility; | |||
import org.sonar.server.user.UserSession; | |||
import org.sonarqube.ws.Projects.SearchWsResponse; | |||
import static java.util.Optional.ofNullable; | |||
import static java.util.function.Function.identity; | |||
import static java.util.stream.Collectors.toMap; | |||
import static org.sonar.api.resources.Qualifiers.APP; | |||
import static org.sonar.api.resources.Qualifiers.PROJECT; | |||
import static org.sonar.api.resources.Qualifiers.VIEW; | |||
@@ -70,10 +72,12 @@ public class SearchAction implements ProjectsWsAction { | |||
private final DbClient dbClient; | |||
private final UserSession userSession; | |||
private final ManagedProjectService managedProjectService; | |||
public SearchAction(DbClient dbClient, UserSession userSession) { | |||
public SearchAction(DbClient dbClient, UserSession userSession, ManagedProjectService managedProjectService) { | |||
this.dbClient = dbClient; | |||
this.userSession = userSession; | |||
this.managedProjectService = managedProjectService; | |||
} | |||
@Override | |||
@@ -84,6 +88,7 @@ public class SearchAction implements ProjectsWsAction { | |||
"Requires 'Administer System' permission") | |||
.addPagingParams(100, MAX_PAGE_SIZE) | |||
.setResponseExample(getClass().getResource("search-example.json")) | |||
.setChangelog(new Change("10.2", "Response includes 'managed' field.")) | |||
.setChangelog(new Change("9.1", "The parameter '" + PARAM_ANALYZED_BEFORE + "' and the field 'lastAnalysisDate' of the returned projects " | |||
+ "take into account the analysis of all branches and pull requests, not only the main branch.")) | |||
.setHandler(this); | |||
@@ -160,17 +165,31 @@ public class SearchAction implements ProjectsWsAction { | |||
List<ComponentDto> components = dbClient.componentDao().selectByQuery(dbSession, query, paging.offset(), paging.pageSize()); | |||
Set<String> componentUuids = components.stream().map(ComponentDto::uuid).collect(Collectors.toSet()); | |||
List<BranchDto> branchDtos = dbClient.branchDao().selectByUuids(dbSession, componentUuids); | |||
Map<String, String> projectUuidByComponentUuid = branchDtos.stream().collect(Collectors.toMap(BranchDto::getUuid,BranchDto::getProjectUuid)); | |||
Map<String, Long> lastAnalysisDateByComponentUuid = dbClient.snapshotDao().selectLastAnalysisDateByProjectUuids(dbSession, projectUuidByComponentUuid.values()).stream() | |||
Map<String, String> componentUuidToProjectUuid = branchDtos.stream().collect(Collectors.toMap(BranchDto::getUuid,BranchDto::getProjectUuid)); | |||
Map<String, Boolean> projectUuidToManaged = managedProjectService.getProjectUuidToManaged(dbSession, new HashSet<>(componentUuidToProjectUuid.values())); | |||
Map<String, Boolean> componentUuidToManaged = toComponentUuidToManaged(componentUuidToProjectUuid, projectUuidToManaged); | |||
Map<String, Long> lastAnalysisDateByComponentUuid = dbClient.snapshotDao().selectLastAnalysisDateByProjectUuids(dbSession, componentUuidToProjectUuid.values()).stream() | |||
.collect(Collectors.toMap(ProjectLastAnalysisDateDto::getProjectUuid, ProjectLastAnalysisDateDto::getDate)); | |||
Map<String, SnapshotDto> snapshotsByComponentUuid = dbClient.snapshotDao() | |||
.selectLastAnalysesByRootComponentUuids(dbSession, componentUuids).stream() | |||
.collect(Collectors.toMap(SnapshotDto::getRootComponentUuid, identity())); | |||
return buildResponse(components, snapshotsByComponentUuid, lastAnalysisDateByComponentUuid, projectUuidByComponentUuid, paging); | |||
return buildResponse(components, snapshotsByComponentUuid, lastAnalysisDateByComponentUuid, componentUuidToProjectUuid, componentUuidToManaged, paging); | |||
} | |||
} | |||
private Map<String, Boolean> toComponentUuidToManaged(Map<String, String> componentUuidToProjectUuid, Map<String, Boolean> projectUuidToManaged) { | |||
return componentUuidToProjectUuid.keySet().stream() | |||
.collect(toMap(identity(), componentUuid -> isComponentManaged( | |||
componentUuidToProjectUuid.get(componentUuid), | |||
projectUuidToManaged)) | |||
); | |||
} | |||
private boolean isComponentManaged(String projectUuid, Map<String, Boolean> projectUuidToManaged) { | |||
return ofNullable(projectUuidToManaged.get(projectUuid)).orElse(false); | |||
} | |||
static ComponentQuery buildDbQuery(SearchRequest request) { | |||
List<String> qualifiers = request.getQualifiers(); | |||
ComponentQuery.Builder query = ComponentQuery.builder() | |||
@@ -196,7 +215,7 @@ public class SearchAction implements ProjectsWsAction { | |||
} | |||
private static SearchWsResponse buildResponse(List<ComponentDto> components, Map<String, SnapshotDto> snapshotsByComponentUuid, | |||
Map<String, Long> lastAnalysisDateByComponentUuid, Map<String, String> projectUuidByComponentUuid, Paging paging) { | |||
Map<String, Long> lastAnalysisDateByComponentUuid, Map<String, String> projectUuidByComponentUuid, Map<String, Boolean> componentUuidToManaged, Paging paging) { | |||
SearchWsResponse.Builder responseBuilder = newBuilder(); | |||
responseBuilder.getPagingBuilder() | |||
.setPageIndex(paging.pageIndex()) | |||
@@ -205,12 +224,13 @@ public class SearchAction implements ProjectsWsAction { | |||
.build(); | |||
components.stream() | |||
.map(dto -> dtoToProject(dto, snapshotsByComponentUuid.get(dto.uuid()), lastAnalysisDateByComponentUuid.get(projectUuidByComponentUuid.get(dto.uuid())))) | |||
.map(dto -> dtoToProject(dto, snapshotsByComponentUuid.get(dto.uuid()), lastAnalysisDateByComponentUuid.get(projectUuidByComponentUuid.get(dto.uuid())), | |||
PROJECT.equals(dto.qualifier()) ? componentUuidToManaged.get(dto.uuid()) : null)) | |||
.forEach(responseBuilder::addComponents); | |||
return responseBuilder.build(); | |||
} | |||
private static Component dtoToProject(ComponentDto dto, @Nullable SnapshotDto snapshot, @Nullable Long lastAnalysisDate) { | |||
private static Component dtoToProject(ComponentDto dto, @Nullable SnapshotDto snapshot, @Nullable Long lastAnalysisDate, @Nullable Boolean isManaged) { | |||
Component.Builder builder = Component.newBuilder() | |||
.setKey(dto.getKey()) | |||
.setName(dto.name()) | |||
@@ -220,6 +240,7 @@ public class SearchAction implements ProjectsWsAction { | |||
ofNullable(lastAnalysisDate).ifPresent(d -> builder.setLastAnalysisDate(formatDateTime(d))); | |||
ofNullable(snapshot.getRevision()).ifPresent(builder::setRevision); | |||
} | |||
ofNullable(isManaged).ifPresent(builder::setManaged); | |||
return builder.build(); | |||
} |
@@ -11,15 +11,17 @@ | |||
"qualifier": "TRK", | |||
"visibility": "public", | |||
"lastAnalysisDate": "2017-03-01T11:39:03+0300", | |||
"revision": "cfb82f55c6ef32e61828c4cb3db2da12795fd767" | |||
"revision": "cfb82f55c6ef32e61828c4cb3db2da12795fd767", | |||
"managed": false | |||
}, | |||
{ | |||
"key": "project-key-2", | |||
"name": "Project Name 1", | |||
"name": "Project Name 2", | |||
"qualifier": "TRK", | |||
"visibility": "private", | |||
"lastAnalysisDate": "2017-03-02T15:21:47+0300", | |||
"revision": "7be96a94ac0c95a61ee6ee0ef9c6f808d386a355" | |||
"revision": "7be96a94ac0c95a61ee6ee0ef9c6f808d386a355", | |||
"managed": false | |||
} | |||
] | |||
} |
@@ -71,6 +71,7 @@ message SearchWsResponse { | |||
optional string visibility = 6; | |||
optional string lastAnalysisDate = 7; | |||
optional string revision = 8; | |||
optional bool managed = 9; | |||
} | |||
} | |||