rest-api/src/jetbrains/buildServer/server/rest/model/change/ChangeStatus.java (254 lines of code) (raw):

/* * Copyright 2000-2024 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.buildServer.server.rest.model.change; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; import jetbrains.buildServer.BuildProblemData; import jetbrains.buildServer.controllers.changes.BuildStatusText; import jetbrains.buildServer.messages.ErrorData; import jetbrains.buildServer.messages.Status; import jetbrains.buildServer.server.rest.data.problem.TestCountersData; import jetbrains.buildServer.server.rest.model.Fields; import jetbrains.buildServer.server.rest.model.ItemsProviders; import jetbrains.buildServer.server.rest.model.PagerData; import jetbrains.buildServer.server.rest.model.build.Builds; import jetbrains.buildServer.server.rest.swagger.annotations.ModelDescription; import jetbrains.buildServer.server.rest.util.BeanContext; import jetbrains.buildServer.server.rest.util.VirtualBuildsUtil; import jetbrains.buildServer.server.rest.util.ValueWithDefault; import jetbrains.buildServer.serverSide.*; import jetbrains.buildServer.serverSide.auth.SecurityContext; import jetbrains.buildServer.serverSide.problems.BuildProblem; import jetbrains.buildServer.users.SUser; import jetbrains.buildServer.users.StandardProperties; import jetbrains.buildServer.util.StringUtil; import jetbrains.buildServer.vcs.BuildTypeChangeStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @XmlType(name = "changeStatus") @ModelDescription( value = "Aggregated statuses of the first builds with this change. Fairly expensive to compute." ) public class ChangeStatus { private static final String CRITICAL_BUILDS_FIELD = "criticalBuilds"; private static final String COMPILATION_ERROR_BUILDS_FIELD = "compilationErrorBuilds"; private static final String NEW_TESTS_FAILED_BUILDS_FIELD = "newTestsFailedBuilds"; private static final String NON_CRITICAL_BUILDS_FIELD = "notCriticalBuilds"; private final Fields myFields; private final jetbrains.buildServer.vcs.ChangeStatus myChangeStatus; private final BeanContext myBeanContext; private int myCancelledCount; private int myQueuedBuildsCount; private int myPendingCount; private int myFinishedBuildsCount; private int myFailedBuildsCount; private int mySuccessfulBuildsCount; private int myRunningBuildsCount; private int myRunningSuccessfullyCount; private int myNewFailedTests; private int myOtherFailedTests; private int myTotalProblemCount; private BuildsCollector myCriticalCollector; private BuildsCollector myCompilationErrorCollector; private BuildsCollector myNewTestsFailedCollector; private BuildsCollector myNotCriticalCollector; public ChangeStatus() { myFields = null; myChangeStatus = null; myBeanContext = null; } public ChangeStatus(@NotNull jetbrains.buildServer.vcs.ChangeStatus mergedStatus, @NotNull Fields fields, @NotNull final BeanContext beanContext) { myFields = fields; myChangeStatus = mergedStatus; myBeanContext = beanContext; initCounters(); initTests(); } @XmlAttribute(name = "runningBuilds") public Integer getRunning() { return myRunningBuildsCount; } @XmlAttribute(name = "runningSuccessfullyBuilds") public Integer getRunningSuccessfuly() { return myRunningSuccessfullyCount; } @XmlAttribute(name = "pendingBuildTypes") public Integer getPendingBuildTypes() { return myPendingCount; } @XmlAttribute(name = "finishedBuilds") public Integer getFinished() { return myFinishedBuildsCount; } @XmlAttribute(name = "successfulBuilds") public Integer getSuccessful() { return mySuccessfulBuildsCount; } @XmlAttribute(name = "failedBuilds") public Integer getFailed() { return myFailedBuildsCount; } @XmlAttribute(name = "cancelledBuilds") public Integer getCancelled() { return myCancelledCount; } @XmlAttribute(name = "totalProblems") public Integer getTotalProblemCount() { return myTotalProblemCount; } @XmlAttribute(name = "newFailedTests") public Integer getNewFailedTests() { return myNewFailedTests; } @XmlAttribute(name = "otherFailedTests") public Integer getOtherFailedTests() { return myOtherFailedTests; } @XmlAttribute(name = "queuedBuildsCount") public Integer getQueuedBuildsCount() { return myQueuedBuildsCount; } @XmlElement(name = CRITICAL_BUILDS_FIELD) public Builds getCriticalBuilds() { return ValueWithDefault.decideDefault( myFields.isIncluded(CRITICAL_BUILDS_FIELD), Builds.createFromBuildPromotions(myCriticalCollector, myFields.getNestedField(CRITICAL_BUILDS_FIELD), myBeanContext) ); } @XmlElement(name = NON_CRITICAL_BUILDS_FIELD) public Builds getNonCriticalBuilds() { return ValueWithDefault.decideDefault( myFields.isIncluded(NON_CRITICAL_BUILDS_FIELD), Builds.createFromBuildPromotions(myNotCriticalCollector, myFields.getNestedField(NON_CRITICAL_BUILDS_FIELD), myBeanContext) ); } @XmlElement(name = NEW_TESTS_FAILED_BUILDS_FIELD) public Builds getNewTestsFailedBuilds() { return ValueWithDefault.decideDefault( myFields.isIncluded(NEW_TESTS_FAILED_BUILDS_FIELD), Builds.createFromBuildPromotions(myNewTestsFailedCollector, myFields.getNestedField(NEW_TESTS_FAILED_BUILDS_FIELD), myBeanContext) ); } @XmlElement(name = COMPILATION_ERROR_BUILDS_FIELD) public Builds getCompilationErrorBuilds() { return ValueWithDefault.decideDefault( myFields.isIncluded(COMPILATION_ERROR_BUILDS_FIELD), Builds.createFromBuildPromotions(myCompilationErrorCollector, myFields.getNestedField(COMPILATION_ERROR_BUILDS_FIELD), myBeanContext) ); } private void initTests() { SecurityContext context = myBeanContext.getSingletonService(SecurityContext.class); List<STestRun> testRuns = myChangeStatus.getFirstBuilds().values().stream() .filter(Objects::nonNull) .filter(b -> !VirtualBuildsUtil.isVirtualBuild(b.getBuildPromotion())) .filter(b -> !b.isCompositeBuild() || VirtualBuildsUtil.isParallelizedBuild(b.getBuildPromotion())) .flatMap(b -> b.getShortStatistics().getFailedTests().stream()) .collect(Collectors.toList()); TestCountersData counters = new TestCountersData(testRuns, false, true, false, false, true, false); myNewFailedTests = counters.getNewFailed(); myOtherFailedTests = counters.getFailed() - counters.getNewFailed(); } private void initCounters() { // This is heavily inspired by ChangeDetailsCalculator, but with some simplifications, so not reusing it here. SecurityContext context = myBeanContext.getSingletonService(SecurityContext.class); SUser self = (SUser) context.getAuthorityHolder().getAssociatedUser(); myCriticalCollector = new BuildsCollector(myFields.getNestedField("criticalBuilds")); myCompilationErrorCollector = new BuildsCollector(myFields.getNestedField("compilationErrorBuilds")); myNewTestsFailedCollector = new BuildsCollector(myFields.getNestedField("newTestsFailedBuilds")); myNotCriticalCollector = new BuildsCollector(myFields.getNestedField("notCriticalBuilds")); final boolean includePersonalBuilds = self != null && StringUtil.isTrue(self.getPropertyValue(StandardProperties.SHOW_ALL_PERSONAL_BUILDS)); for (BuildTypeChangeStatus status : myChangeStatus.getBuildTypesStatus().values()) { final SBuild firstBuild = status.getFirstBuild(); if (firstBuild == null) { SQueuedBuild queued = status.getQueuedBuild(); if(queued != null && (includePersonalBuilds || !queued.isPersonal())) { myQueuedBuildsCount++; } if (status.getQueuedBuild() == null) { myPendingCount++; } continue; } if(firstBuild.isPersonal() && !includePersonalBuilds) { continue; } if(firstBuild.getCanceledInfo() != null) { myCancelledCount++; continue; } if (firstBuild.isFinished()) { myFinishedBuildsCount++; if (status.isSuccessful()) { mySuccessfulBuildsCount++; continue; // no need to count problems, as our build is green } } else { myRunningBuildsCount++; Status runningBuildStatus = firstBuild.getBuildStatus(); if(runningBuildStatus.isSuccessful()) { myRunningSuccessfullyCount++; continue; // no need to count problems, as our build is green } } myFailedBuildsCount++; String statusText = BuildStatusText.getBuildStatus(firstBuild, self); final BuildPromotionEx buildPromo = (BuildPromotionEx) firstBuild.getBuildPromotion(); switch (statusText) { case BuildStatusText.NEW_TESTS_FAILED: myNewTestsFailedCollector.put(buildPromo); break; case BuildStatusText.CRITICAL_PROBLEM: myCriticalCollector.put(buildPromo); break; default: myNotCriticalCollector.put(buildPromo); } boolean compilationErrorCounted = false; for (BuildProblem problem : buildPromo.getBuildProblems()) { if (problem.isMutedInBuild()) continue; final String problemType = problem.getBuildProblemData().getType(); if (BuildProblemData.TC_FAILED_TESTS_TYPE.equals(problemType) || ErrorData.isSnapshotDependencyError(problemType)) continue; if(BuildProblemData.TC_COMPILATION_ERROR_TYPE.equals(problemType) && !compilationErrorCounted) { myCompilationErrorCollector.put(buildPromo); compilationErrorCounted = true; } myTotalProblemCount++; } } } /** * Simple utility class to avoid storing builds promotions if not necessary. */ private class BuildsCollector implements ItemsProviders.ItemsRetriever<BuildPromotion> { private int myCount = 0; private List<BuildPromotion> myPromotions = null; BuildsCollector(@NotNull Fields fields) { if(fields.isIncluded("build", false, true)) { myPromotions = new ArrayList<>(); } } public void put(@NotNull BuildPromotion promotion) { myCount++; if(myPromotions != null) { myPromotions.add(promotion); } } @Nullable @Override public List<BuildPromotion> getItems() { return myPromotions; } @Override public Integer getCount() { return myCount; } @Override public boolean isCountCheap() { return true; } @Nullable @Override public PagerData getPagerData() { return null; } } }