summaryrefslogtreecommitdiff
path: root/app/assets
diff options
context:
space:
mode:
authorKamil Trzciński <ayufan@ayufan.eu>2018-02-07 12:23:36 +0000
committerKamil Trzciński <ayufan@ayufan.eu>2018-02-07 12:23:36 +0000
commita55cfc1e6316232e25a46416b0a60be7c1282da7 (patch)
tree26ea2ed9e04c2110e718441577aec2489ea76642 /app/assets
parent024c8a427eaf3267daf92359dcb9668f28fa8384 (diff)
parentefcdc269e03836e78dcc8d460621c948ac02bc24 (diff)
downloadgitlab-ce-a55cfc1e6316232e25a46416b0a60be7c1282da7.tar.gz
Merge branch 'ce-39118-dynamic-pipeline-variables-fe' into 'master'
Dynamic CI secret variables -- CE backport See merge request gitlab-org/gitlab-ce!16842
Diffstat (limited to 'app/assets')
-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
-rw-r--r--app/assets/javascripts/commons/polyfills/element.js19
-rw-r--r--app/assets/javascripts/lib/utils/http_status.js2
-rw-r--r--app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js17
-rw-r--r--app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js17
-rw-r--r--app/assets/stylesheets/framework/ci_variable_list.scss13
7 files changed, 188 insertions, 23 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,
diff --git a/app/assets/javascripts/commons/polyfills/element.js b/app/assets/javascripts/commons/polyfills/element.js
index 9a1f73bf2ac..b593bde6aa2 100644
--- a/app/assets/javascripts/commons/polyfills/element.js
+++ b/app/assets/javascripts/commons/polyfills/element.js
@@ -18,3 +18,22 @@ Element.prototype.matches = Element.prototype.matches ||
while (i >= 0 && elms.item(i) !== this) { i -= 1; }
return i > -1;
};
+
+// From the polyfill on MDN, https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove#Polyfill
+((arr) => {
+ arr.forEach((item) => {
+ if (Object.prototype.hasOwnProperty.call(item, 'remove')) {
+ return;
+ }
+ Object.defineProperty(item, 'remove', {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: function remove() {
+ if (this.parentNode !== null) {
+ this.parentNode.removeChild(this);
+ }
+ },
+ });
+ });
+})([Element.prototype, CharacterData.prototype, DocumentType.prototype]);
diff --git a/app/assets/javascripts/lib/utils/http_status.js b/app/assets/javascripts/lib/utils/http_status.js
index 625e53ee9de..bb151929431 100644
--- a/app/assets/javascripts/lib/utils/http_status.js
+++ b/app/assets/javascripts/lib/utils/http_status.js
@@ -6,4 +6,6 @@ export default {
ABORTED: 0,
NO_CONTENT: 204,
OK: 200,
+ MULTIPLE_CHOICES: 300,
+ BAD_REQUEST: 400,
};
diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
index f26c7360fbe..ad79f7e09ac 100644
--- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
@@ -1,11 +1,12 @@
-import SecretValues from '~/behaviors/secret_values';
+import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
export default () => {
- const secretVariableTable = document.querySelector('.js-secret-variable-table');
- if (secretVariableTable) {
- const secretVariableTableValues = new SecretValues({
- container: secretVariableTable,
- });
- secretVariableTableValues.init();
- }
+ const variableListEl = document.querySelector('.js-ci-variable-list-section');
+ // eslint-disable-next-line no-new
+ new AjaxVariableList({
+ container: variableListEl,
+ saveButton: variableListEl.querySelector('.js-secret-variables-save-button'),
+ errorBox: variableListEl.querySelector('.js-ci-variable-error-box'),
+ saveEndpoint: variableListEl.dataset.saveEndpoint,
+ });
};
diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
index 18dc1dc03a5..a563d0f9961 100644
--- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
@@ -1,9 +1,11 @@
import initSettingsPanels from '~/settings_panels';
import SecretValues from '~/behaviors/secret_values';
+import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
export default function () {
// Initialize expandable settings panels
initSettingsPanels();
+
const runnerToken = document.querySelector('.js-secret-runner-token');
if (runnerToken) {
const runnerTokenSecretValue = new SecretValues({
@@ -12,11 +14,12 @@ export default function () {
runnerTokenSecretValue.init();
}
- const secretVariableTable = document.querySelector('.js-secret-variable-table');
- if (secretVariableTable) {
- const secretVariableTableValues = new SecretValues({
- container: secretVariableTable,
- });
- secretVariableTableValues.init();
- }
+ const variableListEl = document.querySelector('.js-ci-variable-list-section');
+ // eslint-disable-next-line no-new
+ new AjaxVariableList({
+ container: variableListEl,
+ saveButton: variableListEl.querySelector('.js-secret-variables-save-button'),
+ errorBox: variableListEl.querySelector('.js-ci-variable-error-box'),
+ saveEndpoint: variableListEl.dataset.saveEndpoint,
+ });
}
diff --git a/app/assets/stylesheets/framework/ci_variable_list.scss b/app/assets/stylesheets/framework/ci_variable_list.scss
index 8f654ab363c..5fe835dd8f9 100644
--- a/app/assets/stylesheets/framework/ci_variable_list.scss
+++ b/app/assets/stylesheets/framework/ci_variable_list.scss
@@ -8,7 +8,11 @@
.ci-variable-row {
display: flex;
- align-items: flex-end;
+ align-items: flex-start;
+
+ @media (max-width: $screen-xs-max) {
+ align-items: flex-end;
+ }
&:not(:last-child) {
margin-bottom: $gl-btn-padding;
@@ -41,6 +45,7 @@
.ci-variable-row-body {
display: flex;
+ align-items: flex-start;
width: 100%;
@media (max-width: $screen-xs-max) {
@@ -65,6 +70,8 @@
flex: 0 1 auto;
display: flex;
align-items: center;
+ padding-top: 5px;
+ padding-bottom: 5px;
}
.ci-variable-row-remove-button {
@@ -85,4 +92,8 @@
outline: none;
color: $gl-text-color;
}
+
+ &[disabled] {
+ color: $gl-text-color-disabled;
+ }
}