debian-mirror-gitlab/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue

474 lines
14 KiB
Vue
Raw Normal View History

2020-10-24 23:57:45 +05:30
<script>
import {
GlAlert,
2021-01-03 14:25:43 +05:30
GlIcon,
2020-10-24 23:57:45 +05:30
GlButton,
GlForm,
GlFormGroup,
GlFormInput,
GlFormSelect,
2021-03-11 19:13:27 +05:30
GlFormTextarea,
2020-10-24 23:57:45 +05:30
GlLink,
GlSprintf,
2021-01-29 00:20:46 +05:30
GlLoadingIcon,
2021-03-08 18:12:59 +05:30
GlSafeHtmlDirective as SafeHtml,
2020-10-24 23:57:45 +05:30
} from '@gitlab/ui';
2021-04-17 20:07:23 +05:30
import * as Sentry from '@sentry/browser';
2021-03-11 19:13:27 +05:30
import { uniqueId } from 'lodash';
import Vue from 'vue';
2020-11-24 15:15:51 +05:30
import axios from '~/lib/utils/axios_utils';
2021-02-22 17:27:13 +05:30
import { backOff } from '~/lib/utils/common_utils';
import httpStatusCodes from '~/lib/utils/http_status';
2021-03-11 19:13:27 +05:30
import { redirectTo } from '~/lib/utils/url_utility';
import { s__, __, n__ } from '~/locale';
2021-06-08 01:23:25 +05:30
import {
VARIABLE_TYPE,
FILE_TYPE,
CONFIG_VARIABLES_TIMEOUT,
CC_VALIDATION_REQUIRED_ERROR,
} from '../constants';
import filterVariables from '../utils/filter_variables';
2021-04-17 20:07:23 +05:30
import RefsDropdown from './refs_dropdown.vue';
const i18n = {
variablesDescription: s__(
'Pipeline|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used by default.',
),
defaultError: __('Something went wrong on our end. Please try again.'),
refsLoadingErrorTitle: s__('Pipeline|Branches or tags could not be loaded.'),
submitErrorTitle: s__('Pipeline|Pipeline cannot be run.'),
warningTitle: __('The form contains the following warning:'),
maxWarningsSummary: __('%{total} warnings found: showing first %{warningsDisplayed}'),
2021-04-29 21:17:54 +05:30
removeVariableLabel: s__('CiVariables|Remove variable'),
2021-04-17 20:07:23 +05:30
};
2020-10-24 23:57:45 +05:30
export default {
typeOptions: [
{ value: VARIABLE_TYPE, text: __('Variable') },
{ value: FILE_TYPE, text: __('File') },
],
2021-04-17 20:07:23 +05:30
i18n,
2021-01-03 14:25:43 +05:30
formElementClasses: 'gl-mr-3 gl-mb-3 gl-flex-basis-quarter gl-flex-shrink-0 gl-flex-grow-0',
2021-03-11 19:13:27 +05:30
// this height value is used inline on the textarea to match the input field height
// it's used to prevent the overwrite if 'gl-h-7' or 'gl-h-7!' were used
textAreaStyle: { height: '32px' },
2020-10-24 23:57:45 +05:30
components: {
GlAlert,
2021-01-03 14:25:43 +05:30
GlIcon,
2020-10-24 23:57:45 +05:30
GlButton,
GlForm,
GlFormGroup,
GlFormInput,
GlFormSelect,
2021-03-11 19:13:27 +05:30
GlFormTextarea,
2020-10-24 23:57:45 +05:30
GlLink,
GlSprintf,
2021-01-29 00:20:46 +05:30
GlLoadingIcon,
2021-04-17 20:07:23 +05:30
RefsDropdown,
2021-06-08 01:23:25 +05:30
CcValidationRequiredAlert: () =>
import('ee_component/billings/components/cc_validation_required_alert.vue'),
2020-10-24 23:57:45 +05:30
},
2021-03-08 18:12:59 +05:30
directives: { SafeHtml },
2020-10-24 23:57:45 +05:30
props: {
pipelinesPath: {
type: String,
required: true,
},
2021-01-03 14:25:43 +05:30
configVariablesPath: {
type: String,
required: true,
},
2021-02-22 17:27:13 +05:30
defaultBranch: {
type: String,
required: true,
},
2020-10-24 23:57:45 +05:30
projectId: {
type: String,
required: true,
},
settingsLink: {
type: String,
required: true,
},
fileParams: {
type: Object,
required: false,
default: () => ({}),
},
refParam: {
type: String,
required: false,
default: '',
},
variableParams: {
type: Object,
required: false,
default: () => ({}),
},
2020-11-24 15:15:51 +05:30
maxWarnings: {
type: Number,
required: true,
},
2020-10-24 23:57:45 +05:30
},
data() {
return {
2021-02-22 17:27:13 +05:30
refValue: {
shortName: this.refParam,
},
2021-01-03 14:25:43 +05:30
form: {},
2021-04-17 20:07:23 +05:30
errorTitle: null,
2020-11-24 15:15:51 +05:30
error: null,
warnings: [],
totalWarnings: 0,
isWarningDismissed: false,
2021-01-29 00:20:46 +05:30
isLoading: false,
2021-03-11 19:13:27 +05:30
submitted: false,
2021-11-11 11:23:49 +05:30
ccAlertDismissed: false,
2020-10-24 23:57:45 +05:30
};
},
computed: {
2020-11-24 15:15:51 +05:30
overMaxWarningsLimit() {
return this.totalWarnings > this.maxWarnings;
},
warningsSummary() {
return n__('%d warning found:', '%d warnings found:', this.warnings.length);
},
summaryMessage() {
2021-04-17 20:07:23 +05:30
return this.overMaxWarningsLimit ? i18n.maxWarningsSummary : this.warningsSummary;
2020-11-24 15:15:51 +05:30
},
shouldShowWarning() {
return this.warnings.length > 0 && !this.isWarningDismissed;
},
2021-02-22 17:27:13 +05:30
refShortName() {
return this.refValue.shortName;
},
refFullName() {
return this.refValue.fullName;
},
2021-01-03 14:25:43 +05:30
variables() {
2021-02-22 17:27:13 +05:30
return this.form[this.refFullName]?.variables ?? [];
2021-01-03 14:25:43 +05:30
},
descriptions() {
2021-02-22 17:27:13 +05:30
return this.form[this.refFullName]?.descriptions ?? {};
2021-01-03 14:25:43 +05:30
},
2021-06-08 01:23:25 +05:30
ccRequiredError() {
2021-11-11 11:23:49 +05:30
return this.error === CC_VALIDATION_REQUIRED_ERROR && !this.ccAlertDismissed;
2021-06-08 01:23:25 +05:30
},
2020-10-24 23:57:45 +05:30
},
2021-04-17 20:07:23 +05:30
watch: {
refValue() {
this.loadConfigVariablesForm();
},
},
2020-10-24 23:57:45 +05:30
created() {
2021-02-22 17:27:13 +05:30
// this is needed until we add support for ref type in url query strings
// ensure default branch is called with full ref on load
// https://gitlab.com/gitlab-org/gitlab/-/issues/287815
if (this.refValue.shortName === this.defaultBranch) {
this.refValue.fullName = `refs/heads/${this.refValue.shortName}`;
}
2021-04-17 20:07:23 +05:30
this.loadConfigVariablesForm();
2020-10-24 23:57:45 +05:30
},
methods: {
2021-01-03 14:25:43 +05:30
addEmptyVariable(refValue) {
const { variables } = this.form[refValue];
const lastVar = variables[variables.length - 1];
if (lastVar?.key === '' && lastVar?.value === '') {
return;
}
variables.push({
uniqueId: uniqueId(`var-${refValue}`),
2020-10-24 23:57:45 +05:30
variable_type: VARIABLE_TYPE,
key: '',
value: '',
2021-01-03 14:25:43 +05:30
});
2020-10-24 23:57:45 +05:30
},
2021-01-03 14:25:43 +05:30
setVariable(refValue, type, key, value) {
const { variables } = this.form[refValue];
2021-03-08 18:12:59 +05:30
const variable = variables.find((v) => v.key === key);
2021-01-03 14:25:43 +05:30
if (variable) {
variable.type = type;
variable.value = value;
} else {
variables.push({
uniqueId: uniqueId(`var-${refValue}`),
2020-10-24 23:57:45 +05:30
key,
value,
variable_type: type,
2021-01-03 14:25:43 +05:30
});
}
},
setVariableParams(refValue, type, paramsObj) {
Object.entries(paramsObj).forEach(([key, value]) => {
this.setVariable(refValue, type, key, value);
2020-10-24 23:57:45 +05:30
});
},
2021-01-03 14:25:43 +05:30
removeVariable(index) {
this.variables.splice(index, 1);
2020-10-24 23:57:45 +05:30
},
2021-01-03 14:25:43 +05:30
canRemove(index) {
return index < this.variables.length - 1;
2020-10-24 23:57:45 +05:30
},
2021-04-17 20:07:23 +05:30
loadConfigVariablesForm() {
// Skip when variables already cached in `form`
if (this.form[this.refFullName]) {
return;
}
this.fetchConfigVariables(this.refFullName || this.refShortName)
.then(({ descriptions, params }) => {
Vue.set(this.form, this.refFullName, {
variables: [],
descriptions,
});
// Add default variables from yml
this.setVariableParams(this.refFullName, VARIABLE_TYPE, params);
})
.catch(() => {
Vue.set(this.form, this.refFullName, {
variables: [],
descriptions: {},
});
})
.finally(() => {
// Add/update variables, e.g. from query string
if (this.variableParams) {
this.setVariableParams(this.refFullName, VARIABLE_TYPE, this.variableParams);
}
if (this.fileParams) {
this.setVariableParams(this.refFullName, FILE_TYPE, this.fileParams);
}
// Adds empty var at the end of the form
this.addEmptyVariable(this.refFullName);
});
},
2021-01-03 14:25:43 +05:30
fetchConfigVariables(refValue) {
2021-02-22 17:27:13 +05:30
this.isLoading = true;
2021-01-29 00:20:46 +05:30
2021-02-22 17:27:13 +05:30
return backOff((next, stop) => {
axios
2021-01-03 14:25:43 +05:30
.get(this.configVariablesPath, {
params: {
sha: refValue,
},
})
2021-02-22 17:27:13 +05:30
.then(({ data, status }) => {
if (status === httpStatusCodes.NO_CONTENT) {
next();
} else {
this.isLoading = false;
stop(data);
}
})
2021-03-08 18:12:59 +05:30
.catch((error) => {
2021-02-22 17:27:13 +05:30
stop(error);
});
}, CONFIG_VARIABLES_TIMEOUT)
2021-03-08 18:12:59 +05:30
.then((data) => {
2021-02-22 17:27:13 +05:30
const params = {};
const descriptions = {};
2021-01-03 14:25:43 +05:30
2021-02-22 17:27:13 +05:30
Object.entries(data).forEach(([key, { value, description }]) => {
if (description !== null) {
params[key] = value;
descriptions[key] = description;
}
});
2021-01-03 14:25:43 +05:30
2021-02-22 17:27:13 +05:30
return { params, descriptions };
})
2021-03-08 18:12:59 +05:30
.catch((error) => {
2021-02-22 17:27:13 +05:30
this.isLoading = false;
2021-01-29 00:20:46 +05:30
2021-02-22 17:27:13 +05:30
Sentry.captureException(error);
return { params: {}, descriptions: {} };
});
2020-10-24 23:57:45 +05:30
},
createPipeline() {
2021-03-11 19:13:27 +05:30
this.submitted = true;
2021-11-11 11:23:49 +05:30
this.ccAlertDismissed = false;
2020-10-24 23:57:45 +05:30
2020-11-24 15:15:51 +05:30
return axios
.post(this.pipelinesPath, {
2021-02-22 17:27:13 +05:30
// send shortName as fall back for query params
// https://gitlab.com/gitlab-org/gitlab/-/issues/287815
ref: this.refValue.fullName || this.refShortName,
2021-06-08 01:23:25 +05:30
variables_attributes: filterVariables(this.variables),
2020-11-24 15:15:51 +05:30
})
.then(({ data }) => {
redirectTo(`${this.pipelinesPath}/${data.id}`);
})
2021-03-08 18:12:59 +05:30
.catch((err) => {
2021-03-11 19:13:27 +05:30
// always re-enable submit button
this.submitted = false;
const {
errors = [],
warnings = [],
total_warnings: totalWarnings = 0,
} = err?.response?.data;
2020-11-24 15:15:51 +05:30
const [error] = errors;
2021-03-11 19:13:27 +05:30
2021-04-17 20:07:23 +05:30
this.reportError({
title: i18n.submitErrorTitle,
error,
warnings,
totalWarnings,
});
2020-10-24 23:57:45 +05:30
});
},
2021-04-17 20:07:23 +05:30
onRefsLoadingError(error) {
this.reportError({ title: i18n.refsLoadingErrorTitle });
Sentry.captureException(error);
},
reportError({ title = null, error = i18n.defaultError, warnings = [], totalWarnings = 0 }) {
this.errorTitle = title;
this.error = error;
this.warnings = warnings;
this.totalWarnings = totalWarnings;
},
2021-11-11 11:23:49 +05:30
dismissError() {
this.ccAlertDismissed = true;
this.error = null;
},
2020-10-24 23:57:45 +05:30
},
};
</script>
<template>
<gl-form @submit.prevent="createPipeline">
2021-11-11 11:23:49 +05:30
<cc-validation-required-alert v-if="ccRequiredError" class="gl-pb-5" @dismiss="dismissError" />
2020-10-24 23:57:45 +05:30
<gl-alert
2021-06-08 01:23:25 +05:30
v-else-if="error"
2021-04-17 20:07:23 +05:30
:title="errorTitle"
2020-10-24 23:57:45 +05:30
:dismissible="false"
variant="danger"
class="gl-mb-4"
2020-11-24 15:15:51 +05:30
data-testid="run-pipeline-error-alert"
2020-10-24 23:57:45 +05:30
>
2021-03-08 18:12:59 +05:30
<span v-safe-html="error"></span>
</gl-alert>
2020-11-24 15:15:51 +05:30
<gl-alert
v-if="shouldShowWarning"
2021-04-17 20:07:23 +05:30
:title="$options.i18n.warningTitle"
2020-11-24 15:15:51 +05:30
variant="warning"
class="gl-mb-4"
data-testid="run-pipeline-warning-alert"
@dismiss="isWarningDismissed = true"
>
<details>
<summary>
<gl-sprintf :message="summaryMessage">
<template #total>
{{ totalWarnings }}
</template>
<template #warningsDisplayed>
{{ maxWarnings }}
</template>
</gl-sprintf>
</summary>
<p
v-for="(warning, index) in warnings"
:key="`warning-${index}`"
data-testid="run-pipeline-warning"
>
{{ warning }}
</p>
</details>
</gl-alert>
2021-03-08 18:12:59 +05:30
<gl-form-group :label="s__('Pipeline|Run for branch name or tag')">
2021-04-17 20:07:23 +05:30
<refs-dropdown v-model="refValue" @loadingError="onRefsLoadingError" />
2020-10-24 23:57:45 +05:30
</gl-form-group>
2021-01-29 00:20:46 +05:30
<gl-loading-icon v-if="isLoading" class="gl-mb-5" size="lg" />
<gl-form-group v-else :label="s__('Pipeline|Variables')">
2020-10-24 23:57:45 +05:30
<div
2021-01-03 14:25:43 +05:30
v-for="(variable, index) in variables"
:key="variable.uniqueId"
class="gl-mb-3 gl-ml-n3 gl-pb-2"
2020-10-24 23:57:45 +05:30
data-testid="ci-variable-row"
>
2021-01-03 14:25:43 +05:30
<div
class="gl-display-flex gl-align-items-stretch gl-flex-direction-column gl-md-flex-direction-row"
>
<gl-form-select
v-model="variable.variable_type"
:class="$options.formElementClasses"
:options="$options.typeOptions"
2021-06-08 01:23:25 +05:30
data-testid="pipeline-form-ci-variable-type"
2021-01-03 14:25:43 +05:30
/>
<gl-form-input
v-model="variable.key"
:placeholder="s__('CiVariables|Input variable key')"
:class="$options.formElementClasses"
data-testid="pipeline-form-ci-variable-key"
2021-02-22 17:27:13 +05:30
@change="addEmptyVariable(refFullName)"
2021-01-03 14:25:43 +05:30
/>
2021-03-11 19:13:27 +05:30
<gl-form-textarea
2021-01-03 14:25:43 +05:30
v-model="variable.value"
:placeholder="s__('CiVariables|Input variable value')"
class="gl-mb-3"
2021-03-11 19:13:27 +05:30
:style="$options.textAreaStyle"
:no-resize="false"
2021-01-03 14:25:43 +05:30
data-testid="pipeline-form-ci-variable-value"
/>
<template v-if="variables.length > 1">
<gl-button
v-if="canRemove(index)"
class="gl-md-ml-3 gl-mb-3"
data-testid="remove-ci-variable-row"
variant="danger"
category="secondary"
2021-04-29 21:17:54 +05:30
:aria-label="$options.i18n.removeVariableLabel"
2021-01-03 14:25:43 +05:30
@click="removeVariable(index)"
>
2021-03-11 19:13:27 +05:30
<gl-icon class="gl-mr-0! gl-display-none gl-md-display-block" name="clear" />
2021-04-29 21:17:54 +05:30
<span class="gl-md-display-none">{{ $options.i18n.removeVariableLabel }}</span>
2021-01-03 14:25:43 +05:30
</gl-button>
<gl-button
v-else
2021-03-11 19:13:27 +05:30
class="gl-md-ml-3 gl-mb-3 gl-display-none gl-md-display-block gl-visibility-hidden"
2021-01-03 14:25:43 +05:30
icon="clear"
2021-04-29 21:17:54 +05:30
:aria-label="$options.i18n.removeVariableLabel"
2021-01-03 14:25:43 +05:30
/>
</template>
</div>
<div v-if="descriptions[variable.key]" class="gl-text-gray-500 gl-mb-3">
{{ descriptions[variable.key] }}
</div>
2020-10-24 23:57:45 +05:30
</div>
<template #description
2021-04-17 20:07:23 +05:30
><gl-sprintf :message="$options.i18n.variablesDescription">
2020-10-24 23:57:45 +05:30
<template #link="{ content }">
<gl-link :href="settingsLink">{{ content }}</gl-link>
</template>
</gl-sprintf></template
>
</gl-form-group>
2021-04-29 21:17:54 +05:30
<div class="gl-pt-5 gl-display-flex">
2021-01-03 14:25:43 +05:30
<gl-button
type="submit"
category="primary"
2021-04-29 21:17:54 +05:30
variant="confirm"
class="js-no-auto-disable gl-mr-3"
2021-01-03 14:25:43 +05:30
data-qa-selector="run_pipeline_button"
2021-03-11 19:13:27 +05:30
data-testid="run_pipeline_button"
:disabled="submitted"
2021-04-29 21:17:54 +05:30
>{{ s__('Pipeline|Run pipeline') }}</gl-button
2021-01-03 14:25:43 +05:30
>
2020-10-24 23:57:45 +05:30
<gl-button :href="pipelinesPath">{{ __('Cancel') }}</gl-button>
</div>
</gl-form>
</template>