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
This commit is contained in:
parent
79570ce24f
commit
3be32027b6
28 changed files with 852 additions and 390 deletions
116
app/assets/javascripts/ci_variable_list/ajax_variable_list.js
Normal file
116
app/assets/javascripts/ci_variable_list/ajax_variable_list.js
Normal file
|
@ -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');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -6,4 +6,6 @@ export default {
|
|||
ABORTED: 0,
|
||||
NO_CONTENT: 204,
|
||||
OK: 200,
|
||||
MULTIPLE_CHOICES: 300,
|
||||
BAD_REQUEST: 400,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
@ -85,4 +90,8 @@
|
|||
outline: none;
|
||||
color: $gl-text-color;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
color: $gl-text-color-disabled;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
%p.append-bottom-default
|
||||
Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags.
|
||||
You can use variables for passwords, secret keys, or whatever you want.
|
||||
= _('Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want.')
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
= form_for @variable, as: :variable, url: @variable.form_path do |f|
|
||||
= form_errors(@variable)
|
||||
|
||||
.form-group
|
||||
= f.label :key, "Key", class: "label-light"
|
||||
= f.text_field :key, class: "form-control", placeholder: @variable.placeholder, required: true
|
||||
.form-group
|
||||
= f.label :value, "Value", class: "label-light"
|
||||
= f.text_area :value, class: "form-control", placeholder: @variable.placeholder
|
||||
.form-group
|
||||
.checkbox
|
||||
= f.label :protected do
|
||||
= f.check_box :protected
|
||||
%strong Protected
|
||||
.help-block
|
||||
This variable will be passed only to pipelines running on protected branches and tags
|
||||
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'protected-secret-variables'), target: '_blank'
|
||||
|
||||
= f.submit btn_text, class: "btn btn-save"
|
|
@ -1,16 +1,20 @@
|
|||
.row.prepend-top-default.append-bottom-default
|
||||
.col-lg-12
|
||||
%h5.prepend-top-0
|
||||
Add a variable
|
||||
= render "ci/variables/form", btn_text: "Add new variable"
|
||||
%hr
|
||||
%h5.prepend-top-0
|
||||
Your variables (#{@variables.size})
|
||||
- if @variables.empty?
|
||||
%p.settings-message.text-center.append-bottom-0
|
||||
No variables found, add one with the form above.
|
||||
- else
|
||||
.js-secret-variable-table
|
||||
= render "ci/variables/table"
|
||||
%button.btn.btn-info.js-secret-value-reveal-button{ data: { secret_reveal_status: 'false' } }
|
||||
- save_endpoint = local_assigns.fetch(:save_endpoint, nil)
|
||||
|
||||
.row
|
||||
.col-lg-12.js-ci-variable-list-section{ data: { save_endpoint: save_endpoint } }
|
||||
.hide.alert.alert-danger.js-ci-variable-error-box
|
||||
|
||||
%ul.ci-variable-list
|
||||
- @variables.each.each do |variable|
|
||||
= render 'ci/variables/variable_row', form_field: 'variables', variable: variable
|
||||
= render 'ci/variables/variable_row', form_field: 'variables'
|
||||
.prepend-top-20
|
||||
%button.btn.btn-success.js-secret-variables-save-button{ type: 'button' }
|
||||
%span.hide.js-secret-variables-save-loading-icon
|
||||
= icon('spinner spin')
|
||||
= _('Save variables')
|
||||
%button.btn.btn-info.btn-inverted.prepend-left-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: "#{@variables.size == 0}" } }
|
||||
- if @variables.size == 0
|
||||
= n_('Hide value', 'Hide values', @variables.size)
|
||||
- else
|
||||
= n_('Reveal value', 'Reveal values', @variables.size)
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
- page_title "Variables"
|
||||
|
||||
.row.prepend-top-default.append-bottom-default
|
||||
.col-lg-3
|
||||
= render "ci/variables/content"
|
||||
.col-lg-9
|
||||
%h4.prepend-top-0
|
||||
Update variable
|
||||
= render "ci/variables/form", btn_text: "Save variable"
|
|
@ -1,32 +0,0 @@
|
|||
.table-responsive.variables-table
|
||||
%table.table
|
||||
%colgroup
|
||||
%col
|
||||
%col
|
||||
%col
|
||||
%col{ width: 100 }
|
||||
%thead
|
||||
%th Key
|
||||
%th Value
|
||||
%th Protected
|
||||
%th
|
||||
%tbody
|
||||
- @variables.each do |variable|
|
||||
- if variable.id?
|
||||
%tr
|
||||
%td.variable-key= variable.key
|
||||
%td.variable-value
|
||||
%span.js-secret-value-placeholder
|
||||
= '*' * 6
|
||||
%span.hide.js-secret-value
|
||||
= variable.value
|
||||
%td.variable-protected= Gitlab::Utils.boolean_to_yes_no(variable.protected)
|
||||
%td.variable-menu
|
||||
= link_to variable.edit_path, class: "btn btn-transparent btn-variable-edit" do
|
||||
%span.sr-only
|
||||
Update
|
||||
= icon("pencil")
|
||||
= link_to variable.delete_path, class: "btn btn-transparent btn-variable-delete", method: :delete, data: { confirm: "Are you sure?" } do
|
||||
%span.sr-only
|
||||
Remove
|
||||
= icon("trash")
|
|
@ -1,4 +1,11 @@
|
|||
- breadcrumb_title "CI / CD Settings"
|
||||
- page_title "CI / CD"
|
||||
|
||||
= render 'ci/variables/index'
|
||||
%h4
|
||||
= _('Secret variables')
|
||||
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'secret-variables'), target: '_blank', rel: 'noopener noreferrer'
|
||||
|
||||
%p
|
||||
= render "ci/variables/content"
|
||||
|
||||
= render 'ci/variables/index', save_endpoint: group_variables_path
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
= render 'ci/variables/show'
|
|
@ -29,14 +29,14 @@
|
|||
%section.settings.no-animate{ class: ('expanded' if expanded) }
|
||||
.settings-header
|
||||
%h4
|
||||
Secret variables
|
||||
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'secret-variables'), target: '_blank'
|
||||
= _('Secret variables')
|
||||
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'secret-variables'), target: '_blank', rel: 'noopener noreferrer'
|
||||
%button.btn.js-settings-toggle
|
||||
= expanded ? 'Collapse' : 'Expand'
|
||||
%p
|
||||
%p.append-bottom-0
|
||||
= render "ci/variables/content"
|
||||
.settings-content
|
||||
= render 'ci/variables/index'
|
||||
= render 'ci/variables/index', save_endpoint: project_variables_path(@project)
|
||||
|
||||
%section.settings.no-animate{ class: ('expanded' if expanded) }
|
||||
.settings-header
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
= render 'ci/variables/show'
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Update CI/CD secret variables list to be dynamic and save without reloading
|
||||
the page
|
||||
merge_request: 4110
|
||||
author:
|
||||
type: added
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 32 KiB |
|
@ -31,7 +31,7 @@ module QA
|
|||
page.fill_variable_key(key)
|
||||
page.fill_variable_value(value)
|
||||
|
||||
page.add_variable
|
||||
page.save_variables
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,49 +5,40 @@ module QA
|
|||
class SecretVariables < Page::Base
|
||||
include Common
|
||||
|
||||
view 'app/views/ci/variables/_table.html.haml' do
|
||||
element :variable_key, '.variable-key'
|
||||
element :variable_value, '.variable-value'
|
||||
view 'app/views/ci/variables/_variable_row.html.haml' do
|
||||
element :variable_key, '.js-ci-variable-input-key'
|
||||
element :variable_value, '.js-ci-variable-input-value'
|
||||
end
|
||||
|
||||
view 'app/views/ci/variables/_index.html.haml' do
|
||||
element :add_new_variable, 'btn_text: "Add new variable"'
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/behaviors/secret_values.js' do
|
||||
element :reveal_value, 'Reveal value'
|
||||
element :hide_value, 'Hide value'
|
||||
element :save_variables, '.js-secret-variables-save-button'
|
||||
end
|
||||
|
||||
def fill_variable_key(key)
|
||||
fill_in 'variable_key', with: key
|
||||
end
|
||||
|
||||
def fill_variable_value(value)
|
||||
fill_in 'variable_value', with: value
|
||||
end
|
||||
|
||||
def add_variable
|
||||
click_on 'Add new variable'
|
||||
end
|
||||
|
||||
def variable_key
|
||||
page.find('.variable-key').text
|
||||
end
|
||||
|
||||
def variable_value
|
||||
reveal_value do
|
||||
page.find('.variable-value').text
|
||||
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
|
||||
page.find('.js-ci-variable-input-key').set(key)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def fill_variable_value(value)
|
||||
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
|
||||
page.find('.js-ci-variable-input-value').set(value)
|
||||
end
|
||||
end
|
||||
|
||||
def reveal_value
|
||||
click_button('Reveal value')
|
||||
def save_variables
|
||||
click_button('Save variables')
|
||||
end
|
||||
|
||||
yield.tap do
|
||||
click_button('Hide value')
|
||||
def variable_key
|
||||
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
|
||||
page.find('.js-ci-variable-input-key').value
|
||||
end
|
||||
end
|
||||
|
||||
def variable_value
|
||||
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
|
||||
page.find('.js-ci-variable-input-value').value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,76 +3,15 @@ require 'spec_helper'
|
|||
feature 'Group variables', :js do
|
||||
let(:user) { create(:user) }
|
||||
let(:group) { create(:group) }
|
||||
let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test value', group: group) }
|
||||
let(:page_path) { group_settings_ci_cd_path(group) }
|
||||
|
||||
background do
|
||||
group.add_master(user)
|
||||
gitlab_sign_in(user)
|
||||
|
||||
visit page_path
|
||||
end
|
||||
|
||||
context 'when user creates a new variable' do
|
||||
background do
|
||||
visit group_settings_ci_cd_path(group)
|
||||
fill_in 'variable_key', with: 'AAA'
|
||||
fill_in 'variable_value', with: 'AAA123'
|
||||
find(:css, "#variable_protected").set(true)
|
||||
click_on 'Add new variable'
|
||||
end
|
||||
|
||||
scenario 'user sees the created variable' do
|
||||
page.within('.variables-table') do
|
||||
expect(find(".variable-key")).to have_content('AAA')
|
||||
expect(find(".variable-value")).to have_content('******')
|
||||
expect(find(".variable-protected")).to have_content('Yes')
|
||||
end
|
||||
click_on 'Reveal value'
|
||||
page.within('.variables-table') do
|
||||
expect(find(".variable-value")).to have_content('AAA123')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user edits a variable' do
|
||||
background do
|
||||
create(:ci_group_variable, key: 'AAA', value: 'AAA123', protected: true,
|
||||
group: group)
|
||||
|
||||
visit group_settings_ci_cd_path(group)
|
||||
|
||||
page.within('.variable-menu') do
|
||||
click_on 'Update'
|
||||
end
|
||||
|
||||
fill_in 'variable_key', with: 'BBB'
|
||||
fill_in 'variable_value', with: 'BBB123'
|
||||
find(:css, "#variable_protected").set(false)
|
||||
click_on 'Save variable'
|
||||
end
|
||||
|
||||
scenario 'user sees the updated variable' do
|
||||
page.within('.variables-table') do
|
||||
expect(find(".variable-key")).to have_content('BBB')
|
||||
expect(find(".variable-value")).to have_content('******')
|
||||
expect(find(".variable-protected")).to have_content('No')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user deletes a variable' do
|
||||
background do
|
||||
create(:ci_group_variable, key: 'BBB', value: 'BBB123', protected: false,
|
||||
group: group)
|
||||
|
||||
visit group_settings_ci_cd_path(group)
|
||||
|
||||
page.within('.variable-menu') do
|
||||
page.accept_alert 'Are you sure?' do
|
||||
click_on 'Remove'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scenario 'user does not see the deleted variable' do
|
||||
expect(page).to have_no_css('.variables-table')
|
||||
end
|
||||
end
|
||||
it_behaves_like 'variable list'
|
||||
end
|
||||
|
|
18
spec/features/project_variables_spec.rb
Normal file
18
spec/features/project_variables_spec.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Project variables', :js do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project) }
|
||||
let(:variable) { create(:ci_variable, key: 'test_key', value: 'test value') }
|
||||
let(:page_path) { project_settings_ci_cd_path(project) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
project.add_master(user)
|
||||
project.variables << variable
|
||||
|
||||
visit page_path
|
||||
end
|
||||
|
||||
it_behaves_like 'variable list'
|
||||
end
|
|
@ -1,145 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Project variables', :js do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project) }
|
||||
let(:variable) { create(:ci_variable, key: 'test_key', value: 'test value') }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
project.add_master(user)
|
||||
project.variables << variable
|
||||
|
||||
visit project_settings_ci_cd_path(project)
|
||||
end
|
||||
|
||||
it 'shows list of variables' do
|
||||
page.within('.variables-table') do
|
||||
expect(page).to have_content(variable.key)
|
||||
end
|
||||
end
|
||||
|
||||
it 'adds new secret variable' do
|
||||
fill_in('variable_key', with: 'key')
|
||||
fill_in('variable_value', with: 'key value')
|
||||
click_button('Add new variable')
|
||||
|
||||
expect(page).to have_content('Variable was successfully created.')
|
||||
page.within('.variables-table') do
|
||||
expect(page).to have_content('key')
|
||||
expect(page).to have_content('No')
|
||||
end
|
||||
end
|
||||
|
||||
it 'adds empty variable' do
|
||||
fill_in('variable_key', with: 'new_key')
|
||||
fill_in('variable_value', with: '')
|
||||
click_button('Add new variable')
|
||||
|
||||
expect(page).to have_content('Variable was successfully created.')
|
||||
page.within('.variables-table') do
|
||||
expect(page).to have_content('new_key')
|
||||
end
|
||||
end
|
||||
|
||||
it 'adds new protected variable' do
|
||||
fill_in('variable_key', with: 'key')
|
||||
fill_in('variable_value', with: 'value')
|
||||
check('Protected')
|
||||
click_button('Add new variable')
|
||||
|
||||
expect(page).to have_content('Variable was successfully created.')
|
||||
page.within('.variables-table') do
|
||||
expect(page).to have_content('key')
|
||||
expect(page).to have_content('Yes')
|
||||
end
|
||||
end
|
||||
|
||||
it 'reveals and hides new variable' do
|
||||
fill_in('variable_key', with: 'key')
|
||||
fill_in('variable_value', with: 'key value')
|
||||
click_button('Add new variable')
|
||||
|
||||
page.within('.variables-table') do
|
||||
expect(page).to have_content('key')
|
||||
expect(page).to have_content('******')
|
||||
end
|
||||
|
||||
click_button('Reveal values')
|
||||
|
||||
page.within('.variables-table') do
|
||||
expect(page).to have_content('key')
|
||||
expect(page).to have_content('key value')
|
||||
end
|
||||
|
||||
click_button('Hide values')
|
||||
|
||||
page.within('.variables-table') do
|
||||
expect(page).to have_content('key')
|
||||
expect(page).to have_content('******')
|
||||
end
|
||||
end
|
||||
|
||||
it 'deletes variable' do
|
||||
page.within('.variables-table') do
|
||||
accept_confirm { click_on 'Remove' }
|
||||
end
|
||||
|
||||
expect(page).not_to have_selector('variables-table')
|
||||
end
|
||||
|
||||
it 'edits variable' do
|
||||
page.within('.variables-table') do
|
||||
click_on 'Update'
|
||||
end
|
||||
|
||||
expect(page).to have_content('Update variable')
|
||||
fill_in('variable_key', with: 'key')
|
||||
fill_in('variable_value', with: 'key value')
|
||||
click_button('Save variable')
|
||||
|
||||
expect(page).to have_content('Variable was successfully updated.')
|
||||
expect(project.variables(true).first.value).to eq('key value')
|
||||
end
|
||||
|
||||
it 'edits variable with empty value' do
|
||||
page.within('.variables-table') do
|
||||
click_on 'Update'
|
||||
end
|
||||
|
||||
expect(page).to have_content('Update variable')
|
||||
fill_in('variable_value', with: '')
|
||||
click_button('Save variable')
|
||||
|
||||
expect(page).to have_content('Variable was successfully updated.')
|
||||
expect(project.variables(true).first.value).to eq('')
|
||||
end
|
||||
|
||||
it 'edits variable to be protected' do
|
||||
page.within('.variables-table') do
|
||||
click_on 'Update'
|
||||
end
|
||||
|
||||
expect(page).to have_content('Update variable')
|
||||
check('Protected')
|
||||
click_button('Save variable')
|
||||
|
||||
expect(page).to have_content('Variable was successfully updated.')
|
||||
expect(project.variables(true).first).to be_protected
|
||||
end
|
||||
|
||||
it 'edits variable to be unprotected' do
|
||||
project.variables.first.update(protected: true)
|
||||
|
||||
page.within('.variables-table') do
|
||||
click_on 'Update'
|
||||
end
|
||||
|
||||
expect(page).to have_content('Update variable')
|
||||
uncheck('Protected')
|
||||
click_button('Save variable')
|
||||
|
||||
expect(page).to have_content('Variable was successfully updated.')
|
||||
expect(project.variables(true).first).not_to be_protected
|
||||
end
|
||||
end
|
189
spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
Normal file
189
spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
Normal file
|
@ -0,0 +1,189 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import AjaxFormVariableList from '~/ci_variable_list/ajax_variable_list';
|
||||
|
||||
const VARIABLE_PATCH_ENDPOINT = 'http://test.host/frontend-fixtures/builds-project/variables';
|
||||
|
||||
describe('AjaxFormVariableList', () => {
|
||||
preloadFixtures('projects/ci_cd_settings.html.raw');
|
||||
preloadFixtures('projects/ci_cd_settings_with_variables.html.raw');
|
||||
|
||||
let container;
|
||||
let saveButton;
|
||||
let errorBox;
|
||||
|
||||
let mock;
|
||||
let ajaxVariableList;
|
||||
|
||||
beforeEach(() => {
|
||||
loadFixtures('projects/ci_cd_settings.html.raw');
|
||||
container = document.querySelector('.js-ci-variable-list-section');
|
||||
|
||||
mock = new MockAdapter(axios);
|
||||
|
||||
const ajaxVariableListEl = document.querySelector('.js-ci-variable-list-section');
|
||||
saveButton = ajaxVariableListEl.querySelector('.js-secret-variables-save-button');
|
||||
errorBox = container.querySelector('.js-ci-variable-error-box');
|
||||
ajaxVariableList = new AjaxFormVariableList({
|
||||
container,
|
||||
formField: 'variables',
|
||||
saveButton,
|
||||
errorBox,
|
||||
saveEndpoint: container.dataset.saveEndpoint,
|
||||
});
|
||||
|
||||
spyOn(ajaxVariableList, 'updateRowsWithPersistedVariables').and.callThrough();
|
||||
spyOn(ajaxVariableList.variableList, 'toggleEnableRow').and.callThrough();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
describe('onSaveClicked', () => {
|
||||
it('shows loading spinner while waiting for the request', (done) => {
|
||||
const loadingIcon = saveButton.querySelector('.js-secret-variables-save-loading-icon');
|
||||
|
||||
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(() => {
|
||||
expect(loadingIcon.classList.contains('hide')).toEqual(false);
|
||||
|
||||
return [200, {}];
|
||||
});
|
||||
|
||||
expect(loadingIcon.classList.contains('hide')).toEqual(true);
|
||||
|
||||
ajaxVariableList.onSaveClicked()
|
||||
.then(() => {
|
||||
expect(loadingIcon.classList.contains('hide')).toEqual(true);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('calls `updateRowsWithPersistedVariables` with the persisted variables', (done) => {
|
||||
const variablesResponse = [{ id: 1, key: 'foo', value: 'bar' }];
|
||||
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200, {
|
||||
variables: variablesResponse,
|
||||
});
|
||||
|
||||
ajaxVariableList.onSaveClicked()
|
||||
.then(() => {
|
||||
expect(ajaxVariableList.updateRowsWithPersistedVariables)
|
||||
.toHaveBeenCalledWith(variablesResponse);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('hides any previous error box', (done) => {
|
||||
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200);
|
||||
|
||||
expect(errorBox.classList.contains('hide')).toEqual(true);
|
||||
|
||||
ajaxVariableList.onSaveClicked()
|
||||
.then(() => {
|
||||
expect(errorBox.classList.contains('hide')).toEqual(true);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('disables remove buttons while waiting for the request', (done) => {
|
||||
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(() => {
|
||||
expect(ajaxVariableList.variableList.toggleEnableRow).toHaveBeenCalledWith(false);
|
||||
|
||||
return [200, {}];
|
||||
});
|
||||
|
||||
ajaxVariableList.onSaveClicked()
|
||||
.then(() => {
|
||||
expect(ajaxVariableList.variableList.toggleEnableRow).toHaveBeenCalledWith(true);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('shows error box with validation errors', (done) => {
|
||||
const validationError = 'some validation error';
|
||||
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(400, [
|
||||
validationError,
|
||||
]);
|
||||
|
||||
expect(errorBox.classList.contains('hide')).toEqual(true);
|
||||
|
||||
ajaxVariableList.onSaveClicked()
|
||||
.then(() => {
|
||||
expect(errorBox.classList.contains('hide')).toEqual(false);
|
||||
expect(errorBox.textContent.trim().replace(/\n+\s+/m, ' ')).toEqual(`Validation failed ${validationError}`);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('shows flash message when request fails', (done) => {
|
||||
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(500);
|
||||
|
||||
expect(errorBox.classList.contains('hide')).toEqual(true);
|
||||
|
||||
ajaxVariableList.onSaveClicked()
|
||||
.then(() => {
|
||||
expect(errorBox.classList.contains('hide')).toEqual(true);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateRowsWithPersistedVariables', () => {
|
||||
beforeEach(() => {
|
||||
loadFixtures('projects/ci_cd_settings_with_variables.html.raw');
|
||||
container = document.querySelector('.js-ci-variable-list-section');
|
||||
|
||||
const ajaxVariableListEl = document.querySelector('.js-ci-variable-list-section');
|
||||
saveButton = ajaxVariableListEl.querySelector('.js-secret-variables-save-button');
|
||||
errorBox = container.querySelector('.js-ci-variable-error-box');
|
||||
ajaxVariableList = new AjaxFormVariableList({
|
||||
container,
|
||||
formField: 'variables',
|
||||
saveButton,
|
||||
errorBox,
|
||||
saveEndpoint: container.dataset.saveEndpoint,
|
||||
});
|
||||
});
|
||||
|
||||
it('removes variable that was removed', () => {
|
||||
expect(container.querySelectorAll('.js-row').length).toBe(3);
|
||||
|
||||
container.querySelector('.js-row-remove-button').click();
|
||||
|
||||
expect(container.querySelectorAll('.js-row').length).toBe(3);
|
||||
|
||||
ajaxVariableList.updateRowsWithPersistedVariables([]);
|
||||
|
||||
expect(container.querySelectorAll('.js-row').length).toBe(2);
|
||||
});
|
||||
|
||||
it('updates new variable row with persisted ID', () => {
|
||||
const row = container.querySelector('.js-row:last-child');
|
||||
const idInput = row.querySelector('.js-ci-variable-input-id');
|
||||
const keyInput = row.querySelector('.js-ci-variable-input-key');
|
||||
const valueInput = row.querySelector('.js-ci-variable-input-value');
|
||||
|
||||
keyInput.value = 'foo';
|
||||
keyInput.dispatchEvent(new Event('input'));
|
||||
valueInput.value = 'bar';
|
||||
valueInput.dispatchEvent(new Event('input'));
|
||||
|
||||
expect(idInput.value).toEqual('');
|
||||
|
||||
ajaxVariableList.updateRowsWithPersistedVariables([{
|
||||
id: 3,
|
||||
key: 'foo',
|
||||
value: 'bar',
|
||||
}]);
|
||||
|
||||
expect(idInput.value).toEqual('3');
|
||||
expect(row.dataset.isPersisted).toEqual('true');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -4,6 +4,7 @@ import getSetTimeoutPromise from '../helpers/set_timeout_promise_helper';
|
|||
describe('VariableList', () => {
|
||||
preloadFixtures('pipeline_schedules/edit.html.raw');
|
||||
preloadFixtures('pipeline_schedules/edit_with_variables.html.raw');
|
||||
preloadFixtures('projects/ci_cd_settings.html.raw');
|
||||
|
||||
let $wrapper;
|
||||
let variableList;
|
||||
|
@ -105,37 +106,8 @@ describe('VariableList', () => {
|
|||
|
||||
describe('with all inputs(key, value, protected)', () => {
|
||||
beforeEach(() => {
|
||||
// This markup will be replaced with a fixture when we can render the
|
||||
// CI/CD settings page with the new dynamic variable list in https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4110
|
||||
$wrapper = $(`<form class="js-variable-list">
|
||||
<ul>
|
||||
<li class="js-row">
|
||||
<div class="ci-variable-body-item">
|
||||
<input class="js-ci-variable-input-key" name="variables[variables_attributes][][key]">
|
||||
</div>
|
||||
|
||||
<div class="ci-variable-body-item">
|
||||
<textarea class="js-ci-variable-input-value" name="variables[variables_attributes][][value]"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="ci-variable-body-item ci-variable-protected-item">
|
||||
<button type="button" class="js-project-feature-toggle project-feature-toggle">
|
||||
<input
|
||||
type="hidden"
|
||||
class="js-ci-variable-input-protected js-project-feature-toggle-input"
|
||||
name="variables[variables_attributes][][protected]"
|
||||
value="true"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button type="button" class="js-row-remove-button"></button>
|
||||
</li>
|
||||
</ul>
|
||||
<button type="button" class="js-secret-value-reveal-button">
|
||||
Reveal values
|
||||
</button>
|
||||
</form>`);
|
||||
loadFixtures('projects/ci_cd_settings.html.raw');
|
||||
$wrapper = $('.js-ci-variable-list-section');
|
||||
|
||||
variableList = new VariableList({
|
||||
container: $wrapper,
|
||||
|
@ -160,4 +132,51 @@ describe('VariableList', () => {
|
|||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleEnableRow method', () => {
|
||||
beforeEach(() => {
|
||||
loadFixtures('pipeline_schedules/edit_with_variables.html.raw');
|
||||
$wrapper = $('.js-ci-variable-list-section');
|
||||
|
||||
variableList = new VariableList({
|
||||
container: $wrapper,
|
||||
formField: 'variables',
|
||||
});
|
||||
variableList.init();
|
||||
});
|
||||
|
||||
it('should disable all key inputs', () => {
|
||||
expect($wrapper.find('.js-ci-variable-input-key:not([disabled])').length).toBe(3);
|
||||
|
||||
variableList.toggleEnableRow(false);
|
||||
|
||||
expect($wrapper.find('.js-ci-variable-input-key[disabled]').length).toBe(3);
|
||||
});
|
||||
|
||||
it('should disable all remove buttons', () => {
|
||||
expect($wrapper.find('.js-row-remove-button:not([disabled])').length).toBe(3);
|
||||
|
||||
variableList.toggleEnableRow(false);
|
||||
|
||||
expect($wrapper.find('.js-row-remove-button[disabled]').length).toBe(3);
|
||||
});
|
||||
|
||||
it('should enable all remove buttons', () => {
|
||||
variableList.toggleEnableRow(false);
|
||||
expect($wrapper.find('.js-row-remove-button[disabled]').length).toBe(3);
|
||||
|
||||
variableList.toggleEnableRow(true);
|
||||
|
||||
expect($wrapper.find('.js-row-remove-button:not([disabled])').length).toBe(3);
|
||||
});
|
||||
|
||||
it('should enable all key inputs', () => {
|
||||
variableList.toggleEnableRow(false);
|
||||
expect($wrapper.find('.js-ci-variable-input-key[disabled]').length).toBe(3);
|
||||
|
||||
variableList.toggleEnableRow(true);
|
||||
|
||||
expect($wrapper.find('.js-ci-variable-input-key:not([disabled])').length).toBe(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
29
spec/javascripts/fixtures/groups.rb
Normal file
29
spec/javascripts/fixtures/groups.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Groups (JavaScript fixtures)', type: :controller do
|
||||
include JavaScriptFixturesHelpers
|
||||
|
||||
let(:admin) { create(:admin) }
|
||||
let(:group) { create(:group, name: 'frontend-fixtures-group' )}
|
||||
|
||||
render_views
|
||||
|
||||
before(:all) do
|
||||
clean_frontend_fixtures('groups/')
|
||||
end
|
||||
|
||||
before do
|
||||
group.add_master(admin)
|
||||
sign_in(admin)
|
||||
end
|
||||
|
||||
describe Groups::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do
|
||||
it 'groups/ci_cd_settings.html.raw' do |example|
|
||||
get :show,
|
||||
group_id: group
|
||||
|
||||
expect(response).to be_success
|
||||
store_frontend_fixture(response, example.description)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,11 +1,14 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ProjectsController, '(JavaScript fixtures)', type: :controller do
|
||||
describe 'Projects (JavaScript fixtures)', type: :controller do
|
||||
include JavaScriptFixturesHelpers
|
||||
|
||||
let(:admin) { create(:admin) }
|
||||
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
|
||||
let(:project) { create(:project, namespace: namespace, path: 'builds-project') }
|
||||
let(:project_variable_populated) { create(:project, namespace: namespace, path: 'builds-project2') }
|
||||
let!(:variable1) { create(:ci_variable, project: project_variable_populated) }
|
||||
let!(:variable2) { create(:ci_variable, project: project_variable_populated) }
|
||||
|
||||
render_views
|
||||
|
||||
|
@ -14,6 +17,9 @@ describe ProjectsController, '(JavaScript fixtures)', type: :controller do
|
|||
end
|
||||
|
||||
before do
|
||||
# EE-specific start
|
||||
# EE specific end
|
||||
project.add_master(admin)
|
||||
sign_in(admin)
|
||||
end
|
||||
|
||||
|
@ -21,12 +27,43 @@ describe ProjectsController, '(JavaScript fixtures)', type: :controller do
|
|||
remove_repository(project)
|
||||
end
|
||||
|
||||
it 'projects/dashboard.html.raw' do |example|
|
||||
get :show,
|
||||
namespace_id: project.namespace.to_param,
|
||||
id: project
|
||||
describe ProjectsController, '(JavaScript fixtures)', type: :controller do
|
||||
it 'projects/dashboard.html.raw' do |example|
|
||||
get :show,
|
||||
namespace_id: project.namespace.to_param,
|
||||
id: project
|
||||
|
||||
expect(response).to be_success
|
||||
store_frontend_fixture(response, example.description)
|
||||
expect(response).to be_success
|
||||
store_frontend_fixture(response, example.description)
|
||||
end
|
||||
|
||||
it 'projects/edit.html.raw' do |example|
|
||||
get :edit,
|
||||
namespace_id: project.namespace.to_param,
|
||||
id: project
|
||||
|
||||
expect(response).to be_success
|
||||
store_frontend_fixture(response, example.description)
|
||||
end
|
||||
end
|
||||
|
||||
describe Projects::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do
|
||||
it 'projects/ci_cd_settings.html.raw' do |example|
|
||||
get :show,
|
||||
namespace_id: project.namespace.to_param,
|
||||
project_id: project
|
||||
|
||||
expect(response).to be_success
|
||||
store_frontend_fixture(response, example.description)
|
||||
end
|
||||
|
||||
it 'projects/ci_cd_settings_with_variables.html.raw' do |example|
|
||||
get :show,
|
||||
namespace_id: project_variable_populated.namespace.to_param,
|
||||
project_id: project_variable_populated
|
||||
|
||||
expect(response).to be_success
|
||||
store_frontend_fixture(response, example.description)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
269
spec/support/features/variable_list_shared_examples.rb
Normal file
269
spec/support/features/variable_list_shared_examples.rb
Normal file
|
@ -0,0 +1,269 @@
|
|||
shared_examples 'variable list' do
|
||||
it 'shows list of variables' do
|
||||
page.within('.js-ci-variable-list-section') do
|
||||
expect(first('.js-ci-variable-input-key').value).to eq(variable.key)
|
||||
end
|
||||
end
|
||||
|
||||
it 'adds new secret variable' do
|
||||
page.within('.js-ci-variable-list-section .js-row:last-child') do
|
||||
find('.js-ci-variable-input-key').set('key')
|
||||
find('.js-ci-variable-input-value').set('key value')
|
||||
end
|
||||
|
||||
click_button('Save variables')
|
||||
wait_for_requests
|
||||
|
||||
visit page_path
|
||||
|
||||
# We check the first row because it re-sorts to alphabetical order on refresh
|
||||
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
|
||||
expect(find('.js-ci-variable-input-key').value).to eq('key')
|
||||
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key value')
|
||||
end
|
||||
end
|
||||
|
||||
it 'adds empty variable' do
|
||||
page.within('.js-ci-variable-list-section .js-row:last-child') do
|
||||
find('.js-ci-variable-input-key').set('key')
|
||||
find('.js-ci-variable-input-value').set('')
|
||||
end
|
||||
|
||||
click_button('Save variables')
|
||||
wait_for_requests
|
||||
|
||||
visit page_path
|
||||
|
||||
# We check the first row because it re-sorts to alphabetical order on refresh
|
||||
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
|
||||
expect(find('.js-ci-variable-input-key').value).to eq('key')
|
||||
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('')
|
||||
end
|
||||
end
|
||||
|
||||
it 'adds new unprotected variable' do
|
||||
page.within('.js-ci-variable-list-section .js-row:last-child') do
|
||||
find('.js-ci-variable-input-key').set('key')
|
||||
find('.js-ci-variable-input-value').set('key value')
|
||||
find('.ci-variable-protected-item .js-project-feature-toggle').click
|
||||
|
||||
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false')
|
||||
end
|
||||
|
||||
click_button('Save variables')
|
||||
wait_for_requests
|
||||
|
||||
visit page_path
|
||||
|
||||
# We check the first row because it re-sorts to alphabetical order on refresh
|
||||
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
|
||||
expect(find('.js-ci-variable-input-key').value).to eq('key')
|
||||
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key value')
|
||||
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false')
|
||||
end
|
||||
end
|
||||
|
||||
it 'reveals and hides variables' do
|
||||
page.within('.js-ci-variable-list-section') do
|
||||
expect(first('.js-ci-variable-input-key').value).to eq(variable.key)
|
||||
expect(first('.js-ci-variable-input-value', visible: false).value).to eq(variable.value)
|
||||
expect(page).to have_content('*' * 20)
|
||||
|
||||
click_button('Reveal value')
|
||||
|
||||
expect(first('.js-ci-variable-input-key').value).to eq(variable.key)
|
||||
expect(first('.js-ci-variable-input-value').value).to eq(variable.value)
|
||||
expect(page).not_to have_content('*' * 20)
|
||||
|
||||
click_button('Hide value')
|
||||
|
||||
expect(first('.js-ci-variable-input-key').value).to eq(variable.key)
|
||||
expect(first('.js-ci-variable-input-value', visible: false).value).to eq(variable.value)
|
||||
expect(page).to have_content('*' * 20)
|
||||
end
|
||||
end
|
||||
|
||||
it 'deletes variable' do
|
||||
page.within('.js-ci-variable-list-section') do
|
||||
expect(page).to have_selector('.js-row', count: 2)
|
||||
|
||||
first('.js-row-remove-button').click
|
||||
|
||||
click_button('Save variables')
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_selector('.js-row', count: 1)
|
||||
end
|
||||
end
|
||||
|
||||
it 'edits variable' do
|
||||
page.within('.js-ci-variable-list-section') do
|
||||
click_button('Reveal value')
|
||||
|
||||
page.within('.js-row:nth-child(1)') do
|
||||
find('.js-ci-variable-input-key').set('new_key')
|
||||
find('.js-ci-variable-input-value').set('new_value')
|
||||
end
|
||||
|
||||
click_button('Save variables')
|
||||
wait_for_requests
|
||||
|
||||
visit page_path
|
||||
|
||||
page.within('.js-row:nth-child(1)') do
|
||||
expect(find('.js-ci-variable-input-key').value).to eq('new_key')
|
||||
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('new_value')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'edits variable with empty value' do
|
||||
page.within('.js-ci-variable-list-section') do
|
||||
click_button('Reveal value')
|
||||
|
||||
page.within('.js-row:nth-child(1)') do
|
||||
find('.js-ci-variable-input-key').set('new_key')
|
||||
find('.js-ci-variable-input-value').set('')
|
||||
end
|
||||
|
||||
click_button('Save variables')
|
||||
wait_for_requests
|
||||
|
||||
visit page_path
|
||||
|
||||
page.within('.js-row:nth-child(1)') do
|
||||
expect(find('.js-ci-variable-input-key').value).to eq('new_key')
|
||||
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'edits variable to be protected' do
|
||||
# Create the unprotected variable
|
||||
page.within('.js-ci-variable-list-section .js-row:last-child') do
|
||||
find('.js-ci-variable-input-key').set('unprotected_key')
|
||||
find('.js-ci-variable-input-value').set('unprotected_value')
|
||||
find('.ci-variable-protected-item .js-project-feature-toggle').click
|
||||
|
||||
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false')
|
||||
end
|
||||
|
||||
click_button('Save variables')
|
||||
wait_for_requests
|
||||
|
||||
visit page_path
|
||||
|
||||
# We check the first row because it re-sorts to alphabetical order on refresh
|
||||
page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
|
||||
find('.ci-variable-protected-item .js-project-feature-toggle').click
|
||||
|
||||
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
|
||||
end
|
||||
|
||||
click_button('Save variables')
|
||||
wait_for_requests
|
||||
|
||||
visit page_path
|
||||
|
||||
# We check the first row because it re-sorts to alphabetical order on refresh
|
||||
page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
|
||||
expect(find('.js-ci-variable-input-key').value).to eq('unprotected_key')
|
||||
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('unprotected_value')
|
||||
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
|
||||
end
|
||||
end
|
||||
|
||||
it 'edits variable to be unprotected' do
|
||||
# Create the protected variable
|
||||
page.within('.js-ci-variable-list-section .js-row:last-child') do
|
||||
find('.js-ci-variable-input-key').set('protected_key')
|
||||
find('.js-ci-variable-input-value').set('protected_value')
|
||||
|
||||
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
|
||||
end
|
||||
|
||||
click_button('Save variables')
|
||||
wait_for_requests
|
||||
|
||||
visit page_path
|
||||
|
||||
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
|
||||
find('.ci-variable-protected-item .js-project-feature-toggle').click
|
||||
|
||||
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false')
|
||||
end
|
||||
|
||||
click_button('Save variables')
|
||||
wait_for_requests
|
||||
|
||||
visit page_path
|
||||
|
||||
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
|
||||
expect(find('.js-ci-variable-input-key').value).to eq('protected_key')
|
||||
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('protected_value')
|
||||
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false')
|
||||
end
|
||||
end
|
||||
|
||||
it 'handles multiple edits and deletion in the middle' do
|
||||
page.within('.js-ci-variable-list-section') do
|
||||
# Create 2 variables
|
||||
page.within('.js-row:last-child') do
|
||||
find('.js-ci-variable-input-key').set('akey')
|
||||
find('.js-ci-variable-input-value').set('akeyvalue')
|
||||
end
|
||||
page.within('.js-row:last-child') do
|
||||
find('.js-ci-variable-input-key').set('zkey')
|
||||
find('.js-ci-variable-input-value').set('zkeyvalue')
|
||||
end
|
||||
|
||||
click_button('Save variables')
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_selector('.js-row', count: 4)
|
||||
|
||||
# Remove the `akey` variable
|
||||
page.within('.js-row:nth-child(2)') do
|
||||
first('.js-row-remove-button').click
|
||||
end
|
||||
|
||||
# Add another variable
|
||||
page.within('.js-row:last-child') do
|
||||
find('.js-ci-variable-input-key').set('ckey')
|
||||
find('.js-ci-variable-input-value').set('ckeyvalue')
|
||||
end
|
||||
|
||||
click_button('Save variables')
|
||||
wait_for_requests
|
||||
|
||||
visit page_path
|
||||
|
||||
# Expect to find 3 variables(4 rows) in alphbetical order
|
||||
expect(page).to have_selector('.js-row', count: 4)
|
||||
row_keys = all('.js-ci-variable-input-key')
|
||||
expect(row_keys[0].value).to eq('ckey')
|
||||
expect(row_keys[1].value).to eq('test_key')
|
||||
expect(row_keys[2].value).to eq('zkey')
|
||||
expect(row_keys[3].value).to eq('')
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows validation error box about duplicate keys' do
|
||||
page.within('.js-ci-variable-list-section .js-row:last-child') do
|
||||
find('.js-ci-variable-input-key').set('samekey')
|
||||
find('.js-ci-variable-input-value').set('value1')
|
||||
end
|
||||
page.within('.js-ci-variable-list-section .js-row:last-child') do
|
||||
find('.js-ci-variable-input-key').set('samekey')
|
||||
find('.js-ci-variable-input-value').set('value2')
|
||||
end
|
||||
|
||||
click_button('Save variables')
|
||||
wait_for_requests
|
||||
|
||||
# We check the first row because it re-sorts to alphabetical order on refresh
|
||||
page.within('.js-ci-variable-list-section') do
|
||||
expect(find('.js-ci-variable-error-box')).to have_content('Variables key has already been taken')
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue