underlying infrastructure for dealing with commits is working now

This commit is contained in:
John Mitchell 2016-10-03 15:47:37 -04:00 committed by jaredevantabor
parent 11592047d6
commit bb82bfe95a
3 changed files with 236 additions and 65 deletions

View File

@ -4,36 +4,55 @@
* All Rights Reserved
*************************************************/
export default ['jobResultsService', function(jobResultsService){
export default ['jobResultsService', '$q', function(jobResultsService, $q){
var val = {};
// Get the count of the last event
var getPreviousCount = function(counter) {
// get the ids of all the queue
var counters = Object.keys(val.queue).map(counter => parseInt(counter));
var previousCount = $q.defer();
// iterate backwards to find the last count
while(counters.indexOf(counter - 1) > -1) {
counter = counter - 1;
if (val.queue[counter].count) {
// need to create a new copy of count when returning
// so that it is accurate for the particular event
return _.clone(val.queue[counter].count);
// iteratively find the last count
var findCount = function(counter) {
if (counter === 0) {
// if counter is 0, no count has been initialized
// initialize one!
previousCount.resolve({
ok: 0,
skipped: 0,
unreachable: 0,
failures: 0,
changed: 0
});
} else if (val.queue[counter] && val.queue[counter].count) {
// this event has a count, resolve!
previousCount.resolve(_.clone(val.queue[counter].count));
} else {
// this event doesn't have a count, decrement to the
// previous event and check it
findCount(counter - 1);
}
};
if (val.queue[counter - 1]) {
// if the previous event has been resolved, start the iterative
// get previous count process
findCount(counter - 1);
} else if (val.populateDefers[counter - 1]){
// if the previous event has not been resolved, wait for it to
// be and then start the iterative get previous count process
val.populateDefers[counter - 1].promise.then(function() {
findCount(counter - 1);
});
}
// no count initialized
return {
ok: 0,
skipped: 0,
unreachable: 0,
failures: 0,
changed: 0
};
return previousCount.promise;
};
// munge the raw event from the backend into the event_queue's format
var mungeEvent = function(event) {
var mungedEventDefer = $q.defer();
// basic data needed in the munged event
var mungedEvent = {
counter: event.counter,
id: event.id,
@ -41,58 +60,175 @@ export default ['jobResultsService', function(jobResultsService){
name: event.event_name
};
// for different types of events, you need different types of data
if (event.event_name === 'playbook_on_start') {
// sets count initially so this is a change
mungedEvent.count = getPreviousCount(mungedEvent.counter);
mungedEvent.count = {
ok: 0,
skipped: 0,
unreachable: 0,
failures: 0,
changed: 0
};
mungedEvent.changes = ['count'];
mungedEventDefer.resolve(mungedEvent);
} else if (event.event_name === 'runner_on_ok' ||
event.event_name === 'runner_on_async_ok') {
mungedEvent.count = getPreviousCount(mungedEvent.counter);
mungedEvent.count.ok++;
mungedEvent.changes = ['count'];
getPreviousCount(mungedEvent.counter)
.then(count => {
mungedEvent.count = count;
mungedEvent.count.ok++;
mungedEvent.changes = ['count'];
mungedEventDefer.resolve(mungedEvent);
});
} else if (event.event_name === 'runner_on_skipped') {
mungedEvent.count = getPreviousCount(mungedEvent.counter);
mungedEvent.count.skipped++;
mungedEvent.changes = ['count'];
getPreviousCount(mungedEvent.counter)
.then(count => {
mungedEvent.count = count;
mungedEvent.count.skipped++;
mungedEvent.changes = ['count'];
mungedEventDefer.resolve(mungedEvent);
});
} else if (event.event_name === 'runner_on_unreachable') {
mungedEvent.count = getPreviousCount(mungedEvent.counter);
mungedEvent.count.unreachable++;
mungedEvent.changes = ['count'];
getPreviousCount(mungedEvent.counter)
.then(count => {
mungedEvent.count = count;
mungedEvent.count.unrecahble++;
mungedEvent.changes = ['count'];
mungedEventDefer.resolve(mungedEvent);
});
} else if (event.event_name === 'runner_on_error' ||
event.event_name === 'runner_on_async_failed') {
mungedEvent.count = getPreviousCount(mungedEvent.counter);
mungedEvent.count.failed++;
mungedEvent.changes = ['count'];
getPreviousCount(mungedEvent.counter)
.then(count => {
mungedEvent.count = count;
mungedEvent.count.failed++;
mungedEvent.changes = ['count'];
mungedEventDefer.resolve(mungedEvent);
});
} else if (event.event_name === 'playbook_on_stats') {
// get the data for populating the host status bar
mungedEvent.count = jobResultsService
.getCountsFromStatsEvent(event.event_data);
mungedEvent.changes = ['count', 'countFinished'];
mungedEventDefer.resolve(mungedEvent);
} else {
mungedEventDefer.resolve(mungedEvent);
}
return mungedEvent;
return mungedEventDefer.promise;
};
val = {
populateDefers: {},
queue: {},
// reinitializes the event queue value for the job results page
//
// TODO: implement some sort of local storage scheme
// to make viewing job details that the user has
// previous clicked on super quick, this would be where you grab
// from local storage
initialize: function() {
val.queue = {};
val.populateDefers = {};
},
// populates the event queue
populate: function(event) {
// don't populate the event if it's already been added either
// by rest or by websocket event
if (!val.queue[event.counter]) {
var mungedEvent = mungeEvent(event);
val.queue[mungedEvent.counter] = mungedEvent;
return mungedEvent;
// if a defer hasn't been set up for the event,
// set one up now
if (!val.populateDefers[event.counter]) {
val.populateDefers[event.counter] = $q.defer();
}
if (!val.queue[event.counter]) {
var resolvePopulation = function(event) {
// to resolve, put the event on the queue and
// then resolve the deferred value
//
// TODO: implement some sort of local storage scheme
// to make viewing job details that the user has
// previous clicked on super quick, this would be
// where you put in local storage
val.queue[event.counter] = event;
val.populateDefers[event.counter].resolve(event);
}
if (event.counter === 1) {
// for the first event, go ahead and munge and
// resolve
mungeEvent(event).then(event => {
resolvePopulation(event);
});
} else {
// for all other events, you have to do some things
// to keep the event processing in the UI synchronous
if (!val.populateDefers[event.counter - 1]) {
// first, if the previous event doesn't have
// a defer set up (this happens when websocket
// events are coming in and you need to make
// rest calls to catch up), go ahead and set a
// defer for the previous event
val.populateDefers[event.counter - 1] = $q.defer();
}
// you can start the munging process...
mungeEvent(event).then(event => {
// ...but wait until the previous event has
// been resolved before resolving this one and
// doing stuff in the ui (that's why we
// needed that previous conditional). this
// could be done in a more asynchronous nature
// if we need a performance boost. See the
// todo note in the markProcessed function
// for an idea
val.populateDefers[event.counter - 1].promise
.then(() => {
resolvePopulation(event);
});
});
}
} else {
// don't repopulate the event if it's already been added
// and munged either by rest or by websocket event
val.populateDefers[event.counter]
.resolve(val.queue[event.counter]);
}
return val.populateDefers[event.counter].promise;
},
// the event has been processed in the view and should be marked as
// completed in the queue
markProcessed: function(event) {
val.queue[event.counter].processed = true;
var process = function(event) {
// the event has now done it's work in the UI, record
// that!
val.queue[event.counter].processed = true;
// TODO: we can actually record what has been done in the
// UI and at what event too! (something like "resolved
// the count on event 63)".
//
// if we do something like this, we actually wouldn't
// have to wait until the previous events had completed
// before resolving and returning to the controller
// in populate()...
// in other words, we could send events out of order to
// the controller, but let the controller know that it's
// an older event that what is in the view so we don't
// need to do anything
};
if (!val.queue[event.counter]) {
// sometimes, the process is called in the controller and
// the event queue hasn't caught up and actually added
// the event to the queue yet. Wait until that happens
val.populateDefers[event.counter].promise
.finally(function() {
process(event);
});
} else {
process(event);
}
}
};

View File

@ -76,36 +76,55 @@ export default ['jobData', 'jobDataOptions', 'jobLabels', 'count', '$scope', 'Pa
$scope.hostCount = getTotalHostCount(count.val);
$scope.countFinished = count.countFinished;
// Process incoming job status changes
$rootScope.$on('JobStatusChange-jobDetails', function(e, data) {
if (parseInt(data.unified_job_id, 10) === parseInt($scope.job.id,10)) {
$scope.job.status = data.status;
}
});
// EVENT STUFF BELOW
// just putting the event queue on scope so it can be inspected in the
// console
$scope.event_queue = eventQueue.queue;
$scope.defersArr = eventQueue.populateDefers;
// This is where the async updates to the UI actually happen.
// Flow is event queue munging in the service -> $scope setting in here
var processEvent = function(event) {
// put the event in the queue
var mungedEvent = eventQueue.populate(event);
eventQueue.populate(event).then(mungedEvent => {
// make changes to ui based on the event returned from the queue
if (mungedEvent.changes) {
mungedEvent.changes.forEach(change => {
// we've got a change we need to make to the UI!
// update the necessary scope and make the change
if (change === 'count' && !$scope.countFinished) {
// for all events that affect the host count,
// update the status bar as well as the host
// count badge
$scope.count = mungedEvent.count;
$scope.hostCount = getTotalHostCount(mungedEvent
.count);
}
// make changes to ui based on the event returned from the queue
if (mungedEvent.changes) {
mungedEvent.changes.forEach(change => {
if (change === 'count' && !$scope.countFinished) {
$scope.count = mungedEvent.count;
$scope.hostCount = getTotalHostCount(mungedEvent
.count);
}
if (change === 'countFinished') {
// the playbook_on_stats event actually lets
// us know that we don't need to iteratively
// look at event to update the host counts
// any more.
$scope.countFinished = true;
}
});
}
if (change === 'countFnished') {
$scope.countFinished = true;
}
});
}
// the changes have been processed in the ui, mark it in the queue
eventQueue.markProcessed(event);
// the changes have been processed in the ui, mark it in the queue
eventQueue.markProcessed(event);
});
};
// grab completed event data and process each event
// PULL! grab completed event data and process each event
var getEvents = function(url) {
jobResultsService.getEvents(url)
.then(events => {
@ -122,15 +141,14 @@ export default ['jobData', 'jobDataOptions', 'jobLabels', 'count', '$scope', 'Pa
};
getEvents($scope.job.related.job_events);
// process incoming job events
// PUSH! process incoming job events
$rootScope.event_socket.on("job_events-" + $scope.job.id, function(data) {
processEvent(data);
});
// process incoming job status changes
$rootScope.$on('JobStatusChange-jobDetails', function(e, data) {
if (parseInt(data.unified_job_id, 10) === parseInt($scope.job.id,10)) {
$scope.job.status = data.status;
}
// STOP! stop listening to job events
$scope.$on('$destroy', function() {
$rootScope.event_socket.removeAllListeners("job_events-" +
$scope.job.id);
});
}];

View File

@ -16,6 +16,7 @@ export default {
label: '{{ job.id }} - {{ job.name }}'
},
resolve: {
// the GET for the particular job
jobData: ['Rest', 'GetBasePath', '$stateParams', '$q', '$state', 'Alert', function(Rest, GetBasePath, $stateParams, $q, $state, Alert) {
Rest.setUrl(GetBasePath('jobs') + $stateParams.id);
var val = $q.defer();
@ -35,6 +36,10 @@ export default {
});
return val.promise;
}],
// after the GET for the job, this helps us keep the status bar from
// flashing as rest data comes in. If the job is finished and
// there's a playbook_on_stats event, go ahead and resolve the count
// so you don't get that flashing!
count: ['jobData', 'jobResultsService', 'Rest', '$q', function(jobData, jobResultsService, Rest, $q) {
var defer = $q.defer();
if (jobData.finished) {
@ -72,6 +77,8 @@ export default {
}
return defer.promise;
}],
// GET for the particular jobs labels to be displayed in the
// left-hand pane
jobLabels: ['Rest', 'GetBasePath', '$stateParams', '$q', function(Rest, GetBasePath, $stateParams, $q) {
var getNext = function(data, arr, resolve) {
Rest.setUrl(data.next);
@ -101,6 +108,9 @@ export default {
return seeMoreResolve.promise;
}],
// OPTIONS request for the job. Used to make things like the
// verbosity data in the left-hand pane prettier than just an
// integer
jobDataOptions: ['Rest', 'GetBasePath', '$stateParams', '$q', function(Rest, GetBasePath, $stateParams, $q) {
Rest.setUrl(GetBasePath('jobs') + $stateParams.id);
var val = $q.defer();
@ -112,6 +122,11 @@ export default {
});
return val.promise;
}],
// This gives us access to the job events socket so we can start
// listening for updates we need to make for the ui as data comes in
//
// TODO: we could probably make this better by not initing
// job_events for completed jobs
jobEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) {
if (!$rootScope.event_socket) {
$rootScope.event_socket = Socket({
@ -126,6 +141,8 @@ export default {
return true;
}
}],
// This clears out the event queue, otherwise it'd be full of events
// for previous job results the user had navigated to
eventQueueInit: ['eventQueue', function(eventQueue) {
eventQueue.initialize();
}]