private final Collection<String> languages;
private final Collection<String> tags;
private final Collection<String> types;
- private final Map<String, Date> createdAfterByProjectUuids;
+ private final Map<String, PeriodStart> createdAfterByProjectUuids;
private final Boolean onComponentOnly;
private final Boolean assigned;
private final Boolean resolved;
private final Date createdAt;
- private final Date createdAfter;
+ private final PeriodStart createdAfter;
private final Date createdBefore;
private final String sort;
private final Boolean asc;
return types;
}
- public Map<String, Date> createdAfterByProjectUuids() {
+ public Map<String, PeriodStart> createdAfterByProjectUuids() {
return createdAfterByProjectUuids;
}
}
@CheckForNull
- public Date createdAfter() {
- return createdAfter == null ? null : new Date(createdAfter.getTime());
+ public PeriodStart createdAfter() {
+ return createdAfter;
}
@CheckForNull
private Collection<String> languages;
private Collection<String> tags;
private Collection<String> types;
- private Map<String, Date> createdAfterByProjectUuids;
+ private Map<String, PeriodStart> createdAfterByProjectUuids;
private Boolean onComponentOnly = false;
private Boolean assigned = null;
private Boolean resolved = null;
private Date createdAt;
- private Date createdAfter;
+ private PeriodStart createdAfter;
private Date createdBefore;
private String sort;
private Boolean asc = false;
return this;
}
- public Builder createdAfterByProjectUuids(@Nullable Map<String, Date> createdAfterByProjectUuids) {
+ public Builder createdAfterByProjectUuids(@Nullable Map<String, PeriodStart> createdAfterByProjectUuids) {
this.createdAfterByProjectUuids = createdAfterByProjectUuids;
return this;
}
}
public Builder createdAfter(@Nullable Date d) {
- this.createdAfter = d == null ? null : new Date(d.getTime());
+ this.createdAfter(d, true);
+ return this;
+ }
+
+ public Builder createdAfter(@Nullable Date d, boolean inclusive) {
+ this.createdAfter = d == null ? null : new PeriodStart(new Date(d.getTime()), inclusive);
return this;
}
return c == null ? Collections.emptyList() : Collections.unmodifiableCollection(c);
}
- private static <K,V> Map<K,V> defaultMap(@Nullable Map<K,V> map) {
+ private static <K, V> Map<K, V> defaultMap(@Nullable Map<K, V> map) {
return map == null ? Collections.emptyMap() : Collections.unmodifiableMap(map);
}
+ public static class PeriodStart {
+ private final Date date;
+ private final boolean inclusive;
+
+ public PeriodStart(Date date, boolean inclusive) {
+ this.date = date;
+ this.inclusive = inclusive;
+
+ }
+
+ public Date date() {
+ return date;
+ }
+
+ public boolean inclusive() {
+ return inclusive;
+ }
+
+ }
+
}
*/
package org.sonar.server.issue;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Joiner;
-import com.google.common.base.Splitter;
-import com.google.common.base.Strings;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.Lists;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.Lists.newArrayList;
+import static java.lang.String.format;
+import static java.util.Collections.singleton;
+import static java.util.Collections.singletonList;
+import static org.sonar.api.utils.DateUtils.longToDate;
+import static org.sonar.api.utils.DateUtils.parseDateOrDateTime;
+import static org.sonar.api.utils.DateUtils.parseEndingDateOrDateTime;
+import static org.sonar.api.utils.DateUtils.parseStartingDateOrDateTime;
+import static org.sonar.core.util.stream.MoreCollectors.toHashSet;
+import static org.sonar.core.util.stream.MoreCollectors.toList;
+import static org.sonar.core.util.stream.MoreCollectors.toSet;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+import static org.sonar.server.ws.WsUtils.checkRequest;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_ROOTS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_UUIDS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_IN_LAST;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SINCE_LEAK_PERIOD;
+
import java.time.Clock;
import java.time.OffsetDateTime;
import java.time.Period;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
+
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
+
import org.apache.commons.lang.BooleanUtils;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.rule.RuleKey;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.SnapshotDto;
import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.issue.IssueQuery.PeriodStart;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.client.issue.SearchWsRequest;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.Lists.newArrayList;
-import static java.lang.String.format;
-import static java.util.Collections.singleton;
-import static java.util.Collections.singletonList;
-import static org.sonar.api.utils.DateUtils.longToDate;
-import static org.sonar.api.utils.DateUtils.parseDateOrDateTime;
-import static org.sonar.api.utils.DateUtils.parseEndingDateOrDateTime;
-import static org.sonar.api.utils.DateUtils.parseStartingDateOrDateTime;
-import static org.sonar.core.util.stream.MoreCollectors.toHashSet;
-import static org.sonar.core.util.stream.MoreCollectors.toList;
-import static org.sonar.core.util.stream.MoreCollectors.toSet;
-import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
-import static org.sonar.server.ws.WsUtils.checkRequest;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_ROOTS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_UUIDS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_IN_LAST;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SINCE_LEAK_PERIOD;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Lists;
/**
* This component is used to create an IssueQuery, in order to transform the component and component roots keys into uuid.
boolean effectiveOnComponentOnly = mergeDeprecatedComponentParameters(dbSession, request, allComponents);
addComponentParameters(builder, dbSession, effectiveOnComponentOnly, allComponents, request);
- builder.createdAfter(buildCreatedAfterFromRequest(dbSession, request, allComponents));
+ setCreatedAfterFromRequest(dbSession, builder, request, allComponents);
String sort = request.getSort();
if (!Strings.isNullOrEmpty(sort)) {
builder.sort(sort);
}
}
- @CheckForNull
- private Date buildCreatedAfterFromDates(@Nullable Date createdAfter, @Nullable String createdInLast) {
+ private void setCreatedAfterFromDates(IssueQuery.Builder builder, @Nullable Date createdAfter, @Nullable String createdInLast, boolean createdAfterInclusive) {
checkArgument(createdAfter == null || createdInLast == null, format("Parameters %s and %s cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_CREATED_IN_LAST));
Date actualCreatedAfter = createdAfter;
.minus(Period.parse("P" + createdInLast.toUpperCase(Locale.ENGLISH)))
.toInstant());
}
- return actualCreatedAfter;
+ builder.createdAfter(actualCreatedAfter, createdAfterInclusive);
}
@CheckForNull
return organization.map(OrganizationDto::getUuid).orElse(UNKNOWN);
}
- private Date buildCreatedAfterFromRequest(DbSession dbSession, SearchWsRequest request, List<ComponentDto> componentUuids) {
+ private void setCreatedAfterFromRequest(DbSession dbSession, IssueQuery.Builder builder, SearchWsRequest request, List<ComponentDto> componentUuids) {
Date createdAfter = parseStartingDateOrDateTime(request.getCreatedAfter());
String createdInLast = request.getCreatedInLast();
if (request.getSinceLeakPeriod() == null || !request.getSinceLeakPeriod()) {
- return buildCreatedAfterFromDates(createdAfter, createdInLast);
+ setCreatedAfterFromDates(builder, createdAfter, createdInLast, true);
+ } else {
+ checkRequest(createdAfter == null, "Parameters '%s' and '%s' cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_SINCE_LEAK_PERIOD);
+ checkArgument(componentUuids.size() == 1, "One and only one component must be provided when searching since leak period");
+ ComponentDto component = componentUuids.iterator().next();
+ Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(dbSession, component);
+ setCreatedAfterFromDates(builder, createdAfterFromSnapshot, createdInLast, false);
}
-
- checkRequest(createdAfter == null, "Parameters '%s' and '%s' cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_SINCE_LEAK_PERIOD);
- checkArgument(componentUuids.size() == 1, "One and only one component must be provided when searching since leak period");
- ComponentDto component = componentUuids.iterator().next();
- Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(dbSession, component);
- return buildCreatedAfterFromDates(createdAfterFromSnapshot, createdInLast);
}
@CheckForNull
.flatMap(app -> dbClient.componentDao().selectProjectsFromView(dbSession, app, app).stream())
.collect(toSet());
- Map<String, Date> leakByProjects = dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids)
+ Map<String, PeriodStart> leakByProjects = dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids)
.stream()
.filter(s -> s.getPeriodDate() != null)
- .collect(uniqueIndex(SnapshotDto::getComponentUuid, s -> longToDate(s.getPeriodDate())));
+ .collect(uniqueIndex(SnapshotDto::getComponentUuid, s -> new PeriodStart(longToDate(s.getPeriodDate()), false)));
builder.createdAfterByProjectUuids(leakByProjects);
}
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.indices.TermsLookup;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.sonar.server.es.Sorting;
import org.sonar.server.es.StickyFacetBuilder;
import org.sonar.server.issue.IssueQuery;
+import org.sonar.server.issue.IssueQuery.PeriodStart;
import org.sonar.server.permission.index.AuthorizationTypeSupport;
import org.sonar.server.user.UserSession;
import org.sonar.server.view.index.ViewIndexDefinition;
}
private void addDatesFilter(Map<String, QueryBuilder> filters, IssueQuery query) {
- Date createdAfter = query.createdAfter();
+ PeriodStart createdAfter = query.createdAfter();
Date createdBefore = query.createdBefore();
- validateCreationDateBounds(createdBefore, createdAfter);
+ validateCreationDateBounds(createdBefore, createdAfter != null ? createdAfter.date() : null);
if (createdAfter != null) {
filters.put("__createdAfter", QueryBuilders
.rangeQuery(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT)
- .gte(BaseDoc.dateToEpochSeconds(createdAfter)));
+ .from(BaseDoc.dateToEpochSeconds(createdAfter.date()), createdAfter.inclusive()));
}
if (createdBefore != null) {
filters.put("__createdBefore", QueryBuilders
}
private static void addCreatedAfterByProjectsFilter(Map<String, QueryBuilder> filters, IssueQuery query) {
- Map<String, Date> createdAfterByProjectUuids = query.createdAfterByProjectUuids();
+ Map<String, PeriodStart> createdAfterByProjectUuids = query.createdAfterByProjectUuids();
BoolQueryBuilder boolQueryBuilder = boolQuery();
createdAfterByProjectUuids.forEach((projectUuid, createdAfterDate) -> boolQueryBuilder.should(boolQuery()
.filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, projectUuid))
- .filter(rangeQuery(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT).gte(BaseDoc.dateToEpochSeconds(createdAfterDate)))));
+ .filter(rangeQuery(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT).from(BaseDoc.dateToEpochSeconds(createdAfterDate.date()), createdAfterDate.inclusive()))));
filters.put("createdAfterByProjectUuids", boolQueryBuilder);
}
private Optional<AggregationBuilder> getCreatedAtFacet(IssueQuery query, Map<String, QueryBuilder> filters, QueryBuilder esQuery) {
long startTime;
- Date createdAfter = query.createdAfter();
+ boolean startInclusive;
+ PeriodStart createdAfter = query.createdAfter();
if (createdAfter == null) {
Optional<Long> minDate = getMinCreatedAt(filters, esQuery);
if (!minDate.isPresent()) {
return Optional.empty();
}
startTime = minDate.get();
+ startInclusive = true;
} else {
- startTime = createdAfter.getTime();
+ startTime = createdAfter.date().getTime();
+ startInclusive = createdAfter.inclusive();
}
Date createdBefore = query.createdBefore();
long endTime = createdBefore == null ? system.now() : createdBefore.getTime();
.format(DateUtils.DATETIME_FORMAT)
.timeZone(DateTimeZone.forOffsetMillis(system.getDefaultTimeZone().getRawOffset()))
// ES dateHistogram bounds are inclusive while createdBefore parameter is exclusive
- .extendedBounds(new ExtendedBounds(startTime, endTime - 1L));
+ .extendedBounds(new ExtendedBounds(startInclusive ? startTime : startTime + 1, endTime - 1L));
addEffortAggregationIfNeeded(query, dateHistogram);
return Optional.of(dateHistogram);
}
import java.time.Clock;
import java.time.ZoneOffset;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
import org.junit.Rule;
import org.junit.Test;
assertThat(query.assigned()).isTrue();
assertThat(query.rules()).hasSize(2);
assertThat(query.directories()).containsOnly("aDirPath");
- assertThat(query.createdAfter()).isEqualTo(DateUtils.parseDateTime("2013-04-16T09:08:24+0200"));
+ assertThat(query.createdAfter().date()).isEqualTo(DateUtils.parseDateTime("2013-04-16T09:08:24+0200"));
+ assertThat(query.createdAfter().inclusive()).isTrue();
assertThat(query.createdBefore()).isEqualTo(DateUtils.parseDateTime("2013-04-17T09:08:24+0200"));
assertThat(query.sort()).isEqualTo(IssueQuery.SORT_BY_CREATION_DATE);
assertThat(query.asc()).isTrue();
}
+ @Test
+ public void leak_period_start_date_is_exclusive() {
+ long leakPeriodStart = addDays(new Date(), -14).getTime();
+
+ ComponentDto project = db.components().insertPublicProject();
+ ComponentDto file = db.components().insertComponent(newFileDto(project));
+
+ SnapshotDto analysis = db.components().insertSnapshot(project, s -> s.setPeriodDate(leakPeriodStart));
+
+ SearchWsRequest request = new SearchWsRequest()
+ .setComponentUuids(Collections.singletonList(file.uuid()))
+ .setOnComponentOnly(true)
+ .setSinceLeakPeriod(true);
+
+ IssueQuery query = underTest.create(request);
+
+ assertThat(query.componentUuids()).containsOnly(file.uuid());
+ assertThat(query.createdAfter().date()).isEqualTo(new Date(leakPeriodStart));
+ assertThat(query.createdAfter().inclusive()).isFalse();
+
+ }
+
@Test
public void dates_are_inclusive() {
SearchWsRequest request = new SearchWsRequest()
IssueQuery query = underTest.create(request);
- assertThat(query.createdAfter()).isEqualTo(DateUtils.parseDate("2013-04-16"));
+ assertThat(query.createdAfter().date()).isEqualTo(DateUtils.parseDate("2013-04-16"));
+ assertThat(query.createdAfter().inclusive()).isTrue();
assertThat(query.createdBefore()).isEqualTo(DateUtils.parseDate("2013-04-18"));
}
.setComponentUuids(singletonList(application.uuid()))
.setSinceLeakPeriod(true));
- assertThat(result.createdAfterByProjectUuids()).containsOnly(
- entry(project1.uuid(), new Date(analysis1.getPeriodDate())));
+ assertThat(result.createdAfterByProjectUuids()).hasSize(1);
+ assertThat(result.createdAfterByProjectUuids().get(project1.uuid()).date().getTime()).isEqualTo(analysis1.getPeriodDate());
+ assertThat(result.createdAfterByProjectUuids().get(project1.uuid()).inclusive()).isFalse();
assertThat(result.viewUuids()).containsExactlyInAnyOrder(application.uuid());
}
when(clock.getZone()).thenReturn(ZoneOffset.UTC);
SearchWsRequest request = new SearchWsRequest()
.setCreatedInLast("1y2m3w4d");
- assertThat(underTest.create(request).createdAfter()).isEqualTo(DateUtils.parseDateTime("2012-04-30T07:35:00+0100"));
+ assertThat(underTest.create(request).createdAfter().date()).isEqualTo(DateUtils.parseDateTime("2012-04-30T07:35:00+0100"));
+ assertThat(underTest.create(request).createdAfter().inclusive()).isTrue();
+
}
@Test
import org.sonar.api.issue.Issue;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
+import org.sonar.server.issue.IssueQuery.PeriodStart;
import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;
@Test
public void build_query() {
+ PeriodStart filterDate = new IssueQuery.PeriodStart(new Date(10_000_000_000L),false);
IssueQuery query = IssueQuery.builder()
.issueKeys(newArrayList("ABCDE"))
.severities(newArrayList(Severity.BLOCKER))
.types(newArrayList("RELIABILITY", "SECURITY"))
.organizationUuid("orga")
.branchUuid("my_branch")
- .createdAfterByProjectUuids(ImmutableMap.of("PROJECT", new Date(10_000_000_000L)))
+ .createdAfterByProjectUuids(ImmutableMap.of("PROJECT", filterDate))
.assigned(true)
.createdAfter(new Date())
.createdBefore(new Date())
assertThat(query.types()).containsOnly("RELIABILITY", "SECURITY");
assertThat(query.organizationUuid()).isEqualTo("orga");
assertThat(query.branchUuid()).isEqualTo("my_branch");
- assertThat(query.createdAfterByProjectUuids()).containsOnly(entry("PROJECT", new Date(10_000_000_000L)));
+ assertThat(query.createdAfterByProjectUuids()).containsOnly(entry("PROJECT", filterDate));
assertThat(query.assigned()).isTrue();
assertThat(query.rules()).containsOnly(RuleKey.of("squid", "AvoidCycle"));
assertThat(query.createdAfter()).isNotNull();
// Search for issues of project 1 having less than 15 days
assertThatSearchReturnsOnly(IssueQuery.builder()
- .createdAfterByProjectUuids(ImmutableMap.of(project1.uuid(), addDays(now, -15))),
+ .createdAfterByProjectUuids(ImmutableMap.of(project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -15), true))),
project1Issue1.key());
// Search for issues of project 1 having less than 14 days and project 2 having less then 25 days
assertThatSearchReturnsOnly(IssueQuery.builder()
.createdAfterByProjectUuids(ImmutableMap.of(
- project1.uuid(), addDays(now, -14),
- project2.uuid(), addDays(now, -25))),
+ project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -14), true),
+ project2.uuid(), new IssueQuery.PeriodStart(addDays(now, -25), true))),
project1Issue1.key(), project2Issue1.key());
// Search for issues of project 1 having less than 30 days
assertThatSearchReturnsOnly(IssueQuery.builder()
.createdAfterByProjectUuids(ImmutableMap.of(
- project1.uuid(), addDays(now, -30))),
+ project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -30), true))),
project1Issue1.key(), project1Issue2.key());
// Search for issues of project 1 and project 2 having less than 5 days
assertThatSearchReturnsOnly(IssueQuery.builder()
.createdAfterByProjectUuids(ImmutableMap.of(
- project1.uuid(), addDays(now, -5),
- project2.uuid(), addDays(now, -5))));
+ project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -5), true),
+ project2.uuid(), new IssueQuery.PeriodStart(addDays(now, -5), true))));
}
@Test