in rest-api/src/jetbrains/buildServer/server/rest/data/finder/impl/BuildPromotionFinder.java [1121:1504]
public ItemHolder<BuildPromotion> getPrefilteredItems(@NotNull Locator locator) {
if (!myPermissionChecker.hasPermissionInAnyProject(Permission.VIEW_PROJECT)){
return ItemHolder.of(Collections.emptyList()); //TeamCity issue: should be handled in core. Do not spend any resources on user without permissions
}
final Boolean byPromotion = locator.getSingleDimensionValueAsBoolean(BY_PROMOTION);
if (byPromotion != null && !byPromotion) {
throw new BadRequestException("Found '" + BY_PROMOTION + "' locator set to 'false' which is not supported");
}
final String strob = locator.getSingleDimensionValue(STROB);
if (strob != null) {
final Locator strobLocator = new Locator(strob, BUILD_TYPE, BRANCH, STROB_BUILD_LOCATOR);
List<Locator> partialLocators = Collections.singletonList(Locator.createEmptyLocator());
partialLocators = getPartialLocatorsForStrobDimension(partialLocators, strobLocator, BUILD_TYPE, myBuildTypeFinder);
partialLocators = getPartialLocatorsForStrobDimension(partialLocators, strobLocator, BRANCH, myBranchFinder);
final String strobBuildLocator = strobLocator.getSingleDimensionValue(STROB_BUILD_LOCATOR);
final List<ItemHolder<BuildPromotion>> strobResult = new ArrayList<>();
for (Locator partialLocator : partialLocators) {
partialLocator.setDimensionIfNotPresent(PagerData.COUNT, "1"); //limit to single item per strob item by default
final String finalBuildLocator = Locator.createLocator(strobBuildLocator, partialLocator, new String[]{}).getStringRepresentation();
strobResult.add(ItemHolder.of(getItems(finalBuildLocator).getEntries()));
}
strobLocator.checkLocatorFullyProcessed();
return ItemHolder.concat(strobResult);
}
setLocatorDefaults(locator);
final String equivalent = locator.getSingleDimensionValue(EQUIVALENT);
if (equivalent != null) {
final BuildPromotionEx build = (BuildPromotionEx)getItem(equivalent);
return ItemHolder.of(sortPromotions(build.getStartedEquivalentPromotions(-1).stream()));
}
final String metadata = locator.getSingleDimensionValue(METADATA);
if (metadata != null) {
Iterable<BuildMetadataEntry> metadataEntries = () -> getBuildMetadataEntryIterator(metadata);
return processor -> {
for (BuildMetadataEntry metadataEntry : metadataEntries) {
SBuild build = myBuildsManager.findBuildInstanceById(metadataEntry.getBuildId());
if (build != null && Build.canViewRuntimeData(myPermissionChecker, build.getBuildPromotion())) {
processor.processItem(build.getBuildPromotion());
}
}
};
}
final String graphLocator = locator.getSingleDimensionValue(ORDERED);
if (graphLocator != null) {
final GraphFinder<BuildPromotion> graphFinder = new BuildPromotionOrderedFinder(this);
//consider performance optimization by converting id to build only on actual retrieve (use GraphFinder<Int/buildId>)
return ItemHolder.of(graphFinder.getItems(graphLocator).getEntries());
}
final String snapshotDepDimension = locator.getSingleDimensionValue(SNAPSHOT_DEP);
if (snapshotDepDimension != null) {
return ItemHolder.of(getSnapshotRelatedBuilds(snapshotDepDimension));
}
final String artifactDepDimension = locator.getSingleDimensionValue(ARTIFACT_DEP);
if (artifactDepDimension != null) {
return ItemHolder.of(getArtifactRelatedBuilds(artifactDepDimension, locator));
}
final String snapshotDepProblem = locator.getSingleDimensionValue(SNAPSHOT_PROBLEM);
if (snapshotDepProblem != null) {
return ItemHolder.of(getSnapshotDepProblemBuilds(snapshotDepProblem));
}
if(locator.isUnused(NUMBER) && locator.isUnused(BUILD_TYPE)) {
final String buildNumber = getBuildNumberIfEqualsCondition(locator);
if(buildNumber != null) {
final String buildTypeLocator = locator.getSingleDimensionValue(BUILD_TYPE);
final List<SBuildType> buildTypes = myBuildTypeFinder.getBuildTypes(null, buildTypeLocator);
Stream<BuildPromotion> promotions = buildTypes.stream()
.flatMap(bt -> myBuildsManager.findBuildInstancesByBuildNumber(bt.getBuildTypeId(), buildNumber).stream())
.map(SBuild::getBuildPromotion);
return ItemHolder.of(sortPromotions(promotions));
}
}
/*
Search by build number and project id
*/
if(TeamCityProperties.getBooleanOrTrue("rest.builds.selectByProjectAndBuildNumberOptimization.enabled")) {
if (locator.isUnused(NUMBER) && locator.isUnused(PROJECT)) {
final String buildNumber = getBuildNumberIfEqualsCondition(locator);
if (buildNumber != null) {
SProject project = getProjectFromDimension(locator, PROJECT);
if (project != null) {
List<SBuildType> buildTypes = project.getOwnBuildTypes();
return getBuildsByBuildTypesAndBuildNumber(buildTypes, buildNumber);
}
}
}
if(locator.isUnused(NUMBER) && locator.isUnused(AFFECTED_PROJECT)) {
final String buildNumber = getBuildNumberIfEqualsCondition(locator);
if (buildNumber != null) {
SProject project = getProjectFromDimension(locator, AFFECTED_PROJECT);
if (project != null) {
List<SBuildType> buildTypes = project.getBuildTypes();
return getBuildsByBuildTypesAndBuildNumber(buildTypes, buildNumber);
}
}
}
}
if (locator.isAnyPresent(TAG) && TeamCityProperties.getBooleanOrTrue("rest.request.builds.prefilterByTag")) {
Locator stateLocator = getStateLocator(new Locator(locator)); //using locator copy so that no dimensions are marked as used
if (isStateIncluded(stateLocator, STATE_FINISHED)) {//no sense in going further here if no finished builds are requested
final List<String> tagLocators = locator.lookupDimensionValue(TAG); //not marking as used to enforce filter processing later
Stream<BuildPromotion> finishedBuilds = TagFinder.getPrefilteredFinishedBuildPromotions(tagLocators, myServiceLocator);
if (finishedBuilds != null) {
FilterConditionChecker<BuildPromotion> tagsFilter = getFilterByTag(tagLocators);
// After this point no other builds will be added
locator.markUsed(TAG);
Stream<BuildPromotion> queuedBuilds = Stream.empty();
if(isStateIncluded(stateLocator, STATE_QUEUED)) {
queuedBuilds = myBuildQueue.getItems().stream()
.map(SQueuedBuild::getBuildPromotion);
}
Stream<BuildPromotion> runningBuilds = Stream.empty();
if(isStateIncluded(stateLocator, STATE_RUNNING)) {
// BuildsManager.processBuilds could be used here instead of getting finished and running builds separately, but BuildQueryOptions support
// only exact tag name match, so we can't satisfy all possible TAG queriy conditions with it. To counter that we use
// TagFinder.getPrefilteredFinishedBuildPromotions to obtain a subset of required finished builds and BuildsManager.getRunningBuilds()
// to get all runing builds and later filter everything properly.
runningBuilds = myBuildsManager.getRunningBuilds().stream()
.map(SRunningBuild::getBuildPromotion);
}
// None of the concatenated streams were properly filtered, so let's filter them now.
// This hopefully allows for avoiding of hitting a lookupLimit because of a large queue.
Stream<BuildPromotion> allBuildsFilteredByTag = Stream.concat(Stream.concat(queuedBuilds, runningBuilds), finishedBuilds)
.filter(tagsFilter::isIncluded);
return processor -> allBuildsFilteredByTag.forEach(processor::processItem);
}
}
}
final String testOccurrence = locator.getSingleDimensionValue(TEST_OCCURRENCE);
if (testOccurrence != null) {
TestOccurrenceFinder testOccurrenceFinder = myServiceLocator.getSingletonService(TestOccurrenceFinder.class);
return ItemHolder.of(testOccurrenceFinder.getItems(testOccurrence).getEntries().stream().map(sTestRun -> sTestRun.getBuild().getBuildPromotion()));
}
SBuildAgent agent;
final String agentLocator = locator.getSingleDimensionValue(AGENT);
if (agentLocator != null) {
List<SBuildAgent> agents = myAgentFinder.getItemsNotEmpty(agentLocator).getEntries();
if (agents.size() == 1) {
agent = agents.get(0);
} else { //only if builds processor cannot handle this
Stream<BuildPromotion> result = Stream.empty();
Locator stateLocator = getStateLocator(locator);
if (isStateIncluded(stateLocator, STATE_QUEUED)) {
//todo: should sort backwards as currently the order does not seem right...
result =
Stream.concat(result, myBuildQueue.getItems().stream().filter(build -> !CollectionsUtil.intersect(build.getCanRunOnAgents(), agents).isEmpty())
.map(build -> build.getBuildPromotion()));
}
if (isStateIncluded(stateLocator, STATE_RUNNING)) { //todo: address an issue when a build can appear twice in the output
// agent instance can be different when disconnecting, so need to check id
Set<Integer> agentIds = agents.stream().map(a -> a.getId()).collect(Collectors.toSet());
Set<String> agentNames = agents.stream().map(a -> a.getName()).collect(Collectors.toSet());
result = Stream.concat(result, myBuildsManager.getRunningBuilds().stream().filter(build -> {
SBuildAgent buildAgent = build.getAgent();
int agentId = buildAgent.getId();
return agentId > 0 ? agentIds.contains(agentId) : agentNames.contains(buildAgent.getName());
}).map(build -> build.getBuildPromotion()));
}
if (isStateIncluded(stateLocator, STATE_FINISHED)) {
//todo: optimize for user and canceled
Stream<BuildPromotion> finishedBuilds = sortPromotions(
agents.stream()
.flatMap(a -> a.getBuildHistory(null, true).stream().map(b -> b.getBuildPromotion()))
);
result = Stream.concat(result, finishedBuilds);
}
return ItemHolder.of(result);
}
} else {
agent = null;
}
// process by build states
final List<BuildPromotion> result = new ArrayList<>();
Set<Long> includedPromotionIds = new HashSet<>();
@Nullable Set<SBuildType> buildTypes = getBuildTypes(locator);
String agentName;
String agentNameCondition = locator.lookupSingleDimensionValue(AGENT_NAME);
if (agentNameCondition != null) {
agentName = ParameterCondition.createValueCondition(agentNameCondition).getConstantValueIfSimpleEqualsCondition();
if (agentName != null) locator.markUsed(AGENT_NAME);
} else {
agentName = null;
}
Long agentTypeId = locator.getSingleDimensionValueAsLong(AGENT_TYPE_ID);
Locator stateLocator = getStateLocator(locator);
IncludePersonalBuildsRuling personalBuildsRuling = computePersonalBuildsRuling(locator);
if (isStateIncluded(stateLocator, STATE_QUEUED)) {
//todo: should sort backwards as currently the order does not seem right...
Stream<SQueuedBuild> builds = myBuildQueue.getItems().stream();
if(!personalBuildsRuling.isIncludePersonal()) {
builds = builds.filter(build -> !build.isPersonal());
} else {
if(personalBuildsRuling.getOwner() != null) {
builds = builds.filter(build -> !build.isPersonal() || personalBuildsRuling.getOwner().equals(build.getBuildPromotion().getOwner()));
}
}
if (buildTypes != null) { //make sure buildTypes retrieved from the locator are used
builds = builds.filter(qb -> buildTypes.contains(qb.getBuildPromotion().getParentBuildType()));
}
if (agentTypeId != null) {
builds = builds.filter(build -> build.getCanRunOnAgents().stream().anyMatch(a -> a.getAgentTypeId() == agentTypeId.intValue()));
}
if (agent != null) {
builds = builds.filter(build -> build.getCanRunOnAgents().stream().anyMatch(a -> a.equals(agent))); //todo: check
}
if (agentName != null) {
builds = builds.filter(build -> build.getCanRunOnAgents().stream().anyMatch(a -> a.getName().equals(agentName)));
}
builds.map(b -> b.getBuildPromotion()).forEach(p -> {
if (includedPromotionIds.add(p.getId())) {
result.add(p);
}
});
}
if (isStateIncluded(stateLocator, STATE_RUNNING)) {
Stream<SRunningBuild> builds;
if(personalBuildsRuling.isIncludePersonal()) {
builds = myBuildsManager.getRunningBuilds(personalBuildsRuling.myOwner, null).stream();
} else {
builds = myBuildsManager.getRunningBuilds().stream()
.filter(build -> !build.isPersonal());
}
if (buildTypes != null) { //make sure buildTypes retrieved from the locator are used
builds = builds.filter(b -> buildTypes.contains(b.getBuildPromotion().getParentBuildType()));
}
if (agentTypeId != null) {
builds = builds.filter(build -> build.getAgent().getAgentTypeId() == agentTypeId.intValue());
}
if (agent != null) {
builds = builds.filter(build -> {
SBuildAgent buildAgent = build.getAgent();
int agentId = buildAgent.getId();
return agentId > 0 ? agent.getId() == agentId : agent.getName().equals(buildAgent.getName());
});
}
if (agentName != null) {
builds = builds.filter(build -> build.getAgentName().equals(agentName));
}
builds.map(b -> b.getBuildPromotion()).forEach(p -> {
if (includedPromotionIds.add(p.getId())) {
result.add(p);
}
});
}
ItemHolder<BuildPromotion> finishedBuilds = null;
if (isStateIncluded(stateLocator, STATE_FINISHED)) {
final BuildQueryOptions options = new BuildQueryOptions();
if (buildTypes != null) {
options.setBuildTypeIds(buildTypes.stream().map(bt -> bt.getBuildTypeId()).collect(Collectors.toList()));
}
if (agentTypeId != null) {
options.setAgentTypeId(agentTypeId.intValue());
}
if (agent != null) {
options.setAgent(agent);
}
if (agentName != null) {
options.setAgentName(agentName);
}
final Boolean failedToStart = locator.lookupSingleDimensionValueAsBoolean(FAILED_TO_START);
final Boolean canceled = locator.lookupSingleDimensionValueAsBoolean(CANCELED);
if (canceled == null || canceled || failedToStart == null || failedToStart) {
options.setIncludeCanceled(true); //also includes failed to start builds, TW-32060
} else {
options.setIncludeCanceled(false);
}
final Boolean pinned = locator.getSingleDimensionValueAsBoolean(PINNED);
if (pinned != null) {
options.setPinStatus(pinned);
}
TagFinder.FilterOptions tagFilterOptions = TagFinder.getFilterOptions(locator.lookupDimensionValue(TAG), myServiceLocator);
if (tagFilterOptions != null) {
options.setTagName(tagFilterOptions.getTagName(), tagFilterOptions.getTagOwner());
}
final String branchLocatorValue = locator.lookupSingleDimensionValue(BRANCH); // do not mark dimension as used as not all can be used from it
if (branchLocatorValue != null) {
BranchFinder.BranchFilterDetails branchFilterDetails;
try {
branchFilterDetails = myBranchFinder.getBranchFilterDetailsWithoutLocatorCheck(branchLocatorValue);
// parsed OK, setting options
if (branchFilterDetails.isAnyBranch()) {
options.setMatchAllBranches(true);
} else {
if (branchFilterDetails.isDefaultBranchOrNotBranched()) {
options.setMatchAllBranches(false);
options.setBranch(Branch.DEFAULT_BRANCH_NAME);
}
if (branchFilterDetails.getBranchName() != null) {
options.setMatchAllBranches(false); //causes a bug in certain cases, see https://youtrack.jetbrains.com/issue/TW-61530 however, no performant fix is possible so far
options.setBranch(branchFilterDetails.getBranchName());
}
}
} catch (LocatorProcessException e) {
// not parsed, cannot extract name or default status
options.setMatchAllBranches(true);
}
} else {
options.setMatchAllBranches(true);
}
options.setIncludeRunning(false); //running builds are retrieved separately and appear before finished ones
options.setOrderByChanges(false);
// Do not mark COUNT dimension used as we can recieve more results than we need.
Long count = locator.lookupSingleDimensionValueAsLong(PagerData.COUNT, getDefaultPageItemsCount());
if (count != null && count != -1) {
options.setPageSize(count.intValue());
}
// In a case when we need personal builds there is a twist, hence the following check.
// BuildQueryOptions and underlayoing mechanics treats given user as if we are acting at his will.
// This means that his it will check if the given user can see
// a specific personal build according to his own permissions and user settings, which is not what we want.
// Instead, we pretend that we need all personal builds and filter out unwanted ones later.
final SUser currentUser = (SUser) myServiceLocator.getSingletonService(SecurityContext.class).getAuthorityHolder().getAssociatedUser();
if(Objects.equals(personalBuildsRuling.myOwner, currentUser)) {
options.setIncludePersonal(personalBuildsRuling.isIncludePersonal(), personalBuildsRuling.getOwner());
} else {
options.setIncludePersonal(personalBuildsRuling.isIncludePersonal(), null);
}
finishedBuilds = processor -> myBuildsManager.processBuilds(options, item -> {
if (includedPromotionIds.contains(item.getBuildPromotion().getId())) return true; // ignore already added builds
return processor.processItem(item.getBuildPromotion());
});
}
stateLocator.checkLocatorFullyProcessed();
final ItemHolder<BuildPromotion> finishedBuildsFinal = finishedBuilds;
return processor -> {
ItemHolder.of(result).process(processor);
if (finishedBuildsFinal != null) {
finishedBuildsFinal.process(processor);
}
};
}