Browse Source

SONAR-19790 Return managed status for project in /api/project/search

tags/10.2.0.77647
Antoine Vigneau 10 months ago
parent
commit
dd463f2cc9

+ 17
- 0
server/sonar-db-dao/src/it/java/org/sonar/db/alm/setting/ProjectAlmSettingDaoIT.java View File

@@ -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");

+ 4
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/alm/setting/ProjectAlmSettingDao.java View File

@@ -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();
}

+ 3
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/alm/setting/ProjectAlmSettingMapper.java View File

@@ -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();
}

+ 13
- 1
server/sonar-db-dao/src/main/resources/org/sonar/db/alm/setting/ProjectAlmSettingMapper.xml View File

@@ -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">

+ 7
- 0
server/sonar-server-common/src/main/java/org/sonar/server/management/DelegatingManagedServices.java View File

@@ -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()

+ 4
- 0
server/sonar-server-common/src/main/java/org/sonar/server/management/ManagedProjectService.java View File

@@ -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);

}

+ 42
- 0
server/sonar-server-common/src/test/java/org/sonar/server/management/DelegatingManagedServicesTest.java View File

@@ -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;

+ 31
- 2
server/sonar-webserver-webapi/src/it/java/org/sonar/server/project/ws/SearchActionIT.java View File

@@ -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"));

+ 28
- 7
server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/SearchAction.java View File

@@ -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();
}

+ 5
- 3
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/project/ws/search-example.json View File

@@ -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
}
]
}

+ 1
- 0
sonar-ws/src/main/protobuf/ws-projects.proto View File

@@ -71,6 +71,7 @@ message SearchWsResponse {
optional string visibility = 6;
optional string lastAnalysisDate = 7;
optional string revision = 8;
optional bool managed = 9;
}
}


Loading…
Cancel
Save