diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-20 12:55:51 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-20 12:55:51 +0300 |
commit | e8d2c2579383897a1dd7f9debd359abe8ae8373d (patch) | |
tree | c42be41678c2586d49a75cabce89322082698334 /app/assets/javascripts/jobs | |
parent | fc845b37ec3a90aaa719975f607740c22ba6a113 (diff) |
Add latest changes from gitlab-org/gitlab@14-1-stable-eev14.1.0-rc42
Diffstat (limited to 'app/assets/javascripts/jobs')
13 files changed, 176 insertions, 61 deletions
diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue index 35b16d73cc7..e31c13f40b0 100644 --- a/app/assets/javascripts/jobs/components/empty_state.vue +++ b/app/assets/javascripts/jobs/components/empty_state.vue @@ -35,11 +35,6 @@ export default { required: false, default: false, }, - variablesSettingsUrl: { - type: String, - required: false, - default: null, - }, action: { type: Object, required: false, @@ -75,11 +70,7 @@ export default { <p v-if="content" data-testid="job-empty-state-content">{{ content }}</p> </div> - <manual-variables-form - v-if="shouldRenderManualVariables" - :action="action" - :variables-settings-url="variablesSettingsUrl" - /> + <manual-variables-form v-if="shouldRenderManualVariables" :action="action" /> <div class="text-content"> <div v-if="action && !shouldRenderManualVariables" class="text-center"> <gl-link diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index be95001a396..fa9ee56c049 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -50,11 +50,6 @@ export default { required: false, default: null, }, - variablesSettingsUrl: { - type: String, - required: false, - default: null, - }, deploymentHelpUrl: { type: String, required: false, @@ -315,7 +310,6 @@ export default { :action="emptyStateAction" :playable="job.playable" :scheduled="job.scheduled" - :variables-settings-url="variablesSettingsUrl" /> <!-- EO empty state --> diff --git a/app/assets/javascripts/jobs/components/log/collapsible_section.vue b/app/assets/javascripts/jobs/components/log/collapsible_section.vue index 55cdfb691f4..c0d5fac0e8d 100644 --- a/app/assets/javascripts/jobs/components/log/collapsible_section.vue +++ b/app/assets/javascripts/jobs/components/log/collapsible_section.vue @@ -1,4 +1,6 @@ <script> +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF } from '../../constants'; import LogLine from './line.vue'; import LogLineHeader from './line_header.vue'; @@ -7,7 +9,9 @@ export default { components: { LogLine, LogLineHeader, + CollapsibleLogSection: () => import('./collapsible_section.vue'), }, + mixins: [glFeatureFlagsMixin()], props: { section: { type: Object, @@ -22,6 +26,9 @@ export default { badgeDuration() { return this.section.line && this.section.line.section_duration; }, + infinitelyCollapsibleSectionsFlag() { + return this.glFeatures?.[INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF]; + }, }, methods: { handleOnClickCollapsibleLine(section) { @@ -40,12 +47,26 @@ export default { @toggleLine="handleOnClickCollapsibleLine(section)" /> <template v-if="!section.isClosed"> - <log-line - v-for="line in section.lines" - :key="line.offset" - :line="line" - :path="traceEndpoint" - /> + <template v-if="infinitelyCollapsibleSectionsFlag"> + <template v-for="line in section.lines"> + <collapsible-log-section + v-if="line.isHeader" + :key="line.line.offset" + :section="line" + :trace-endpoint="traceEndpoint" + @onClickCollapsibleLine="handleOnClickCollapsibleLine" + /> + <log-line v-else :key="line.offset" :line="line" :path="traceEndpoint" /> + </template> + </template> + <template v-else> + <log-line + v-for="line in section.lines" + :key="line.offset" + :line="line" + :path="traceEndpoint" + /> + </template> </template> </div> </template> diff --git a/app/assets/javascripts/jobs/components/log/line_number.vue b/app/assets/javascripts/jobs/components/log/line_number.vue index 7ca9154d2fe..c8ceac2c7ff 100644 --- a/app/assets/javascripts/jobs/components/log/line_number.vue +++ b/app/assets/javascripts/jobs/components/log/line_number.vue @@ -1,4 +1,6 @@ <script> +import { INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF } from '../../constants'; + export default { functional: true, props: { @@ -14,7 +16,9 @@ export default { render(h, { props }) { const { lineNumber, path } = props; - const parsedLineNumber = lineNumber + 1; + const parsedLineNumber = gon.features?.[INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF] + ? lineNumber + : lineNumber + 1; const lineId = `L${parsedLineNumber}`; const lineHref = `${path}#${lineId}`; diff --git a/app/assets/javascripts/jobs/components/manual_variables_form.vue b/app/assets/javascripts/jobs/components/manual_variables_form.vue index d45012d2023..269551ff9aa 100644 --- a/app/assets/javascripts/jobs/components/manual_variables_form.vue +++ b/app/assets/javascripts/jobs/components/manual_variables_form.vue @@ -1,14 +1,16 @@ <script> -/* eslint-disable vue/no-v-html */ -import { GlButton } from '@gitlab/ui'; +import { GlButton, GlLink, GlSprintf } from '@gitlab/ui'; import { uniqueId } from 'lodash'; import { mapActions } from 'vuex'; -import { s__, sprintf } from '~/locale'; +import { helpPagePath } from '~/helpers/help_page_helper'; +import { s__ } from '~/locale'; export default { name: 'ManualVariablesForm', components: { GlButton, + GlLink, + GlSprintf, }, props: { action: { @@ -24,11 +26,6 @@ export default { ); }, }, - variablesSettingsUrl: { - type: String, - required: true, - default: '', - }, }, inputTypes: { key: 'key', @@ -37,6 +34,9 @@ export default { i18n: { keyPlaceholder: s__('CiVariables|Input variable key'), valuePlaceholder: s__('CiVariables|Input variable value'), + formHelpText: s__( + 'CiVariables|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used as default', + ), }, data() { return { @@ -47,17 +47,8 @@ export default { }; }, computed: { - helpText() { - return sprintf( - s__( - 'CiVariables|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used as default', - ), - { - linkStart: `<a href="${this.variablesSettingsUrl}">`, - linkEnd: '</a>', - }, - false, - ); + variableSettings() { + return helpPagePath('ci/variables/index', { anchor: 'add-a-cicd-variable-to-a-project' }); }, }, watch: { @@ -188,8 +179,14 @@ export default { </div> </div> </div> - <div class="d-flex gl-mt-3 justify-content-center"> - <p class="text-muted" data-testid="form-help-text" v-html="helpText"></p> + <div class="gl-text-center gl-mt-3"> + <gl-sprintf :message="$options.i18n.formHelpText"> + <template #link="{ content }"> + <gl-link :href="variableSettings" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> </div> <div class="d-flex justify-content-center"> <gl-button diff --git a/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue b/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue index 98badb96ed7..a6eff743ce9 100644 --- a/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue +++ b/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue @@ -46,7 +46,7 @@ export default { return timeIntervalInWords(this.job.queued); }, runnerHelpUrl() { - return helpPagePath('ci/runners/README.html', { + return helpPagePath('ci/runners/index.html', { anchor: 'set-maximum-job-timeout-for-a-runner', }); }, diff --git a/app/assets/javascripts/jobs/constants.js b/app/assets/javascripts/jobs/constants.js index 3040d4e2379..97f31eee57c 100644 --- a/app/assets/javascripts/jobs/constants.js +++ b/app/assets/javascripts/jobs/constants.js @@ -24,3 +24,5 @@ export const JOB_RETRY_FORWARD_DEPLOYMENT_MODAL = { }; export const SUCCESS_STATUS = 'SUCCESS'; + +export const INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF = 'infinitelyCollapsibleSections'; diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js index 260190f5043..1fb6a6f9850 100644 --- a/app/assets/javascripts/jobs/index.js +++ b/app/assets/javascripts/jobs/index.js @@ -15,7 +15,6 @@ export default () => { deploymentHelpUrl, codeQualityHelpUrl, runnerSettingsUrl, - variablesSettingsUrl, subscriptionsMoreMinutesUrl, endpoint, pagePath, @@ -41,7 +40,6 @@ export default () => { deploymentHelpUrl, codeQualityHelpUrl, runnerSettingsUrl, - variablesSettingsUrl, subscriptionsMoreMinutesUrl, endpoint, pagePath, diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js index c89aeada69d..a8be5d8d039 100644 --- a/app/assets/javascripts/jobs/store/actions.js +++ b/app/assets/javascripts/jobs/store/actions.js @@ -1,5 +1,5 @@ import Visibility from 'visibilityjs'; -import { deprecatedCreateFlash as flash } from '~/flash'; +import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { setFaviconOverlay, resetFavicon } from '~/lib/utils/favicon'; import httpStatusCodes from '~/lib/utils/http_status'; @@ -99,7 +99,9 @@ export const receiveJobSuccess = ({ commit }, data = {}) => { }; export const receiveJobError = ({ commit }) => { commit(types.RECEIVE_JOB_ERROR); - flash(__('An error occurred while fetching the job.')); + createFlash({ + message: __('An error occurred while fetching the job.'), + }); resetFavicon(); }; @@ -197,11 +199,15 @@ export const stopPollingTrace = ({ state, commit }) => { export const receiveTraceSuccess = ({ commit }, log) => commit(types.RECEIVE_TRACE_SUCCESS, log); export const receiveTraceError = ({ dispatch }) => { dispatch('stopPollingTrace'); - flash(__('An error occurred while fetching the job log.')); + createFlash({ + message: __('An error occurred while fetching the job log.'), + }); }; export const receiveTraceUnauthorizedError = ({ dispatch }) => { dispatch('stopPollingTrace'); - flash(__('The current user is not authorized to access the job log.')); + createFlash({ + message: __('The current user is not authorized to access the job log.'), + }); }; /** * When the user clicks a collapsible line in the job @@ -240,7 +246,9 @@ export const receiveJobsForStageSuccess = ({ commit }, data) => commit(types.RECEIVE_JOBS_FOR_STAGE_SUCCESS, data); export const receiveJobsForStageError = ({ commit }) => { commit(types.RECEIVE_JOBS_FOR_STAGE_ERROR); - flash(__('An error occurred while fetching the jobs.')); + createFlash({ + message: __('An error occurred while fetching the jobs.'), + }); }; export const triggerManualJob = ({ state }, variables) => { @@ -254,5 +262,9 @@ export const triggerManualJob = ({ state }, variables) => { .post(state.job.status.action.path, { job_variables_attributes: parsedVariables, }) - .catch(() => flash(__('An error occurred while triggering the job.'))); + .catch(() => + createFlash({ + message: __('An error occurred while triggering the job.'), + }), + ); }; diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js index 924b811d0d6..4045d8a0c16 100644 --- a/app/assets/javascripts/jobs/store/mutations.js +++ b/app/assets/javascripts/jobs/store/mutations.js @@ -1,6 +1,7 @@ import Vue from 'vue'; +import { INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF } from '../constants'; import * as types from './mutation_types'; -import { logLinesParser, updateIncrementalTrace } from './utils'; +import { logLinesParser, logLinesParserLegacy, updateIncrementalTrace } from './utils'; export default { [types.SET_JOB_ENDPOINT](state, endpoint) { @@ -20,12 +21,26 @@ export default { }, [types.RECEIVE_TRACE_SUCCESS](state, log = {}) { + const infinitelyCollapsibleSectionsFlag = + gon.features?.[INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF]; if (log.state) { state.traceState = log.state; } if (log.append) { - state.trace = log.lines ? updateIncrementalTrace(log.lines, state.trace) : state.trace; + if (infinitelyCollapsibleSectionsFlag) { + if (log.lines) { + const parsedResult = logLinesParser( + log.lines, + state.auxiliaryPartialTraceHelpers, + state.trace, + ); + state.trace = parsedResult.parsedLines; + state.auxiliaryPartialTraceHelpers = parsedResult.auxiliaryPartialTraceHelpers; + } + } else { + state.trace = log.lines ? updateIncrementalTrace(log.lines, state.trace) : state.trace; + } state.traceSize += log.size; } else { @@ -33,7 +48,14 @@ export default { // the trace response will not have a defined // html or size. We keep the old value otherwise these // will be set to `null` - state.trace = log.lines ? logLinesParser(log.lines) : state.trace; + + if (infinitelyCollapsibleSectionsFlag) { + const parsedResult = logLinesParser(log.lines); + state.trace = parsedResult.parsedLines; + state.auxiliaryPartialTraceHelpers = parsedResult.auxiliaryPartialTraceHelpers; + } else { + state.trace = log.lines ? logLinesParserLegacy(log.lines) : state.trace; + } state.traceSize = log.size || state.traceSize; } diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js index 2fe945b2985..718324c8bad 100644 --- a/app/assets/javascripts/jobs/store/state.js +++ b/app/assets/javascripts/jobs/store/state.js @@ -30,4 +30,7 @@ export default () => ({ selectedStage: '', stages: [], jobs: [], + + // to parse partial logs + auxiliaryPartialTraceHelpers: {}, }); diff --git a/app/assets/javascripts/jobs/store/utils.js b/app/assets/javascripts/jobs/store/utils.js index a0e0a0fb8bd..36391a4d433 100644 --- a/app/assets/javascripts/jobs/store/utils.js +++ b/app/assets/javascripts/jobs/store/utils.js @@ -104,7 +104,7 @@ export const getIncrementalLineNumber = (acc) => { * @param Array accumulator * @returns Array parsed log lines */ -export const logLinesParser = (lines = [], accumulator = []) => +export const logLinesParserLegacy = (lines = [], accumulator = []) => lines.reduce( (acc, line, index) => { const lineNumber = accumulator.length > 0 ? getIncrementalLineNumber(acc) : index; @@ -131,6 +131,77 @@ export const logLinesParser = (lines = [], accumulator = []) => [...accumulator], ); +export const logLinesParser = (lines = [], previousTraceState = {}, prevParsedLines = []) => { + let currentLine = previousTraceState?.prevLineCount ?? 0; + let currentHeader = previousTraceState?.currentHeader; + let isPreviousLineHeader = previousTraceState?.isPreviousLineHeader ?? false; + const parsedLines = prevParsedLines.length > 0 ? prevParsedLines : []; + const sectionsQueue = previousTraceState?.sectionsQueue ?? []; + + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i]; + // First run we can use the current index, later runs we have to retrieve the last number of lines + currentLine = previousTraceState?.prevLineCount ? currentLine + 1 : i + 1; + + if (line.section_header && !isPreviousLineHeader) { + // If there's no previous line header that means we're at the root of the log + + isPreviousLineHeader = true; + parsedLines.push(parseHeaderLine(line, currentLine)); + currentHeader = { index: parsedLines.length - 1 }; + } else if (line.section_header && isPreviousLineHeader) { + // If there's a current section, we can't push to the parsedLines array + sectionsQueue.push(currentHeader); + currentHeader = parseHeaderLine(line, currentLine); // Let's parse the incoming header line + } else if (line.section && !line.section_duration) { + // We're inside a collapsible section and want to parse a standard line + if (currentHeader?.index) { + // If the current section header is only an index, add the line as part of the lines + // array of the current collapsible section + parsedLines[currentHeader.index].lines.push(parseLine(line, currentLine)); + } else { + // Otherwise add it to the innermost collapsible section lines array + currentHeader.lines.push(parseLine(line, currentLine)); + } + } else if (line.section && line.section_duration) { + // NOTE: This marks the end of a section_header + const previousSection = sectionsQueue.pop(); + + // Add the duration to section header + // If at the root, just push the end to the current parsedLine, + // otherwise, push it to the previous sections queue + if (currentHeader?.index) { + parsedLines[currentHeader.index].line.section_duration = line.section_duration; + isPreviousLineHeader = false; + currentHeader = null; + } else { + currentHeader.line.section_duration = line.section_duration; + + if (previousSection && previousSection?.index) { + // Is the previous section on root? + parsedLines[previousSection.index].lines.push(currentHeader); + } else if (previousSection && !previousSection?.index) { + previousSection.lines.push(currentHeader); + } + + currentHeader = previousSection; + } + } else { + parsedLines.push(parseLine(line, currentLine)); + } + } + + return { + parsedLines, + auxiliaryPartialTraceHelpers: { + isPreviousLineHeader, + currentHeader, + sectionsQueue, + prevLineCount: lines.length, + }, + }; +}; + /** * Finds the repeated offset, removes the old one * @@ -177,5 +248,5 @@ export const findOffsetAndRemove = (newLog = [], oldParsed = []) => { export const updateIncrementalTrace = (newLog = [], oldParsed = []) => { const parsedLog = findOffsetAndRemove(newLog, oldParsed); - return logLinesParser(newLog, parsedLog); + return logLinesParserLegacy(newLog, parsedLog); }; diff --git a/app/assets/javascripts/jobs/utils.js b/app/assets/javascripts/jobs/utils.js index 122f23a5bb5..1ccecf3eb53 100644 --- a/app/assets/javascripts/jobs/utils.js +++ b/app/assets/javascripts/jobs/utils.js @@ -3,10 +3,10 @@ * https?:\/\/ * * up until a disallowed character or whitespace - * [^"<>\\^`{|}\s]+ + * [^"<>()\\^`{|}\s]+ * * and a disallowed character or whitespace, including non-ending chars .,:;!? - * [^"<>\\^`{|}\s.,:;!?] + * [^"<>()\\^`{|}\s.,:;!?] */ -export const linkRegex = /(https?:\/\/[^"<>\\^`{|}\s]+[^"<>\\^`{|}\s.,:;!?])/g; +export const linkRegex = /(https?:\/\/[^"<>()\\^`{|}\s]+[^"<>()\\^`{|}\s.,:;!?])/g; export default { linkRegex }; |