@@ -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); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); |
@@ -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); | |||
} |
@@ -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); |
@@ -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")); |
@@ -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(); |
@@ -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); | |||
} | |||
} | |||
@@ -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) { |
@@ -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"; | |||
@@ -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"; | |||