assertThat(docsById).hasSize(1);
ProjectMeasures doc = docsById.get(projectData.projectUuid());
assertThat(doc.getProject().getAnalysisDate()).isNull();
+ assertThat(doc.getProject().getCreationDate()).isEqualTo(projectData.getProjectDto().getCreatedAt());
}
@Test
CoreMetrics.NEW_LINES_KEY,
CoreMetrics.NEW_RELIABILITY_RATING_KEY);
- private static final String SQL_PROJECTS = "SELECT p.uuid, p.kee, p.name, s.created_at, p.tags, p.qualifier " +
+ private static final String SQL_PROJECTS = "SELECT p.uuid, p.kee, p.name, p.created_at, s.created_at, p.tags, p.qualifier " +
"FROM projects p " +
"INNER JOIN project_branches pb ON pb.project_uuid = p.uuid AND pb.is_main = ? " +
"LEFT OUTER JOIN snapshots s ON s.root_component_uuid=pb.uuid AND s.islast=? " +
String uuid = rs.getString(1);
String key = rs.getString(2);
String name = rs.getString(3);
- Long analysisDate = DatabaseUtils.getLong(rs, 4);
- List<String> tags = readDbTags(DatabaseUtils.getString(rs, 5));
- String qualifier = rs.getString(6);
- Project project = new Project(uuid, key, name, qualifier, tags, analysisDate);
+ Long creationDate = rs.getLong(4);
+ Long analysisDate = DatabaseUtils.getLong(rs, 5);
+ List<String> tags = readDbTags(DatabaseUtils.getString(rs, 6));
+ String qualifier = rs.getString(7);
+ Project project = new Project(uuid, key, name, qualifier, tags, creationDate, analysisDate);
projects.add(project);
}
return projects;
private final String key;
private final String name;
private final String qualifier;
+ private final Long creationDate;
private final Long analysisDate;
private final List<String> tags;
- public Project(String uuid, String key, String name, String qualifier, List<String> tags, @Nullable Long analysisDate) {
+ public Project(String uuid, String key, String name, String qualifier, List<String> tags, Long creationDate, @Nullable Long analysisDate) {
this.uuid = uuid;
this.key = key;
this.name = name;
this.qualifier = qualifier;
this.tags = tags;
this.analysisDate = analysisDate;
+ this.creationDate = creationDate;
}
public String getUuid() {
public Long getAnalysisDate() {
return analysisDate;
}
+
+ public Long getCreationDate() {
+ return creationDate;
+ }
}
public static class Measures {
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.lucene.search.join.ScoreMode;
+import org.assertj.core.groups.Tuple;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.joda.time.DateTime;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.measures.CoreMetrics;
import static java.util.Collections.emptySet;
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.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.nestedQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
@Test
public void indexAll_indexes_all_projects() {
- SnapshotDto snapshot1 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
- SnapshotDto snapshot2 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
- SnapshotDto snapshot3 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
+
+ ProjectData project1 = db.components().insertPrivateProject();
+ SnapshotDto snapshot1 = db.components().insertSnapshot(project1.getMainBranchComponent());
+
+ ProjectData project2 = db.components().insertPrivateProject();
+ SnapshotDto snapshot2 = db.components().insertSnapshot(project2.getMainBranchComponent());
+
+ ProjectData project3 = db.components().insertPrivateProject();
+ SnapshotDto snapshot3 = db.components().insertSnapshot(project3.getMainBranchComponent());
underTest.indexAll();
assertThatIndexContainsOnly(snapshot1, snapshot2, snapshot3);
assertThatQualifierIs("TRK", snapshot1, snapshot2, snapshot3);
+ assertThatIndexContainsCreationDate(project1, project2, project3);
}
/**
BranchDto branchDto = db.components().insertProjectBranch(project1.getProjectDto());
underTest.indexOnAnalysis(branchDto.getUuid());
- assertThatIndexContainsOnly(new ProjectDto[]{});
+ assertThatIndexContainsOnly(new ProjectDto[] {});
}
@Test
boolQuery()
.filter(termQuery(FIELD_MEASURES_MEASURE_KEY, metric))
.filter(termQuery(FIELD_MEASURES_MEASURE_VALUE, value)),
- ScoreMode.Avg
- )));
+ ScoreMode.Avg)));
assertThat(es.client().search(request).getHits().getHits())
.extracting(SearchHit::getId)
Arrays.stream(expectedSnapshots).map(this::getProjectUuidFromSnapshot).toArray(String[]::new));
}
+ private void assertThatIndexContainsCreationDate(ProjectData... projectDatas) {
+ List<Map<String, Object>> documents = es.getDocuments(TYPE_PROJECT_MEASURES).stream().map(SearchHit::getSourceAsMap).toList();
+
+ List<Tuple> expected = Arrays.stream(projectDatas).map(
+ projectData -> tuple(
+ projectData.getProjectDto().getKey(),
+ projectData.getProjectDto().getCreatedAt()))
+ .toList();
+ assertThat(documents)
+ .extracting(hit -> hit.get("key"), hit -> stringDateToMilliseconds((String) hit.get("createdAt")))
+ .containsExactlyInAnyOrderElementsOf(expected);
+
+ }
+
+ private static long stringDateToMilliseconds(String date) {
+ return DateTime.parse(date).getMillis();
+ }
+
private String getProjectUuidFromSnapshot(SnapshotDto s) {
ProjectDto projectDto = db.getDbClient().projectDao().selectByBranchUuid(db.getSession(), s.getRootComponentUuid()).orElseThrow();
return projectDto.getUuid();
}
private void assertThatIndexContainsOnly(ProjectDto... expectedProjects) {
- assertThat(es.getIds(TYPE_PROJECT_MEASURES)).containsExactlyInAnyOrder(
- Arrays.stream(expectedProjects).map(ProjectDto::getUuid).toArray(String[]::new));
+ assertThat(es.getIds(TYPE_PROJECT_MEASURES)).containsExactlyInAnyOrderElementsOf(
+ Arrays.stream(expectedProjects).map(ProjectDto::getUuid).toList());
}
private void assertThatQualifierIs(String qualifier, ProjectDto... expectedProjects) {
import static org.sonar.api.measures.Metric.Level.ERROR;
import static org.sonar.api.measures.Metric.Level.OK;
import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_ANALYSED_AT;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_CREATED_AT;
import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_KEY;
import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_LANGUAGES;
import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
return this;
}
+ public ProjectMeasuresDoc setCreatedAt(Date d) {
+ setField(FIELD_CREATED_AT, d);
+ return this;
+ }
+
+ public Date getCreatedAt() {
+ return getField(FIELD_CREATED_AT);
+ }
+
public Collection<Map<String, Object>> getMeasures() {
return getField(FIELD_MEASURES);
}
*/
package org.sonar.server.measure.index;
+import javax.inject.Inject;
import org.sonar.api.config.Configuration;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.server.es.Index;
import static org.sonar.server.es.newindex.SettingsConfiguration.MANUAL_REFRESH_INTERVAL;
import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
-import javax.inject.Inject;
-
public class ProjectMeasuresIndexDefinition implements IndexDefinition {
public static final Index DESCRIPTOR = Index.withRelations("projectmeasures");
public static final String FIELD_NAME = "name";
public static final String FIELD_QUALIFIER = "qualifier";
public static final String FIELD_ANALYSED_AT = "analysedAt";
+ public static final String FIELD_CREATED_AT = "createdAt";
public static final String FIELD_QUALITY_GATE_STATUS = "qualityGateStatus";
public static final String FIELD_TAGS = "tags";
public static final String FIELD_MEASURES = "measures";
.addIntegerField(SUB_FIELD_DISTRIB_NCLOC)
.build();
mapping.createDateTimeField(FIELD_ANALYSED_AT);
+ mapping.createDateTimeField(FIELD_CREATED_AT);
}
}
.setQualityGateStatus(projectMeasures.getMeasures().getQualityGateStatus())
.setTags(project.getTags())
.setAnalysedAt(analysisDate == null ? null : new Date(analysisDate))
+ .setCreatedAt(new Date(project.getCreationDate()))
.setMeasuresFromMap(projectMeasures.getMeasures().getNumericMeasures())
.setLanguages(new ArrayList<>(projectMeasures.getMeasures().getNclocByLanguages().keySet()))
.setNclocLanguageDistributionFromMap(projectMeasures.getMeasures().getNclocByLanguages());
import static org.sonar.server.measure.index.ProjectMeasuresIndex.Facet.LANGUAGES;
import static org.sonar.server.measure.index.ProjectMeasuresIndex.Facet.TAGS;
import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_ANALYSED_AT;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_CREATED_AT;
import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_KEY;
import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_LANGUAGES;
import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_TAGS;
import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.SUB_FIELD_MEASURES_KEY;
import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_CREATION_DATE;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_LAST_ANALYSIS_DATE;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_NAME;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_LANGUAGES;
requestBuilder.sort(DefaultIndexSettingsElement.SORTABLE_ANALYZER.subField(FIELD_NAME), query.isAsc() ? ASC : DESC);
} else if (SORT_BY_LAST_ANALYSIS_DATE.equals(sort)) {
requestBuilder.sort(FIELD_ANALYSED_AT, query.isAsc() ? ASC : DESC);
+ } else if (SORT_BY_CREATION_DATE.equals(sort)) {
+ requestBuilder.sort(FIELD_CREATED_AT, query.isAsc() ? ASC : DESC);
} else if (ALERT_STATUS_KEY.equals(sort)) {
requestBuilder.sort(FIELD_QUALITY_GATE_STATUS, query.isAsc() ? ASC : DESC);
requestBuilder.sort(DefaultIndexSettingsElement.SORTABLE_ANALYZER.subField(FIELD_NAME), ASC);
public static final String SORT_BY_NAME = "name";
public static final String SORT_BY_LAST_ANALYSIS_DATE = "analysisDate";
+ public static final String SORT_BY_CREATION_DATE = "creationDate";
private List<MetricCriterion> metricCriteria = new ArrayList<>();
private Metric.Level qualityGateStatus = null;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.time.Instant;
import java.util.Arrays;
+import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
assertResults(new ProjectMeasuresQuery().setSort("alert_status").setAsc(false), PROJECT2, PROJECT1, project4);
}
+ public void sort_by_creation_date() {
+ Instant now = Instant.ofEpochMilli(1000L);
+ Date nowMinus10 = Date.from(now.minusSeconds(10));
+ Date nowMinus20 = Date.from(now.minusSeconds(20));
+ Date nowMinus30 = Date.from(now.minusSeconds(30));
+
+ ComponentDto project4 = ComponentTesting.newPrivateProjectDto().setUuid("Project-4").setName("Project 4").setKey("key-4");
+ index(
+ newDoc(PROJECT1).setCreatedAt(nowMinus10),
+ newDoc(PROJECT2).setCreatedAt(nowMinus30),
+ newDoc(project4).setCreatedAt(nowMinus20));
+
+ assertResults(new ProjectMeasuresQuery().setSort("creation_date").setAsc(true), PROJECT1, project4, PROJECT2);
+ assertResults(new ProjectMeasuresQuery().setSort("creation_date").setAsc(false), PROJECT2, PROJECT1, project4);
+ }
+
+ public void sort_by_analysis_date() {
+ Instant now = Instant.ofEpochMilli(1000L);
+ Date nowMinus10 = Date.from(now.minusSeconds(10));
+ Date nowMinus20 = Date.from(now.minusSeconds(20));
+ Date nowMinus30 = Date.from(now.minusSeconds(30));
+
+ ComponentDto project4 = ComponentTesting.newPrivateProjectDto().setUuid("Project-4").setName("Project 4").setKey("key-4");
+ index(
+ newDoc(PROJECT1).setAnalysedAt(nowMinus10),
+ newDoc(PROJECT2).setAnalysedAt(nowMinus30),
+ newDoc(project4).setAnalysedAt(nowMinus20));
+
+ assertResults(new ProjectMeasuresQuery().setSort("analysis_date").setAsc(true), PROJECT1, project4, PROJECT2);
+ assertResults(new ProjectMeasuresQuery().setSort("analysis_date").setAsc(false), PROJECT2, PROJECT1, project4);
+ }
+
@Test
public void sort_by_quality_gate_status_then_by_name_then_by_key() {
ComponentDto windows = ComponentTesting.newPrivateProjectDto().setUuid("windows").setName("Windows").setKey("project1");
newDoc().setTags(newArrayList("finance", "offshore")),
newDoc().setTags(newArrayList("offshore")));
- List<String> result = underTest.searchTags("off", 1,10);
+ List<String> result = underTest.searchTags("off", 1, 10);
assertThat(result).containsOnly("offshore", "official", "Madhoff");
}
newDoc().setTags(newArrayList("finance", "offshore")),
newDoc().setTags(newArrayList("offshore")));
- List<String> result = underTest.searchTags(null, 1,10);
+ List<String> result = underTest.searchTags(null, 1, 10);
assertThat(result).containsExactly("Madhoff", "finance", "java", "javascript", "marketing", "official", "offshore");
}
newDoc().setTags(newArrayList("finance", "offshore")),
newDoc().setTags(newArrayList("offshore")));
- List<String> result = underTest.searchTags(null, 1,3);
+ List<String> result = underTest.searchTags(null, 1, 3);
assertThat(result).containsExactly("Madhoff", "finance", "java");
- result = underTest.searchTags(null, 2,3);
+ result = underTest.searchTags(null, 2, 3);
assertThat(result).containsExactly("javascript", "marketing", "official");
- result = underTest.searchTags(null, 3,3);
+ result = underTest.searchTags(null, 3, 3);
assertThat(result).containsExactly("offshore");
- result = underTest.searchTags(null, 3,4);
+ result = underTest.searchTags(null, 3, 4);
assertThat(result).isEmpty();
}
newDoc().setTags(newArrayList("finance", "offshore")),
newDoc().setTags(newArrayList("offshore")));
- List<String> result = underTest.searchTags(null, 10,2);
+ List<String> result = underTest.searchTags(null, 10, 2);
assertThat(result).isEmpty();
}
userSession.logIn(USER1);
- List<String> result = underTest.searchTags(null, 1,10);
+ List<String> result = underTest.searchTags(null, 1, 10);
assertThat(result).containsOnly("finance", "marketing");
}
@Test
public void search_tags_with_no_tags() {
- List<String> result = underTest.searchTags("whatever", 1,10);
+ List<String> result = underTest.searchTags("whatever", 1, 10);
assertThat(result).isEmpty();
}
public void search_tags_with_page_size_at_0() {
index(newDoc().setTags(newArrayList("offshore")));
- List<String> result = underTest.searchTags(null, 1,0);
+ List<String> result = underTest.searchTags(null, 1, 0);
assertThat(result).isEmpty();
}
int jsLocByProjects = 900;
int csLocByProjects = 2;
- ProjectMeasuresDoc[] documents = IntStream.range(0, nbProjects).mapToObj(i ->
- newDoc("lines", 10, "coverage", 80)
- .setLanguages(asList("java", "cs", "js"))
- .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", javaLocByProjects, "cs", csLocByProjects, "js", jsLocByProjects))).toArray(ProjectMeasuresDoc[]::new);
+ ProjectMeasuresDoc[] documents = IntStream.range(0, nbProjects).mapToObj(i -> newDoc("lines", 10, "coverage", 80)
+ .setLanguages(asList("java", "cs", "js"))
+ .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", javaLocByProjects, "cs", csLocByProjects, "js", jsLocByProjects))).toArray(ProjectMeasuresDoc[]::new);
es.putDocuments(TYPE_PROJECT_MEASURES, documents);
assertThat(result.getNclocByLanguage())
.hasSize(3)
- .containsEntry("java",(long) nbProjects * javaLocByProjects)
- .containsEntry("cs",(long) nbProjects * csLocByProjects)
- .containsEntry("js",(long) nbProjects * jsLocByProjects);
+ .containsEntry("java", (long) nbProjects * javaLocByProjects)
+ .containsEntry("cs", (long) nbProjects * csLocByProjects)
+ .containsEntry("js", (long) nbProjects * jsLocByProjects);
}
@Test
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.apache.ibatis.session.ResultHandler;
-import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
assertThat(def.isPost()).isFalse();
assertThat(def.responseExampleAsString()).isNotEmpty();
assertThat(def.params().stream().map(Param::key).toList()).containsOnly("filter", "facets", "s", "asc", "ps", "p", "f");
- assertThat(def.changelog()).hasSize(3);
+ assertThat(def.changelog()).hasSize(4);
Param sort = def.param("s");
assertThat(sort.defaultValue()).isEqualTo("name");
"ncloc",
"new_maintainability_rating",
"name",
- "analysisDate");
+ "analysisDate",
+ "creationDate");
Param asc = def.param("asc");
assertThat(asc.defaultValue()).isEqualTo("true");
private void addFavourite(@Nullable String entityUuid, @Nullable String entityKey, @Nullable String entityName, @Nullable String qualifier) {
dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("favourite")
- .setEntityUuid(entityUuid).setUserUuid(userSession.getUuid()), userSession.getLogin(), entityKey, entityName, qualifier);
+ .setEntityUuid(entityUuid).setUserUuid(userSession.getUuid()), userSession.getLogin(), entityKey, entityName, qualifier);
dbSession.commit();
}
import static java.util.Arrays.asList;
import static org.sonar.db.measure.ProjectMeasuresIndexerIterator.METRIC_KEYS;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_CREATION_DATE;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_LAST_ANALYSIS_DATE;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_NAME;
public class ProjectMeasuresQueryValidator {
- static final Set<String> NON_METRIC_SORT_KEYS = new HashSet<>(asList(SORT_BY_NAME, SORT_BY_LAST_ANALYSIS_DATE));
+ static final Set<String> NON_METRIC_SORT_KEYS = new HashSet<>(asList(SORT_BY_NAME, SORT_BY_LAST_ANALYSIS_DATE, SORT_BY_CREATION_DATE));
private ProjectMeasuresQueryValidator() {
}
import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.IS_FAVORITE_CRITERION;
import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.newProjectMeasuresQuery;
import static org.sonar.server.component.ws.ProjectMeasuresQueryValidator.NON_METRIC_SORT_KEYS;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_CREATION_DATE;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_LAST_ANALYSIS_DATE;
import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_NAME;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
.addPagingParams(DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE)
.setInternal(true)
.setChangelog(
+ new Change("10.3", "Add 'creationDate' sort parameter."),
new Change("10.2", "Field 'needIssueSync' removed from response"),
new Change("8.3", "Add 'qualifier' filter and facet"),
new Change("8.0", "Field 'id' removed from response"))
" <li>APP - for applications</li>" +
HTML_UL_END_TAG);
action.createParam(Param.SORT)
- .setDescription("Sort projects by numeric metric key, quality gate status (using '%s'), last analysis date (using '%s'), or by project name.",
- ALERT_STATUS_KEY, SORT_BY_LAST_ANALYSIS_DATE, PARAM_FILTER)
+ .setDescription("Sort projects by numeric metric key, quality gate status (using '%s'), last analysis date (using '%s'), project name or creationDate (using '%s').",
+ ALERT_STATUS_KEY, SORT_BY_LAST_ANALYSIS_DATE, PARAM_FILTER, SORT_BY_CREATION_DATE)
.setDefaultValue(SORT_BY_NAME)
.setPossibleValues(
Stream.concat(METRIC_KEYS.stream(), NON_METRIC_SORT_KEYS.stream()).sorted().toList())
public class ProjectMeasuresQueryValidatorTest {
-
@Test
public void query_with_empty_metrics_is_valid() {
ProjectMeasuresQueryValidator.validate(new ProjectMeasuresQuery());
assertValidSortKey("new_reliability_rating");
}
+ @Test
+ public void sort_by_creation_date_is_valid() {
+ assertValidSortKey("creationDate");
+ }
+
+ @Test
+ public void sort_by_analysis_date_is_valid() {
+ assertValidSortKey("analysisDate");
+ }
+
@Test
public void sort_by_bla_is_invalid() {
assertInvalidSortKey("bla");