summaryrefslogtreecommitdiff
path: root/app/assets/javascripts
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/behaviors/secret_values.js8
-rw-r--r--app/assets/javascripts/ci_variable_list/ci_variable_list.js205
-rw-r--r--app/assets/javascripts/ci_variable_list/native_form_variable_list.js26
-rw-r--r--app/assets/javascripts/commons/polyfills.js2
-rw-r--r--app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js7
-rw-r--r--app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js73
-rw-r--r--app/assets/javascripts/toggle_buttons.js8
7 files changed, 247 insertions, 82 deletions
diff --git a/app/assets/javascripts/behaviors/secret_values.js b/app/assets/javascripts/behaviors/secret_values.js
index 7f70fce913a..0d6e0dbefcc 100644
--- a/app/assets/javascripts/behaviors/secret_values.js
+++ b/app/assets/javascripts/behaviors/secret_values.js
@@ -15,10 +15,12 @@ export default class SecretValues {
init() {
this.revealButton = this.container.querySelector('.js-secret-value-reveal-button');
- const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus);
- this.updateDom(isRevealed);
+ if (this.revealButton) {
+ const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus);
+ this.updateDom(isRevealed);
- this.revealButton.addEventListener('click', this.onRevealButtonClicked.bind(this));
+ this.revealButton.addEventListener('click', this.onRevealButtonClicked.bind(this));
+ }
}
onRevealButtonClicked() {
diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
new file mode 100644
index 00000000000..e46478ddb98
--- /dev/null
+++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
@@ -0,0 +1,205 @@
+import $ from 'jquery';
+import { convertPermissionToBoolean } from '../lib/utils/common_utils';
+import { s__ } from '../locale';
+import setupToggleButtons from '../toggle_buttons';
+import CreateItemDropdown from '../create_item_dropdown';
+import SecretValues from '../behaviors/secret_values';
+
+const ALL_ENVIRONMENTS_STRING = s__('CiVariable|All environments');
+
+function createEnvironmentItem(value) {
+ return {
+ title: value === '*' ? ALL_ENVIRONMENTS_STRING : value,
+ id: value,
+ text: value,
+ };
+}
+
+export default class VariableList {
+ constructor({
+ container,
+ formField,
+ }) {
+ this.$container = $(container);
+ this.formField = formField;
+ this.environmentDropdownMap = new WeakMap();
+
+ this.inputMap = {
+ id: {
+ selector: '.js-ci-variable-input-id',
+ default: '',
+ },
+ key: {
+ selector: '.js-ci-variable-input-key',
+ default: '',
+ },
+ value: {
+ selector: '.js-ci-variable-input-value',
+ default: '',
+ },
+ protected: {
+ selector: '.js-ci-variable-input-protected',
+ default: 'true',
+ },
+ environment: {
+ // We can't use a `.js-` class here because
+ // gl_dropdown replaces the <input> and doesn't copy over the class
+ // See https://gitlab.com/gitlab-org/gitlab-ce/issues/42458
+ selector: `input[name="${this.formField}[variables_attributes][][environment]"]`,
+ default: '*',
+ },
+ _destroy: {
+ selector: '.js-ci-variable-input-destroy',
+ default: '',
+ },
+ };
+
+ this.secretValues = new SecretValues({
+ container: this.$container[0],
+ valueSelector: '.js-row:not(:last-child) .js-secret-value',
+ placeholderSelector: '.js-row:not(:last-child) .js-secret-value-placeholder',
+ });
+ }
+
+ init() {
+ this.bindEvents();
+ this.secretValues.init();
+ }
+
+ bindEvents() {
+ this.$container.find('.js-row').each((index, rowEl) => {
+ this.initRow(rowEl);
+ });
+
+ this.$container.on('click', '.js-row-remove-button', (e) => {
+ e.preventDefault();
+ this.removeRow($(e.currentTarget).closest('.js-row'));
+ });
+
+ const inputSelector = Object.keys(this.inputMap)
+ .map(name => this.inputMap[name].selector)
+ .join(',');
+
+ // Remove any empty rows except the last row
+ this.$container.on('blur', inputSelector, (e) => {
+ const $row = $(e.currentTarget).closest('.js-row');
+
+ if ($row.is(':not(:last-child)') && !this.checkIfRowTouched($row)) {
+ this.removeRow($row);
+ }
+ });
+
+ // Always make sure there is an empty last row
+ this.$container.on('input trigger-change', inputSelector, () => {
+ const $lastRow = this.$container.find('.js-row').last();
+
+ if (this.checkIfRowTouched($lastRow)) {
+ this.insertRow($lastRow);
+ }
+ });
+ }
+
+ initRow(rowEl) {
+ const $row = $(rowEl);
+
+ setupToggleButtons($row[0]);
+
+ const $environmentSelect = $row.find('.js-variable-environment-toggle');
+ if ($environmentSelect.length) {
+ const createItemDropdown = new CreateItemDropdown({
+ $dropdown: $environmentSelect,
+ defaultToggleLabel: ALL_ENVIRONMENTS_STRING,
+ fieldName: `${this.formField}[variables_attributes][][environment]`,
+ getData: (term, callback) => callback(this.getEnvironmentValues()),
+ createNewItemFromValue: createEnvironmentItem,
+ onSelect: () => {
+ // Refresh the other dropdowns in the variable list
+ // so they have the new value we just picked
+ this.refreshDropdownData();
+
+ $row.find(this.inputMap.environment.selector).trigger('trigger-change');
+ },
+ });
+
+ // Clear out any data that might have been left-over from the row clone
+ createItemDropdown.clearDropdown();
+
+ this.environmentDropdownMap.set($row[0], createItemDropdown);
+ }
+ }
+
+ insertRow($row) {
+ const $rowClone = $row.clone();
+ $rowClone.removeAttr('data-is-persisted');
+
+ // Reset the inputs to their defaults
+ Object.keys(this.inputMap).forEach((name) => {
+ const entry = this.inputMap[name];
+ $rowClone.find(entry.selector).val(entry.default);
+ });
+
+ this.initRow($rowClone);
+
+ $row.after($rowClone);
+ }
+
+ removeRow($row) {
+ const isPersisted = convertPermissionToBoolean($row.attr('data-is-persisted'));
+
+ if (isPersisted) {
+ $row.hide();
+ $row
+ // eslint-disable-next-line no-underscore-dangle
+ .find(this.inputMap._destroy.selector)
+ .val(true);
+ } else {
+ $row.remove();
+ }
+ }
+
+ checkIfRowTouched($row) {
+ return Object.keys(this.inputMap).some((name) => {
+ const entry = this.inputMap[name];
+ const $el = $row.find(entry.selector);
+ return $el.length && $el.val() !== entry.default;
+ });
+ }
+
+ getAllData() {
+ // Ignore the last empty row because we don't want to try persist
+ // a blank variable and run into validation problems.
+ const validRows = this.$container.find('.js-row').toArray().slice(0, -1);
+
+ return validRows.map((rowEl) => {
+ const resultant = {};
+ Object.keys(this.inputMap).forEach((name) => {
+ const entry = this.inputMap[name];
+ const $input = $(rowEl).find(entry.selector);
+ if ($input.length) {
+ resultant[name] = $input.val();
+ }
+ });
+
+ return resultant;
+ });
+ }
+
+ getEnvironmentValues() {
+ const valueMap = this.$container.find(this.inputMap.environment.selector).toArray()
+ .reduce((prevValueMap, envInput) => ({
+ ...prevValueMap,
+ [envInput.value]: envInput.value,
+ }), {});
+
+ return Object.keys(valueMap).map(createEnvironmentItem);
+ }
+
+ refreshDropdownData() {
+ this.$container.find('.js-row').each((index, rowEl) => {
+ const environmentDropdown = this.environmentDropdownMap.get(rowEl);
+ if (environmentDropdown) {
+ environmentDropdown.refreshData();
+ }
+ });
+ }
+}
diff --git a/app/assets/javascripts/ci_variable_list/native_form_variable_list.js b/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
new file mode 100644
index 00000000000..d54ea7df1c3
--- /dev/null
+++ b/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
@@ -0,0 +1,26 @@
+import VariableList from './ci_variable_list';
+
+// Used for the variable list on scheduled pipeline edit page
+export default function setupNativeFormVariableList({
+ container,
+ formField = 'variables',
+}) {
+ const $container = $(container);
+
+ const variableList = new VariableList({
+ container: $container,
+ formField,
+ });
+ variableList.init();
+
+ // Clear out the names in the empty last row so it
+ // doesn't get submitted and throw validation errors
+ $container.closest('form').on('submit trigger-submit', () => {
+ const $lastRow = $container.find('.js-row').last();
+
+ const isTouched = variableList.checkIfRowTouched($lastRow);
+ if (!isTouched) {
+ $lastRow.find('input, textarea').attr('name', '');
+ }
+ });
+}
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index ff9e4485916..46232726510 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -8,6 +8,8 @@ import 'core-js/fn/promise';
import 'core-js/fn/string/code-point-at';
import 'core-js/fn/string/from-code-point';
import 'core-js/fn/symbol';
+import 'core-js/es6/map';
+import 'core-js/es6/weak-map';
// Browser polyfills
import 'classlist-polyfill';
diff --git a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js b/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
index f1cf6e92ef5..0b1a81bae13 100644
--- a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
+++ b/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
@@ -4,7 +4,7 @@ import GlFieldErrors from '../gl_field_errors';
import intervalPatternInput from './components/interval_pattern_input.vue';
import TimezoneDropdown from './components/timezone_dropdown';
import TargetBranchDropdown from './components/target_branch_dropdown';
-import { setupPipelineVariableList } from './setup_pipeline_variable_list';
+import setupNativeFormVariableList from '../ci_variable_list/native_form_variable_list';
Vue.use(Translate);
@@ -42,5 +42,8 @@ document.addEventListener('DOMContentLoaded', () => {
gl.targetBranchDropdown = new TargetBranchDropdown();
gl.pipelineScheduleFieldErrors = new GlFieldErrors(formElement);
- setupPipelineVariableList($('.js-pipeline-variable-list'));
+ setupNativeFormVariableList({
+ container: $('.js-ci-variable-list-section'),
+ formField: 'schedule',
+ });
});
diff --git a/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js b/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js
deleted file mode 100644
index 9e0e5cacb11..00000000000
--- a/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import { convertPermissionToBoolean } from '../lib/utils/common_utils';
-
-function insertRow($row) {
- const $rowClone = $row.clone();
- $rowClone.removeAttr('data-is-persisted');
- $rowClone.find('input, textarea').val('');
- $row.after($rowClone);
-}
-
-function removeRow($row) {
- const isPersisted = convertPermissionToBoolean($row.attr('data-is-persisted'));
-
- if (isPersisted) {
- $row.hide();
- $row
- .find('.js-destroy-input')
- .val(1);
- } else {
- $row.remove();
- }
-}
-
-function checkIfRowTouched($row) {
- return $row.find('.js-user-input').toArray().some(el => $(el).val().length > 0);
-}
-
-function setupPipelineVariableList(parent = document) {
- const $parent = $(parent);
-
- $parent.on('click', '.js-row-remove-button', (e) => {
- const $row = $(e.currentTarget).closest('.js-row');
- removeRow($row);
-
- e.preventDefault();
- });
-
- // Remove any empty rows except the last r
- $parent.on('blur', '.js-user-input', (e) => {
- const $row = $(e.currentTarget).closest('.js-row');
-
- const isTouched = checkIfRowTouched($row);
- if ($row.is(':not(:last-child)') && !isTouched) {
- removeRow($row);
- }
- });
-
- // Always make sure there is an empty last row
- $parent.on('input', '.js-user-input', () => {
- const $lastRow = $parent.find('.js-row').last();
-
- const isTouched = checkIfRowTouched($lastRow);
- if (isTouched) {
- insertRow($lastRow);
- }
- });
-
- // Clear out the empty last row so it
- // doesn't get submitted and throw validation errors
- $parent.closest('form').on('submit', () => {
- const $lastRow = $parent.find('.js-row').last();
-
- const isTouched = checkIfRowTouched($lastRow);
- if (!isTouched) {
- $lastRow.find('input, textarea').attr('name', '');
- }
- });
-}
-
-export {
- setupPipelineVariableList,
- insertRow,
- removeRow,
-};
diff --git a/app/assets/javascripts/toggle_buttons.js b/app/assets/javascripts/toggle_buttons.js
index 974dc3ee052..2d680d0f0dc 100644
--- a/app/assets/javascripts/toggle_buttons.js
+++ b/app/assets/javascripts/toggle_buttons.js
@@ -13,7 +13,7 @@ import { convertPermissionToBoolean } from './lib/utils/common_utils';
```
*/
-function updatetoggle(toggle, isOn) {
+function updateToggle(toggle, isOn) {
toggle.classList.toggle('is-checked', isOn);
}
@@ -21,7 +21,7 @@ function onToggleClicked(toggle, input, clickCallback) {
const previousIsOn = convertPermissionToBoolean(input.value);
// Visually change the toggle and start loading
- updatetoggle(toggle, !previousIsOn);
+ updateToggle(toggle, !previousIsOn);
toggle.setAttribute('disabled', true);
toggle.classList.toggle('is-loading', true);
@@ -32,7 +32,7 @@ function onToggleClicked(toggle, input, clickCallback) {
})
.catch(() => {
// Revert the visuals if something goes wrong
- updatetoggle(toggle, previousIsOn);
+ updateToggle(toggle, previousIsOn);
})
.then(() => {
// Remove the loading indicator in any case
@@ -54,7 +54,7 @@ export default function setupToggleButtons(container, clickCallback = () => {})
const isOn = convertPermissionToBoolean(input.value);
// Get the visible toggle in sync with the hidden input
- updatetoggle(toggle, isOn);
+ updateToggle(toggle, isOn);
toggle.addEventListener('click', onToggleClicked.bind(null, toggle, input, clickCallback));
});