]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15397 Fix Issue tags aren't filtered by branch
authorZipeng WU <zipeng.wu@sonarsource.com>
Thu, 4 Nov 2021 15:58:20 +0000 (16:58 +0100)
committersonartech <sonartech@sonarsource.com>
Fri, 5 Nov 2021 20:03:28 +0000 (20:03 +0000)
server/sonar-web/src/main/js/api/issues.ts
server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/TagFacet.tsx
server/sonar-web/src/main/js/components/issue/popups/SetIssueTagsPopup.tsx
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/TagsAction.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/TagsActionTest.java

index f1c5ff3b74bf4cdc4d72c6616b7e1a205bd7b26e..47f94bf9fd48f37e527a68521b428836b10dfec4 100644 (file)
@@ -75,6 +75,8 @@ export function getFacet(
 
 export function searchIssueTags(data: {
   project?: string;
+  branch?: string;
+  all?: boolean;
   ps?: number;
   q?: string;
 }): Promise<string[]> {
index 51cfb8fea755627634327ab2db6ee181b4a1c9ba..67666f9bc7781fb4454734567c1630b7e694745a 100644 (file)
@@ -21,6 +21,7 @@ import * as React from 'react';
 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,
@@ -105,7 +106,19 @@ export class Sidebar extends React.PureComponent<Props> {
   }
 
   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);
@@ -216,6 +229,7 @@ export class Sidebar extends React.PureComponent<Props> {
         />
         <TagFacet
           component={component}
+          branch={branch}
           fetching={this.props.loadingFacets.tags === true}
           loadSearchResultCount={this.props.loadSearchResultCount}
           onChange={this.props.onFilterChange}
index dd3cb484862fa762983b0a71b41c8cce5f1639f2..6c8f4af269c0425116204404ed2bef6764d060b1 100644 (file)
@@ -30,6 +30,7 @@ import { Query } from '../utils';
 
 interface Props {
   component: T.Component | undefined;
+  branch?: string;
   fetching: boolean;
   loadSearchResultCount: (property: string, changes: Partial<Query>) => Promise<Facet>;
   onChange: (changes: Partial<Query>) => void;
@@ -44,11 +45,12 @@ const SEARCH_SIZE = 100;
 
 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 }));
index b163ab90472217760721100864b8f549fd9d693d..79f79be5d49abe21224f11e630435cbed7028fd0 100644 (file)
@@ -50,6 +50,7 @@ export default class SetIssueTagsPopup extends React.PureComponent<Props, State>
 
   onSearch = (query: string) => {
     return searchIssueTags({
+      all: true,
       q: query,
       ps: Math.min(this.props.selectedTags.length - 1 + LIST_SIZE, MAX_LIST_SIZE)
     }).then(
index ed80458dd0e9e801c5ddd76bd47088893d8399de..9b9b0e49fd7cc7aa5a59977c8700ac9c101e8abf 100644 (file)
@@ -516,9 +516,11 @@ public class IssueIndex {
     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()));
index 6c186591c91b14b6e4d22788b9d676c284e8896c..16e0cafe9276ecbf8ebe2e2ec0c413410028da5f 100644 (file)
@@ -96,7 +96,7 @@ public class IssueQuery {
   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) {
@@ -279,7 +279,7 @@ public class IssueQuery {
     return branchUuid;
   }
 
-  public boolean isMainBranch() {
+  public Boolean isMainBranch() {
     return mainBranch;
   }
 
@@ -336,7 +336,7 @@ public class IssueQuery {
     private Boolean asc = false;
     private String facetMode;
     private String branchUuid;
-    private boolean mainBranch = true;
+    private Boolean mainBranch = true;
     private ZoneId timeZone;
 
     private Builder() {
@@ -540,7 +540,7 @@ public class IssueQuery {
       return this;
     }
 
-    public Builder mainBranch(boolean mainBranch) {
+    public Builder mainBranch(@Nullable Boolean mainBranch) {
       this.mainBranch = mainBranch;
       return this;
     }
index a95f32ed55e5da58a6becc8d76335d7d18cfd9b4..26b2f8f1faa791afa16f746ec6a41cb3cc261fee 100644 (file)
@@ -276,6 +276,7 @@ public class IssueIndexFiltersTest {
     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"));
   }
 
index ffbe70ea0c9e0b43a80a96e30ff88bbd9ccb25f8..364f796fe46ac9d1b4752865eb68d1b6dde1113e 100644 (file)
  */
 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;
@@ -33,6 +33,7 @@ import org.sonar.api.server.ws.WebService;
 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;
@@ -41,19 +42,22 @@ import org.sonar.server.issue.index.IssueQuery;
 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;
@@ -61,8 +65,8 @@ public class TagsAction implements IssuesWsAction {
   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;
@@ -84,15 +88,31 @@ public class TagsAction implements IssuesWsAction {
       .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);
@@ -116,22 +136,31 @@ public class TagsAction implements IssuesWsAction {
     }
   }
 
-  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),
index 1ce5af692adad03c6f0e4e1e893e70b13b98c1eb..7d86aa5c8d8b2cb2e895d7782f298553091dd5df 100644 (file)
@@ -156,6 +156,66 @@ public class TagsActionTest {
     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();
@@ -319,7 +379,9 @@ public class TagsActionTest {
       .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) {