diff options
Diffstat (limited to 'app/assets/javascripts/ci/job_details/store/utils.js')
-rw-r--r-- | app/assets/javascripts/ci/job_details/store/utils.js | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/app/assets/javascripts/ci/job_details/store/utils.js b/app/assets/javascripts/ci/job_details/store/utils.js new file mode 100644 index 00000000000..bc76901026d --- /dev/null +++ b/app/assets/javascripts/ci/job_details/store/utils.js @@ -0,0 +1,195 @@ +import { parseBoolean } from '~/lib/utils/common_utils'; + +/** + * Adds the line number property + * @param Object line + * @param Number lineNumber + */ +export const parseLine = (line = {}, lineNumber) => ({ + ...line, + lineNumber, +}); + +/** + * When a line has `section_header` set to true, we create a new + * structure to allow to nest the lines that belong to the + * collapsible section + * + * @param Object line + * @param Number lineNumber + */ +export const parseHeaderLine = (line = {}, lineNumber, hash) => { + // if a hash is present in the URL then we ensure + // all sections are visible so we can scroll to the hash + // in the DOM + if (hash) { + return { + isClosed: false, + isHeader: true, + line: parseLine(line, lineNumber), + lines: [], + }; + } + + return { + isClosed: parseBoolean(line.section_options?.collapsed), + isHeader: true, + line: parseLine(line, lineNumber), + lines: [], + }; +}; + +/** + * Finds the matching header section + * for the section_duration object and adds it to it + * + * { + * isHeader: true, + * line: { + * content: [], + * lineNumber: 0, + * section_duration: "", + * }, + * lines: [] + * } + * + * @param Array data + * @param Object durationLine + */ +export function addDurationToHeader(data, durationLine) { + data.forEach((el) => { + if (el.line && el.line.section === durationLine.section) { + el.line.section_duration = durationLine.section_duration; + } + }); +} + +/** + * Check is the current section belongs to a collapsible section + * + * @param Array acc + * @param Object last + * @param Object section + * + * @returns Boolean + */ +export const isCollapsibleSection = (acc = [], last = {}, section = {}) => + acc.length > 0 && + last.isHeader === true && + !section.section_duration && + section.section === last.line.section; + +/** + * Returns the lineNumber of the last line in + * a parsed log + * + * @param Array acc + * @returns Number + */ +export const getIncrementalLineNumber = (acc) => { + let lineNumberValue; + const lastIndex = acc.length - 1; + const lastElement = acc[lastIndex]; + const nestedLines = lastElement.lines; + + if (lastElement.isHeader && !nestedLines.length && lastElement.line) { + lineNumberValue = lastElement.line.lineNumber; + } else if (lastElement.isHeader && nestedLines.length) { + lineNumberValue = nestedLines[nestedLines.length - 1].lineNumber; + } else { + lineNumberValue = lastElement.lineNumber; + } + + return lineNumberValue === 0 ? 1 : lineNumberValue + 1; +}; + +/** + * Parses the job log content into a structure usable by the template + * + * For collaspible lines (section_header = true): + * - creates a new array to hold the lines that are collapsible, + * - adds a isClosed property to handle toggle + * - adds a isHeader property to handle template logic + * - adds the section_duration + * For each line: + * - adds the index as lineNumber + * + * @param Array lines + * @param Array accumulator + * @returns Array parsed log lines + */ +export const logLinesParser = (lines = [], accumulator = [], hash = '') => + lines.reduce( + (acc, line, index) => { + const lineNumber = accumulator.length > 0 ? getIncrementalLineNumber(acc) : index; + + const last = acc[acc.length - 1]; + + // If the object is an header, we parse it into another structure + if (line.section_header) { + acc.push(parseHeaderLine(line, lineNumber, hash)); + } else if (isCollapsibleSection(acc, last, line)) { + // if the object belongs to a nested section, we append it to the new `lines` array of the + // previously formatted header + last.lines.push(parseLine(line, lineNumber)); + } else if (line.section_duration) { + // if the line has section_duration, we look for the correct header to add it + addDurationToHeader(acc, line); + } else { + // otherwise it's a regular line + acc.push(parseLine(line, lineNumber)); + } + + return acc; + }, + [...accumulator], + ); + +/** + * Finds the repeated offset, removes the old one + * + * Returns a new array with the updated log without + * the repeated offset + * + * @param Array newLog + * @param Array oldParsed + * @returns Array + * + */ +export const findOffsetAndRemove = (newLog = [], oldParsed = []) => { + const cloneOldLog = [...oldParsed]; + const lastIndex = cloneOldLog.length - 1; + const last = cloneOldLog[lastIndex]; + + const firstNew = newLog[0]; + + if (last && firstNew) { + if (last.offset === firstNew.offset || (last.line && last.line.offset === firstNew.offset)) { + cloneOldLog.splice(lastIndex); + } else if (last.lines && last.lines.length) { + const lastNestedIndex = last.lines.length - 1; + const lastNested = last.lines[lastNestedIndex]; + if (lastNested.offset === firstNew.offset) { + last.lines.splice(lastNestedIndex); + } + } + } + + return cloneOldLog; +}; + +/** + * When the job log is not complete, backend may send the last received line + * in the new response. + * + * We need to check if that is the case by looking for the offset property + * before parsing the incremental part + * + * @param array oldLog + * @param array newLog + */ +export const updateIncrementalJobLog = (newLog = [], oldParsed = []) => { + const parsedLog = findOffsetAndRemove(newLog, oldParsed); + + return logLinesParser(newLog, parsedLog); +}; |