diff options
author | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2016-04-06 18:36:39 +0200 |
---|---|---|
committer | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2016-04-07 13:54:46 +0200 |
commit | 616389be7c3eecd9a979aafec6d42c4445a1558a (patch) | |
tree | 837260d69fa50bda415c5f06bee1c46095747ab6 /server | |
parent | 3049d9176376e1907980a692c318c9d0299b8bd8 (diff) | |
download | sonarqube-616389be7c3eecd9a979aafec6d42c4445a1558a.tar.gz sonarqube-616389be7c3eecd9a979aafec6d42c4445a1558a.zip |
SONAR-7528 WS api/measures/component_tree handles developer measures
Diffstat (limited to 'server')
6 files changed, 158 insertions, 11 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentFinder.java b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentFinder.java index d2b84d00be2..41554dd51ce 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentFinder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentFinder.java @@ -67,9 +67,9 @@ public class ComponentFinder { /** * A project can be: * <ul> - * <li>a project – ex: SonarQube</li> - * <li>a view – ex: Language Team</li> - * <li>a developer – ex: Simon Brandhof</li> + * <li>a project – ex: SonarQube</li> + * <li>a view – ex: Language Team</li> + * <li>a developer – ex: Simon Brandhof</li> * </ul> */ public ComponentDto getRootComponentOrModuleByUuidOrKey(DbSession dbSession, @Nullable String projectUuid, @Nullable String projectKey, ResourceTypes resourceTypes) { @@ -106,7 +106,8 @@ public class ComponentFinder { UUID_AND_KEY("uuid", "key"), ID_AND_KEY("id", "key"), COMPONENT_ID_AND_KEY("componentId", "componentKey"), - BASE_COMPONENT_ID_AND_KEY("baseComponentId", "baseComponentKey"); + BASE_COMPONENT_ID_AND_KEY("baseComponentId", "baseComponentKey"), + DEVELOPER_ID_AND_KEY("developerId", "developerKey"); private final String uuidParamName; private final String keyParamName; diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java index c85ee281759..8af4ffe482b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java @@ -39,6 +39,7 @@ import static java.lang.String.format; import static org.sonar.core.util.Uuids.UUID_EXAMPLE_02; import static org.sonar.server.measure.ws.ComponentDtoToWsComponent.componentDtoToWsComponent; import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createAdditionalFieldsParameter; +import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createDeveloperParameters; import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createMetricKeysParameter; import static org.sonar.server.measure.ws.MetricDtoToWsMetric.metricDtoToWsMetric; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; @@ -52,6 +53,8 @@ import static org.sonarqube.ws.client.measure.MeasuresWsParameters.ADDITIONAL_PE import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_ADDITIONAL_FIELDS; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_BASE_COMPONENT_ID; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_BASE_COMPONENT_KEY; +import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_DEVELOPER_ID; +import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_DEVELOPER_KEY; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_KEYS; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_PERIOD_SORT; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_SORT; @@ -151,6 +154,7 @@ public class ComponentTreeAction implements MeasuresWsAction { createMetricKeysParameter(action); createAdditionalFieldsParameter(action); + createDeveloperParameters(action); createQualifiersParameter(action, newQualifierParameterContext(userSession, i18n, resourceTypes)); action.createParam(PARAM_STRATEGY) @@ -250,6 +254,8 @@ public class ComponentTreeAction implements MeasuresWsAction { .setAsc(request.paramAsBoolean(Param.ASCENDING)) .setMetricSort(request.param(PARAM_METRIC_SORT)) .setMetricPeriodSort(request.paramAsInt(PARAM_METRIC_PERIOD_SORT)) + .setDeveloperId(request.param(PARAM_DEVELOPER_ID)) + .setDeveloperKey(request.param(PARAM_DEVELOPER_KEY)) .setPage(request.mandatoryParamAsInt(Param.PAGE)) .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE)) .setQuery(request.param(Param.TEXT_QUERY)); diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeDataLoader.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeDataLoader.java index fd26d5ba2b6..789a2171d00 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeDataLoader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeDataLoader.java @@ -30,6 +30,7 @@ import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.collect.Table; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; @@ -38,6 +39,7 @@ import java.util.Map; import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.ResourceTypes; import org.sonar.api.utils.Paging; @@ -65,7 +67,9 @@ import static com.google.common.collect.Sets.newHashSet; import static java.lang.String.format; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; import static org.sonar.server.component.ComponentFinder.ParamNames.BASE_COMPONENT_ID_AND_KEY; +import static org.sonar.server.component.ComponentFinder.ParamNames.DEVELOPER_ID_AND_KEY; import static org.sonar.server.measure.ws.ComponentTreeAction.ALL_STRATEGY; import static org.sonar.server.measure.ws.ComponentTreeAction.CHILDREN_STRATEGY; import static org.sonar.server.measure.ws.ComponentTreeAction.LEAVES_STRATEGY; @@ -101,6 +105,7 @@ public class ComponentTreeDataLoader { .setBaseComponent(baseComponent) .build(); } + Long developerId = searchDeveloperId(dbSession, wsRequest); ComponentTreeQuery dbQuery = toComponentTreeQuery(wsRequest, baseSnapshot); ComponentDtosAndTotal componentDtosAndTotal = searchComponents(dbSession, dbQuery, wsRequest); @@ -109,7 +114,7 @@ public class ComponentTreeDataLoader { List<MetricDto> metrics = searchMetrics(dbSession, wsRequest); List<WsMeasures.Period> periods = snapshotToWsPeriods(baseSnapshot); Table<String, MetricDto, MeasureDto> measuresByComponentUuidAndMetric = searchMeasuresByComponentUuidAndMetric(dbSession, baseComponent, baseSnapshot, components, metrics, - periods); + periods, developerId); components = sortComponents(components, wsRequest, metrics, measuresByComponentUuidAndMetric); components = paginateComponents(components, componentCount, wsRequest); @@ -129,6 +134,15 @@ public class ComponentTreeDataLoader { } } + @CheckForNull + private Long searchDeveloperId(DbSession dbSession, ComponentTreeWsRequest wsRequest) { + if (wsRequest.getDeveloperId() == null && wsRequest.getDeveloperKey() == null) { + return null; + } + + return componentFinder.getByUuidOrKey(dbSession, wsRequest.getDeveloperId(), wsRequest.getDeveloperKey(), DEVELOPER_ID_AND_KEY).getId(); + } + private Map<Long, ComponentDto> searchReferenceComponentsById(DbSession dbSession, List<ComponentDtoWithSnapshotId> components) { List<Long> referenceComponentIds = from(components) .transform(ComponentDtoWithSnapshotIdToCopyResourceIdFunction.INSTANCE) @@ -148,7 +162,11 @@ public class ComponentTreeDataLoader { } private ComponentDtosAndTotal searchComponents(DbSession dbSession, ComponentTreeQuery dbQuery, ComponentTreeWsRequest wsRequest) { - switch (wsRequest.getStrategy()) { + if (dbQuery.getQualifiers() != null && dbQuery.getQualifiers().isEmpty()) { + return new ComponentDtosAndTotal(Collections.<ComponentDtoWithSnapshotId>emptyList(), 0); + } + String strategy = requireNonNull(wsRequest.getStrategy()); + switch (strategy) { case CHILDREN_STRATEGY: return new ComponentDtosAndTotal( dbClient.componentDao().selectDirectChildren(dbSession, dbQuery), @@ -164,11 +182,12 @@ public class ComponentTreeDataLoader { } private List<MetricDto> searchMetrics(DbSession dbSession, ComponentTreeWsRequest request) { - List<MetricDto> metrics = dbClient.metricDao().selectByKeys(dbSession, request.getMetricKeys()); - if (metrics.size() < request.getMetricKeys().size()) { + List<String> metricKeys = requireNonNull(request.getMetricKeys()); + List<MetricDto> metrics = dbClient.metricDao().selectByKeys(dbSession, metricKeys); + if (metrics.size() < metricKeys.size()) { List<String> foundMetricKeys = Lists.transform(metrics, MetricDtoFunctions.toKey()); Set<String> missingMetricKeys = Sets.difference( - new LinkedHashSet<>(request.getMetricKeys()), + new LinkedHashSet<>(metricKeys), new LinkedHashSet<>(foundMetricKeys)); throw new NotFoundException(format("The following metric keys are not found: %s", Joiner.on(", ").join(missingMetricKeys))); @@ -179,7 +198,7 @@ public class ComponentTreeDataLoader { private Table<String, MetricDto, MeasureDto> searchMeasuresByComponentUuidAndMetric(DbSession dbSession, ComponentDto baseComponent, SnapshotDto baseSnapshot, List<ComponentDtoWithSnapshotId> components, List<MetricDto> metrics, - List<WsMeasures.Period> periods) { + List<WsMeasures.Period> periods, @Nullable Long developerId) { Map<Long, ComponentDto> componentsBySnapshotId = new HashMap<>(); componentsBySnapshotId.put(baseSnapshot.getId(), baseComponent); for (ComponentDtoWithSnapshotId component : components) { @@ -187,7 +206,8 @@ public class ComponentTreeDataLoader { } Map<Integer, MetricDto> metricsById = Maps.uniqueIndex(metrics, MetricDtoFunctions.toId()); - List<MeasureDto> measureDtos = dbClient.measureDao().selectBySnapshotIdsAndMetricIds(dbSession, + List<MeasureDto> measureDtos = dbClient.measureDao().selectByDeveloperAndSnapshotIdsAndMetricIds(dbSession, + developerId, new ArrayList<>(componentsBySnapshotId.keySet()), new ArrayList<>(metricsById.keySet())); diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/MeasuresWsParametersBuilder.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/MeasuresWsParametersBuilder.java index 4107953931a..2f24e466991 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/MeasuresWsParametersBuilder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/MeasuresWsParametersBuilder.java @@ -21,9 +21,13 @@ package org.sonar.server.measure.ws; import org.sonar.api.server.ws.WebService.NewAction; import org.sonar.api.server.ws.WebService.NewParam; +import org.sonar.core.util.Uuids; +import org.sonar.server.ws.KeyExamples; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.ADDITIONAL_FIELDS; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_ADDITIONAL_FIELDS; +import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_DEVELOPER_ID; +import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_DEVELOPER_KEY; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_KEYS; class MeasuresWsParametersBuilder { @@ -46,4 +50,13 @@ class MeasuresWsParametersBuilder { .setExampleValue("ncloc,complexity,violations"); } + static void createDeveloperParameters(NewAction action) { + action.createParam(PARAM_DEVELOPER_ID) + .setDescription("Developer id. If set, developer's measures are returned.") + .setExampleValue(Uuids.UUID_EXAMPLE_01); + action.createParam(PARAM_DEVELOPER_KEY) + .setDescription("Developer key. If set, developer's measures are returned.") + .setExampleValue(KeyExamples.KEY_DEVELOPER_EXAMPLE_001); + } + } diff --git a/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java b/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java index 4f57cc635b9..72ecdebd799 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java @@ -22,6 +22,7 @@ package org.sonar.server.ws; public class KeyExamples { public static final String KEY_FILE_EXAMPLE_001 = "my_project:/src/foo/Bar.php"; public static final String KEY_PROJECT_EXAMPLE_001 = "my_project"; + public static final String KEY_DEVELOPER_EXAMPLE_001 = "DEV:ada@lovelace.com"; private KeyExamples() { // prevent instantiation diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java index c087d4c949c..cc932d405b9 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java @@ -65,6 +65,8 @@ import static org.sonar.db.component.ComponentTesting.newProjectDto; import static org.sonar.db.component.SnapshotTesting.newSnapshotForProject; import static org.sonar.db.measure.MeasureTesting.newMeasureDto; import static org.sonar.db.metric.MetricTesting.newMetricDto; +import static org.sonar.server.measure.ws.ComponentTreeAction.CHILDREN_STRATEGY; +import static org.sonar.server.measure.ws.ComponentTreeAction.LEAVES_STRATEGY; import static org.sonar.server.measure.ws.ComponentTreeAction.METRIC_PERIOD_SORT; import static org.sonar.server.measure.ws.ComponentTreeAction.METRIC_SORT; import static org.sonar.server.measure.ws.ComponentTreeAction.NAME_SORT; @@ -72,6 +74,8 @@ import static org.sonar.test.JsonAssert.assertJson; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.ADDITIONAL_PERIODS; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_ADDITIONAL_FIELDS; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_BASE_COMPONENT_ID; +import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_DEVELOPER_ID; +import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_DEVELOPER_KEY; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_KEYS; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_PERIOD_SORT; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_SORT; @@ -318,6 +322,108 @@ public class ComponentTreeActionTest { } @Test + public void load_developer_measures_by_developer_uuid() { + ComponentDto developer = newDeveloper("developer").setUuid("developer-uuid"); + ComponentDto project = newProjectDto("project-uuid").setKey("project-key"); + SnapshotDto developerSnapshot = componentDb.insertDeveloperAndSnapshot(developer); + SnapshotDto projectSnapshot = componentDb.insertProjectAndSnapshot(project); + SnapshotDto file1Snapshot = componentDb.insertComponentAndSnapshot(newFileDto(project, "file1-uuid"), projectSnapshot); + SnapshotDto file2Snapshot = componentDb.insertComponentAndSnapshot(newFileDto(project, "file2-uuid"), projectSnapshot); + componentDb.insertComponentAndSnapshot(newDevProjectCopy("project-uuid-copy", project, developer), developerSnapshot); + MetricDto ncloc = insertNclocMetric(); + dbClient.measureDao().insert(dbSession, + newMeasureDto(ncloc, projectSnapshot.getId()).setDeveloperId(developer.getId()), + newMeasureDto(ncloc, file1Snapshot.getId()) + .setValue(3d) + .setDeveloperId(developer.getId()), + // measures are not specific to the developer + newMeasureDto(ncloc, file1Snapshot.getId()).setDeveloperId(null), + newMeasureDto(ncloc, file2Snapshot.getId()).setDeveloperId(null)); + db.commit(); + + ComponentTreeWsResponse response = call(ws.newRequest() + .setParam(PARAM_BASE_COMPONENT_ID, "project-uuid") + .setParam(PARAM_DEVELOPER_ID, "developer-uuid") + .setParam(PARAM_STRATEGY, CHILDREN_STRATEGY) + .setParam(PARAM_METRIC_KEYS, "ncloc")); + + assertThat(response.getComponentsCount()).isEqualTo(2); + WsMeasures.Component file = response.getComponents(0); + assertThat(file.getId()).isEqualTo("file1-uuid"); + assertThat(file.getMeasuresCount()).isEqualTo(1); + assertThat(file.getMeasures(0).getValue()).isEqualTo("3"); + } + + @Test + public void load_developer_measures_by_developer_key() { + ComponentDto developer = newDeveloper("developer").setUuid("developer-uuid"); + ComponentDto project = newProjectDto("project-uuid").setKey("project-key"); + SnapshotDto developerSnapshot = componentDb.insertDeveloperAndSnapshot(developer); + SnapshotDto projectSnapshot = componentDb.insertProjectAndSnapshot(project); + SnapshotDto file1Snapshot = componentDb.insertComponentAndSnapshot(newFileDto(project, "file1-uuid"), projectSnapshot); + componentDb.insertComponentAndSnapshot(newDevProjectCopy("project-uuid-copy", project, developer), developerSnapshot); + MetricDto ncloc = insertNclocMetric(); + dbClient.measureDao().insert(dbSession, + newMeasureDto(ncloc, file1Snapshot.getId()) + .setValue(3d) + .setDeveloperId(developer.getId())); + db.commit(); + + ComponentTreeWsResponse response = call(ws.newRequest() + .setParam(PARAM_BASE_COMPONENT_ID, "project-uuid") + .setParam(PARAM_DEVELOPER_KEY, developer.key()) + .setParam(PARAM_METRIC_KEYS, "ncloc")); + + assertThat(response.getComponentsCount()).isEqualTo(1); + WsMeasures.Component file = response.getComponents(0); + assertThat(file.getId()).isEqualTo("file1-uuid"); + assertThat(file.getMeasuresCount()).isEqualTo(1); + assertThat(file.getMeasures(0).getValue()).isEqualTo("3"); + } + + @Test + public void load_measures_when_no_leave_qualifier() { + resourceTypes.setLeavesQualifiers(); + String projectUuid = "project-uuid"; + ComponentDto project = newProjectDto(projectUuid); + SnapshotDto projectSnapshot = componentDb.insertProjectAndSnapshot(project); + componentDb.insertComponentAndSnapshot(newFileDto(project), projectSnapshot); + insertNclocMetric(); + + ComponentTreeWsResponse result = call(ws.newRequest() + .setParam(PARAM_BASE_COMPONENT_ID, projectUuid) + .setParam(PARAM_STRATEGY, LEAVES_STRATEGY) + .setParam(PARAM_METRIC_KEYS, "ncloc") + ); + + assertThat(result.getBaseComponent().getId()).isEqualTo(projectUuid); + assertThat(result.getComponentsCount()).isEqualTo(0); + } + + @Test + public void fail_when_developer_is_unknown() { + expectedException.expect(NotFoundException.class); + + ComponentDto developer = newDeveloper("developer").setUuid("developer-uuid"); + ComponentDto project = newProjectDto("project-uuid").setKey("project-key"); + SnapshotDto developerSnapshot = componentDb.insertDeveloperAndSnapshot(developer); + SnapshotDto projectSnapshot = componentDb.insertProjectAndSnapshot(project); + SnapshotDto file1Snapshot = componentDb.insertComponentAndSnapshot(newFileDto(project, "file1-uuid"), projectSnapshot); + componentDb.insertComponentAndSnapshot(newDevProjectCopy("project-uuid-copy", project, developer), developerSnapshot); + MetricDto ncloc = insertNclocMetric(); + dbClient.measureDao().insert(dbSession, + newMeasureDto(ncloc, file1Snapshot.getId()) + .setValue(3d) + .setDeveloperId(developer.getId())); + db.commit(); + + call(ws.newRequest() + .setParam(PARAM_BASE_COMPONENT_ID, "project-uuid") + .setParam(PARAM_DEVELOPER_KEY, "unknown-developer-key") + .setParam(PARAM_METRIC_KEYS, "ncloc")); + } + + @Test public void fail_when_metric_keys_parameter_is_empty() { componentDb.insertProjectAndSnapshot(newProjectDto("project-uuid")); |