diff options
14 files changed, 284 insertions, 24 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")); diff --git a/sonar-db/src/main/java/org/sonar/db/measure/MeasureDao.java b/sonar-db/src/main/java/org/sonar/db/measure/MeasureDao.java index a73d92b128b..69724405aab 100644 --- a/sonar-db/src/main/java/org/sonar/db/measure/MeasureDao.java +++ b/sonar-db/src/main/java/org/sonar/db/measure/MeasureDao.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.sonar.db.Dao; import org.sonar.db.DatabaseUtils; import org.sonar.db.DbSession; @@ -69,7 +70,7 @@ public class MeasureDao implements Dao { } public List<PastMeasureDto> selectByComponentUuidAndProjectSnapshotIdAndMetricIds(final DbSession session, final String componentUuid, final long projectSnapshotId, - Set<Integer> metricIds) { + Set<Integer> metricIds) { return DatabaseUtils.executeLargeInputs(metricIds, new Function<List<Integer>, List<PastMeasureDto>>() { @Override public List<PastMeasureDto> apply(List<Integer> ids) { @@ -82,7 +83,8 @@ public class MeasureDao implements Dao { /** * Used by plugin Developer Cockpit */ - public List<MeasureDto> selectByDeveloperForSnapshotAndMetrics(final DbSession dbSession, final long developerId, final long snapshotId, Collection<Integer> metricIds) { + public List<MeasureDto> selectByDeveloperForSnapshotAndMetrics(final DbSession dbSession, @Nullable final Long developerId, final long snapshotId, + Collection<Integer> metricIds) { return DatabaseUtils.executeLargeInputs(metricIds, new Function<List<Integer>, List<MeasureDto>>() { @Override @Nonnull @@ -109,11 +111,16 @@ public class MeasureDao implements Dao { * Used by plugin Developer Cockpit */ public List<MeasureDto> selectBySnapshotIdsAndMetricIds(final DbSession dbSession, List<Long> snapshotIds, final List<Integer> metricIds) { + return selectByDeveloperAndSnapshotIdsAndMetricIds(dbSession, null, snapshotIds, metricIds); + } + + public List<MeasureDto> selectByDeveloperAndSnapshotIdsAndMetricIds(final DbSession dbSession, @Nullable final Long developerId, List<Long> snapshotIds, + final List<Integer> metricIds) { return DatabaseUtils.executeLargeInputs(snapshotIds, new Function<List<Long>, List<MeasureDto>>() { @Override @Nonnull public List<MeasureDto> apply(@Nonnull List<Long> input) { - return mapper(dbSession).selectBySnapshotIdsAndMetricIds(input, metricIds); + return mapper(dbSession).selectByDeveloperAndSnapshotIdsAndMetricIds(developerId, input, metricIds); } }); } diff --git a/sonar-db/src/main/java/org/sonar/db/measure/MeasureMapper.java b/sonar-db/src/main/java/org/sonar/db/measure/MeasureMapper.java index 82bf90bd0f1..fb9d7254bcc 100644 --- a/sonar-db/src/main/java/org/sonar/db/measure/MeasureMapper.java +++ b/sonar-db/src/main/java/org/sonar/db/measure/MeasureMapper.java @@ -22,6 +22,7 @@ package org.sonar.db.measure; import java.util.Collection; import java.util.List; import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import org.apache.ibatis.annotations.Param; public interface MeasureMapper { @@ -37,7 +38,8 @@ public interface MeasureMapper { List<MeasureDto> selectBySnapshotAndMetrics(@Param("snapshotId") long snapshotId, @Param("metricIds") List<Integer> input); - List<MeasureDto> selectBySnapshotIdsAndMetricIds(@Param("snapshotIds") List<Long> snapshotIds, @Param("metricIds") List<Integer> metricIds); + List<MeasureDto> selectByDeveloperAndSnapshotIdsAndMetricIds(@Nullable @Param("developerId") Long developerId, @Param("snapshotIds") List<Long> snapshotIds, + @Param("metricIds") List<Integer> metricIds); List<MeasureDto> selectProjectMeasuresByDeveloperForMetrics(@Param("developerId") long developerId, @Param("metricIds") Collection<Integer> metricIds); diff --git a/sonar-db/src/main/resources/org/sonar/db/measure/MeasureMapper.xml b/sonar-db/src/main/resources/org/sonar/db/measure/MeasureMapper.xml index 4f9f8211536..7c97ed532e9 100644 --- a/sonar-db/src/main/resources/org/sonar/db/measure/MeasureMapper.xml +++ b/sonar-db/src/main/resources/org/sonar/db/measure/MeasureMapper.xml @@ -84,7 +84,7 @@ </where> </select> - <select id="selectBySnapshotIdsAndMetricIds" parameterType="map" resultType="Measure"> + <select id="selectByDeveloperAndSnapshotIdsAndMetricIds" parameterType="map" resultType="Measure"> SELECT <include refid="measureColumns"/> FROM project_measures pm @@ -98,7 +98,12 @@ <foreach item="metricId" collection="metricIds" open="(" separator="," close=")"> #{metricId} </foreach> - AND pm.person_id is NULL + <if test="developerId==null"> + AND pm.person_id is NULL + </if> + <if test="developerId!=null"> + AND pm.person_id=#{developerId} + </if> </where> </select> diff --git a/sonar-db/src/test/java/org/sonar/db/measure/MeasureDaoTest.java b/sonar-db/src/test/java/org/sonar/db/measure/MeasureDaoTest.java index d85f6cbbfce..2e7c592e568 100644 --- a/sonar-db/src/test/java/org/sonar/db/measure/MeasureDaoTest.java +++ b/sonar-db/src/test/java/org/sonar/db/measure/MeasureDaoTest.java @@ -35,14 +35,23 @@ import org.sonar.api.utils.System2; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDbTester; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentTesting; import org.sonar.db.component.SnapshotDto; +import org.sonar.db.metric.MetricDto; +import org.sonar.db.metric.MetricTesting; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Sets.newHashSet; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.guava.api.Assertions.assertThat; +import static org.sonar.db.component.ComponentTesting.newDevProjectCopy; +import static org.sonar.db.component.ComponentTesting.newDeveloper; +import static org.sonar.db.component.ComponentTesting.newFileDto; +import static org.sonar.db.component.ComponentTesting.newProjectDto; +import static org.sonar.db.measure.MeasureTesting.newMeasureDto; public class MeasureDaoTest { @@ -59,6 +68,7 @@ public class MeasureDaoTest { final DbSession dbSession = db.getSession(); MeasureDao underTest = dbClient.measureDao(); + ComponentDbTester componentDb = new ComponentDbTester(db); @Test public void get_value_by_key() { @@ -326,7 +336,62 @@ public class MeasureDaoTest { assertThat(measureDtos).isEmpty(); } - // TODO add test for selectBySnapshotIdsAndMetricIds + @Test + public void selectSnapshotIdsAndMetricIds() { + MetricDto metric = dbClient.metricDao().insert(dbSession, MetricTesting.newMetricDto()); + ComponentDto project = newProjectDto(); + SnapshotDto projectSnapshot = componentDb.insertProjectAndSnapshot(project); + SnapshotDto fileSnapshot = componentDb.insertComponentAndSnapshot(newFileDto(project, "file-uuid"), projectSnapshot); + ComponentDto developer = newDeveloper("Ray Bradbury"); + SnapshotDto developerSnapshot = componentDb.insertDeveloperAndSnapshot(developer); + componentDb.insertComponentAndSnapshot(newDevProjectCopy("project-copy-uuid", project, developer), developerSnapshot); + underTest.insert(dbSession, + newMeasureDto(metric, developerSnapshot.getId()).setDeveloperId(developer.getId()), + newMeasureDto(metric, projectSnapshot.getId()), + newMeasureDto(metric, fileSnapshot.getId())); + dbSession.commit(); + + List<MeasureDto> result = underTest.selectBySnapshotIdsAndMetricIds(dbSession, + newArrayList(developerSnapshot.getId(), projectSnapshot.getId(), fileSnapshot.getId()), + singletonList(metric.getId())); + + assertThat(result) + .hasSize(2) + .extracting("snapshotId") + .containsOnly(projectSnapshot.getId(), fileSnapshot.getId()) + .doesNotContain(developerSnapshot.getId()); + } + + @Test + public void selectDeveloperAndSnapshotIdsAndMetricIds() { + MetricDto metric = dbClient.metricDao().insert(dbSession, MetricTesting.newMetricDto()); + ComponentDto project = newProjectDto(); + SnapshotDto projectSnapshot = componentDb.insertProjectAndSnapshot(project); + SnapshotDto fileSnapshot = componentDb.insertComponentAndSnapshot(newFileDto(project, "file-uuid"), projectSnapshot); + ComponentDto developer = newDeveloper("Ray Bradbury"); + SnapshotDto developerSnapshot = componentDb.insertDeveloperAndSnapshot(developer); + componentDb.insertComponentAndSnapshot(newDevProjectCopy("project-copy-uuid", project, developer), developerSnapshot); + + underTest.insert(dbSession, + newMeasureDto(metric, developerSnapshot.getId()).setDeveloperId(developer.getId()), + newMeasureDto(metric, projectSnapshot.getId()).setDeveloperId(developer.getId()), + newMeasureDto(metric, projectSnapshot.getId()).setDeveloperId(null), + newMeasureDto(metric, fileSnapshot.getId()).setDeveloperId(developer.getId())); + dbSession.commit(); + + List<MeasureDto> result = underTest.selectByDeveloperAndSnapshotIdsAndMetricIds(dbSession, + developer.getId(), + newArrayList(developerSnapshot.getId(), projectSnapshot.getId(), fileSnapshot.getId()), + singletonList(metric.getId())); + + assertThat(result) + .hasSize(3) + .extracting("snapshotId") + .containsOnly(developerSnapshot.getId(), projectSnapshot.getId(), fileSnapshot.getId()); + assertThat(result) + .extracting("developerId") + .containsOnly(developer.getId()); + } @Test public void selectProjectMeasuresByDeveloperForMetrics_returns_empty_on_empty_db() { @@ -397,7 +462,7 @@ public class MeasureDaoTest { } private ComponentDto insertProject(String uuid) { - ComponentDto projectDto = ComponentTesting.newProjectDto(uuid); + ComponentDto projectDto = newProjectDto(uuid); return insertComponent(projectDto); } @@ -434,8 +499,7 @@ public class MeasureDaoTest { .setVariation(5, 5.0d) .setAlertStatus("alert") .setAlertText("alert-text") - .setDescription("measure-description") - ); + .setDescription("measure-description")); dbSession.commit(); db.assertDbUnit(getClass(), "insert-result.xml", new String[] {"id"}, "project_measures"); @@ -454,8 +518,7 @@ public class MeasureDaoTest { .setSnapshotId(3L) .setMetricId(4) .setComponentId(6L) - .setValue(4.0d) - ); + .setValue(4.0d)); dbSession.commit(); assertThat(db.countRowsOfTable("project_measures")).isEqualTo(2); diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/ComponentTreeWsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/ComponentTreeWsRequest.java index 9194be603f6..86727a6c5c3 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/ComponentTreeWsRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/ComponentTreeWsRequest.java @@ -38,6 +38,8 @@ public class ComponentTreeWsRequest { private List<String> metricKeys; private Integer page; private Integer pageSize; + private String developerId; + private String developerKey; @CheckForNull public String getBaseComponentId() { @@ -168,4 +170,24 @@ public class ComponentTreeWsRequest { this.metricPeriodSort = metricPeriodSort; return this; } + + @CheckForNull + public String getDeveloperId() { + return developerId; + } + + public ComponentTreeWsRequest setDeveloperId(@Nullable String developerId) { + this.developerId = developerId; + return this; + } + + @CheckForNull + public String getDeveloperKey() { + return developerKey; + } + + public ComponentTreeWsRequest setDeveloperKey(@Nullable String developerKey) { + this.developerKey = developerKey; + return this; + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/MeasuresService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/MeasuresService.java index 7fb0019e03f..829b170b9a9 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/MeasuresService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/MeasuresService.java @@ -33,6 +33,8 @@ import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_BASE_CO import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_BASE_COMPONENT_KEY; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_COMPONENT_ID; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_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_SORT; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_QUALIFIERS; @@ -51,6 +53,8 @@ public class MeasuresService extends BaseService { .setParam(PARAM_QUALIFIERS, inlineMultipleParamValue(request.getQualifiers())) .setParam(PARAM_METRIC_KEYS, inlineMultipleParamValue(request.getMetricKeys())) .setParam(PARAM_ADDITIONAL_FIELDS, inlineMultipleParamValue(request.getAdditionalFields())) + .setParam(PARAM_DEVELOPER_ID, request.getDeveloperId()) + .setParam(PARAM_DEVELOPER_KEY, request.getDeveloperKey()) .setParam("q", request.getQuery()) .setParam("p", request.getPage()) .setParam("ps", request.getPageSize()) diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/MeasuresWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/MeasuresWsParameters.java index 4aca69b797e..b06b762c8e8 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/MeasuresWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/MeasuresWsParameters.java @@ -42,6 +42,8 @@ public class MeasuresWsParameters { public static final String PARAM_ADDITIONAL_FIELDS = "additionalFields"; public static final String PARAM_COMPONENT_ID = "componentId"; public static final String PARAM_COMPONENT_KEY = "componentKey"; + public static final String PARAM_DEVELOPER_ID = "developerId"; + public static final String PARAM_DEVELOPER_KEY = "developerKey"; public static final String ADDITIONAL_METRICS = "metrics"; public static final String ADDITIONAL_PERIODS = "periods"; diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/measure/MeasuresServiceTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/measure/MeasuresServiceTest.java index 9e3bd05e6b9..869dd6fcd21 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/measure/MeasuresServiceTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/measure/MeasuresServiceTest.java @@ -34,6 +34,8 @@ import static org.mockito.Mockito.mock; 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_SORT; import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_QUALIFIERS; @@ -52,6 +54,8 @@ public class MeasuresServiceTest { private static final int VALUE_PAGE = 42; private static final int VALUE_PAGE_SIZE = 1984; private static final String VALUE_QUERY = "query-sq"; + private static final String VALUE_DEVELOPER_ID = "developer-id"; + private static final String VALUE_DEVELOPER_KEY = "developer-key"; @Rule public ServiceTester<MeasuresService> serviceTester = new ServiceTester<>(new MeasuresService(mock(WsConnector.class))); @@ -72,7 +76,9 @@ public class MeasuresServiceTest { .setMetricSort(VALUE_METRIC_SORT) .setPage(VALUE_PAGE) .setPageSize(VALUE_PAGE_SIZE) - .setQuery(VALUE_QUERY); + .setQuery(VALUE_QUERY) + .setDeveloperId(VALUE_DEVELOPER_ID) + .setDeveloperKey(VALUE_DEVELOPER_KEY); underTest.componentTree(componentTreeRequest); GetRequest getRequest = serviceTester.getGetRequest(); @@ -91,6 +97,8 @@ public class MeasuresServiceTest { .hasParam("p", VALUE_PAGE) .hasParam("ps", VALUE_PAGE_SIZE) .hasParam("q", VALUE_QUERY) + .hasParam(PARAM_DEVELOPER_ID, VALUE_DEVELOPER_ID) + .hasParam(PARAM_DEVELOPER_KEY, VALUE_DEVELOPER_KEY) .andNoOtherParam(); } } |