export function searchIssueTags(data: {
project?: string;
+ branch?: string;
+ all?: boolean;
ps?: number;
q?: string;
}): Promise<string[]> {
import { connect } from 'react-redux';
import { getGlobalSettingValue, Store } from '../../../store/rootReducer';
import { BranchLike } from '../../../types/branch-like';
+import { isBranch, isPullRequest } from '../../../helpers/branch-like';
import { ComponentQualifier, isApplication, isPortfolioLike } from '../../../types/component';
import {
Facet,
}
render() {
- const { component, createdAfterIncludesTime, facets, openFacets, query } = this.props;
+ const {
+ component,
+ createdAfterIncludesTime,
+ facets,
+ openFacets,
+ query,
+ branchLike
+ } = this.props;
+
+ const branch =
+ (isBranch(branchLike) && branchLike.name) ||
+ (isPullRequest(branchLike) && branchLike.branch) ||
+ undefined;
const displayProjectsFacet =
!component || !['TRK', 'BRC', 'DIR', 'DEV_PRJ'].includes(component.qualifier);
/>
<TagFacet
component={component}
+ branch={branch}
fetching={this.props.loadingFacets.tags === true}
loadSearchResultCount={this.props.loadSearchResultCount}
onChange={this.props.onFilterChange}
interface Props {
component: T.Component | undefined;
+ branch?: string;
fetching: boolean;
loadSearchResultCount: (property: string, changes: Partial<Query>) => Promise<Facet>;
onChange: (changes: Partial<Query>) => void;
export default class TagFacet extends React.PureComponent<Props> {
handleSearch = (query: string) => {
- const { component } = this.props;
+ const { component, branch } = this.props;
const project =
component && ['TRK', 'VW', 'APP'].includes(component.qualifier) ? component.key : undefined;
return searchIssueTags({
project,
+ branch,
ps: SEARCH_SIZE,
q: query
}).then(tags => ({ maxResults: tags.length === SEARCH_SIZE, results: tags }));
onSearch = (query: string) => {
return searchIssueTags({
+ all: true,
q: query,
ps: Math.min(this.props.selectedTags.length - 1 + LIST_SIZE, MAX_LIST_SIZE)
}).then(
if (Boolean.TRUE.equals(query.onComponentOnly())) {
return;
}
- allFilters.addFilter(
- "__is_main_branch", new SimpleFieldFilterScope(FIELD_ISSUE_IS_MAIN_BRANCH),
- createTermFilter(FIELD_ISSUE_IS_MAIN_BRANCH, Boolean.toString(query.isMainBranch())));
+ if (query.isMainBranch() != null) {
+ allFilters.addFilter(
+ "__is_main_branch", new SimpleFieldFilterScope(FIELD_ISSUE_IS_MAIN_BRANCH),
+ createTermFilter(FIELD_ISSUE_IS_MAIN_BRANCH, query.isMainBranch().toString()));
+ }
allFilters.addFilter(
FIELD_ISSUE_BRANCH_UUID, new SimpleFieldFilterScope(FIELD_ISSUE_BRANCH_UUID),
createTermFilter(FIELD_ISSUE_BRANCH_UUID, query.branchUuid()));
private final Boolean asc;
private final String facetMode;
private final String branchUuid;
- private final boolean mainBranch;
+ private final Boolean mainBranch;
private final ZoneId timeZone;
private IssueQuery(Builder builder) {
return branchUuid;
}
- public boolean isMainBranch() {
+ public Boolean isMainBranch() {
return mainBranch;
}
private Boolean asc = false;
private String facetMode;
private String branchUuid;
- private boolean mainBranch = true;
+ private Boolean mainBranch = true;
private ZoneId timeZone;
private Builder() {
return this;
}
- public Builder mainBranch(boolean mainBranch) {
+ public Builder mainBranch(@Nullable Boolean mainBranch) {
this.mainBranch = mainBranch;
return this;
}
assertThatSearchReturnsOnly(
IssueQuery.builder().componentUuids(singletonList(branch.uuid())).projectUuids(singletonList(project.uuid())).branchUuid(branch.uuid()).mainBranch(false),
issueOnBranch.key());
+ assertThatSearchReturnsOnly(IssueQuery.builder().projectUuids(singletonList(project.uuid())).mainBranch(null), issueOnProject.key(), issueOnBranch.key(), issueOnAnotherBranch.key());
assertThatSearchReturnsEmpty(IssueQuery.builder().branchUuid("unknown"));
}
*/
package org.sonar.server.issue.ws;
-import com.google.common.collect.ImmutableSet;
import com.google.common.io.Resources;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import javax.annotation.Nullable;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Scopes;
import org.sonar.api.server.ws.WebService.NewAction;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
+import org.sonar.db.component.BranchDto;
import org.sonar.db.component.ComponentDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.issue.index.IssueIndex;
import org.sonarqube.ws.Issues;
import static com.google.common.base.Preconditions.checkArgument;
-import static java.util.Optional.ofNullable;
import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY;
import static org.sonar.server.issue.index.IssueQueryFactory.ISSUE_TYPE_NAMES;
+import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
/**
* List issue tags matching a given query.
+ *
* @since 5.1
*/
public class TagsAction implements IssuesWsAction {
private static final String PARAM_PROJECT = "project";
+ private static final String PARAM_BRANCH = "branch";
+ private static final String PARAM_ALL = "all";
private final IssueIndex issueIndex;
private final IssueIndexSyncProgressChecker issueIndexSyncProgressChecker;
private final ComponentFinder componentFinder;
public TagsAction(IssueIndex issueIndex,
- IssueIndexSyncProgressChecker issueIndexSyncProgressChecker, DbClient dbClient,
- ComponentFinder componentFinder) {
+ IssueIndexSyncProgressChecker issueIndexSyncProgressChecker, DbClient dbClient,
+ ComponentFinder componentFinder) {
this.issueIndex = issueIndex;
this.issueIndexSyncProgressChecker = issueIndexSyncProgressChecker;
this.dbClient = dbClient;
.setRequired(false)
.setExampleValue(KEY_PROJECT_EXAMPLE_001)
.setSince("7.4");
+ action.createParam(PARAM_BRANCH)
+ .setDescription("Branch key")
+ .setRequired(false)
+ .setExampleValue(KEY_BRANCH_EXAMPLE_001)
+ .setSince("9.2");
+ action.createParam(PARAM_ALL)
+ .setDescription("Indicator to search for all tags or only for tags in the main branch of a project")
+ .setRequired(false)
+ .setDefaultValue(Boolean.FALSE)
+ .setPossibleValues(Boolean.TRUE, Boolean.FALSE)
+ .setSince("9.2");
}
@Override
public void handle(Request request, Response response) throws Exception {
try (DbSession dbSession = dbClient.openSession(false)) {
String projectKey = request.param(PARAM_PROJECT);
+ String branchKey = request.param(PARAM_BRANCH);
+ boolean all = request.mandatoryParamAsBoolean(PARAM_ALL);
checkIfAnyComponentsNeedIssueSync(dbSession, projectKey);
+
Optional<ComponentDto> project = getProject(dbSession, projectKey);
- List<String> tags = searchTags(project.orElse(null), request);
+ Optional<BranchDto> branch = project.flatMap(p -> dbClient.branchDao().selectByBranchKey(dbSession, p.uuid(), branchKey));
+ List<String> tags = searchTags(project.orElse(null), branch.orElse(null), request, all);
+
Issues.TagsResponse.Builder tagsResponseBuilder = Issues.TagsResponse.newBuilder();
tags.forEach(tagsResponseBuilder::addTags);
writeProtobuf(tagsResponseBuilder.build(), request, response);
}
}
- private List<String> searchTags(@Nullable ComponentDto project, Request request) {
+ private List<String> searchTags(@Nullable ComponentDto project, @Nullable BranchDto branch, Request request, boolean all) {
IssueQuery.Builder issueQueryBuilder = IssueQuery.builder()
.types(ISSUE_TYPE_NAMES);
- ofNullable(project).ifPresent(p -> {
- switch (p.qualifier()) {
+ if (project != null) {
+ switch (project.qualifier()) {
case Qualifiers.PROJECT:
- issueQueryBuilder.projectUuids(ImmutableSet.of(p.uuid()));
- return;
+ issueQueryBuilder.projectUuids(Set.of(project.uuid()));
+ break;
case Qualifiers.APP:
case Qualifiers.VIEW:
- issueQueryBuilder.viewUuids(ImmutableSet.of(p.uuid()));
- return;
+ issueQueryBuilder.viewUuids(Set.of(project.uuid()));
+ break;
default:
- throw new IllegalArgumentException(String.format("Component of type '%s' is not supported", p.qualifier()));
+ throw new IllegalArgumentException(String.format("Component of type '%s' is not supported", project.qualifier()));
}
- });
+
+ if (branch != null && !project.uuid().equals(branch.getUuid())) {
+ issueQueryBuilder.branchUuid(branch.getUuid());
+ issueQueryBuilder.mainBranch(false);
+ }
+ }
+ if (all) {
+ issueQueryBuilder.mainBranch(null);
+ }
+
return issueIndex.searchTags(
issueQueryBuilder.build(),
request.param(TEXT_QUERY),
verify(issueIndexSyncProgressChecker).checkIfComponentNeedIssueSync(any(), eq(project1.getKey()));
}
+ @Test
+ public void search_tags_by_branch_equals_main_branch() {
+ RuleDefinitionDto rule = db.rules().insertIssueRule();
+ ComponentDto project = db.components().insertPrivateProject();
+ ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
+ db.issues().insertIssue(rule, project, project, issue -> issue.setTags(asList("tag1", "tag2")));
+ db.issues().insertIssue(rule, branch, branch, issue -> issue.setTags(asList("tag12", "tag4", "tag5")));
+ indexIssues();
+ permissionIndexer.allowOnlyAnyone(project, branch);
+
+ assertThat(tagListOf(ws.newRequest()
+ .setParam("project", project.getKey())
+ .setParam("branch", project.uuid()))).containsExactly("tag1", "tag2");
+ }
+
+ @Test
+ public void search_tags_by_branch() {
+ RuleDefinitionDto rule = db.rules().insertIssueRule();
+ ComponentDto project = db.components().insertPrivateProject();
+ ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
+ db.issues().insertIssue(rule, project, project, issue -> issue.setTags(asList("tag1", "tag2")));
+ db.issues().insertIssue(rule, branch, branch, issue -> issue.setTags(asList("tag12", "tag4", "tag5")));
+ indexIssues();
+ permissionIndexer.allowOnlyAnyone(project, branch);
+
+ assertThat(tagListOf(ws.newRequest()
+ .setParam("project", project.getKey())
+ .setParam("branch", "my_branch"))).containsExactly("tag12", "tag4", "tag5");
+ }
+
+ @Test
+ public void search_tags_by_branch_not_exist_fall_back_to_main_branch() {
+ RuleDefinitionDto rule = db.rules().insertIssueRule();
+ ComponentDto project = db.components().insertPrivateProject();
+ ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
+ db.issues().insertIssue(rule, project, project, issue -> issue.setTags(asList("tag1", "tag2")));
+ db.issues().insertIssue(rule, branch, branch, issue -> issue.setTags(asList("tag12", "tag4", "tag5")));
+ indexIssues();
+ permissionIndexer.allowOnlyAnyone(project, branch);
+
+ assertThat(tagListOf(ws.newRequest()
+ .setParam("project", project.getKey())
+ .setParam("branch", "not_exist"))).containsExactly("tag1", "tag2");
+ }
+
+ @Test
+ public void search_all_tags_by_query() {
+ RuleDefinitionDto rule = db.rules().insertIssueRule();
+ ComponentDto project = db.components().insertPrivateProject();
+ ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
+ db.issues().insertIssue(rule, project, project, issue -> issue.setTags(asList("tag1", "tag2")));
+ db.issues().insertIssue(rule, branch, branch, issue -> issue.setTags(asList("tag12", "tag4", "tag5")));
+ indexIssues();
+ permissionIndexer.allowOnlyAnyone(project, branch);
+
+ assertThat(tagListOf(ws.newRequest()
+ .setParam("q", "tag1")
+ .setParam("all", "true"))).containsExactly("tag1", "tag12");
+ }
+
@Test
public void search_tags_by_project_ignores_hotspots() {
RuleDefinitionDto issueRule = db.rules().insertIssueRule();
.containsExactlyInAnyOrder(
tuple("q", null, null, false, false),
tuple("ps", "10", null, false, false),
- tuple("project", null, "7.4", false, false));
+ tuple("project", null, "7.4", false, false),
+ tuple("branch", null, "9.2", false, false),
+ tuple("all", "false", "9.2", false, false));
}
private static ProtocolStringList tagListOf(TestRequest testRequest) {