import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-import javax.annotation.Nullable;
import org.assertj.core.groups.Tuple;
import org.junit.After;
import org.junit.Before;
import org.sonarqube.ws.client.project.CreateRequest;
import util.ItUtils;
-import static com.sonar.orchestrator.build.SonarScanner.create;
import static it.Category6Suite.enableOrganizationsSupport;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.groups.Tuple.tuple;
+import static util.ItUtils.concat;
import static util.ItUtils.deleteOrganizationsIfExists;
import static util.ItUtils.newAdminWsClient;
import static util.ItUtils.newProjectKey;
import static util.ItUtils.projectDir;
import static util.ItUtils.restoreProfile;
+import static util.ItUtils.sanitizeTimezones;
import static util.ItUtils.setServerProperty;
/**
@Test
public void filter_projects_by_measure_values() throws Exception {
String projectKey = newProjectKey();
- analyzeProject(projectKey,"shared/xoo-sample");
+ analyzeProject(projectKey, "shared/xoo-sample");
verifyFilterMatches(projectKey, "ncloc > 1");
verifyFilterMatches(projectKey, "ncloc > 1 and comment_lines < 10000");
@Test
public void find_projects_with_no_data() throws Exception {
String projectKey = newProjectKey();
- analyzeProject(projectKey,"shared/xoo-sample");
+ analyzeProject(projectKey, "shared/xoo-sample");
verifyFilterMatches(projectKey, "coverage = NO_DATA");
verifyFilterDoesNotMatch("ncloc = NO_DATA");
}
+ @Test
+ public void provisioned_projects_should_be_included_to_results() throws Exception {
+ String projectKey = newProjectKey();
+ newAdminWsClient(orchestrator).projects().create(CreateRequest.builder().setKey(projectKey).setName(projectKey).setOrganization(organizationKey).build());
+
+ SearchProjectsWsResponse response = searchProjects(SearchProjectsRequest.builder().setOrganization(organizationKey).build());
+
+ assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly(projectKey);
+ }
+
+ @Test
+ public void return_leak_period_date() throws Exception {
+ setServerProperty(orchestrator, "sonar.leak.period", "previous_version");
+ // This project has a leak period
+ String projectKey1 = newProjectKey();
+ analyzeProject(projectKey1, "shared/xoo-sample", "sonar.projectDate", "2016-12-31");
+ analyzeProject(projectKey1, "shared/xoo-sample");
+ // This project has only one analysis, so no leak period
+ String projectKey2 = newProjectKey();
+ analyzeProject(projectKey2, "shared/xoo-sample");
+ // This project is provisioned, so has no leak period
+ String projectKey3 = newProjectKey();
+ newAdminWsClient(orchestrator).projects().create(CreateRequest.builder().setKey(projectKey3).setName(projectKey3).setOrganization(organizationKey).build());
+
+ SearchProjectsWsResponse response = searchProjects(
+ SearchProjectsRequest.builder().setAdditionalFields(singletonList("leakPeriodDate")).setOrganization(organizationKey).build());
+
+ assertThat(response.getComponentsList()).extracting(Component::getKey, Component::hasLeakPeriodDate)
+ .containsOnly(
+ tuple(projectKey1, true),
+ tuple(projectKey2, false),
+ tuple(projectKey3, false));
+ Component project1 = response.getComponentsList().stream().filter(component -> component.getKey().equals(projectKey1)).findFirst()
+ .orElseThrow(() -> new IllegalStateException("Project1 is not found"));
+ assertThat(sanitizeTimezones(project1.getLeakPeriodDate())).isEqualTo("2016-12-31T00:00:00+0000");
+ }
+
@Test
public void filter_by_text_query() throws IOException {
- orchestrator.executeBuild(create(projectDir("shared/xoo-sample"), "sonar.projectKey", "project1", "sonar.projectName", "apachee"));
- orchestrator.executeBuild(create(projectDir("shared/xoo-sample"), "sonar.projectKey", "project2", "sonar.projectName", "Apache"));
- orchestrator.executeBuild(create(projectDir("shared/xoo-multi-modules-sample"), "sonar.projectKey", "project3", "sonar.projectName", "Apache Foundation"));
- orchestrator.executeBuild(create(projectDir("shared/xoo-multi-modules-sample"), "sonar.projectKey", "project4", "sonar.projectName", "Windows"));
+ analyzeProject("project1", "shared/xoo-sample", "sonar.projectName", "apachee");
+ analyzeProject("project2", "shared/xoo-sample", "sonar.projectName", "Apache");
+ analyzeProject("project3", "shared/xoo-multi-modules-sample", "sonar.projectName", "Apache Foundation");
+ analyzeProject("project4", "shared/xoo-multi-modules-sample", "sonar.projectName", "Windows");
// Search only by text query
assertThat(searchProjects("query = \"apache\"").getComponentsList()).extracting(Component::getKey).containsExactly("project2", "project3", "project1");
.containsOnly(tuple("*-1000.0", 0L), tuple("1000.0-10000.0", 0L), tuple("10000.0-100000.0", 0L), tuple("100000.0-500000.0", 0L), tuple("500000.0-*", 0L));
}
- @Test
- public void provisioned_projects_should_be_included_to_results() throws Exception {
- String projectKey = newProjectKey();
- newAdminWsClient(orchestrator).projects().create(CreateRequest.builder().setKey(projectKey).setName(projectKey).setOrganization(organizationKey).build());
-
- SearchProjectsWsResponse response = searchProjects(SearchProjectsRequest.builder().setOrganization(organizationKey).build());
-
- assertThat(response.getComponentsList()).extracting(Component::getKey).contains(projectKey);
- }
-
@Test
public void should_return_facets() throws Exception {
analyzeProject(newProjectKey(), "shared/xoo-sample");
setServerProperty(orchestrator, "sonar.leak.period", "previous_analysis");
// This project has no duplication on new code
String projectKey1 = newProjectKey();
- analyzeProject(projectKey1, "shared/xoo-sample", "2016-12-31");
+ analyzeProject(projectKey1, "shared/xoo-sample", "sonar.projectDate", "2016-12-31");
analyzeProject(projectKey1, "shared/xoo-sample");
// This project has 0% duplication on new code
String projectKey2 = newProjectKey();
- analyzeProject(projectKey2, "projectSearch/xoo-history-v1", "2016-12-31");
+ analyzeProject(projectKey2, "projectSearch/xoo-history-v1", "sonar.projectDate", "2016-12-31");
analyzeProject(projectKey2, "projectSearch/xoo-history-v2");
SearchProjectsWsResponse response = searchProjects(SearchProjectsRequest.builder().setOrganization(organizationKey).setFacets(asList(
assertThat(facet.getValuesList()).extracting(Common.FacetValue::getVal, Common.FacetValue::getCount).containsExactlyInAnyOrder(values);
}
- private void analyzeProject(String projectKey, String relativePath) {
- analyzeProject(projectKey, relativePath, null);
- }
-
- private void analyzeProject(String projectKey, String relativePath, @Nullable String analysisDate) {
+ private void analyzeProject(String projectKey, String relativePath, String... properties) {
List<String> keyValueProperties = new ArrayList<>(asList(
"sonar.projectKey", projectKey,
"sonar.organization", organizationKey,
"sonar.profile", "with-many-rules",
"sonar.login", "admin", "sonar.password", "admin",
- "sonar.scm.disabled", "false"
- ));
- if (analysisDate != null) {
- keyValueProperties.add("sonar.projectDate");
- keyValueProperties.add(analysisDate);
- }
- orchestrator.executeBuild(SonarScanner.create(projectDir(relativePath), keyValueProperties.toArray(new String[0])));
+ "sonar.scm.disabled", "false"));
+ orchestrator.executeBuild(SonarScanner.create(projectDir(relativePath), concat(keyValueProperties.toArray(new String[0]), properties)));
}
private SearchProjectsWsResponse searchProjects(String filter) throws IOException {
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.DateUtils;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.server.es.Facets;
import org.sonar.server.es.SearchIdResult;
import org.sonar.server.es.SearchOptions;
-import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.measure.index.ProjectMeasuresIndex;
import org.sonar.server.measure.index.ProjectMeasuresQuery;
import org.sonar.server.project.Visibility;
import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
import static org.sonar.api.server.ws.WebService.Param.FIELDS;
+import static org.sonar.api.utils.DateUtils.formatDateTime;
+import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.core.util.stream.MoreCollectors.toSet;
import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.IS_FAVORITE_CRITERION;
import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.newProjectMeasuresQuery;
import static org.sonar.server.measure.index.ProjectMeasuresIndex.SUPPORTED_FACETS;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_LAST_ANALYSIS_DATE;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_NAME;
+import static org.sonar.server.ws.WsUtils.checkFound;
import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_FILTER;
public class SearchProjectsAction implements ComponentsWsAction {
private static final String ANALYSIS_DATE = "analysisDate";
- private static final Set<String> POSSIBLE_FIELDS = newHashSet(ANALYSIS_DATE);
+ private static final String LEAK_PERIOD_DATE = "leakPeriodDate";
+ private static final Set<String> POSSIBLE_FIELDS = newHashSet(ANALYSIS_DATE, LEAK_PERIOD_DATE);
private final DbClient dbClient;
private final ProjectMeasuresIndex index;
new Change("6.4", format("The '%s' parameter accepts '%s' to filter by language", FILTER_LANGUAGES, PARAM_FILTER)),
new Change("6.4", "The 'visibility' field is added"),
new Change("6.5", "The 'filter' parameter now allows 'NO_DATA' as value for numeric metrics"),
- new Change("6.5", "Added the option 'analysisDate' for the 'sort' parameter")
- )
+ new Change("6.5", "Added the option 'analysisDate' for the 'sort' parameter"),
+ new Change("6.5", format("Value '%s' is added to parameter '%s'", LEAK_PERIOD_DATE, FIELDS)))
.setHandler(this);
action.createFieldsParam(POSSIBLE_FIELDS)
}
private Map<String, SnapshotDto> getSnapshots(DbSession dbSession, SearchProjectsRequest request, List<String> projectUuids) {
- if (request.getAdditionalFields().contains(ANALYSIS_DATE)) {
+ if (request.getAdditionalFields().contains(ANALYSIS_DATE) || request.getAdditionalFields().contains(LEAK_PERIOD_DATE)) {
return dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids)
.stream()
.collect(MoreCollectors.uniqueIndex(SnapshotDto::getComponentUuid));
if (httpRequest.hasParam(Param.FACETS)) {
request.setFacets(httpRequest.paramAsStrings(Param.FACETS));
}
- if (httpRequest.hasParam(Param.FIELDS)) {
+ if (httpRequest.hasParam(FIELDS)) {
request.setAdditionalFields(httpRequest.paramAsStrings(FIELDS));
}
return request.build();
}
private SearchProjectsWsResponse buildResponse(SearchProjectsRequest request, SearchResults searchResults, Map<String, OrganizationDto> organizationsByUuid) {
- Function<ComponentDto, Component> dbToWsComponent = new DbToWsComponent(organizationsByUuid, searchResults.favoriteProjectUuids, searchResults.analysisByProjectUuid,
+ Function<ComponentDto, Component> dbToWsComponent = new DbToWsComponent(request, organizationsByUuid, searchResults.favoriteProjectUuids, searchResults.analysisByProjectUuid,
userSession.isLoggedIn());
return Stream.of(SearchProjectsWsResponse.newBuilder())
}
private static class DbToWsComponent implements Function<ComponentDto, Component> {
+ private final SearchProjectsRequest request;
private final Component.Builder wsComponent;
private final Map<String, OrganizationDto> organizationsByUuid;
private final Set<String> favoriteProjectUuids;
private final boolean isUserLoggedIn;
private final Map<String, SnapshotDto> analysisByProjectUuid;
- private DbToWsComponent(Map<String, OrganizationDto> organizationsByUuid, Set<String> favoriteProjectUuids, Map<String, SnapshotDto> analysisByProjectUuid,
- boolean isUserLoggedIn) {
+ private DbToWsComponent(SearchProjectsRequest request, Map<String, OrganizationDto> organizationsByUuid, Set<String> favoriteProjectUuids,
+ Map<String, SnapshotDto> analysisByProjectUuid, boolean isUserLoggedIn) {
+ this.request = request;
this.analysisByProjectUuid = analysisByProjectUuid;
this.wsComponent = Component.newBuilder();
this.organizationsByUuid = organizationsByUuid;
@Override
public Component apply(ComponentDto dbComponent) {
- OrganizationDto organizationDto = organizationsByUuid.get(dbComponent.getOrganizationUuid());
- if (organizationDto == null) {
- throw new NotFoundException(format("Organization with uuid '%s' not found", dbComponent.getOrganizationUuid()));
- }
+ String organizationUuid = dbComponent.getOrganizationUuid();
+ OrganizationDto organizationDto = organizationsByUuid.get(organizationUuid);
+ checkFound(organizationDto, "Organization with uuid '%s' not found", organizationUuid);
wsComponent
.clear()
.setOrganization(organizationDto.getKey())
SnapshotDto snapshotDto = analysisByProjectUuid.get(dbComponent.uuid());
if (snapshotDto != null) {
- wsComponent.setAnalysisDate(DateUtils.formatDateTime(snapshotDto.getCreatedAt()));
+ if (request.getAdditionalFields().contains(ANALYSIS_DATE)) {
+ wsComponent.setAnalysisDate(formatDateTime(snapshotDto.getCreatedAt()));
+ }
+ if (request.getAdditionalFields().contains(LEAK_PERIOD_DATE)) {
+ setNullable(snapshotDto.getPeriodDate(), leakPeriodDate -> wsComponent.setLeakPeriodDate(formatDateTime(leakPeriodDate)));
+ }
}
if (isUserLoggedIn) {
tuple("6.4", "The 'languages' parameter accepts 'filter' to filter by language"),
tuple("6.4", "The 'visibility' field is added"),
tuple("6.5", "The 'filter' parameter now allows 'NO_DATA' as value for numeric metrics"),
- tuple("6.5", "Added the option 'analysisDate' for the 'sort' parameter")
- );
+ tuple("6.5", "Added the option 'analysisDate' for the 'sort' parameter"),
+ tuple("6.5", "Value 'leakPeriodDate' is added to parameter 'f'"));
Param organization = def.param("organization");
assertThat(organization.isRequired()).isFalse();
Param additionalFields = def.param("f");
assertThat(additionalFields.defaultValue()).isNull();
- assertThat(additionalFields.possibleValues()).containsOnly("analysisDate");
+ assertThat(additionalFields.possibleValues()).containsOnly("analysisDate", "leakPeriodDate");
Param facets = def.param("facets");
assertThat(facets.defaultValue()).isNull();
SearchProjectsWsResponse result = call(request.setAdditionalFields(singletonList("analysisDate")));
- assertThat(result.getComponentsList()).extracting(Component::getAnalysisDate)
- .containsOnly(formatDateTime(new Date(20_000_000_000L)), formatDateTime(new Date(30_000_000_000L)), "");
+ assertThat(result.getComponentsList()).extracting(Component::getKey, Component::hasAnalysisDate, Component::getAnalysisDate)
+ .containsOnly(
+ tuple(project1.getKey(), true, formatDateTime(new Date(20_000_000_000L))),
+ tuple(project2.getKey(), true, formatDateTime(new Date(30_000_000_000L))),
+ tuple(project3.getKey(), false, ""));
+ }
+
+ @Test
+ public void return_leak_period_date() {
+ userSession.logIn();
+ OrganizationDto organization = db.organizations().insert();
+ ComponentDto project1 = db.components().insertPublicProject(organization);
+ db.components().insertSnapshot(project1, snapshot -> snapshot.setPeriodDate(10_000_000_000L));
+ authorizationIndexerTester.allowOnlyAnyone(project1);
+ // No leak period
+ ComponentDto project2 = db.components().insertPublicProject(organization);
+ db.components().insertSnapshot(project2, snapshot -> snapshot.setPeriodDate(null));
+ authorizationIndexerTester.allowOnlyAnyone(project2);
+ // No snapshot on project 3
+ ComponentDto project3 = db.components().insertPublicProject(organization);
+ authorizationIndexerTester.allowOnlyAnyone(project3);
+ projectMeasuresIndexer.indexOnStartup(null);
+
+ SearchProjectsWsResponse result = call(request.setAdditionalFields(singletonList("leakPeriodDate")));
+
+ assertThat(result.getComponentsList()).extracting(Component::getKey, Component::hasLeakPeriodDate, Component::getLeakPeriodDate)
+ .containsOnly(
+ tuple(project1.getKey(), true, formatDateTime(new Date(10_000_000_000L))),
+ tuple(project2.getKey(), false, ""),
+ tuple(project3.getKey(), false, ""));
}
@Test
optional string analysisDate = 13;
optional Tags tags = 14;
optional string visibility = 15;
+ optional string leakPeriodDate = 16;
message Tags {
repeated string tags = 1;