Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/ci/job_details/store/utils.js')
-rw-r--r--app/assets/javascripts/ci/job_details/store/utils.js195
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);
+};