import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
-import org.sonarqube.ws.client.GetRequest;
-import org.sonarqube.ws.client.WsResponse;
+import org.sonarqube.ws.WsMeasures;
+import org.sonarqube.ws.WsMeasures.SearchHistoryResponse.HistoryValue;
+import org.sonarqube.ws.client.measure.SearchHistoryRequest;
import util.ItUtils;
+import static java.util.Collections.singletonList;
import static org.apache.commons.lang.time.DateUtils.addDays;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.equalTo;
runProjectAnalysis(orchestrator, PROJECT_SAMPLE_PATH);
// Check that only analysis from last thursday is kept (as it's the last one from previous week)
- WsResponse response = newAdminWsClient(orchestrator).wsConnector().call(
- new GetRequest("/api/timemachine/index")
- .setParam("resource", PROJECT_KEY)
- .setParam("metrics", "ncloc"))
- .failIfNotSuccessful();
- String content = response.content();
- assertThat(content).contains(lastThursdayFormatted);
- assertThat(content).doesNotContain(lastWednesdayFormatted);
+ WsMeasures.SearchHistoryResponse response = newAdminWsClient(orchestrator).measures().searchHistory(SearchHistoryRequest.builder()
+ .setComponent(PROJECT_KEY)
+ .setMetrics(singletonList("ncloc"))
+ .build());
+ assertThat(response.getMeasuresCount()).isEqualTo(1);
+ assertThat(response.getMeasuresList().get(0).getHistoryList()).extracting(HistoryValue::getDate).doesNotContain(lastWednesdayFormatted, lastThursdayFormatted);
}
/**
scan(PROJECT_SAMPLE_PATH, ONE_DAY_AGO);
// second analysis as NEW_* metrics
- assertThat(count(COUNT_FILE_MEASURES)).isLessThan( 2 * fileMeasures);
+ assertThat(count(COUNT_FILE_MEASURES)).isLessThan(2 * fileMeasures);
assertThat(count(COUNT_DIR_MEASURES)).isGreaterThan(2 * dirMeasures);
}
import com.sonar.orchestrator.build.SonarScanner;
import com.sonar.orchestrator.locator.FileLocation;
import it.Category1Suite;
+import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
-import org.sonar.wsclient.services.TimeMachine;
-import org.sonar.wsclient.services.TimeMachineCell;
-import org.sonar.wsclient.services.TimeMachineQuery;
import org.sonarqube.ws.WsMeasures.Measure;
+import org.sonarqube.ws.WsMeasures.SearchHistoryResponse;
+import org.sonarqube.ws.WsMeasures.SearchHistoryResponse.HistoryValue;
+import org.sonarqube.ws.client.measure.MeasuresService;
+import org.sonarqube.ws.client.measure.SearchHistoryRequest;
import util.ItUtils;
import util.ItUtils.ComponentNavigation;
-import static java.lang.Double.parseDouble;
+import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
+import static util.ItUtils.formatDate;
import static util.ItUtils.getComponentNavigation;
import static util.ItUtils.getMeasuresByMetricKey;
import static util.ItUtils.getMeasuresWithVariationsByMetricKey;
+import static util.ItUtils.newAdminWsClient;
import static util.ItUtils.projectDir;
import static util.ItUtils.setServerProperty;
public class TimeMachineTest {
private static final String PROJECT = "sample";
+ private static final String FIRST_ANALYSIS_DATE = "2014-10-19";
+ private static final String SECOND_ANALYSIS_DATE = "2014-11-13";
@ClassRule
public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR;
+ private static MeasuresService wsMeasures;
@BeforeClass
public static void initialize() {
orchestrator.getServer().restoreProfile(FileLocation.ofClasspath("/measureHistory/one-issue-per-line-profile.xml"));
orchestrator.getServer().provisionProject("sample", "Sample");
orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line");
- analyzeProject("measure/xoo-history-v1", "2014-10-19");
- analyzeProject("measure/xoo-history-v2", "2014-11-13");
+ analyzeProject("measure/xoo-history-v1", FIRST_ANALYSIS_DATE);
+ analyzeProject("measure/xoo-history-v2", SECOND_ANALYSIS_DATE);
+
+ wsMeasures = newAdminWsClient(orchestrator).measures();
}
public static void initPeriods() {
@Test
public void testHistoryOfIssues() {
- TimeMachineQuery query = TimeMachineQuery.createForMetrics(PROJECT, "blocker_violations", "critical_violations", "major_violations",
- "minor_violations", "info_violations");
- TimeMachine timemachine = orchestrator.getServer().getWsClient().find(query);
- assertThat(timemachine.getCells().length).isEqualTo(2);
-
- TimeMachineCell cell1 = timemachine.getCells()[0];
- TimeMachineCell cell2 = timemachine.getCells()[1];
-
- assertThat(cell1.getDate().getMonth()).isEqualTo(9);
- assertThat(cell1.getValues()).isEqualTo(new Object[] {0L, 0L, 0L, 26L, 0L});
-
- assertThat(cell2.getDate().getMonth()).isEqualTo(10);
- assertThat(cell2.getValues()).isEqualTo(new Object[] {0L, 0L, 0L, 43L, 0L});
+ SearchHistoryResponse response = searchHistory("blocker_violations", "critical_violations", "info_violations", "major_violations", "minor_violations");
+ assertThat(response.getPaging().getTotal()).isEqualTo(2);
+
+ assertHistory(response, "blocker_violations", "0", "0");
+ assertHistory(response, "critical_violations", "0", "0");
+ assertHistory(response, "info_violations", "0", "0");
+ assertHistory(response, "major_violations", "0", "0");
+ assertHistory(response, "minor_violations", "26", "43");
}
@Test
public void testHistoryOfMeasures() {
- TimeMachineQuery query = TimeMachineQuery.createForMetrics(PROJECT, "lines", "ncloc");
- TimeMachine timemachine = orchestrator.getServer().getWsClient().find(query);
- assertThat(timemachine.getCells().length).isEqualTo(2);
-
- TimeMachineCell cell1 = timemachine.getCells()[0];
- TimeMachineCell cell2 = timemachine.getCells()[1];
-
- assertThat(cell1.getDate().getMonth()).isEqualTo(9);
- assertThat(cell1.getValues()).isEqualTo(new Object[] {26L, 24L});
-
- assertThat(cell2.getDate().getMonth()).isEqualTo(10);
- assertThat(cell2.getValues()).isEqualTo(new Object[] {43L, 40L});
- }
-
- @Test
- public void unknownMetrics() {
- TimeMachine timemachine = orchestrator.getServer().getWsClient().find(TimeMachineQuery.createForMetrics(PROJECT, "notfound"));
- assertThat(timemachine.getCells().length).isEqualTo(0);
-
- timemachine = orchestrator.getServer().getWsClient().find(TimeMachineQuery.createForMetrics(PROJECT, "lines", "notfound"));
- assertThat(timemachine.getCells().length).isEqualTo(2);
- for (TimeMachineCell cell : timemachine.getCells()) {
- assertThat(cell.getValues().length).isEqualTo(1);
- assertThat(cell.getValues()[0]).isInstanceOf(Long.class);
- }
+ SearchHistoryResponse response = searchHistory("lines", "ncloc");
- timemachine = orchestrator.getServer().getWsClient().find(TimeMachineQuery.createForMetrics(PROJECT));
- assertThat(timemachine.getCells().length).isEqualTo(0);
+ assertThat(response.getPaging().getTotal()).isEqualTo(2);
+ assertHistory(response, "lines", "26", "43");
+ assertHistory(response, "ncloc", "24", "40");
}
@Test
public void noDataForInterval() {
Date now = new Date();
- TimeMachine timemachine = orchestrator.getServer().getWsClient().find(TimeMachineQuery.createForMetrics(PROJECT, "lines").setFrom(now).setTo(now));
- assertThat(timemachine.getCells().length).isEqualTo(0);
- }
- @Test
- public void unknownResource() {
- TimeMachine timemachine = orchestrator.getServer().getWsClient().find(TimeMachineQuery.createForMetrics("notfound:notfound", "lines"));
- assertThat(timemachine).isNull();
- }
+ SearchHistoryResponse response = wsMeasures.searchHistory(SearchHistoryRequest.builder()
+ .setComponent(PROJECT)
+ .setMetrics(singletonList("lines"))
+ .setFrom(formatDate(now))
+ .setTo(formatDate(now))
+ .build());
- @Test
- public void test_measure_variations() {
- Map<String, Measure> measures = getMeasuresWithVariationsByMetricKey(orchestrator, PROJECT, "files", "ncloc", "violations");
- // variations from previous analysis
- assertThat(parseDouble(measures.get("files").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(1.0);
- assertThat(parseDouble(measures.get("ncloc").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(16.0);
- assertThat(parseDouble(measures.get("violations").getPeriods().getPeriodsValue(0).getValue())).isGreaterThan(0.0);
+ assertThat(response.getPaging().getTotal()).isEqualTo(0);
+ assertThat(response.getMeasures(0).getHistoryList()).isEmpty();
}
/**
Map<String, Measure> measures = getMeasuresWithVariationsByMetricKey(orchestrator, PROJECT, "violations", "new_violations");
assertThat(measures.get("violations")).isNotNull();
assertThat(measures.get("new_violations")).isNotNull();
+ SearchHistoryResponse response = searchHistory("new_violations");
+ assertThat(response.getMeasures(0).getHistoryCount()).isGreaterThan(0);
measures = getMeasuresByMetricKey(orchestrator, PROJECT, "violations", "new_violations");
assertThat(measures.get("violations")).isNotNull();
assertThat(measures.get("new_violations")).isNull();
}
+
+ private static SearchHistoryResponse searchHistory(String... metrics) {
+ return wsMeasures.searchHistory(SearchHistoryRequest.builder()
+ .setComponent(PROJECT)
+ .setMetrics(Arrays.asList(metrics))
+ .build());
+ }
+
+ private static void assertHistory(SearchHistoryResponse response, String metric, String... expectedMeasures) {
+ for (SearchHistoryResponse.HistoryMeasure measures : response.getMeasuresList()) {
+ if (metric.equals(measures.getMetric())) {
+ assertThat(measures.getHistoryList()).extracting(HistoryValue::getValue).containsExactly(expectedMeasures);
+ return;
+ }
+ }
+
+ throw new IllegalArgumentException("Metric not found");
+ }
}
MeasuresWs.class,
ComponentTreeAction.class,
ComponentAction.class,
- SearchAction.class);
+ SearchAction.class,
+ SearchHistoryAction.class);
}
}
*/
package org.sonar.server.measure.ws;
+import com.google.common.collect.ImmutableSortedSet;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.db.component.ComponentDto;
import org.sonar.db.measure.MeasureDto;
import org.sonar.db.metric.MetricDto;
import org.sonarqube.ws.WsMeasures;
class MetricDtoWithBestValue {
private static final String LOWER_CASE_NEW_METRIC_PREFIX = "new_";
+ private static final Set<String> QUALIFIERS_ELIGIBLE_FOR_BEST_VALUE = ImmutableSortedSet.of(Qualifiers.FILE, Qualifiers.UNIT_TEST_FILE);
private final MetricDto metric;
private final MeasureDto bestValue;
return bestValue;
}
+ static Predicate<ComponentDto> isEligibleForBestValue() {
+ return component -> QUALIFIERS_ELIGIBLE_FOR_BEST_VALUE.contains(component.qualifier());
+ }
+
static class MetricDtoToMetricDtoWithBestValueFunction implements Function<MetricDto, MetricDtoWithBestValue> {
private final List<Integer> periodIndexes;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.sonar.server.measure.ws;
+
+import com.google.common.collect.Sets;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Stream;
+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.web.UserRole;
+import org.sonar.core.util.stream.Collectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.component.SnapshotQuery;
+import org.sonar.db.component.SnapshotQuery.SORT_FIELD;
+import org.sonar.db.component.SnapshotQuery.SORT_ORDER;
+import org.sonar.db.measure.MeasureDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.ws.KeyExamples;
+import org.sonarqube.ws.WsMeasures.SearchHistoryResponse;
+import org.sonarqube.ws.client.measure.SearchHistoryRequest;
+
+import static java.lang.String.format;
+import static org.sonar.api.utils.DateUtils.parseEndingDateOrDateTime;
+import static org.sonar.api.utils.DateUtils.parseStartingDateOrDateTime;
+import static org.sonar.core.util.Protobuf.setNullable;
+import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+import static org.sonarqube.ws.client.measure.MeasuresWsParameters.ACTION_SEARCH_HISTORY;
+import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_COMPONENT;
+import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_FROM;
+import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRICS;
+import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_TO;
+import static org.sonarqube.ws.client.measure.SearchHistoryRequest.DEFAULT_PAGE_SIZE;
+import static org.sonarqube.ws.client.measure.SearchHistoryRequest.MAX_PAGE_SIZE;
+
+public class SearchHistoryAction implements MeasuresWsAction {
+ private final DbClient dbClient;
+ private final ComponentFinder componentFinder;
+ private final UserSession userSession;
+
+ public SearchHistoryAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) {
+ this.dbClient = dbClient;
+ this.componentFinder = componentFinder;
+ this.userSession = userSession;
+ }
+
+ @Override
+ public void define(WebService.NewController context) {
+ WebService.NewAction action = context.createAction(ACTION_SEARCH_HISTORY)
+ .setDescription("Search measures history of a component.<br>" +
+ "Measures are ordered chronologically.<br>" +
+ "Pagination applies to the number of measures for each metric.")
+ .setResponseExample(getClass().getResource("search_history-example.json"))
+ .setSince("6.3")
+ .setHandler(this);
+
+ action.createParam(PARAM_COMPONENT)
+ .setDescription("Component key")
+ .setRequired(true)
+ .setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001);
+
+ action.createParam(PARAM_METRICS)
+ .setDescription("Comma-separated list of metric keys")
+ .setRequired(true)
+ .setExampleValue("ncloc,coverage,new_violations");
+
+ action.createParam(PARAM_FROM)
+ .setDescription("Filter measures created after the given date (inclusive). Format: date or datetime ISO formats")
+ .setExampleValue("2013-05-01 (or 2013-05-01T13:00:00+0100)");
+
+ action.createParam(PARAM_TO)
+ .setDescription("Filter issues created before the given date (inclusive). Format: date or datetime ISO formats")
+ .setExampleValue("2013-05-01 (or 2013-05-01T13:00:00+0100)");
+
+ action.addPagingParams(DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ SearchHistoryResponse searchHistoryResponse = Stream.of(request)
+ .map(SearchHistoryAction::toWsRequest)
+ .map(search())
+ .map(result -> new SearchHistoryResponseFactory(result).apply())
+ .collect(Collectors.toOneElement());
+
+ writeProtobuf(searchHistoryResponse, request, response);
+ }
+
+ private Function<SearchHistoryRequest, SearchHistoryResult> search() {
+ return request -> {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ ComponentDto component = searchComponent(request, dbSession);
+
+ SearchHistoryResult result = new SearchHistoryResult(request)
+ .setComponent(component)
+ .setAnalyses(searchAnalyses(dbSession, request, component))
+ .setMetrics(searchMetrics(dbSession, request));
+ return result.setMeasures(searchMeasures(dbSession, component, result.getAnalyses(), result.getMetrics()));
+ }
+ };
+ }
+
+ private ComponentDto searchComponent(SearchHistoryRequest request, DbSession dbSession) {
+ ComponentDto component = componentFinder.getByKey(dbSession, request.getComponent());
+ userSession.checkComponentUuidPermission(UserRole.USER, component.projectUuid());
+ return component;
+ }
+
+ private List<MeasureDto> searchMeasures(DbSession dbSession, ComponentDto component, List<SnapshotDto> analyses, List<MetricDto> metrics) {
+ return dbClient.measureDao().selectPastMeasures(
+ dbSession,
+ component.uuid(),
+ analyses.stream().map(SnapshotDto::getUuid).collect(Collectors.toList()),
+ metrics.stream().map(MetricDto::getId).collect(Collectors.toList()));
+ }
+
+ private List<SnapshotDto> searchAnalyses(DbSession dbSession, SearchHistoryRequest request, ComponentDto component) {
+ SnapshotQuery dbQuery = new SnapshotQuery()
+ .setComponentUuid(component.projectUuid())
+ .setStatus(STATUS_PROCESSED)
+ .setSort(SORT_FIELD.BY_DATE, SORT_ORDER.ASC);
+ setNullable(request.getFrom(), from -> dbQuery.setCreatedAfter(parseStartingDateOrDateTime(from).getTime()));
+ setNullable(request.getTo(), to -> dbQuery.setCreatedBefore(parseEndingDateOrDateTime(to).getTime() + 1_000L));
+
+ return dbClient.snapshotDao().selectAnalysesByQuery(dbSession, dbQuery);
+ }
+
+ private List<MetricDto> searchMetrics(DbSession dbSession, SearchHistoryRequest request) {
+ List<MetricDto> metrics = dbClient.metricDao().selectByKeys(dbSession, request.getMetrics());
+ if (request.getMetrics().size() > metrics.size()) {
+ Set<String> requestedMetrics = request.getMetrics().stream().collect(Collectors.toSet());
+ Set<String> foundMetrics = metrics.stream().map(MetricDto::getKey).collect(Collectors.toSet());
+
+ Set<String> unfoundMetrics = Sets.difference(requestedMetrics, foundMetrics).immutableCopy();
+ throw new IllegalArgumentException(format("Metrics %s are not found", String.join(", ", unfoundMetrics)));
+ }
+
+ return metrics;
+ }
+
+ private static SearchHistoryRequest toWsRequest(Request request) {
+ return SearchHistoryRequest.builder()
+ .setComponent(request.mandatoryParam(PARAM_COMPONENT))
+ .setMetrics(request.mandatoryParamAsStrings(PARAM_METRICS))
+ .setFrom(request.param(PARAM_FROM))
+ .setTo(request.param(PARAM_TO))
+ .setPage(request.mandatoryParamAsInt(Param.PAGE))
+ .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE))
+ .build();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.sonar.server.measure.ws;
+
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+import java.util.stream.Stream;
+import org.sonar.core.util.stream.Collectors;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.measure.MeasureDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonarqube.ws.WsMeasures.SearchHistoryResponse;
+import org.sonarqube.ws.WsMeasures.SearchHistoryResponse.HistoryMeasure;
+import org.sonarqube.ws.WsMeasures.SearchHistoryResponse.HistoryValue;
+
+import static org.sonar.api.utils.DateUtils.formatDateTime;
+import static org.sonar.server.measure.ws.MeasureValueFormatter.formatMeasureValue;
+import static org.sonar.server.measure.ws.MeasureValueFormatter.formatNumericalValue;
+
+class SearchHistoryResponseFactory {
+ private final SearchHistoryResult result;
+ private final HistoryMeasure.Builder measure;
+ private final HistoryValue.Builder value;
+
+ SearchHistoryResponseFactory(SearchHistoryResult result) {
+ this.result = result;
+ this.measure = HistoryMeasure.newBuilder();
+ this.value = HistoryValue.newBuilder();
+ }
+
+ public SearchHistoryResponse apply() {
+ return Stream.of(SearchHistoryResponse.newBuilder())
+ .map(addPaging())
+ .map(addMeasures())
+ .map(SearchHistoryResponse.Builder::build)
+ .collect(Collectors.toOneElement());
+ }
+
+ private UnaryOperator<SearchHistoryResponse.Builder> addPaging() {
+ return response -> response.setPaging(result.getPaging());
+ }
+
+ private UnaryOperator<SearchHistoryResponse.Builder> addMeasures() {
+ Map<Integer, MetricDto> metricsById = result.getMetrics().stream().collect(Collectors.uniqueIndex(MetricDto::getId));
+ Map<String, SnapshotDto> analysesByUuid = result.getAnalyses().stream().collect(Collectors.uniqueIndex(SnapshotDto::getUuid));
+ Table<MetricDto, SnapshotDto, MeasureDto> measuresByMetricByAnalysis = HashBasedTable.create(result.getMetrics().size(), result.getAnalyses().size());
+ result.getMeasures().forEach(m -> measuresByMetricByAnalysis.put(metricsById.get(m.getMetricId()), analysesByUuid.get(m.getAnalysisUuid()), m));
+
+ return response -> {
+ result.getMetrics().stream()
+ .peek(metric -> measure.clear())
+ .map(addMetric())
+ .map(metric -> addValues(measuresByMetricByAnalysis.row(metric)).apply(metric))
+ .forEach(metric -> response.addMeasures(measure));
+
+ return response;
+ };
+ }
+
+ private UnaryOperator<MetricDto> addMetric() {
+ return metric -> {
+ measure.setMetric(metric.getKey());
+ return metric;
+ };
+ }
+
+ private UnaryOperator<MetricDto> addValues(Map<SnapshotDto, MeasureDto> measuresByAnalysis) {
+ Predicate<SnapshotDto> hasMeasure = analysis -> measuresByAnalysis.get(analysis) != null;
+ return metric -> {
+ result.getAnalyses().stream()
+ .filter(hasMeasure)
+ .peek(analysis -> value.clear())
+ .map(addDate())
+ .map(analysis -> addValue(metric, measuresByAnalysis.get(analysis)).apply(analysis))
+ .forEach(analysis -> measure.addHistory(value));
+
+ return metric;
+ };
+ }
+
+ private UnaryOperator<SnapshotDto> addDate() {
+ return analysis -> {
+ value.setDate(formatDateTime(analysis.getCreatedAt()));
+ return analysis;
+ };
+ }
+
+ private UnaryOperator<SnapshotDto> addValue(MetricDto dbMetric, MeasureDto dbMeasure) {
+ return analysis -> {
+ String measureValue = dbMetric.getKey().startsWith("new_")
+ ? formatNumericalValue(dbMeasure.getVariation(1), dbMetric)
+ : formatMeasureValue(dbMeasure, dbMetric);
+ value.setValue(measureValue);
+ return analysis;
+ };
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.sonar.server.measure.ws;
+
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Table;
+import java.util.ArrayList;
+import java.util.List;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.measure.MeasureDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonarqube.ws.Common;
+import org.sonarqube.ws.client.measure.SearchHistoryRequest;
+
+import static java.util.Collections.emptyList;
+import static java.util.Objects.requireNonNull;
+import static org.sonar.api.utils.Paging.offset;
+import static org.sonar.core.util.stream.Collectors.toList;
+import static org.sonar.db.metric.MetricDtoFunctions.isOptimizedForBestValue;
+import static org.sonar.server.measure.ws.MetricDtoWithBestValue.isEligibleForBestValue;
+
+class SearchHistoryResult {
+ private final SearchHistoryRequest request;
+ private List<SnapshotDto> analyses;
+ private List<MetricDto> metrics;
+ private List<MeasureDto> measures;
+ private Common.Paging paging;
+ private ComponentDto component;
+
+ SearchHistoryResult(SearchHistoryRequest request) {
+ this.request = request;
+ }
+
+ boolean hasResults() {
+ return !analyses.isEmpty();
+ }
+
+ SearchHistoryResult setComponent(ComponentDto component) {
+ this.component = component;
+
+ return this;
+ }
+
+ List<SnapshotDto> getAnalyses() {
+ return requireNonNull(analyses);
+ }
+
+ SearchHistoryResult setAnalyses(List<SnapshotDto> analyses) {
+ this.paging = Common.Paging.newBuilder().setPageIndex(request.getPage()).setPageSize(request.getPageSize()).setTotal(analyses.size()).build();
+ this.analyses = analyses.stream().skip(offset(request.getPage(), request.getPageSize())).limit(request.getPageSize()).collect(toList());
+ return this;
+ }
+
+ List<MetricDto> getMetrics() {
+ return requireNonNull(metrics);
+ }
+
+ SearchHistoryResult setMetrics(List<MetricDto> metrics) {
+ this.metrics = metrics;
+ return this;
+ }
+
+ List<MeasureDto> getMeasures() {
+ return requireNonNull(measures);
+ }
+
+ SearchHistoryResult setMeasures(List<MeasureDto> measures) {
+ this.measures = ImmutableList.<MeasureDto>builder()
+ .addAll(measures)
+ .addAll(addBestValuesToMeasures(component, measures)).build();
+ return this;
+ }
+
+ /**
+ * Conditions for best value measure:
+ * <ul>
+ * <li>component is a production file or test file</li>
+ * <li>metric is optimized for best value</li>
+ * </ul>
+ */
+ private List<MeasureDto> addBestValuesToMeasures(ComponentDto component, List<MeasureDto> measures) {
+ if (!isEligibleForBestValue().test(component)) {
+ return emptyList();
+ }
+
+ requireNonNull(metrics);
+ requireNonNull(analyses);
+
+ Table<Integer, String, MeasureDto> measuresByMetricIdAndAnalysisUuid = HashBasedTable.create(metrics.size(), analyses.size());
+ measures.forEach(measure -> measuresByMetricIdAndAnalysisUuid.put(measure.getMetricId(), measure.getAnalysisUuid(), measure));
+ List<MeasureDto> bestValues = new ArrayList<>();
+ metrics.stream()
+ .filter(isOptimizedForBestValue())
+ .forEach(metric -> analyses.stream()
+ .filter(analysis -> !measuresByMetricIdAndAnalysisUuid.contains(metric.getId(), analysis.getUuid()))
+ .map(analysis -> toBestValue(metric, analysis))
+ .forEach(bestValues::add));
+
+ return bestValues;
+ }
+
+ private static MeasureDto toBestValue(MetricDto metric, SnapshotDto analysis) {
+ MeasureDto measure = new MeasureDto()
+ .setMetricId(metric.getId())
+ .setAnalysisUuid(analysis.getUuid());
+
+ if (metric.getKey().startsWith("new_")) {
+ measure.setVariation(1, metric.getBestValue());
+ } else {
+ measure.setValue(metric.getBestValue());
+ }
+
+ return measure;
+ }
+
+ Common.Paging getPaging() {
+ return requireNonNull(paging);
+ }
+}
--- /dev/null
+{
+ "paging": {
+ "pageIndex": 1,
+ "pageSize": 100,
+ "total": 3
+ },
+ "measures": [
+ {
+ "metric": "complexity",
+ "history": [
+ {
+ "date": "2017-01-23T17:00:53+0100",
+ "value": "45"
+ },
+ {
+ "date": "2017-01-24T17:00:53+0100",
+ "value": "45"
+ },
+ {
+ "date": "2017-01-25T17:00:53+0100",
+ "value": "45"
+ }
+ ]
+ },
+ {
+ "metric": "ncloc",
+ "history": [
+ {
+ "date": "2017-01-23T17:00:53+0100",
+ "value": "47"
+ },
+ {
+ "date": "2017-01-24T17:00:53+0100",
+ "value": "47"
+ },
+ {
+ "date": "2017-01-25T17:00:53+0100",
+ "value": "47"
+ }
+ ]
+ },
+ {
+ "metric": "new_violations",
+ "history": [
+ {
+ "date": "2017-01-23T17:00:53+0100",
+ "value": "46"
+ },
+ {
+ "date": "2017-01-24T17:00:53+0100",
+ "value": "46"
+ },
+ {
+ "date": "2017-01-25T17:00:53+0100",
+ "value": "46"
+ }
+ ]
+ }
+ ]
+}
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new MeasuresWsModule().configure(container);
- assertThat(container.size()).isEqualTo(5 + 2);
+ assertThat(container.size()).isEqualTo(6 + 2);
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.sonar.server.measure.ws;
+
+import com.google.common.base.Throwables;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.LongStream;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.measures.Metric.ValueType;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.util.stream.Collectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.Common.Paging;
+import org.sonarqube.ws.MediaTypes;
+import org.sonarqube.ws.WsMeasures.SearchHistoryResponse;
+import org.sonarqube.ws.WsMeasures.SearchHistoryResponse.HistoryMeasure;
+import org.sonarqube.ws.WsMeasures.SearchHistoryResponse.HistoryValue;
+import org.sonarqube.ws.client.measure.SearchHistoryRequest;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.api.utils.DateUtils.formatDateTime;
+import static org.sonar.api.utils.DateUtils.parseDateTime;
+import static org.sonar.core.util.Protobuf.setNullable;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.component.ComponentTesting.newProjectDto;
+import static org.sonar.db.component.SnapshotDto.STATUS_UNPROCESSED;
+import static org.sonar.db.component.SnapshotTesting.newAnalysis;
+import static org.sonar.db.measure.MeasureTesting.newMeasureDto;
+import static org.sonar.db.metric.MetricTesting.newMetricDto;
+import static org.sonar.test.JsonAssert.assertJson;
+import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_COMPONENT;
+import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_FROM;
+import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRICS;
+import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_TO;
+
+public class SearchHistoryActionTest {
+
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public DbTester db = DbTester.create();
+ private DbClient dbClient = db.getDbClient();
+ private DbSession dbSession = db.getSession();
+
+ private WsActionTester ws = new WsActionTester(new SearchHistoryAction(dbClient, new ComponentFinder(dbClient), userSession));
+
+ private ComponentDto project;
+ private SnapshotDto analysis;
+ private List<String> metrics;
+ private MetricDto complexityMetric;
+ private MetricDto nclocMetric;
+ private MetricDto newViolationMetric;
+ private SearchHistoryRequest.Builder wsRequest;
+
+ @Before
+ public void setUp() {
+ project = newProjectDto(db.getDefaultOrganization());
+ analysis = db.components().insertProjectAndSnapshot(project);
+ userSession.addProjectUuidPermissions(UserRole.USER, project.uuid());
+ nclocMetric = insertNclocMetric();
+ complexityMetric = insertComplexityMetric();
+ newViolationMetric = insertNewViolationMetric();
+ metrics = newArrayList(nclocMetric.getKey(), complexityMetric.getKey(), newViolationMetric.getKey());
+ wsRequest = SearchHistoryRequest.builder().setComponent(project.getKey()).setMetrics(metrics);
+ }
+
+ @Test
+ public void empty_response() {
+ project = db.components().insertProject();
+ userSession.addProjectUuidPermissions(UserRole.USER, project.uuid());
+ wsRequest
+ .setComponent(project.getKey())
+ .setMetrics(singletonList(complexityMetric.getKey()));
+
+ SearchHistoryResponse result = call();
+
+ assertThat(result.getMeasuresList()).hasSize(1);
+ assertThat(result.getMeasures(0).getHistoryCount()).isEqualTo(0);
+
+ assertThat(result.getPaging()).extracting(Paging::getPageIndex, Paging::getPageSize, Paging::getTotal)
+ // pagination is applied to the number of analyses
+ .containsExactly(1, 100, 0);
+ }
+
+ @Test
+ public void return_metrics() {
+ dbClient.measureDao().insert(dbSession, newMeasureDto(complexityMetric, project, analysis).setValue(42.0d));
+ db.commit();
+
+ SearchHistoryResponse result = call();
+
+ assertThat(result.getMeasuresList()).hasSize(3)
+ .extracting(HistoryMeasure::getMetric)
+ .containsExactly(complexityMetric.getKey(), nclocMetric.getKey(), newViolationMetric.getKey());
+ }
+
+ @Test
+ public void return_measures() {
+ SnapshotDto laterAnalysis = dbClient.snapshotDao().insert(dbSession, newAnalysis(project).setCreatedAt(analysis.getCreatedAt() + 42_000));
+ ComponentDto file = db.components().insertComponent(newFileDto(project));
+ dbClient.measureDao().insert(dbSession,
+ newMeasureDto(complexityMetric, project, analysis).setValue(101d),
+ newMeasureDto(complexityMetric, project, laterAnalysis).setValue(100d),
+ newMeasureDto(complexityMetric, file, analysis).setValue(42d),
+ newMeasureDto(nclocMetric, project, analysis).setValue(201d),
+ newMeasureDto(newViolationMetric, project, analysis).setVariation(1, 5d),
+ newMeasureDto(newViolationMetric, project, laterAnalysis).setVariation(1, 10d));
+ db.commit();
+
+ SearchHistoryResponse result = call();
+
+ assertThat(result.getPaging()).extracting(Paging::getPageIndex, Paging::getPageSize, Paging::getTotal)
+ .containsExactly(1, 100, 2);
+ assertThat(result.getMeasuresList()).extracting(HistoryMeasure::getMetric).hasSize(3)
+ .containsExactly(complexityMetric.getKey(), nclocMetric.getKey(), newViolationMetric.getKey());
+ String analysisDate = formatDateTime(analysis.getCreatedAt());
+ String laterAnalysisDate = formatDateTime(laterAnalysis.getCreatedAt());
+ // complexity measures
+ HistoryMeasure complexityMeasures = result.getMeasures(0);
+ assertThat(complexityMeasures.getMetric()).isEqualTo(complexityMetric.getKey());
+ assertThat(complexityMeasures.getHistoryList()).extracting(HistoryValue::getDate, HistoryValue::getValue)
+ .containsExactly(tuple(analysisDate, "101"), tuple(laterAnalysisDate, "100"));
+ // ncloc measures
+ HistoryMeasure nclocMeasures = result.getMeasures(1);
+ assertThat(nclocMeasures.getMetric()).isEqualTo(nclocMetric.getKey());
+ assertThat(nclocMeasures.getHistoryList()).extracting(HistoryValue::getDate, HistoryValue::getValue)
+ .containsExactly(tuple(analysisDate, "201"));
+ // new_violation measures
+ HistoryMeasure newViolationMeasures = result.getMeasures(2);
+ assertThat(newViolationMeasures.getMetric()).isEqualTo(newViolationMetric.getKey());
+ assertThat(newViolationMeasures.getHistoryList()).extracting(HistoryValue::getDate, HistoryValue::getValue)
+ .containsExactly(tuple(analysisDate, "5"), tuple(laterAnalysisDate, "10"));
+ }
+
+ @Test
+ public void pagination_applies_to_analyses() {
+ project = db.components().insertProject();
+ userSession.addProjectUuidPermissions(UserRole.USER, project.uuid());
+ List<String> analysisDates = LongStream.rangeClosed(1, 9)
+ .mapToObj(i -> dbClient.snapshotDao().insert(dbSession, newAnalysis(project).setCreatedAt(i * 1_000_000_000)))
+ .peek(a -> dbClient.measureDao().insert(dbSession, newMeasureDto(complexityMetric, project, a).setValue(101d)))
+ .map(a -> formatDateTime(a.getCreatedAt()))
+ .collect(Collectors.toList());
+ db.commit();
+ wsRequest.setComponent(project.getKey()).setPage(2).setPageSize(3);
+
+ SearchHistoryResponse result = call();
+
+ assertThat(result.getPaging()).extracting(Paging::getPageIndex, Paging::getPageSize, Paging::getTotal).containsExactly(2, 3, 9);
+ assertThat(result.getMeasures(0).getHistoryList()).extracting(HistoryValue::getDate).containsExactly(
+ analysisDates.get(3), analysisDates.get(4), analysisDates.get(5));
+ }
+
+ @Test
+ public void inclusive_from_and_to_dates() {
+ project = db.components().insertProject();
+ userSession.addProjectUuidPermissions(UserRole.USER, project.uuid());
+ List<String> analysisDates = LongStream.rangeClosed(1, 9)
+ .mapToObj(i -> dbClient.snapshotDao().insert(dbSession, newAnalysis(project).setCreatedAt(System2.INSTANCE.now() + i * 1_000_000_000L)))
+ .peek(a -> dbClient.measureDao().insert(dbSession, newMeasureDto(complexityMetric, project, a).setValue(Double.valueOf(a.getCreatedAt()))))
+ .map(a -> formatDateTime(a.getCreatedAt()))
+ .collect(Collectors.toList());
+ db.commit();
+ wsRequest.setComponent(project.getKey()).setFrom(analysisDates.get(1)).setTo(analysisDates.get(3));
+
+ SearchHistoryResponse result = call();
+
+ assertThat(result.getPaging()).extracting(Paging::getPageIndex, Paging::getPageSize, Paging::getTotal).containsExactly(1, 100, 3);
+ assertThat(result.getMeasures(0).getHistoryList()).extracting(HistoryValue::getDate).containsExactly(
+ analysisDates.get(1), analysisDates.get(2), analysisDates.get(3));
+ }
+
+ @Test
+ public void return_best_values_for_files() {
+ dbClient.metricDao().insert(dbSession, newMetricDto().setKey("optimized").setValueType(ValueType.INT.name()).setOptimizedBestValue(true).setBestValue(456d));
+ dbClient.metricDao().insert(dbSession, newMetricDto().setKey("new_optimized").setValueType(ValueType.INT.name()).setOptimizedBestValue(true).setBestValue(789d));
+ db.commit();
+ ComponentDto file = db.components().insertComponent(newFileDto(project));
+ wsRequest.setComponent(file.getKey()).setMetrics(Arrays.asList("optimized", "new_optimized"));
+
+ SearchHistoryResponse result = call();
+
+ assertThat(result.getMeasuresCount()).isEqualTo(2);
+ assertThat(result.getMeasuresList().get(0).getHistoryList()).extracting(HistoryValue::getValue).containsExactly("789");
+ assertThat(result.getMeasuresList().get(1).getHistoryList()).extracting(HistoryValue::getValue).containsExactly("456");
+
+ // Best value is not applied to project
+ wsRequest.setComponent(project.getKey());
+ result = call();
+ assertThat(result.getMeasuresList().get(0).getHistoryCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void do_not_return_unprocessed_analyses() {
+ dbClient.snapshotDao().insert(dbSession, newAnalysis(project).setStatus(STATUS_UNPROCESSED));
+ db.commit();
+
+ SearchHistoryResponse result = call();
+
+ // one analysis in setUp method
+ assertThat(result.getPaging().getTotal()).isEqualTo(1);
+ }
+
+ @Test
+ public void do_not_return_developer_measures() {
+ wsRequest.setMetrics(singletonList(complexityMetric.getKey()));
+ dbClient.measureDao().insert(dbSession, newMeasureDto(complexityMetric, project, analysis).setDeveloperId(42L));
+ db.commit();
+
+ SearchHistoryResponse result = call();
+
+ assertThat(result.getMeasuresCount()).isEqualTo(1);
+ assertThat(result.getMeasures(0).getHistoryCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void fail_if_unknown_metric() {
+ wsRequest.setMetrics(newArrayList(complexityMetric.getKey(), nclocMetric.getKey(), "METRIC_42", "42_METRIC"));
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Metrics 42_METRIC, METRIC_42 are not found");
+
+ call();
+ }
+
+ @Test
+ public void fail_if_not_enough_permissions() {
+ userSession.login().addProjectUuidPermissions(UserRole.ADMIN, project.uuid());
+
+ expectedException.expect(ForbiddenException.class);
+
+ call();
+ }
+
+ @Test
+ public void fail_if_unknown_component() {
+ wsRequest.setComponent("PROJECT_42");
+
+ expectedException.expect(NotFoundException.class);
+
+ call();
+ }
+
+ @Test
+ public void definition() {
+ WebService.Action definition = ws.getDef();
+
+ assertThat(definition.key()).isEqualTo("search_history");
+ assertThat(definition.responseExampleAsString()).isNotEmpty();
+ assertThat(definition.isPost()).isFalse();
+ assertThat(definition.isInternal()).isFalse();
+ assertThat(definition.since()).isEqualTo("6.3");
+ }
+
+ @Test
+ public void json_example() {
+ project = db.components().insertProject();
+ userSession.addProjectUuidPermissions(UserRole.USER, project.uuid());
+ long now = parseDateTime("2017-01-23T17:00:53+0100").getTime();
+ LongStream.rangeClosed(0, 2)
+ .mapToObj(i -> dbClient.snapshotDao().insert(dbSession, newAnalysis(project).setCreatedAt(now + i * 24 * 1_000 * 60 * 60)))
+ .forEach(analysis -> dbClient.measureDao().insert(dbSession,
+ newMeasureDto(complexityMetric, project, analysis).setValue(45d),
+ newMeasureDto(newViolationMetric, project, analysis).setVariation(1, 46d),
+ newMeasureDto(nclocMetric, project, analysis).setValue(47d)));
+ db.commit();
+
+ String result = ws.newRequest()
+ .setParam(PARAM_COMPONENT, project.getKey())
+ .setParam(PARAM_METRICS, String.join(",", metrics))
+ .execute().getInput();
+
+ assertJson(result).isSimilarTo(ws.getDef().responseExampleAsString());
+ }
+
+ private SearchHistoryResponse call() {
+ SearchHistoryRequest wsRequest = this.wsRequest.build();
+
+ TestRequest request = ws.newRequest()
+ .setMediaType(MediaTypes.PROTOBUF);
+
+ request.setParam(PARAM_COMPONENT, wsRequest.getComponent());
+ request.setParam(PARAM_METRICS, String.join(",", wsRequest.getMetrics()));
+ setNullable(wsRequest.getFrom(), from -> request.setParam(PARAM_FROM, from));
+ setNullable(wsRequest.getTo(), to -> request.setParam(PARAM_TO, to));
+ setNullable(wsRequest.getPage(), p -> request.setParam(Param.PAGE, String.valueOf(p)));
+ setNullable(wsRequest.getPageSize(), ps -> request.setParam(Param.PAGE_SIZE, String.valueOf(ps)));
+
+ try {
+ return SearchHistoryResponse.parseFrom(request.execute().getInputStream());
+ } catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ private static MetricDto newMetricDtoWithoutOptimization() {
+ return newMetricDto()
+ .setWorstValue(null)
+ .setOptimizedBestValue(false)
+ .setBestValue(null)
+ .setUserManaged(false);
+ }
+
+ private MetricDto insertNclocMetric() {
+ MetricDto metric = dbClient.metricDao().insert(dbSession, newMetricDtoWithoutOptimization()
+ .setKey("ncloc")
+ .setShortName("Lines of code")
+ .setDescription("Non Commenting Lines of Code")
+ .setDomain("Size")
+ .setValueType("INT")
+ .setDirection(-1)
+ .setQualitative(false)
+ .setHidden(false)
+ .setUserManaged(false));
+ db.commit();
+ return metric;
+ }
+
+ private MetricDto insertComplexityMetric() {
+ MetricDto metric = dbClient.metricDao().insert(dbSession, newMetricDtoWithoutOptimization()
+ .setKey("complexity")
+ .setShortName("Complexity")
+ .setDescription("Cyclomatic complexity")
+ .setDomain("Complexity")
+ .setValueType("INT")
+ .setDirection(-1)
+ .setQualitative(false)
+ .setHidden(false)
+ .setUserManaged(false));
+ db.commit();
+ return metric;
+ }
+
+ private MetricDto insertNewViolationMetric() {
+ MetricDto metric = dbClient.metricDao().insert(dbSession, newMetricDtoWithoutOptimization()
+ .setKey("new_violations")
+ .setShortName("New issues")
+ .setDescription("New Issues")
+ .setDomain("Issues")
+ .setValueType("INT")
+ .setDirection(-1)
+ .setQualitative(true)
+ .setHidden(false)
+ .setUserManaged(false));
+ db.commit();
+ return metric;
+ }
+}
return this;
}
+ /**
+ * @return analysis date
+ */
public Long getCreatedAt() {
return createdAt;
}
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.apache.ibatis.session.ResultHandler;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
+import static java.util.Collections.emptyList;
import static org.sonar.db.DatabaseUtils.executeLargeInputs;
import static org.sonar.db.DatabaseUtils.executeLargeInputsWithoutOutput;
*/
public List<MeasureDto> selectByQuery(DbSession dbSession, MeasureQuery query) {
if (query.returnsEmpty()) {
- return Collections.emptyList();
+ return emptyList();
}
if (query.isOnComponents()) {
return executeLargeInputs(
public List<MeasureDto> selectTreeByQuery(DbSession dbSession, ComponentDto baseComponent, MeasureTreeQuery query) {
if (query.returnsEmpty()) {
- return Collections.emptyList();
+ return emptyList();
}
return mapper(dbSession).selectTreeByQuery(query, baseComponent.uuid(), query.getUuidPath(baseComponent));
}
- public List<PastMeasureDto> selectPastMeasures(DbSession dbSession,
- String componentUuid,
- String analysisUuid,
- Collection<Integer> metricIds) {
+ public List<PastMeasureDto> selectPastMeasures(DbSession dbSession, String componentUuid, String analysisUuid, Collection<Integer> metricIds) {
+ if (metricIds.isEmpty()) {
+ return emptyList();
+ }
return executeLargeInputs(
metricIds,
- ids -> mapper(dbSession).selectPastMeasures(componentUuid, analysisUuid, ids));
+ ids -> mapper(dbSession).selectPastMeasuresOnSingleAnalysis(componentUuid, analysisUuid, ids));
+ }
+
+ public List<MeasureDto> selectPastMeasures(DbSession dbSession, String componentUuid, List<String> analysisUuids, List<Integer> metricIds) {
+ if (analysisUuids.isEmpty() || metricIds.isEmpty()) {
+ return emptyList();
+ }
+
+ return executeLargeInputs(
+ analysisUuids,
+ analyses -> mapper(dbSession).selectPastMeasuresOnSeveralAnalyses(componentUuid, analyses, metricIds));
}
/**
return this;
}
+ /**
+ * @param index starts at 1
+ */
@CheckForNull
public Double getVariation(int index) {
switch (index) {
List<MeasureDto> selectTreeByQuery(@Param("query") MeasureTreeQuery measureQuery, @Param("baseUuid") String baseUuid, @Param("baseUuidPath") String baseUuidPath);
- List<PastMeasureDto> selectPastMeasures(@Param("componentUuid") String componentUuid, @Param("analysisUuid") String analysisUuid, @Param("metricIds") List<Integer> metricIds);
+ List<PastMeasureDto> selectPastMeasuresOnSingleAnalysis(@Param("componentUuid") String componentUuid, @Param("analysisUuid") String analysisUuid,
+ @Param("metricIds") List<Integer> metricIds);
+
+ List<MeasureDto> selectPastMeasuresOnSeveralAnalyses(@Param("componentUuid") String componentUuid, @Param("analysisUuids") Collection<String> analysisUuid,
+ @Param("metricIds") Collection<Integer> metricIds);
List<MeasureDto> selectProjectMeasuresOfDeveloper(@Param("developerId") long developerId, @Param("metricIds") Collection<Integer> metricIds);
package org.sonar.db.metric;
import java.util.function.Predicate;
-import javax.annotation.Nonnull;
/**
* Common functions on MetricDto
public static Predicate<MetricDto> isOptimizedForBestValue() {
- return IsMetricOptimizedForBestValue.INSTANCE;
- }
-
- private enum IsMetricOptimizedForBestValue implements Predicate<MetricDto> {
- INSTANCE;
-
- @Override
- public boolean test(@Nonnull MetricDto input) {
- return input.isOptimizedBestValue() && input.getBestValue() != null;
- }
+ return m -> m != null && m.isOptimizedBestValue() && m.getBestValue() != null;
}
}
<include refid="org.sonar.db.component.ComponentMapper.selectDescendantsFilters"/>
</sql>
- <select id="selectPastMeasures" parameterType="map" resultType="org.sonar.db.measure.PastMeasureDto">
+ <select id="selectPastMeasuresOnSingleAnalysis" parameterType="map" resultType="org.sonar.db.measure.PastMeasureDto">
select pm.id as id, pm.metric_id as metricId, pm.person_id as personId, pm.value as value
from project_measures pm
inner join snapshots analysis on analysis.uuid = pm.analysis_uuid
and pm.person_id is null
</select>
+ <select id="selectPastMeasuresOnSeveralAnalyses" parameterType="map" resultType="Measure">
+ select <include refid="measureColumns"/>
+ from project_measures pm
+ inner join snapshots analysis on analysis.uuid = pm.analysis_uuid
+ where
+ pm.component_uuid = #{componentUuid}
+ and analysis.uuid in <foreach item="analysisUuid" collection="analysisUuids" open="(" separator="," close=")">#{analysisUuid}</foreach>
+ and pm.metric_id in <foreach item="metricId" collection="metricIds" open="(" separator="," close=")">#{metricId}</foreach>
+ and pm.person_id is null
+ </select>
+
<select id="selectProjectMeasuresOfDeveloper" parameterType="map" resultType="Measure">
SELECT
<include refid="measureColumns"/>
import org.sonar.db.component.SnapshotTesting;
import org.sonar.db.organization.OrganizationDto;
+import static com.google.common.collect.Lists.newArrayList;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
verifyMeasures(file1, MeasureTreeQuery.builder().setStrategy(LEAVES), "M2", "M3");
}
+ @Test
+ public void select_past_measures_with_several_analyses() {
+ ComponentDto project = db.components().insertProject();
+ insertAnalysis(LAST_ANALYSIS_UUID, project.uuid(), true);
+ insertAnalysis(OTHER_ANALYSIS_UUID, project.uuid(), false);
+ db.components().indexAllComponents();
+
+ // project
+ insertMeasure("PROJECT_M1", LAST_ANALYSIS_UUID, project.uuid(), NCLOC_METRIC_ID);
+ insertMeasure("PROJECT_M2", OTHER_ANALYSIS_UUID, project.uuid(), NCLOC_METRIC_ID);
+ db.commit();
+
+ // Children measures of project
+ List<MeasureDto> result = underTest.selectPastMeasures(db.getSession(), project.uuid(), newArrayList(LAST_ANALYSIS_UUID, OTHER_ANALYSIS_UUID), singletonList(NCLOC_METRIC_ID));
+
+ assertThat(result).hasSize(2).extracting(MeasureDto::getData).containsOnly("PROJECT_M1", "PROJECT_M2");
+ }
+
private Optional<MeasureDto> selectSingle(MeasureQuery.Builder query) {
return underTest.selectSingle(db.getSession(), query.build());
}
*/
package org.sonarqube.ws.client.measure;
+import org.sonar.api.server.ws.WebService.Param;
import org.sonarqube.ws.WsMeasures.ComponentTreeWsResponse;
import org.sonarqube.ws.WsMeasures.ComponentWsResponse;
+import org.sonarqube.ws.WsMeasures.SearchHistoryResponse;
import org.sonarqube.ws.client.BaseService;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.WsConnector;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.ACTION_COMPONENT;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.ACTION_COMPONENT_TREE;
+import static org.sonarqube.ws.client.measure.MeasuresWsParameters.ACTION_SEARCH_HISTORY;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.CONTROLLER_MEASURES;
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_COMPONENT;
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_FROM;
+import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRICS;
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_METRIC_SORT_FILTER;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_QUALIFIERS;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_STRATEGY;
+import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_TO;
public class MeasuresService extends BaseService {
public MeasuresService(WsConnector wsConnector) {
return call(getRequest, ComponentWsResponse.parser());
}
+
+ public SearchHistoryResponse searchHistory(SearchHistoryRequest request) {
+ GetRequest getRequest = new GetRequest(path(ACTION_SEARCH_HISTORY))
+ .setParam(PARAM_COMPONENT, request.getComponent())
+ .setParam(PARAM_METRICS, inlineMultipleParamValue(request.getMetrics()))
+ .setParam(PARAM_FROM, request.getFrom())
+ .setParam(PARAM_TO, request.getTo())
+ .setParam(Param.PAGE, request.getPage())
+ .setParam(Param.PAGE_SIZE, request.getPageSize());
+
+ return call(getRequest, SearchHistoryResponse.parser());
+ }
}
// actions
public static final String ACTION_COMPONENT_TREE = "component_tree";
-
public static final String ACTION_COMPONENT = "component";
+ public static final String ACTION_SEARCH_HISTORY = "search_history";
+
// parameters
public static final String PARAM_BASE_COMPONENT_ID = "baseComponentId";
-
public static final String PARAM_BASE_COMPONENT_KEY = "baseComponentKey";
+ public static final String PARAM_COMPONENT = "component";
public static final String PARAM_STRATEGY = "strategy";
public static final String PARAM_QUALIFIERS = "qualifiers";
+ public static final String PARAM_METRICS = "metrics";
public static final String PARAM_METRIC_KEYS = "metricKeys";
public static final String PARAM_METRIC_SORT = "metricSort";
public static final String PARAM_METRIC_PERIOD_SORT = "metricPeriodSort";
public static final String PARAM_PROJECT_KEYS = "projectKeys";
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 PARAM_FROM = "from";
+ public static final String PARAM_TO = "to";
+ public static final String ADDITIONAL_METRICS = "metrics";
public static final String ADDITIONAL_PERIODS = "periods";
+
public static final Set<String> ADDITIONAL_FIELDS = ImmutableSortedSet.of(ADDITIONAL_METRICS, ADDITIONAL_PERIODS);
private MeasuresWsParameters() {
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.ws.client.measure;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+
+import static java.lang.String.format;
+
+public class SearchHistoryRequest {
+ public static final int MAX_PAGE_SIZE = 1_000;
+ public static final int DEFAULT_PAGE_SIZE = 100;
+
+ private final String component;
+ private final List<String> metrics;
+ private final String from;
+ private final String to;
+ private final int page;
+ private final int pageSize;
+
+ public SearchHistoryRequest(Builder builder) {
+ this.component = builder.component;
+ this.metrics = builder.metrics;
+ this.from = builder.from;
+ this.to = builder.to;
+ this.page = builder.page;
+ this.pageSize = builder.pageSize;
+ }
+
+ public String getComponent() {
+ return component;
+ }
+
+ public List<String> getMetrics() {
+ return metrics;
+ }
+
+ @CheckForNull
+ public String getFrom() {
+ return from;
+ }
+
+ @CheckForNull
+ public String getTo() {
+ return to;
+ }
+
+ public int getPage() {
+ return page;
+ }
+
+ public int getPageSize() {
+ return pageSize;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String component;
+ private List<String> metrics;
+ private String from;
+ private String to;
+ private int page = 1;
+ private int pageSize = DEFAULT_PAGE_SIZE;
+
+ private Builder() {
+ // enforce build factory method
+ }
+
+ public Builder setComponent(String component) {
+ this.component = component;
+ return this;
+ }
+
+ public Builder setMetrics(List<String> metrics) {
+ this.metrics = metrics;
+ return this;
+ }
+
+ public Builder setFrom(String from) {
+ this.from = from;
+ return this;
+ }
+
+ public Builder setTo(String to) {
+ this.to = to;
+ return this;
+ }
+
+ public Builder setPage(int page) {
+ this.page = page;
+ return this;
+ }
+
+ public Builder setPageSize(int pageSize) {
+ this.pageSize = pageSize;
+ return this;
+ }
+
+ public SearchHistoryRequest build() {
+ checkArgument(component != null && !component.isEmpty(), "Component key is required");
+ checkArgument(metrics != null && !metrics.isEmpty(), "Metric keys are required");
+ checkArgument(pageSize <= MAX_PAGE_SIZE, "Page size (%d) must be lower than or equal to %d", pageSize, MAX_PAGE_SIZE);
+
+ return new SearchHistoryRequest(this);
+ }
+
+ private static void checkArgument(boolean condition, String message, Object... args) {
+ if (!condition) {
+ throw new IllegalArgumentException(format(message, args));
+ }
+ }
+ }
+}
repeated Measure measures = 1;
}
+// WS api/measures/search_history
+message SearchHistoryResponse {
+ optional sonarqube.ws.commons.Paging paging = 1;
+ repeated HistoryMeasure measures = 2;
+
+ message HistoryMeasure {
+ optional string metric = 1;
+ repeated HistoryValue history = 2;
+ }
+
+ message HistoryValue {
+ optional string date = 1;
+ optional string value = 2;
+ }
+}
+
message Component {
optional string id = 1;
optional string key = 2;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
+import org.sonarqube.ws.WsMeasures;
import org.sonarqube.ws.WsMeasures.ComponentTreeWsResponse;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.ServiceTester;
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_COMPONENT;
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_FROM;
+import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRICS;
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_METRIC_SORT_FILTER;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_QUALIFIERS;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_STRATEGY;
+import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_TO;
public class MeasuresServiceTest {
private static final String VALUE_BASE_COMPONENT_ID = "base-component-id";
private static final String VALUE_BASE_COMPONENT_KEY = "base-component-key";
+ private static final String VALUE_COMPONENT = "component-key";
private static final List<String> VALUE_METRIC_KEYS = newArrayList("ncloc", "complexity");
+ private static final List<String> VALUE_METRICS = newArrayList("ncloc", "complexity");
private static final String VALUE_STRATEGY = "all";
private static final List<String> VALUE_QUALIFIERS = newArrayList("FIL", "PRJ");
private static final ArrayList<String> VALUE_ADDITIONAL_FIELDS = newArrayList("metrics");
private static final String VALUE_METRIC_SORT = "ncloc";
private static final String VALUE_METRIC_SORT_FILTER = "all";
private static final int VALUE_PAGE = 42;
- private static final int VALUE_PAGE_SIZE = 1984;
+ private static final int VALUE_PAGE_SIZE = 1000;
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";
+ private static final String VALUE_FROM = "2017-10-01";
+ private static final String VALUE_TO = "2017-11-01";
@Rule
public ServiceTester<MeasuresService> serviceTester = new ServiceTester<>(new MeasuresService(mock(WsConnector.class)));
.hasParam(PARAM_METRIC_SORT_FILTER, VALUE_METRIC_SORT_FILTER)
.andNoOtherParam();
}
+
+ @Test
+ public void search_history() {
+ SearchHistoryRequest request = SearchHistoryRequest.builder()
+ .setComponent(VALUE_COMPONENT)
+ .setMetrics(VALUE_METRICS)
+ .setFrom(VALUE_FROM)
+ .setTo(VALUE_TO)
+ .setPage(VALUE_PAGE)
+ .setPageSize(VALUE_PAGE_SIZE)
+ .build();
+
+ underTest.searchHistory(request);
+ GetRequest getRequest = serviceTester.getGetRequest();
+
+ assertThat(serviceTester.getGetParser()).isSameAs(WsMeasures.SearchHistoryResponse.parser());
+ serviceTester.assertThat(getRequest)
+ .hasParam(PARAM_COMPONENT, VALUE_COMPONENT)
+ .hasParam(PARAM_METRICS, "ncloc,complexity")
+ .hasParam(PARAM_FROM, VALUE_FROM)
+ .hasParam(PARAM_TO, VALUE_TO)
+ .hasParam("p", VALUE_PAGE)
+ .hasParam("ps", VALUE_PAGE_SIZE)
+ .andNoOtherParam();
+ }
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.ws.client.measure;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonarqube.ws.client.measure.SearchHistoryRequest.DEFAULT_PAGE_SIZE;
+import static org.sonarqube.ws.client.measure.SearchHistoryRequest.MAX_PAGE_SIZE;
+
+public class SearchHistoryRequestTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private SearchHistoryRequest.Builder underTest = SearchHistoryRequest.builder();
+
+ @Test
+ public void full_example() {
+ SearchHistoryRequest result = underTest
+ .setComponent("C1")
+ .setMetrics(singletonList("new_lines"))
+ .setFrom("2017-01-15")
+ .setTo("2017-01-20")
+ .setPage(23)
+ .setPageSize(42)
+ .build();
+
+ assertThat(result)
+ .extracting(SearchHistoryRequest::getComponent, SearchHistoryRequest::getMetrics, SearchHistoryRequest::getFrom, SearchHistoryRequest::getTo,
+ SearchHistoryRequest::getPage, SearchHistoryRequest::getPageSize)
+ .containsExactly("C1", singletonList("new_lines"), "2017-01-15", "2017-01-20", 23, 42);
+ }
+
+ @Test
+ public void default_values() {
+ SearchHistoryRequest result = underTest.setComponent("C1").setMetrics(singletonList("new_lines")).build();
+
+ assertThat(result.getPage()).isEqualTo(1);
+ assertThat(result.getPageSize()).isEqualTo(DEFAULT_PAGE_SIZE);
+ }
+
+ @Test
+ public void fail_if_no_component() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Component key is required");
+
+ underTest.setMetrics(singletonList("new_lines")).build();
+ }
+
+ @Test
+ public void fail_if_empty_component() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Component key is required");
+
+ underTest.setComponent("").setMetrics(singletonList("new_lines")).build();
+ }
+
+ @Test
+ public void fail_if_no_metric() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Metric keys are required");
+
+ underTest.setComponent("C1").build();
+ }
+
+ @Test
+ public void fail_if_empty_metrics() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Metric keys are required");
+
+ underTest.setComponent("C1").setMetrics(emptyList()).build();
+ }
+
+ @Test
+ public void fail_if_page_size_greater_than_max_authorized_size() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Page size (1001) must be lower than or equal to 1000");
+
+ underTest.setComponent("C1").setMetrics(singletonList("violations")).setPageSize(MAX_PAGE_SIZE + 1).build();
+ }
+}