Commit 332aa756 authored by Eric Eastwood's avatar Eric Eastwood

Refactor CI variable list code for usage with CI/CD settings page secret variables

Part of https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4110
parent 03f386c2
......@@ -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() {
......
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();
}
});
}
}
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', '');
}
});
}
......@@ -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';
......
......@@ -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',
});
});
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,
};
......@@ -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));
});
......
......@@ -60,3 +60,4 @@
@import "framework/memory_graph";
@import "framework/responsive_tables";
@import "framework/stacked-progress-bar";
@import "framework/ci_variable_list";
......@@ -176,6 +176,11 @@
&.btn-remove {
@include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700);
}
&.btn-primary,
&.btn-info {
@include btn-outline($white-light, $blue-500, $blue-500, $blue-500, $white-light, $blue-600, $blue-600, $blue-700);
}
}
&.btn-gray {
......
.ci-variable-list {
margin-left: 0;
margin-bottom: 0;
padding-left: 0;
list-style: none;
clear: both;
}
.ci-variable-row {
display: flex;
align-items: flex-end;
&:not(:last-child) {
margin-bottom: $gl-btn-padding;
@media (max-width: $screen-xs-max) {
margin-bottom: 3 * $gl-btn-padding;
}
}
&:last-child {
.ci-variable-body-item:last-child {
margin-right: $ci-variable-remove-button-width;
@media (max-width: $screen-xs-max) {
margin-right: 0;
}
}
.ci-variable-row-remove-button {
display: none;
}
@media (max-width: $screen-xs-max) {
.ci-variable-row-body {
margin-right: $ci-variable-remove-button-width;
}
}
}
}
.ci-variable-row-body {
display: flex;
width: 100%;
@media (max-width: $screen-xs-max) {
display: block;
}
}
.ci-variable-body-item {
flex: 1;
&:not(:last-child) {
margin-right: $gl-btn-padding;
@media (max-width: $screen-xs-max) {
margin-right: 0;
margin-bottom: $gl-btn-padding;
}
}
}
.ci-variable-protected-item {
flex: 0 1 auto;
display: flex;
align-items: center;
}
.ci-variable-row-remove-button {
@include transition(color);
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
width: $ci-variable-remove-button-width;
height: $input-height;
padding: 0;
background: transparent;
border: 0;
color: $gl-text-color-secondary;
&:hover,
&:focus {
outline: none;
color: $gl-text-color;
}
}
......@@ -668,9 +668,9 @@ $pipeline-dropdown-line-height: 20px;
$pipeline-dropdown-status-icon-size: 18px;
/*
Pipeline Schedules
CI variable lists
*/
$pipeline-variable-remove-button-width: calc(1em + #{2 * $gl-padding});
$ci-variable-remove-button-width: calc(1em + #{2 * $gl-padding});
/*
......
......@@ -78,84 +78,3 @@
margin-right: 3px;
}
}
.pipeline-variable-list {
margin-left: 0;
margin-bottom: 0;
padding-left: 0;
list-style: none;
clear: both;
}
.pipeline-variable-row {
display: flex;
align-items: flex-end;
&:not(:last-child) {
margin-bottom: $gl-btn-padding;
}
@media (max-width: $screen-sm-max) {
padding-right: $gl-col-padding;
}
&:last-child {
.pipeline-variable-row-remove-button {
display: none;
}
@media (max-width: $screen-sm-max) {
.pipeline-variable-value-input {
margin-right: $pipeline-variable-remove-button-width;
}
}
@media (max-width: $screen-xs-max) {
.pipeline-variable-row-body {
margin-right: $pipeline-variable-remove-button-width;
}
}
}
}
.pipeline-variable-row-body {
display: flex;
width: calc(75% - #{$gl-col-padding});
padding-left: $gl-col-padding;
@media (max-width: $screen-sm-max) {
width: 100%;
}
@media (max-width: $screen-xs-max) {
display: block;
}
}
.pipeline-variable-key-input {
margin-right: $gl-btn-padding;
@media (max-width: $screen-xs-max) {
margin-bottom: $gl-btn-padding;
}
}
.pipeline-variable-row-remove-button {
@include transition(color);
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
width: $pipeline-variable-remove-button-width;
height: $input-height;
padding: 0;
background: transparent;
border: 0;
color: $gl-text-color-secondary;
&:hover,
&:focus {
outline: none;
color: $gl-text-color;
}
}
- form_field = local_assigns.fetch(:form_field, nil)
- variable = local_assigns.fetch(:variable, nil)
- only_key_value = local_assigns.fetch(:only_key_value, false)
- id = variable&.id
- key = variable&.key
- value = variable&.value
- is_protected = variable && !only_key_value ? variable.protected : true
- id_input_name = "#{form_field}[variables_attributes][][id]"
- destroy_input_name = "#{form_field}[variables_attributes][][_destroy]"
- key_input_name = "#{form_field}[variables_attributes][][key]"
- value_input_name = "#{form_field}[variables_attributes][][value]"
- protected_input_name = "#{form_field}[variables_attributes][][protected]"
%li.js-row.ci-variable-row{ data: { is_persisted: "#{!id.nil?}" } }
.ci-variable-row-body
%input.js-ci-variable-input-id{ type: "hidden", name: id_input_name, value: id }
%input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name }
%input.js-ci-variable-input-key.ci-variable-body-item.form-control{ type: "text",
name: key_input_name,
value: key,
placeholder: s_('CiVariables|Input variable key') }
.ci-variable-body-item
.form-control.js-secret-value-placeholder{ class: ('hide' unless id) }
= '*' * 20
%textarea.js-ci-variable-input-value.js-secret-value.form-control{ class: ('hide' if id),
rows: 1,
name: value_input_name,
placeholder: s_('CiVariables|Input variable value') }
= value
- unless only_key_value
.ci-variable-body-item.ci-variable-protected-item
.append-right-default
= s_("CiVariable|Protected")
%button{ type: 'button',
class: "js-project-feature-toggle project-feature-toggle #{'is-checked' if is_protected}",
"aria-label": s_("CiVariable|Toggle protected") }
%input{ type: "hidden",
class: 'js-ci-variable-input-protected js-project-feature-toggle-input',
name: protected_input_name,
value: is_protected }
%span.toggle-icon
= sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
-# EE-specific start
-# EE-specific end
%button.js-row-remove-button.ci-variable-row-remove-button{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') }
= icon('minus-circle')
......@@ -22,14 +22,20 @@
= f.label :ref, _('Target Branch'), class: 'label-light'
= dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } )
= f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true
.form-group
.form-group.js-ci-variable-list-section
.col-md-9
%label.label-light
#{ s_('PipelineSchedules|Variables') }
%ul.js-pipeline-variable-list.pipeline-variable-list
- @schedule.variables.each do |variable|
= render 'variable_row', id: variable.id, key: variable.key, value: variable.value
= render 'variable_row'
%ul.ci-variable-list
- @schedule.variables.each do |variable|
= render 'ci/variables/variable_row', form_field: 'schedule', variable: variable, only_key_value: true
= render 'ci/variables/variable_row', form_field: 'schedule', only_key_value: true
- if @schedule.variables.size > 0
%button.btn.btn-info.btn-inverted.prepend-top-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: "#{@schedule.variables.size == 0}" } }
- if @schedule.variables.size == 0
= n_('Hide value', 'Hide values', @schedule.variables.size)
- else
= n_('Reveal value', 'Reveal values', @schedule.variables.size)
.form-group
.col-md-9
= f.label :active, s_('PipelineSchedules|Activated'), class: 'label-light'
......
- id = local_assigns.fetch(:id, nil)
- key = local_assigns.fetch(:key, "")
- value = local_assigns.fetch(:value, "")
%li.js-row.pipeline-variable-row{ data: { is_persisted: "#{!id.nil?}" } }
.pipeline-variable-row-body
%input{ type: "hidden", name: "schedule[variables_attributes][][id]", value: id }
%input.js-destroy-input{ type: "hidden", name: "schedule[variables_attributes][][_destroy]" }
%input.js-user-input.pipeline-variable-key-input.form-control{ type: "text",
name: "schedule[variables_attributes][][key]",
value: key,
placeholder: s_('PipelineSchedules|Input variable key') }
%textarea.js-user-input.pipeline-variable-value-input.form-control{ rows: 1,
name: "schedule[variables_attributes][][value]",
placeholder: s_('PipelineSchedules|Input variable value') }
= value
%button.js-row-remove-button.pipeline-variable-row-remove-button{ 'aria-label': s_('PipelineSchedules|Remove variable row') }
%i.fa.fa-minus-circle{ 'aria-hidden': "true" }
---
title: Hide variable values on pipeline schedule edit page
merge_request: 16729
author:
type: changed
......@@ -168,11 +168,11 @@ feature 'Pipeline Schedules', :js do
scenario 'user sees the new variable in edit window' do
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
page.within('.pipeline-variable-list') do
expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('AAA')
expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('AAA123')
expect(find(".pipeline-variable-row:nth-child(2) .pipeline-variable-key-input").value).to eq('BBB')
expect(find(".pipeline-variable-row:nth-child(2) .pipeline-variable-value-input").value).to eq('BBB123')
page.within('.ci-variable-list') do
expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-key").value).to eq('AAA')
expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-value", visible: false).value).to eq('AAA123')
expect(find(".ci-variable-row:nth-child(2) .js-ci-variable-input-key").value).to eq('BBB')
expect(find(".ci-variable-row:nth-child(2) .js-ci-variable-input-value", visible: false).value).to eq('BBB123')
end
end
end
......@@ -185,16 +185,18 @@ feature 'Pipeline Schedules', :js do
visit_pipelines_schedules
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
all('[name="schedule[variables_attributes][][key]"]')[0].set('foo')
all('[name="schedule[variables_attributes][][value]"]')[0].set('bar')
find('.js-ci-variable-list-section .js-secret-value-reveal-button').click
first('.js-ci-variable-input-key').set('foo')
first('.js-ci-variable-input-value').set('bar')
click_button 'Save pipeline schedule'
end
scenario 'user sees the updated variable in edit window' do
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
page.within('.pipeline-variable-list') do
expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('foo')
expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('bar')
page.within('.ci-variable-list') do
expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-key").value).to eq('foo')
expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-value", visible: false).value).to eq('bar')
end
end
end
......@@ -207,15 +209,15 @@ feature 'Pipeline Schedules', :js do
visit_pipelines_schedules
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
find('.pipeline-variable-list .pipeline-variable-row-remove-button').click
find('.ci-variable-list .ci-variable-row-remove-button').click
click_button 'Save pipeline schedule'
end
scenario 'user does not see the removed variable in edit window' do
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
page.within('.pipeline-variable-list') do
expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('')
expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('')
page.within('.ci-variable-list') do
expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-key").value).to eq('')
expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-value", visible: false).value).to eq('')
end
end
end
......
import VariableList from '~/ci_variable_list/ci_variable_list';
import getSetTimeoutPromise from '../helpers/set_timeout_promise_helper';
describe('VariableList', () => {
preloadFixtures('pipeline_schedules/edit.html.raw');
preloadFixtures('pipeline_schedules/edit_with_variables.html.raw');
let $wrapper;
let variableList;
describe('with only key/value inputs', () => {
describe('with no variables', () => {
beforeEach(() => {
loadFixtures('pipeline_schedules/edit.html.raw');
$wrapper = $('.js-ci-variable-list-section');
variableList = new VariableList({
container: $wrapper,
formField: 'schedule',
});
variableList.init();
});
it('should remove the row when clicking the remove button', () => {
$wrapper.find('.js-row-remove-button').trigger('click');
expect($wrapper.find('.js-row').length).toBe(0);
});
it('should add another row when editing the last rows key input', () => {
const $row = $wrapper.find('.js-row');
$row.find('.js-ci-variable-input-key')
.val('foo')
.trigger('input');
expect($wrapper.find('.js-row').length).toBe(2);
// Check for the correct default in the new row
const $keyInput = $wrapper.find('.js-row:last-child').find('.js-ci-variable-input-key');
expect($keyInput.val()).toBe('');
});
it('should add another row when editing the last rows value textarea', () => {
const $row = $wrapper.find('.js-row');
$row.find('.js-ci-variable-input-value')
.val('foo')
.trigger('input');
expect($wrapper.find('.js-row').length).toBe(2);
// Check for the correct default in the new row
const $valueInput = $wrapper.find('.js-row:last-child').find('.js-ci-variable-input-key');
expect($valueInput.val()).toBe('');
});
it('should remove empty row after blurring', () => {
const $row = $wrapper.find('.js-row');
$row.find('.js-ci-variable-input-key')
.val('foo')
.trigger('input');
expect($wrapper.find('.js-row').length).toBe(2);
$row.find('.js-ci-variable-input-key')
.val('')
.trigger('input')
.trigger('blur');
expect($wrapper.find('.js-row').length).toBe(1);
});
});
describe('with persisted variables', () => {
beforeEach(() => {
loadFixtures('pipeline_schedules/edit_with_variables.html.raw');
$wrapper = $('.js-ci-variable-list-section');
variableList = new VariableList({
container: $wrapper,
formField: 'schedule',
});
variableList.init();
});
it('should have "Reveal values" button initially when there are already variables', () => {
expect($wrapper.find('.js-secret-value-reveal-button').text()).toBe('Reveal values');
});
it('should reveal hidden values', () => {
const $row = $wrapper.find('.js-row:first-child');
const $inputValue = $row.find('.js-ci-variable-input-value');
const $placeholder = $row.find('.js-secret-value-placeholder');
expect($placeholder.hasClass('hide')).toBe(false);
expect($inputValue.hasClass('hide')).toBe(true);
// Reveal values
$wrapper.find('.js-secret-value-reveal-button').click();
expect($placeholder.hasClass('hide')).toBe(true);
expect($inputValue.hasClass('hide')).toBe(false);
});
});
});
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>`);
variableList = new VariableList({
container: $wrapper,
formField: 'variables',
});
variableList.init();
});
it('should add another row when editing the last rows protected checkbox', (done) => {
const $row = $wrapper.find('.js-row:last-child');
$row.find('.ci-variable-protected-item .js-project-feature-toggle').click();
getSetTimeoutPromise()
.then(() => {
expect($wrapper.find('.js-row').length).toBe(2);
// Check for the correct default in the new row
const $protectedInput = $wrapper.find('.js-row:last-child').find('.js-ci-variable-input-protected');
expect($protectedInput.val()).toBe('true');
})
.then(done)
.catch(done.fail);
});
});
});
import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list';
describe('NativeFormVariableList', () => {
preloadFixtures('pipeline_schedules/edit.html.raw');
let $wrapper;
beforeEach(() => {
loadFixtures('pipeline_schedules/edit.html.raw');
$wrapper = $('.js-ci-variable-list-section');
setupNativeFormVariableList({
container: $wrapper,
formField: 'schedule',
});
});
describe('onFormSubmit', () => {
it('should clear out the `name` attribute on the inputs for the last empty row on form submission (avoid BE validation)', () => {
const $row = $wrapper.find('.js-row');
expect($row.find('.js-ci-variable-input-key').attr('name')).toBe('schedule[variables_attributes][][key]');
expect($row.find('.js-ci-variable-input-value').attr('name')).toBe('schedule[variables_attributes][][value]');
$wrapper.closest('form').trigger('trigger-submit');
expect($row.find('.js-ci-variable-input-key').attr('name')).toBe('');
expect($row.find('.js-ci-variable-input-value').attr('name')).toBe('');
});
});
});
require 'spec_helper'
describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :public, :repository) }
let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: admin) }
let!(:pipeline_schedule_populated) { create(:ci_pipeline_schedule, project: project, owner: admin) }
let!(:pipeline_schedule_variable1) { create(:ci_pipeline_schedule_variable, key: 'foo', value: 'foovalue', pipeline_schedule: pipeline_schedule_populated) }
let!(:pipeline_schedule_variable2) { create(:ci_pipeline_schedule_variable, key: 'bar', value: 'barvalue', pipeline_schedule: pipeline_schedule_populated) }
render_views
before(:all) do
clean_frontend_fixtures('pipeline_schedules/')
end
before do
sign_in(admin)
end
it 'pipeline_schedules/edit.html.raw' do |example|
get :edit,
namespace_id: project.namespace.to_param,
project_id: project,
id: pipeline_schedule.id
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
it 'pipeline_schedules/edit_with_variables.html.raw' do |example|
get :edit,
namespace_id: project.namespace.to_param,
project_id: project,
id: pipeline_schedule_populated.id
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
end
import {
setupPipelineVariableList,
insertRow,
removeRow,
} from '~/pipeline_schedules/setup_pipeline_variable_list';
describe('Pipeline Variable List', () => {
let $markup;
describe('insertRow', () => {
it('should insert another row', () => {
$markup = $(`<div>
<li class="js-row">
<input>
<textarea></textarea>
</li>
</div>`);
insertRow($markup.find('.js-row'));
expect($markup.find('.js-row').length).toBe(2);
});
it('should clear `data-is-persisted` on cloned row', () => {
$markup = $(`<div>
<li class="js-row" data-is-persisted="true"></li>
</div>`);
insertRow($markup.find('.js-row'));
const $lastRow = $markup.find('.js-row').last();
expect($lastRow.attr('data-is-persisted')).toBe(undefined);
});
it('should clear inputs on cloned row', () => {
$markup = $(`<div>
<li class="js-row">
<input value="foo">
<textarea>bar</textarea>
</li>
</div>`);
insertRow($markup.find('.js-row'));
const $lastRow = $markup.find('.js-row').last();
expect($lastRow.find('input').val()).toBe('');
expect($lastRow.find('textarea').val()).toBe('');
});
});
describe('removeRow', () => {
it('should remove dynamic row', () => {
$markup = $(`<div>
<li class="js-row">
<input>
<textarea></textarea>
</li>
</div>`);
removeRow($markup.find('.js-row'));
expect($markup.find('.js-row').length).toBe(0);
});
it('should hide and mark to destroy with already persisted rows', () => {
$markup = $(`<div>
<li class="js-row" data-is-persisted="true">
<input class="js-destroy-input">
</li>
</div>`);
const $row = $markup.find('.js-row');
removeRow($row);
expect($row.find('.js-destroy-input').val()).toBe('1');
expect($markup.find('.js-row').length).toBe(1);
});
});
describe('setupPipelineVariableList', () => {
beforeEach(() => {
$markup = $(`<form>
<li class="js-row">
<input class="js-user-input" name="schedule[variables_attributes][][key]">
<textarea class="js-user-input" name="schedule[variables_attributes][][value]"></textarea>
<button class="js-row-remove-button"></button>
<button class="js-row-add-button"></button>
</li>
</form>`);
setupPipelineVariableList($markup);
});
it('should remove the row when clicking the remove button', () => {
$markup.find('.js-row-remove-button').trigger('click');
expect($markup.find('.js-row').length).toBe(0);
});
it('should add another row when editing the last rows key input', () => {
const $row = $markup.find('.js-row');
$row.find('input.js-user-input')
.val('foo')
.trigger('input');
expect($markup.find('.js-row').length).toBe(2);
});
it('should add another row when editing the last rows value textarea', () => {
const $row = $markup.find('.js-row');
$row.find('textarea.js-user-input')
.val('foo')
.trigger('input');
expect($markup.find('.js-row').length).toBe(2);
});
it('should remove empty row after blurring', () => {
const $row = $markup.find('.js-row');
$row.find('input.js-user-input')
.val('foo')
.trigger('input');
expect($markup.find('.js-row').length).toBe(2);
$row.find('input.js-user-input')
.val('')
.trigger('input')
.trigger('blur');
expect($markup.find('.js-row').length).toBe(1);
});
it('should clear out the `name` attribute on the inputs for the last empty row on form submission (avoid BE validation)', () => {
const $row = $markup.find('.js-row');
expect($row.find('input').attr('name')).toBe('schedule[variables_attributes][][key]');
expect($row.find('textarea').attr('name')).toBe('schedule[variables_attributes][][value]');
$markup.filter('form').submit();
expect($row.find('input').attr('name')).toBe('');
expect($row.find('textarea').attr('name')).toBe('');
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment