From 15636efe8959106a7b8de4520e908c24720b941f Mon Sep 17 00:00:00 2001 From: David Manthey Date: Wed, 27 Jul 2022 09:30:33 -0400 Subject: [PATCH] Inheritable config files. --- girder/girder_large_image/__init__.py | 9 + girder/girder_large_image/constants.py | 1 + girder/girder_large_image/rest/__init__.py | 37 +++- .../web_client/templates/largeImageConfig.pug | 11 ++ .../web_client/views/configView.js | 46 ++++- girder/test_girder/test_large_image.py | 65 ++++++- .../web_client_specs/largeImageSpec.js | 162 ++++++++++-------- 7 files changed, 247 insertions(+), 84 deletions(-) diff --git a/girder/girder_large_image/__init__.py b/girder/girder_large_image/__init__.py index 98e441eb6..af8009581 100644 --- a/girder/girder_large_image/__init__.py +++ b/girder/girder_large_image/__init__.py @@ -27,6 +27,7 @@ from girder.constants import AccessType from girder.exceptions import ValidationException from girder.models.file import File +from girder.models.folder import Folder from girder.models.item import Item from girder.models.notification import Notification from girder.models.setting import Setting @@ -286,6 +287,14 @@ def validateDefaultViewer(doc): doc['value'] = str(doc['value']).strip() +@setting_utilities.validator(constants.PluginSettings.LARGE_IMAGE_CONFIG_FOLDER) +def validateFolder(doc): + if not doc.get('value', None): + doc['value'] = None + else: + Folder().load(doc['value'], force=True, exc=True) + + # Defaults # Defaults that have fixed values can just be added to the system defaults diff --git a/girder/girder_large_image/constants.py b/girder/girder_large_image/constants.py index e8812f057..942c82e8c 100644 --- a/girder/girder_large_image/constants.py +++ b/girder/girder_large_image/constants.py @@ -30,3 +30,4 @@ class PluginSettings: LARGE_IMAGE_MAX_THUMBNAIL_FILES = 'large_image.max_thumbnail_files' LARGE_IMAGE_MAX_SMALL_IMAGE_SIZE = 'large_image.max_small_image_size' LARGE_IMAGE_AUTO_USE_ALL_FILES = 'large_image.auto_use_all_files' + LARGE_IMAGE_CONFIG_FOLDER = 'large_image.config_folder' diff --git a/girder/girder_large_image/rest/__init__.py b/girder/girder_large_image/rest/__init__.py index 53922b760..23b400047 100644 --- a/girder/girder_large_image/rest/__init__.py +++ b/girder/girder_large_image/rest/__init__.py @@ -9,6 +9,9 @@ from girder.models.folder import Folder from girder.models.group import Group from girder.models.item import Item +from girder.models.setting import Setting + +from .. import constants def addSystemEndpoints(apiRoot): @@ -96,10 +99,11 @@ def adjustConfigForUser(config, user): ) @boundHandler() def getYAMLConfigFile(self, folder, name): + addConfig = None user = self.getCurrentUser() + last = False while folder: - item = Item().findOne({'folderId': folder['_id'], 'name': name}, - user=user, level=AccessType.READ) + item = Item().findOne({'folderId': folder['_id'], 'name': name}) if item: for file in Item().childFiles(item): if file['size'] > 10 * 1024 ** 2: @@ -110,13 +114,28 @@ def getYAMLConfigFile(self, folder, name): # combine and adjust config values based on current user if isinstance(config, dict) and 'access' in config or 'group' in config: config = adjustConfigForUser(config, user) - return config + if addConfig and isinstance(config, dict): + config = _mergeDictionaries(config, addConfig) + if not isinstance(config, dict) or config.get('__inherit__') is not True: + return config + config.pop('__inherit__') + addConfig = config + if last: + break if folder['parentCollection'] != 'folder': - if folder['name'] == '.config': - break - folder = Folder().findOne({ - 'parentId': folder['parentId'], - 'parentCollection': folder['parentCollection'], - 'name': '.config'}) + if folder['name'] != '.config': + folder = Folder().findOne({ + 'parentId': folder['parentId'], + 'parentCollection': folder['parentCollection'], + 'name': '.config'}) + else: + last = 'setting' + if not folder or last == 'setting': + folderId = Setting().get(constants.PluginSettings.LARGE_IMAGE_CONFIG_FOLDER) + if not folderId: + break + folder = Folder().load(folderId, force=True) + last = True else: folder = Folder().load(folder['parentId'], user=user, level=AccessType.READ) + return addConfig diff --git a/girder/girder_large_image/web_client/templates/largeImageConfig.pug b/girder/girder_large_image/web_client/templates/largeImageConfig.pug index f912ecb60..5abbc9d42 100644 --- a/girder/girder_large_image/web_client/templates/largeImageConfig.pug +++ b/girder/girder_large_image/web_client/templates/largeImageConfig.pug @@ -82,6 +82,17 @@ form#g-large-image-form(role="form") | Details to show on item pages for admins and owners of the images. input.input-sm.form-control.g-large-image-show-item-extra-admin( type="text", value=settings['large_image.show_item_extra_admin'], placeholder=detailplaceholder, title=detailtitle) + .form-group + label(for="g-large-image-config-folder") Configuration Folder + p.g-large-image-description + | If a configuration folder is specified, configuration files stored there are used for the entire system if they are not overridden by local configuration files. + .input-group.input-group-sm + input#g-large-image-config-folder.form-control.input-sm( + type="text", value=settings['large_image.config_folder'] || '', + title="A folder to store configuration files.") + .input-group-btn + button.g-open-browser.btn.btn-default(type="button") + i.icon-folder-open .form-group label | Maximum number of thumbnail files to save per item diff --git a/girder/girder_large_image/web_client/views/configView.js b/girder/girder_large_image/web_client/views/configView.js index 85c902fa9..e9c1ae500 100644 --- a/girder/girder_large_image/web_client/views/configView.js +++ b/girder/girder_large_image/web_client/views/configView.js @@ -1,9 +1,11 @@ +import $ from 'jquery'; import View from '@girder/core/views/View'; -import PluginConfigBreadcrumbWidget from '@girder/core/views/widgets/PluginConfigBreadcrumbWidget'; -import { restRequest } from '@girder/core/rest'; import { AccessType } from '@girder/core/constants'; import events from '@girder/core/events'; +import { restRequest } from '@girder/core/rest'; +import BrowserWidget from '@girder/core/views/widgets/BrowserWidget'; +import PluginConfigBreadcrumbWidget from '@girder/core/views/widgets/PluginConfigBreadcrumbWidget'; import ConfigViewTemplate from '../templates/largeImageConfig.pug'; import '../stylesheets/largeImageConfig.styl'; @@ -52,14 +54,47 @@ var ConfigView = View.extend({ }, { key: 'large_image.show_item_extra_admin', value: this.$('.g-large-image-show-item-extra-admin').val() + }, { + key: 'large_image.config_folder', + value: (this.$('#g-large-image-config-folder').val() || '').split(' ')[0] }]); - } + }, + 'click .g-open-browser': '_openBrowser' }, initialize: function () { ConfigView.getSettings((settings) => { this.settings = settings; this.render(); }); + + this._browserWidgetView = new BrowserWidget({ + parentView: this, + titleText: 'Configuration File Location', + helpText: 'Browse to a location to select it.', + submitText: 'Select Location', + validate: function (model) { + let isValid = $.Deferred(); + if (!model || model.get('_modelType') !== 'folder') { + isValid.reject('Please select a folder.'); + } else { + isValid.resolve(); + } + return isValid.promise(); + } + }); + this.listenTo(this._browserWidgetView, 'g:saved', function (val) { + this.$('#g-large-image-config-folder').val(val.id); + restRequest({ + url: `resource/${val.id}/path`, + method: 'GET', + data: { type: val.get('_modelType') } + }).done((result) => { + // Only add the resource path if the value wasn't altered + if (this.$('#g-large-image-config-folder').val() === val.id) { + this.$('#g-large-image-config-folder').val(`${val.id} (${result})`); + } + }); + }); }, render: function () { @@ -101,6 +136,11 @@ var ConfigView = View.extend({ resp.responseJSON.message ); }); + }, + + _openBrowser: function () { + console.log('A'); + this._browserWidgetView.setElement($('#g-dialog-container')).render(); } }, { /* Class methods and objects */ diff --git a/girder/test_girder/test_large_image.py b/girder/test_girder/test_large_image.py index f0c7a512a..b668dd510 100644 --- a/girder/test_girder/test_large_image.py +++ b/girder/test_girder/test_large_image.py @@ -415,7 +415,7 @@ def testYAMLConfigFile(server, admin, user, fsAssetstore): collection, 'folder A', parentType='collection', creator=admin) colFolderB = Folder().createFolder( - colFolderA, 'folder C', creator=admin) + colFolderA, 'folder B', creator=admin) groupA = Group().createGroup('Group A', admin) resp = server.request( @@ -475,3 +475,66 @@ def testYAMLConfigFile(server, admin, user, fsAssetstore): path='/folder/%s/yaml_config/sample.json' % str(colFolderB['_id']), user=admin) assert utilities.respStatus(resp) == 200 assert resp.json['keyA'] == 'value6' + + +@pytest.mark.usefixtures('unbindLargeImage') +@pytest.mark.plugin('large_image') +def testYAMLConfigFileInherit(server, admin, user, fsAssetstore): + # Create some resources to use in the tests + collection = Collection().createCollection( + 'collection A', admin) + colFolderA = Folder().createFolder( + collection, 'folder A', parentType='collection', + creator=admin) + colFolderB = Folder().createFolder( + colFolderA, 'folder B', creator=admin) + colFolderConfig = Folder().createFolder( + collection, '.config', parentType='collection', + creator=admin) + collectionB = Collection().createCollection( + 'collection B', admin) + configFolder = Folder().createFolder( + collectionB, 'any', parentType='collection', + creator=admin) + Setting().set(constants.PluginSettings.LARGE_IMAGE_CONFIG_FOLDER, str(configFolder['_id'])) + utilities.uploadText( + json.dumps({ + 'keyA': 'value1', + 'keyB': 'value2', + 'keyC': 'value3', + '__inherit__': True}), + admin, fsAssetstore, colFolderB, 'sample.json') + utilities.uploadText( + json.dumps({ + 'keyA': 'value4', + 'keyD': 'value5', + '__inherit__': True}), + admin, fsAssetstore, colFolderConfig, 'sample.json') + resp = server.request( + path='/folder/%s/yaml_config/sample.json' % str(colFolderB['_id']), user=admin) + assert utilities.respStatus(resp) == 200 + assert resp.json['keyA'] == 'value1' + assert resp.json['keyB'] == 'value2' + assert resp.json['keyC'] == 'value3' + assert resp.json['keyD'] == 'value5' + utilities.uploadText( + json.dumps({ + 'keyB': 'value6', + 'keyE': 'value7'}), + admin, fsAssetstore, configFolder, 'sample.json') + resp = server.request( + path='/folder/%s/yaml_config/sample.json' % str(colFolderB['_id']), user=admin) + assert utilities.respStatus(resp) == 200 + assert resp.json['keyA'] == 'value1' + assert resp.json['keyB'] == 'value2' + assert resp.json['keyC'] == 'value3' + assert resp.json['keyD'] == 'value5' + assert resp.json['keyE'] == 'value7' + Folder().remove(colFolderConfig, user=admin) + resp = server.request( + path='/folder/%s/yaml_config/sample.json' % str(colFolderB['_id']), user=admin) + assert utilities.respStatus(resp) == 200 + assert resp.json['keyA'] == 'value1' + assert resp.json['keyB'] == 'value2' + assert resp.json['keyC'] == 'value3' + assert resp.json['keyE'] == 'value7' diff --git a/girder/test_girder/web_client_specs/largeImageSpec.js b/girder/test_girder/web_client_specs/largeImageSpec.js index f81dcdb84..ec8b4c0f4 100644 --- a/girder/test_girder/web_client_specs/largeImageSpec.js +++ b/girder/test_girder/web_client_specs/largeImageSpec.js @@ -4,79 +4,99 @@ girderTest.importPlugin('jobs', 'worker', 'large_image'); girderTest.startApp(); -$(function () { - describe('Test the large image plugin', function () { - it('create the admin user', function () { - girderTest.createUser( - 'admin', 'admin@email.com', 'Admin', 'Admin', 'testpassword')(); - }); - it('change the large_image settings', function () { - var settings; +describe('Test the large image plugin', function () { + it('create the admin user', function () { + girderTest.createUser( + 'admin', 'admin@email.com', 'Admin', 'Admin', 'testpassword')(); + }); + it('change the large_image settings', function () { + var settings; - waitsFor(function () { - return $('a.g-nav-link[g-target="admin"]').length > 0; - }, 'admin console link to load'); - runs(function () { - $('a.g-nav-link[g-target="admin"]').click(); - }); - waitsFor(function () { - return $('.g-plugins-config').length > 0; - }, 'the admin console to load'); - runs(function () { - $('.g-plugins-config').click(); - }); - girderTest.waitForLoad(); - waitsFor(function () { - return $('.g-plugin-config-link').length > 0; - }, 'the plugins page to load'); - runs(function () { - expect($('.g-plugin-config-link[g-route="plugins/large_image/config"]').length > 0); - $('.g-plugin-config-link[g-route="plugins/large_image/config"]').click(); - }); - girderTest.waitForLoad(); - waitsFor(function () { - return $('#g-large-image-form input').length > 0; - }, 'resource list setting to be shown'); - runs(function () { - $('.g-large-image-thumbnails-hide').trigger('click'); - $('.g-large-image-viewer-hide').trigger('click'); - $('.g-large-image-default-viewer').val('geojs'); - $('.g-large-image-auto-set-off').trigger('click'); - $('.g-large-image-max-thumbnail-files').val('5'); - $('.g-large-image-max-small-image-size').val('1024'); - $('.g-large-image-show-extra-public').val('{}'); - $('.g-large-image-show-extra').val('{}'); - $('.g-large-image-show-extra-admin').val('{"images": ["label", "macro"]}'); - $('.g-large-image-show-item-extra-public').val('{}'); - $('.g-large-image-show-item-extra').val('{}'); - $('.g-large-image-show-item-extra-admin').val('{"metadata": ["tile", "internal"], "images": ["label", "macro", "*"]}'); - $('#g-large-image-form input.btn-primary').click(); - }); - girderTest.waitForLoad(); - waitsFor(function () { - var resp = girder.rest.restRequest({ - url: 'large_image/settings', - type: 'GET', - async: false - }); - settings = resp.responseJSON; - return settings['large_image.max_thumbnail_files'] === 5; - }, 'large_image settings to change'); - runs(function () { - expect(settings['large_image.show_thumbnails']).toBe(false); - expect(settings['large_image.show_viewer']).toBe(false); - expect(settings['large_image.auto_set']).toBe(false); - expect(settings['large_image.default_viewer']).toBe('geojs'); - expect(settings['large_image.max_thumbnail_files']).toBe(5); - expect(settings['large_image.max_small_image_size']).toBe(1024); - expect(settings['large_image.show_extra_public']).toBe('{}'); - expect(settings['large_image.show_extra']).toBe('{}'); - expect(settings['large_image.show_extra_admin']).toBe('{"images": ["label", "macro"]}'); - expect(settings['large_image.show_item_extra_public']).toBe('{}'); - expect(settings['large_image.show_item_extra']).toBe('{}'); - expect(JSON.parse(settings['large_image.show_item_extra_admin'])).toEqual({'metadata': ['tile', 'internal'], 'images': ['label', 'macro', '*']}); + waitsFor(function () { + return $('a.g-nav-link[g-target="admin"]').length > 0; + }, 'admin console link to load'); + runs(function () { + $('a.g-nav-link[g-target="admin"]').click(); + }); + waitsFor(function () { + return $('.g-plugins-config').length > 0; + }, 'the admin console to load'); + runs(function () { + $('.g-plugins-config').click(); + }); + girderTest.waitForLoad(); + waitsFor(function () { + return $('.g-plugin-config-link').length > 0; + }, 'the plugins page to load'); + runs(function () { + expect($('.g-plugin-config-link[g-route="plugins/large_image/config"]').length > 0); + $('.g-plugin-config-link[g-route="plugins/large_image/config"]').click(); + }); + girderTest.waitForLoad(); + waitsFor(function () { + return $('#g-large-image-form input').length > 0; + }, 'resource list setting to be shown'); + runs(function () { + $('.g-large-image-thumbnails-hide').trigger('click'); + $('.g-large-image-viewer-hide').trigger('click'); + $('.g-large-image-default-viewer').val('geojs'); + $('.g-large-image-auto-set-off').trigger('click'); + $('.g-large-image-max-thumbnail-files').val('5'); + $('.g-large-image-max-small-image-size').val('1024'); + $('.g-large-image-show-extra-public').val('{}'); + $('.g-large-image-show-extra').val('{}'); + $('.g-large-image-show-extra-admin').val('{"images": ["label", "macro"]}'); + $('.g-large-image-show-item-extra-public').val('{}'); + $('.g-large-image-show-item-extra').val('{}'); + $('.g-large-image-show-item-extra-admin').val('{"metadata": ["tile", "internal"], "images": ["label", "macro", "*"]}'); + $('#g-large-image-form input.btn-primary').click(); + }); + girderTest.waitForLoad(); + waitsFor(function () { + var resp = girder.rest.restRequest({ + url: 'large_image/settings', + type: 'GET', + async: false }); - girderTest.waitForLoad(); + settings = resp.responseJSON; + return settings['large_image.max_thumbnail_files'] === 5; + }, 'large_image settings to change'); + runs(function () { + expect(settings['large_image.show_thumbnails']).toBe(false); + expect(settings['large_image.show_viewer']).toBe(false); + expect(settings['large_image.auto_set']).toBe(false); + expect(settings['large_image.default_viewer']).toBe('geojs'); + expect(settings['large_image.max_thumbnail_files']).toBe(5); + expect(settings['large_image.max_small_image_size']).toBe(1024); + expect(settings['large_image.show_extra_public']).toBe('{}'); + expect(settings['large_image.show_extra']).toBe('{}'); + expect(settings['large_image.show_extra_admin']).toBe('{"images": ["label", "macro"]}'); + expect(settings['large_image.show_item_extra_public']).toBe('{}'); + expect(settings['large_image.show_item_extra']).toBe('{}'); + expect(JSON.parse(settings['large_image.show_item_extra_admin'])).toEqual({'metadata': ['tile', 'internal'], 'images': ['label', 'macro', '*']}); + }); + girderTest.waitForLoad(); + }); + it('change the config folder', function () { + runs(function () { + $('.g-open-browser').click(); + }); + girderTest.waitForDialog(); + runs(function () { + $('#g-root-selector').val($('#g-root-selector')[0].options[1].value).trigger('change'); + }); + waitsFor(function () { + return $('.g-folder-list-link').length >= 2; + }); + runs(function () { + $('.g-folder-list-link').click(); + }); + waitsFor(function () { + return $('#g-selected-model').val() !== ''; + }); + runs(function () { + $('.g-submit-button').click(); }); + girderTest.waitForLoad(); }); });