marcells/node-build-monitor

View on GitHub
app/monitor.js

Summary

Maintainability
C
7 hrs
Test Coverage
var async = require('async'),
    events = require('events'),
    log = function (text, debug) {
        if(debug) {
            console.log(new Date().toLocaleTimeString(), '|', text);
        }
    },
    generateAndApplyETags = function (newBuilds) {
        for (var i = 0; i < newBuilds.length; i++) {
            var build = newBuilds[i];

            build.etag = require('crypto')
                .createHash('md5')
                .update(JSON.stringify(build))
                .digest('hex');
        }
    },
    sortBuilds = function (newBuilds, sortOrder, errorFirst) {
        if(sortOrder == "project") {
            newBuilds.sort(function (a, b) {
                if(a.project > b.project) return 1;
                if(a.project < b.project) return -1;

                return 0;
            });
        }
        else {
            sortBuildsByDate(newBuilds);
        }
        
        if (errorFirst)
        {
            var errorChunk = [];
            var normalChunk = [];
            for (var i = 0; i < newBuilds.length; i++)
            {
                if (newBuilds[i].hasErrors)
                    errorChunk.push(newBuilds[i]);
                else
                    normalChunk.push(newBuilds[i]);
            }

            newBuilds = errorChunk.concat(normalChunk);
        }

        return newBuilds;
    },
    sortBuildsByDate = function(newBuilds){
        var takeDate = function (build) {
                return build.isRunning ? build.startedAt : build.finishedAt;
            };

        newBuilds.sort(function (a, b) {
            var dateA = takeDate(a);
            var dateB = takeDate(b);

            if (dateA && dateB) {
              if(dateA < dateB) return 1;
              if(dateA > dateB) return -1;

              return 0;
            }
            else {
              // if either of the date is not available, compare by id
              // build with higher id is sorted before build with lower id
              return b.id.toString().localeCompare(a.id.toString());
            }
        });
    },
    distinctBuildsByETag = function (newBuilds) {
        var unique = {};

        for (var i = newBuilds.length - 1; i >= 0; i--) {
            var build = newBuilds[i];

            if (unique[build.etag]) {
                newBuilds.splice(i, 1);
            }

            unique[build.etag] = true;
        }
    },
    onlyTake = function (numberOfBuilds, newBuilds) {
        newBuilds.splice(numberOfBuilds);
    },
    changed = function (currentBuilds, newBuilds) {
        var newBuildsHash = newBuilds
            .map(function (value) {
                return value.etag;
            })
            .join('|');

        var currentBuildsHash = currentBuilds
            .map(function (value) {
                return value.etag;
            })
            .join('|');

        return newBuildsHash !== currentBuildsHash;
    },
    detectChanges = function (currentBuilds, newBuilds) {
        var changes = {
                added: [],
                removed: [],
                updated: []
            },
            getById = function (builds, id) {
                return builds.filter(function (build) {
                    return build.id === id;
                })[0];
            };

        var currentBuildIds = currentBuilds.map(function (build) { return build.id; });
        var newBuildIds = newBuilds.map(function (build) { return build.id; });

        newBuildIds.forEach(function (newBuildId) {
            if (currentBuildIds.indexOf(newBuildId) === -1) {
                changes.added.push(getById(newBuilds, newBuildId));
            }

            if (currentBuildIds.indexOf(newBuildId) >= 0) {
                var currentBuild = getById(currentBuilds, newBuildId);
                var newBuild = getById(newBuilds, newBuildId);

                if (currentBuild.etag !== newBuild.etag) {
                    changes.updated.push(getById(newBuilds, newBuildId));
                }
            }
        });

        currentBuildIds.forEach(function (currentBuildId) {
            if (newBuildIds.indexOf(currentBuildId) === -1) {
                changes.removed.push(getById(currentBuilds, currentBuildId));
            }
        });

        changes.order = newBuildIds;

        return changes;
    },
    shouldOnlyTakeLatestBuild = function (globalConfiguration, pluginConfiguration) {
      return (globalConfiguration.latestBuildOnly && pluginConfiguration != undefined && pluginConfiguration.latestBuildOnly === undefined) ||
             (pluginConfiguration != undefined && pluginConfiguration.latestBuildOnly);
    };

module.exports = function () {
    var self = this;

    self.configuration = {
        interval: 5000,
        numberOfBuilds: 12,
        debug: false
    };
    self.plugins = [];
    self.currentBuilds = [];

    self.configure = function (config) {
        self.configuration = config;
    };

    self.watchOn = function (plugin) {
        self.plugins.push(plugin);
    };

    self.run = function () {
        var allBuilds = [];

        async.each(self.plugins, function (plugin, callback) {
            log('Check for builds...', self.configuration.debug);

            plugin.check(function (error, pluginBuilds) {
                if (error) {
                  if (self.configuration.debug) {
                    console.log('**********************************************************************');
                    console.log('An error occured when fetching builds for the following configuration:');
                    console.log('----------------------------------------------------------------------');
                    console.log(plugin.configuration);
                    console.log('----------------------------------------------------------------------');
                    console.log();
                    console.error(error);
                    console.log('**********************************************************************');
                    console.log();
                    console.log();
                  } else {
                    console.log('**********************************************************************');
                    console.log('An error occured when fetching builds:');
                    console.log('----------------------------------------------------------------------');
                    console.error(error);
                    console.log('**********************************************************************');
                    console.log();
                    console.log();
                  }
                  
                  callback();
                  
                  return;
                }

                if(pluginBuilds.length > 0) {
                    if (shouldOnlyTakeLatestBuild(self.configuration, plugin.configuration)) {
                        sortBuildsByDate(pluginBuilds);
                        Array.prototype.push.apply(allBuilds, [pluginBuilds.shift()]);
                    } else {
                        Array.prototype.push.apply(allBuilds, pluginBuilds);
                    }
                }
                callback();
            });
        },
        function (error) {
            log(allBuilds.length + ' builds found....', self.configuration.debug);

            generateAndApplyETags(allBuilds);
            distinctBuildsByETag(allBuilds);
            allBuilds = sortBuilds(allBuilds, self.configuration.sortOrder, self.configuration.errorsFirst);

            if(!self.configuration.latestBuildOnly) {
              onlyTake(self.configuration.numberOfBuilds, allBuilds);
            }

            if(changed(self.currentBuilds, allBuilds)) {
                log('builds changed', self.configuration.debug);

                self.emit('buildsChanged', detectChanges(self.currentBuilds, allBuilds));

                self.currentBuilds = allBuilds;
            }

            setTimeout(self.run, self.configuration.interval);
        });
    };
};

module.exports.prototype = new events.EventEmitter();