diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2019-09-12 12:34:12 +0300 |
---|---|---|
committer | Kushal Pandya <kushalspandya@gmail.com> | 2019-09-12 12:34:12 +0300 |
commit | 1aba56b2ffc2b504ee750929b97dd92f876083f4 (patch) | |
tree | 0c98376b1c15859c0a82033516c0dad10caec029 /app/assets/javascripts/jobs | |
parent | 10c440c1e6aab0748d31f247d6a7ef936c81aaab (diff) |
Creates job log component
Creates vue and vuex support for new job log
Creates the new log.vue component to handle the
new format
Updates the store to use the new parser
Creates an utility function to handle the
incremental log
Diffstat (limited to 'app/assets/javascripts/jobs')
-rw-r--r-- | app/assets/javascripts/jobs/components/job_app.vue | 6 | ||||
-rw-r--r-- | app/assets/javascripts/jobs/components/log/log.vue | 45 | ||||
-rw-r--r-- | app/assets/javascripts/jobs/store/actions.js | 8 | ||||
-rw-r--r-- | app/assets/javascripts/jobs/store/mutation_types.js | 1 | ||||
-rw-r--r-- | app/assets/javascripts/jobs/store/mutations.js | 28 | ||||
-rw-r--r-- | app/assets/javascripts/jobs/store/state.js | 5 | ||||
-rw-r--r-- | app/assets/javascripts/jobs/store/utils.js | 54 |
7 files changed, 136 insertions, 11 deletions
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index c7d4d7c4b9b..36701a95673 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -18,6 +18,7 @@ import UnmetPrerequisitesBlock from './unmet_prerequisites_block.vue'; import Sidebar from './sidebar.vue'; import { sprintf } from '~/locale'; import delayedJobMixin from '../mixins/delayed_job_mixin'; +import { isNewJobLogActive } from '../store/utils'; export default { name: 'JobPageApp', @@ -29,10 +30,7 @@ export default { EnvironmentsBlock, ErasedBlock, Icon, - Log: () => - gon && gon.features && gon.features.jobLogJson - ? import('./job_log_json.vue') - : import('./job_log.vue'), + Log: () => (isNewJobLogActive() ? import('./job_log_json.vue') : import('./job_log.vue')), LogTopBar, StuckBlock, UnmetPrerequisitesBlock, diff --git a/app/assets/javascripts/jobs/components/log/log.vue b/app/assets/javascripts/jobs/components/log/log.vue new file mode 100644 index 00000000000..5db866afe5a --- /dev/null +++ b/app/assets/javascripts/jobs/components/log/log.vue @@ -0,0 +1,45 @@ +<script> +import { mapState, mapActions } from 'vuex'; +import LogLine from './line.vue'; +import LogLineHeader from './line_header.vue'; + +export default { + components: { + LogLine, + LogLineHeader, + }, + computed: { + ...mapState(['traceEndpoint', 'trace']), + }, + methods: { + ...mapActions(['toggleCollapsibleLine']), + handleOnClickCollapsibleLine(section) { + this.toggleCollapsibleLine(section); + }, + }, +}; +</script> +<template> + <code class="job-log"> + <template v-for="(section, index) in trace"> + <template v-if="section.isHeader"> + <log-line-header + :key="`collapsible-${index}`" + :line="section.line" + :path="traceEndpoint" + :is-closed="section.isClosed" + @toggleLine="handleOnClickCollapsibleLine(section)" + /> + <template v-if="!section.isClosed"> + <log-line + v-for="line in section.lines" + :key="line.offset" + :line="line" + :path="traceEndpoint" + /> + </template> + </template> + <log-line v-else :key="section.offset" :line="section" :path="traceEndpoint" /> + </template> + </code> +</template> diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js index a2daef96a2d..41cc5a181dc 100644 --- a/app/assets/javascripts/jobs/store/actions.js +++ b/app/assets/javascripts/jobs/store/actions.js @@ -177,6 +177,14 @@ export const receiveTraceError = ({ commit }) => { clearTimeout(traceTimeout); flash(__('An error occurred while fetching the job log.')); }; +/** + * When the user clicks a collpasible line in the job + * log, we commit a mutation to update the state + * + * @param {Object} section + */ +export const toggleCollapsibleLine = ({ commit }, section) => + commit(types.TOGGLE_COLLAPSIBLE_LINE, section); /** * Jobs list on sidebar - depend on stages dropdown diff --git a/app/assets/javascripts/jobs/store/mutation_types.js b/app/assets/javascripts/jobs/store/mutation_types.js index 39146b2eefd..858fa3b73ab 100644 --- a/app/assets/javascripts/jobs/store/mutation_types.js +++ b/app/assets/javascripts/jobs/store/mutation_types.js @@ -23,6 +23,7 @@ export const REQUEST_TRACE = 'REQUEST_TRACE'; export const STOP_POLLING_TRACE = 'STOP_POLLING_TRACE'; export const RECEIVE_TRACE_SUCCESS = 'RECEIVE_TRACE_SUCCESS'; export const RECEIVE_TRACE_ERROR = 'RECEIVE_TRACE_ERROR'; +export const TOGGLE_COLLAPSIBLE_LINE = 'TOGGLE_COLLAPSIBLE_LINE'; export const SET_SELECTED_STAGE = 'SET_SELECTED_STAGE'; export const REQUEST_JOBS_FOR_STAGE = 'REQUEST_JOBS_FOR_STAGE'; diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js index ad08f27b147..540c3e2ad69 100644 --- a/app/assets/javascripts/jobs/store/mutations.js +++ b/app/assets/javascripts/jobs/store/mutations.js @@ -1,4 +1,6 @@ +import Vue from 'vue'; import * as types from './mutation_types'; +import { logLinesParser, updateIncrementalTrace, isNewJobLogActive } from './utils'; export default { [types.SET_JOB_ENDPOINT](state, endpoint) { @@ -23,14 +25,24 @@ export default { } if (log.append) { - state.trace += log.html; + if (isNewJobLogActive()) { + state.originalTrace = state.originalTrace.concat(log.trace); + state.trace = updateIncrementalTrace(state.originalTrace, state.trace, log.lines); + } else { + state.trace += log.html; + } state.traceSize += log.size; } else { // When the job still does not have a trace // the trace response will not have a defined // html or size. We keep the old value otherwise these // will be set to `undefined` - state.trace = log.html || state.trace; + if (isNewJobLogActive()) { + state.originalTrace = log.lines || state.trace; + state.trace = logLinesParser(log.lines) || state.trace; + } else { + state.trace = log.html || state.trace; + } state.traceSize = log.size || state.traceSize; } @@ -57,6 +69,18 @@ export default { state.isTraceComplete = true; }, + /** + * Instead of filtering the array of lines to find the one that must be updated + * we use Vue.set to make this process more performant + * + * https://vuex.vuejs.org/guide/mutations.html#mutations-follow-vue-s-reactivity-rules + * @param {Object} state + * @param {Object} section + */ + [types.TOGGLE_COLLAPSIBLE_LINE](state, section) { + Vue.set(section, 'isClosed', !section.isClosed); + }, + [types.REQUEST_JOB](state) { state.isLoading = true; }, diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js index 6019214e62c..585878f8240 100644 --- a/app/assets/javascripts/jobs/store/state.js +++ b/app/assets/javascripts/jobs/store/state.js @@ -1,3 +1,5 @@ +import { isNewJobLogActive } from '../store/utils'; + export default () => ({ jobEndpoint: null, traceEndpoint: null, @@ -16,7 +18,8 @@ export default () => ({ // Used to check if we should keep the automatic scroll isScrolledToBottomBeforeReceivingTrace: true, - trace: '', + trace: isNewJobLogActive() ? [] : '', + originalTrace: [], isTraceComplete: false, traceSize: 0, isTraceSizeVisible: false, diff --git a/app/assets/javascripts/jobs/store/utils.js b/app/assets/javascripts/jobs/store/utils.js index de7de92ed2e..f6a87b9a212 100644 --- a/app/assets/javascripts/jobs/store/utils.js +++ b/app/assets/javascripts/jobs/store/utils.js @@ -11,15 +11,16 @@ * @param {Array} lines * @returns {Array} */ -export default (lines = []) => +export const logLinesParser = (lines = [], lineNumberStart) => lines.reduce((acc, line, index) => { + const lineNumber = lineNumberStart ? lineNumberStart + index : index; if (line.section_header) { acc.push({ isClosed: true, isHeader: true, line: { ...line, - lineNumber: index, + lineNumber, }, lines: [], @@ -27,14 +28,59 @@ export default (lines = []) => } else if (acc.length && acc[acc.length - 1].isHeader) { acc[acc.length - 1].lines.push({ ...line, - lineNumber: index, + lineNumber, }); } else { acc.push({ ...line, - lineNumber: index, + lineNumber, }); } return acc; }, []); + +/** + * When the trace 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 originalTrace + * @param array oldLog + * @param array newLog + */ +export const updateIncrementalTrace = (originalTrace = [], oldLog = [], newLog = []) => { + const firstLine = newLog[0]; + const firstLineOffset = firstLine.offset; + + // We are going to return a new array, + // let's make a shallow copy to make sure we + // are not updating the state outside of a mutation first. + const cloneOldLog = [...oldLog]; + + const lastIndex = cloneOldLog.length - 1; + const lastLine = cloneOldLog[lastIndex]; + + // The last line may be inside a collpasible section + // If it is, we use the not parsed saved log, remove the last element + // and parse the first received part togheter with the incremental log + if ( + lastLine.isHeader && + (lastLine.line.offset === firstLineOffset || + (lastLine.lines.length && + lastLine.lines[lastLine.lines.length - 1].offset === firstLineOffset)) + ) { + const cloneOriginal = [...originalTrace]; + cloneOriginal.splice(cloneOriginal.length - 1); + return logLinesParser(cloneOriginal.concat(newLog)); + } else if (lastLine.offset === firstLineOffset) { + cloneOldLog.splice(lastIndex); + return cloneOldLog.concat(logLinesParser(newLog, cloneOldLog.length)); + } + // there are no matches, let's parse the new log and return them together + return cloneOldLog.concat(logLinesParser(newLog, cloneOldLog.length)); +}; + +export const isNewJobLogActive = () => gon && gon.features && gon.features.jobLogJson; |