From ae367c7e97b99885c7cb1feadae22aa1bb6cb729 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Mon, 8 Feb 2016 17:02:05 +0100 Subject: [PATCH 1/3] Fix elementToFile to also return path when defined Fixes issue when opening the share dialog for a file inside the favorite list, and the file is from a subfolder --- apps/files/js/filelist.js | 4 ++++ apps/files/tests/js/filelistSpec.js | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 35999b5d0e..1a6f38d3d7 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -815,6 +815,10 @@ if (mountType) { data.mountType = mountType; } + var path = $el.attr('data-path'); + if (path) { + data.path = path; + } return data; }, diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index 1b2dd12213..0091a9ee6e 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -2521,6 +2521,12 @@ describe('OCA.Files.FileList tests', function() { expect(fileInfo.size).toEqual(12); expect(fileInfo.mimetype).toEqual('text/plain'); expect(fileInfo.type).toEqual('file'); + expect(fileInfo.path).not.toBeDefined(); + }); + it('adds path attribute if available', function() { + $tr.attr('data-path', '/subdir'); + var fileInfo = fileList.elementToFile($tr); + expect(fileInfo.path).toEqual('/subdir'); }); }); describe('new file menu', function() { From e378a757fffa3e43a798c0bce7d2d831912bcf75 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Mon, 8 Feb 2016 11:43:42 +0100 Subject: [PATCH 2/3] Add system tags filter section for files app --- apps/systemtags/appinfo/app.php | 14 + apps/systemtags/css/systemtagsfilelist.css | 29 +++ apps/systemtags/img/tag.png | Bin 0 -> 293 bytes apps/systemtags/img/tag.svg | 5 + apps/systemtags/js/app.js | 87 +++++++ apps/systemtags/js/filesplugin.js | 3 +- apps/systemtags/js/systemtagsfilelist.js | 240 ++++++++++++++++++ apps/systemtags/list.php | 25 ++ apps/systemtags/templates/list.php | 38 +++ .../tests/js/systemtagsfilelistSpec.js | 226 +++++++++++++++++ core/js/files/client.js | 73 +++++- core/js/systemtags/systemtagsinputfield.js | 4 + core/js/tests/specs/files/clientSpec.js | 154 +++++++++++ tests/karma.config.js | 1 + 14 files changed, 897 insertions(+), 2 deletions(-) create mode 100644 apps/systemtags/css/systemtagsfilelist.css create mode 100644 apps/systemtags/img/tag.png create mode 100644 apps/systemtags/img/tag.svg create mode 100644 apps/systemtags/js/systemtagsfilelist.js create mode 100644 apps/systemtags/list.php create mode 100644 apps/systemtags/templates/list.php create mode 100644 apps/systemtags/tests/js/systemtagsfilelistSpec.js diff --git a/apps/systemtags/appinfo/app.php b/apps/systemtags/appinfo/app.php index 0bb57e1227..6bcbae4d0d 100644 --- a/apps/systemtags/appinfo/app.php +++ b/apps/systemtags/appinfo/app.php @@ -39,9 +39,11 @@ $eventDispatcher->addListener( \OCP\Util::addScript('systemtags/systemtagscollection'); \OCP\Util::addScript('systemtags/systemtagsinputfield'); \OCP\Util::addScript('systemtags', 'app'); + \OCP\Util::addScript('systemtags', 'systemtagsfilelist'); \OCP\Util::addScript('systemtags', 'filesplugin'); \OCP\Util::addScript('systemtags', 'systemtagsinfoview'); \OCP\Util::addStyle('systemtags'); + \OCP\Util::addStyle('systemtags', 'systemtagsfilelist'); } ); @@ -73,3 +75,15 @@ $mapperListener = function(MapperEvent $event) use ($activityManager) { $eventDispatcher->addListener(MapperEvent::EVENT_ASSIGN, $mapperListener); $eventDispatcher->addListener(MapperEvent::EVENT_UNASSIGN, $mapperListener); + +$l = \OC::$server->getL10N('files_sharing'); + +\OCA\Files\App::getNavigationManager()->add( + array( + 'id' => 'systemtagsfilter', + 'appname' => 'systemtags', + 'script' => 'list.php', + 'order' => 9, + 'name' => $l->t('Tags') + ) +); diff --git a/apps/systemtags/css/systemtagsfilelist.css b/apps/systemtags/css/systemtagsfilelist.css new file mode 100644 index 0000000000..e8fb665e26 --- /dev/null +++ b/apps/systemtags/css/systemtagsfilelist.css @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ +#app-content-systemtagsfilter .select2-container { + width: 30%; +} + +#app-content-systemtagsfilter .select2-choices { + white-space: nowrap; + text-overflow: ellipsis; + background: #fff; + color: #555; + box-sizing: content-box; + border-radius: 3px; + border: 1px solid #ddd; + margin: 3px 3px 3px 0; + padding: 0; + min-height: auto; +} + +.nav-icon-systemtagsfilter { + background-image: url('../img/tag.svg'); +} diff --git a/apps/systemtags/img/tag.png b/apps/systemtags/img/tag.png new file mode 100644 index 0000000000000000000000000000000000000000..5f4767a6f461956689bc15b231cafe91e5690481 GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}NPtg>>;M1%flTUx4SfrRfhNk7 z1o;Ish)c=HDyV4a7}|Jv`bT8s<`ou~mRD3&*VZ>wM9eS(s!Q>7aSW-rmFs_9s6m0} zG8dnSj0VTg|NleY$GCo;l)Y=x8~#HP*++s`cgP0b{VQQIk?-Ry!wc-0GlDlPXyz~z zJyx>RyKm9Md`9)SV{O~!JyiWXTR*~k*#zem6>9zSmQM-(*t?IhO`lEmw`2NGkWW2b L{an^LB{Ts5S9eS3 literal 0 HcmV?d00001 diff --git a/apps/systemtags/img/tag.svg b/apps/systemtags/img/tag.svg new file mode 100644 index 0000000000..6024607dd0 --- /dev/null +++ b/apps/systemtags/img/tag.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/systemtags/js/app.js b/apps/systemtags/js/app.js index f55aa5c9a6..d28514358c 100644 --- a/apps/systemtags/js/app.js +++ b/apps/systemtags/js/app.js @@ -16,5 +16,92 @@ OCA.SystemTags = {}; } + OCA.SystemTags.App = { + + initFileList: function($el) { + if (this._fileList) { + return this._fileList; + } + + this._fileList = new OCA.SystemTags.FileList( + $el, + { + id: 'systemtags', + scrollContainer: $('#app-content'), + fileActions: this._createFileActions() + } + ); + + this._fileList.appName = t('systemtags', 'Tags'); + return this._fileList; + }, + + removeFileList: function() { + if (this._fileList) { + this._fileList.$fileList.empty(); + } + }, + + _createFileActions: function() { + // inherit file actions from the files app + var fileActions = new OCA.Files.FileActions(); + // note: not merging the legacy actions because legacy apps are not + // compatible with the sharing overview and need to be adapted first + fileActions.registerDefaultActions(); + fileActions.merge(OCA.Files.fileActions); + + if (!this._globalActionsInitialized) { + // in case actions are registered later + this._onActionsUpdated = _.bind(this._onActionsUpdated, this); + OCA.Files.fileActions.on('setDefault.app-systemtags', this._onActionsUpdated); + OCA.Files.fileActions.on('registerAction.app-systemtags', this._onActionsUpdated); + this._globalActionsInitialized = true; + } + + // when the user clicks on a folder, redirect to the corresponding + // folder in the files app instead of opening it directly + fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) { + OCA.Files.App.setActiveView('files', {silent: true}); + OCA.Files.App.fileList.changeDirectory(OC.joinPaths(context.$file.attr('data-path'), filename), true, true); + }); + fileActions.setDefault('dir', 'Open'); + return fileActions; + }, + + _onActionsUpdated: function(ev) { + if (!this._fileList) { + return; + } + + if (ev.action) { + this._fileList.fileActions.registerAction(ev.action); + } else if (ev.defaultAction) { + this._fileList.fileActions.setDefault( + ev.defaultAction.mime, + ev.defaultAction.name + ); + } + }, + + /** + * Destroy the app + */ + destroy: function() { + OCA.Files.fileActions.off('setDefault.app-systemtags', this._onActionsUpdated); + OCA.Files.fileActions.off('registerAction.app-systemtags', this._onActionsUpdated); + this.removeFileList(); + this._fileList = null; + delete this._globalActionsInitialized; + } + }; + })(); +$(document).ready(function() { + $('#app-content-systemtagsfilter').on('show', function(e) { + OCA.SystemTags.App.initFileList($(e.target)); + }); + $('#app-content-systemtagsfilter').on('hide', function() { + OCA.SystemTags.App.removeFileList(); + }); +}); diff --git a/apps/systemtags/js/filesplugin.js b/apps/systemtags/js/filesplugin.js index 471440c2e0..588037455a 100644 --- a/apps/systemtags/js/filesplugin.js +++ b/apps/systemtags/js/filesplugin.js @@ -23,7 +23,8 @@ OCA.SystemTags.FilesPlugin = { allowedLists: [ 'files', - 'favorites' + 'favorites', + 'systemtagsfilter' ], attach: function(fileList) { diff --git a/apps/systemtags/js/systemtagsfilelist.js b/apps/systemtags/js/systemtagsfilelist.js new file mode 100644 index 0000000000..56838018a2 --- /dev/null +++ b/apps/systemtags/js/systemtagsfilelist.js @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2016 Vincent Petry + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ +(function() { + /** + * @class OCA.SystemTags.FileList + * @augments OCA.Files.FileList + * + * @classdesc SystemTags file list. + * Contains a list of files filtered by system tags. + * + * @param $el container element with existing markup for the #controls + * and a table + * @param [options] map of options, see other parameters + * @param {Array.} [options.systemTagIds] array of system tag ids to + * filter by + */ + var FileList = function($el, options) { + this.initialize($el, options); + }; + FileList.prototype = _.extend({}, OCA.Files.FileList.prototype, + /** @lends OCA.SystemTags.FileList.prototype */ { + id: 'systemtagsfilter', + appName: t('systemtags', 'Tagged files'), + + /** + * Array of system tag ids to filter by + * + * @type Array. + */ + _systemTagIds: [], + + _clientSideSort: true, + _allowSelection: false, + + _filterField: null, + + /** + * @private + */ + initialize: function($el, options) { + OCA.Files.FileList.prototype.initialize.apply(this, arguments); + if (this.initialized) { + return; + } + + if (options && options.systemTagIds) { + this._systemTagIds = options.systemTagIds; + } + + OC.Plugins.attach('OCA.SystemTags.FileList', this); + + var $controls = this.$el.find('#controls').empty(); + + this._initFilterField($controls); + }, + + destroy: function() { + this.$filterField.remove(); + + OCA.Files.FileList.prototype.destroy.apply(this, arguments); + }, + + _initFilterField: function($container) { + this.$filterField = $(''); + $container.append(this.$filterField); + this.$filterField.select2({ + placeholder: t('systemtags', 'Select tags to filter by'), + allowClear: false, + multiple: true, + separator: ',', + query: _.bind(this._queryTagsAutocomplete, this), + + id: function(tag) { + return tag.id; + }, + + initSelection: function(element, callback) { + var val = $(element).val().trim(); + if (val) { + var tagIds = val.split(','), + tags = []; + + OC.SystemTags.collection.fetch({ + success: function() { + _.each(tagIds, function(tagId) { + var tag = OC.SystemTags.collection.get(tagId); + if (!_.isUndefined(tag)) { + tags.push(tag.toJSON()); + } + }); + + callback(tags); + } + }); + } else { + callback([]); + } + }, + + formatResult: function (tag) { + return OC.SystemTags.getDescriptiveTag(tag); + }, + + formatSelection: function (tag) { + return OC.SystemTags.getDescriptiveTag(tag)[0].outerHTML; + }, + + escapeMarkup: function(m) { + // prevent double markup escape + return m; + } + }); + this.$filterField.on('change', _.bind(this._onTagsChanged, this)); + return this.$filterField; + }, + + /** + * Autocomplete function for dropdown results + * + * @param {Object} query select2 query object + */ + _queryTagsAutocomplete: function(query) { + OC.SystemTags.collection.fetch({ + success: function() { + var results = OC.SystemTags.collection.filterByName(query.term); + + query.callback({ + results: _.invoke(results, 'toJSON') + }); + } + }); + }, + + /** + * Event handler for when the URL changed + */ + _onUrlChanged: function(e) { + if (e.dir) { + var tags = _.filter(e.dir.split('/'), function(val) { return val.trim() !== ''; }); + this.$filterField.select2('val', tags || []); + this._systemTagIds = tags; + this.reload(); + } + }, + + _onTagsChanged: function(ev) { + var val = $(ev.target).val().trim(); + if (val !== '') { + this._systemTagIds = val.split(','); + } else { + this._systemTagIds = []; + } + + this.$el.trigger(jQuery.Event('changeDirectory', { + dir: this._systemTagIds.join('/') + })); + this.reload(); + }, + + updateEmptyContent: function() { + var dir = this.getCurrentDirectory(); + if (dir === '/') { + // root has special permissions + if (!this._systemTagIds.length) { + // no tags selected + this.$el.find('#emptycontent').html('
' + + '

' + t('systemtags', 'Please select tags to filter by') + '

'); + } else { + // tags selected but no results + this.$el.find('#emptycontent').html('
' + + '

' + t('systemtags', 'No files found for the selected tags') + '

'); + } + this.$el.find('#emptycontent').toggleClass('hidden', !this.isEmpty); + this.$el.find('#filestable thead th').toggleClass('hidden', this.isEmpty); + } + else { + OCA.Files.FileList.prototype.updateEmptyContent.apply(this, arguments); + } + }, + + getDirectoryPermissions: function() { + return OC.PERMISSION_READ | OC.PERMISSION_DELETE; + }, + + updateStorageStatistics: function() { + // no op because it doesn't have + // storage info like free space / used space + }, + + reload: function() { + if (!this._systemTagIds.length) { + // don't reload + this.updateEmptyContent(); + this.setFiles([]); + return $.Deferred().resolve(); + } + + this._selectedFiles = {}; + this._selectionSummary.clear(); + if (this._currentFileModel) { + this._currentFileModel.off(); + } + this._currentFileModel = null; + this.$el.find('.select-all').prop('checked', false); + this.showMask(); + this._reloadCall = this.filesClient.getFilteredFiles( + { + systemTagIds: this._systemTagIds + }, + { + properties: this._getWebdavProperties() + } + ); + if (this._detailsView) { + // close sidebar + this._updateDetailsView(null); + } + var callBack = this.reloadCallback.bind(this); + return this._reloadCall.then(callBack, callBack); + }, + + reloadCallback: function(status, result) { + if (result) { + // prepend empty dir info because original handler + result.unshift({}); + } + + return OCA.Files.FileList.prototype.reloadCallback.call(this, status, result); + } + }); + + OCA.SystemTags.FileList = FileList; +})(); diff --git a/apps/systemtags/list.php b/apps/systemtags/list.php new file mode 100644 index 0000000000..dd4fe01e76 --- /dev/null +++ b/apps/systemtags/list.php @@ -0,0 +1,25 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +// Check if we are a user +OCP\User::checkLoggedIn(); + +$tmpl = new OCP\Template('systemtags', 'list', ''); +$tmpl->printPage(); diff --git a/apps/systemtags/templates/list.php b/apps/systemtags/templates/list.php new file mode 100644 index 0000000000..841ce7b5b6 --- /dev/null +++ b/apps/systemtags/templates/list.php @@ -0,0 +1,38 @@ +
+
+ + + + + + + + + + + + + + + + + +
+ + diff --git a/apps/systemtags/tests/js/systemtagsfilelistSpec.js b/apps/systemtags/tests/js/systemtagsfilelistSpec.js new file mode 100644 index 0000000000..ba41d347ca --- /dev/null +++ b/apps/systemtags/tests/js/systemtagsfilelistSpec.js @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2016 Vincent Petry + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +describe('OCA.SystemTags.FileList tests', function() { + var FileInfo = OC.Files.FileInfo; + var fileList; + + beforeEach(function() { + // init parameters and test table elements + $('#testArea').append( + '
' + + // init horrible parameters + '' + + '' + + '
' + + // dummy table + // TODO: at some point this will be rendered by the fileList class itself! + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '
Empty content message
' + + '
' + ); + }); + afterEach(function() { + fileList.destroy(); + fileList = undefined; + }); + + describe('filter field', function() { + var select2Stub, oldCollection, fetchTagsStub; + var $tagsField; + + beforeEach(function() { + fetchTagsStub = sinon.stub(OC.SystemTags.SystemTagsCollection.prototype, 'fetch'); + select2Stub = sinon.stub($.fn, 'select2'); + oldCollection = OC.SystemTags.collection; + OC.SystemTags.collection = new OC.SystemTags.SystemTagsCollection([ + { + id: '123', + name: 'abc' + }, + { + id: '456', + name: 'def' + } + ]); + + fileList = new OCA.SystemTags.FileList( + $('#app-content-container'), { + systemTagIds: [] + } + ); + $tagsField = fileList.$el.find('[name=tags]'); + }); + afterEach(function() { + select2Stub.restore(); + fetchTagsStub.restore(); + OC.SystemTags.collection = oldCollection; + }); + it('inits select2 on filter field', function() { + expect(select2Stub.calledOnce).toEqual(true); + }); + it('uses global system tags collection', function() { + var callback = sinon.stub(); + var opts = select2Stub.firstCall.args[0]; + + $tagsField.val('123'); + + opts.initSelection($tagsField, callback); + + expect(callback.notCalled).toEqual(true); + expect(fetchTagsStub.calledOnce).toEqual(true); + + fetchTagsStub.yieldTo('success', fetchTagsStub.thisValues[0]); + + expect(callback.calledOnce).toEqual(true); + expect(callback.lastCall.args[0]).toEqual([ + OC.SystemTags.collection.get('123').toJSON() + ]); + }); + it('fetches tag list from the global collection', function() { + var callback = sinon.stub(); + var opts = select2Stub.firstCall.args[0]; + + $tagsField.val('123'); + + opts.query({ + term: 'de', + callback: callback + }); + + expect(fetchTagsStub.calledOnce).toEqual(true); + expect(callback.notCalled).toEqual(true); + fetchTagsStub.yieldTo('success', fetchTagsStub.thisValues[0]); + + expect(callback.calledOnce).toEqual(true); + expect(callback.lastCall.args[0]).toEqual({ + results: [ + OC.SystemTags.collection.get('456').toJSON() + ] + }); + }); + it('reloads file list after selection', function() { + var reloadStub = sinon.stub(fileList, 'reload'); + $tagsField.val('456,123').change(); + expect(reloadStub.calledOnce).toEqual(true); + reloadStub.restore(); + }); + it('updates URL after selection', function() { + var handler = sinon.stub(); + fileList.$el.on('changeDirectory', handler); + $tagsField.val('456,123').change(); + + expect(handler.calledOnce).toEqual(true); + expect(handler.lastCall.args[0].dir).toEqual('456/123'); + }); + it('updates tag selection when url changed', function() { + fileList.$el.trigger(new $.Event('urlChanged', {dir: '456/123'})); + + expect(select2Stub.lastCall.args[0]).toEqual('val'); + expect(select2Stub.lastCall.args[1]).toEqual(['456', '123']); + }); + }); + + describe('loading results', function() { + var getFilteredFilesSpec, requestDeferred; + + beforeEach(function() { + requestDeferred = new $.Deferred(); + getFilteredFilesSpec = sinon.stub(OC.Files.Client.prototype, 'getFilteredFiles') + .returns(requestDeferred.promise()); + }); + afterEach(function() { + getFilteredFilesSpec.restore(); + }); + + it('renders empty message when no tags were set', function() { + fileList = new OCA.SystemTags.FileList( + $('#app-content-container'), { + systemTagIds: [] + } + ); + + fileList.reload(); + + expect(fileList.$el.find('#emptycontent').hasClass('hidden')).toEqual(false); + + expect(getFilteredFilesSpec.notCalled).toEqual(true); + }); + + it('render files', function() { + fileList = new OCA.SystemTags.FileList( + $('#app-content-container'), { + systemTagIds: ['123', '456'] + } + ); + + fileList.reload(); + + expect(getFilteredFilesSpec.calledOnce).toEqual(true); + expect(getFilteredFilesSpec.lastCall.args[0].systemTagIds).toEqual(['123', '456']); + + var testFiles = [new FileInfo({ + id: 1, + type: 'file', + name: 'One.txt', + mimetype: 'text/plain', + mtime: 123456789, + size: 12, + etag: 'abc', + permissions: OC.PERMISSION_ALL + }), new FileInfo({ + id: 2, + type: 'file', + name: 'Two.jpg', + mimetype: 'image/jpeg', + mtime: 234567890, + size: 12049, + etag: 'def', + permissions: OC.PERMISSION_ALL + }), new FileInfo({ + id: 3, + type: 'file', + name: 'Three.pdf', + mimetype: 'application/pdf', + mtime: 234560000, + size: 58009, + etag: '123', + permissions: OC.PERMISSION_ALL + }), new FileInfo({ + id: 4, + type: 'dir', + name: 'somedir', + mimetype: 'httpd/unix-directory', + mtime: 134560000, + size: 250, + etag: '456', + permissions: OC.PERMISSION_ALL + })]; + + requestDeferred.resolve(207, testFiles); + + expect(fileList.$el.find('#emptycontent').hasClass('hidden')).toEqual(true); + expect(fileList.$el.find('tbody>tr').length).toEqual(4); + }); + }); +}); diff --git a/core/js/files/client.js b/core/js/files/client.js index b736447d65..55a8e2c485 100644 --- a/core/js/files/client.js +++ b/core/js/files/client.js @@ -241,7 +241,7 @@ path = decodeURIComponent(path); - if (response.propStat.length === 1 && response.propStat[0].status !== 200) { + if (response.propStat.length === 0 || response.propStat[0].status !== 'HTTP/1.1 200 OK') { return null; } @@ -414,6 +414,77 @@ return promise; }, + /** + * Fetches a flat list of files filtered by a given filter criteria. + * (currently only system tags is supported) + * + * @param {Object} filter filter criteria + * @param {Object} [filter.systemTagIds] list of system tag ids to filter by + * @param {Object} [options] options + * @param {Array} [options.properties] list of Webdav properties to retrieve + * + * @return {Promise} promise + */ + getFilteredFiles: function(filter, options) { + options = options || {}; + var self = this; + var deferred = $.Deferred(); + var promise = deferred.promise(); + var properties; + if (_.isUndefined(options.properties)) { + properties = this.getPropfindProperties(); + } else { + properties = options.properties; + } + + if (!filter || !filter.systemTagIds || !filter.systemTagIds.length) { + throw 'Missing filter argument'; + } + + var headers = _.extend({}, this._defaultHeaders); + // root element with namespaces + var body = '\n'; + _.each(properties, function(prop) { + var property = self._client.parseClarkNotation(prop); + body += ' <' + self._client.xmlNamespaces[property.namespace] + ':' + property.name + ' />\n'; + }); + + body += ' \n'; + + // rules block + body += ' \n'; + _.each(filter.systemTagIds, function(systemTagIds) { + body += ' ' + escapeHTML(systemTagIds) + '\n'; + }); + body += ' \n'; + + // end of root + body += '\n'; + + this._client.request( + 'REPORT', + this._buildUrl(), + headers, + body + ).then(function(result) { + if (self._isSuccessStatus(result.status)) { + var results = self._parseResult(result.body); + deferred.resolve(result.status, results); + } else { + deferred.reject(result.status); + } + }); + return promise; + }, + /** * Returns the file info of a given path. * diff --git a/core/js/systemtags/systemtagsinputfield.js b/core/js/systemtags/systemtagsinputfield.js index 48fc98c618..a64e538610 100644 --- a/core/js/systemtags/systemtagsinputfield.js +++ b/core/js/systemtags/systemtagsinputfield.js @@ -425,6 +425,10 @@ } }, + getValues: function() { + this.$tagsField.select2('val'); + }, + setValues: function(values) { this.$tagsField.select2('val', values); }, diff --git a/core/js/tests/specs/files/clientSpec.js b/core/js/tests/specs/files/clientSpec.js index b945e1bb4d..7673ec6e0f 100644 --- a/core/js/tests/specs/files/clientSpec.js +++ b/core/js/tests/specs/files/clientSpec.js @@ -318,6 +318,160 @@ describe('OC.Files.Client tests', function() { }); }); + describe('file filtering', function() { + + // TODO: switch this to the already parsed structure + var folderContentsXml = dav.Client.prototype.parseMultiStatus( + '' + + '' + + makeResponseBlock( + '/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/', + { + 'd:getlastmodified': 'Fri, 10 Jul 2015 10:00:05 GMT', + 'd:getetag': '"56cfcabd79abb"', + 'd:resourcetype': '', + 'oc:id': '00000011oc2d13a6a068', + 'oc:fileid': '11', + 'oc:permissions': 'RDNVCK', + 'oc:size': '120' + }, + [ + 'd:getcontenttype', + 'd:getcontentlength' + ] + ) + + makeResponseBlock( + '/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt', + { + 'd:getlastmodified': 'Fri, 10 Jul 2015 13:38:05 GMT', + 'd:getetag': '"559fcabd79a38"', + 'd:getcontenttype': 'text/plain', + 'd:getcontentlength': 250, + 'd:resourcetype': '', + 'oc:id': '00000051oc2d13a6a068', + 'oc:fileid': '51', + 'oc:permissions': 'RDNVW' + }, + [ + 'oc:size', + ] + ) + + makeResponseBlock( + '/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/sub', + { + 'd:getlastmodified': 'Fri, 10 Jul 2015 14:00:00 GMT', + 'd:getetag': '"66cfcabd79abb"', + 'd:resourcetype': '', + 'oc:id': '00000015oc2d13a6a068', + 'oc:fileid': '15', + 'oc:permissions': 'RDNVCK', + 'oc:size': '100' + }, + [ + 'd:getcontenttype', + 'd:getcontentlength' + ] + ) + + '' + ); + + it('sends REPORT with filter information', function() { + client.getFilteredFiles({ + systemTagIds: ['123', '456'] + }); + + expect(requestStub.calledOnce).toEqual(true); + expect(requestStub.lastCall.args[0]).toEqual('REPORT'); + expect(requestStub.lastCall.args[1]).toEqual(baseUrl); + + var body = requestStub.lastCall.args[3]; + var doc = (new window.DOMParser()).parseFromString( + body, + 'application/xml' + ); + + var ns = 'http://owncloud.org/ns'; + expect(doc.documentElement.localName).toEqual('filter-files'); + expect(doc.documentElement.namespaceURI).toEqual(ns); + + var filterRoots = doc.getElementsByTagNameNS(ns, 'filter-rules'); + var rulesList = filterRoots[0] = doc.getElementsByTagNameNS(ns, 'systemtag'); + expect(rulesList.length).toEqual(2); + expect(rulesList[0].localName).toEqual('systemtag'); + expect(rulesList[0].namespaceURI).toEqual(ns); + expect(rulesList[0].textContent).toEqual('123'); + expect(rulesList[1].localName).toEqual('systemtag'); + expect(rulesList[1].namespaceURI).toEqual(ns); + expect(rulesList[1].textContent).toEqual('456'); + }); + it('sends REPORT with explicit properties to filter file list', function() { + client.getFilteredFiles({ + systemTagIds: ['123', '456'] + }); + + expect(requestStub.calledOnce).toEqual(true); + expect(requestStub.lastCall.args[0]).toEqual('REPORT'); + expect(requestStub.lastCall.args[1]).toEqual(baseUrl); + + var props = getRequestedProperties(requestStub.lastCall.args[3]); + expect(props).toContain('{DAV:}getlastmodified'); + expect(props).toContain('{DAV:}getcontentlength'); + expect(props).toContain('{DAV:}getcontenttype'); + expect(props).toContain('{DAV:}getetag'); + expect(props).toContain('{DAV:}resourcetype'); + expect(props).toContain('{http://owncloud.org/ns}fileid'); + expect(props).toContain('{http://owncloud.org/ns}size'); + expect(props).toContain('{http://owncloud.org/ns}permissions'); + }); + it('parses the result list into a FileInfo array', function() { + var promise = client.getFilteredFiles({ + systemTagIds: ['123', '456'] + }); + + expect(requestStub.calledOnce).toEqual(true); + + requestDeferred.resolve({ + status: 207, + body: folderContentsXml + }); + + promise.then(function(status, response) { + expect(status).toEqual(207); + expect(_.isArray(response)).toEqual(true); + + // returns all entries + expect(response.length).toEqual(3); + + // file entry + var info = response[0]; + expect(info instanceof OC.Files.FileInfo).toEqual(true); + expect(info.id).toEqual(11); + + // file entry + var info = response[1]; + expect(info instanceof OC.Files.FileInfo).toEqual(true); + expect(info.id).toEqual(51); + + // sub entry + info = response[2]; + expect(info instanceof OC.Files.FileInfo).toEqual(true); + expect(info.id).toEqual(15); + }); + }); + it('throws exception if arguments are missing', function() { + var thrown = null; + try { + client.getFilteredFiles({ + systemTagIds: [] + }); + } catch (e) { + thrown = true; + } + + expect(thrown).toEqual(true); + }); + }); + describe('file info', function() { var responseXml = dav.Client.prototype.parseMultiStatus( '' + diff --git a/tests/karma.config.js b/tests/karma.config.js index 2b569fb758..111af7a155 100644 --- a/tests/karma.config.js +++ b/tests/karma.config.js @@ -101,6 +101,7 @@ module.exports = function(config) { // need to enforce loading order... 'apps/systemtags/js/app.js', 'apps/systemtags/js/systemtagsinfoview.js', + 'apps/systemtags/js/systemtagsfilelist.js', 'apps/systemtags/js/filesplugin.js' ], testFiles: ['apps/systemtags/tests/js/**/*.js'] From 847a2426b8614b8fd5f7d05175dc4885a8a45e9c Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 9 Feb 2016 11:22:05 +0100 Subject: [PATCH 3/3] Fix unit tests for system tag filter section --- .../tests/controller/ViewControllerTest.php | 25 ++++++++++++++----- apps/systemtags/appinfo/app.php | 2 +- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/apps/files/tests/controller/ViewControllerTest.php b/apps/files/tests/controller/ViewControllerTest.php index b5df3cfc90..28e2f0c2c9 100644 --- a/apps/files/tests/controller/ViewControllerTest.php +++ b/apps/files/tests/controller/ViewControllerTest.php @@ -168,6 +168,15 @@ class ViewControllerTest extends TestCase { 'icon' => '', ], 2 => [ + 'id' => 'systemtagsfilter', + 'appname' => 'systemtags', + 'script' => 'list.php', + 'order' => 9, + 'name' => new \OC_L10N_String(new \OC_L10N('systemtags'), 'Tags', []), + 'active' => false, + 'icon' => '', + ], + 3 => [ 'id' => 'sharingin', 'appname' => 'files_sharing', 'script' => 'list.php', @@ -176,7 +185,7 @@ class ViewControllerTest extends TestCase { 'active' => false, 'icon' => '', ], - 3 => [ + 4 => [ 'id' => 'sharingout', 'appname' => 'files_sharing', 'script' => 'list.php', @@ -185,7 +194,7 @@ class ViewControllerTest extends TestCase { 'active' => false, 'icon' => '', ], - 4 => [ + 5 => [ 'id' => 'sharinglinks', 'appname' => 'files_sharing', 'script' => 'list.php', @@ -194,7 +203,7 @@ class ViewControllerTest extends TestCase { 'active' => false, 'icon' => '', ], - 5 => [ + 6 => [ 'id' => 'trashbin', 'appname' => 'files_trashbin', 'script' => 'list.php', @@ -227,18 +236,22 @@ class ViewControllerTest extends TestCase { 'content' => null, ], 2 => [ - 'id' => 'sharingin', + 'id' => 'systemtagsfilter', 'content' => null, ], 3 => [ - 'id' => 'sharingout', + 'id' => 'sharingin', 'content' => null, ], 4 => [ - 'id' => 'sharinglinks', + 'id' => 'sharingout', 'content' => null, ], 5 => [ + 'id' => 'sharinglinks', + 'content' => null, + ], + 6 => [ 'id' => 'trashbin', 'content' => null, ], diff --git a/apps/systemtags/appinfo/app.php b/apps/systemtags/appinfo/app.php index 6bcbae4d0d..00663d5082 100644 --- a/apps/systemtags/appinfo/app.php +++ b/apps/systemtags/appinfo/app.php @@ -76,7 +76,7 @@ $mapperListener = function(MapperEvent $event) use ($activityManager) { $eventDispatcher->addListener(MapperEvent::EVENT_ASSIGN, $mapperListener); $eventDispatcher->addListener(MapperEvent::EVENT_UNASSIGN, $mapperListener); -$l = \OC::$server->getL10N('files_sharing'); +$l = \OC::$server->getL10N('systemtags'); \OCA\Files\App::getNavigationManager()->add( array(