mirror of
https://github.com/ansible/awx.git
synced 2026-02-25 23:16:01 -03:30
When jobs are still processing events, the UI uses numerical ranges based on job_event.counter instead of page numbers. We can't apply search filters in this state because then there would be no way to distinguish between events that are missing due to being filtered out by search and events that are missing because they're still being processed. The UI must be able to distinguish between the two types of missing events because their absence is presented differently. Events that are filtered out by a search query have no visual representation, while events that are missing due to event processing or other causes are displayed as clickable "..." segments.
372 lines
11 KiB
JavaScript
372 lines
11 KiB
JavaScript
/* eslint camelcase: 0 */
|
|
import {
|
|
EVENT_START_PLAYBOOK,
|
|
EVENT_STATS_PLAY,
|
|
EVENT_START_PLAY,
|
|
EVENT_START_TASK,
|
|
HOST_STATUS_KEYS,
|
|
JOB_STATUS_COMPLETE,
|
|
JOB_STATUS_INCOMPLETE,
|
|
JOB_STATUS_UNSUCCESSFUL,
|
|
JOB_STATUS_FINISHED,
|
|
} from './constants';
|
|
|
|
function JobStatusService (moment, message) {
|
|
this.dispatch = () => message.dispatch('status', this.state);
|
|
this.subscribe = listener => message.subscribe('status', listener);
|
|
|
|
this.init = ({ model }) => {
|
|
this.model = model;
|
|
this.created = model.get('created');
|
|
this.job = model.get('id');
|
|
this.jobType = model.get('type');
|
|
this.project = model.get('project');
|
|
|
|
this.active = false;
|
|
this.latestTime = null;
|
|
this.counter = -1;
|
|
|
|
this.state = {
|
|
running: false,
|
|
anyFailed: false,
|
|
counts: {
|
|
plays: 0,
|
|
tasks: 0,
|
|
hosts: 0,
|
|
},
|
|
hosts: {},
|
|
status: model.get('status'),
|
|
elapsed: model.get('elapsed'),
|
|
started: model.get('started'),
|
|
finished: model.get('finished'),
|
|
environment: model.get('custom_virtualenv'),
|
|
artifacts: model.get('artifacts'),
|
|
scm: {
|
|
id: model.get('summary_fields.project_update.id'),
|
|
status: model.get('summary_fields.project_update.status')
|
|
},
|
|
scmBranch: model.get('scm_branch'),
|
|
scmRefspec: model.get('scm_refspec'),
|
|
inventoryScm: {
|
|
id: model.get('source_project_update'),
|
|
status: model.get('summary_fields.inventory_source.status')
|
|
},
|
|
event_processing_finished: model.get('event_processing_finished'),
|
|
};
|
|
|
|
this.initHostStatusCounts({ model });
|
|
this.initPlaybookCounts({ model });
|
|
|
|
this.updateRunningState();
|
|
this.dispatch();
|
|
};
|
|
|
|
this.initHostStatusCounts = ({ model }) => {
|
|
if (model.has('host_status_counts')) {
|
|
this.setHostStatusCounts(model.get('host_status_counts'));
|
|
} else {
|
|
const hostStatusCounts = this.createHostStatusCounts(this.state.status);
|
|
|
|
this.setHostStatusCounts(hostStatusCounts);
|
|
}
|
|
};
|
|
|
|
this.initPlaybookCounts = ({ model }) => {
|
|
if (model.has('playbook_counts')) {
|
|
this.setPlaybookCounts(model.get('playbook_counts'));
|
|
} else {
|
|
this.setPlaybookCounts({ task_count: 1, play_count: 1 });
|
|
}
|
|
};
|
|
|
|
this.createHostStatusCounts = status => {
|
|
if (JOB_STATUS_UNSUCCESSFUL.includes(status)) {
|
|
return { failures: 1 };
|
|
}
|
|
|
|
if (JOB_STATUS_COMPLETE.includes(status)) {
|
|
return { ok: 1 };
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
this.pushStatusEvent = data => {
|
|
const isJobStatusEvent = (this.job === data.unified_job_id);
|
|
const isProjectStatusEvent = (this.project && (this.project === data.project_id));
|
|
const isInventoryScmStatus = (this.model.get('source_project_update') === data.unified_job_id);
|
|
|
|
if (isJobStatusEvent) {
|
|
this.setJobStatus(data.status);
|
|
if (JOB_STATUS_FINISHED.includes(data.status)) {
|
|
this.sync();
|
|
}
|
|
this.dispatch();
|
|
} else if (isProjectStatusEvent) {
|
|
this.setProjectStatus(data.status);
|
|
this.setProjectUpdateId(data.unified_job_id);
|
|
this.dispatch();
|
|
} else if (isInventoryScmStatus) {
|
|
this.setInventoryScmStatus(data.status);
|
|
this.setInventoryScmId(data.unified_job_id);
|
|
this.dispatch();
|
|
}
|
|
};
|
|
|
|
this.pushJobEvent = data => {
|
|
const isLatest = ((!this.counter) || (data.counter > this.counter));
|
|
|
|
let changed = false;
|
|
|
|
if (!this.active && !(data.event === EVENT_STATS_PLAY)) {
|
|
this.active = true;
|
|
this.setJobStatus('running');
|
|
changed = true;
|
|
}
|
|
|
|
if (isLatest) {
|
|
this.counter = data.counter;
|
|
this.latestTime = data.created;
|
|
this.setElapsed(moment(data.created).diff(this.created, 'seconds'));
|
|
changed = true;
|
|
}
|
|
|
|
if (data.event === EVENT_START_PLAYBOOK) {
|
|
this.setStarted(this.state.started || data.created);
|
|
changed = true;
|
|
}
|
|
|
|
if (data.event === EVENT_START_PLAY) {
|
|
this.state.counts.plays++;
|
|
changed = true;
|
|
}
|
|
|
|
if (data.event === EVENT_START_TASK) {
|
|
this.state.counts.tasks++;
|
|
changed = true;
|
|
}
|
|
|
|
if (data.event === EVENT_STATS_PLAY) {
|
|
this.setStatsEvent(data);
|
|
changed = true;
|
|
}
|
|
|
|
if (data.failed) {
|
|
this.state.anyFailed = true;
|
|
}
|
|
|
|
if (changed) {
|
|
this.dispatch();
|
|
}
|
|
};
|
|
|
|
this.isExpectingStatsEvent = () => (this.jobType === 'job') ||
|
|
(this.jobType === 'project_update') ||
|
|
(this.jobType === 'ad_hoc_command');
|
|
|
|
this.updateStats = () => {
|
|
this.updateHostCounts();
|
|
|
|
if (this.statsEvent) {
|
|
this.setFinished(this.statsEvent.created);
|
|
|
|
const failures = _.get(this.statsEvent, ['event_data', 'failures'], {});
|
|
const dark = _.get(this.statsEvent, ['event_data', 'dark'], {});
|
|
|
|
if (this.statsEvent.failed ||
|
|
Object.keys(failures).length > 0 ||
|
|
Object.keys(dark).length > 0) {
|
|
this.setJobStatus('failed');
|
|
} else {
|
|
this.setJobStatus('successful');
|
|
}
|
|
} else if (this.state.counter > -1) {
|
|
this.setJobStatus(this.state.anyFailed ? 'failed' : 'unknown');
|
|
} else {
|
|
this.setJobStatus('unknown');
|
|
}
|
|
};
|
|
|
|
this.updateRunningState = () => {
|
|
this.state.running = (Boolean(this.state.started) && !this.state.finished) ||
|
|
(this.state.status === 'running') ||
|
|
(this.state.status === 'pending') ||
|
|
(this.state.status === 'waiting');
|
|
};
|
|
|
|
this.updateHostCounts = () => {
|
|
const countedHostNames = [];
|
|
|
|
const counts = Object.assign(...HOST_STATUS_KEYS.map(key => ({ [key]: 0 })));
|
|
|
|
HOST_STATUS_KEYS.forEach(key => {
|
|
const hostData = _.get(this.statsEvent, ['event_data', key], {});
|
|
|
|
Object.keys(hostData).forEach(hostName => {
|
|
const isAlreadyCounted = (countedHostNames.indexOf(hostName) > -1);
|
|
const shouldBeCounted = ((!isAlreadyCounted) && hostData[hostName] > 0);
|
|
|
|
if (shouldBeCounted) {
|
|
countedHostNames.push(hostName);
|
|
counts[key]++;
|
|
}
|
|
});
|
|
});
|
|
|
|
this.state.counts.hosts = countedHostNames.length;
|
|
this.setHostStatusCounts(counts);
|
|
};
|
|
|
|
this.setJobStatus = status => {
|
|
const isExpectingStats = this.isExpectingStatsEvent();
|
|
const isIncomplete = JOB_STATUS_INCOMPLETE.includes(status);
|
|
const isFinished = JOB_STATUS_FINISHED.includes(status);
|
|
const isAlreadyFinished = JOB_STATUS_FINISHED.includes(this.state.status);
|
|
|
|
if (isAlreadyFinished && !isFinished) {
|
|
return;
|
|
}
|
|
|
|
if ((isExpectingStats && isIncomplete) || (!isExpectingStats && isFinished)) {
|
|
if (this.latestTime) {
|
|
if (!this.state.finished) {
|
|
this.setFinished(this.latestTime);
|
|
}
|
|
|
|
if (!this.state.started && this.state.elapsed) {
|
|
this.setStarted(moment(this.latestTime)
|
|
.subtract(this.state.elapsed, 'seconds'));
|
|
}
|
|
}
|
|
}
|
|
|
|
this.state.status = status;
|
|
this.updateRunningState();
|
|
};
|
|
|
|
this.setElapsed = elapsed => {
|
|
if (!elapsed) return;
|
|
|
|
this.state.elapsed = elapsed;
|
|
};
|
|
|
|
this.setStarted = started => {
|
|
if (!started) return;
|
|
|
|
this.state.started = started;
|
|
this.updateRunningState();
|
|
};
|
|
|
|
this.setProjectStatus = status => {
|
|
this.state.scm.status = status;
|
|
};
|
|
|
|
this.setProjectUpdateId = id => {
|
|
this.state.scm.id = id;
|
|
};
|
|
|
|
this.setInventoryScmStatus = status => {
|
|
this.state.inventoryScm.status = status;
|
|
};
|
|
|
|
this.setInventoryScmId = id => {
|
|
this.state.inventoryScm.id = id;
|
|
};
|
|
|
|
this.setFinished = time => {
|
|
if (!time) return;
|
|
|
|
this.state.finished = time;
|
|
this.updateRunningState();
|
|
};
|
|
|
|
this.setEnvironment = env => {
|
|
if (!env) return;
|
|
|
|
this.state.environment = env;
|
|
};
|
|
|
|
this.setArtifacts = val => {
|
|
if (!val) return;
|
|
|
|
this.state.artifacts = val;
|
|
};
|
|
|
|
this.setExecutionNode = node => {
|
|
if (!node) return;
|
|
|
|
this.state.executionNode = node;
|
|
};
|
|
|
|
this.setStatsEvent = data => {
|
|
if (!data) return;
|
|
|
|
this.statsEvent = data;
|
|
};
|
|
|
|
this.setResultTraceback = traceback => {
|
|
if (!traceback) return;
|
|
|
|
this.state.resultTraceback = traceback;
|
|
};
|
|
|
|
this.setEventProcessingFinished = val => {
|
|
this.state.event_processing_finished = val;
|
|
};
|
|
|
|
this.setHostStatusCounts = counts => {
|
|
counts = counts || {};
|
|
|
|
HOST_STATUS_KEYS.forEach(key => {
|
|
counts[key] = counts[key] || 0;
|
|
});
|
|
|
|
if (!this.state.counts.hosts) {
|
|
this.state.counts.hosts = Object.keys(counts)
|
|
.reduce((sum, key) => sum + counts[key], 0);
|
|
}
|
|
|
|
this.state.hosts = counts;
|
|
};
|
|
|
|
this.setPlaybookCounts = ({ play_count, task_count }) => {
|
|
this.state.counts.plays = play_count;
|
|
this.state.counts.tasks = task_count;
|
|
};
|
|
|
|
this.resetCounts = () => {
|
|
this.state.counts.plays = 0;
|
|
this.state.counts.tasks = 0;
|
|
this.state.counts.hosts = 0;
|
|
};
|
|
|
|
this.sync = () => {
|
|
const { model } = this;
|
|
|
|
return model.http.get({ resource: model.get('id') })
|
|
.then(() => {
|
|
this.setFinished(model.get('finished'));
|
|
this.setElapsed(model.get('elapsed'));
|
|
this.setStarted(model.get('started'));
|
|
this.setJobStatus(model.get('status'));
|
|
this.setEnvironment(model.get('custom_virtualenv'));
|
|
this.setArtifacts(model.get('artifacts'));
|
|
this.setExecutionNode(model.get('execution_node'));
|
|
this.setResultTraceback(model.get('result_traceback'));
|
|
this.setEventProcessingFinished(model.get('event_processing_finished'));
|
|
|
|
this.initHostStatusCounts({ model });
|
|
this.initPlaybookCounts({ model });
|
|
|
|
this.dispatch();
|
|
});
|
|
};
|
|
}
|
|
|
|
JobStatusService.$inject = [
|
|
'moment',
|
|
'OutputMessageService',
|
|
];
|
|
|
|
export default JobStatusService;
|