summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/pipeline_wizard/components/step.vue
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/pipeline_wizard/components/step.vue')
-rw-r--r--app/assets/javascripts/pipeline_wizard/components/step.vue149
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>