const templateUrl = require('~features/output/details.partial.html'); let $http; let $filter; let $state; let error; let parse; let prompt; let resource; let strings; let wait; let vm; function mapChoices (choices) { if (!choices) return {}; return Object.assign(...choices.map(([k, v]) => ({ [k]: v }))); } function getStatusDetails (jobStatus) { const unmapped = jobStatus || resource.model.get('status'); if (!unmapped) { return null; } const choices = mapChoices(resource.model.options('actions.GET.status.choices')); const label = strings.get('labels.STATUS'); const icon = `fa icon-job-${unmapped}`; const value = choices[unmapped]; return { label, icon, value }; } function getStartDetails (started) { const unfiltered = started || resource.model.get('started'); const label = strings.get('labels.STARTED'); let value; if (unfiltered) { value = $filter('longDate')(unfiltered); } else { value = strings.get('details.NOT_STARTED'); } return { label, value }; } function getFinishDetails (finished) { const unfiltered = finished || resource.model.get('finished'); const label = strings.get('labels.FINISHED'); let value; if (unfiltered) { value = $filter('longDate')(unfiltered); } else { value = strings.get('details.NOT_FINISHED'); } return { label, value }; } function getModuleArgDetails () { const value = resource.model.get('module_args'); const label = strings.get('labels.MODULE_ARGS'); if (!value) { return null; } return { label, value }; } function getJobTypeDetails () { const unmapped = resource.model.get('job_type'); if (!unmapped) { return null; } const choices = mapChoices(resource.model.options('actions.GET.job_type.choices')); const label = strings.get('labels.JOB_TYPE'); const value = choices[unmapped]; return { label, value }; } function getVerbosityDetails () { const verbosity = resource.model.get('verbosity'); if (!verbosity) { return null; } const choices = mapChoices(resource.model.options('actions.GET.verbosity.choices')); const label = strings.get('labels.VERBOSITY'); const value = choices[verbosity]; return { label, value }; } function getSourceWorkflowJobDetails () { const sourceWorkflowJob = resource.model.get('summary_fields.source_workflow_job'); if (!sourceWorkflowJob) { return null; } const link = `/#/workflows/${sourceWorkflowJob.id}`; const tooltip = strings.get('tooltips.SOURCE_WORKFLOW_JOB'); return { link, tooltip }; } function getJobTemplateDetails () { const jobTemplate = resource.model.get('summary_fields.job_template'); if (!jobTemplate) { return null; } const label = strings.get('labels.JOB_TEMPLATE'); const link = `/#/templates/job_template/${jobTemplate.id}`; const value = $filter('sanitize')(jobTemplate.name); const tooltip = strings.get('tooltips.JOB_TEMPLATE'); return { label, link, value, tooltip }; } function getInventoryJobNameDetails () { if (resource.model.get('type') !== 'inventory_update') { return null; } const jobArgs = resource.model.get('job_args'); if (!jobArgs) { return null; } let parsedJobArgs; try { parsedJobArgs = JSON.parse(jobArgs); } catch (e) { return null; } if (!Array.isArray(parsedJobArgs)) { return null; } const jobArgIndex = parsedJobArgs.indexOf('--inventory-id'); const inventoryId = parsedJobArgs[jobArgIndex + 1]; if (jobArgIndex < 0) { return null; } if (!Number.isInteger(parseInt(inventoryId, 10))) { return null; } const name = resource.model.get('name'); const id = resource.model.get('id'); const label = strings.get('labels.NAME'); const tooltip = strings.get('tooltips.INVENTORY'); const value = `${id} - ${$filter('sanitize')(name)}`; const link = `/#/inventories/inventory/${inventoryId}`; return { label, link, tooltip, value }; } function getInventorySourceDetails () { if (!resource.model.has('summary_fields.inventory_source.source')) { return null; } const { source } = resource.model.get('summary_fields.inventory_source'); const choices = mapChoices(resource.model.options('actions.GET.source.choices')); const label = strings.get('labels.SOURCE'); const value = choices[source]; return { label, value }; } function getOverwriteDetails () { if (!resource.model.has('overwrite')) { return null; } const label = strings.get('labels.OVERWRITE'); const value = resource.model.get('overwrite'); return { label, value }; } function getOverwriteVarsDetails () { if (!resource.model.has('overwrite_vars')) { return null; } const label = strings.get('labels.OVERWRITE_VARS'); const value = resource.model.get('overwrite_vars'); return { label, value }; } function getLicenseErrorDetails () { if (!resource.model.has('license_error')) { return null; } const label = strings.get('labels.LICENSE_ERROR'); const value = resource.model.get('license_error'); return { label, value }; } function getLaunchedByDetails () { const createdBy = resource.model.get('summary_fields.created_by'); const jobTemplate = resource.model.get('summary_fields.job_template'); const relatedSchedule = resource.model.get('related.schedule'); const schedule = resource.model.get('summary_fields.schedule'); if (!createdBy && !schedule) { return null; } const label = strings.get('labels.LAUNCHED_BY'); let link; let tooltip; let value; if (createdBy) { tooltip = strings.get('tooltips.USER'); link = `/#/users/${createdBy.id}`; value = $filter('sanitize')(createdBy.username); } else if (relatedSchedule && jobTemplate) { tooltip = strings.get('tooltips.SCHEDULE'); link = `/#/templates/job_template/${jobTemplate.id}/schedules/${schedule.id}`; value = $filter('sanitize')(schedule.name); } else { tooltip = null; link = null; value = $filter('sanitize')(schedule.name); } return { label, link, tooltip, value }; } function getInventoryDetails () { const inventory = resource.model.get('summary_fields.inventory'); if (!inventory) { return null; } const label = strings.get('labels.INVENTORY'); const tooltip = strings.get('tooltips.INVENTORY'); const value = $filter('sanitize')(inventory.name); let link; if (inventory.kind === 'smart') { link = `/#/inventories/smart/${inventory.id}`; } else { link = `/#/inventories/inventory/${inventory.id}`; } return { label, link, tooltip, value }; } function getProjectDetails () { const project = resource.model.get('summary_fields.project'); if (!project) { return null; } const label = strings.get('labels.PROJECT'); const link = `/#/projects/${project.id}`; const value = $filter('sanitize')(project.name); const tooltip = strings.get('tooltips.PROJECT'); return { label, link, value, tooltip }; } function getProjectStatusDetails (projectStatus) { const project = resource.model.get('summary_fields.project'); const jobStatus = projectStatus || resource.model.get('summary_fields.project_update.status'); if (!project) { return null; } return jobStatus; } function getProjectUpdateDetails (updateId) { const project = resource.model.get('summary_fields.project'); const jobId = updateId || resource.model.get('summary_fields.project_update.id'); if (!project) { return null; } const link = `/#/jobs/project/${jobId}`; const tooltip = strings.get('tooltips.PROJECT_UPDATE'); return { link, tooltip }; } function getSCMRevisionDetails () { const label = strings.get('labels.SCM_REVISION'); const value = resource.model.get('scm_revision'); if (!value) { return null; } return { label, value }; } function getPlaybookDetails () { const label = strings.get('labels.PLAYBOOK'); const value = resource.model.get('playbook'); if (!value) { return null; } return { label, value }; } function getJobExplanationDetails () { const explanation = resource.model.get('job_explanation'); if (!explanation) { return null; } const limit = 150; const label = strings.get('labels.JOB_EXPLANATION'); let more = explanation; if (explanation.split(':')[0] === 'Previous Task Failed') { const taskStringIndex = explanation.split(':')[0].length + 1; const task = JSON.parse(explanation.substring(taskStringIndex)); more = `${task.job_type} failed for ${task.job_name} with ID ${task.job_id}`; } const less = $filter('limitTo')(more, limit); const showMore = false; const hasMoreToShow = more.length > limit; return { label, less, more, showMore, hasMoreToShow }; } function getResultTracebackDetails () { const traceback = resource.model.get('result_traceback'); if (!traceback) { return null; } const limit = 150; const label = strings.get('labels.RESULT_TRACEBACK'); const more = traceback; const less = $filter('limitTo')(more, limit); const showMore = false; const hasMoreToShow = more.length > limit; return { label, less, more, showMore, hasMoreToShow }; } function getCredentialDetails () { const credentials = resource.model.get('summary_fields.credentials'); let credentialTags = []; if (!credentials || credentials.length < 1) { return null; } credentialTags = credentials.map((cred) => buildCredentialDetails(cred)); const label = strings.get('labels.CREDENTIAL'); const value = credentialTags; return { label, value }; } function buildCredentialDetails (credential) { const icon = `${credential.kind}`; const link = `/#/credentials/${credential.id}`; const tooltip = strings.get('tooltips.CREDENTIAL'); const value = $filter('sanitize')(credential.name); return { icon, link, tooltip, value }; } function getForkDetails () { const label = strings.get('labels.FORKS'); const value = resource.model.get('forks'); if (!value) { return null; } return { label, value }; } function getLimitDetails () { const label = strings.get('labels.LIMIT'); const value = resource.model.get('limit'); if (!value) { return null; } return { label, value }; } function getInstanceGroupDetails () { const instanceGroup = resource.model.get('summary_fields.instance_group'); if (!instanceGroup) { return null; } const label = strings.get('labels.INSTANCE_GROUP'); const value = $filter('sanitize')(instanceGroup.name); let isolated = null; if (instanceGroup.controller_id) { isolated = strings.get('details.ISOLATED'); } return { label, value, isolated }; } function getJobTagDetails () { const tagString = resource.model.get('job_tags'); let jobTags; if (tagString) { jobTags = tagString.split(',').filter(tag => tag !== ''); } else { jobTags = []; } if (jobTags.length < 1) { return null; } const label = strings.get('labels.JOB_TAGS'); const more = false; const value = jobTags.map($filter('sanitize')); return { label, more, value }; } function getSkipTagDetails () { const tagString = resource.model.get('skip_tags'); let skipTags; if (tagString) { skipTags = tagString.split(',').filter(tag => tag !== ''); } else { skipTags = []; } if (skipTags.length < 1) { return null; } const more = false; const label = strings.get('labels.SKIP_TAGS'); const value = skipTags.map($filter('sanitize')); return { label, more, value }; } function getExtraVarsDetails () { const extraVars = resource.model.get('extra_vars'); if (!extraVars) { return null; } const label = strings.get('labels.EXTRA_VARS'); const tooltip = strings.get('tooltips.EXTRA_VARS'); const value = parse(extraVars); const disabled = true; return { label, tooltip, value, disabled }; } function getLabelDetails () { const jobLabels = _.get(resource.model.get('related.labels'), 'results', []); if (jobLabels.length < 1) { return null; } const label = strings.get('labels.LABELS'); const more = false; const value = jobLabels.map(({ name }) => name).map($filter('sanitize')); return { label, more, value }; } function createErrorHandler (path, action) { return res => { const hdr = strings.get('error.HEADER'); const msg = strings.get('error.CALL', { path, action, status: res.status }); error(null, res.data, res.status, null, { hdr, msg }); }; } const ELEMENT_LABELS = '#job-results-labels'; const ELEMENT_JOB_TAGS = '#job-results-job-tags'; const ELEMENT_SKIP_TAGS = '#job-results-skip-tags'; const ELEMENT_PROMPT_MODAL = '#prompt-modal'; const TAGS_SLIDE_DISTANCE = 200; function toggleLabels () { if (!this.labels.more) { $(ELEMENT_LABELS).slideUp(TAGS_SLIDE_DISTANCE); this.labels.more = true; } else { $(ELEMENT_LABELS).slideDown(TAGS_SLIDE_DISTANCE); this.labels.more = false; } } function toggleJobTags () { if (!this.jobTags.more) { $(ELEMENT_JOB_TAGS).slideUp(TAGS_SLIDE_DISTANCE); this.jobTags.more = true; } else { $(ELEMENT_JOB_TAGS).slideDown(TAGS_SLIDE_DISTANCE); this.jobTags.more = false; } } function toggleSkipTags () { if (!this.skipTags.more) { $(ELEMENT_SKIP_TAGS).slideUp(TAGS_SLIDE_DISTANCE); this.skipTags.more = true; } else { $(ELEMENT_SKIP_TAGS).slideDown(TAGS_SLIDE_DISTANCE); this.skipTags.more = false; } } function cancelJob () { const actionText = strings.get('cancelJob.CANCEL_JOB'); const hdr = strings.get('cancelJob.HEADER'); const warning = strings.get('cancelJob.SUBMIT_REQUEST'); const cancelText = strings.get('cancelJob.RETURN'); const id = resource.model.get('id'); const name = $filter('sanitize')(resource.model.get('name')); const body = `
${warning}
`; const resourceName = `#${id} ${name}`; const method = 'POST'; const url = `${resource.model.path}${id}/cancel/`; const errorHandler = createErrorHandler('cancel job', method); const action = () => { wait('start'); $http({ method, url }) .catch(errorHandler) .finally(() => { $(ELEMENT_PROMPT_MODAL).modal('hide'); wait('stop'); }); }; prompt({ hdr, resourceName, body, actionText, action, cancelText }); } function deleteJob () { const actionText = strings.get('DELETE'); const hdr = strings.get('deleteResource.HEADER'); const warning = strings.get('deleteResource.CONFIRM', 'job'); const id = resource.model.get('id'); const name = $filter('sanitize')(resource.model.get('name')); const body = `
${warning}
`; const resourceName = `#${id} ${name}`; const method = 'DELETE'; const url = `${resource.model.path}${id}/`; const errorHandler = createErrorHandler('delete job', method); const action = () => { wait('start'); $http({ method, url }) .then(() => $state.go('jobs')) .catch(errorHandler) .finally(() => { $(ELEMENT_PROMPT_MODAL).modal('hide'); wait('stop'); }); }; prompt({ hdr, resourceName, body, actionText, action }); } function JobDetailsController ( _$http_, _$filter_, _$state_, _error_, _prompt_, _strings_, _wait_, _parse_, { subscribe }, ) { vm = this || {}; $http = _$http_; $filter = _$filter_; $state = _$state_; error = _error_; parse = _parse_; prompt = _prompt_; strings = _strings_; wait = _wait_; let unsubscribe; vm.$onInit = () => { resource = this.resource; // eslint-disable-line prefer-destructuring vm.strings = strings; vm.status = getStatusDetails(); vm.started = getStartDetails(); vm.finished = getFinishDetails(); vm.moduleArgs = getModuleArgDetails(); vm.jobType = getJobTypeDetails(); vm.jobTemplate = getJobTemplateDetails(); vm.sourceWorkflowJob = getSourceWorkflowJobDetails(); vm.inventory = getInventoryDetails(); vm.project = getProjectDetails(); vm.projectUpdate = getProjectUpdateDetails(); vm.projectStatus = getProjectStatusDetails(); vm.scmRevision = getSCMRevisionDetails(); vm.playbook = getPlaybookDetails(); vm.resultTraceback = getResultTracebackDetails(); vm.launchedBy = getLaunchedByDetails(); vm.jobExplanation = getJobExplanationDetails(); vm.verbosity = getVerbosityDetails(); vm.credentials = getCredentialDetails(); vm.forks = getForkDetails(); vm.limit = getLimitDetails(); vm.instanceGroup = getInstanceGroupDetails(); vm.jobTags = getJobTagDetails(); vm.skipTags = getSkipTagDetails(); vm.extraVars = getExtraVarsDetails(); vm.labels = getLabelDetails(); vm.inventoryJobName = getInventoryJobNameDetails(); vm.inventorySource = getInventorySourceDetails(); vm.overwrite = getOverwriteDetails(); vm.overwriteVars = getOverwriteVarsDetails(); vm.licenseError = getLicenseErrorDetails(); // Relaunch and Delete Components vm.job = angular.copy(_.get(resource.model, 'model.GET', {})); vm.canDelete = resource.model.get('summary_fields.user_capabilities.delete'); vm.cancelJob = cancelJob; vm.deleteJob = deleteJob; vm.toggleJobTags = toggleJobTags; vm.toggleSkipTags = toggleSkipTags; vm.toggleLabels = toggleLabels; unsubscribe = subscribe(({ status, started, finished, scm }) => { vm.started = getStartDetails(started); vm.finished = getFinishDetails(finished); vm.projectUpdate = getProjectUpdateDetails(scm.id); vm.projectStatus = getProjectStatusDetails(scm.status); vm.status = getStatusDetails(status); vm.job.status = status; }); }; vm.$onDestroy = () => { unsubscribe(); }; } JobDetailsController.$inject = [ '$http', '$filter', '$state', 'ProcessErrors', 'Prompt', 'OutputStrings', 'Wait', 'ParseVariableString', 'JobStatusService', ]; export default { templateUrl, controller: JobDetailsController, controllerAs: 'vm', bindings: { resource: '<' }, };