import static org.sonar.api.server.ws.WebService.Param.PAGE;
import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
import static org.sonar.api.utils.DateUtils.formatDateTime;
+import static org.sonar.api.utils.DateUtils.longToDate;
import static org.sonar.api.utils.Paging.forPageIndex;
import static org.sonar.core.util.stream.MoreCollectors.toList;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
private static final String PARAM_HOTSPOTS = "hotspots";
private static final String PARAM_BRANCH = "branch";
private static final String PARAM_PULL_REQUEST = "pullRequest";
+ private static final String PARAM_SINCE_LEAK_PERIOD = "sinceLeakPeriod";
private final DbClient dbClient;
private final UserSession userSession;
PARAM_PROJECT_KEY, STATUS_REVIEWED))
.setPossibleValues(RESOLUTION_FIXED, RESOLUTION_SAFE)
.setRequired(false);
+ action.createParam(PARAM_SINCE_LEAK_PERIOD)
+ .setDescription("If '%s' is provided, only Security Hotspots created since the leak period are returned.")
+ .setBooleanPossibleValues()
+ .setDefaultValue("false");
// FIXME add response example and test it
// action.setResponseExample()
}
request.mandatoryParamAsInt(PAGE), request.mandatoryParamAsInt(PAGE_SIZE),
request.param(PARAM_PROJECT_KEY), request.param(PARAM_BRANCH), request.param(PARAM_PULL_REQUEST),
hotspotKeys,
- request.param(PARAM_STATUS), request.param(PARAM_RESOLUTION));
+ request.param(PARAM_STATUS), request.param(PARAM_RESOLUTION),
+ request.paramAsBoolean(PARAM_SINCE_LEAK_PERIOD));
}
private static void validateParameters(WsRequest wsRequest) {
}
private SearchResponseData searchHotspots(WsRequest wsRequest, DbSession dbSession, Optional<ComponentDto> project, Set<String> hotspotKeys) {
- SearchResponse result = doIndexSearch(wsRequest, project, hotspotKeys);
+ SearchResponse result = doIndexSearch(wsRequest, dbSession, project, hotspotKeys);
List<String> issueKeys = Arrays.stream(result.getHits().getHits())
.map(SearchHit::getId)
.collect(toList(result.getHits().getHits().length));
.collect(Collectors.toList());
}
- private SearchResponse doIndexSearch(WsRequest wsRequest, Optional<ComponentDto> project, Set<String> hotspotKeys) {
+ private SearchResponse doIndexSearch(WsRequest wsRequest, DbSession dbSession, Optional<ComponentDto> project, Set<String> hotspotKeys) {
IssueQuery.Builder builder = IssueQuery.builder()
.types(singleton(RuleType.SECURITY_HOTSPOT.name()))
.sort(IssueQuery.SORT_HOTSPOTS)
builder.branchUuid(p.projectUuid());
builder.mainBranch(false);
}
+ if (wsRequest.isSinceLeakPeriod()) {
+ dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, p.uuid())
+ .map(s -> longToDate(s.getPeriodDate()))
+ .ifPresent(d -> builder.createdAfter(d, false));
+ }
});
if (!hotspotKeys.isEmpty()) {
builder.issueKeys(hotspotKeys);
private final Set<String> hotspotKeys;
private final String status;
private final String resolution;
+ private final boolean sinceLeakPeriod;
private WsRequest(int page, int index,
@Nullable String projectKey, @Nullable String branch, @Nullable String pullRequest,
Set<String> hotspotKeys,
- @Nullable String status, @Nullable String resolution) {
+ @Nullable String status, @Nullable String resolution, @Nullable Boolean sinceLeakPeriod) {
this.page = page;
this.index = index;
this.projectKey = projectKey;
this.hotspotKeys = hotspotKeys;
this.status = status;
this.resolution = resolution;
+ this.sinceLeakPeriod = sinceLeakPeriod == null ? false : sinceLeakPeriod;
}
int getPage() {
Optional<String> getResolution() {
return ofNullable(resolution);
}
+
+ boolean isSinceLeakPeriod() {
+ return sinceLeakPeriod;
+ }
}
private static final class SearchResponseData {
import org.sonarqube.ws.Hotspots.SearchWsResponse;
import static java.util.Collections.singleton;
-import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
@RunWith(DataProviderRunner.class)
public class SearchActionTest {
private static final Random RANDOM = new Random();
+ private static final int ONE_MINUTE = 60_000;
@Rule
public DbTester dbTester = DbTester.create(System2.INSTANCE);
.containsExactlyInAnyOrder(selectedHotspots.stream().map(IssueDto::getKey).toArray(String[]::new));
}
+ @Test
+ public void returns_hotspots_on_the_leak_period_when_sinceLeakPeriod_is_true() {
+ ComponentDto project = dbTester.components().insertPublicProject();
+ userSessionRule.registerComponents(project);
+ indexPermissions();
+ ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+ long periodDate = 800_996_999_332L;
+ dbTester.components().insertSnapshot(project, t -> t.setPeriodDate(periodDate).setLast(false));
+ dbTester.components().insertSnapshot(project, t -> t.setPeriodDate(periodDate - 1_500).setLast(true));
+ RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+ List<IssueDto> hotspotsInLeakPeriod = IntStream.range(0, 1 + RANDOM.nextInt(20))
+ .mapToObj(i -> {
+ long issueCreationDate = periodDate + ONE_MINUTE + (RANDOM.nextInt(300) * ONE_MINUTE);
+ return newIssue(rule, project, file).setType(SECURITY_HOTSPOT).setLine(i).setIssueCreationTime(issueCreationDate);
+ })
+ .map(i -> dbTester.issues().insertIssue(i))
+ .collect(toList());
+ // included because
+ List<IssueDto> atLeakPeriod = IntStream.range(0, 1 + RANDOM.nextInt(20))
+ .mapToObj(i -> newIssue(rule, project, file).setType(SECURITY_HOTSPOT).setLine(i).setIssueCreationTime(periodDate))
+ .map(i -> dbTester.issues().insertIssue(i))
+ .collect(toList());
+ List<IssueDto> hotspotsBefore = IntStream.range(0, 1 + RANDOM.nextInt(20))
+ .mapToObj(i -> {
+ long issueCreationDate = periodDate - ONE_MINUTE - (RANDOM.nextInt(300) * ONE_MINUTE);
+ return newIssue(rule, project, file).setType(SECURITY_HOTSPOT).setLine(i).setIssueCreationTime(issueCreationDate);
+ })
+ .map(i -> dbTester.issues().insertIssue(i))
+ .collect(toList());
+ indexIssues();
+
+ SearchWsResponse responseAll = newRequest(project)
+ .executeProtobuf(SearchWsResponse.class);
+ assertThat(responseAll.getHotspotsList())
+ .extracting(SearchWsResponse.Hotspot::getKey)
+ .containsExactlyInAnyOrder(Stream.of(
+ hotspotsInLeakPeriod.stream(),
+ atLeakPeriod.stream(),
+ hotspotsBefore.stream())
+ .flatMap(t -> t)
+ .map(IssueDto::getKey)
+ .toArray(String[]::new));
+
+ SearchWsResponse responseOnLeak = newRequest(project,
+ t -> t.setParam("sinceLeakPeriod", "true"))
+ .executeProtobuf(SearchWsResponse.class);
+ assertThat(responseOnLeak.getHotspotsList())
+ .extracting(SearchWsResponse.Hotspot::getKey)
+ .containsExactlyInAnyOrder(Stream.concat(
+ hotspotsInLeakPeriod.stream(),
+ atLeakPeriod.stream())
+ .map(IssueDto::getKey)
+ .toArray(String[]::new));
+ }
+
private IssueDto insertHotspot(ComponentDto project, ComponentDto file, RuleDefinitionDto rule) {
return dbTester.issues().insert(rule, project, file, t -> t.setType(SECURITY_HOTSPOT));
}
return newRequest(project, null, null);
}
+ private TestRequest newRequest(ComponentDto project, Consumer<TestRequest> consumer) {
+ return newRequest(project, null, null, consumer);
+ }
+
private TestRequest newRequest(ComponentDto project, @Nullable String status, @Nullable String resolution) {
+ return newRequest(project, status, resolution, t -> {
+ });
+ }
+
+ private TestRequest newRequest(ComponentDto project, @Nullable String status, @Nullable String resolution, Consumer<TestRequest> consumer) {
TestRequest res = actionTester.newRequest()
.setParam("projectKey", project.getKey());
String branch = project.getBranch();
if (resolution != null) {
res.setParam("resolution", resolution);
}
+ consumer.accept(res);
return res;
}
private TestRequest newRequest(Collection<String> hotspotKeys) {
return actionTester.newRequest()
- .setParam("hotspots", hotspotKeys.stream().collect(joining(",")));
+ .setParam("hotspots", String.join(",", hotspotKeys));
}
private void indexPermissions() {