import Ansi from 'ansi-to-html'; import Entities from 'html-entities'; const ELEMENT_TBODY = '#atStdoutResultTable'; const EVENT_START_TASK = 'playbook_on_task_start'; const EVENT_START_PLAY = 'playbook_on_play_start'; const EVENT_STATS_PLAY = 'playbook_on_stats'; const EVENT_GROUPS = [ EVENT_START_TASK, EVENT_START_PLAY ]; const TIME_EVENTS = [ EVENT_START_TASK, EVENT_START_PLAY, EVENT_STATS_PLAY ]; const ansi = new Ansi(); const entities = new Entities.AllHtmlEntities(); // https://github.com/chalk/ansi-regex const pattern = [ '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)', '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))' ].join('|'); const re = new RegExp(pattern); const hasAnsi = input => re.test(input); function JobRenderService ($q, $sce, $window) { this.init = ({ compile, apply, isStreamActive }) => { this.parent = null; this.record = {}; this.el = $(ELEMENT_TBODY); this.hooks = { isStreamActive, compile, apply }; }; this.sortByLineNumber = (a, b) => { if (a.start_line > b.start_line) { return 1; } if (a.start_line < b.start_line) { return -1; } return 0; }; this.transformEventGroup = events => { let lines = 0; let html = ''; events.sort(this.sortByLineNumber); events.forEach(event => { const line = this.transformEvent(event); html += line.html; lines += line.count; }); return { html, lines }; }; this.transformEvent = event => { if (!event || !event.stdout) { return { html: '', count: 0 }; } const stdout = this.sanitize(event.stdout); const lines = stdout.split('\r\n'); let count = lines.length; let ln = event.start_line; const current = this.createRecord(ln, lines, event); const html = lines.reduce((concat, line, i) => { ln++; const isLastLine = i === lines.length - 1; let row = this.createRow(current, ln, line); if (current && current.isTruncated && isLastLine) { row += this.createRow(current); count++; } return `${concat}${row}`; }, ''); return { html, count }; }; this.createRecord = (ln, lines, event) => { if (!event.uuid) { return null; } const info = { id: event.id, line: ln + 1, uuid: event.uuid, level: event.event_level, start: event.start_line, end: event.end_line, isTruncated: (event.end_line - event.start_line) > lines.length, isHost: typeof event.host === 'number' }; if (event.parent_uuid) { info.parents = this.getParentEvents(event.parent_uuid); } if (info.isTruncated) { info.truncatedAt = event.start_line + lines.length; } if (EVENT_GROUPS.includes(event.event)) { info.isParent = true; if (event.event_level === 1) { this.parent = event.uuid; } if (event.parent_uuid) { if (this.record[event.parent_uuid]) { if (this.record[event.parent_uuid].children && !this.record[event.parent_uuid].children.includes(event.uuid)) { this.record[event.parent_uuid].children.push(event.uuid); } else { this.record[event.parent_uuid].children = [event.uuid]; } } } } if (TIME_EVENTS.includes(event.event)) { info.time = this.getTimestamp(event.created); info.line++; } this.record[event.uuid] = info; return info; }; this.createRow = (current, ln, content) => { let id = ''; let timestamp = ''; let tdToggle = ''; let tdEvent = ''; let classList = ''; content = content || ''; if (hasAnsi(content)) { content = ansi.toHtml(content); } if (current) { if (!this.hooks.isStreamActive() && current.isParent && current.line === ln) { id = current.uuid; tdToggle = `