public void triggerOnIndexCreation() {
throw new IllegalStateException("Async issue indexing should not be triggered in Compute Engine");
}
+
+ @Override
+ public void triggerForProject(String projectUuid) {
+ throw new IllegalStateException("Async issue indexing should not be triggered in Compute Engine");
+ }
}
return mapper(dbSession).selectBranchNeedingIssueSync();
}
+ public List<BranchDto> selectBranchNeedingIssueSyncForProject(DbSession dbSession, String projectUuid) {
+ return mapper(dbSession).selectBranchNeedingIssueSyncForProject(projectUuid);
+ }
+
public long updateAllNeedIssueSync(DbSession dbSession) {
return mapper(dbSession).updateAllNeedIssueSync(system2.now());
}
+ public long updateAllNeedIssueSyncForProject(DbSession dbSession, String projectUuid) {
+ return mapper(dbSession).updateAllNeedIssueSyncForProject(projectUuid, system2.now());
+ }
+
public long updateNeedIssueSync(DbSession dbSession, String branchUuid, boolean needIssueSync) {
long now = system2.now();
return mapper(dbSession).updateNeedIssueSync(branchUuid, needIssueSync, now);
List<BranchDto> selectBranchNeedingIssueSync();
+ List<BranchDto> selectBranchNeedingIssueSyncForProject(@Param("projectUuid") String projectUuid);
+
long updateAllNeedIssueSync(@Param("now") long now);
+ long updateAllNeedIssueSyncForProject(@Param("projectUuid") String projectUuid, @Param("now") long now);
+
long updateNeedIssueSync(@Param("uuid") String uuid, @Param("needIssueSync")boolean needIssueSync,@Param("now") long now);
short doAnyOfComponentsNeedIssueSync(@Param("componentKeys") List<String> components);
order by pb.updated_at desc, uuid
</select>
+ <select id="selectBranchNeedingIssueSyncForProject" resultType="org.sonar.db.component.BranchDto">
+ select
+ <include refid="columns"/>
+ from project_branches pb
+ where need_issue_sync = ${_true} and project_uuid = #{projectUuid, jdbcType=VARCHAR}
+ order by pb.updated_at desc, uuid
+ </select>
+
<update id="updateAllNeedIssueSync">
update project_branches
set
updated_at = #{now, jdbcType=BIGINT}
</update>
+ <update id="updateAllNeedIssueSyncForProject">
+ update project_branches
+ set
+ need_issue_sync = ${_true},
+ updated_at = #{now, jdbcType=BIGINT}
+ where
+ project_uuid = #{projectUuid, jdbcType=VARCHAR}
+ </update>
+
<update id="updateNeedIssueSync">
update project_branches
set
assertThat(loadedPullRequestData.getBranch()).isEqualTo(branch);
assertThat(loadedPullRequestData.getTitle()).isEqualTo(title);
assertThat(loadedPullRequestData.getUrl()).isEqualTo(url);
- assertThat(loadedPullRequestData.getAttributesMap().get(tokenAttributeName)).isEqualTo(tokenAttributeValue);
+ assertThat(loadedPullRequestData.getAttributesMap()).containsEntry(tokenAttributeName, tokenAttributeValue);
}
@Test
assertThat(loadedPullRequestData.getBranch()).isEqualTo(branch);
assertThat(loadedPullRequestData.getTitle()).isEqualTo(title);
assertThat(loadedPullRequestData.getUrl()).isEqualTo(url);
- assertThat(loadedPullRequestData.getAttributesMap().get(tokenAttributeName)).isEqualTo(tokenAttributeValue);
+ assertThat(loadedPullRequestData.getAttributesMap()).containsEntry(tokenAttributeName, tokenAttributeValue);
}
@Test
assertThat(loadedPullRequestData.getBranch()).isEqualTo(branch);
assertThat(loadedPullRequestData.getTitle()).isEqualTo(title);
assertThat(loadedPullRequestData.getUrl()).isEqualTo(url);
- assertThat(loadedPullRequestData.getAttributesMap().get(tokenAttributeName)).isEqualTo(tokenAttributeValue);
+ assertThat(loadedPullRequestData.getAttributesMap()).containsEntry(tokenAttributeName, tokenAttributeValue);
}
@Test
.containsExactly(uuid);
}
+ @Test
+ public void selectBranchNeedingIssueSyncForProject() {
+ ComponentDto project = db.components().insertPrivateProject();
+ String uuid = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(true)).uuid();
+ db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(false));
+
+ assertThat(underTest.selectBranchNeedingIssueSyncForProject(dbSession, project.uuid()))
+ .extracting(BranchDto::getUuid)
+ .containsExactly(uuid);
+ }
+
@Test
public void updateAllNeedIssueSync() {
ComponentDto project = db.components().insertPrivateProject();
assertThat(project2.get().isNeedIssueSync()).isTrue();
}
+ @Test
+ public void updateAllNeedIssueSyncForProject() {
+ ComponentDto project = db.components().insertPrivateProject();
+ String uuid1 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(true)).uuid();
+ String uuid2 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(false)).uuid();
+
+ underTest.updateAllNeedIssueSyncForProject(dbSession, project.uuid());
+
+ Optional<BranchDto> project1 = underTest.selectByUuid(dbSession, uuid1);
+ assertThat(project1).isPresent();
+ assertThat(project1.get().isNeedIssueSync()).isTrue();
+
+ Optional<BranchDto> project2 = underTest.selectByUuid(dbSession, uuid2);
+ assertThat(project2).isPresent();
+ assertThat(project2.get().isNeedIssueSync()).isTrue();
+ }
+
@Test
public void updateNeedIssueSync() {
ComponentDto project = db.components().insertPrivateProject();
public interface AsyncIssueIndexing {
void triggerOnIndexCreation();
+ void triggerForProject(String projectUuid);
}
}
}
+ public void indexProject(String projectUuid) {
+ asyncIssueIndexing.triggerForProject(projectUuid);
+ }
+
@Override
public Collection<EsQueueDto> prepareForRecovery(DbSession dbSession, Collection<String> projectUuids, ProjectIndexer.Cause cause) {
switch (cause) {
}
}
+ @Override
+ public void triggerForProject(String projectUuid) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+
+ // remove already existing indexation task, if any
+ removeExistingIndexationTasksForProject(dbSession, projectUuid);
+
+ dbClient.branchDao().updateAllNeedIssueSyncForProject(dbSession, projectUuid);
+ List<BranchDto> branchInNeedOfIssueSync = dbClient.branchDao().selectBranchNeedingIssueSyncForProject(dbSession, projectUuid);
+ LOG.info("{} branch(es) found in need of issue sync for project.", branchInNeedOfIssueSync.size());
+
+ List<CeTaskSubmit> tasks = new ArrayList<>();
+ for (BranchDto branch : branchInNeedOfIssueSync) {
+ tasks.add(buildTaskSubmit(branch));
+ }
+
+ ceQueue.massSubmit(tasks);
+ dbSession.commit();
+ }
+ }
+
private void sortProjectUuids(DbSession dbSession, List<String> projectUuids) {
Map<String, SnapshotDto> snapshotByProjectUuid = dbClient.snapshotDao()
.selectLastAnalysesByRootComponentUuids(dbSession, projectUuids).stream()
}
private void removeExistingIndexationTasks(DbSession dbSession) {
- List<String> uuids = dbClient.ceQueueDao().selectAllInAscOrder(dbSession).stream()
+ removeIndexationTasks(dbSession, dbClient.ceQueueDao().selectAllInAscOrder(dbSession));
+ }
+
+ private void removeExistingIndexationTasksForProject(DbSession dbSession, String projectUuid) {
+ removeIndexationTasks(dbSession, dbClient.ceQueueDao().selectByMainComponentUuid(dbSession, projectUuid));
+ }
+
+ private void removeIndexationTasks(DbSession dbSession, List<CeQueueDto> ceQueueDtos) {
+ List<String> uuids = ceQueueDtos.stream()
.filter(p -> p.getTaskType().equals(BRANCH_ISSUE_SYNC))
.map(CeQueueDto::getUuid)
.collect(Collectors.toList());
+
LOG.info(String.format("%s pending indexation task found to be deleted...", uuids.size()));
for (String uuid : uuids) {
dbClient.ceQueueDao().deleteByUuid(dbSession, uuid);
package org.sonar.server.issue.index;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import org.sonar.db.ce.CeTaskCharacteristicDto;
import org.sonar.db.component.BranchDto;
import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.project.ProjectDto;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
@Rule
public LogTester logTester = new LogTester();
- private DbClient dbClient = dbTester.getDbClient();
- private CeQueue ceQueue = mock(CeQueue.class);
- private UuidFactory uuidFactory = new SequenceUuidFactory();
+ private final DbClient dbClient = dbTester.getDbClient();
+ private final CeQueue ceQueue = mock(CeQueue.class);
+ private final UuidFactory uuidFactory = new SequenceUuidFactory();
private final AsyncIssueIndexingImpl underTest = new AsyncIssueIndexingImpl(ceQueue, dbClient);
.contains("1 branch found in need of issue sync.");
}
+ @Test
+ public void triggerForProject() {
+ ProjectDto projectDto = dbTester.components().insertPrivateProjectDto();
+ BranchDto dto = new BranchDto()
+ .setBranchType(BRANCH)
+ .setKey("branchName")
+ .setUuid("branch_uuid")
+ .setProjectUuid(projectDto.getUuid());
+ dbTester.components().insertProjectBranch(projectDto, dto);
+
+ underTest.triggerForProject(projectDto.getUuid());
+
+ Optional<BranchDto> branch = dbClient.branchDao().selectByUuid(dbTester.getSession(), "branch_uuid");
+ assertThat(branch).isPresent();
+ assertThat(branch.get().isNeedIssueSync()).isTrue();
+ verify(ceQueue, times(2)).prepareSubmit();
+ verify(ceQueue, times(1)).massSubmit(anyCollection());
+ assertThat(logTester.logs(LoggerLevel.INFO))
+ .contains("2 branch(es) found in need of issue sync for project.");
+ }
+
@Test
public void triggerOnIndexCreation_no_branch() {
underTest.triggerOnIndexCreation();
assertThat(logTester.logs(LoggerLevel.INFO)).contains("0 branch found in need of issue sync.");
}
+ @Test
+ public void triggerForProject_no_branch() {
+ underTest.triggerForProject("some-random-uuid");
+ assertThat(logTester.logs(LoggerLevel.INFO)).contains("0 branch(es) found in need of issue sync for project.");
+ }
+
@Test
public void remove_existing_indexation_task() {
- CeQueueDto reportTask = new CeQueueDto();
- reportTask.setUuid("uuid_1");
- reportTask.setTaskType(REPORT);
- dbClient.ceQueueDao().insert(dbTester.getSession(), reportTask);
+ String reportTaskUuid = persistReportTasks();
- CeActivityDto reportActivity = new CeActivityDto(reportTask);
- reportActivity.setStatus(Status.SUCCESS);
- dbClient.ceActivityDao().insert(dbTester.getSession(), reportActivity);
CeQueueDto task = new CeQueueDto();
task.setUuid("uuid_2");
task.setTaskType(BRANCH_ISSUE_SYNC);
dbClient.ceQueueDao().insert(dbTester.getSession(), task);
-
CeActivityDto activityDto = new CeActivityDto(task);
activityDto.setStatus(Status.SUCCESS);
dbClient.ceActivityDao().insert(dbTester.getSession(), activityDto);
-
dbTester.commit();
underTest.triggerOnIndexCreation();
- assertThat(dbClient.ceQueueDao().selectAllInAscOrder(dbTester.getSession())).extracting("uuid")
- .containsExactly(reportTask.getUuid());
- assertThat(dbClient.ceActivityDao().selectByTaskType(dbTester.getSession(), BRANCH_ISSUE_SYNC)).isEmpty();
-
- assertThat(dbClient.ceActivityDao().selectByTaskType(dbTester.getSession(), REPORT)).hasSize(1);
-
- assertThat(dbClient.ceTaskCharacteristicsDao().selectByTaskUuids(dbTester.getSession(), new HashSet<>(Arrays.asList("uuid_2")))).isEmpty();
-
+ assertCeTasks(reportTaskUuid);
assertThat(logTester.logs(LoggerLevel.INFO))
.contains(
"1 pending indexation task found to be deleted...",
"Tasks characteristics deletion complete.");
}
+ @Test
+ public void remove_existing_indexation_for_project_task() {
+ String reportTaskUuid = persistReportTasks();
+
+ ProjectDto projectDto = dbTester.components().insertPrivateProjectDto();
+ String branchUuid = "branch_uuid";
+ dbTester.components().insertProjectBranch(projectDto, b -> b.setBranchType(BRANCH).setUuid(branchUuid));
+ CeQueueDto mainBranchTask = new CeQueueDto().setUuid("uuid_2").setTaskType(BRANCH_ISSUE_SYNC)
+ .setMainComponentUuid(projectDto.getUuid()).setComponentUuid(projectDto.getUuid());
+ CeQueueDto branchTask = new CeQueueDto().setUuid("uuid_3").setTaskType(BRANCH_ISSUE_SYNC)
+ .setMainComponentUuid(projectDto.getUuid()).setComponentUuid(branchUuid);
+ dbClient.ceQueueDao().insert(dbTester.getSession(), mainBranchTask);
+ dbClient.ceQueueDao().insert(dbTester.getSession(), branchTask);
+ dbTester.commit();
+
+ underTest.triggerForProject(projectDto.getUuid());
+
+ assertCeTasks(reportTaskUuid);
+ assertThat(logTester.logs(LoggerLevel.INFO))
+ .contains(
+ "2 pending indexation task found to be deleted...",
+ "2 completed indexation task found to be deleted...",
+ "Indexation task deletion complete.",
+ "Deleting tasks characteristics...",
+ "Tasks characteristics deletion complete.",
+ "Tasks characteristics deletion complete.",
+ "2 branch(es) found in need of issue sync for project.");
+ }
+
@Test
public void order_by_last_analysis_date() {
BranchDto dto = new BranchDto()
return snapshot;
}
+ private String persistReportTasks() {
+ CeQueueDto reportTask = new CeQueueDto();
+ reportTask.setUuid("uuid_1");
+ reportTask.setTaskType(REPORT);
+ dbClient.ceQueueDao().insert(dbTester.getSession(), reportTask);
+
+ CeActivityDto reportActivity = new CeActivityDto(reportTask);
+ reportActivity.setStatus(Status.SUCCESS);
+ dbClient.ceActivityDao().insert(dbTester.getSession(), reportActivity);
+ return reportTask.getUuid();
+ }
+
+ private void assertCeTasks(String reportTaskUuid) {
+ assertThat(dbClient.ceQueueDao().selectAllInAscOrder(dbTester.getSession())).extracting("uuid")
+ .containsExactly(reportTaskUuid);
+ assertThat(dbClient.ceActivityDao().selectByTaskType(dbTester.getSession(), BRANCH_ISSUE_SYNC)).isEmpty();
+ assertThat(dbClient.ceActivityDao().selectByTaskType(dbTester.getSession(), REPORT)).hasSize(1);
+ assertThat(dbClient.ceTaskCharacteristicsDao().selectByTaskUuids(dbTester.getSession(), new HashSet<>(List.of("uuid_2")))).isEmpty();
+ }
+
}
SetTagsAction.class,
SetTypeAction.class,
ComponentTagsAction.class,
+ ReindexAction.class,
AuthorsAction.class,
ChangelogAction.class,
BulkChangeAction.class,
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info 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.issue.ws;
+
+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.web.UserRole;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.issue.index.IssueIndexer;
+import org.sonar.server.user.UserSession;
+
+import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT;
+
+/**
+ * Implementation of the {@code reindex} action for the Issues WebService.
+ */
+public class ReindexAction implements IssuesWsAction {
+
+ private static final String ACTION = "reindex";
+ private final DbClient dbClient;
+ private final IssueIndexer issueIndexer;
+ private final UserSession userSession;
+
+ public ReindexAction(DbClient dbClient, IssueIndexer indexer, UserSession userSession) {
+ this.dbClient = dbClient;
+ this.issueIndexer = indexer;
+ this.userSession = userSession;
+ }
+
+ @Override
+ public void define(WebService.NewController context) {
+ WebService.NewAction action = context
+ .createAction(ACTION)
+ .setPost(true)
+ .setDescription("Reindex issues for a project.<br> " +
+ "Requires one of the following permissions: " +
+ "<ul>" +
+ "<li>'Administer System'</li>" +
+ "<li>'Administer' rights on the specified project</li>" +
+ "</ul>")
+ .setSince("9.8")
+ .setHandler(this);
+
+ action
+ .createParam(PARAM_PROJECT)
+ .setDescription("Project key")
+ .setRequired(true)
+ .setExampleValue(KEY_PROJECT_EXAMPLE_001);
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ String projectKey = request.mandatoryParam(PARAM_PROJECT);
+
+ ProjectDto projectDto;
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ projectDto = dbClient.projectDao().selectProjectByKey(dbSession, projectKey).orElseThrow(() -> new NotFoundException("project not found"));
+ userSession.checkProjectPermission(UserRole.ADMIN, projectDto);
+ }
+
+ issueIndexer.indexProject(projectDto.getUuid());
+ response.noContent();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info 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.issue.ws;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbTester;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.issue.index.AsyncIssueIndexing;
+import org.sonar.server.issue.index.IssueIndexer;
+import org.sonar.server.issue.index.IssueIteratorFactory;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.TestResponse;
+import org.sonar.server.ws.WsActionTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class ReindexActionTest {
+
+ @Rule
+ public EsTester es = EsTester.create();
+ @Rule
+ public DbTester db = DbTester.create();
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+
+ private final AsyncIssueIndexing mock = mock(AsyncIssueIndexing.class);
+ private final IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), mock);
+ private final ReindexAction underTest = new ReindexAction(db.getDbClient(), issueIndexer, userSession);
+ private final WsActionTester tester = new WsActionTester(underTest);
+
+ @Test
+ public void test_definition() {
+ WebService.Action action = tester.getDef();
+
+ assertThat(action.key()).isEqualTo("reindex");
+ assertThat(action.isPost()).isTrue();
+ assertThat(action.isInternal()).isFalse();
+ assertThat(action.params()).extracting(WebService.Param::key).containsExactly("project");
+ }
+
+ @Test
+ public void reindex_project() {
+ ProjectDto project = db.components().insertPrivateProjectDto();
+ userSession.logIn().setSystemAdministrator();
+ userSession.addProjectPermission(UserRole.ADMIN, project);
+
+ TestResponse response = tester.newRequest()
+ .setParam("project", project.getKey())
+ .execute();
+
+ assertThat(response.getStatus()).isEqualTo(204);
+ verify(mock, times(1)).triggerForProject(project.getUuid());
+ }
+
+ @Test
+ public void fail_if_project_does_not_exist() {
+ userSession.logIn().setSystemAdministrator();
+
+ TestRequest testRequest = tester.newRequest().setParam("project", "some-key");
+ assertThatThrownBy(testRequest::execute)
+ .isInstanceOf(NotFoundException.class)
+ .hasMessage("project not found");
+ }
+
+ @Test
+ public void fail_if_parameter_not_present() {
+ userSession.anonymous();
+ TestRequest testRequest = tester.newRequest();
+ assertThatThrownBy(testRequest::execute)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("The 'project' parameter is missing");
+ }
+
+ @Test
+ public void fail_if_not_authorized() {
+ ProjectDto project = db.components().insertPrivateProjectDto();
+ userSession.addProjectPermission(UserRole.USER, project);
+
+ TestRequest testRequest = tester.newRequest().setParam("project", project.getKey());
+ assertThatThrownBy(testRequest::execute)
+ .isInstanceOf(ForbiddenException.class)
+ .hasMessage("Insufficient privileges");
+ }
+
+}
.setMediaType(MediaTypes.JSON)).content();
}
+ /**
+ *
+ * This is part of the internal API.
+ * This is a POST request.
+ * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/issues/reindex">Further information about this action online (including a response example)</a>
+ * @since 9.8
+ */
+ public void reindex() {
+ call(
+ new PostRequest(path("reindex"))
+ .setMediaType(MediaTypes.JSON)).content();
+ }
+
/**
*
* This is part of the internal API.