import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.groups.Tuple.tuple;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.server.es.Indexers.BranchEvent.DELETION;
import static org.sonar.server.es.Indexers.EntityEvent.PROJECT_KEY_UPDATE;
assertThatIndexHasSize(3);
- IndexingResult result = indexBranch(branch1.getUuid(), DELETION);
+ IndexingResult result = indexBranches(List.of(branch1.getUuid()), DELETION);
assertThat(result.getTotal()).isEqualTo(2);
assertThat(result.getSuccess()).isEqualTo(2);
es.lockWrites(TYPE_ISSUE);
- IndexingResult result = indexBranch(project.uuid(), DELETION);
+ IndexingResult result = indexBranches(List.of(project.uuid()), DELETION);
assertThat(result.getTotal()).isEqualTo(2L);
assertThat(result.getFailures()).isEqualTo(2L);
assertThatEsQueueTableHasSize(0);
}
- private IndexingResult indexBranch(String branchUuid, Indexers.BranchEvent cause) {
- Collection<EsQueueDto> items = underTest.prepareForRecoveryOnBranchEvent(db.getSession(), singletonList(branchUuid), cause);
+ private IndexingResult indexBranches(List<String> branchUuids, Indexers.BranchEvent cause) {
+ Collection<EsQueueDto> items = underTest.prepareForRecoveryOnBranchEvent(db.getSession(), branchUuids, cause);
db.commit();
return underTest.index(db.getSession(), items);
}
@Test
public void deleteByKeys_shouldNotRecoverFromErrors() {
- addIssueToIndex("P1", "B1","Issue1");
+ addIssueToIndex("P1", "B1", "Issue1");
es.lockWrites(TYPE_ISSUE);
List<String> issues = List.of("Issue1");
assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
}
+ @Test
+ public void indexIssue_whenSwitchMainBranch_shouldIndexIsMainBranch() {
+ RuleDto rule = db.rules().insert();
+ ProjectData projectData = db.components().insertPrivateProject();
+ BranchDto mainBranchDto = projectData.getMainBranchDto();
+ ComponentDto mainBranchComponent = projectData.getMainBranchComponent();
+ BranchDto newMainBranchDto = db.components().insertProjectBranch(projectData.getProjectDto(), b -> b.setKey("newMainBranch"));
+ ComponentDto newMainBranchComponent = db.components().getComponentDto(newMainBranchDto);
+ IssueDto issue1 = createIssue(rule, mainBranchComponent);
+ IssueDto issue2 = createIssue(rule, newMainBranchComponent);
+ underTest.indexAllIssues();
+ assertThat(es.getDocuments(TYPE_ISSUE, IssueDoc.class)).extracting(IssueDoc::branchUuid, IssueDoc::isMainBranch)
+ .containsExactlyInAnyOrder(tuple(issue1.getProjectUuid(), true), tuple(issue2.getProjectUuid(), false));
+
+ db.getDbClient().branchDao().updateIsMain(db.getSession(), projectData.getMainBranchDto().getUuid(), false);
+ db.getDbClient().branchDao().updateIsMain(db.getSession(), newMainBranchDto.getUuid(), true);
+ indexBranches(List.of(mainBranchDto.getUuid(), newMainBranchDto.getUuid()), Indexers.BranchEvent.SWITCH_OF_MAIN_BRANCH);
+
+ assertThat(es.getDocuments(TYPE_ISSUE, IssueDoc.class)).extracting(IssueDoc::branchUuid, IssueDoc::isMainBranch)
+ .containsExactlyInAnyOrder(tuple(issue1.getProjectUuid(), false), tuple(issue2.getProjectUuid(), true));
+ }
+
+ private IssueDto createIssue(RuleDto rule, ComponentDto branch) {
+ ComponentDto dir2 = db.components().insertComponent(ComponentTesting.newDirectory(branch, "src/main/java/foo"));
+ ComponentDto file2 = db.components().insertComponent(newFileDto(branch, dir2));
+ return db.issues().insert(rule, branch, file2);
+ }
+
@Test
public void issue_on_test_file_has_test_scope() {
RuleDto rule = db.rules().insert();
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.junit.Rule;
import org.junit.Test;
+import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.utils.System2;
import org.sonar.db.DbSession;
import org.sonar.db.component.ProjectData;
import org.sonar.db.component.SnapshotDto;
import org.sonar.db.es.EsQueueDto;
+import org.sonar.db.metric.MetricDto;
import org.sonar.db.project.ProjectDto;
import org.sonar.server.es.EsTester;
import org.sonar.server.es.Indexers;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.nestedQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
import static org.sonar.server.es.Indexers.EntityEvent.DELETION;
import static org.sonar.server.es.Indexers.EntityEvent.PROJECT_KEY_UPDATE;
import static org.sonar.server.es.Indexers.EntityEvent.PROJECT_TAGS_UPDATE;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_MEASURES_MEASURE_KEY;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_MEASURES_MEASURE_VALUE;
import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_QUALIFIER;
import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_TAGS;
import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_UUID;
assertThat(result.getSuccess()).isOne();
}
+ @Test
+ public void prepareForRecoveryOnEntityEvent_shouldReindexProject_whenSwitchMainBranch() {
+ ProjectData projectData = db.components().insertPrivateProject(defaults(), p -> p.setTagsString("foo"));
+ ProjectDto project = projectData.getProjectDto();
+ BranchDto oldMainBranchDto = projectData.getMainBranchDto();
+ BranchDto newMainBranchDto = db.components().insertProjectBranch(project);
+ MetricDto nloc = db.measures().insertMetric(m -> m.setKey(CoreMetrics.NCLOC_KEY));
+ db.measures().insertLiveMeasure(oldMainBranchDto, nloc, e -> e.setValue(1d));
+ db.measures().insertLiveMeasure(newMainBranchDto, nloc, e -> e.setValue(2d));
+ indexProject(project, CREATION);
+ assertThatProjectHasMeasure(project, CoreMetrics.NCLOC_KEY, 1d);
+
+ db.getDbClient().branchDao().updateIsMain(db.getSession(), oldMainBranchDto.getUuid(), false);
+ db.getDbClient().branchDao().updateIsMain(db.getSession(), newMainBranchDto.getUuid(), true);
+ IndexingResult result = indexBranches(List.of(oldMainBranchDto, newMainBranchDto), Indexers.BranchEvent.SWITCH_OF_MAIN_BRANCH);
+
+ assertThatProjectHasMeasure(project, CoreMetrics.NCLOC_KEY, 2d);
+ assertThat(result.getTotal()).isOne();
+ assertThat(result.getSuccess()).isOne();
+ }
+
@Test
public void delete_doc_from_index_when_project_is_deleted() {
ProjectDto project = db.components().insertPrivateProject().getProjectDto();
return underTest.index(dbSession, items);
}
+ private IndexingResult indexBranches(List<BranchDto> branches, Indexers.BranchEvent cause) {
+ DbSession dbSession = db.getSession();
+ Collection<EsQueueDto> items = underTest.prepareForRecoveryOnBranchEvent(dbSession, branches.stream().map(BranchDto::getUuid).collect(Collectors.toSet()), cause);
+ dbSession.commit();
+ return underTest.index(dbSession, items);
+ }
+
private void assertThatProjectHasTag(ProjectDto project, String expectedTag) {
SearchRequest request = prepareSearch(TYPE_PROJECT_MEASURES.getMainType())
.source(new SearchSourceBuilder()
.contains(project.getUuid());
}
+ private void assertThatProjectHasMeasure(ProjectDto project, String metric, Double value) {
+ SearchRequest request = prepareSearch(TYPE_PROJECT_MEASURES.getMainType())
+ .source(new SearchSourceBuilder()
+ .query(nestedQuery(
+ FIELD_MEASURES,
+ boolQuery()
+ .filter(termQuery(FIELD_MEASURES_MEASURE_KEY, metric))
+ .filter(termQuery(FIELD_MEASURES_MEASURE_VALUE, value)),
+ ScoreMode.Avg
+ )));
+
+ assertThat(es.client().search(request).getHits().getHits())
+ .extracting(SearchHit::getId)
+ .contains(project.getUuid());
+ }
+
private void assertThatEsQueueTableHasSize(int expectedSize) {
assertThat(db.countRowsOfTable("es_queue")).isEqualTo(expectedSize);
}
enum BranchEvent {
// Note that when a project/app is deleted, no events are sent for each branch removed as part of that process
DELETION,
- MEASURE_CHANGE
+ MEASURE_CHANGE,
+ SWITCH_OF_MAIN_BRANCH
}
/**
// Measures, permissions, project key and tags are not used in type issues/issue
emptyList();
- case DELETION -> {
+ case DELETION, SWITCH_OF_MAIN_BRANCH -> {
+ //switch of main branch requires to reindex the project issues
List<EsQueueDto> items = createBranchRecoveryItems(branchUuids);
yield dbClient.esQueueDao().insert(dbSession, items);
}
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
+import org.jetbrains.annotations.NotNull;
import org.sonar.api.resources.Qualifiers;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
public Collection<EsQueueDto> prepareForRecoveryOnBranchEvent(DbSession dbSession, Collection<String> branchUuids, Indexers.BranchEvent cause) {
return switch (cause) {
case DELETION -> Collections.emptyList();
- case MEASURE_CHANGE -> {
- // when MEASURE_CHANGE or PROJECT_KEY_UPDATE project must be re-indexed because key is used in this index
- Set<String> entityUuids = dbClient.branchDao().selectByUuids(dbSession, branchUuids)
- .stream().map(BranchDto::getProjectUuid)
- .collect(Collectors.toSet());
- yield prepareForRecovery(dbSession, entityUuids);
+ case MEASURE_CHANGE, SWITCH_OF_MAIN_BRANCH -> {
+ Set<String> projectUuids = retrieveProjectUuidsFromBranchUuids(dbSession, branchUuids);
+ yield prepareForRecovery(dbSession, projectUuids);
}
};
}
+ @NotNull
+ private Set<String> retrieveProjectUuidsFromBranchUuids(DbSession dbSession, Collection<String> branchUuids) {
+ return dbClient.branchDao().selectByUuids(dbSession, branchUuids)
+ .stream().map(BranchDto::getProjectUuid)
+ .collect(Collectors.toSet());
+ }
+
private Collection<EsQueueDto> prepareForRecovery(DbSession dbSession, Collection<String> entityUuids) {
List<EsQueueDto> items = entityUuids.stream()
.map(entityUuid -> EsQueueDto.create(TYPE_PROJECT_MEASURES.format(), entityUuid, null, entityUuid))
*/
package org.sonar.server.branch.ws;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.junit.Rule;
import org.sonar.db.component.ComponentTesting;
import org.sonar.db.component.ProjectData;
import org.sonar.db.project.ProjectDto;
+import org.sonar.server.es.Indexers;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.sonar.server.branch.ws.ProjectBranchesParameters.ACTION_SET_MAIN_BRANCH;
@Rule
public LogTester logTester = new LogTester().setLevel(Level.INFO);
ProjectLifeCycleListeners projectLifeCycleListeners = mock(ProjectLifeCycleListeners.class);
- private WsActionTester tester = new WsActionTester(new SetMainBranchAction(db.getDbClient(), userSession, projectLifeCycleListeners));
+
+ private final Indexers indexers = mock(Indexers.class);
+ private WsActionTester tester = new WsActionTester(new SetMainBranchAction(db.getDbClient(), userSession, projectLifeCycleListeners, indexers));
@Test
public void testDefinition() {
.setParam(PARAM_BRANCH, newMainBranch.getKey()).execute();
checkCallToProjectLifeCycleListenersOnProjectBranchesChanges(projectData.getProjectDto());
+ verify(indexers).commitAndIndexBranches(any(), eq(List.of(projectData.getMainBranchDto(), newMainBranch)), eq(Indexers.BranchEvent.SWITCH_OF_MAIN_BRANCH));
checkNewMainBranch(projectData.projectUuid(), newMainBranch.getUuid());
checkPreviousMainBranch(projectData);
assertThat(logTester.logs(Level.INFO))
*/
package org.sonar.server.branch.ws;
+import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.db.DbSession;
import org.sonar.db.component.BranchDto;
import org.sonar.db.project.ProjectDto;
+import org.sonar.server.es.Indexers;
import org.sonar.server.project.Project;
import org.sonar.server.project.ProjectLifeCycleListeners;
import org.sonar.server.user.UserSession;
private final DbClient dbClient;
private final UserSession userSession;
private final ProjectLifeCycleListeners projectLifeCycleListeners;
+ private final Indexers indexers;
- public SetMainBranchAction(DbClient dbClient, UserSession userSession, ProjectLifeCycleListeners projectLifeCycleListeners) {
+ public SetMainBranchAction(DbClient dbClient, UserSession userSession, ProjectLifeCycleListeners projectLifeCycleListeners, Indexers indexers) {
this.dbClient = dbClient;
this.userSession = userSession;
this.projectLifeCycleListeners = projectLifeCycleListeners;
+ this.indexers = indexers;
}
@Override
}
configureProjectWithNewMainBranch(dbSession, projectDto.getKey(), oldMainBranch, newMainBranch);
refreshApplicationsAndPortfoliosComputedByProject(projectDto);
- // todo : refresh elasticSearchIndexes
+ indexers.commitAndIndexBranches(dbSession, List.of(oldMainBranch, newMainBranch), Indexers.BranchEvent.SWITCH_OF_MAIN_BRANCH);
dbSession.commit();
response.noContent();