Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-02-18 12:45:46 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-02-18 12:45:46 +0300
commita7b3560714b4d9cc4ab32dffcd1f74a284b93580 (patch)
tree7452bd5c3545c2fa67a28aa013835fb4fa071baf /app/assets/javascripts/pipeline_wizard
parentee9173579ae56a3dbfe5afe9f9410c65bb327ca7 (diff)
Add latest changes from gitlab-org/gitlab@14-8-stable-eev14.8.0-rc42
Diffstat (limited to 'app/assets/javascripts/pipeline_wizard')
-rw-r--r--app/assets/javascripts/pipeline_wizard/components/commit.vue224
-rw-r--r--app/assets/javascripts/pipeline_wizard/components/editor.vue94
-rw-r--r--app/assets/javascripts/pipeline_wizard/components/step_nav.vue54
-rw-r--r--app/assets/javascripts/pipeline_wizard/components/widgets/text.vue126
-rw-r--r--app/assets/javascripts/pipeline_wizard/queries/create_commit.graphql9
-rw-r--r--app/assets/javascripts/pipeline_wizard/queries/get_file_meta.graphql12
6 files changed, 519 insertions, 0 deletions
diff --git a/app/assets/javascripts/pipeline_wizard/components/commit.vue b/app/assets/javascripts/pipeline_wizard/components/commit.vue
new file mode 100644
index 00000000000..518b41c66b1
--- /dev/null
+++ b/app/assets/javascripts/pipeline_wizard/components/commit.vue
@@ -0,0 +1,224 @@
+<script>
+import { GlAlert, GlButton, GlForm, GlFormGroup, GlFormTextarea } from '@gitlab/ui';
+import RefSelector from '~/ref/components/ref_selector.vue';
+import { __, s__, sprintf } from '~/locale';
+import createCommitMutation from '../queries/create_commit.graphql';
+import getFileMetaDataQuery from '../queries/get_file_meta.graphql';
+import StepNav from './step_nav.vue';
+
+export const i18n = {
+ updateFileHeading: s__('PipelineWizard|Commit changes to your file'),
+ createFileHeading: s__('PipelineWizard|Commit your new file'),
+ fieldRequiredFeedback: __('This field is required'),
+ commitMessageLabel: s__('PipelineWizard|Commit Message'),
+ branchSelectorLabel: s__('PipelineWizard|Commit file to Branch'),
+ defaultUpdateCommitMessage: s__('PipelineWizardDefaultCommitMessage|Update %{filename}'),
+ defaultCreateCommitMessage: s__('PipelineWizardDefaultCommitMessage|Add %{filename}'),
+ commitButtonLabel: s__('PipelineWizard|Commit'),
+ commitSuccessMessage: s__('PipelineWizard|The file has been committed.'),
+ errors: {
+ loadError: s__(
+ 'PipelineWizard|There was a problem while checking whether your file already exists in the specified branch.',
+ ),
+ commitError: s__('PipelineWizard|There was a problem committing the changes.'),
+ },
+};
+
+const COMMIT_ACTION = {
+ CREATE: 'CREATE',
+ UPDATE: 'UPDATE',
+};
+
+export default {
+ i18n,
+ name: 'PipelineWizardCommitStep',
+ components: {
+ RefSelector,
+ GlAlert,
+ GlButton,
+ GlForm,
+ GlFormGroup,
+ GlFormTextarea,
+ StepNav,
+ },
+ props: {
+ prev: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ defaultBranch: {
+ type: String,
+ required: true,
+ },
+ fileContent: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ filename: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ branch: this.defaultBranch,
+ loading: false,
+ loadError: null,
+ commitError: null,
+ message: null,
+ };
+ },
+ computed: {
+ fileExistsInRepo() {
+ return this.project?.repository?.blobs.nodes.length > 0;
+ },
+ commitAction() {
+ return this.fileExistsInRepo ? COMMIT_ACTION.UPDATE : COMMIT_ACTION.CREATE;
+ },
+ defaultMessage() {
+ return sprintf(
+ this.fileExistsInRepo
+ ? this.$options.i18n.defaultUpdateCommitMessage
+ : this.$options.i18n.defaultCreateCommitMessage,
+ { filename: this.filename },
+ );
+ },
+ isCommitButtonEnabled() {
+ return this.fileExistsCheckInProgress;
+ },
+ fileExistsCheckInProgress() {
+ return this.$apollo.queries.project.loading;
+ },
+ mutationPayload() {
+ return {
+ mutation: createCommitMutation,
+ variables: {
+ input: {
+ projectPath: this.projectPath,
+ branch: this.branch,
+ message: this.message || this.defaultMessage,
+ actions: [
+ {
+ action: this.commitAction,
+ filePath: `/${this.filename}`,
+ content: this.fileContent,
+ },
+ ],
+ },
+ },
+ };
+ },
+ },
+ apollo: {
+ project: {
+ query: getFileMetaDataQuery,
+ variables() {
+ this.loadError = null;
+ return {
+ fullPath: this.projectPath,
+ filePath: this.filename,
+ ref: this.branch,
+ };
+ },
+ error() {
+ this.loadError = this.$options.i18n.errors.loadError;
+ },
+ },
+ },
+ methods: {
+ async commit() {
+ this.loading = true;
+ try {
+ const { data } = await this.$apollo.mutate(this.mutationPayload);
+ const hasError = Boolean(data.commitCreate.errors?.length);
+ if (hasError) {
+ this.commitError = this.$options.i18n.errors.commitError;
+ } else {
+ this.handleCommitSuccess();
+ }
+ } catch (e) {
+ this.commitError = this.$options.i18n.errors.commitError;
+ } finally {
+ this.loading = false;
+ }
+ },
+ handleCommitSuccess() {
+ this.$toast.show(this.$options.i18n.commitSuccessMessage);
+ this.$emit('done');
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <h4 v-if="fileExistsInRepo" key="create-heading">
+ {{ $options.i18n.updateFileHeading }}
+ </h4>
+ <h4 v-else key="update-heading">
+ {{ $options.i18n.createFileHeading }}
+ </h4>
+ <gl-alert
+ v-if="!!loadError"
+ :dismissible="false"
+ class="gl-mb-5"
+ data-testid="load-error"
+ variant="danger"
+ >
+ {{ loadError }}
+ </gl-alert>
+ <gl-form class="gl-max-w-48">
+ <gl-form-group
+ :invalid-feedback="$options.i18n.fieldRequiredFeedback"
+ :label="$options.i18n.commitMessageLabel"
+ data-testid="commit_message_group"
+ label-for="commit_message"
+ >
+ <gl-form-textarea
+ id="commit_message"
+ v-model="message"
+ :placeholder="defaultMessage"
+ data-testid="commit_message"
+ size="md"
+ @input="(v) => $emit('update:message', v)"
+ />
+ </gl-form-group>
+ <gl-form-group
+ :invalid-feedback="$options.i18n.fieldRequiredFeedback"
+ :label="$options.i18n.branchSelectorLabel"
+ data-testid="branch_selector_group"
+ label-for="branch"
+ >
+ <ref-selector id="branch" v-model="branch" data-testid="branch" :project-id="projectPath" />
+ </gl-form-group>
+ <gl-alert
+ v-if="!!commitError"
+ :dismissible="false"
+ class="gl-mb-5"
+ data-testid="commit-error"
+ variant="danger"
+ >
+ {{ commitError }}
+ </gl-alert>
+ <step-nav show-back-button v-bind="$props" @back="$emit('go-back')">
+ <template #after>
+ <gl-button
+ :disabled="isCommitButtonEnabled"
+ :loading="fileExistsCheckInProgress || loading"
+ category="primary"
+ variant="confirm"
+ @click="commit"
+ >
+ {{ $options.i18n.commitButtonLabel }}
+ </gl-button>
+ </template>
+ </step-nav>
+ </gl-form>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipeline_wizard/components/editor.vue b/app/assets/javascripts/pipeline_wizard/components/editor.vue
new file mode 100644
index 00000000000..41611233f71
--- /dev/null
+++ b/app/assets/javascripts/pipeline_wizard/components/editor.vue
@@ -0,0 +1,94 @@
+<script>
+import { debounce } from 'lodash';
+import { isDocument } from 'yaml';
+import { CONTENT_UPDATE_DEBOUNCE } from '~/editor/constants';
+import SourceEditor from '~/editor/source_editor';
+import { YamlEditorExtension } from '~/editor/extensions/source_editor_yaml_ext';
+import { SourceEditorExtension } from '~/editor/extensions/source_editor_extension_base';
+
+export default {
+ name: 'YamlEditor',
+ props: {
+ doc: {
+ type: Object,
+ required: true,
+ validator: (d) => isDocument(d),
+ },
+ highlight: {
+ type: [String, Array],
+ required: false,
+ default: null,
+ },
+ filename: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ editor: null,
+ isUpdating: false,
+ yamlEditorExtension: null,
+ };
+ },
+ watch: {
+ doc: {
+ handler() {
+ this.updateEditorContent();
+ },
+ deep: true,
+ },
+ highlight(v) {
+ this.requestHighlight(v);
+ },
+ },
+ mounted() {
+ this.editor = new SourceEditor().createInstance({
+ el: this.$el,
+ blobPath: this.filename,
+ language: 'yaml',
+ });
+ [, this.yamlEditorExtension] = this.editor.use([
+ { definition: SourceEditorExtension },
+ {
+ definition: YamlEditorExtension,
+ setupOptions: {
+ highlightPath: this.highlight,
+ },
+ },
+ ]);
+ this.editor.onDidChangeModelContent(
+ debounce(() => this.handleChange(), CONTENT_UPDATE_DEBOUNCE),
+ );
+ this.updateEditorContent();
+ this.emitValue();
+ },
+ methods: {
+ async updateEditorContent() {
+ this.isUpdating = true;
+ this.editor.setDoc(this.doc);
+ this.isUpdating = false;
+ this.requestHighlight(this.highlight);
+ },
+ handleChange() {
+ this.emitValue();
+ if (!this.isUpdating) {
+ this.handleTouch();
+ }
+ },
+ emitValue() {
+ this.$emit('update:yaml', this.editor.getValue());
+ },
+ handleTouch() {
+ this.$emit('touch');
+ },
+ requestHighlight(path) {
+ this.editor.highlight(path, true);
+ },
+ },
+};
+</script>
+
+<template>
+ <div id="source-editor-yaml-editor"></div>
+</template>
diff --git a/app/assets/javascripts/pipeline_wizard/components/step_nav.vue b/app/assets/javascripts/pipeline_wizard/components/step_nav.vue
new file mode 100644
index 00000000000..8f9198855c6
--- /dev/null
+++ b/app/assets/javascripts/pipeline_wizard/components/step_nav.vue
@@ -0,0 +1,54 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+
+export default {
+ name: 'StepNav',
+ components: {
+ GlButton,
+ },
+ props: {
+ showBackButton: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ showNextButton: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ nextButtonEnabled: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <slot name="before"></slot>
+ <gl-button
+ v-if="showBackButton"
+ category="secondary"
+ data-testid="back-button"
+ @click="$emit('back')"
+ >
+ {{ __('Back') }}
+ </gl-button>
+ <gl-button
+ v-if="showNextButton"
+ :disabled="!nextButtonEnabled"
+ category="primary"
+ data-testid="next-button"
+ variant="confirm"
+ @click="$emit('next')"
+ >
+ {{ __('Next') }}
+ </gl-button>
+ <slot name="after"></slot>
+ </div>
+</template>
+
+<style scoped></style>
diff --git a/app/assets/javascripts/pipeline_wizard/components/widgets/text.vue b/app/assets/javascripts/pipeline_wizard/components/widgets/text.vue
new file mode 100644
index 00000000000..26235b20ce9
--- /dev/null
+++ b/app/assets/javascripts/pipeline_wizard/components/widgets/text.vue
@@ -0,0 +1,126 @@
+<script>
+import { GlFormGroup, GlFormInput } from '@gitlab/ui';
+import { uniqueId } from 'lodash';
+import { s__ } from '~/locale';
+
+const VALIDATION_STATE = {
+ NO_VALIDATION: null,
+ INVALID: false,
+ VALID: true,
+};
+
+export default {
+ name: 'TextWidget',
+ components: {
+ GlFormGroup,
+ GlFormInput,
+ },
+ props: {
+ label: {
+ type: String,
+ required: true,
+ },
+ description: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ placeholder: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ invalidFeedback: {
+ type: String,
+ required: false,
+ default: s__('PipelineWizardInputValidation|This value is not valid'),
+ },
+ id: {
+ type: String,
+ required: false,
+ default: () => uniqueId('textWidget-'),
+ },
+ pattern: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ validate: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ required: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ default: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ touched: false,
+ value: this.default,
+ };
+ },
+ computed: {
+ validationState() {
+ if (!this.showValidationState) return VALIDATION_STATE.NO_VALIDATION;
+ if (this.isRequiredButEmpty) return VALIDATION_STATE.INVALID;
+ return this.needsValidationAndPasses ? VALIDATION_STATE.VALID : VALIDATION_STATE.INVALID;
+ },
+ showValidationState() {
+ return this.touched || this.validate;
+ },
+ isRequiredButEmpty() {
+ return this.required && !this.value;
+ },
+ needsValidationAndPasses() {
+ return !this.pattern || new RegExp(this.pattern).test(this.value);
+ },
+ invalidFeedbackMessage() {
+ return this.isRequiredButEmpty
+ ? s__('PipelineWizardInputValidation|This field is required')
+ : this.invalidFeedback;
+ },
+ },
+ watch: {
+ validationState(v) {
+ this.$emit('update:valid', v);
+ },
+ value(v) {
+ this.$emit('input', v.trim());
+ },
+ },
+ created() {
+ if (this.default) {
+ this.$emit('input', this.value);
+ }
+ },
+};
+</script>
+
+<template>
+ <div data-testid="text-widget">
+ <gl-form-group
+ :description="description"
+ :invalid-feedback="invalidFeedbackMessage"
+ :label="label"
+ :label-for="id"
+ :state="validationState"
+ >
+ <gl-form-input
+ :id="id"
+ v-model="value"
+ :placeholder="placeholder"
+ :state="validationState"
+ type="text"
+ @blur="touched = true"
+ />
+ </gl-form-group>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipeline_wizard/queries/create_commit.graphql b/app/assets/javascripts/pipeline_wizard/queries/create_commit.graphql
new file mode 100644
index 00000000000..9abf8eff587
--- /dev/null
+++ b/app/assets/javascripts/pipeline_wizard/queries/create_commit.graphql
@@ -0,0 +1,9 @@
+mutation CreateCommit($input: CommitCreateInput!) {
+ commitCreate(input: $input) {
+ commit {
+ id
+ }
+ content
+ errors
+ }
+}
diff --git a/app/assets/javascripts/pipeline_wizard/queries/get_file_meta.graphql b/app/assets/javascripts/pipeline_wizard/queries/get_file_meta.graphql
new file mode 100644
index 00000000000..87f014fade6
--- /dev/null
+++ b/app/assets/javascripts/pipeline_wizard/queries/get_file_meta.graphql
@@ -0,0 +1,12 @@
+query GetFileMetadata($fullPath: ID!, $filePath: String!, $ref: String) {
+ project(fullPath: $fullPath) {
+ id
+ repository {
+ blobs(paths: [$filePath], ref: $ref) {
+ nodes {
+ id
+ }
+ }
+ }
+ }
+}