146 lines
4.2 KiB
Vue
146 lines
4.2 KiB
Vue
<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_wrapper.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) => 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-8"
|
|
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>
|