diff options
Diffstat (limited to 'app/assets/javascripts/pipeline_wizard/components/step.vue')
-rw-r--r-- | app/assets/javascripts/pipeline_wizard/components/step.vue | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/app/assets/javascripts/pipeline_wizard/components/step.vue b/app/assets/javascripts/pipeline_wizard/components/step.vue new file mode 100644 index 00000000000..c6f793e4cc5 --- /dev/null +++ b/app/assets/javascripts/pipeline_wizard/components/step.vue @@ -0,0 +1,149 @@ +<script> +import { GlAlert } from '@gitlab/ui'; +import { isNode, isDocument, parseDocument, Document } from 'yaml'; +import { merge } from '~/lib/utils/yaml'; +import { s__ } from '~/locale'; +import { logError } from '~/lib/logger'; +import InputWrapper from './input.vue'; +import StepNav from './step_nav.vue'; + +export default { + name: 'PipelineWizardStep', + i18n: { + errors: { + cloneErrorUserMessage: s__( + 'PipelineWizard|There was an unexpected error trying to set up the template. The error has been logged.', + ), + }, + }, + components: { + StepNav, + InputWrapper, + GlAlert, + }, + props: { + // As the inputs prop we expect to receive an array of instructions + // on how to display the input fields that will be used to obtain the + // user's input. Each input instruction needs a target prop, specifying + // the placeholder in the template that will be replaced by the user's + // input. The selected widget may require additional validation for the + // input object. + inputs: { + type: Array, + required: true, + validator: (value) => + value.every((i) => { + return i?.target && i?.widget; + }), + }, + template: { + type: null, + required: true, + validator: (v) => isNode(v), + }, + hasPreviousStep: { + type: Boolean, + required: false, + default: false, + }, + compiled: { + type: Object, + required: true, + validator: (v) => isDocument(v), + }, + }, + data() { + return { + wasCompiled: false, + validate: false, + inputValidStates: Array(this.inputs.length).fill(null), + error: null, + }; + }, + computed: { + inputValidStatesThatAreNotNull() { + return this.inputValidStates?.filter((s) => s !== null); + }, + areAllInputValidStatesNull() { + return !this.inputValidStatesThatAreNotNull?.length; + }, + isValid() { + return this.areAllInputValidStatesNull || this.inputValidStatesThatAreNotNull.every((s) => s); + }, + }, + methods: { + forceClone(yamlNode) { + try { + // document.clone() will only clone the root document object, + // but the references to the child nodes inside will be retained. + // So in order to ensure a full clone, we need to stringify + // and parse until there's a better implementation in the + // yaml package. + return parseDocument(new Document(yamlNode).toString()); + } catch (e) { + // eslint-disable-next-line @gitlab/require-i18n-strings + logError('An unexpected error occurred while trying to clone a template', e); + this.error = this.$options.i18n.errors.cloneErrorUserMessage; + return null; + } + }, + compile() { + if (this.wasCompiled) return; + // NOTE: This modifies this.compiled without triggering reactivity. + // this is done on purpose, see + // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81412#note_862972703 + // for more information + merge(this.compiled, this.forceClone(this.template)); + this.wasCompiled = true; + }, + onUpdate(c) { + this.$emit('update:compiled', c); + }, + onPrevClick() { + this.$emit('back'); + }, + async onNextClick() { + this.validate = true; + await this.$nextTick(); + if (this.isValid) { + this.$emit('next'); + } + }, + onInputValidationStateChange(inputId, value) { + this.$set(this.inputValidStates, inputId, value); + }, + onHighlight(path) { + this.$emit('update:highlight', path); + }, + }, +}; +</script> +<template> + <div> + <gl-alert v-if="error" class="gl-mb-4" variant="danger"> + {{ error }} + </gl-alert> + <input-wrapper + v-for="(input, i) in inputs" + :key="input.target" + :compiled="compiled" + :target="input.target" + :template="template" + :validate="validate" + :widget="input.widget" + class="gl-mb-2" + v-bind="input" + @highlight="onHighlight" + @update:valid="(validationState) => onInputValidationStateChange(i, validationState)" + @update:compiled="onUpdate" + @beforeUpdate:compiled.once="compile" + /> + <step-nav + :next-button-enabled="isValid" + :show-back-button="hasPreviousStep" + show-next-button + @back="onPrevClick" + @next="onNextClick" + /> + </div> +</template> |