]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21259 added new param to api/issues/search
authorlukasz-jarocki-sonarsource <lukasz.jarocki@sonarsource.com>
Wed, 20 Dec 2023 15:02:32 +0000 (16:02 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 17 Jan 2024 20:02:44 +0000 (20:02 +0000)
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java
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/main/java/org/sonar/server/issue/index/IssueQueryFactory.java
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java
server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/KeyExamples.java
sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java

index 2bad9323319eb39782ab90e624c4b69f901f1c2b..bf0d7bf582f515f506bde2a3c59038e567c668fd 100644 (file)
@@ -77,13 +77,6 @@ public class BranchDao implements Dao {
     return mapper(dbSession).selectByBranchKeys(branchKeyByProjectUuid);
   }
 
-  public List<BranchDto> selectByPullRequestKeys(DbSession dbSession, Map<String, String> prKeyByProjectUuid) {
-    if (prKeyByProjectUuid.isEmpty()) {
-      return emptyList();
-    }
-    return mapper(dbSession).selectByPullRequestKeys(prKeyByProjectUuid);
-  }
-
   public Optional<BranchDto> selectByPullRequestKey(DbSession dbSession, String projectUuid, String key) {
     return selectByKey(dbSession, projectUuid, key, BranchType.PULL_REQUEST);
   }
index 6c3cf20c941f47f5798d4489ae287755f8910eea..d146dfd071fecf00c341cf22d0bd1f8d8bae0f9d 100644 (file)
@@ -75,6 +75,7 @@ public class SearchRequest {
   private String timeZone;
   private Integer owaspAsvsLevel;
   private List<String> codeVariants;
+  private String fixedInPullRequest;
 
   public SearchRequest() {
     // nothing to do here
@@ -553,4 +554,14 @@ public class SearchRequest {
     this.cleanCodeAttributesCategories = cleanCodeAttributesCategories;
     return this;
   }
+
+  @CheckForNull
+  public String getFixedInPullRequest() {
+    return fixedInPullRequest;
+  }
+
+  public SearchRequest setFixedInPullRequest(@Nullable String fixedInPullRequest) {
+    this.fixedInPullRequest = fixedInPullRequest;
+    return this;
+  }
 }
index 677830a4ba04c6cb32307c513fd5a1a2a49ce147..df616a13ebcf672681791463b56b586e87b01611 100644 (file)
@@ -465,7 +465,7 @@ public class IssueIndex {
     }
 
     // Field Filters
-    filters.addFilter(FIELD_ISSUE_KEY, new SimpleFieldFilterScope(FIELD_ISSUE_KEY), createTermsFilter(FIELD_ISSUE_KEY, query.issueKeys()));
+    filters.addFilter(FIELD_ISSUE_KEY, new SimpleFieldFilterScope(FIELD_ISSUE_KEY), createTermsFilterForNullableCollection(FIELD_ISSUE_KEY, query.issueKeys()));
     filters.addFilter(FIELD_ISSUE_ASSIGNEE_UUID, ASSIGNEES.getFilterScope(), createTermsFilter(FIELD_ISSUE_ASSIGNEE_UUID, query.assignees()));
     filters.addFilter(FIELD_ISSUE_SCOPE, SCOPES.getFilterScope(), createTermsFilter(FIELD_ISSUE_SCOPE, query.scopes()));
     filters.addFilter(FIELD_ISSUE_LANGUAGE, LANGUAGES.getFilterScope(), createTermsFilter(FIELD_ISSUE_LANGUAGE, query.languages()));
@@ -741,11 +741,24 @@ public class IssueIndex {
     return FACET_MODE_EFFORT.equals(query.facetMode());
   }
 
+  /**
+   * This method is for creating a filter that passes null to the elasticsearch query whenever empty or null collection is passed.
+   * This means that filter will not filter anything, all the documents (issues) will be returned in this case.
+   */
   @CheckForNull
   private static QueryBuilder createTermsFilter(String field, Collection<?> values) {
     return values.isEmpty() ? null : termsQuery(field, values);
   }
 
+  /**
+   * This method is for creating a filter that passes null to the elasticsearch query only when null collection is passed.
+   * This ensures that whenever we pass empty collection to the filter, it will filter out all the documents (issues).
+   */
+  @CheckForNull
+  private static QueryBuilder createTermsFilterForNullableCollection(String field, @Nullable Collection<?> values) {
+    return values != null ? termsQuery(field, values) : null;
+  }
+
   @CheckForNull
   private static QueryBuilder createTermFilter(String field, @Nullable String value) {
     return value == null ? null : termQuery(field, value);
index 0612edb8b1ec65297a7114de079c271961be574d..5da45db5877429812a49a0824b38decd6959b21a 100644 (file)
@@ -105,7 +105,7 @@ public class IssueQuery {
   private final Collection<String> cleanCodeAttributesCategories;
 
   private IssueQuery(Builder builder) {
-    this.issueKeys = defaultCollection(builder.issueKeys);
+    this.issueKeys = nullableDefaultCollection(builder.issueKeys);
     this.severities = defaultCollection(builder.severities);
     this.impactSeverities = defaultCollection(builder.impactSeverities);
     this.impactSoftwareQualities = defaultCollection(builder.impactSoftwareQualities);
@@ -679,6 +679,10 @@ public class IssueQuery {
     return c == null ? Collections.emptyList() : Collections.unmodifiableCollection(c);
   }
 
+  private static <T> Collection<T> nullableDefaultCollection(@Nullable Collection<T> c) {
+    return c == null ? null : Collections.unmodifiableCollection(c);
+  }
+
   private static <K, V> Map<K, V> defaultMap(@Nullable Map<K, V> map) {
     return map == null ? Collections.emptyMap() : Collections.unmodifiableMap(map);
   }
index 8f602db25f4fbade88a5bca7c7e35e6eabe3962e..1222629845fdfa170519e90419b756d3da8f68b3 100644 (file)
@@ -53,7 +53,9 @@ import org.sonar.db.DbSession;
 import org.sonar.db.component.BranchDto;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.issue.IssueFixedDto;
 import org.sonar.db.permission.GlobalPermission;
+import org.sonar.db.project.ProjectDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.server.issue.SearchRequest;
 import org.sonar.server.issue.index.IssueQuery.PeriodStart;
@@ -79,6 +81,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_UUIDS;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_IN_LAST;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FIXED_IN_PULL_REQUEST;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IN_NEW_CODE_PERIOD;
 
 /**
@@ -99,7 +102,8 @@ public class IssueQueryFactory {
     .map(Enum::name)
     .collect(Collectors.toSet());
   private static final ComponentDto UNKNOWN_COMPONENT = new ComponentDto().setUuid(UNKNOWN).setBranchUuid(UNKNOWN);
-  private static final Set<String> QUALIFIERS_WITHOUT_LEAK_PERIOD = new HashSet<>(Arrays.asList(Qualifiers.APP, Qualifiers.VIEW, Qualifiers.SUBVIEW));
+  private static final Set<String> QUALIFIERS_WITHOUT_LEAK_PERIOD = new HashSet<>(Arrays.asList(Qualifiers.APP, Qualifiers.VIEW,
+    Qualifiers.SUBVIEW));
   private final DbClient dbClient;
   private final Clock clock;
   private final UserSession userSession;
@@ -116,13 +120,14 @@ public class IssueQueryFactory {
 
       Collection<RuleDto> ruleDtos = ruleKeysToRuleId(dbSession, request.getRules());
       Collection<String> ruleUuids = ruleDtos.stream().map(RuleDto::getUuid).collect(Collectors.toSet());
+      Collection<String> issueKeys = collectIssueKeys(dbSession, request);
 
       if (request.getRules() != null && request.getRules().stream().collect(Collectors.toSet()).size() != ruleDtos.size()) {
         ruleUuids.add("non-existing-uuid");
       }
 
       IssueQuery.Builder builder = IssueQuery.builder()
-        .issueKeys(request.getIssues())
+        .issueKeys(issueKeys)
         .severities(request.getSeverities())
         .cleanCodeAttributesCategories(request.getCleanCodeAttributesCategories())
         .impactSoftwareQualities(request.getImpactSoftwareQualities())
@@ -169,6 +174,49 @@ public class IssueQueryFactory {
     }
   }
 
+  private Collection<String> collectIssueKeys(DbSession dbSession, SearchRequest request) {
+    Collection<String> issueKeys = null;
+    if (request.getFixedInPullRequest() != null) {
+      issueKeys = getIssuesFixedByPullRequest(dbSession, request);
+    }
+    if (request.getIssues() != null && !request.getIssues().isEmpty()) {
+      if (issueKeys == null) {
+        issueKeys = new ArrayList<>();
+      }
+      issueKeys.addAll(request.getIssues());
+    }
+
+    return issueKeys;
+  }
+
+  private Collection<String> getIssuesFixedByPullRequest(DbSession dbSession, SearchRequest request) {
+    String fixedInPullRequest = request.getFixedInPullRequest();
+    List<String> componentKeys = request.getComponentKeys();
+    if (componentKeys == null || componentKeys.size() != 1) {
+      throw new IllegalArgumentException("Exactly one project needs to be provided in the " +
+        "'" + PARAM_COMPONENTS + "' param when used together with '" + PARAM_FIXED_IN_PULL_REQUEST + "' param");
+    }
+    String projectKey = componentKeys.get(0);
+    ProjectDto projectDto = dbClient.projectDao().selectProjectByKey(dbSession, projectKey)
+      .orElseThrow(() -> new IllegalArgumentException("Project with key '" + projectKey + "' does not exist"));
+    BranchDto pullRequest = dbClient.branchDao().selectByPullRequestKey(dbSession, projectDto.getUuid(), fixedInPullRequest)
+      .orElseThrow(() -> new IllegalArgumentException("Pull request with key '" + fixedInPullRequest + "' does not exist for a project " +
+        projectKey));
+
+    if (request.getBranch() != null) {
+      BranchDto targetBranch = dbClient.branchDao().selectByBranchKey(dbSession, projectDto.getUuid(), request.getBranch())
+        .orElseThrow(() -> new IllegalArgumentException("Branch with key '" + request.getBranch() + "' does not exist"));
+      if (!Objects.equals(targetBranch.getUuid(), pullRequest.getMergeBranchUuid())) {
+        throw new IllegalArgumentException("Pull request with key '" + fixedInPullRequest + "' does not target branch '" + request.getBranch() + "'");
+      }
+    }
+    return dbClient.issueFixedDao().selectByPullRequest(dbSession, pullRequest.getUuid())
+      .stream()
+      .map(IssueFixedDto::issueKey)
+      .collect(Collectors.toSet());
+  }
+
+
   private static Optional<ZoneId> parseTimeZone(@Nullable String timeZone) {
     if (timeZone == null) {
       return Optional.empty();
@@ -181,7 +229,8 @@ public class IssueQueryFactory {
     }
   }
 
-  private void setCreatedAfterFromDates(IssueQuery.Builder builder, @Nullable Date createdAfter, @Nullable String createdInLast, boolean createdAfterInclusive) {
+  private void setCreatedAfterFromDates(IssueQuery.Builder builder, @Nullable Date createdAfter, @Nullable String createdInLast,
+    boolean createdAfterInclusive) {
     Date actualCreatedAfter = createdAfter;
     if (createdInLast != null) {
       actualCreatedAfter = Date.from(
@@ -192,16 +241,19 @@ public class IssueQueryFactory {
     builder.createdAfter(actualCreatedAfter, createdAfterInclusive);
   }
 
-  private void setCreatedAfterFromRequest(DbSession dbSession, IssueQuery.Builder builder, SearchRequest request, List<ComponentDto> componentUuids, ZoneId timeZone) {
+  private void setCreatedAfterFromRequest(DbSession dbSession, IssueQuery.Builder builder, SearchRequest request,
+    List<ComponentDto> componentUuids, ZoneId timeZone) {
     Date createdAfter = parseStartingDateOrDateTime(request.getCreatedAfter(), timeZone);
     String createdInLast = request.getCreatedInLast();
 
     if (notInNewCodePeriod(request)) {
-      checkArgument(createdAfter == null || createdInLast == null, format("Parameters %s and %s cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_CREATED_IN_LAST));
+      checkArgument(createdAfter == null || createdInLast == null, format("Parameters %s and %s cannot be set simultaneously",
+        PARAM_CREATED_AFTER, PARAM_CREATED_IN_LAST));
       setCreatedAfterFromDates(builder, createdAfter, createdInLast, true);
     } else {
       // If the filter is on leak period
-      checkArgument(createdAfter == null, "Parameters '%s' and '%s' cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_IN_NEW_CODE_PERIOD);
+      checkArgument(createdAfter == null, "Parameters '%s' and '%s' cannot be set simultaneously", PARAM_CREATED_AFTER,
+        PARAM_IN_NEW_CODE_PERIOD);
       checkArgument(createdInLast == null,
         format("Parameters '%s' and '%s' cannot be set simultaneously", PARAM_CREATED_IN_LAST, PARAM_IN_NEW_CODE_PERIOD));
 
@@ -306,7 +358,8 @@ public class IssueQueryFactory {
       .collect(Collectors.toSet());
   }
 
-  private void addComponentsBasedOnQualifier(IssueQuery.Builder builder, DbSession dbSession, List<ComponentDto> components, SearchRequest request) {
+  private void addComponentsBasedOnQualifier(IssueQuery.Builder builder, DbSession dbSession, List<ComponentDto> components,
+    SearchRequest request) {
     if (components.isEmpty()) {
       return;
     }
@@ -369,7 +422,8 @@ public class IssueQueryFactory {
     builder.viewUuids(filteredViewUuids);
   }
 
-  private void addApplications(IssueQuery.Builder builder, DbSession dbSession, List<ComponentDto> appBranchComponents, SearchRequest request) {
+  private void addApplications(IssueQuery.Builder builder, DbSession dbSession, List<ComponentDto> appBranchComponents,
+    SearchRequest request) {
     Set<String> authorizedAppBranchUuids = appBranchComponents.stream()
       .filter(app -> userSession.hasComponentPermission(USER, app) && userSession.hasChildProjectsPermission(USER, app))
       .map(ComponentDto::uuid)
@@ -379,7 +433,8 @@ public class IssueQueryFactory {
     addCreatedAfterByProjects(builder, dbSession, request, authorizedAppBranchUuids);
   }
 
-  private void addCreatedAfterByProjects(IssueQuery.Builder builder, DbSession dbSession, SearchRequest request, Set<String> appBranchUuids) {
+  private void addCreatedAfterByProjects(IssueQuery.Builder builder, DbSession dbSession, SearchRequest request,
+    Set<String> appBranchUuids) {
     if (notInNewCodePeriod(request) || request.getPullRequest() != null) {
       return;
     }
@@ -415,7 +470,8 @@ public class IssueQueryFactory {
     builder.directories(paths);
   }
 
-  private List<ComponentDto> getComponentsFromKeys(DbSession dbSession, Collection<String> componentKeys, @Nullable String branch, @Nullable String pullRequest) {
+  private List<ComponentDto> getComponentsFromKeys(DbSession dbSession, Collection<String> componentKeys, @Nullable String branch,
+    @Nullable String pullRequest) {
     List<ComponentDto> componentDtos = dbClient.componentDao().selectByKeys(dbSession, componentKeys, branch, pullRequest);
     if (!componentKeys.isEmpty() && componentDtos.isEmpty()) {
       return singletonList(UNKNOWN_COMPONENT);
index f84916093e5019ff85cd0e7d4be592ae8ebda26b..2d98b68b1fbf6fd5880fcf32f0d891806fe51d39 100644 (file)
@@ -135,6 +135,64 @@ public class IssueQueryFactoryTest {
     assertThat(query.codeVariants()).containsOnly("variant1", "variant2");
   }
 
+  @Test
+  public void getIssuesFixedByPullRequest_returnIssuesFixedByThePullRequest() {
+    String ruleAdHocName = "New Name";
+    UserDto user = db.users().insertUser(u -> u.setLogin("joanna"));
+    ProjectData projectData = db.components().insertPrivateProject();
+    ComponentDto project = projectData.getMainBranchComponent();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+
+    RuleDto rule1 = ruleDbTester.insert(r -> r.setAdHocName(ruleAdHocName));
+    RuleDto rule2 = ruleDbTester.insert(r -> r.setAdHocName(ruleAdHocName));
+    newRule(RuleKey.of("findbugs", "NullReference"));
+    SearchRequest request = new SearchRequest()
+      .setIssues(asList("anIssueKey"))
+      .setSeverities(asList("MAJOR", "MINOR"))
+      .setStatuses(asList("CLOSED"))
+      .setResolutions(asList("FALSE-POSITIVE"))
+      .setResolved(true)
+      .setProjectKeys(asList(project.getKey()))
+      .setDirectories(asList("aDirPath"))
+      .setFiles(asList(file.uuid()))
+      .setAssigneesUuid(asList(user.getUuid()))
+      .setScopes(asList("MAIN", "TEST"))
+      .setLanguages(asList("xoo"))
+      .setTags(asList("tag1", "tag2"))
+      .setAssigned(true)
+      .setCreatedAfter("2013-04-16T09:08:24+0200")
+      .setCreatedBefore("2013-04-17T09:08:24+0200")
+      .setRules(asList(rule1.getKey().toString(), rule2.getKey().toString()))
+      .setSort("CREATION_DATE")
+      .setAsc(true)
+      .setCodeVariants(asList("variant1", "variant2"));
+
+    IssueQuery query = underTest.create(request);
+
+    assertThat(query.issueKeys()).containsOnly("anIssueKey");
+    assertThat(query.severities()).containsOnly("MAJOR", "MINOR");
+    assertThat(query.statuses()).containsOnly("CLOSED");
+    assertThat(query.resolutions()).containsOnly("FALSE-POSITIVE");
+    assertThat(query.resolved()).isTrue();
+    assertThat(query.projectUuids()).containsOnly(projectData.projectUuid());
+    assertThat(query.files()).containsOnly(file.uuid());
+    assertThat(query.assignees()).containsOnly(user.getUuid());
+    assertThat(query.scopes()).containsOnly("TEST", "MAIN");
+    assertThat(query.languages()).containsOnly("xoo");
+    assertThat(query.tags()).containsOnly("tag1", "tag2");
+    assertThat(query.onComponentOnly()).isFalse();
+    assertThat(query.assigned()).isTrue();
+    assertThat(query.rules()).hasSize(2);
+    assertThat(query.ruleUuids()).hasSize(2);
+    assertThat(query.directories()).containsOnly("aDirPath");
+    assertThat(query.createdAfter().date()).isEqualTo(parseDateTime("2013-04-16T09:08:24+0200"));
+    assertThat(query.createdAfter().inclusive()).isTrue();
+    assertThat(query.createdBefore()).isEqualTo(parseDateTime("2013-04-17T09:08:24+0200"));
+    assertThat(query.sort()).isEqualTo(IssueQuery.SORT_BY_CREATION_DATE);
+    assertThat(query.asc()).isTrue();
+    assertThat(query.codeVariants()).containsOnly("variant1", "variant2");
+  }
+
   @Test
   public void create_with_rule_key_that_does_not_exist_in_the_db() {
     db.users().insertUser(u -> u.setLogin("joanna"));
index fd44cf728a3ea6c1407cf1e5cff7b1a0c588ba6c..d50061f65f8af776c78471c155e6116fb9bdc33d 100644 (file)
@@ -153,7 +153,7 @@ public class IssueQueryTest {
   }
 
   @Test
-  public void collection_params_should_not_be_null_but_empty() {
+  public void collection_params_should_not_be_null_but_empty_except_issue_keys() {
     IssueQuery query = IssueQuery.builder()
       .issueKeys(null)
       .projectUuids(null)
@@ -171,7 +171,7 @@ public class IssueQueryTest {
       .cwe(null)
       .createdAfterByProjectUuids(null)
       .build();
-    assertThat(query.issueKeys()).isEmpty();
+    assertThat(query.issueKeys()).isNull();
     assertThat(query.projectUuids()).isEmpty();
     assertThat(query.componentUuids()).isEmpty();
     assertThat(query.statuses()).isEmpty();
@@ -191,7 +191,6 @@ public class IssueQueryTest {
   @Test
   public void test_default_query() {
     IssueQuery query = IssueQuery.builder().build();
-    assertThat(query.issueKeys()).isEmpty();
     assertThat(query.projectUuids()).isEmpty();
     assertThat(query.componentUuids()).isEmpty();
     assertThat(query.statuses()).isEmpty();
@@ -202,6 +201,7 @@ public class IssueQueryTest {
     assertThat(query.languages()).isEmpty();
     assertThat(query.tags()).isEmpty();
     assertThat(query.types()).isEmpty();
+    assertThat(query.issueKeys()).isNull();
     assertThat(query.branchUuid()).isNull();
     assertThat(query.assigned()).isNull();
     assertThat(query.createdAfter()).isNull();
index 864ba7eeb3d33c4474e8247547fd1b345e871292..e9fc5f67010f07bdf035f3b81788915ea404de87 100644 (file)
@@ -54,6 +54,7 @@ import org.sonar.core.util.Uuids;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchDto;
 import org.sonar.db.component.BranchType;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ProjectData;
@@ -61,6 +62,7 @@ import org.sonar.db.component.SnapshotDto;
 import org.sonar.db.issue.ImpactDto;
 import org.sonar.db.issue.IssueChangeDto;
 import org.sonar.db.issue.IssueDto;
+import org.sonar.db.issue.IssueFixedDto;
 import org.sonar.db.permission.GroupPermissionDto;
 import org.sonar.db.project.ProjectDto;
 import org.sonar.db.protobuf.DbCommons;
@@ -1921,10 +1923,10 @@ public class SearchActionIT {
 
   @Test
   public void fail_if_trying_to_filter_issues_by_hotspots() {
-    ComponentDto project = db.components().insertPublicProject().getMainBranchComponent();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    ComponentDto mainBranch = db.components().insertPublicProject().getMainBranchComponent();
+    ComponentDto file = db.components().insertComponent(newFileDto(mainBranch));
     RuleDto hotspotRule = newHotspotRule();
-    db.issues().insertHotspot(hotspotRule, project, file);
+    db.issues().insertHotspot(hotspotRule, mainBranch, file);
     insertIssues(i -> i.setType(RuleType.BUG), i -> i.setType(RuleType.VULNERABILITY),
       i -> i.setType(RuleType.CODE_SMELL));
     indexPermissionsAndIssues();
@@ -2082,7 +2084,7 @@ public class SearchActionIT {
       "createdBefore", "createdInLast", "directories", "facets", "files", "issues", "scopes", "languages", "onComponentOnly",
       "p", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "statuses", "tags", "types", "pciDss-3.2", "pciDss-4.0", "owaspAsvs-4.0",
       "owaspAsvsLevel", "owaspTop10", "owaspTop10-2021", "sansTop25", "cwe", "sonarsourceSecurity", "timeZone", "inNewCodePeriod", "codeVariants",
-      "cleanCodeAttributeCategories", "impactSeverities", "impactSoftwareQualities", "issueStatuses");
+      "cleanCodeAttributeCategories", "impactSeverities", "impactSoftwareQualities", "issueStatuses", "fixedInPullRequest");
 
     WebService.Param branch = def.param(PARAM_BRANCH);
     assertThat(branch.isInternal()).isFalse();
@@ -2145,6 +2147,167 @@ public class SearchActionIT {
       .extracting(Issue::getRuleDescriptionContextKey).containsExactly("spring");
   }
 
+  @Test
+  public void search_whenFixedInPullRequestSetAndNoComponentsSet_throwException() {
+    TestRequest request = ws.newRequest()
+      .setParam("fixedInPullRequest", "1000");
+
+    assertThatThrownBy(request::execute)
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Exactly one project needs to be provided in the 'components' param when used together with 'fixedInPullRequest' param");
+  }
+
+  @Test
+  public void search_whenFixedInPullRequestSetAndWrongBranchIsSet_throwException() {
+    String pullRequestId = "1000";
+    String pullRequestUuid = "pullRequestUuid";
+    userSession.logIn(db.users().insertUser());
+    ProjectData project = db.components().insertPublicProject();
+    db.getDbClient().branchDao().insert(session, new BranchDto()
+      .setUuid(pullRequestUuid)
+      .setProjectUuid(project.projectUuid())
+      .setKey(pullRequestId)
+      .setIsMain(false)
+      .setBranchType(BranchType.PULL_REQUEST)
+      .setMergeBranchUuid(project.mainBranchUuid()));
+    db.getDbClient().branchDao().insert(session, new BranchDto()
+      .setUuid("wrongBranchUuid")
+      .setProjectUuid(project.projectUuid())
+      .setKey("wrongBranch")
+      .setIsMain(false)
+      .setBranchType(BranchType.BRANCH)
+      .setMergeBranchUuid("wrongTargetBranchUuid"));
+
+    session.commit();
+    TestRequest request = ws.newRequest().setParam("fixedInPullRequest", pullRequestId).setParam("components", project.projectKey())
+      .setParam("branch", "wrongBranch");
+
+    assertThatThrownBy(request::execute)
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Pull request with key '1000' does not target branch 'wrongBranch'");
+  }
+
+  @Test
+  public void search_whenFixedInPullRequestSetAndProjectDoesNotExist_throwException() {
+    String pullRequestId = "1000";
+    String pullRequestUuid = "pullRequestUuid";
+    userSession.logIn(db.users().insertUser());
+    ProjectData project = db.components().insertPublicProject();
+    db.getDbClient().branchDao().insert(session, new BranchDto()
+      .setUuid(pullRequestUuid)
+      .setProjectUuid(project.projectUuid())
+      .setKey(pullRequestId)
+      .setIsMain(false)
+      .setBranchType(BranchType.PULL_REQUEST)
+      .setMergeBranchUuid(project.mainBranchUuid()));
+
+    session.commit();
+    TestRequest request = ws.newRequest().setParam("fixedInPullRequest", pullRequestId).setParam("components", "nonExistingProjectKey");
+
+    assertThatThrownBy(request::execute)
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Project with key 'nonExistingProjectKey' does not exist");
+  }
+
+  @Test
+  public void search_whenWrongFixedInPullRequestSet_throwException() {
+    String pullRequestId = "wrongPullRequest";
+    String pullRequestUuid = "pullRequestUuid";
+    userSession.logIn(db.users().insertUser());
+    ProjectData project = db.components().insertPublicProject();
+    db.getDbClient().branchDao().insert(session, new BranchDto()
+      .setUuid(pullRequestUuid)
+      .setProjectUuid(project.projectUuid())
+      .setKey("pullRequestId")
+      .setIsMain(false)
+      .setBranchType(BranchType.PULL_REQUEST)
+      .setMergeBranchUuid(project.mainBranchUuid()));
+
+    session.commit();
+    TestRequest request = ws.newRequest().setParam("fixedInPullRequest", pullRequestId).setParam("components", project.projectKey());
+
+    assertThatThrownBy(request::execute)
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Pull request with key 'wrongPullRequest' does not exist for a project " + project.projectKey());
+  }
+
+  @Test
+  public void search_whenFixedInPullRequestSetAndNonExistingBranchIsSet_throwException() {
+    String pullRequestId = "1000";
+    String pullRequestUuid = "pullRequestUuid";
+    userSession.logIn(db.users().insertUser());
+    ProjectData project = db.components().insertPublicProject();
+    db.getDbClient().branchDao().insert(session, new BranchDto()
+      .setUuid(pullRequestUuid)
+      .setProjectUuid(project.projectUuid())
+      .setKey(pullRequestId)
+      .setIsMain(false)
+      .setBranchType(BranchType.PULL_REQUEST)
+      .setMergeBranchUuid(project.mainBranchUuid()));
+
+    session.commit();
+    TestRequest request = ws.newRequest().setParam("fixedInPullRequest", pullRequestId).setParam("components", project.projectKey())
+      .setParam("branch", "nonExistingBranch");
+
+    assertThatThrownBy(request::execute)
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Branch with key 'nonExistingBranch' does not exist");
+  }
+
+  @Test
+  public void search_whenFixedInPullRequestSetAndComponentsIsSetButNoIssueFixedInPR_returnZeroIssues() {
+    String pullRequestId = "1000";
+    String pullRequestUuid = "pullRequestUuid";
+    String issueKey = "issueKey";
+    userSession.logIn(db.users().insertUser());
+    ProjectData project = db.components().insertPublicProject();
+    db.getDbClient().branchDao().insert(session, new BranchDto()
+      .setUuid(pullRequestUuid)
+      .setProjectUuid(project.projectUuid())
+      .setKey(pullRequestId)
+      .setIsMain(false)
+      .setBranchType(BranchType.PULL_REQUEST)
+      .setMergeBranchUuid(project.mainBranchUuid()));
+
+    TestRequest request = ws.newRequest().setParam("components", project.projectKey()).setParam("fixedInPullRequest", pullRequestId);
+    insertIssues(project.getMainBranchComponent(), i -> i.setKee(issueKey));
+    session.commit();
+    indexPermissionsAndIssues();
+
+    SearchWsResponse response = request.executeProtobuf(SearchWsResponse.class);
+
+    List<Issue> issuesList = response.getIssuesList();
+    assertThat(issuesList).isEmpty();
+  }
+
+  @Test
+  public void search_whenFixedInPullRequestSetAndComponentsIsSet_returnOneIssueFixedInPR() {
+    String pullRequestId = "1000";
+    String pullRequestUuid = "pullRequestUuid";
+    String issueKey = "issueKey";
+    userSession.logIn(db.users().insertUser());
+    ProjectData project = db.components().insertPublicProject();
+    db.getDbClient().branchDao().insert(session, new BranchDto()
+      .setUuid(pullRequestUuid)
+      .setProjectUuid(project.projectUuid())
+      .setKey(pullRequestId)
+      .setIsMain(false)
+      .setBranchType(BranchType.PULL_REQUEST)
+      .setMergeBranchUuid(project.mainBranchUuid()));
+
+    TestRequest request = ws.newRequest().setParam("components", project.projectKey()).setParam("fixedInPullRequest", pullRequestId);
+    insertIssues(project.getMainBranchComponent(), i -> i.setKee(issueKey));
+    db.getDbClient().issueFixedDao().insert(session, new IssueFixedDto(pullRequestUuid, issueKey));
+    session.commit();
+    indexPermissionsAndIssues();
+
+    SearchWsResponse response = request.executeProtobuf(SearchWsResponse.class);
+
+    List<Issue> issuesList = response.getIssuesList();
+    assertThat(issuesList).hasSize(1);
+    assertThat(issuesList.get(0).getKey()).isEqualTo(issueKey);
+  }
+
   private RuleDto newIssueRule() {
     RuleDto rule = newRule(XOO_X1, createDefaultRuleDescriptionSection(uuidFactory.create(), "Rule desc"))
       .setLanguage("xoo")
@@ -2200,10 +2363,20 @@ public class SearchActionIT {
     UserDto john = db.users().insertUser();
     userSession.logIn(john);
     RuleDto rule = db.rules().insertIssueRule();
-    ComponentDto project = db.components().insertPublicProject().getMainBranchComponent();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    ComponentDto branch = db.components().insertPublicProject().getMainBranchComponent();
+    ComponentDto file = db.components().insertComponent(newFileDto(branch));
+    for (Consumer<IssueDto> populator : populators) {
+      db.issues().insertIssue(rule, branch, file, populator);
+    }
+  }
+
+  private void insertIssues(ComponentDto branch, Consumer<IssueDto>... populators) {
+    UserDto john = db.users().insertUser();
+    userSession.logIn(john);
+    RuleDto rule = db.rules().insertIssueRule();
+    ComponentDto file = db.components().insertComponent(newFileDto(branch));
     for (Consumer<IssueDto> populator : populators) {
-      db.issues().insertIssue(rule, project, file, populator);
+      db.issues().insertIssue(rule, branch, file, populator);
     }
   }
 
index 401e5c10258237c26aba83d801a81ac7212b069b..17ad80ee46b786e2d1004355caca174142df592b 100644 (file)
@@ -110,10 +110,12 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_IN_
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CWE;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_DIRECTORIES;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FIXED_IN_PULL_REQUEST;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SEVERITIES;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SOFTWARE_QUALITIES;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IN_NEW_CODE_PERIOD;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE_STATUSES;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ON_COMPONENT_ONLY;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_ASVS_40;
@@ -130,7 +132,6 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SCOPES;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE_STATUSES;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SONARSOURCE_SECURITY;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS;
@@ -140,7 +141,8 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TYPES;
 public class SearchAction implements IssuesWsAction {
   private static final String LOGIN_MYSELF = "__me__";
   private static final Set<String> ISSUE_SCOPES = Arrays.stream(IssueScope.values()).map(Enum::name).collect(Collectors.toSet());
-  private static final EnumSet<RuleType> ALL_RULE_TYPES_EXCEPT_SECURITY_HOTSPOTS = EnumSet.complementOf(EnumSet.of(RuleType.SECURITY_HOTSPOT));
+  private static final EnumSet<RuleType> ALL_RULE_TYPES_EXCEPT_SECURITY_HOTSPOTS =
+    EnumSet.complementOf(EnumSet.of(RuleType.SECURITY_HOTSPOT));
 
   static final List<String> SUPPORTED_FACETS = List.of(
     FACET_PROJECTS,
@@ -172,7 +174,8 @@ public class SearchAction implements IssuesWsAction {
     PARAM_IMPACT_SEVERITIES,
     PARAM_ISSUE_STATUSES);
 
-  private static final String INTERNAL_PARAMETER_DISCLAIMER = "This parameter is mostly used by the Issues page, please prefer usage of the componentKeys parameter. ";
+  private static final String INTERNAL_PARAMETER_DISCLAIMER = "This parameter is mostly used by the Issues page, please prefer usage of " +
+    "the componentKeys parameter. ";
   private static final String NEW_FACET_ADDED_MESSAGE = "Facet '%s' has been added";
   private static final String NEW_PARAM_ADDED_MESSAGE = "Param '%s' has been added";
   private static final Set<String> FACETS_REQUIRING_PROJECT = newHashSet(PARAM_FILES, PARAM_DIRECTORIES);
@@ -186,7 +189,8 @@ public class SearchAction implements IssuesWsAction {
   private final System2 system2;
   private final DbClient dbClient;
 
-  public SearchAction(UserSession userSession, IssueIndex issueIndex, IssueQueryFactory issueQueryFactory, IssueIndexSyncProgressChecker issueIndexSyncProgressChecker,
+  public SearchAction(UserSession userSession, IssueIndex issueIndex, IssueQueryFactory issueQueryFactory,
+    IssueIndexSyncProgressChecker issueIndexSyncProgressChecker,
     SearchResponseLoader searchResponseLoader, SearchResponseFormat searchResponseFormat, System2 system2, DbClient dbClient) {
     this.userSession = userSession;
     this.issueIndex = issueIndex;
@@ -208,7 +212,10 @@ public class SearchAction implements IssuesWsAction {
         + "<br/>When issue indexation is in progress returns 503 service unavailable HTTP code.")
       .setSince("3.6")
       .setChangelog(
-        new Change("10.4", "Value '%s' for 'transition' response field is deprecated, use '%s' instead".formatted(DefaultTransitions.WONT_FIX, DefaultTransitions.ACCEPT)),
+        new Change("10.4", "Added new param '%s'".formatted(PARAM_FIXED_IN_PULL_REQUEST)),
+        new Change("10.4",
+          "Value '%s' for 'transition' response field is deprecated, use '%s' instead".formatted(DefaultTransitions.WONT_FIX,
+            DefaultTransitions.ACCEPT)),
         new Change("10.4", "Possible value '%s' for 'transition' response field has been added".formatted(DefaultTransitions.ACCEPT)),
         new Change("10.4", format(NEW_PARAM_ADDED_MESSAGE, PARAM_ISSUE_STATUSES)),
         new Change("10.4", format("Parameters '%s' and '%s' are deprecated in favor of '%s'.", PARAM_RESOLUTIONS, PARAM_STATUSES, PARAM_ISSUE_STATUSES)),
@@ -251,10 +258,12 @@ public class SearchAction implements IssuesWsAction {
         new Change("8.5", "Internal parameter 'fileUuids' has been dropped"),
         new Change("8.4", "parameters 'componentUuids', 'projectKeys' has been dropped."),
         new Change("8.2", "'REVIEWED', 'TO_REVIEW' status param values are no longer supported"),
-        new Change("8.2", "Security hotspots are no longer returned as type 'SECURITY_HOTSPOT' is not supported anymore, use dedicated api/hotspots"),
+        new Change("8.2", "Security hotspots are no longer returned as type 'SECURITY_HOTSPOT' is not supported anymore, use dedicated " +
+          "api/hotspots"),
         new Change("8.2", "response field 'fromHotspot' has been deprecated and is no more populated"),
         new Change("8.2", "Status 'IN_REVIEW' for Security Hotspots has been deprecated"),
-        new Change("7.8", format("added new Security Hotspots statuses : %s, %s and %s", STATUS_TO_REVIEW, STATUS_IN_REVIEW, STATUS_REVIEWED)),
+        new Change("7.8", format("added new Security Hotspots statuses : %s, %s and %s", STATUS_TO_REVIEW, STATUS_IN_REVIEW,
+          STATUS_REVIEWED)),
         new Change("7.8", "Security hotspots are returned by default"),
         new Change("7.7", format("Value 'authors' in parameter '%s' is deprecated, please use '%s' instead", FACETS, PARAM_AUTHOR)),
         new Change("7.6", format("The use of module keys in parameter '%s' is deprecated", PARAM_COMPONENT_KEYS)),
@@ -268,7 +277,8 @@ public class SearchAction implements IssuesWsAction {
         new Change("6.5", "parameters 'projects', 'projectUuids', 'moduleUuids', 'directories', 'fileUuids' are marked as internal"),
         new Change("6.3", "response field 'email' is renamed 'avatar'"),
         new Change("5.5", "response fields 'reporter' and 'actionPlan' are removed (drop of action plan and manual issue features)"),
-        new Change("5.5", "parameters 'reporters', 'actionPlans' and 'planned' are dropped and therefore ignored (drop of action plan and manual issue features)"),
+        new Change("5.5", "parameters 'reporters', 'actionPlans' and 'planned' are dropped and therefore ignored (drop of action plan and" +
+          " manual issue features)"),
         new Change("5.5", "response field 'debt' is renamed 'effort'"))
       .setResponseExample(getClass().getResource("search-example.json"));
 
@@ -279,7 +289,8 @@ public class SearchAction implements IssuesWsAction {
     action.addSortParams(IssueQuery.SORTS, null, true);
     action.createParam(PARAM_ADDITIONAL_FIELDS)
       .setSince("5.2")
-      .setDescription("Comma-separated list of the optional fields to be returned in response. Action plans are dropped in 5.5, it is not returned in the response.")
+      .setDescription("Comma-separated list of the optional fields to be returned in response. Action plans are dropped in 5.5, it is not" +
+        " returned in the response.")
       .setPossibleValues(SearchAdditionalField.possibleValues());
     addComponentRelatedParams(action);
     action.createParam(PARAM_ISSUES)
@@ -363,7 +374,8 @@ public class SearchAction implements IssuesWsAction {
       .setDescription("Comma-separated list of CWE identifiers. Use '" + UNKNOWN_STANDARD + "' to select issues not associated to any CWE.")
       .setExampleValue("12,125," + UNKNOWN_STANDARD);
     action.createParam(PARAM_SONARSOURCE_SECURITY)
-      .setDescription("Comma-separated list of SonarSource security categories. Use '" + SQCategory.OTHERS.getKey() + "' to select issues not associated" +
+      .setDescription("Comma-separated list of SonarSource security categories. Use '" + SQCategory.OTHERS.getKey() + "' to select issues" +
+        " not associated" +
         " with any category")
       .setSince("7.8")
       .setPossibleValues(Arrays.stream(SQCategory.values()).map(SQCategory::getKey).toList());
@@ -371,7 +383,8 @@ public class SearchAction implements IssuesWsAction {
       .setDescription("SCM accounts. To set several values, the parameter must be called once for each value.")
       .setExampleValue("author=torvalds@linux-foundation.org&author=linux@fondation.org");
     action.createParam(PARAM_ASSIGNEES)
-      .setDescription("Comma-separated list of assignee logins. The value '__me__' can be used as a placeholder for user who performs the request")
+      .setDescription("Comma-separated list of assignee logins. The value '__me__' can be used as a placeholder for user who performs the" +
+        " request")
       .setExampleValue("admin,usera,__me__");
     action.createParam(PARAM_ASSIGNED)
       .setDescription("To retrieve assigned or unassigned issues")
@@ -407,7 +420,8 @@ public class SearchAction implements IssuesWsAction {
       .setSince("9.4");
     action.createParam(PARAM_TIMEZONE)
       .setDescription(
-        "To resolve dates passed to '" + PARAM_CREATED_AFTER + "' or '" + PARAM_CREATED_BEFORE + "' (does not apply to datetime) and to compute creation date histogram")
+        "To resolve dates passed to '" + PARAM_CREATED_AFTER + "' or '" + PARAM_CREATED_BEFORE + "' (does not apply to datetime) and to " +
+          "compute creation date histogram")
       .setRequired(false)
       .setExampleValue("'Europe/Paris', 'Z' or '+02:00'")
       .setSince("8.6");
@@ -431,7 +445,8 @@ public class SearchAction implements IssuesWsAction {
 
     action.createParam(PARAM_COMPONENTS)
       .setDeprecatedKey(PARAM_COMPONENT_KEYS, "10.2")
-      .setDescription("Comma-separated list of component keys. Retrieve issues associated to a specific list of components (and all its descendants). " +
+      .setDescription("Comma-separated list of component keys. Retrieve issues associated to a specific list of components (and all its " +
+        "descendants). " +
         "A component can be a portfolio, project, module, directory or file.")
       .setExampleValue(KEY_PROJECT_EXAMPLE_001);
 
@@ -465,6 +480,13 @@ public class SearchAction implements IssuesWsAction {
       .setDescription("Pull request id. Not available in the community edition.")
       .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001)
       .setSince("7.1");
+
+    action.createParam(PARAM_FIXED_IN_PULL_REQUEST)
+      .setDescription("Pull request id to filter issues that would be fixed in the specified project or branch by the pull request. " +
+        "Should not be used together with + '" + PARAM_PULL_REQUEST + "'. At least the '" + PARAM_COMPONENTS + "' must be be specified " +
+        "when this param is used.  Not available in the community edition.")
+      .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001)
+      .setSince("10.4");
   }
 
   @Override
@@ -487,7 +509,7 @@ public class SearchAction implements IssuesWsAction {
       .filter(FACETS_REQUIRING_PROJECT::contains)
       .collect(Collectors.toSet());
     checkArgument(facetsRequiringProjectParameter.isEmpty() ||
-      (!query.projectUuids().isEmpty()), "Facet(s) '%s' require to also filter by project",
+        (!query.projectUuids().isEmpty()), "Facet(s) '%s' require to also filter by project",
       String.join(",", facetsRequiringProjectParameter));
 
     // execute request
@@ -519,7 +541,8 @@ public class SearchAction implements IssuesWsAction {
   }
 
   private static TotalHits getTotalHits(SearchResponse response) {
-    return ofNullable(response.getHits().getTotalHits()).orElseThrow(() -> new IllegalStateException("Could not get total hits of search results"));
+    return ofNullable(response.getHits().getTotalHits()).orElseThrow(() -> new IllegalStateException("Could not get total hits of search " +
+      "results"));
   }
 
   private static SearchOptions createSearchOptionsFromRequest(SearchRequest request) {
@@ -527,12 +550,10 @@ public class SearchAction implements IssuesWsAction {
     options.setPage(request.getPage(), request.getPageSize());
 
     List<String> facets = request.getFacets();
-
-    if (facets == null || facets.isEmpty()) {
-      return options;
+    if (facets != null && !facets.isEmpty()) {
+      options.addFacets(facets);
     }
 
-    options.addFacets(facets);
     return options;
   }
 
@@ -657,7 +678,8 @@ public class SearchAction implements IssuesWsAction {
       .setCwe(request.paramAsStrings(PARAM_CWE))
       .setSonarsourceSecurity(request.paramAsStrings(PARAM_SONARSOURCE_SECURITY))
       .setTimeZone(request.param(PARAM_TIMEZONE))
-      .setCodeVariants(request.paramAsStrings(PARAM_CODE_VARIANTS));
+      .setCodeVariants(request.paramAsStrings(PARAM_CODE_VARIANTS))
+      .setFixedInPullRequest(request.param(PARAM_FIXED_IN_PULL_REQUEST));
   }
 
   private void checkIfNeedIssueSync(DbSession dbSession, SearchRequest searchRequest) {
index 698f15d21f1ee6604e47465ba3fabc4ebae92364..3dfa936b314fefbc0a59d3843cb5805773f3fac6 100644 (file)
@@ -23,14 +23,10 @@ public class KeyExamples {
   public static final String KEY_APPLICATION_EXAMPLE_001 = "my_app";
 
   public static final String KEY_FILE_EXAMPLE_001 = "my_project:/src/foo/Bar.php";
-  public static final String KEY_FILE_EXAMPLE_002 = "another_project:/src/foo/Foo.php";
   public static final String KEY_PROJECT_EXAMPLE_001 = "my_project";
   public static final String KEY_PROJECT_EXAMPLE_002 = "another_project";
   public static final String KEY_PROJECT_EXAMPLE_003 = "third_project";
 
-  public static final String KEY_ORG_EXAMPLE_001 = "my-org";
-  public static final String KEY_ORG_EXAMPLE_002 = "foo-company";
-
   public static final String KEY_BRANCH_EXAMPLE_001 = "feature/my_branch";
   public static final String KEY_PULL_REQUEST_EXAMPLE_001 = "5461";
 
index f03e1f0fffb8a6706e16c4438411bcc16ccc649a..3c557ffe473e9a36cecb77a5c1f618cb5b4730a6 100644 (file)
@@ -109,6 +109,7 @@ public class IssuesWsParameters {
   public static final String PARAM_ADDITIONAL_FIELDS = "additionalFields";
   public static final String PARAM_TIMEZONE = "timeZone";
   public static final String PARAM_CODE_VARIANTS = "codeVariants";
+  public static final String PARAM_FIXED_IN_PULL_REQUEST = "fixedInPullRequest";
 
   public static final String FACET_MODE_EFFORT = "effort";