@@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableMap; | |||
import com.google.common.collect.Ordering; | |||
import java.util.ArrayList; | |||
import java.util.Collections; | |||
import java.util.HashMap; | |||
import java.util.LinkedHashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
@@ -58,16 +59,19 @@ import org.sonar.server.measure.index.ProjectMeasuresQuery; | |||
import org.sonar.server.project.Visibility; | |||
import org.sonar.server.user.UserSession; | |||
import org.sonarqube.ws.Common; | |||
import org.sonarqube.ws.Components; | |||
import org.sonarqube.ws.Components.Component; | |||
import org.sonarqube.ws.Components.SearchProjectsWsResponse; | |||
import static com.google.common.base.MoreObjects.firstNonNull; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
import static com.google.common.collect.ImmutableList.of; | |||
import static com.google.common.collect.Sets.newHashSet; | |||
import static java.lang.String.format; | |||
import static java.util.Collections.emptyMap; | |||
import static java.util.Objects.requireNonNull; | |||
import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY; | |||
import static org.sonar.api.server.ws.WebService.Param.FACETS; | |||
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; | |||
@@ -92,9 +96,11 @@ public class SearchProjectsAction implements ComponentsWsAction { | |||
public static final int MAX_PAGE_SIZE = 500; | |||
public static final int DEFAULT_PAGE_SIZE = 100; | |||
private static final String ALL = "_all"; | |||
private static final String ORGANIZATIONS = "organizations"; | |||
private static final String ANALYSIS_DATE = "analysisDate"; | |||
private static final String LEAK_PERIOD_DATE = "leakPeriodDate"; | |||
private static final Set<String> POSSIBLE_FIELDS = newHashSet(ANALYSIS_DATE, LEAK_PERIOD_DATE); | |||
private static final Set<String> POSSIBLE_FIELDS = newHashSet(ALL, ORGANIZATIONS, ANALYSIS_DATE, LEAK_PERIOD_DATE); | |||
private final DbClient dbClient; | |||
private final ProjectMeasuresIndex index; | |||
@@ -130,7 +136,7 @@ public class SearchProjectsAction implements ComponentsWsAction { | |||
.setRequired(false) | |||
.setInternal(true) | |||
.setSince("6.3"); | |||
action.createParam(Param.FACETS) | |||
action.createParam(FACETS) | |||
.setDescription("Comma-separated list of the facets to be computed. No facet is computed by default.") | |||
.setPossibleValues(SUPPORTED_FACETS.stream().sorted().collect(MoreCollectors.toList(SUPPORTED_FACETS.size()))); | |||
action | |||
@@ -299,11 +305,16 @@ public class SearchProjectsAction implements ComponentsWsAction { | |||
.setAsc(httpRequest.mandatoryParamAsBoolean(Param.ASCENDING)) | |||
.setPage(httpRequest.mandatoryParamAsInt(Param.PAGE)) | |||
.setPageSize(httpRequest.mandatoryParamAsInt(Param.PAGE_SIZE)); | |||
if (httpRequest.hasParam(Param.FACETS)) { | |||
request.setFacets(httpRequest.paramAsStrings(Param.FACETS)); | |||
if (httpRequest.hasParam(FACETS)) { | |||
request.setFacets(httpRequest.mandatoryParamAsStrings(FACETS)); | |||
} | |||
if (httpRequest.hasParam(FIELDS)) { | |||
request.setAdditionalFields(httpRequest.paramAsStrings(FIELDS)); | |||
List<String> paramsAsString = httpRequest.mandatoryParamAsStrings(FIELDS); | |||
if (paramsAsString.contains(ALL)) { | |||
request.setAdditionalFields(of(ORGANIZATIONS, ANALYSIS_DATE, LEAK_PERIOD_DATE)); | |||
} else { | |||
request.setAdditionalFields(paramsAsString); | |||
} | |||
} | |||
return request.build(); | |||
} | |||
@@ -312,6 +323,11 @@ public class SearchProjectsAction implements ComponentsWsAction { | |||
Function<ComponentDto, Component> dbToWsComponent = new DbToWsComponent(request, organizationsByUuid, searchResults.favoriteProjectUuids, searchResults.analysisByProjectUuid, | |||
userSession.isLoggedIn()); | |||
Map<String, OrganizationDto> organizationsByUuidForAdditionalInfo = new HashMap<>(); | |||
if (request.additionalFields.contains(ORGANIZATIONS)) { | |||
organizationsByUuidForAdditionalInfo.putAll(organizationsByUuid); | |||
} | |||
return Stream.of(SearchProjectsWsResponse.newBuilder()) | |||
.map(response -> response.setPaging(Common.Paging.newBuilder() | |||
.setPageIndex(request.getPage()) | |||
@@ -323,6 +339,15 @@ public class SearchProjectsAction implements ComponentsWsAction { | |||
.forEach(response::addComponents); | |||
return response; | |||
}) | |||
.map(response -> { | |||
organizationsByUuidForAdditionalInfo.values().stream().forEach( | |||
dto -> response.addOrganizations( | |||
Components.Organization.newBuilder() | |||
.setKey(dto.getKey()) | |||
.setName(dto.getName()) | |||
.build())); | |||
return response; | |||
}) | |||
.map(response -> addFacets(searchResults, response)) | |||
.map(SearchProjectsWsResponse.Builder::build) | |||
.findFirst() | |||
@@ -532,6 +557,7 @@ public class SearchProjectsAction implements ComponentsWsAction { | |||
public static RequestBuilder builder() { | |||
return new RequestBuilder(); | |||
} | |||
} | |||
static class RequestBuilder { |
@@ -72,7 +72,7 @@ import static org.sonar.core.util.stream.MoreCollectors.toSet; | |||
import static org.sonar.server.component.index.SuggestionQuery.DEFAULT_LIMIT; | |||
import static org.sonar.server.es.DefaultIndexSettings.MINIMUM_NGRAM_LENGTH; | |||
import static org.sonar.server.ws.WsUtils.writeProtobuf; | |||
import static org.sonarqube.ws.Components.SuggestionsWsResponse.Organization; | |||
import static org.sonarqube.ws.Components.Organization; | |||
import static org.sonarqube.ws.Components.SuggestionsWsResponse.newBuilder; | |||
import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_SUGGESTIONS; | |||
@@ -4,6 +4,16 @@ | |||
"pageSize": 100, | |||
"total": 3 | |||
}, | |||
"organizations": [ | |||
{ | |||
"key": "my-org-key-1", | |||
"name": "Foo" | |||
}, | |||
{ | |||
"key": "my-org-key-2", | |||
"name": "Bar" | |||
} | |||
], | |||
"components": [ | |||
{ | |||
"organization": "my-org-key-1", |
@@ -54,7 +54,6 @@ import org.sonar.server.measure.index.ProjectMeasuresIndexer; | |||
import org.sonar.server.permission.index.AuthorizationTypeSupport; | |||
import org.sonar.server.permission.index.PermissionIndexerTester; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.ws.KeyExamples; | |||
import org.sonar.server.ws.TestRequest; | |||
import org.sonar.server.ws.WsActionTester; | |||
import org.sonarqube.ws.Common; | |||
@@ -89,6 +88,9 @@ import static org.sonar.core.util.stream.MoreCollectors.toList; | |||
import static org.sonar.server.computation.task.projectanalysis.metric.Metric.MetricType.DATA; | |||
import static org.sonar.server.computation.task.projectanalysis.metric.Metric.MetricType.PERCENT; | |||
import static org.sonar.server.computation.task.projectanalysis.metric.Metric.MetricType.RATING; | |||
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; | |||
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_002; | |||
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_003; | |||
import static org.sonar.test.JsonAssert.assertJson; | |||
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_FILTER; | |||
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_ORGANIZATION; | |||
@@ -183,9 +185,9 @@ public class SearchProjectsActionTest { | |||
assertThat(asc.defaultValue()).isEqualTo("true"); | |||
assertThat(asc.possibleValues()).containsOnly("true", "false", "yes", "no"); | |||
Param additionalFields = def.param("f"); | |||
assertThat(additionalFields.defaultValue()).isNull(); | |||
assertThat(additionalFields.possibleValues()).containsOnly("analysisDate", "leakPeriodDate"); | |||
Param f = def.param("f"); | |||
assertThat(f.defaultValue()).isNull(); | |||
assertThat(f.possibleValues()).containsOnly("_all", "organizations", "analysisDate", "leakPeriodDate"); | |||
Param facets = def.param("facets"); | |||
assertThat(facets.defaultValue()).isNull(); | |||
@@ -196,31 +198,40 @@ public class SearchProjectsActionTest { | |||
@Test | |||
public void json_example() { | |||
userSession.logIn(); | |||
OrganizationDto organization1Dto = db.organizations().insertForKey("my-org-key-1"); | |||
OrganizationDto organization2Dto = db.organizations().insertForKey("my-org-key-2"); | |||
OrganizationDto organization2Dto = db.organizations().insert(dto-> dto.setKey("my-org-key-2").setName("Bar")); | |||
OrganizationDto organization1Dto = db.organizations().insert(dto-> dto.setKey("my-org-key-1").setName("Foo")); | |||
MetricDto coverage = db.measures().insertMetric(c -> c.setKey(COVERAGE).setValueType(PERCENT.name())); | |||
ComponentDto project1 = insertProject(organization1Dto, c -> c | |||
.setDbKey(KeyExamples.KEY_PROJECT_EXAMPLE_001) | |||
.setDbKey(KEY_PROJECT_EXAMPLE_001) | |||
.setName("My Project 1") | |||
.setTagsString("finance, java"), | |||
new Measure(coverage, c -> c.setValue(80d))); | |||
ComponentDto project2 = insertProject(organization1Dto, c -> c | |||
.setDbKey(KeyExamples.KEY_PROJECT_EXAMPLE_002) | |||
.setDbKey(KEY_PROJECT_EXAMPLE_002) | |||
.setName("My Project 2"), | |||
new Measure(coverage, c -> c.setValue(90d))); | |||
ComponentDto project3 = insertProject(organization2Dto, c -> c | |||
.setDbKey(KeyExamples.KEY_PROJECT_EXAMPLE_003) | |||
.setDbKey(KEY_PROJECT_EXAMPLE_003) | |||
.setName("My Project 3") | |||
.setTagsString("sales, offshore, java"), | |||
new Measure(coverage, c -> c.setValue(20d))); | |||
addFavourite(project1); | |||
String jsonResult = ws.newRequest().setParam(Param.FACETS, COVERAGE).execute().getInput(); | |||
SearchProjectsWsResponse protobufResult = ws.newRequest().setParam(Param.FACETS, COVERAGE).executeProtobuf(SearchProjectsWsResponse.class); | |||
String jsonResult = ws.newRequest() | |||
.setParam(FACETS, COVERAGE) | |||
.setParam(FIELDS, "_all") | |||
.execute().getInput(); | |||
assertJson(jsonResult).withStrictArrayOrder().ignoreFields("id").isSimilarTo(ws.getDef().responseExampleAsString()); | |||
assertJson(ws.getDef().responseExampleAsString()).ignoreFields("id").withStrictArrayOrder().isSimilarTo(jsonResult); | |||
assertThat(protobufResult.getComponentsList()).extracting(Component::getId).containsExactly(project1.uuid(), project2.uuid(), project3.uuid()); | |||
SearchProjectsWsResponse protobufResult = ws.newRequest() | |||
.setParam(FACETS, COVERAGE) | |||
.executeProtobuf(SearchProjectsWsResponse.class); | |||
assertThat(protobufResult.getComponentsList()).extracting(Component::getId) | |||
.containsExactly(project1.uuid(), project2.uuid(), project3.uuid()); | |||
} | |||
@Test |
@@ -49,12 +49,12 @@ import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.ws.TestRequest; | |||
import org.sonar.server.ws.TestResponse; | |||
import org.sonar.server.ws.WsActionTester; | |||
import org.sonarqube.ws.MediaTypes; | |||
import org.sonarqube.ws.Components.Organization; | |||
import org.sonarqube.ws.Components.SuggestionsWsResponse; | |||
import org.sonarqube.ws.Components.SuggestionsWsResponse.Category; | |||
import org.sonarqube.ws.Components.SuggestionsWsResponse.Organization; | |||
import org.sonarqube.ws.Components.SuggestionsWsResponse.Project; | |||
import org.sonarqube.ws.Components.SuggestionsWsResponse.Suggestion; | |||
import org.sonarqube.ws.MediaTypes; | |||
import static java.util.Arrays.asList; | |||
import static java.util.Collections.singletonList; |
@@ -69,11 +69,6 @@ message SuggestionsWsResponse { | |||
optional bool isFavorite = 7; | |||
} | |||
message Organization { | |||
optional string key = 1; | |||
optional string name = 2; | |||
} | |||
message Project { | |||
optional string key = 1; | |||
optional string name = 2; | |||
@@ -83,8 +78,9 @@ message SuggestionsWsResponse { | |||
// WS api/components/search_projects | |||
message SearchProjectsWsResponse { | |||
optional sonarqube.ws.commons.Paging paging = 1; | |||
repeated Component components = 2; | |||
optional sonarqube.ws.commons.Facets facets = 3; | |||
repeated Organization organizations = 2; | |||
repeated Component components = 3; | |||
optional sonarqube.ws.commons.Facets facets = 4; | |||
} | |||
// WS api/components/provisioned | |||
@@ -129,3 +125,10 @@ message Component { | |||
} | |||
} | |||
message Organization { | |||
optional string key = 1; | |||
optional string name = 2; | |||
} | |||