diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-21 00:09:23 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-21 00:09:23 +0300 |
commit | 192bc8bd3109f30e957bf30a0139ae27fefd7936 (patch) | |
tree | 61c8c415c765900386ac43ea0a5a8a5ce94366b6 /app | |
parent | bf213f07c8146b7121240af90a07cb4b2ecc41fa (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
14 files changed, 378 insertions, 199 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 |