BuildEventListener.java

package com.capitalone.dashboard.event;

import com.capitalone.dashboard.event.sync.SyncDashboard;
import com.capitalone.dashboard.model.BaseModel;
import com.capitalone.dashboard.model.Build;
import com.capitalone.dashboard.model.BuildStatus;
import com.capitalone.dashboard.model.Collector;
import com.capitalone.dashboard.model.CollectorItem;
import com.capitalone.dashboard.model.Component;
import com.capitalone.dashboard.model.Dashboard;
import com.capitalone.dashboard.model.Pipeline;
import com.capitalone.dashboard.model.PipelineCommit;
import com.capitalone.dashboard.model.PipelineStage;
import com.capitalone.dashboard.model.SCM;
import com.capitalone.dashboard.repository.CollectorItemRepository;
import com.capitalone.dashboard.repository.CollectorRepository;
import com.capitalone.dashboard.repository.CommitRepository;
import com.capitalone.dashboard.repository.ComponentRepository;
import com.capitalone.dashboard.repository.DashboardRepository;
import com.capitalone.dashboard.repository.PipelineRepository;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.capitalone.dashboard.util.PipelineUtils.isMoveCommitToBuild;
import static com.capitalone.dashboard.util.PipelineUtils.processPreviousFailedBuilds;

@org.springframework.stereotype.Component
public class BuildEventListener extends HygieiaMongoEventListener<Build> {
    private final DashboardRepository dashboardRepository;
    private final ComponentRepository componentRepository;
    private final CommitRepository commitRepository;
    private final SyncDashboard syncDashboard;

    @Autowired
    public BuildEventListener(DashboardRepository dashboardRepository,
                              CollectorItemRepository collectorItemRepository,
                              ComponentRepository componentRepository,
                              PipelineRepository pipelineRepository,
                              CollectorRepository collectorRepository, CommitRepository commitRepository, SyncDashboard syncDashboard) {
        super(collectorItemRepository, pipelineRepository, collectorRepository);
        this.dashboardRepository = dashboardRepository;
        this.componentRepository = componentRepository;
        this.commitRepository = commitRepository;
        this.syncDashboard = syncDashboard;
    }

    @Override
    public void onAfterSave(AfterSaveEvent<Build> event) {
        Build build = event.getSource();
        //if a build is successful, process it
        if (build.getBuildStatus().equals(BuildStatus.Success)) {
            processBuild(event.getSource());
        } else if (build.getBuildStatus().equals(BuildStatus.Failure)) {
            processFailedBuild(event.getSource());
        }
        syncDashboard.sync(build);
    }

    /**
     * If the build has failed, find the pipelines of the dashboards referencing the build and add the failed build to
     * the failed builds bucket on the pipeline
     *
     * @param failedBuild
     */
    private void processFailedBuild(Build failedBuild) {
        List<Dashboard> teamDashboardsReferencingBuild = findAllDashboardsForBuild(failedBuild);
        for (Dashboard teamDashboard : teamDashboardsReferencingBuild) {
            Pipeline pipeline = getOrCreatePipeline(teamDashboard);
            pipeline.addFailedBuild(failedBuild);
            pipelineRepository.save(pipeline);
        }
    }

    /**
     * Find all dashboards referencing the build and then then for each commit in the changeset of the build (as per jenkins)
     * add the commit to the pipeline for the dashboard
     *
     * @param build
     */
    private void processBuild(Build build) {
        List<Dashboard> teamDashboardsReferencingBuild = findAllDashboardsForBuild(build);

        //for every team dashboard referencing the build, find the pipeline, put this commit in the build stage
        for (Dashboard teamDashboard : teamDashboardsReferencingBuild) {
            Pipeline pipeline = getOrCreatePipeline(teamDashboard);

            for (SCM scm : build.getSourceChangeSet()) {
                // we want to use the build start time since the timestamp was just the time that the collector ran
                PipelineCommit commit = new PipelineCommit(scm, build.getStartTime());
                pipeline.addCommit(PipelineStage.BUILD.getName(), commit);
            }

            processPreviousFailedBuilds(build, pipeline);


            /**
             * If some build events are missed, here is an attempt to move commits to the build stage
             * This also takes care of the problem with Jenkins first build change set being empty.
             *
             * Logic:
             * If the build start time is after the scm commit, move the commit to build stage. Match the repo at the very least.
             */
            Map<String, PipelineCommit> commitStageCommits = pipeline.getCommitsByEnvironmentName(PipelineStage.COMMIT.getName());
            Map<String, PipelineCommit> buildStageCommits = pipeline.getCommitsByEnvironmentName(PipelineStage.BUILD.getName());
            for (String rev : commitStageCommits.keySet()) {
                PipelineCommit commit = commitStageCommits.get(rev);
                if ((commit.getScmCommitTimestamp() < build.getStartTime()) && !buildStageCommits.containsKey(rev) && isMoveCommitToBuild(build, commit, commitRepository)) {
                    pipeline.addCommit(PipelineStage.BUILD.getName(), commit);
                }
            }
            pipelineRepository.save(pipeline);

        }
    }

    /**
     * Finds all of the dashboards for a given build way of the build by:
     * 1. Get collector item id for the build
     * 2. Get the build components referencing the build collectoritem
     * 3. Find all dashboards whose application references the build components
     *
     * @param build
     * @return
     */
    private List<Dashboard> findAllDashboardsForBuild(Build build) {
        List<Dashboard> dashboards = new ArrayList<>();
        if (build == null || build.getCollectorItemId() == null) {
            //return an empty list if the build is not associated with a Dashboard
            return dashboards;
        }
        CollectorItem buildCollectorItem = collectorItemRepository.findOne(build.getCollectorItemId());
        if(buildCollectorItem != null) {
            List<Component> components = componentRepository.findByBuildCollectorItemId(buildCollectorItem.getId());
            if (!components.isEmpty()) {
                //return an empty list if the build is not associated with a Dashboard
                List<ObjectId> componentIds = components.stream().map(BaseModel::getId).collect(Collectors.toList());
                dashboards = dashboardRepository.findByApplicationComponentIdsIn(componentIds);
            }
        }
        return dashboards;
    }


    private CollectorItem getCollectorItem(ObjectId id) {
        return collectorItemRepository.findOne(id);
    }

    private Collector getCollector(ObjectId id) {
        return collectorRepository.findOne(id);
    }
}