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:
-rw-r--r--app/assets/javascripts/api.js11
-rw-r--r--app/assets/javascripts/helpers/monitor_helper.js3
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard_panel.vue12
-rw-r--r--app/assets/javascripts/monitoring/csv_export.js147
-rw-r--r--app/assets/javascripts/pages/projects/pipelines/new/index.js17
-rw-r--r--app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue247
-rw-r--r--app/assets/javascripts/pipeline_new/constants.js2
-rw-r--r--app/assets/javascripts/pipeline_new/index.js36
-rw-r--r--app/assets/javascripts/snippets/components/edit.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue13
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js18
-rw-r--r--app/controllers/projects/pipelines_controller.rb1
-rw-r--r--app/models/releases/link.rb2
-rw-r--r--app/views/projects/pipelines/new.html.haml66
-rw-r--r--changelogs/unreleased/214627-fix-incorrect-csv-export.yml5
-rw-r--r--changelogs/unreleased/227598-make-ordered-list-configurable.yml5
-rw-r--r--doc/user/incident_management/img/pagerduty_incidents_integration_13_3.png (renamed from doc/user/incident_management/img/pagerduty_incidents_integration_13_2.png)bin43318 -> 43318 bytes
-rw-r--r--doc/user/incident_management/index.md4
-rw-r--r--doc/user/project/integrations/bamboo.md6
-rw-r--r--doc/user/project/integrations/bugzilla.md6
-rw-r--r--doc/user/project/integrations/custom_issue_tracker.md6
-rw-r--r--doc/user/project/integrations/discord_notifications.md6
-rw-r--r--doc/user/project/integrations/emails_on_push.md6
-rw-r--r--doc/user/project/integrations/github.md6
-rw-r--r--doc/user/project/integrations/gitlab_slack_application.md6
-rw-r--r--doc/user/project/integrations/hangouts_chat.md6
-rw-r--r--doc/user/project/integrations/hipchat.md6
-rw-r--r--doc/user/project/integrations/index.md6
-rw-r--r--doc/user/project/integrations/irker.md6
-rw-r--r--doc/user/project/integrations/jira.md6
-rw-r--r--doc/user/project/integrations/jira_cloud_configuration.md6
-rw-r--r--doc/user/project/integrations/jira_server_configuration.md6
-rw-r--r--doc/user/project/integrations/mattermost.md6
-rw-r--r--doc/user/project/integrations/mattermost_slash_commands.md6
-rw-r--r--doc/user/project/integrations/microsoft_teams.md6
-rw-r--r--doc/user/project/integrations/mock_ci.md6
-rw-r--r--doc/user/project/integrations/overview.md6
-rw-r--r--doc/user/project/integrations/redmine.md6
-rw-r--r--doc/user/project/integrations/services_templates.md6
-rw-r--r--doc/user/project/integrations/slack.md6
-rw-r--r--doc/user/project/integrations/slack_slash_commands.md6
-rw-r--r--doc/user/project/integrations/unify_circuit.md6
-rw-r--r--doc/user/project/integrations/webex_teams.md6
-rw-r--r--doc/user/project/integrations/webhooks.md6
-rw-r--r--doc/user/project/integrations/youtrack.md6
-rw-r--r--locale/gitlab.pot18
-rw-r--r--spec/features/populate_new_pipeline_vars_with_params_spec.rb1
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb2
-rw-r--r--spec/fixtures/api/schemas/release/link.json1
-rw-r--r--spec/frontend/api_spec.js30
-rw-r--r--spec/frontend/helpers/monitor_helper_spec.js58
-rw-r--r--spec/frontend/monitoring/components/dashboard_panel_spec.js2
-rw-r--r--spec/frontend/monitoring/csv_export_spec.js126
-rw-r--r--spec/frontend/monitoring/graph_data.js4
-rw-r--r--spec/frontend/pipeline_new/components/pipeline_new_form_spec.js108
-rw-r--r--spec/frontend/pipeline_new/mock_data.js21
-rw-r--r--spec/frontend/snippets/components/edit_spec.js69
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js22
58 files changed, 810 insertions, 405 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 246231d969b..64b55b4d12f 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -55,6 +55,7 @@ const Api = {
adminStatisticsPath: '/api/:version/application/statistics',
pipelineSinglePath: '/api/:version/projects/:id/pipelines/:pipeline_id',
pipelinesPath: '/api/:version/projects/:id/pipelines/',
+ createPipelinePath: '/api/:version/projects/:id/pipeline',
environmentsPath: '/api/:version/projects/:id/environments',
rawFilePath: '/api/:version/projects/:id/repository/files/:path/raw',
issuePath: '/api/:version/projects/:id/issues/:issue_iid',
@@ -576,6 +577,16 @@ const Api = {
});
},
+ createPipeline(id, data) {
+ const url = Api.buildUrl(this.createPipelinePath).replace(':id', encodeURIComponent(id));
+
+ return axios.post(url, data, {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+ },
+
environments(id) {
const url = Api.buildUrl(this.environmentsPath).replace(':id', encodeURIComponent(id));
return axios.get(url);
diff --git a/app/assets/javascripts/helpers/monitor_helper.js b/app/assets/javascripts/helpers/monitor_helper.js
index 5e345321013..5f85ee58779 100644
--- a/app/assets/javascripts/helpers/monitor_helper.js
+++ b/app/assets/javascripts/helpers/monitor_helper.js
@@ -49,7 +49,7 @@ const multiMetricLabel = metricAttributes => {
* @param {Object} metricAttributes - Default metric attribute values (e.g. method, instance)
* @returns {String} The formatted query label
*/
-export const getSeriesLabel = (queryLabel, metricAttributes) => {
+const getSeriesLabel = (queryLabel, metricAttributes) => {
return (
singleAttributeLabel(queryLabel, metricAttributes) ||
templatedLabel(queryLabel, metricAttributes) ||
@@ -63,6 +63,7 @@ export const getSeriesLabel = (queryLabel, metricAttributes) => {
* @param {Object} defaultConfig - Default chart config values (e.g. lineStyle, name)
* @returns {Array} The formatted values
*/
+// eslint-disable-next-line import/prefer-default-export
export const makeDataSeries = (queryResults, defaultConfig) =>
queryResults.map(result => {
return {
diff --git a/app/assets/javascripts/monitoring/components/dashboard_panel.vue b/app/assets/javascripts/monitoring/components/dashboard_panel.vue
index 610bef37fdb..3e3c8408de3 100644
--- a/app/assets/javascripts/monitoring/components/dashboard_panel.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard_panel.vue
@@ -30,7 +30,6 @@ import MonitorStackedColumnChart from './charts/stacked_column.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event';
import AlertWidget from './alert_widget.vue';
import { timeRangeToUrl, downloadCSVOptions, generateLinkToChartOptions } from '../utils';
-import { graphDataToCsv } from '../csv_export';
const events = {
timeRangeZoom: 'timerangezoom',
@@ -149,10 +148,13 @@ export default {
return null;
},
csvText() {
- if (this.graphData) {
- return graphDataToCsv(this.graphData);
- }
- return null;
+ const chartData = this.graphData?.metrics[0].result[0].values || [];
+ const yLabel = this.graphData.y_label;
+ const header = `timestamp,${yLabel}\r\n`; // eslint-disable-line @gitlab/require-i18n-strings
+ return chartData.reduce((csv, data) => {
+ const row = data.join(',');
+ return `${csv}${row}\r\n`;
+ }, header);
},
downloadCsv() {
const data = new Blob([this.csvText], { type: 'text/plain' });
diff --git a/app/assets/javascripts/monitoring/csv_export.js b/app/assets/javascripts/monitoring/csv_export.js
deleted file mode 100644
index 734e8dc07a7..00000000000
--- a/app/assets/javascripts/monitoring/csv_export.js
+++ /dev/null
@@ -1,147 +0,0 @@
-import { getSeriesLabel } from '~/helpers/monitor_helper';
-
-/**
- * Returns a label for a header of the csv.
- *
- * Includes double quotes ("") in case the header includes commas or other separator.
- *
- * @param {String} axisLabel
- * @param {String} metricLabel
- * @param {Object} metricAttributes
- */
-const csvHeader = (axisLabel, metricLabel, metricAttributes = {}) =>
- `${axisLabel} > ${getSeriesLabel(metricLabel, metricAttributes)}`;
-
-/**
- * Returns an array with the header labels given a list of metrics
- *
- * ```
- * metrics = [
- * {
- * label: "..." // user-defined label
- * result: [
- * {
- * metric: { ... } // metricAttributes
- * },
- * ...
- * ]
- * },
- * ...
- * ]
- * ```
- *
- * When metrics have a `label` or `metricAttributes`, they are
- * used to generate the column name.
- *
- * @param {String} axisLabel - Main label
- * @param {Array} metrics - Metrics with results
- */
-const csvMetricHeaders = (axisLabel, metrics) =>
- metrics.flatMap(({ label, result }) =>
- // The `metric` in a `result` is a map of `metricAttributes`
- // contains key-values to identify the series, rename it
- // here for clarity.
- result.map(({ metric: metricAttributes }) => {
- return csvHeader(axisLabel, label, metricAttributes);
- }),
- );
-
-/**
- * Returns a (flat) array with all the values arrays in each
- * metric and series
- *
- * ```
- * metrics = [
- * {
- * result: [
- * {
- * values: [ ... ] // `values`
- * },
- * ...
- * ]
- * },
- * ...
- * ]
- * ```
- *
- * @param {Array} metrics - Metrics with results
- */
-const csvMetricValues = metrics =>
- metrics.flatMap(({ result }) => result.map(res => res.values || []));
-
-/**
- * Returns headers and rows for csv, sorted by their timestamp.
- *
- * {
- * headers: ["timestamp", "<col_1_name>", "col_2_name"],
- * rows: [
- * [ <timestamp>, <col_1_value>, <col_2_value> ],
- * [ <timestamp>, <col_1_value>, <col_2_value> ]
- * ...
- * ]
- * }
- *
- * @param {Array} metricHeaders
- * @param {Array} metricValues
- */
-const csvData = (metricHeaders, metricValues) => {
- const rowsByTimestamp = {};
-
- metricValues.forEach((values, colIndex) => {
- values.forEach(([timestamp, value]) => {
- if (!rowsByTimestamp[timestamp]) {
- rowsByTimestamp[timestamp] = [];
- }
- // `value` should be in the right column
- rowsByTimestamp[timestamp][colIndex] = value;
- });
- });
-
- const rows = Object.keys(rowsByTimestamp)
- .sort()
- .map(timestamp => {
- // force each row to have the same number of entries
- rowsByTimestamp[timestamp].length = metricHeaders.length;
- // add timestamp as the first entry
- return [timestamp, ...rowsByTimestamp[timestamp]];
- });
-
- // Escape double quotes and enclose headers:
- // "If double-quotes are used to enclose fields, then a double-quote
- // appearing inside a field must be escaped by preceding it with
- // another double quote."
- // https://tools.ietf.org/html/rfc4180#page-2
- const headers = metricHeaders.map(header => `"${header.replace(/"/g, '""')}"`);
-
- return {
- headers: ['timestamp', ...headers],
- rows,
- };
-};
-
-/**
- * Returns dashboard panel's data in a string in CSV format
- *
- * @param {Object} graphData - Panel contents
- * @returns {String}
- */
-// eslint-disable-next-line import/prefer-default-export
-export const graphDataToCsv = graphData => {
- const delimiter = ',';
- const br = '\r\n';
- const { metrics = [], y_label: axisLabel } = graphData;
-
- const metricsWithResults = metrics.filter(metric => metric.result);
- const metricHeaders = csvMetricHeaders(axisLabel, metricsWithResults);
- const metricValues = csvMetricValues(metricsWithResults);
- const { headers, rows } = csvData(metricHeaders, metricValues);
-
- if (rows.length === 0) {
- return '';
- }
-
- const headerLine = headers.join(delimiter) + br;
- const lines = rows.map(row => row.join(delimiter));
-
- return headerLine + lines.join(br) + br;
-};
diff --git a/app/assets/javascripts/pages/projects/pipelines/new/index.js b/app/assets/javascripts/pages/projects/pipelines/new/index.js
index b0b077a5e4c..d5563143f0c 100644
--- a/app/assets/javascripts/pages/projects/pipelines/new/index.js
+++ b/app/assets/javascripts/pages/projects/pipelines/new/index.js
@@ -1,12 +1,19 @@
import $ from 'jquery';
import NewBranchForm from '~/new_branch_form';
import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list';
+import initNewPipeline from '~/pipeline_new/index';
document.addEventListener('DOMContentLoaded', () => {
- new NewBranchForm($('.js-new-pipeline-form')); // eslint-disable-line no-new
+ const el = document.getElementById('js-new-pipeline');
- setupNativeFormVariableList({
- container: $('.js-ci-variable-list-section'),
- formField: 'variables_attributes',
- });
+ if (el) {
+ initNewPipeline();
+ } else {
+ new NewBranchForm($('.js-new-pipeline-form')); // eslint-disable-line no-new
+
+ setupNativeFormVariableList({
+ container: $('.js-ci-variable-list-section'),
+ formField: 'variables_attributes',
+ });
+ }
});
diff --git a/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue b/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
new file mode 100644
index 00000000000..c2c5e58eedd
--- /dev/null
+++ b/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
@@ -0,0 +1,247 @@
+<script>
+import Vue from 'vue';
+import { s__, __ } from '~/locale';
+import Api from '~/api';
+import { redirectTo } from '~/lib/utils/url_utility';
+import { VARIABLE_TYPE, FILE_TYPE } from '../constants';
+import { uniqueId } from 'lodash';
+import {
+ GlAlert,
+ GlButton,
+ GlForm,
+ GlFormGroup,
+ GlFormInput,
+ GlFormSelect,
+ GlLink,
+ GlNewDropdown,
+ GlNewDropdownItem,
+ GlSearchBoxByType,
+ GlSprintf,
+} from '@gitlab/ui';
+
+export default {
+ typeOptions: [
+ { value: VARIABLE_TYPE, text: __('Variable') },
+ { value: FILE_TYPE, text: __('File') },
+ ],
+ 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.',
+ ),
+ formElementClasses: 'gl-mr-3 gl-mb-3 table-section section-15',
+ errorTitle: __('The form contains the following error:'),
+ components: {
+ GlAlert,
+ GlButton,
+ GlForm,
+ GlFormGroup,
+ GlFormInput,
+ GlFormSelect,
+ GlLink,
+ GlNewDropdown,
+ GlNewDropdownItem,
+ GlSearchBoxByType,
+ GlSprintf,
+ },
+ props: {
+ pipelinesPath: {
+ type: String,
+ required: true,
+ },
+ projectId: {
+ type: String,
+ required: true,
+ },
+ refs: {
+ type: Array,
+ 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: () => ({}),
+ },
+ },
+ data() {
+ return {
+ searchTerm: '',
+ refValue: this.refParam,
+ variables: {},
+ error: false,
+ };
+ },
+ computed: {
+ filteredRefs() {
+ const lowerCasedSearchTerm = this.searchTerm.toLowerCase();
+ return this.refs.filter(ref => ref.toLowerCase().includes(lowerCasedSearchTerm));
+ },
+ variablesLength() {
+ return Object.keys(this.variables).length;
+ },
+ },
+ created() {
+ if (this.variableParams) {
+ this.setVariableParams(VARIABLE_TYPE, this.variableParams);
+ }
+
+ if (this.fileParams) {
+ this.setVariableParams(FILE_TYPE, this.fileParams);
+ }
+
+ this.addEmptyVariable();
+ },
+ methods: {
+ addEmptyVariable() {
+ this.variables[uniqueId('var')] = {
+ variable_type: VARIABLE_TYPE,
+ key: '',
+ value: '',
+ };
+ },
+ setVariableParams(type, paramsObj) {
+ Object.entries(paramsObj).forEach(([key, value]) => {
+ this.variables[uniqueId('var')] = {
+ key,
+ value,
+ variable_type: type,
+ };
+ });
+ },
+ setRefSelected(ref) {
+ this.refValue = ref;
+ },
+ isSelected(ref) {
+ return ref === this.refValue;
+ },
+ insertNewVariable() {
+ Vue.set(this.variables, uniqueId('var'), {
+ variable_type: VARIABLE_TYPE,
+ key: '',
+ value: '',
+ });
+ },
+ removeVariable(key) {
+ Vue.delete(this.variables, key);
+ },
+
+ canRemove(index) {
+ return index < this.variablesLength - 1;
+ },
+ createPipeline() {
+ const filteredVariables = Object.values(this.variables).filter(
+ ({ key, value }) => key !== '' && value !== '',
+ );
+
+ return Api.createPipeline(this.projectId, {
+ ref: this.refValue,
+ variables: filteredVariables,
+ })
+ .then(({ data }) => redirectTo(data.web_url))
+ .catch(err => {
+ this.error = err.response.data.message.base;
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-form @submit.prevent="createPipeline">
+ <gl-alert
+ v-if="error"
+ :title="$options.errorTitle"
+ :dismissible="false"
+ variant="danger"
+ class="gl-mb-4"
+ >{{ error }}</gl-alert
+ >
+ <gl-form-group :label="s__('Pipeline|Run for')">
+ <gl-new-dropdown :text="refValue" block>
+ <gl-search-box-by-type
+ v-model.trim="searchTerm"
+ :placeholder="__('Search branches and tags')"
+ class="gl-p-2"
+ />
+ <gl-new-dropdown-item
+ v-for="(ref, index) in filteredRefs"
+ :key="index"
+ class="gl-font-monospace"
+ is-check-item
+ :is-checked="isSelected(ref)"
+ @click="setRefSelected(ref)"
+ >
+ {{ ref }}
+ </gl-new-dropdown-item>
+ </gl-new-dropdown>
+
+ <template #description>
+ <div>
+ {{ s__('Pipeline|Existing branch name or tag') }}
+ </div></template
+ >
+ </gl-form-group>
+
+ <gl-form-group :label="s__('Pipeline|Variables')">
+ <div
+ v-for="(value, key, index) in variables"
+ :key="key"
+ class="gl-display-flex gl-align-items-center gl-mb-4 gl-pb-2 gl-border-b-solid gl-border-gray-200 gl-border-b-1 gl-flex-direction-column gl-md-flex-direction-row"
+ data-testid="ci-variable-row"
+ >
+ <gl-form-select
+ v-model="variables[key].variable_type"
+ :class="$options.formElementClasses"
+ :options="$options.typeOptions"
+ />
+ <gl-form-input
+ v-model="variables[key].key"
+ :placeholder="s__('CiVariables|Input variable key')"
+ :class="$options.formElementClasses"
+ data-testid="pipeline-form-ci-variable-key"
+ @change.once="insertNewVariable()"
+ />
+ <gl-form-input
+ v-model="variables[key].value"
+ :placeholder="s__('CiVariables|Input variable value')"
+ class="gl-mr-5 gl-mb-3 table-section section-15"
+ />
+ <gl-button
+ v-if="canRemove(index)"
+ icon="issue-close"
+ class="gl-mb-3"
+ data-testid="remove-ci-variable-row"
+ @click="removeVariable(key)"
+ />
+ </div>
+
+ <template #description
+ ><gl-sprintf :message="$options.variablesDescription">
+ <template #link="{ content }">
+ <gl-link :href="settingsLink">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf></template
+ >
+ </gl-form-group>
+ <div
+ class="gl-border-t-solid gl-border-gray-100 gl-border-t-1 gl-p-5 gl-bg-gray-10 gl-display-flex gl-justify-content-space-between"
+ >
+ <gl-button type="submit" category="primary" variant="success">{{
+ s__('Pipeline|Run Pipeline')
+ }}</gl-button>
+ <gl-button :href="pipelinesPath">{{ __('Cancel') }}</gl-button>
+ </div>
+ </gl-form>
+</template>
diff --git a/app/assets/javascripts/pipeline_new/constants.js b/app/assets/javascripts/pipeline_new/constants.js
new file mode 100644
index 00000000000..b4ab1143f60
--- /dev/null
+++ b/app/assets/javascripts/pipeline_new/constants.js
@@ -0,0 +1,2 @@
+export const VARIABLE_TYPE = 'env_var';
+export const FILE_TYPE = 'file';
diff --git a/app/assets/javascripts/pipeline_new/index.js b/app/assets/javascripts/pipeline_new/index.js
new file mode 100644
index 00000000000..1c4812c2e0e
--- /dev/null
+++ b/app/assets/javascripts/pipeline_new/index.js
@@ -0,0 +1,36 @@
+import Vue from 'vue';
+import PipelineNewForm from './components/pipeline_new_form.vue';
+
+export default () => {
+ const el = document.getElementById('js-new-pipeline');
+ const {
+ projectId,
+ pipelinesPath,
+ refParam,
+ varParam,
+ fileParam,
+ refNames,
+ settingsLink,
+ } = el?.dataset;
+
+ const variableParams = JSON.parse(varParam);
+ const fileParams = JSON.parse(fileParam);
+ const refs = JSON.parse(refNames);
+
+ return new Vue({
+ el,
+ render(createElement) {
+ return createElement(PipelineNewForm, {
+ props: {
+ projectId,
+ pipelinesPath,
+ refParam,
+ variableParams,
+ fileParams,
+ refs,
+ settingsLink,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/snippets/components/edit.vue b/app/assets/javascripts/snippets/components/edit.vue
index 69593dc77f8..71ba4e0c183 100644
--- a/app/assets/javascripts/snippets/components/edit.vue
+++ b/app/assets/javascripts/snippets/components/edit.vue
@@ -116,7 +116,7 @@ export default {
onBeforeUnload(e = {}) {
const returnValue = __('Are you sure you want to lose unsaved changes?');
- if (!this.allBlobChangesRegistered) return undefined;
+ if (!this.allBlobChangesRegistered || this.isUpdating) return undefined;
Object.assign(e, { returnValue });
return returnValue;
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index 049f5e71849..dfa4730d4fa 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -1,6 +1,6 @@
<script>
import $ from 'jquery';
-import { GlPopover, GlDeprecatedButton, GlTooltipDirective } from '@gitlab/ui';
+import { GlPopover, GlButton, GlTooltipDirective } from '@gitlab/ui';
import ToolbarButton from './toolbar_button.vue';
import Icon from '../icon.vue';
@@ -9,7 +9,7 @@ export default {
ToolbarButton,
Icon,
GlPopover,
- GlDeprecatedButton,
+ GlButton,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -141,9 +141,14 @@ export default {
)
}}
</p>
- <gl-deprecated-button variant="primary" size="sm" @click="handleSuggestDismissed">
+ <gl-button
+ variant="info"
+ category="primary"
+ size="sm"
+ @click="handleSuggestDismissed"
+ >
{{ __('Got it') }}
- </gl-deprecated-button>
+ </gl-button>
</gl-popover>
</template>
<toolbar-button tag="`" tag-block="```" :button-title="__('Insert code')" icon="code" />
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js
index 21ded83a771..89a0df395d3 100644
--- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js
@@ -4,6 +4,7 @@ import { defaults, repeat } from 'lodash';
const DEFAULTS = {
subListIndentSpaces: 4,
unorderedListBulletChar: '-',
+ incrementListMarker: false,
strong: '*',
emphasis: '_',
};
@@ -15,12 +16,16 @@ const countIndentSpaces = text => {
};
const buildHTMLToMarkdownRender = (baseRenderer, formattingPreferences = {}) => {
- const { subListIndentSpaces, unorderedListBulletChar, strong, emphasis } = defaults(
- formattingPreferences,
- DEFAULTS,
- );
+ const {
+ subListIndentSpaces,
+ unorderedListBulletChar,
+ incrementListMarker,
+ strong,
+ emphasis,
+ } = defaults(formattingPreferences, DEFAULTS);
const sublistNode = 'LI OL, LI UL';
const unorderedListItemNode = 'UL LI';
+ const orderedListItemNode = 'OL LI';
const emphasisNode = 'EM, I';
const strongNode = 'STRONG, B';
@@ -61,6 +66,11 @@ const buildHTMLToMarkdownRender = (baseRenderer, formattingPreferences = {}) =>
return baseResult.replace(/^(\s*)([*|-])/, `$1${unorderedListBulletChar}`);
},
+ [orderedListItemNode](node, subContent) {
+ const baseResult = baseRenderer.convert(node, subContent);
+
+ return incrementListMarker ? baseResult : baseResult.replace(/^(\s*)\d\./, '$11.');
+ },
[emphasisNode](node, subContent) {
const result = baseRenderer.convert(node, subContent);
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index d8e11ddd423..fde2a7e5d92 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -17,6 +17,7 @@ class Projects::PipelinesController < Projects::ApplicationController
push_frontend_feature_flag(:filter_pipelines_search, project, default_enabled: true)
push_frontend_feature_flag(:dag_pipeline_tab, project, default_enabled: true)
push_frontend_feature_flag(:pipelines_security_report_summary, project)
+ push_frontend_feature_flag(:new_pipeline_form, default_enabled: true)
end
before_action :ensure_pipeline, only: [:show]
diff --git a/app/models/releases/link.rb b/app/models/releases/link.rb
index dc7e78a85a9..e1dc3b904b9 100644
--- a/app/models/releases/link.rb
+++ b/app/models/releases/link.rb
@@ -6,7 +6,7 @@ module Releases
belongs_to :release
- FILEPATH_REGEX = /\A\/([\-\.\w]+\/?)*[\da-zA-Z]+\z/.freeze
+ FILEPATH_REGEX = %r{\A/(?:[\-\.\w]+/?)*[\da-zA-Z]+\z}.freeze
validates :url, presence: true, addressable_url: { schemes: %w(http https ftp) }, uniqueness: { scope: :release }
validates :name, presence: true, uniqueness: { scope: :release }
diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml
index a3e46a0939c..11fdbd31382 100644
--- a/app/views/projects/pipelines/new.html.haml
+++ b/app/views/projects/pipelines/new.html.haml
@@ -6,37 +6,41 @@
= s_('Pipeline|Run Pipeline')
%hr
-= form_for @pipeline, as: :pipeline, url: project_pipelines_path(@project), html: { id: "new-pipeline-form", class: "js-new-pipeline-form js-requires-input" } do |f|
- = form_errors(@pipeline)
- .form-group.row
- .col-sm-12
- = f.label :ref, s_('Pipeline|Run for'), class: 'col-form-label'
- = hidden_field_tag 'pipeline[ref]', params[:ref] || @project.default_branch
- = dropdown_tag(params[:ref] || @project.default_branch,
- options: { toggle_class: 'js-branch-select wide monospace',
- filter: true, dropdown_class: "dropdown-menu-selectable git-revision-dropdown", placeholder: s_("Pipeline|Search branches"),
- data: { selected: params[:ref] || @project.default_branch, field_name: 'pipeline[ref]' } })
- .form-text.text-muted
- = s_("Pipeline|Existing branch name or tag")
+- if Feature.enabled?(:new_pipeline_form, default_enabled: true)
+ #js-new-pipeline{ data: { project_id: @project.id, pipelines_path: project_pipelines_path(@project), ref_param: params[:ref] || @project.default_branch, var_param: params[:var].to_json, file_param: params[:file_var].to_json, ref_names: @project.repository.ref_names.to_json.html_safe, settings_link: project_settings_ci_cd_path(@project) } }
- .col-sm-12.prepend-top-10.js-ci-variable-list-section
- %label
- = s_('Pipeline|Variables')
- %ul.ci-variable-list
- - if params[:var]
- - params[:var].each do |variable|
- = render 'ci/variables/url_query_variable_row', form_field: 'pipeline', variable: variable
- - if params[:file_var]
- - params[:file_var].each do |variable|
- - variable.push("file")
- = render 'ci/variables/url_query_variable_row', form_field: 'pipeline', variable: variable
- = render 'ci/variables/variable_row', form_field: 'pipeline', only_key_value: true
- .form-text.text-muted
- = (s_("Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default.") % {settings_link: settings_link}).html_safe
+- else
+ = form_for @pipeline, as: :pipeline, url: project_pipelines_path(@project), html: { id: "new-pipeline-form", class: "js-new-pipeline-form js-requires-input" } do |f|
+ = form_errors(@pipeline)
+ .form-group.row
+ .col-sm-12
+ = f.label :ref, s_('Pipeline|Run for'), class: 'col-form-label'
+ = hidden_field_tag 'pipeline[ref]', params[:ref] || @project.default_branch
+ = dropdown_tag(params[:ref] || @project.default_branch,
+ options: { toggle_class: 'js-branch-select wide monospace',
+ filter: true, dropdown_class: "dropdown-menu-selectable git-revision-dropdown", placeholder: s_("Pipeline|Search branches"),
+ data: { selected: params[:ref] || @project.default_branch, field_name: 'pipeline[ref]' } })
+ .form-text.text-muted
+ = s_("Pipeline|Existing branch name or tag")
- .form-actions
- = f.submit s_('Pipeline|Run Pipeline'), class: 'btn btn-success js-variables-save-button', tabindex: 3
- = link_to _('Cancel'), project_pipelines_path(@project), class: 'btn btn-default float-right'
+ .col-sm-12.prepend-top-10.js-ci-variable-list-section
+ %label
+ = s_('Pipeline|Variables')
+ %ul.ci-variable-list
+ - if params[:var]
+ - params[:var].each do |variable|
+ = render 'ci/variables/url_query_variable_row', form_field: 'pipeline', variable: variable
+ - if params[:file_var]
+ - params[:file_var].each do |variable|
+ - variable.push("file")
+ = render 'ci/variables/url_query_variable_row', form_field: 'pipeline', variable: variable
+ = render 'ci/variables/variable_row', form_field: 'pipeline', only_key_value: true
+ .form-text.text-muted
+ = (s_("Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default.") % {settings_link: settings_link}).html_safe
--# haml-lint:disable InlineJavaScript
-%script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe
+ .form-actions
+ = f.submit s_('Pipeline|Run Pipeline'), class: 'btn btn-success js-variables-save-button', tabindex: 3
+ = link_to _('Cancel'), project_pipelines_path(@project), class: 'btn btn-default float-right'
+
+ -# haml-lint:disable InlineJavaScript
+ %script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe
diff --git a/changelogs/unreleased/214627-fix-incorrect-csv-export.yml b/changelogs/unreleased/214627-fix-incorrect-csv-export.yml
deleted file mode 100644
index e69a392f4ac..00000000000
--- a/changelogs/unreleased/214627-fix-incorrect-csv-export.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix CSV downloads for multiple series in the same chart
-merge_request: 36556
-author:
-type: fixed
diff --git a/changelogs/unreleased/227598-make-ordered-list-configurable.yml b/changelogs/unreleased/227598-make-ordered-list-configurable.yml
new file mode 100644
index 00000000000..3c9f3ed1eec
--- /dev/null
+++ b/changelogs/unreleased/227598-make-ordered-list-configurable.yml
@@ -0,0 +1,5 @@
+---
+title: When generating markdown for ordered lists, the list marker should not increment
+merge_request: 36851
+author:
+type: changed
diff --git a/doc/user/incident_management/img/pagerduty_incidents_integration_13_2.png b/doc/user/incident_management/img/pagerduty_incidents_integration_13_3.png
index 0991e963e02..0991e963e02 100644
--- a/doc/user/incident_management/img/pagerduty_incidents_integration_13_2.png
+++ b/doc/user/incident_management/img/pagerduty_incidents_integration_13_3.png
Binary files differ
diff --git a/doc/user/incident_management/index.md b/doc/user/incident_management/index.md
index a8714660afd..6d72dbaa90f 100644
--- a/doc/user/incident_management/index.md
+++ b/doc/user/incident_management/index.md
@@ -51,7 +51,7 @@ To send separate email notifications to users with
## Configure PagerDuty integration
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/119018) in GitLab 13.2.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/119018) in GitLab 13.3.
You can set up a webhook with PagerDuty to automatically create a GitLab issue
for each PagerDuty incident. This configuration requires you to make changes
@@ -61,7 +61,7 @@ in both PagerDuty and GitLab:
1. Navigate to **{settings}** **Settings > Operations > Incidents** and expand **Incidents**.
1. Select the **PagerDuty integration** tab:
- ![PagerDuty incidents integration](img/pagerduty_incidents_integration_13_2.png)
+ ![PagerDuty incidents integration](img/pagerduty_incidents_integration_13_3.png)
1. Activate the integration, and save the changes in GitLab.
1. Copy the value of **Webhook URL** for use in a later step.
diff --git a/doc/user/project/integrations/bamboo.md b/doc/user/project/integrations/bamboo.md
index 7b21c590c8a..9cade323ed2 100644
--- a/doc/user/project/integrations/bamboo.md
+++ b/doc/user/project/integrations/bamboo.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Atlassian Bamboo CI Service
GitLab provides integration with Atlassian Bamboo for continuous integration.
diff --git a/doc/user/project/integrations/bugzilla.md b/doc/user/project/integrations/bugzilla.md
index 6d44c56743e..2ed14a4c69c 100644
--- a/doc/user/project/integrations/bugzilla.md
+++ b/doc/user/project/integrations/bugzilla.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Bugzilla Service
Navigate to the [Integrations page](overview.md#accessing-integrations),
diff --git a/doc/user/project/integrations/custom_issue_tracker.md b/doc/user/project/integrations/custom_issue_tracker.md
index 7d15ae82b6f..5ec044731d1 100644
--- a/doc/user/project/integrations/custom_issue_tracker.md
+++ b/doc/user/project/integrations/custom_issue_tracker.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Custom Issue Tracker service
To enable the Custom Issue Tracker integration in a project:
diff --git a/doc/user/project/integrations/discord_notifications.md b/doc/user/project/integrations/discord_notifications.md
index aa45cc38cb5..f261362eeae 100644
--- a/doc/user/project/integrations/discord_notifications.md
+++ b/doc/user/project/integrations/discord_notifications.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Discord Notifications service
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/22684) in GitLab 11.6.
diff --git a/doc/user/project/integrations/emails_on_push.md b/doc/user/project/integrations/emails_on_push.md
index b0838690d3b..d8b864e0396 100644
--- a/doc/user/project/integrations/emails_on_push.md
+++ b/doc/user/project/integrations/emails_on_push.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Enabling emails on push
By enabling this service, you will receive email notifications for every change
diff --git a/doc/user/project/integrations/github.md b/doc/user/project/integrations/github.md
index 416996fb629..a0594b4537e 100644
--- a/doc/user/project/integrations/github.md
+++ b/doc/user/project/integrations/github.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# GitHub project integration **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3836) in GitLab Premium 10.6.
diff --git a/doc/user/project/integrations/gitlab_slack_application.md b/doc/user/project/integrations/gitlab_slack_application.md
index 7a827364d41..ef4cf372f56 100644
--- a/doc/user/project/integrations/gitlab_slack_application.md
+++ b/doc/user/project/integrations/gitlab_slack_application.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# GitLab Slack application **(FREE ONLY)**
> - Introduced in GitLab 9.4.
diff --git a/doc/user/project/integrations/hangouts_chat.md b/doc/user/project/integrations/hangouts_chat.md
index f65b31150a9..54f9bd8d622 100644
--- a/doc/user/project/integrations/hangouts_chat.md
+++ b/doc/user/project/integrations/hangouts_chat.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Hangouts Chat service
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/43756) in GitLab 11.2.
diff --git a/doc/user/project/integrations/hipchat.md b/doc/user/project/integrations/hipchat.md
index 2ed7f13db9b..718f00273bd 100644
--- a/doc/user/project/integrations/hipchat.md
+++ b/doc/user/project/integrations/hipchat.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Atlassian HipChat
GitLab provides a way to send HipChat notifications upon a number of events,
diff --git a/doc/user/project/integrations/index.md b/doc/user/project/integrations/index.md
index 75565dd2750..0a1db5da61d 100644
--- a/doc/user/project/integrations/index.md
+++ b/doc/user/project/integrations/index.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Project integrations
You can find the available integrations under your project's
diff --git a/doc/user/project/integrations/irker.md b/doc/user/project/integrations/irker.md
index 2d807d4302b..f2e769dcfc0 100644
--- a/doc/user/project/integrations/irker.md
+++ b/doc/user/project/integrations/irker.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Irker IRC Gateway
GitLab provides a way to push update messages to an Irker server. When
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index 541c65041ad..3f02f85a8bd 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# GitLab Jira integration
GitLab Issues are a powerful tool for discussing ideas and planning and tracking work.
diff --git a/doc/user/project/integrations/jira_cloud_configuration.md b/doc/user/project/integrations/jira_cloud_configuration.md
index c7157b6bd0e..14999734c00 100644
--- a/doc/user/project/integrations/jira_cloud_configuration.md
+++ b/doc/user/project/integrations/jira_cloud_configuration.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Creating an API token in Jira Cloud
An API token is needed when integrating with Jira Cloud, follow the steps
diff --git a/doc/user/project/integrations/jira_server_configuration.md b/doc/user/project/integrations/jira_server_configuration.md
index c8278a0f083..38098d7d15b 100644
--- a/doc/user/project/integrations/jira_server_configuration.md
+++ b/doc/user/project/integrations/jira_server_configuration.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Creating a username and password for Jira Server
We need to create a user in Jira which will have access to all projects that
diff --git a/doc/user/project/integrations/mattermost.md b/doc/user/project/integrations/mattermost.md
index 67d60984c22..c12a969ca3c 100644
--- a/doc/user/project/integrations/mattermost.md
+++ b/doc/user/project/integrations/mattermost.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Mattermost Notifications Service
The Mattermost Notifications Service allows your GitLab project to send events (e.g., `issue created`) to your existing Mattermost team as notifications. This requires configurations in both Mattermost and GitLab.
diff --git a/doc/user/project/integrations/mattermost_slash_commands.md b/doc/user/project/integrations/mattermost_slash_commands.md
index 6a202c9a130..a392b20ee41 100644
--- a/doc/user/project/integrations/mattermost_slash_commands.md
+++ b/doc/user/project/integrations/mattermost_slash_commands.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Mattermost slash commands
> Introduced in GitLab 8.14
diff --git a/doc/user/project/integrations/microsoft_teams.md b/doc/user/project/integrations/microsoft_teams.md
index 611ae1a01af..b2a2f1c3e7b 100644
--- a/doc/user/project/integrations/microsoft_teams.md
+++ b/doc/user/project/integrations/microsoft_teams.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Microsoft Teams service
## On Microsoft Teams
diff --git a/doc/user/project/integrations/mock_ci.md b/doc/user/project/integrations/mock_ci.md
index b06ccda8287..4567d345336 100644
--- a/doc/user/project/integrations/mock_ci.md
+++ b/doc/user/project/integrations/mock_ci.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Mock CI Service
**NB: This service is only listed if you are in a development environment!**
diff --git a/doc/user/project/integrations/overview.md b/doc/user/project/integrations/overview.md
index 79c55e2d140..3b54b3c9696 100644
--- a/doc/user/project/integrations/overview.md
+++ b/doc/user/project/integrations/overview.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Integrations
Integrations allow you to integrate GitLab with other applications. They
diff --git a/doc/user/project/integrations/redmine.md b/doc/user/project/integrations/redmine.md
index c92ddf38ad2..2a85dd9b79b 100644
--- a/doc/user/project/integrations/redmine.md
+++ b/doc/user/project/integrations/redmine.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Redmine Service
1. To enable the Redmine integration in a project, navigate to the
diff --git a/doc/user/project/integrations/services_templates.md b/doc/user/project/integrations/services_templates.md
index bc2bdde2f64..688643a85a7 100644
--- a/doc/user/project/integrations/services_templates.md
+++ b/doc/user/project/integrations/services_templates.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Service templates
Using a service template, GitLab administrators can provide default values for configuring integrations at the project level.
diff --git a/doc/user/project/integrations/slack.md b/doc/user/project/integrations/slack.md
index 6c5dc787c5e..1365f11ebe0 100644
--- a/doc/user/project/integrations/slack.md
+++ b/doc/user/project/integrations/slack.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Slack Notifications Service
The Slack Notifications Service allows your GitLab project to send events
diff --git a/doc/user/project/integrations/slack_slash_commands.md b/doc/user/project/integrations/slack_slash_commands.md
index d25a367bd1f..7c2413fce81 100644
--- a/doc/user/project/integrations/slack_slash_commands.md
+++ b/doc/user/project/integrations/slack_slash_commands.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Slack slash commands **(CORE ONLY)**
> Introduced in GitLab 8.15.
diff --git a/doc/user/project/integrations/unify_circuit.md b/doc/user/project/integrations/unify_circuit.md
index 98dc6f298d5..c4959a8711b 100644
--- a/doc/user/project/integrations/unify_circuit.md
+++ b/doc/user/project/integrations/unify_circuit.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Unify Circuit service
The Unify Circuit service sends notifications from GitLab to the conversation for which the webhook was created.
diff --git a/doc/user/project/integrations/webex_teams.md b/doc/user/project/integrations/webex_teams.md
index 10735e33746..39daa14407f 100644
--- a/doc/user/project/integrations/webex_teams.md
+++ b/doc/user/project/integrations/webex_teams.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Webex Teams service
You can configure GitLab to send notifications to a Webex Teams space.
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 5a0ca03a646..d84ae9ac910 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Webhooks
> **Note:**
diff --git a/doc/user/project/integrations/youtrack.md b/doc/user/project/integrations/youtrack.md
index e067ab6071e..d243ffc7a37 100644
--- a/doc/user/project/integrations/youtrack.md
+++ b/doc/user/project/integrations/youtrack.md
@@ -1,3 +1,9 @@
+---
+stage: Create
+group: Ecosystem
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# YouTrack Service
JetBrains [YouTrack](https://www.jetbrains.com/help/youtrack/standalone/YouTrack-Documentation.html) is a web-based issue tracking and project management platform.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index bac41af8a66..d556e96db61 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -17281,6 +17281,9 @@ msgstr ""
msgid "Pipeline|Skipped"
msgstr ""
+msgid "Pipeline|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used by default."
+msgstr ""
+
msgid "Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default."
msgstr ""
@@ -23227,6 +23230,15 @@ msgstr ""
msgid "Templates"
msgstr ""
+msgid "TemporaryStorage|GitLab allows you a %{strongStart}free, one-time storage increase%{strongEnd}. For 30 days your storage will be unlimited. This gives you time to reduce your storage usage. After 30 days, your original storage limit of %{limit} applies. If you are at maximum storage capacity, your account will be read-only. To continue using GitLab you'll have to purchase additional storage or decrease storage usage."
+msgstr ""
+
+msgid "TemporaryStorage|Increase storage temporarily"
+msgstr ""
+
+msgid "TemporaryStorage|Temporarily increase storage now?"
+msgstr ""
+
msgid "Terminal"
msgstr ""
@@ -23510,6 +23522,9 @@ msgstr[1] ""
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The form contains the following error:"
+msgstr ""
+
msgid "The global settings require you to enable Two-Factor Authentication for your account."
msgstr ""
@@ -25689,6 +25704,9 @@ msgstr ""
msgid "UsageQuota|Current period usage"
msgstr ""
+msgid "UsageQuota|Increase storage temporarily"
+msgstr ""
+
msgid "UsageQuota|LFS Objects"
msgstr ""
diff --git a/spec/features/populate_new_pipeline_vars_with_params_spec.rb b/spec/features/populate_new_pipeline_vars_with_params_spec.rb
index f931e8497fc..37fea5331a3 100644
--- a/spec/features/populate_new_pipeline_vars_with_params_spec.rb
+++ b/spec/features/populate_new_pipeline_vars_with_params_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe "Populate new pipeline CI variables with url params", :js do
let(:page_path) { new_project_pipeline_path(project) }
before do
+ stub_feature_flags(new_pipeline_form: false)
sign_in(user)
project.add_maintainer(user)
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 0eb92f3e679..8747b3ab54c 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -652,6 +652,7 @@ RSpec.describe 'Pipelines', :js do
let(:project) { create(:project, :repository) }
before do
+ stub_feature_flags(new_pipeline_form: false)
visit new_project_pipeline_path(project)
end
@@ -718,6 +719,7 @@ RSpec.describe 'Pipelines', :js do
let(:project) { create(:project, :repository) }
before do
+ stub_feature_flags(new_pipeline_form: false)
visit new_project_pipeline_path(project)
end
diff --git a/spec/fixtures/api/schemas/release/link.json b/spec/fixtures/api/schemas/release/link.json
index b3aebfa131e..669f0a39343 100644
--- a/spec/fixtures/api/schemas/release/link.json
+++ b/spec/fixtures/api/schemas/release/link.json
@@ -4,7 +4,6 @@
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
- "filepath": { "type": "string" },
"url": { "type": "string" },
"direct_asset_url": { "type": "string" },
"external": { "type": "boolean" },
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index b4e25867fad..b76cfe6204d 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -891,4 +891,34 @@ describe('Api', () => {
});
});
});
+
+ describe('createPipeline', () => {
+ it('creates new pipeline', () => {
+ const redirectUrl = 'ci-project/-/pipelines/95';
+ const projectId = 8;
+ const postData = {
+ ref: 'tag-1',
+ variables: [
+ { key: 'test_file', value: 'test_file_val', variable_type: 'file' },
+ { key: 'test_var', value: 'test_var_val', variable_type: 'env_var' },
+ ],
+ };
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/pipeline`;
+
+ jest.spyOn(axios, 'post');
+
+ mock.onPost(expectedUrl).replyOnce(200, {
+ web_url: redirectUrl,
+ });
+
+ return Api.createPipeline(projectId, postData).then(({ data }) => {
+ expect(data.web_url).toBe(redirectUrl);
+ expect(axios.post).toHaveBeenCalledWith(expectedUrl, postData, {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+ });
+ });
+ });
});
diff --git a/spec/frontend/helpers/monitor_helper_spec.js b/spec/frontend/helpers/monitor_helper_spec.js
index 219b05e312b..083b6404125 100644
--- a/spec/frontend/helpers/monitor_helper_spec.js
+++ b/spec/frontend/helpers/monitor_helper_spec.js
@@ -1,38 +1,12 @@
-import { getSeriesLabel, makeDataSeries } from '~/helpers/monitor_helper';
+import * as monitorHelper from '~/helpers/monitor_helper';
describe('monitor helper', () => {
const defaultConfig = { default: true, name: 'default name' };
const name = 'data name';
const series = [[1, 1], [2, 2], [3, 3]];
-
- describe('getSeriesLabel', () => {
- const metricAttributes = { __name__: 'up', app: 'prometheus' };
-
- it('gets a single attribute label', () => {
- expect(getSeriesLabel('app', metricAttributes)).toBe('app: prometheus');
- });
-
- it('gets a templated label', () => {
- expect(getSeriesLabel('{{__name__}}', metricAttributes)).toBe('up');
- expect(getSeriesLabel('{{app}}', metricAttributes)).toBe('prometheus');
- expect(getSeriesLabel('{{missing}}', metricAttributes)).toBe('{{missing}}');
- });
-
- it('gets a multiple label', () => {
- expect(getSeriesLabel(null, metricAttributes)).toBe('__name__: up, app: prometheus');
- expect(getSeriesLabel('', metricAttributes)).toBe('__name__: up, app: prometheus');
- });
-
- it('gets a simple label', () => {
- expect(getSeriesLabel('A label', {})).toBe('A label');
- });
- });
+ const data = ({ metric = { default_name: name }, values = series } = {}) => [{ metric, values }];
describe('makeDataSeries', () => {
- const data = ({ metric = { default_name: name }, values = series } = {}) => [
- { metric, values },
- ];
-
const expectedDataSeries = [
{
...defaultConfig,
@@ -41,17 +15,19 @@ describe('monitor helper', () => {
];
it('converts query results to data series', () => {
- expect(makeDataSeries(data({ metric: {} }), defaultConfig)).toEqual(expectedDataSeries);
+ expect(monitorHelper.makeDataSeries(data({ metric: {} }), defaultConfig)).toEqual(
+ expectedDataSeries,
+ );
});
it('returns an empty array if no query results exist', () => {
- expect(makeDataSeries([], defaultConfig)).toEqual([]);
+ expect(monitorHelper.makeDataSeries([], defaultConfig)).toEqual([]);
});
it('handles multi-series query results', () => {
const expectedData = { ...expectedDataSeries[0], name: 'default name: data name' };
- expect(makeDataSeries([...data(), ...data()], defaultConfig)).toEqual([
+ expect(monitorHelper.makeDataSeries([...data(), ...data()], defaultConfig)).toEqual([
expectedData,
expectedData,
]);
@@ -63,7 +39,10 @@ describe('monitor helper', () => {
name: '{{cmd}}',
};
- const [result] = makeDataSeries([{ metric: { cmd: 'brpop' }, values: series }], config);
+ const [result] = monitorHelper.makeDataSeries(
+ [{ metric: { cmd: 'brpop' }, values: series }],
+ config,
+ );
expect(result.name).toEqual('brpop');
});
@@ -74,7 +53,7 @@ describe('monitor helper', () => {
name: '',
};
- const [result] = makeDataSeries(
+ const [result] = monitorHelper.makeDataSeries(
[
{
metric: {
@@ -100,7 +79,7 @@ describe('monitor helper', () => {
name: 'backend: {{ backend }}',
};
- const [result] = makeDataSeries(
+ const [result] = monitorHelper.makeDataSeries(
[{ metric: { backend: 'HA Server' }, values: series }],
config,
);
@@ -111,7 +90,10 @@ describe('monitor helper', () => {
it('supports repeated template variables', () => {
const config = { ...defaultConfig, name: '{{cmd}}, {{cmd}}' };
- const [result] = makeDataSeries([{ metric: { cmd: 'brpop' }, values: series }], config);
+ const [result] = monitorHelper.makeDataSeries(
+ [{ metric: { cmd: 'brpop' }, values: series }],
+ config,
+ );
expect(result.name).toEqual('brpop, brpop');
});
@@ -119,7 +101,7 @@ describe('monitor helper', () => {
it('supports hyphenated template variables', () => {
const config = { ...defaultConfig, name: 'expired - {{ test-attribute }}' };
- const [result] = makeDataSeries(
+ const [result] = monitorHelper.makeDataSeries(
[{ metric: { 'test-attribute': 'test-attribute-value' }, values: series }],
config,
);
@@ -133,7 +115,7 @@ describe('monitor helper', () => {
name: '{{job}}: {{cmd}}',
};
- const [result] = makeDataSeries(
+ const [result] = monitorHelper.makeDataSeries(
[{ metric: { cmd: 'brpop', job: 'redis' }, values: series }],
config,
);
@@ -147,7 +129,7 @@ describe('monitor helper', () => {
name: '{{cmd}}',
};
- const [firstSeries, secondSeries] = makeDataSeries(
+ const [firstSeries, secondSeries] = monitorHelper.makeDataSeries(
[
{ metric: { cmd: 'brpop' }, values: series },
{ metric: { cmd: 'zrangebyscore' }, values: series },
diff --git a/spec/frontend/monitoring/components/dashboard_panel_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js
index a38af9770cf..507598ad3aa 100644
--- a/spec/frontend/monitoring/components/dashboard_panel_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js
@@ -443,7 +443,7 @@ describe('Dashboard Panel', () => {
describe('csvText', () => {
it('converts metrics data from json to csv', () => {
- const header = `timestamp,"${graphData.y_label} > ${graphData.metrics[0].label}"`;
+ const header = `timestamp,${graphData.y_label}`;
const data = graphData.metrics[0].result[0].values;
const firstRow = `${data[0][0]},${data[0][1]}`;
const secondRow = `${data[1][0]},${data[1][1]}`;
diff --git a/spec/frontend/monitoring/csv_export_spec.js b/spec/frontend/monitoring/csv_export_spec.js
deleted file mode 100644
index 90d6eaa435f..00000000000
--- a/spec/frontend/monitoring/csv_export_spec.js
+++ /dev/null
@@ -1,126 +0,0 @@
-import { timeSeriesGraphData } from './graph_data';
-import { graphDataToCsv } from '~/monitoring/csv_export';
-
-describe('monitoring export_csv', () => {
- describe('graphDataToCsv', () => {
- const expectCsvToMatchLines = (csv, lines) => expect(`${lines.join('\r\n')}\r\n`).toEqual(csv);
-
- it('should return a csv with 0 metrics', () => {
- const data = timeSeriesGraphData({}, { metricCount: 0 });
-
- expect(graphDataToCsv(data)).toEqual('');
- });
-
- it('should return a csv with 1 metric with no data', () => {
- const data = timeSeriesGraphData({}, { metricCount: 1 });
-
- // When state is NO_DATA, result is null
- data.metrics[0].result = null;
-
- expect(graphDataToCsv(data)).toEqual('');
- });
-
- it('should return a csv with multiple metrics and one with no data', () => {
- const data = timeSeriesGraphData({}, { metricCount: 2 });
-
- // When state is NO_DATA, result is null
- data.metrics[0].result = null;
-
- expectCsvToMatchLines(graphDataToCsv(data), [
- `timestamp,"Y Axis > Metric 2"`,
- '2015-07-01T20:10:51.781Z,1',
- '2015-07-01T20:11:06.781Z,2',
- '2015-07-01T20:11:21.781Z,3',
- ]);
- });
-
- it('should return a csv when not all metrics have the same timestamps', () => {
- const data = timeSeriesGraphData({}, { metricCount: 3 });
-
- // Add an "odd" timestamp that is not in the dataset
- Object.assign(data.metrics[2].result[0], {
- value: ['2016-01-01T00:00:00.000Z', 9],
- values: [['2016-01-01T00:00:00.000Z', 9]],
- });
-
- expectCsvToMatchLines(graphDataToCsv(data), [
- `timestamp,"Y Axis > Metric 1","Y Axis > Metric 2","Y Axis > Metric 3"`,
- '2015-07-01T20:10:51.781Z,1,1,',
- '2015-07-01T20:11:06.781Z,2,2,',
- '2015-07-01T20:11:21.781Z,3,3,',
- '2016-01-01T00:00:00.000Z,,,9',
- ]);
- });
-
- it('should return a csv with 1 metric', () => {
- const data = timeSeriesGraphData({}, { metricCount: 1 });
-
- expectCsvToMatchLines(graphDataToCsv(data), [
- `timestamp,"Y Axis > Metric 1"`,
- '2015-07-01T20:10:51.781Z,1',
- '2015-07-01T20:11:06.781Z,2',
- '2015-07-01T20:11:21.781Z,3',
- ]);
- });
-
- it('should escape double quotes in metric labels with two double quotes ("")', () => {
- const data = timeSeriesGraphData({}, { metricCount: 1 });
-
- data.metrics[0].label = 'My "quoted" metric';
-
- expectCsvToMatchLines(graphDataToCsv(data), [
- `timestamp,"Y Axis > My ""quoted"" metric"`,
- '2015-07-01T20:10:51.781Z,1',
- '2015-07-01T20:11:06.781Z,2',
- '2015-07-01T20:11:21.781Z,3',
- ]);
- });
-
- it('should return a csv with multiple metrics', () => {
- const data = timeSeriesGraphData({}, { metricCount: 3 });
-
- expectCsvToMatchLines(graphDataToCsv(data), [
- `timestamp,"Y Axis > Metric 1","Y Axis > Metric 2","Y Axis > Metric 3"`,
- '2015-07-01T20:10:51.781Z,1,1,1',
- '2015-07-01T20:11:06.781Z,2,2,2',
- '2015-07-01T20:11:21.781Z,3,3,3',
- ]);
- });
-
- it('should return a csv with 1 metric and multiple series with labels', () => {
- const data = timeSeriesGraphData({}, { isMultiSeries: true });
-
- expectCsvToMatchLines(graphDataToCsv(data), [
- `timestamp,"Y Axis > Metric 1","Y Axis > Metric 1"`,
- '2015-07-01T20:10:51.781Z,1,4',
- '2015-07-01T20:11:06.781Z,2,5',
- '2015-07-01T20:11:21.781Z,3,6',
- ]);
- });
-
- it('should return a csv with 1 metric and multiple series', () => {
- const data = timeSeriesGraphData({}, { isMultiSeries: true, withLabels: false });
-
- expectCsvToMatchLines(graphDataToCsv(data), [
- `timestamp,"Y Axis > __name__: up, job: prometheus, instance: localhost:9090","Y Axis > __name__: up, job: node, instance: localhost:9091"`,
- '2015-07-01T20:10:51.781Z,1,4',
- '2015-07-01T20:11:06.781Z,2,5',
- '2015-07-01T20:11:21.781Z,3,6',
- ]);
- });
-
- it('should return a csv with multiple metrics and multiple series', () => {
- const data = timeSeriesGraphData(
- {},
- { metricCount: 3, isMultiSeries: true, withLabels: false },
- );
-
- expectCsvToMatchLines(graphDataToCsv(data), [
- `timestamp,"Y Axis > __name__: up, job: prometheus, instance: localhost:9090","Y Axis > __name__: up, job: node, instance: localhost:9091","Y Axis > __name__: up, job: prometheus, instance: localhost:9090","Y Axis > __name__: up, job: node, instance: localhost:9091","Y Axis > __name__: up, job: prometheus, instance: localhost:9090","Y Axis > __name__: up, job: node, instance: localhost:9091"`,
- '2015-07-01T20:10:51.781Z,1,4,1,4,1,4',
- '2015-07-01T20:11:06.781Z,2,5,2,5,2,5',
- '2015-07-01T20:11:21.781Z,3,6,3,6,3,6',
- ]);
- });
- });
-});
diff --git a/spec/frontend/monitoring/graph_data.js b/spec/frontend/monitoring/graph_data.js
index fcdca95ac09..8e81ad7a585 100644
--- a/spec/frontend/monitoring/graph_data.js
+++ b/spec/frontend/monitoring/graph_data.js
@@ -83,7 +83,7 @@ const matrixMultiResult = ({ values1 = ['1', '2', '3'], values2 = ['4', '5', '6'
* @param {Object} dataOptions.isMultiSeries
*/
export const timeSeriesGraphData = (panelOptions = {}, dataOptions = {}) => {
- const { metricCount = 1, isMultiSeries = false, withLabels = true } = dataOptions;
+ const { metricCount = 1, isMultiSeries = false } = dataOptions;
return mapPanelToViewModel({
title: 'Time Series Panel',
@@ -91,7 +91,7 @@ export const timeSeriesGraphData = (panelOptions = {}, dataOptions = {}) => {
x_label: 'X Axis',
y_label: 'Y Axis',
metrics: Array.from(Array(metricCount), (_, i) => ({
- label: withLabels ? `Metric ${i + 1}` : undefined,
+ label: `Metric ${i + 1}`,
state: metricStates.OK,
result: isMultiSeries ? matrixMultiResult() : matrixSingleResult(),
})),
diff --git a/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js b/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
new file mode 100644
index 00000000000..5ad1cdbfa51
--- /dev/null
+++ b/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
@@ -0,0 +1,108 @@
+import Api from '~/api';
+import { mount, shallowMount } from '@vue/test-utils';
+import PipelineNewForm from '~/pipeline_new/components/pipeline_new_form.vue';
+import { GlNewDropdown, GlNewDropdownItem, GlForm } from '@gitlab/ui';
+import { mockRefs, mockParams, mockPostParams, mockProjectId } from '../mock_data';
+
+describe('Pipeline New Form', () => {
+ let wrapper;
+
+ const dummySubmitEvent = {
+ preventDefault() {},
+ };
+
+ const findForm = () => wrapper.find(GlForm);
+ const findDropdown = () => wrapper.find(GlNewDropdown);
+ const findDropdownItems = () => wrapper.findAll(GlNewDropdownItem);
+ const findVariableRows = () => wrapper.findAll('[data-testid="ci-variable-row"]');
+ const findRemoveIcons = () => wrapper.findAll('[data-testid="remove-ci-variable-row"]');
+ const findKeyInputs = () => wrapper.findAll('[data-testid="pipeline-form-ci-variable-key"]');
+
+ const createComponent = (term = '', props = {}, method = shallowMount) => {
+ wrapper = method(PipelineNewForm, {
+ propsData: {
+ projectId: mockProjectId,
+ pipelinesPath: '',
+ refs: mockRefs,
+ defaultBranch: 'master',
+ settingsLink: '',
+ ...props,
+ },
+ data() {
+ return {
+ searchTerm: term,
+ };
+ },
+ });
+ };
+
+ beforeEach(() => {
+ jest.spyOn(Api, 'createPipeline').mockResolvedValue({ data: { web_url: '/' } });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('Dropdown with branches and tags', () => {
+ it('displays dropdown with all branches and tags', () => {
+ createComponent();
+ expect(findDropdownItems().length).toBe(mockRefs.length);
+ });
+
+ it('when user enters search term the list is filtered', () => {
+ createComponent('master');
+
+ expect(findDropdownItems().length).toBe(1);
+ expect(
+ findDropdownItems()
+ .at(0)
+ .text(),
+ ).toBe('master');
+ });
+ });
+
+ describe('Form', () => {
+ beforeEach(() => {
+ createComponent('', mockParams, mount);
+ });
+ it('displays the correct values for the provided query params', () => {
+ expect(findDropdown().props('text')).toBe('tag-1');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findVariableRows().length).toBe(3);
+ });
+ });
+
+ it('does not display remove icon for last row', () => {
+ expect(findRemoveIcons().length).toBe(2);
+ });
+
+ it('removes ci variable row on remove icon button click', () => {
+ findRemoveIcons()
+ .at(1)
+ .trigger('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findVariableRows().length).toBe(2);
+ });
+ });
+
+ it('creates a pipeline on submit', () => {
+ findForm().vm.$emit('submit', dummySubmitEvent);
+
+ expect(Api.createPipeline).toHaveBeenCalledWith(mockProjectId, mockPostParams);
+ });
+
+ it('creates blank variable on input change event', () => {
+ findKeyInputs()
+ .at(2)
+ .trigger('change');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findVariableRows().length).toBe(4);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/pipeline_new/mock_data.js b/spec/frontend/pipeline_new/mock_data.js
new file mode 100644
index 00000000000..55ec1fb5afc
--- /dev/null
+++ b/spec/frontend/pipeline_new/mock_data.js
@@ -0,0 +1,21 @@
+export const mockRefs = ['master', 'branch-1', 'tag-1'];
+
+export const mockParams = {
+ refParam: 'tag-1',
+ variableParams: {
+ test_var: 'test_var_val',
+ },
+ fileParams: {
+ test_file: 'test_file_val',
+ },
+};
+
+export const mockProjectId = '21';
+
+export const mockPostParams = {
+ ref: 'tag-1',
+ variables: [
+ { key: 'test_var', value: 'test_var_val', variable_type: 'env_var' },
+ { key: 'test_file', value: 'test_file_val', variable_type: 'file' },
+ ],
+};
diff --git a/spec/frontend/snippets/components/edit_spec.js b/spec/frontend/snippets/components/edit_spec.js
index 6149ecbf00c..0148439d74c 100644
--- a/spec/frontend/snippets/components/edit_spec.js
+++ b/spec/frontend/snippets/components/edit_spec.js
@@ -388,42 +388,49 @@ describe('Snippet Edit app', () => {
returnValueSetter = jest.spyOn(event, 'returnValue', 'set');
};
- it('does not prevent page navigation if there are no blobs', () => {
- bootstrap();
- window.dispatchEvent(event);
-
- expect(returnValueSetter).not.toHaveBeenCalled();
- });
-
- it('does not prevent page navigation if there are no changes to the blobs content', () => {
- bootstrap({
- blobsActions: {
- foo: {
- ...actionWithContent,
- action: '',
- },
+ const actionsWithoutAction = {
+ blobsActions: {
+ foo: {
+ ...actionWithContent,
+ action: '',
},
- });
- window.dispatchEvent(event);
-
- expect(returnValueSetter).not.toHaveBeenCalled();
- });
-
- it('prevents page navigation if there are some changes in the snippet content', () => {
- bootstrap({
- blobsActions: {
- foo: {
- ...actionWithContent,
- action: 'update',
- },
+ },
+ };
+ const actionsWithUpdate = {
+ blobsActions: {
+ foo: {
+ ...actionWithContent,
+ action: 'update',
},
- });
+ },
+ };
+ const actionsWithUpdateWhileSaving = {
+ blobsActions: {
+ foo: {
+ ...actionWithContent,
+ action: 'update',
+ },
+ },
+ isUpdating: true,
+ };
+ it.each`
+ bool | expectToBePrevented | data | condition
+ ${'does not prevent'} | ${false} | ${undefined} | ${'there are no blobs'}
+ ${'does not prevent'} | ${false} | ${actionsWithoutAction} | ${'there are no changes to the blobs content'}
+ ${'prevents'} | ${true} | ${actionsWithUpdate} | ${'there are changes to the blobs content'}
+ ${'does not prevent'} | ${false} | ${actionsWithUpdateWhileSaving} | ${'the snippet is being saved'}
+ `('$bool page navigation if $condition', ({ expectToBePrevented, data }) => {
+ bootstrap(data);
window.dispatchEvent(event);
- expect(returnValueSetter).toHaveBeenCalledWith(
- 'Are you sure you want to lose unsaved changes?',
- );
+ if (expectToBePrevented) {
+ expect(returnValueSetter).toHaveBeenCalledWith(
+ 'Are you sure you want to lose unsaved changes?',
+ );
+ } else {
+ expect(returnValueSetter).not.toHaveBeenCalled();
+ }
});
});
});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js
index 218ca8f3f5a..2bbd3572d4b 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js
@@ -68,6 +68,28 @@ describe('HTMLToMarkdownRenderer', () => {
);
});
+ describe('OL LI visitor', () => {
+ it.each`
+ listItem | result | incrementListMarker | action
+ ${'2. list item'} | ${'1. list item'} | ${false} | ${'increments'}
+ ${' 3. list item'} | ${' 1. list item'} | ${false} | ${'increments'}
+ ${'3. list item'} | ${'3. list item'} | ${true} | ${'does not increment'}
+ `(
+ '$action a list item counter when incrementListMaker is $incrementListMarker',
+ ({ listItem, result, incrementListMarker }) => {
+ const subContent = null;
+
+ htmlToMarkdownRenderer = buildHTMLToMarkdownRenderer(baseRenderer, {
+ incrementListMarker,
+ });
+ baseRenderer.convert.mockReturnValueOnce(listItem);
+
+ expect(htmlToMarkdownRenderer['OL LI'](NODE, subContent)).toBe(result);
+ expect(baseRenderer.convert).toHaveBeenCalledWith(NODE, subContent);
+ },
+ );
+ });
+
describe('STRONG, B visitor', () => {
it.each`
input | strongCharacter | result