summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/ci_variable_list
diff options
context:
space:
mode:
authorEric Eastwood <contact@ericeastwood.com>2018-01-16 15:13:14 -0600
committerEric Eastwood <contact@ericeastwood.com>2018-02-05 13:09:29 -0600
commit3be32027b6c543287b94b5be34bf53039d86f88c (patch)
tree6b883f46148f4f6a2f8c127cdefbb205acb2609b /app/assets/javascripts/ci_variable_list
parent79570ce24fa93709db7a7bdd4fae2532a7235486 (diff)
downloadgitlab-ce-3be32027b6c543287b94b5be34bf53039d86f88c.tar.gz
Use dynamic variable list in scheduled pipelines and group/project CI secret variables
See https://gitlab.com/gitlab-org/gitlab-ce/issues/39118 Conflicts: app/views/ci/variables/_form.html.haml app/views/ci/variables/_table.html.haml ee/app/views/ci/variables/_environment_scope.html.haml spec/javascripts/ci_variable_list/ci_variable_list_ee_spec.js spec/javascripts/fixtures/projects.rb
Diffstat (limited to 'app/assets/javascripts/ci_variable_list')
-rw-r--r--app/assets/javascripts/ci_variable_list/ajax_variable_list.js116
-rw-r--r--app/assets/javascripts/ci_variable_list/ci_variable_list.js27
2 files changed, 136 insertions, 7 deletions
diff --git a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
new file mode 100644
index 00000000000..76f93e5c6bd
--- /dev/null
+++ b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
@@ -0,0 +1,116 @@
+import _ from 'underscore';
+import axios from '../lib/utils/axios_utils';
+import { s__ } from '../locale';
+import Flash from '../flash';
+import { convertPermissionToBoolean } from '../lib/utils/common_utils';
+import statusCodes from '../lib/utils/http_status';
+import VariableList from './ci_variable_list';
+
+function generateErrorBoxContent(errors) {
+ const errorList = [].concat(errors).map(errorString => `
+ <li>
+ ${_.escape(errorString)}
+ </li>
+ `);
+
+ return `
+ <p>
+ ${s__('CiVariable|Validation failed')}
+ </p>
+ <ul>
+ ${errorList.join('')}
+ </ul>
+ `;
+}
+
+// Used for the variable list on CI/CD projects/groups settings page
+export default class AjaxVariableList {
+ constructor({
+ container,
+ saveButton,
+ errorBox,
+ formField = 'variables',
+ saveEndpoint,
+ }) {
+ this.container = container;
+ this.saveButton = saveButton;
+ this.errorBox = errorBox;
+ this.saveEndpoint = saveEndpoint;
+
+ this.variableList = new VariableList({
+ container: this.container,
+ formField,
+ });
+
+ this.bindEvents();
+ this.variableList.init();
+ }
+
+ bindEvents() {
+ this.saveButton.addEventListener('click', this.onSaveClicked.bind(this));
+ }
+
+ onSaveClicked() {
+ const loadingIcon = this.saveButton.querySelector('.js-secret-variables-save-loading-icon');
+ loadingIcon.classList.toggle('hide', false);
+ this.errorBox.classList.toggle('hide', true);
+ // We use this to prevent a user from changing a key before we have a chance
+ // to match it up in `updateRowsWithPersistedVariables`
+ this.variableList.toggleEnableRow(false);
+
+ return axios.patch(this.saveEndpoint, {
+ variables_attributes: this.variableList.getAllData(),
+ }, {
+ // We want to be able to process the `res.data` from a 400 error response
+ // and print the validation messages such as duplicate variable keys
+ validateStatus: status => (
+ status >= statusCodes.OK &&
+ status < statusCodes.MULTIPLE_CHOICES
+ ) ||
+ status === statusCodes.BAD_REQUEST,
+ })
+ .then((res) => {
+ loadingIcon.classList.toggle('hide', true);
+ this.variableList.toggleEnableRow(true);
+
+ if (res.status === statusCodes.OK && res.data) {
+ this.updateRowsWithPersistedVariables(res.data.variables);
+ } else if (res.status === statusCodes.BAD_REQUEST) {
+ // Validation failed
+ this.errorBox.innerHTML = generateErrorBoxContent(res.data);
+ this.errorBox.classList.toggle('hide', false);
+ }
+ })
+ .catch(() => {
+ loadingIcon.classList.toggle('hide', true);
+ this.variableList.toggleEnableRow(true);
+ Flash(s__('CiVariable|Error occured while saving variables'));
+ });
+ }
+
+ updateRowsWithPersistedVariables(persistedVariables = []) {
+ const persistedVariableMap = [].concat(persistedVariables).reduce((variableMap, variable) => ({
+ ...variableMap,
+ [variable.key]: variable,
+ }), {});
+
+ this.container.querySelectorAll('.js-row').forEach((row) => {
+ // If we submitted a row that was destroyed, remove it so we don't try
+ // to destroy it again which would cause a BE error
+ const destroyInput = row.querySelector('.js-ci-variable-input-destroy');
+ if (convertPermissionToBoolean(destroyInput.value)) {
+ row.remove();
+ // Update the ID input so any future edits and `_destroy` will apply on the BE
+ } else {
+ const key = row.querySelector('.js-ci-variable-input-key').value;
+ const persistedVariable = persistedVariableMap[key];
+
+ if (persistedVariable) {
+ // eslint-disable-next-line no-param-reassign
+ row.querySelector('.js-ci-variable-input-id').value = persistedVariable.id;
+ row.setAttribute('data-is-persisted', 'true');
+ }
+ }
+ });
+ }
+}
diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
index e46478ddb98..d91789c2192 100644
--- a/app/assets/javascripts/ci_variable_list/ci_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
@@ -11,7 +11,7 @@ function createEnvironmentItem(value) {
return {
title: value === '*' ? ALL_ENVIRONMENTS_STRING : value,
id: value,
- text: value,
+ text: value === '*' ? s__('CiVariable|* (All environments)') : value,
};
}
@@ -41,11 +41,11 @@ export default class VariableList {
selector: '.js-ci-variable-input-protected',
default: 'true',
},
- environment: {
+ environment_scope: {
// 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]"]`,
+ selector: `input[name="${this.formField}[variables_attributes][][environment_scope]"]`,
default: '*',
},
_destroy: {
@@ -104,12 +104,15 @@ export default class VariableList {
setupToggleButtons($row[0]);
+ // Reset the resizable textarea
+ $row.find(this.inputMap.value.selector).css('height', '');
+
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]`,
+ fieldName: `${this.formField}[variables_attributes][][environment_scope]`,
getData: (term, callback) => callback(this.getEnvironmentValues()),
createNewItemFromValue: createEnvironmentItem,
onSelect: () => {
@@ -117,7 +120,7 @@ export default class VariableList {
// so they have the new value we just picked
this.refreshDropdownData();
- $row.find(this.inputMap.environment.selector).trigger('trigger-change');
+ $row.find(this.inputMap.environment_scope.selector).trigger('trigger-change');
},
});
@@ -143,7 +146,8 @@ export default class VariableList {
$row.after($rowClone);
}
- removeRow($row) {
+ removeRow(row) {
+ const $row = $(row);
const isPersisted = convertPermissionToBoolean($row.attr('data-is-persisted'));
if (isPersisted) {
@@ -155,6 +159,10 @@ export default class VariableList {
} else {
$row.remove();
}
+
+ // Refresh the other dropdowns in the variable list
+ // so any value with the variable deleted is gone
+ this.refreshDropdownData();
}
checkIfRowTouched($row) {
@@ -165,6 +173,11 @@ export default class VariableList {
});
}
+ toggleEnableRow(isEnabled = true) {
+ this.$container.find(this.inputMap.key.selector).attr('disabled', !isEnabled);
+ this.$container.find('.js-row-remove-button').attr('disabled', !isEnabled);
+ }
+
getAllData() {
// Ignore the last empty row because we don't want to try persist
// a blank variable and run into validation problems.
@@ -185,7 +198,7 @@ export default class VariableList {
}
getEnvironmentValues() {
- const valueMap = this.$container.find(this.inputMap.environment.selector).toArray()
+ const valueMap = this.$container.find(this.inputMap.environment_scope.selector).toArray()
.reduce((prevValueMap, envInput) => ({
...prevValueMap,
[envInput.value]: envInput.value,