Skip to content

Commit 5cde45b

Browse files
authored
Merge pull request #36174 from nextcloud/enh/noid/improve-applicable-ext-storage/stable25
[stable25] Improve saving applicable users in ext storage
2 parents b6d850d + a86f632 commit 5cde45b

File tree

6 files changed

+155
-32
lines changed

6 files changed

+155
-32
lines changed

apps/files_external/css/settings.css

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/files_external/css/settings.css.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/files_external/css/settings.scss

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
}
1212

1313
#externalStorage td {
14-
& > input, & > select {
14+
& > input:not(.applicableToAllUsers), & > select {
1515
width: 100%;
1616
}
1717
}
@@ -101,6 +101,10 @@
101101
background-color: rgba(134, 255, 110, 0.9);
102102
}
103103

104+
#externalStorage td.applicable label {
105+
display: inline-flex;
106+
align-items: center;
107+
}
104108

105109
#externalStorage td.applicable div.chzn-container {
106110
position: relative;

apps/files_external/js/settings.js

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,28 @@ function getSelection($row) {
2424
return values;
2525
}
2626

27+
function getSelectedApplicable($row) {
28+
var users = [];
29+
var groups = [];
30+
var multiselect = getSelection($row);
31+
$.each(multiselect, function(index, value) {
32+
// FIXME: don't rely on string parts to detect groups...
33+
var pos = (value.indexOf)?value.indexOf('(group)'): -1;
34+
if (pos !== -1) {
35+
groups.push(value.substr(0, pos));
36+
} else {
37+
users.push(value);
38+
}
39+
});
40+
41+
// FIXME: this should be done in the multiselect change event instead
42+
$row.find('.applicable')
43+
.data('applicable-groups', groups)
44+
.data('applicable-users', users);
45+
46+
return { users, groups };
47+
}
48+
2749
function highlightBorder($element, highlight) {
2850
$element.toggleClass('warning-input', highlight);
2951
return highlight;
@@ -56,7 +78,7 @@ function highlightInput($input) {
5678
* @param {Array<Object>} array of jQuery elements
5779
* @param {number} userListLimit page size for result list
5880
*/
59-
function addSelect2 ($elements, userListLimit) {
81+
function initApplicableUsersMultiselect($elements, userListLimit) {
6082
var escapeHTML = function (text) {
6183
return text.toString()
6284
.split('&').join('&amp;')
@@ -68,8 +90,8 @@ function addSelect2 ($elements, userListLimit) {
6890
if (!$elements.length) {
6991
return;
7092
}
71-
$elements.select2({
72-
placeholder: t('files_external', 'All users. Type to select user or group.'),
93+
return $elements.select2({
94+
placeholder: t('files_external', 'Type to select user or group.'),
7395
allowClear: true,
7496
multiple: true,
7597
toggleSelect: true,
@@ -167,6 +189,8 @@ function addSelect2 ($elements, userListLimit) {
167189
$div.avatar($div.data('name'),32);
168190
}
169191
});
192+
}).on('change', function(event) {
193+
highlightBorder($(event.target).closest('.applicableUsersContainer').find('.select2-choices'), !event.val.length);
170194
});
171195
}
172196

@@ -721,6 +745,8 @@ MountConfigListView.prototype = _.extend({
721745

722746
this.$el.on('change', '.selectBackend', _.bind(this._onSelectBackend, this));
723747
this.$el.on('change', '.selectAuthMechanism', _.bind(this._onSelectAuthMechanism, this));
748+
749+
this.$el.on('change', '.applicableToAllUsers', _.bind(this._onChangeApplicableToAllUsers, this));
724750
},
725751

726752
_onChange: function(event) {
@@ -746,6 +772,7 @@ MountConfigListView.prototype = _.extend({
746772

747773
var onCompletion = jQuery.Deferred();
748774
$tr = this.newStorage(storageConfig, onCompletion);
775+
$tr.find('.applicableToAllUsers').prop('checked', false).trigger('change');
749776
onCompletion.resolve();
750777

751778
$tr.find('td.configuration').children().not('[type=hidden]').first().focus();
@@ -764,6 +791,19 @@ MountConfigListView.prototype = _.extend({
764791
this.saveStorageConfig($tr);
765792
},
766793

794+
_onChangeApplicableToAllUsers: function(event) {
795+
var $target = $(event.target);
796+
var $tr = $target.closest('tr');
797+
var checked = $target.is(':checked');
798+
799+
$tr.find('.applicableUsersContainer').toggleClass('hidden', checked);
800+
if (!checked) {
801+
$tr.find('.applicableUsers').select2('val', '', true);
802+
}
803+
804+
this.saveStorageConfig($tr);
805+
},
806+
767807
/**
768808
* Configure the storage config with a new authentication mechanism
769809
*
@@ -818,7 +858,7 @@ MountConfigListView.prototype = _.extend({
818858
$tr.removeAttr('id');
819859
$tr.find('select#selectBackend');
820860
if (!deferAppend) {
821-
addSelect2($tr.find('.applicableUsers'), this._userListLimit);
861+
initApplicableUsersMultiselect($tr.find('.applicableUsers'), this._userListLimit);
822862
}
823863

824864
if (storageConfig.id) {
@@ -890,7 +930,14 @@ MountConfigListView.prototype = _.extend({
890930
})
891931
);
892932
}
893-
$tr.find('.applicableUsers').val(applicable).trigger('change');
933+
if (applicable.length) {
934+
$tr.find('.applicableUsers').val(applicable).trigger('change')
935+
$tr.find('.applicableUsersContainer').removeClass('hidden');
936+
} else {
937+
// applicable to all
938+
$tr.find('.applicableUsersContainer').addClass('hidden');
939+
}
940+
$tr.find('.applicableToAllUsers').prop('checked', !applicable.length);
894941

895942
var priorityEl = $('<input type="hidden" class="priority" value="' + backend.priority + '" />');
896943
$tr.append(priorityEl);
@@ -967,7 +1014,7 @@ MountConfigListView.prototype = _.extend({
9671014
}
9681015
$rows = $rows.add($tr);
9691016
});
970-
addSelect2(self.$el.find('.applicableUsers'), this._userListLimit);
1017+
initApplicableUsersMultiselect(self.$el.find('.applicableUsers'), this._userListLimit);
9711018
self.$el.find('tr#addMountPoint').before($rows);
9721019
var mainForm = $('#files_external');
9731020
if (result.length === 0 && mainForm.attr('data-can-create') === 'false') {
@@ -1007,7 +1054,7 @@ MountConfigListView.prototype = _.extend({
10071054
}
10081055
$rows = $rows.add($tr);
10091056
});
1010-
addSelect2($rows.find('.applicableUsers'), this._userListLimit);
1057+
initApplicableUsersMultiselect($rows.find('.applicableUsers'), this._userListLimit);
10111058
self.$el.find('tr#addMountPoint').before($rows);
10121059
onCompletion.resolve();
10131060
onLoaded2.resolve();
@@ -1126,24 +1173,25 @@ MountConfigListView.prototype = _.extend({
11261173

11271174
// gather selected users and groups
11281175
if (!this._isPersonal) {
1129-
var groups = [];
1130-
var users = [];
1131-
var multiselect = getSelection($tr);
1132-
$.each(multiselect, function(index, value) {
1133-
var pos = (value.indexOf)?value.indexOf('(group)'): -1;
1134-
if (pos !== -1) {
1135-
groups.push(value.substr(0, pos));
1136-
} else {
1137-
users.push(value);
1138-
}
1139-
});
1140-
// FIXME: this should be done in the multiselect change event instead
1141-
$tr.find('.applicable')
1142-
.data('applicable-groups', groups)
1143-
.data('applicable-users', users);
1176+
var multiselect = getSelectedApplicable($tr);
1177+
var users = multiselect.users || [];
1178+
var groups = multiselect.groups || [];
1179+
var isApplicableToAllUsers = $tr.find('.applicableToAllUsers').is(':checked');
1180+
1181+
if (isApplicableToAllUsers) {
1182+
storage.applicableUsers = [];
1183+
storage.applicableGroups = [];
1184+
} else {
1185+
storage.applicableUsers = users;
1186+
storage.applicableGroups = groups;
11441187

1145-
storage.applicableUsers = users;
1146-
storage.applicableGroups = groups;
1188+
if (!storage.applicableUsers.length && !storage.applicableGroups.length) {
1189+
if (!storage.errors) {
1190+
storage.errors = {};
1191+
}
1192+
storage.errors['requiredApplicable'] = true;
1193+
}
1194+
}
11471195

11481196
storage.priority = parseInt($tr.find('input.priority').val() || '100', 10);
11491197
}

apps/files_external/templates/settings.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,10 @@ function writeParameterInput($parameter, $options, $classes = []) {
169169
<td class="configuration"></td>
170170
<?php if ($_['visibilityType'] === BackendService::VISIBILITY_ADMIN): ?>
171171
<td class="applicable" align="right">
172-
<input type="hidden" class="applicableUsers" style="width:20em;" value="" />
172+
<label><input type="checkbox" class="applicableToAllUsers" checked="" /><?php p($l->t('All users')); ?></label>
173+
<div class="applicableUsersContainer">
174+
<input type="hidden" class="applicableUsers" style="width:20em;" value="" />
175+
</div>
173176
</td>
174177
<?php endif; ?>
175178
<td class="mountOptionsToggle hidden">

apps/files_external/tests/js/settingsSpec.js

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ describe('OCA.Files_External.Settings tests', function() {
3535

3636
beforeEach(function() {
3737
clock = sinon.useFakeTimers();
38+
select2ApplicableUsers = [];
3839
select2Stub = sinon.stub($.fn, 'select2').callsFake(function(args) {
3940
if (args === 'val') {
4041
return select2ApplicableUsers;
4142
}
4243
return {
43-
on: function() {}
44+
on: function() { return this; }
4445
};
4546
});
4647

@@ -63,6 +64,7 @@ describe('OCA.Files_External.Settings tests', function() {
6364
'<td class="authentication"></td>' +
6465
'<td class="configuration"></td>' +
6566
'<td class="applicable">' +
67+
'<input type="checkbox" class="applicableToAllUsers">' +
6668
'<input type="hidden" class="applicableUsers">' +
6769
'</td>' +
6870
'<td class="mountOptionsToggle">'+
@@ -172,6 +174,7 @@ describe('OCA.Files_External.Settings tests', function() {
172174

173175
function selectBackend(backendName) {
174176
view.$el.find('.selectBackend:first').val(backendName).trigger('change');
177+
view.$el.find('.applicableToAllUsers').prop('checked', true).trigger('change');
175178
}
176179

177180
beforeEach(function() {
@@ -255,6 +258,59 @@ describe('OCA.Files_External.Settings tests', function() {
255258

256259
// TODO: respond and check data-id
257260
});
261+
it('saves storage with applicable users', function() {
262+
var $field1 = $tr.find('input[data-parameter=field1]');
263+
expect($field1.length).toEqual(1);
264+
$field1.val('test');
265+
$field1.trigger(new $.Event('keyup', {keyCode: 97}));
266+
267+
$tr.find('.applicableToAllUsers').prop('checked', false).trigger('change');
268+
select2ApplicableUsers = ['user1', 'user2', 'group1(group)', 'group2(group)'];
269+
270+
var $saveButton = $tr.find('td.save .icon-checkmark');
271+
$saveButton.click();
272+
273+
expect(fakeServer.requests.length).toEqual(1);
274+
var request = fakeServer.requests[0];
275+
expect(request.url).toEqual(OC.getRootPath() + '/index.php/apps/files_external/globalstorages');
276+
expect(JSON.parse(request.requestBody)).toEqual({
277+
backend: '\\OC\\TestBackend',
278+
authMechanism: 'mechanism1',
279+
backendOptions: {
280+
'field1': 'test',
281+
'field2': ''
282+
},
283+
mountPoint: 'TestBackend',
284+
priority: 11,
285+
applicableUsers: ['user1', 'user2'],
286+
applicableGroups: ['group1', 'group2'],
287+
mountOptions: {
288+
encrypt: true,
289+
previews: true,
290+
enable_sharing: false,
291+
filesystem_check_changes: 1,
292+
encoding_compatibility: false,
293+
readonly: false,
294+
},
295+
testOnly: true
296+
});
297+
298+
// TODO: respond and check data-id
299+
});
300+
it('does not saves storage without applicable users and unchecked all users checkbox', function() {
301+
var $field1 = $tr.find('input[data-parameter=field1]');
302+
expect($field1.length).toEqual(1);
303+
$field1.val('test');
304+
$field1.trigger(new $.Event('keyup', {keyCode: 97}));
305+
306+
$tr.find('.applicableToAllUsers').prop('checked', false).trigger('change');
307+
308+
var $saveButton = $tr.find('td.save .icon-checkmark');
309+
$saveButton.click();
310+
311+
expect(fakeServer.requests.length).toEqual(0);
312+
});
313+
258314
it('saves storage after closing mount options popovermenu', function() {
259315
$tr.find('.mountOptionsToggle .icon-more').click();
260316
$tr.find('[name=previews]').trigger(new $.Event('keyup', {keyCode: 97}));
@@ -279,6 +335,16 @@ describe('OCA.Files_External.Settings tests', function() {
279335
});
280336

281337
it('lists missing fields in storage errors', function() {
338+
$tr.find('.applicableToAllUsers').prop('checked', false).trigger('change');
339+
var storage = view.getStorageConfig($tr);
340+
341+
expect(storage.errors).toEqual({
342+
backendOptions: ['field_text', 'field_password'],
343+
requiredApplicable: true,
344+
});
345+
});
346+
347+
it('does not list applicable when all users checkbox is ticked', function() {
282348
var storage = view.getStorageConfig($tr);
283349

284350
expect(storage.errors).toEqual({
@@ -399,9 +465,6 @@ describe('OCA.Files_External.Settings tests', function() {
399465
});
400466
});
401467
});
402-
describe('applicable user list', function() {
403-
// TODO: test select2 retrieval logic
404-
});
405468
describe('allow user mounts section', function() {
406469
// TODO: test allowUserMounting section
407470
});

0 commit comments

Comments
 (0)