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:
Diffstat (limited to 'app/assets/javascripts/ci/job_details/components/manual_variables_form.vue')
-rw-r--r--app/assets/javascripts/ci/job_details/components/manual_variables_form.vue305
1 files changed, 305 insertions, 0 deletions
diff --git a/app/assets/javascripts/ci/job_details/components/manual_variables_form.vue b/app/assets/javascripts/ci/job_details/components/manual_variables_form.vue
new file mode 100644
index 00000000000..1232ffffb57
--- /dev/null
+++ b/app/assets/javascripts/ci/job_details/components/manual_variables_form.vue
@@ -0,0 +1,305 @@
+<script>
+import {
+ GlFormInputGroup,
+ GlInputGroupText,
+ GlFormInput,
+ GlButton,
+ GlLink,
+ GlLoadingIcon,
+ GlSprintf,
+ GlTooltipDirective,
+} from '@gitlab/ui';
+import { cloneDeep, uniqueId } from 'lodash';
+import { fetchPolicies } from '~/lib/graphql';
+import { createAlert } from '~/alert';
+import { TYPENAME_CI_BUILD, TYPENAME_COMMIT_STATUS } from '~/graphql_shared/constants';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
+import { JOB_GRAPHQL_ERRORS } from '~/ci/constants';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated
+import { s__ } from '~/locale';
+import { reportMessageToSentry } from '~/ci/utils';
+import GetJob from '../graphql/queries/get_job.query.graphql';
+import playJobWithVariablesMutation from '../graphql/mutations/job_play_with_variables.mutation.graphql';
+import retryJobWithVariablesMutation from '../graphql/mutations/job_retry_with_variables.mutation.graphql';
+
+// This component is a port of ~/ci/job_details/components/legacy_manual_variables_form.vue
+// It is meant to fetch/update the job information via GraphQL instead of REST API.
+
+export default {
+ name: 'ManualVariablesForm',
+ components: {
+ GlFormInputGroup,
+ GlInputGroupText,
+ GlFormInput,
+ GlButton,
+ GlLink,
+ GlLoadingIcon,
+ GlSprintf,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ inject: ['projectPath'],
+ apollo: {
+ variables: {
+ query: GetJob,
+ variables() {
+ return {
+ fullPath: this.projectPath,
+ id: convertToGraphQLId(TYPENAME_COMMIT_STATUS, this.jobId),
+ };
+ },
+ fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
+ update(data) {
+ const jobVariables = cloneDeep(data?.project?.job?.manualVariables?.nodes);
+ return [...jobVariables.reverse(), ...this.variables];
+ },
+ error(error) {
+ createAlert({ message: JOB_GRAPHQL_ERRORS.jobQueryErrorText });
+ reportMessageToSentry(this.$options.name, error, {});
+ },
+ },
+ },
+ props: {
+ isRetryable: {
+ type: Boolean,
+ required: true,
+ },
+ jobId: {
+ type: Number,
+ required: true,
+ },
+ },
+ clearBtnSharedClasses: ['gl-flex-grow-0 gl-flex-basis-0 gl-m-0! gl-ml-3!'],
+ inputTypes: {
+ key: 'key',
+ value: 'value',
+ },
+ i18n: {
+ cancel: s__('CiVariables|Cancel'),
+ removeInputs: s__('CiVariables|Remove inputs'),
+ formHelpText: s__(
+ 'CiVariables|Specify variable values to be used in this run. The variables specified in the configuration file and %{linkStart}CI/CD settings%{linkEnd} are used by default.',
+ ),
+ overrideNoteText: s__(
+ 'CiVariables|Variables specified here are %{boldStart}expanded%{boldEnd} and not %{boldStart}masked.%{boldEnd}',
+ ),
+ header: s__('CiVariables|Variables'),
+ keyLabel: s__('CiVariables|Key'),
+ keyPlaceholder: s__('CiVariables|Input variable key'),
+ runAgainButtonText: s__('CiVariables|Run job again'),
+ runButtonText: s__('CiVariables|Run job'),
+ valueLabel: s__('CiVariables|Value'),
+ valuePlaceholder: s__('CiVariables|Input variable value'),
+ },
+ data() {
+ return {
+ job: {},
+ variables: [
+ {
+ id: uniqueId(),
+ key: '',
+ value: '',
+ },
+ ],
+ runBtnDisabled: false,
+ };
+ },
+ computed: {
+ mutationVariables() {
+ return {
+ id: convertToGraphQLId(TYPENAME_CI_BUILD, this.jobId),
+ variables: this.preparedVariables,
+ };
+ },
+ preparedVariables() {
+ return this.variables
+ .filter((variable) => variable.key !== '')
+ .map(({ key, value }) => ({ key, value }));
+ },
+ runBtnText() {
+ return this.isRetryable
+ ? this.$options.i18n.runAgainButtonText
+ : this.$options.i18n.runButtonText;
+ },
+ variableSettings() {
+ return helpPagePath('ci/variables/index', { anchor: 'add-a-cicd-variable-to-a-project' });
+ },
+ },
+ methods: {
+ async playJob() {
+ try {
+ const { data } = await this.$apollo.mutate({
+ mutation: playJobWithVariablesMutation,
+ variables: this.mutationVariables,
+ });
+ if (data.jobPlay?.errors?.length) {
+ createAlert({ message: data.jobPlay.errors[0] });
+ } else {
+ this.navigateToJob(data.jobPlay?.job?.webPath);
+ }
+ } catch (error) {
+ createAlert({ message: JOB_GRAPHQL_ERRORS.jobMutationErrorText });
+ reportMessageToSentry(this.$options.name, error, {});
+ }
+ },
+ async retryJob() {
+ try {
+ const { data } = await this.$apollo.mutate({
+ mutation: retryJobWithVariablesMutation,
+ variables: this.mutationVariables,
+ });
+ if (data.jobRetry?.errors?.length) {
+ createAlert({ message: data.jobRetry.errors[0] });
+ } else {
+ this.navigateToJob(data.jobRetry?.job?.webPath);
+ }
+ } catch (error) {
+ createAlert({ message: JOB_GRAPHQL_ERRORS.jobMutationErrorText });
+ reportMessageToSentry(this.$options.name, error, {});
+ }
+ },
+ addEmptyVariable() {
+ const lastVar = this.variables[this.variables.length - 1];
+
+ if (lastVar.key === '') {
+ return;
+ }
+
+ this.variables.push({
+ id: uniqueId(),
+ key: '',
+ value: '',
+ });
+ },
+ canRemove(index) {
+ return index < this.variables.length - 1;
+ },
+ deleteVariable(id) {
+ this.variables.splice(
+ this.variables.findIndex((el) => el.id === id),
+ 1,
+ );
+ },
+ inputRef(type, id) {
+ return `${this.$options.inputTypes[type]}-${id}`;
+ },
+ navigateToJob(path) {
+ redirectTo(path); // eslint-disable-line import/no-deprecated
+ },
+ runJob() {
+ this.runBtnDisabled = true;
+
+ if (this.isRetryable) {
+ this.retryJob();
+ } else {
+ this.playJob();
+ }
+ },
+ },
+};
+</script>
+<template>
+ <gl-loading-icon v-if="$apollo.queries.variables.loading" class="gl-mt-9" size="lg" />
+ <div v-else class="row gl-justify-content-center">
+ <div class="col-10">
+ <label>{{ $options.i18n.header }}</label>
+
+ <div
+ v-for="(variable, index) in variables"
+ :key="variable.id"
+ class="gl-display-flex gl-align-items-center gl-mb-5"
+ data-testid="ci-variable-row"
+ >
+ <gl-form-input-group class="gl-mr-4 gl-flex-grow-1">
+ <template #prepend>
+ <gl-input-group-text>
+ {{ $options.i18n.keyLabel }}
+ </gl-input-group-text>
+ </template>
+ <gl-form-input
+ :ref="inputRef('key', variable.id)"
+ v-model="variable.key"
+ :placeholder="$options.i18n.keyPlaceholder"
+ data-testid="ci-variable-key"
+ @change="addEmptyVariable"
+ />
+ </gl-form-input-group>
+
+ <gl-form-input-group class="gl-flex-grow-2">
+ <template #prepend>
+ <gl-input-group-text>
+ {{ $options.i18n.valueLabel }}
+ </gl-input-group-text>
+ </template>
+ <gl-form-input
+ :ref="inputRef('value', variable.id)"
+ v-model="variable.value"
+ :placeholder="$options.i18n.valuePlaceholder"
+ data-testid="ci-variable-value"
+ />
+ </gl-form-input-group>
+
+ <gl-button
+ v-if="canRemove(index)"
+ v-gl-tooltip
+ :aria-label="$options.i18n.removeInputs"
+ :title="$options.i18n.removeInputs"
+ :class="$options.clearBtnSharedClasses"
+ category="tertiary"
+ icon="remove"
+ data-testid="delete-variable-btn"
+ @click="deleteVariable(variable.id)"
+ />
+ <!-- Placeholder button to keep the layout fixed -->
+ <gl-button
+ v-else
+ class="gl-opacity-0 gl-pointer-events-none"
+ :class="$options.clearBtnSharedClasses"
+ data-testid="delete-variable-btn-placeholder"
+ category="tertiary"
+ icon="remove"
+ />
+ </div>
+
+ <div class="gl-text-center gl-mt-5">
+ <gl-sprintf :message="$options.i18n.formHelpText">
+ <template #link="{ content }">
+ <gl-link :href="variableSettings" target="_blank">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+ <div class="gl-text-center gl-mt-3">
+ <gl-sprintf :message="$options.i18n.overrideNoteText">
+ <template #bold="{ content }">
+ <strong>
+ {{ content }}
+ </strong>
+ </template>
+ </gl-sprintf>
+ </div>
+ <div class="gl-display-flex gl-justify-content-center gl-mt-5">
+ <gl-button
+ v-if="isRetryable"
+ class="gl-mt-5"
+ data-testid="cancel-btn"
+ @click="$emit('hideManualVariablesForm')"
+ >{{ $options.i18n.cancel }}</gl-button
+ >
+ <gl-button
+ class="gl-mt-5"
+ variant="confirm"
+ category="primary"
+ :disabled="runBtnDisabled"
+ data-testid="run-manual-job-btn"
+ @click="runJob"
+ >
+ {{ runBtnText }}
+ </gl-button>
+ </div>
+ </div>
+ </div>
+</template>