public static final String SORT_BY_ASSIGNEE = "ASSIGNEE";
public static final String SORT_BY_SEVERITY = "SEVERITY";
public static final String SORT_BY_STATUS = "STATUS";
- public static final Set<String> SORTS = ImmutableSet.of(SORT_BY_CREATION_DATE, SORT_BY_UPDATE_DATE, SORT_BY_CLOSE_DATE, SORT_BY_ASSIGNEE, SORT_BY_SEVERITY, SORT_BY_STATUS);
+
+ /**
+ * Sort by project, file path then line id
+ */
+ public static final String SORT_BY_FILE_LINE = "FILE_LINE";
+
+ public static final Set<String> SORTS = ImmutableSet.of(SORT_BY_CREATION_DATE, SORT_BY_UPDATE_DATE, SORT_BY_CLOSE_DATE, SORT_BY_ASSIGNEE, SORT_BY_SEVERITY,
+ SORT_BY_STATUS, SORT_BY_FILE_LINE);
private final Collection<String> issueKeys;
private final Collection<String> severities;
Integer debt = getNullableField(IssueNormalizer.IssueField.DEBT.field());
return (debt != null) ? Duration.create(Long.valueOf(debt)) : null;
}
+
+ @CheckForNull
+ public String filePath() {
+ return getNullableField(IssueNormalizer.IssueField.FILE_PATH.field());
+ }
}
package org.sonar.server.issue.index;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import org.apache.commons.lang.BooleanUtils;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.unit.TimeValue;
-import org.elasticsearch.index.query.*;
+import org.elasticsearch.index.query.BoolFilterBuilder;
+import org.elasticsearch.index.query.FilterBuilder;
+import org.elasticsearch.index.query.FilterBuilders;
+import org.elasticsearch.index.query.OrFilterBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
import org.sonar.core.issue.db.IssueDto;
import org.sonar.server.issue.IssueQuery;
import org.sonar.server.issue.filter.IssueFilterParameters;
-import org.sonar.server.search.*;
+import org.sonar.server.search.BaseIndex;
+import org.sonar.server.search.FacetValue;
+import org.sonar.server.search.IndexDefinition;
+import org.sonar.server.search.IndexField;
+import org.sonar.server.search.QueryContext;
+import org.sonar.server.search.Result;
+import org.sonar.server.search.SearchClient;
import javax.annotation.Nullable;
-import java.util.*;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import static com.google.common.collect.Lists.newArrayList;
-import static com.google.common.collect.Maps.newHashMap;
public class IssueIndex extends BaseIndex<Issue, IssueDto, String> {
- private Map<String, IndexField> sortColumns = newHashMap();
+ private ListMultimap<String, IndexField> sortColumns = ArrayListMultimap.create();
public IssueIndex(IssueNormalizer normalizer, SearchClient client) {
super(IndexDefinition.ISSUES, normalizer, client);
sortColumns.put(IssueQuery.SORT_BY_CREATION_DATE, IssueNormalizer.IssueField.ISSUE_CREATED_AT);
sortColumns.put(IssueQuery.SORT_BY_UPDATE_DATE, IssueNormalizer.IssueField.ISSUE_UPDATED_AT);
sortColumns.put(IssueQuery.SORT_BY_CLOSE_DATE, IssueNormalizer.IssueField.ISSUE_CLOSE_DATE);
+ sortColumns.put(IssueQuery.SORT_BY_FILE_LINE, IssueNormalizer.IssueField.PROJECT);
+ sortColumns.put(IssueQuery.SORT_BY_FILE_LINE, IssueNormalizer.IssueField.FILE_PATH);
+ sortColumns.put(IssueQuery.SORT_BY_FILE_LINE, IssueNormalizer.IssueField.LINE);
}
@Override
QueryBuilder esQuery = QueryBuilders.matchAllQuery();
BoolFilterBuilder esFilter = FilterBuilders.boolFilter();
Map<String, FilterBuilder> filters = getFilters(query, options);
- for (FilterBuilder filter: filters.values()) {
+ for (FilterBuilder filter : filters.values()) {
if (filter != null) {
esFilter.must(filter);
}
private BoolFilterBuilder getFilter(IssueQuery query, QueryContext options) {
BoolFilterBuilder esFilter = FilterBuilders.boolFilter();
- for (FilterBuilder filter: getFilters(query, options).values()) {
+ for (FilterBuilder filter : getFilters(query, options).values()) {
if (filter != null) {
esFilter.must(filter);
}
FilterBuilders.boolFilter()
.must(FilterBuilders.termFilter(IssueAuthorizationNormalizer.IssueAuthorizationField.PERMISSION.field(), UserRole.USER), groupsAndUser)
.cache(true))
- );
+ );
}
private void addDatesFilter(Map<String, FilterBuilder> filters, IssueQuery query) {
.subAggregation(facetTopAggregation);
}
-
private void setSorting(IssueQuery query, SearchRequestBuilder esSearch) {
/* integrate Query Sort */
String sortField = query.sort();
Boolean asc = query.asc();
if (sortField != null) {
- FieldSortBuilder sort = SortBuilders.fieldSort(toIndexField(sortField).sortField());
- if (asc != null && asc) {
- sort.order(SortOrder.ASC);
- } else {
- sort.order(SortOrder.DESC);
+ List<IndexField> fields = toIndexFields(sortField);
+ for (IndexField field : fields) {
+ FieldSortBuilder sort = SortBuilders.fieldSort(field.sortField());
+ if (asc != null && asc) {
+ sort.order(SortOrder.ASC);
+ } else {
+ sort.order(SortOrder.DESC);
+ }
+ esSearch.addSort(sort);
}
- esSearch.addSort(sort);
} else {
esSearch.addSort(IssueNormalizer.IssueField.ISSUE_UPDATED_AT.sortField(), SortOrder.DESC);
// deterministic sort when exactly the same updated_at (same millisecond)
}
}
- private IndexField toIndexField(String sort) {
- IndexField indexFieldSort = sortColumns.get(sort);
- if (indexFieldSort != null) {
- return indexFieldSort;
+ private List<IndexField> toIndexFields(String sort) {
+ List<IndexField> fields = sortColumns.get(sort);
+ if (fields != null) {
+ return fields;
}
throw new IllegalStateException("Unknown sort field : " + sort);
}
public static final IndexField CREATED_AT = add(IndexField.Type.DATE, "createdAt");
public static final IndexField UPDATED_AT = add(IndexField.Type.DATE, "updatedAt");
- public static final IndexField PROJECT = add(IndexField.Type.STRING, "project");
+ public static final IndexField PROJECT = addSortable(IndexField.Type.STRING, "project");
public static final IndexField COMPONENT = add(IndexField.Type.STRING, "component");
public static final IndexField MODULE = add(IndexField.Type.STRING, "module");
public static final IndexField MODULE_PATH = add(IndexField.Type.UUID_PATH, "modulePath");
public static final IndexField ISSUE_CREATED_AT = addSortable(IndexField.Type.DATE, "issueCreatedAt");
public static final IndexField ISSUE_UPDATED_AT = addSortable(IndexField.Type.DATE, "issueUpdatedAt");
public static final IndexField ISSUE_CLOSE_DATE = addSortable(IndexField.Type.DATE, "issueClosedAt");
- public static final IndexField LINE = add(IndexField.Type.NUMERIC, "line");
+ public static final IndexField LINE = addSortable(IndexField.Type.NUMERIC, "line");
public static final IndexField MESSAGE = add(IndexField.Type.STRING, "message");
public static final IndexField RESOLUTION = add(IndexField.Type.STRING, "resolution");
public static final IndexField REPORTER = add(IndexField.Type.STRING, "reporter");
public static final IndexField SEVERITY_VALUE = addSortable(IndexField.Type.NUMERIC, "severityValue");
public static final IndexField LANGUAGE = add(IndexField.Type.STRING, "language");
public static final IndexField RULE_KEY = add(IndexField.Type.STRING, "ruleKey");
+ public static final IndexField FILE_PATH = addSortable(IndexField.Type.STRING, "filePath");
public static final Set<IndexField> ALL_FIELDS = getAllFields();
update.put(IssueField.DEBT.field(), dto.getDebt());
update.put(IssueField.LANGUAGE.field(), dto.getLanguage());
update.put(IssueField.RULE_KEY.field(), dto.getRuleKey().toString());
+ update.put(IssueField.FILE_PATH.field(), dto.getFilePath());
/** Upsert elements */
Map<String, Object> upsert = getUpsertFor(IssueField.ALL_FIELDS, update);
assertThat(issue.language()).isEqualTo(dto.getLanguage());
assertThat(issue.status()).isEqualTo(dto.getStatus());
assertThat(issue.severity()).isEqualTo(dto.getSeverity());
+ assertThat(issue.filePath()).isEqualTo(dto.getFilePath());
assertThat(issue.attributes()).isEqualTo(KeyValueFormat.parse(dto.getIssueAttributes()));
import org.sonar.server.platform.BackendCleanup;
import org.sonar.server.rule.RuleTesting;
import org.sonar.server.rule.db.RuleDao;
-import org.sonar.server.search.*;
+import org.sonar.server.search.FacetValue;
+import org.sonar.server.search.IndexDefinition;
+import org.sonar.server.search.QueryContext;
+import org.sonar.server.search.Result;
+import org.sonar.server.search.SearchClient;
import org.sonar.server.tester.ServerTester;
import org.sonar.server.user.MockUserSession;
IssueTesting.newDto(rule, file, project).setStatus(Issue.STATUS_OPEN),
IssueTesting.newDto(rule, file, project).setStatus(Issue.STATUS_CLOSED),
IssueTesting.newDto(rule, file, project).setStatus(Issue.STATUS_REOPENED)
- );
+ );
session.commit();
IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_STATUS).asc(true);
IssueTesting.newDto(rule, file, project).setSeverity(Severity.MINOR),
IssueTesting.newDto(rule, file, project).setSeverity(Severity.CRITICAL),
IssueTesting.newDto(rule, file, project).setSeverity(Severity.MAJOR)
- );
+ );
session.commit();
IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_SEVERITY).asc(true);
assertThat(result.getHits().get(2).closeDate()).isNull();
}
+ @Test
+ public void sort_by_file_and_line() throws Exception {
+ db.issueDao().insert(session,
+ // file Foo.java
+ IssueTesting.newDto(rule, file, project).setLine(20).setFilePath("src/Foo.java"),
+ IssueTesting.newDto(rule, file, project).setLine(null).setFilePath("src/Foo.java"),
+ IssueTesting.newDto(rule, file, project).setLine(25).setFilePath("src/Foo.java"),
+
+ // file Bar.java
+ IssueTesting.newDto(rule, file, project).setLine(9).setFilePath("src/Bar.java"),
+ IssueTesting.newDto(rule, file, project).setLine(109).setFilePath("src/Bar.java")
+ );
+ session.commit();
+
+ // ascending sort -> Bar then Foo
+ IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_FILE_LINE).asc(true);
+ Result<Issue> result = index.search(query.build(), new QueryContext());
+ assertThat(result.getHits()).hasSize(5);
+ assertThat(result.getHits().get(0).line()).isEqualTo(9);
+ assertThat(result.getHits().get(1).line()).isEqualTo(109);
+ assertThat(result.getHits().get(2).line()).isEqualTo(20);
+ assertThat(result.getHits().get(3).line()).isEqualTo(25);
+ assertThat(result.getHits().get(4).line()).isEqualTo(null);
+
+ // descending sort -> Foo then Bar
+ query = IssueQuery.builder().sort(IssueQuery.SORT_BY_FILE_LINE).asc(false);
+ result = index.search(query.build(), new QueryContext());
+ assertThat(result.getHits()).hasSize(5);
+ assertThat(result.getHits().get(0).line()).isEqualTo(25);
+ assertThat(result.getHits().get(1).line()).isEqualTo(20);
+ assertThat(result.getHits().get(2).line()).isEqualTo(null);
+ assertThat(result.getHits().get(3).line()).isEqualTo(109);
+ assertThat(result.getHits().get(4).line()).isEqualTo(9);
+ }
+
@Test
public void authorized_issues_on_groups() throws Exception {
ComponentDto project1 = ComponentTesting.newProjectDto().setKey("project1");
IssueTesting.newDto(rule, file1, project1),
IssueTesting.newDto(rule, file2, project2),
IssueTesting.newDto(rule, file2, project3)
- );
+ );
session.commit();
session.clearCache();
assertThat(db.issueDao().findAfterDate(session, new Date(0))).hasSize(numberOfIssues);
assertThat(index.countAll()).isEqualTo(numberOfIssues);
- // Clear issue index in order to simulate these issues have been inserted without being indexed in E/S (from a previous version of SQ or from batch)
+ // Clear issue index in order to simulate these issues have been inserted without being indexed in E/S (from a previous version of SQ or
+ // from batch)
tester.get(BackendCleanup.class).clearIndex(IndexDefinition.ISSUES);
tester.clearIndexes();
assertThat(index.countAll()).isEqualTo(0);
IssueTesting.newDto(rule, file, project).setAssignee("steph").setStatus(Issue.STATUS_OPEN),
// julien should not be returned as the issue is closed
IssueTesting.newDto(rule, file, project).setAssignee("julien").setStatus(Issue.STATUS_CLOSED)
- );
+ );
session.commit();
List<FacetValue> results = index.listAssignees(IssueQuery.builder().statuses(newArrayList(Issue.STATUS_OPEN)).build());
assertThat(results.get(2).getValue()).isEqualTo(1);
}
-
@Test
public void index_has_4_shards() {
private String moduleUuidPath;
private String projectKey;
private String projectUuid;
+ private String filePath;
@Override
public String getKey() {
this.componentUuid = component.uuid();
this.moduleUuid = component.moduleUuid();
this.moduleUuidPath = component.moduleUuidPath();
+ this.filePath = component.path();
return this;
}
return this;
}
+ /**
+ * Should only be used to persist in E/S
+ *
+ * Please use {@link #setProject(org.sonar.core.component.ComponentDto)} instead
+ */
+ public String getFilePath() {
+ return filePath;
+ }
+
+ /**
+ * Should only be used to persist in E/S
+ *
+ * Please use {@link #setProject(org.sonar.core.component.ComponentDto)} instead
+ */
+ public IssueDto setFilePath(String filePath) {
+ this.filePath = filePath;
+ return this;
+ }
+
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
p.uuid as componentUuid,
p.module_uuid as moduleUuid,
p.module_uuid_path as moduleUuidPath,
+ p.path as filePath,
root.kee as projectKey,
root.uuid as projectUuid
</sql>