marcells/node-build-monitor

View on GitHub
app/services/GitLab.js

Summary

Maintainability
F
3 days
Test Coverage
var request = require('request'),
    async = require('async');

module.exports = function () {
    var self = this,
        flatten = function(arrayOfArray) {
            return [].concat.apply([], arrayOfArray);
        },
        buildProjectUrl = function(projectId) {
            return self.config.url + '/api/v4/projects/' + projectId;
        },
        buildProjectPipelinesUrl = function(projectId, ref) {
            var url = self.config.url + '/api/v4/projects/' + projectId + '/pipelines';
            if(ref) { url = url + '?ref=' + ref; }
            return url;
        },
        buildPipelineDetailsUrl = function(projectId, pipelineId) {
            return self.config.url + '/api/v4/projects/' + projectId + '/pipelines/' + pipelineId;
        },
        buildJobUrl = function(projectId, pipelineId) {
            return self.config.url + '/api/v4/projects/' + projectId + '/pipelines/' + pipelineId + '/jobs/';
        },
        getProjectsApiUrl = function(page, perPage) {
            var query = '?page=' + page + '&per_page=' + perPage + self.config.additional_query;
            return self.config.url + '/api/v4/projects' + query;
        },
        getRequestHeaders = function() {
            return { 'PRIVATE-TOKEN': self.config.token };
        },
        makeRequest = function (url, callback) {
            request({
                headers: getRequestHeaders(),
                'url': url,
                json: true
            }, function(err, response, body) {
                callback(err, body);
            });
        },
        getProjectPipelines = function(project, callback) {
            makeRequest(buildProjectPipelinesUrl(project.id, project.ref), function(err, pipelines) {
                if(err) {
                    callback(err);
                    return;
                }
                if(pipelines && pipelines.slice) {
                    pipelines = pipelines.filter(function(pipeline) {
                        return (self.config.pipeline.status.includes(pipeline.status));
                    });

                    if(typeof self.config.numberOfPipelinesPerProject !== 'undefined') {
                        pipelines = pipelines.slice(0, self.config.numberOfPipelinesPerProject);
                    }
                } else {
                    pipelines = [];
                }
                async.map(pipelines, function(pipeline, callback) {
                    getPipelineDetails(project, pipeline.id, callback);
                }, callback);
            });
        },
        getPipelineDetails = function(project, pipelineId, callback) {
            async.waterfall([
                function(callback) {
                    makeRequest(buildPipelineDetailsUrl(project.id, pipelineId), callback);
                },
                function(pipeline, callback) {
                    makeRequest(buildJobUrl(project.id, pipelineId), function(err, jobs) {
                        pipeline.jobs = jobs;
                        callback(err, simplifyBuild(project, pipeline));
                    });
                }
            ], callback);
        },
        getBuilds = function(callback) {
            self.projects = [];
            loadProjects(function () {
                _getBuilds(callback);
            });
        },
        _getBuilds = function(callback) {
            async.map(self.projects, getProjectPipelines, function(err, builds) {
                if(err) {
                    callback(err);
                    return;
                }
                callback(err, flatten(builds));
            });
        },
        simplifyBuild = function(project, build) {
            return {
                id: project.id + '|' + build.id,
                number: build.id,
                project: project.name + '/' + build.ref,
                branch: build.ref,
                commit: build.sha ? build.sha.substr(0, 7) : undefined,
                isRunning: ['running', 'pending'].includes(build.status),
                startedAt: getDateTime(build.started_at),
                finishedAt: getDateTime(build.finished_at),
                requestedFor: getAuthor(build),
                status: getBuildStatus(build.status, build.detailed_status, build.jobs),
                statusText: build.status,
                reason: getCommitMessage(build),
                hasErrors: false,
                hasWarnings: getHasWarnings(build.detailed_status),
                url: getBuildUrl(project, build)
            };
        },
        getDateTime = function(dateTime) {
            return dateTime ? new Date(dateTime) : dateTime;
        },
        getCommitMessage = function(build) {
            var job = build.jobs && build.jobs[0];
            return job && job.commit ? job.commit.message : undefined;
        },
        getAuthor = function(build) {
            var job = build.jobs && build.jobs[0];
            return job && job.commit ? job.commit.author_name : undefined;
        },
        getHasWarnings = function (detailed_status) {
          return detailed_status.icon === "status_warning";
        },
        getBuildStatus = function (status, detailed_status, jobs) {
            switch (status) {
                case 'pending':
                    return '#ffa500';
                case 'running':
                    return 'Blue';
                case 'failed':
                    return 'Red';
                case 'success':
                    if (getHasWarnings(detailed_status)) {
                      return '#ffa500';
                    }
                    return 'Green';
                case 'manual':
                    return getStatusForManual(detailed_status, jobs);
                default:
                  return 'Gray';
            }
        },
      getStatusForManual = function (detailed_status, jobs) {
            return getBuildStatus(jobs
                .map(job => job.status)
                .includes('running') ? 'running' : 'success',
                  detailed_status);
    },
        getBuildUrl = function(project, build) {
            if(build.jobs && build.jobs[0]) {
                var base = self.config.url + '/';
                return base + project.path_with_namespace + '/-/jobs/' + build.jobs[0].id; //Not sure whether this works
            } else {
                return "";
            }
        },
        getAllProjects = function(callback) {
            request({
                headers: getRequestHeaders(),
                'url': getProjectsApiUrl(1, 100),
                json: true
            }, function(err, response, body) {
                if (!err && response.statusCode === 200) {
                    var urls = [], pages = Math.ceil(
                        parseInt(response.headers['x-total-pages'], 10));
                    for (var i = 1; i <= pages; i = i + 1) {
                        urls.push(getProjectsApiUrl(i, 100));
                    }

                    async.map(urls, makeRequest, function(err, projects){
                        callback(err, flatten(projects));
                    });
                }
            });
        },
        loadProjects = function(callback) {
            var slugs = self.config.slugs,
                matchers = slugs.map(slug => slug.project),
                findNamespaceIndexInMatchers = function(namespace) {
                    for(var i = 0; i < matchers.length; i++){
                        var matcher = matchers[i];
                        if(matcher.endsWith("/**")){
                            prefix = matcher.replace("/**","");
                            if(namespace.full_path.startsWith(prefix)) return i;
                        }else{
                            if( matcher === namespace.full_path + "/*") return i;
                        }
                    }
                    return -1;
                };

            getAllProjects(function(err, projects){
                if(err) return;
                var indexOfAllMatch = matchers.indexOf('*/*');
                projects.forEach(function(project){
                    var indexOfNamespace = findNamespaceIndexInMatchers(project.namespace),
                        indexOfProject = matchers.indexOf(project.path_with_namespace),
                        index = indexOfAllMatch > -1 ? indexOfAllMatch : (
                            indexOfNamespace > -1 ? indexOfNamespace : (
                            indexOfProject > -1 ? indexOfProject : null));

                    if(index !== null) {
                        if(slugs[index].ref) {
                            project.ref = slugs[index].ref;
                        }
                        self.projects.push(project);
                    }
                });
                callback();
            });
        };

    self.configure = function (config) {
        self.config = config;
        self.projects = [];
        if (typeof self.config.slugs === 'undefined') {
            self.config.slugs = [{project: '*/*'}];
        }
        if (typeof self.config.additional_query === 'undefined') {
            self.config.additional_query = "";
        }
        if(typeof self.config.pipeline === 'undefined' || typeof self.config.pipeline.status === 'undefined') {
            self.config.pipeline = {
              status: ['running', 'pending', 'success', 'failed', 'canceled', 'skipped']
            };
        }
        if (typeof process.env.GITLAB_TOKEN !== 'undefined') {
            self.config.token = process.env.GITLAB_TOKEN;
        }
        if (typeof self.config.caPath !== 'undefined') {
            request = request.defaults({
                agentOptions: {
                    ca: require('fs').readFileSync(self.config.caPath).toString().split("\n\n")
                }
            });
        }
    };

    self.check = getBuilds;
};