diff options
author | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2017-08-30 15:46:54 +0200 |
---|---|---|
committer | Stas Vilchik <stas.vilchik@sonarsource.com> | 2017-09-11 11:28:29 +0200 |
commit | ce06125872c315509d4687cb8cf83fff38ba31ec (patch) | |
tree | 31497bc059695fb26f70738d9b398d71099cc387 | |
parent | 71140666ff0e26cc77ac42e27e8badbc4fff8893 (diff) | |
download | sonarqube-ce06125872c315509d4687cb8cf83fff38ba31ec.tar.gz sonarqube-ce06125872c315509d4687cb8cf83fff38ba31ec.zip |
SONAR-4566 Search old projects in WS api/projects/search
14 files changed, 213 insertions, 20 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentQuery.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentQuery.java index 72ed52dd99c..58858246033 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentQuery.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentQuery.java @@ -34,6 +34,7 @@ public class ComponentQuery { private final String language; private final Boolean isPrivate; private final Set<Long> componentIds; + private final Long analyzedBefore; /** * Used by Dev Cockpit 1.9. @@ -48,6 +49,7 @@ public class ComponentQuery { this.language = null; this.componentIds = null; this.isPrivate = null; + this.analyzedBefore = null; } private ComponentQuery(Builder builder) { @@ -56,6 +58,7 @@ public class ComponentQuery { this.language = builder.language; this.componentIds = builder.componentIds; this.isPrivate = builder.isPrivate; + this.analyzedBefore = builder.analyzedBefore; } public String[] getQualifiers() { @@ -90,6 +93,11 @@ public class ComponentQuery { return isPrivate; } + @CheckForNull + public Long getAnalyzedBefore() { + return analyzedBefore; + } + public static Builder builder() { return new Builder(); } @@ -100,6 +108,7 @@ public class ComponentQuery { private String language; private Boolean isPrivate; private Set<Long> componentIds; + private Long analyzedBefore; public Builder setNameOrKeyQuery(@Nullable String nameOrKeyQuery) { this.nameOrKeyQuery = nameOrKeyQuery; @@ -126,6 +135,11 @@ public class ComponentQuery { return this; } + public Builder setAnalyzedBefore(@Nullable Long analyzedBefore) { + this.analyzedBefore = analyzedBefore; + return this; + } + protected static String[] validateQualifiers(@Nullable String... qualifiers) { checkArgument(qualifiers != null && qualifiers.length > 0, "At least one qualifier must be provided"); return qualifiers; diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml index 1c5a72ff24c..905d1ef8717 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml @@ -249,23 +249,27 @@ <sql id="sqlSelectByQuery"> from projects p + <if test="query.analyzedBefore!=null"> + inner join snapshots s on s.component_uuid=p.uuid and s.status='P' and s.islast=${_true} + and s.created_at < #{query.analyzedBefore,jdbcType=BIGINT} + </if> where p.enabled=${_true} - AND p.copy_component_uuid is null + and p.copy_component_uuid is null <if test="organizationUuid!=null"> and p.organization_uuid=#{organizationUuid,jdbcType=VARCHAR} </if> <if test="query.qualifiers!=null"> - AND p.qualifier in - <foreach collection="query.qualifiers" item="qualifier" open="(" close=")" separator=","> - #{qualifier,jdbcType=VARCHAR} - </foreach> + and p.qualifier in + <foreach collection="query.qualifiers" item="qualifier" open="(" close=")" separator=","> + #{qualifier,jdbcType=VARCHAR} + </foreach> </if> <if test="query.language!=null"> - AND p.language = #{query.language,jdbcType=VARCHAR} + and p.language = #{query.language,jdbcType=VARCHAR} </if> <if test="query.componentIds!=null"> - AND p.id in + and p.id in <foreach collection="query.componentIds" item="componentId" open="(" close=")" separator=","> #{componentId,jdbcType=BIGINT} </foreach> diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java index b63377d8427..aae487bf389 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java @@ -58,6 +58,7 @@ import static org.sonar.db.component.ComponentTesting.newSubView; import static org.sonar.db.component.ComponentTesting.newView; import static org.sonar.db.component.ComponentTreeQuery.Strategy.CHILDREN; import static org.sonar.db.component.ComponentTreeQuery.Strategy.LEAVES; +import static org.sonar.db.component.SnapshotTesting.newAnalysis; public class ComponentDaoTest { @@ -985,6 +986,25 @@ public class ComponentDaoTest { } @Test + public void selectByQuery_filter_on_last_analysis_date() { + long aLongTimeAgo = 1_000_000_000L; + long recentTime = 3_000_000_000L; + ComponentDto oldProject = db.components().insertPublicProject(); + db.getDbClient().snapshotDao().insert(dbSession, newAnalysis(oldProject).setCreatedAt(aLongTimeAgo)); + ComponentDto recentProject = db.components().insertPublicProject(); + db.getDbClient().snapshotDao().insert(dbSession, newAnalysis(recentProject).setCreatedAt(recentTime)); + db.getDbClient().snapshotDao().insert(dbSession, newAnalysis(recentProject).setCreatedAt(aLongTimeAgo).setLast(false)); + ComponentQuery.Builder query = ComponentQuery.builder().setQualifiers(Qualifiers.PROJECT); + + assertThat(underTest.selectByQuery(dbSession, query.setAnalyzedBefore(recentTime).build(), 0, 10)).extracting(ComponentDto::getKey) + .containsExactlyInAnyOrder(oldProject.getKey()); + assertThat(underTest.selectByQuery(dbSession, query.setAnalyzedBefore(aLongTimeAgo).build(), 0, 10)).extracting(ComponentDto::getKey) + .isEmpty(); + assertThat(underTest.selectByQuery(dbSession, query.setAnalyzedBefore(recentTime + 1_000L).build(), 0, 10)).extracting(ComponentDto::getKey) + .containsExactlyInAnyOrder(oldProject.getKey(), recentProject.getKey()); + } + + @Test public void selectByQuery_filter_on_visibility() { db.components().insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("private-key")); db.components().insertComponent(ComponentTesting.newPublicProjectDto(db.getDefaultOrganization()).setDbKey("public-key")); diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentQueryTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentQueryTest.java index 277cfcc8622..1b6971c6fc7 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentQueryTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentQueryTest.java @@ -36,12 +36,14 @@ public class ComponentQueryTest { ComponentQuery underTest = ComponentQuery.builder() .setNameOrKeyQuery("key") .setLanguage("java") + .setAnalyzedBefore(1_000_000_000L) .setQualifiers(PROJECT) .build(); assertThat(underTest.getNameOrKeyQuery()).isEqualTo("key"); assertThat(underTest.getLanguage()).isEqualTo("java"); assertThat(underTest.getQualifiers()).containsOnly(PROJECT); + assertThat(underTest.getAnalyzedBefore()).isEqualTo(1_000_000_000L); } @Test diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java index e801645689c..9aed6b6e00f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java @@ -20,16 +20,20 @@ package org.sonar.server.project.ws; import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; 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.api.server.ws.WebService.Param; import org.sonar.api.utils.Paging; +import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentQuery; +import org.sonar.db.component.SnapshotDto; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.permission.OrganizationPermission; import org.sonar.server.organization.DefaultOrganizationProvider; @@ -43,6 +47,8 @@ import static java.util.Optional.ofNullable; import static org.sonar.api.resources.Qualifiers.APP; import static org.sonar.api.resources.Qualifiers.PROJECT; import static org.sonar.api.resources.Qualifiers.VIEW; +import static org.sonar.api.utils.DateUtils.formatDateTime; +import static org.sonar.api.utils.DateUtils.parseDateOrDateTime; import static org.sonar.core.util.Protobuf.setNullable; import static org.sonar.server.project.Visibility.PRIVATE; import static org.sonar.server.project.Visibility.PUBLIC; @@ -51,6 +57,7 @@ import static org.sonarqube.ws.WsProjects.SearchWsResponse.Component; import static org.sonarqube.ws.WsProjects.SearchWsResponse.newBuilder; import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_SEARCH; import static org.sonarqube.ws.client.project.ProjectsWsParameters.MAX_PAGE_SIZE; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ANALYZED_BEFORE; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ORGANIZATION; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_QUALIFIERS; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_VISIBILITY; @@ -97,6 +104,11 @@ public class SearchAction implements ProjectsWsAction { .setInternal(true) .setSince("6.4") .setPossibleValues(Visibility.getLabels()); + + action.createParam(PARAM_ANALYZED_BEFORE) + .setDescription("Filter the projects for which last analysis is older than the given date (exclusive).<br> " + + "Format: date or datetime ISO formats.") + .setSince("6.6"); } @Override @@ -113,6 +125,7 @@ public class SearchAction implements ProjectsWsAction { .setPage(request.mandatoryParamAsInt(Param.PAGE)) .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE)) .setVisibility(request.param(PARAM_VISIBILITY)) + .setAnalyzedBefore(request.param(PARAM_ANALYZED_BEFORE)) .build(); } @@ -124,7 +137,10 @@ public class SearchAction implements ProjectsWsAction { ComponentQuery query = buildQuery(request); Paging paging = buildPaging(dbSession, request, organization, query); List<ComponentDto> components = dbClient.componentDao().selectByQuery(dbSession, organization.getUuid(), query, paging.offset(), paging.pageSize()); - return buildResponse(components, organization, paging); + Map<String, Long> analysisDateByComponentUuid = dbClient.snapshotDao() + .selectLastAnalysesByRootComponentUuids(dbSession, components.stream().map(ComponentDto::uuid).collect(MoreCollectors.toList())).stream() + .collect(MoreCollectors.uniqueIndex(SnapshotDto::getComponentUuid, SnapshotDto::getCreatedAt)); + return buildResponse(components, organization, analysisDateByComponentUuid, paging); } } @@ -135,6 +151,7 @@ public class SearchAction implements ProjectsWsAction { .setQualifiers(qualifiers.toArray(new String[qualifiers.size()])); setNullable(request.getVisibility(), v -> query.setPrivate(Visibility.isPrivate(v))); + setNullable(request.getAnalyzedBefore(), d -> query.setAnalyzedBefore(parseDateOrDateTime(d).getTime())); return query.build(); } @@ -146,7 +163,7 @@ public class SearchAction implements ProjectsWsAction { .andTotal(total); } - private static SearchWsResponse buildResponse(List<ComponentDto> components, OrganizationDto organization, Paging paging) { + private static SearchWsResponse buildResponse(List<ComponentDto> components, OrganizationDto organization, Map<String, Long> analysisDateByComponentUuid, Paging paging) { SearchWsResponse.Builder responseBuilder = newBuilder(); responseBuilder.getPagingBuilder() .setPageIndex(paging.pageIndex()) @@ -155,12 +172,12 @@ public class SearchAction implements ProjectsWsAction { .build(); components.stream() - .map(dto -> dtoToProject(organization, dto)) + .map(dto -> dtoToProject(organization, dto, analysisDateByComponentUuid.get(dto.uuid()))) .forEach(responseBuilder::addComponents); return responseBuilder.build(); } - private static Component dtoToProject(OrganizationDto organization, ComponentDto dto) { + private static Component dtoToProject(OrganizationDto organization, ComponentDto dto, @Nullable Long analysisDate) { checkArgument( organization.getUuid().equals(dto.getOrganizationUuid()), "No Organization found for uuid '%s'", @@ -173,6 +190,8 @@ public class SearchAction implements ProjectsWsAction { .setName(dto.name()) .setQualifier(dto.qualifier()) .setVisibility(dto.isPrivate() ? PRIVATE.getLabel() : PUBLIC.getLabel()); + setNullable(analysisDate, d -> builder.setLastAnalysisDate(formatDateTime(d))); + return builder.build(); } diff --git a/server/sonar-server/src/main/resources/org/sonar/server/project/ws/search-example.json b/server/sonar-server/src/main/resources/org/sonar/server/project/ws/search-example.json index dd9c8a510c2..ac6accbe924 100644 --- a/server/sonar-server/src/main/resources/org/sonar/server/project/ws/search-example.json +++ b/server/sonar-server/src/main/resources/org/sonar/server/project/ws/search-example.json @@ -11,7 +11,8 @@ "key": "project-key-1", "name": "Project Name 1", "qualifier": "TRK", - "visibility": "public" + "visibility": "public", + "lastAnalysisDate": "2017-03-01T11:39:03+0300" }, { "organization": "my-org-1", @@ -19,7 +20,8 @@ "key": "project-key-2", "name": "Project Name 1", "qualifier": "TRK", - "visibility": "private" + "visibility": "private", + "lastAnalysisDate": "2017-03-02T15:21:47+0300" } ] } diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java index 356e9cf9033..c6739877a71 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java @@ -23,6 +23,7 @@ import com.google.common.base.Joiner; import java.io.IOException; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Date; import java.util.List; import org.assertj.core.api.Assertions; import org.junit.Rule; @@ -54,15 +55,19 @@ import static org.mockito.Mockito.mock; 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; +import static org.sonar.api.utils.DateUtils.formatDate; +import static org.sonar.api.utils.DateUtils.parseDateTime; import static org.sonar.core.util.Protobuf.setNullable; import static org.sonar.db.component.ComponentTesting.newDirectory; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.ComponentTesting.newModuleDto; import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; import static org.sonar.db.component.ComponentTesting.newView; +import static org.sonar.db.component.SnapshotTesting.newAnalysis; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_PROFILES; import static org.sonar.test.JsonAssert.assertJson; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ANALYZED_BEFORE; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ORGANIZATION; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_VISIBILITY; @@ -205,6 +210,24 @@ public class SearchActionTest { } @Test + public void search_for_old_projects() { + userSession.addPermission(ADMINISTER, db.getDefaultOrganization()); + long aLongTimeAgo = 1_000_000_000L; + long recentTime = 3_000_000_000L; + ComponentDto oldProject = db.components().insertPublicProject(); + db.getDbClient().snapshotDao().insert(db.getSession(), newAnalysis(oldProject).setCreatedAt(aLongTimeAgo)); + ComponentDto recentProject = db.components().insertPublicProject(); + db.getDbClient().snapshotDao().insert(db.getSession(), newAnalysis(recentProject).setCreatedAt(recentTime)); + db.commit(); + + SearchWsResponse result = call(SearchWsRequest.builder().setAnalyzedBefore(formatDate(new Date(recentTime))).build()); + + assertThat(result.getComponentsList()).extracting(Component::getKey) + .containsExactlyInAnyOrder(oldProject.getKey()) + .doesNotContain(recentProject.getKey()); + } + + @Test public void result_is_paginated() throws IOException { userSession.addPermission(ADMINISTER, db.getDefaultOrganization()); List<ComponentDto> componentDtoList = new ArrayList<>(); @@ -243,7 +266,7 @@ public class SearchActionTest { } @Test - public void verify_define() { + public void definition() { WebService.Action action = ws.getDef(); assertThat(action.key()).isEqualTo("search"); assertThat(action.isPost()).isFalse(); @@ -251,7 +274,7 @@ public class SearchActionTest { assertThat(action.isInternal()).isTrue(); assertThat(action.since()).isEqualTo("6.3"); assertThat(action.handler()).isEqualTo(ws.getDef().handler()); - assertThat(action.params()).hasSize(6); + assertThat(action.params()).hasSize(7); assertThat(action.responseExample()).isEqualTo(getClass().getResource("search-example.json")); WebService.Param organization = action.param("organization"); @@ -284,21 +307,32 @@ public class SearchActionTest { assertThat(visibilityParam.isRequired()).isFalse(); assertThat(visibilityParam.description()).isEqualTo("Filter the projects that should be visible to everyone (public), or only specific user/groups (private).<br/>" + "If no visibility is specified, the default project visibility of the organization will be used."); + + WebService.Param lastAnalysisBefore = action.param("analyzedBefore"); + assertThat(lastAnalysisBefore.isRequired()).isFalse(); + assertThat(lastAnalysisBefore.since()).isEqualTo("6.6"); } @Test - public void verify_response_example() throws URISyntaxException, IOException { + public void json_example() throws URISyntaxException, IOException { OrganizationDto organization = db.organizations().insertForKey("my-org-1"); userSession.addPermission(ADMINISTER, organization); + ComponentDto publicProject = newPrivateProjectDto(organization, "project-uuid-1").setName("Project Name 1").setDbKey("project-key-1").setPrivate(false); + ComponentDto privateProject = newPrivateProjectDto(organization, "project-uuid-2").setName("Project Name 1").setDbKey("project-key-2"); db.components().insertComponents( - newPrivateProjectDto(organization, "project-uuid-1").setName("Project Name 1").setDbKey("project-key-1").setPrivate(false), - newPrivateProjectDto(organization, "project-uuid-2").setName("Project Name 1").setDbKey("project-key-2")); + publicProject, + privateProject); + db.getDbClient().snapshotDao().insert(db.getSession(), newAnalysis(publicProject).setCreatedAt(parseDateTime("2017-03-01T11:39:03+0300").getTime())); + db.getDbClient().snapshotDao().insert(db.getSession(), newAnalysis(privateProject).setCreatedAt(parseDateTime("2017-03-02T15:21:47+0300").getTime())); + db.commit(); String response = ws.newRequest() .setMediaType(MediaTypes.JSON) .setParam(PARAM_ORGANIZATION, organization.getKey()) .execute().getInput(); + assertJson(response).isSimilarTo(ws.getDef().responseExampleAsString()); + assertJson(ws.getDef().responseExampleAsString()).isSimilarTo(response); } private SearchWsResponse call(SearchWsRequest wsRequest) { @@ -312,6 +346,7 @@ public class SearchActionTest { setNullable(wsRequest.getPage(), page -> request.setParam(PAGE, String.valueOf(page))); setNullable(wsRequest.getPageSize(), pageSize -> request.setParam(PAGE_SIZE, String.valueOf(pageSize))); setNullable(wsRequest.getVisibility(), v -> request.setParam(PARAM_VISIBILITY, v)); + setNullable(wsRequest.getAnalyzedBefore(), d -> request.setParam(PARAM_ANALYZED_BEFORE, d)); return request.executeProtobuf(SearchWsResponse.class); } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsService.java index f43c40d50be..5dde5e0939f 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsService.java @@ -31,12 +31,14 @@ import org.sonarqube.ws.client.WsConnector; 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; +import static org.sonar.api.utils.DateUtils.formatDateTimeNullSafe; import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_BULK_UPDATE_KEY; import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_CREATE; import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_SEARCH; import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_UPDATE_KEY; import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_UPDATE_VISIBILITY; import static org.sonarqube.ws.client.project.ProjectsWsParameters.CONTROLLER; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ANALYZED_BEFORE; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_BRANCH; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_FROM; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NAME; @@ -112,6 +114,7 @@ public class ProjectsService extends BaseService { GetRequest get = new GetRequest(path(ACTION_SEARCH)) .setParam(PARAM_ORGANIZATION, request.getOrganization()) .setParam(PARAM_QUALIFIERS, Joiner.on(",").join(request.getQualifiers())) + .setParam(PARAM_ANALYZED_BEFORE, request.getAnalyzedBefore()) .setParam(TEXT_QUERY, request.getQuery()) .setParam(PAGE, request.getPage()) .setParam(PAGE_SIZE, request.getPageSize()); diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java index f4e9c7bc438..e03d82faa64 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java @@ -41,8 +41,9 @@ public class ProjectsWsParameters { public static final String PARAM_FROM = "from"; public static final String PARAM_TO = "to"; public static final String PARAM_DRY_RUN = "dryRun"; - public static final String PARAM_VISIBILITY = "visibility"; + public static final String PARAM_ANALYZED_BEFORE = "analyzedBefore"; + public static final String FILTER_LANGUAGES = "languages"; public static final String FILTER_TAGS = "tags"; diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/SearchWsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/SearchWsRequest.java index 2b0be938a08..ef36c34b61d 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/SearchWsRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/SearchWsRequest.java @@ -36,6 +36,7 @@ public class SearchWsRequest { private final String visibility; private final Integer page; private final Integer pageSize; + private final String analyzedBefore; public SearchWsRequest(Builder builder) { this.organization = builder.organization; @@ -44,6 +45,7 @@ public class SearchWsRequest { this.visibility = builder.visibility; this.page = builder.page; this.pageSize = builder.pageSize; + this.analyzedBefore = builder.analyzedBefore; } @CheckForNull @@ -75,6 +77,11 @@ public class SearchWsRequest { return visibility; } + @CheckForNull + public String getAnalyzedBefore() { + return analyzedBefore; + } + public static Builder builder() { return new Builder(); } @@ -86,6 +93,7 @@ public class SearchWsRequest { private Integer pageSize; private String query; private String visibility; + private String analyzedBefore; public Builder setOrganization(@Nullable String organization) { this.organization = organization; @@ -117,10 +125,14 @@ public class SearchWsRequest { return this; } + public Builder setAnalyzedBefore(@Nullable String lastAnalysisBefore) { + this.analyzedBefore = lastAnalysisBefore; + return this; + } + public SearchWsRequest build() { checkArgument(pageSize == null || pageSize <= MAX_PAGE_SIZE, "Page size must not be greater than %s", MAX_PAGE_SIZE); return new SearchWsRequest(this); } } - } diff --git a/sonar-ws/src/main/protobuf/ws-projects.proto b/sonar-ws/src/main/protobuf/ws-projects.proto index 0ec1cbad85c..8668ffc355a 100644 --- a/sonar-ws/src/main/protobuf/ws-projects.proto +++ b/sonar-ws/src/main/protobuf/ws-projects.proto @@ -70,6 +70,7 @@ message SearchWsResponse { optional string name = 4; optional string qualifier = 5; optional string visibility = 6; + optional string lastAnalysisDate = 7; } } diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/project/ProjectsServiceTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/project/ProjectsServiceTest.java index 8d21940ab02..d8b8c7e5e6f 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/project/ProjectsServiceTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/project/ProjectsServiceTest.java @@ -138,6 +138,7 @@ public class ProjectsServiceTest { .setOrganization("default") .setQuery("project") .setQualifiers(asList("TRK", "VW")) + .setAnalyzedBefore("2017-09-01") .setPage(3) .setPageSize(10) .build()); @@ -146,6 +147,7 @@ public class ProjectsServiceTest { .hasPath("search") .hasParam("organization", "default") .hasParam("q", "project") + .hasParam("analyzedBefore", "2017-09-01") .hasParam("qualifiers", "TRK,VW") .hasParam(PAGE, 3) .hasParam(PAGE_SIZE, 10) diff --git a/tests/src/test/java/org/sonarqube/tests/Category6Suite.java b/tests/src/test/java/org/sonarqube/tests/Category6Suite.java index 01e7db07d07..d075f209596 100644 --- a/tests/src/test/java/org/sonarqube/tests/Category6Suite.java +++ b/tests/src/test/java/org/sonarqube/tests/Category6Suite.java @@ -38,6 +38,7 @@ import org.sonarqube.tests.organization.RootUserOnOrganizationTest; import org.sonarqube.tests.projectAdministration.ProjectDeletionTest; import org.sonarqube.tests.projectAdministration.ProjectKeyUpdateTest; import org.sonarqube.tests.projectAdministration.ProjectProvisioningTest; +import org.sonarqube.tests.projectAdministration.ProjectSearchTest; import org.sonarqube.tests.projectSearch.LeakProjectsPageTest; import org.sonarqube.tests.projectSearch.SearchProjectsTest; import org.sonarqube.tests.qualityGate.OrganizationQualityGateUiTest; @@ -79,6 +80,7 @@ import static util.ItUtils.xooPlugin; ProjectDeletionTest.class, ProjectProvisioningTest.class, ProjectKeyUpdateTest.class, + ProjectSearchTest.class, PermissionTemplateTest.class }) public class Category6Suite { diff --git a/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectSearchTest.java b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectSearchTest.java new file mode 100644 index 00000000000..69178286b3b --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectSearchTest.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonarqube.tests.projectAdministration; + +import com.sonar.orchestrator.Orchestrator; +import java.util.Date; +import org.apache.commons.lang.time.DateUtils; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Category6Suite; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Organizations; +import org.sonarqube.ws.WsProjects.CreateWsResponse; +import org.sonarqube.ws.WsProjects.SearchWsResponse; +import org.sonarqube.ws.client.project.SearchWsRequest; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.formatDate; +import static util.ItUtils.runProjectAnalysis; + +public class ProjectSearchTest { + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + @Rule + public Tester tester = new Tester(orchestrator); + + @Test + public void search_old_projects() { + Organizations.Organization organization = tester.organizations().generate(); + CreateWsResponse.Project oldProject = tester.projects().generate(organization); + CreateWsResponse.Project recentProject = tester.projects().generate(organization); + Date now = new Date(); + Date oneYearAgo = DateUtils.addDays(now, -365); + Date moreThanOneYearAgo = DateUtils.addDays(now, -366); + + analyzeProject(oldProject.getKey(), moreThanOneYearAgo, organization.getKey()); + analyzeProject(recentProject.getKey(), now, organization.getKey()); + + SearchWsResponse result = tester.wsClient().projects().search(SearchWsRequest.builder() + .setOrganization(organization.getKey()) + .setQualifiers(singletonList("TRK")) + .setAnalyzedBefore(formatDate(oneYearAgo)).build()); + + assertThat(result.getComponentsList()).extracting(SearchWsResponse.Component::getKey).containsExactlyInAnyOrder(oldProject.getKey()); + } + + private void analyzeProject(String projectKey, Date analysisDate, String organizationKey) { + runProjectAnalysis(orchestrator, "shared/xoo-sample", + "sonar.organization", organizationKey, + "sonar.projectKey", projectKey, + "sonar.projectDate", formatDate(analysisDate), + "sonar.login", "admin", + "sonar.password", "admin"); + } +} |