-
Notifications
You must be signed in to change notification settings - Fork 75
Bypass blob storage in UploadArtifact(Closes #1704) #1717
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
14e5667
7df7dab
36cf80d
03eb659
71a7bf1
430674b
5529cf6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,12 +4,14 @@ define([ | |
| 'js/Dialogs/PluginConfig/PluginConfigDialog', | ||
| 'deepforge/utils', | ||
| 'text!js/Dialogs/PluginConfig/templates/PluginConfigDialog.html', | ||
| 'text!./CustomConfigWidgets.json', | ||
| 'css!./ConfigDialog.css' | ||
| ], function( | ||
| Q, | ||
| PluginConfigDialog, | ||
| utils, | ||
| pluginConfigDialogTemplate, | ||
| CustomWidgets | ||
| ) { | ||
| var SECTION_DATA_KEY = 'section', | ||
| ATTRIBUTE_DATA_KEY = 'attribute', | ||
|
|
@@ -20,36 +22,56 @@ define([ | |
| DESCRIPTION_BASE = $('<div class="desc muted col-sm-8"></div>'), | ||
| SECTION_HEADER = $('<h6 class="config-section-header">'); | ||
|
|
||
| const CUSTOM_WIDGETS = JSON.parse(CustomWidgets); | ||
|
|
||
| var ConfigDialog = function(client) { | ||
| PluginConfigDialog.call(this, {client: client}); | ||
| this._widgets = {}; | ||
| this.imported = this._registerCustomWidgets(CUSTOM_WIDGETS); | ||
| }; | ||
|
|
||
| ConfigDialog.prototype = Object.create(PluginConfigDialog.prototype); | ||
|
|
||
| ConfigDialog.prototype._registerCustomWidgets = async function(customWidgets) { | ||
| const self = this; | ||
|
|
||
| const promises = customWidgets.map(widgetInfo => { | ||
| return new Promise((resolve, reject) => { | ||
| require([widgetInfo.path], function(customWidget) { | ||
| const targetType = widgetInfo.type; | ||
| self._propertyGridWidgetManager | ||
| .registerWidgetForType(targetType, customWidget); | ||
| resolve(); | ||
| }, reject); | ||
| }); | ||
| }); | ||
|
|
||
| return Promise.all(promises); | ||
| }; | ||
|
|
||
| ConfigDialog.prototype.show = async function(pluginMetadata, options={}) { | ||
| const deferred = Q.defer(); | ||
|
|
||
| this._pluginMetadata = pluginMetadata; | ||
| const prevConfig = await this.getSavedConfig(); | ||
| await this.imported; | ||
| this._initDialog(pluginMetadata, prevConfig, options); | ||
|
|
||
| this._dialog.on('shown', () => { | ||
| this._dialog.find('input').first().focus(); | ||
| }); | ||
|
|
||
| this._btnSave.on('click', event => { | ||
| this.submit(deferred.resolve); | ||
| this._btnSave.on('click', async event => { | ||
| await this.submit(deferred.resolve); | ||
| event.stopPropagation(); | ||
| event.preventDefault(); | ||
| }); | ||
|
|
||
| //save&run on CTRL + Enter | ||
| this._dialog.on('keydown.PluginConfigDialog', event => { | ||
| this._dialog.on('keydown.PluginConfigDialog', async event => { | ||
| if (event.keyCode === 13 && (event.ctrlKey || event.metaKey)) { | ||
| event.stopPropagation(); | ||
| event.preventDefault(); | ||
| this.submit(deferred.resolve); | ||
| await this.submit(deferred.resolve); | ||
| } | ||
| }); | ||
| this._dialog.modal('show'); | ||
|
|
@@ -63,6 +85,7 @@ define([ | |
| this._divContainer = this._dialog.find('.modal-body'); | ||
| this._saveConfigurationCb = this._dialog.find('.save-configuration'); | ||
| this._modalHeader = this._dialog.find('.modal-header'); | ||
| this._modalFooter = this._dialog.find('.modal-footer'); | ||
| this._saveConfigurationCb.find('input').prop('checked', true); | ||
|
|
||
| // Create the header | ||
|
|
@@ -85,8 +108,9 @@ define([ | |
| }; | ||
| }; | ||
|
|
||
| ConfigDialog.prototype.submit = function (callback) { | ||
| const config = this._getAllConfigValues(); | ||
| ConfigDialog.prototype.submit = async function (callback) { | ||
| this._btnSave.attr('disabled', true); | ||
| const config = await this._getAllConfigValues(); | ||
| const saveConfig = this._saveConfigurationCb.find('input') | ||
| .prop('checked'); | ||
|
|
||
|
|
@@ -159,9 +183,9 @@ define([ | |
| return response.status < 399 ? await response.json() : null; | ||
| }; | ||
|
|
||
| ConfigDialog.prototype._getAllConfigValues = function () { | ||
| ConfigDialog.prototype._getAllConfigValues = async function () { | ||
| var settings = {}; | ||
|
|
||
| const basedir = `${this._client.getActiveProjectId()}/artifacts`; | ||
|
||
| Object.keys(this._widgets).forEach(namespace => { | ||
| settings[namespace] = {}; | ||
|
|
||
|
|
@@ -170,6 +194,16 @@ define([ | |
| }); | ||
| }); | ||
|
|
||
| // Second pass necessary to resolve value from functions | ||
| for (const namespace of Object.keys(this._widgets)){ | ||
| for(const name of Object.keys(this._widgets[namespace])){ | ||
| if(this._widgets[namespace][name].beforeSubmit){ | ||
| await this._widgets[namespace][name].beforeSubmit(basedir, settings); | ||
| settings[namespace][name] = this._widgets[namespace][name].getValue(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return settings; | ||
| }; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| [ | ||
|
||
| { | ||
| "name" : "BrowserAssetWidget", | ||
|
||
| "type": "userAsset", | ||
| "path" : "deepforge/viz/widgets/BrowserAssetWidget" | ||
| } | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| /*globals define, $, WebGMEGlobal*/ | ||
| define([ | ||
| 'js/Controls/PropertyGrid/Widgets/WidgetBase', | ||
| 'js/logger', | ||
| 'deepforge/storage/index' | ||
| ], function (WidgetBase, | ||
| Logger, | ||
| Storage) { | ||
| 'use strict'; | ||
|
|
||
| const BTN_ATTACH = $('<a class="btn btn-mini btn-dialog-open"><i class="glyphicon glyphicon-file"/></a>'), | ||
| INPUT_FILE_UPLOAD = $('<input type="file" />'), | ||
| ASSET_WIDGET_BASE = $('<div class="asset-widget" />'), | ||
| ASSET_LINK = $('<a class="local-download-link" href="" target="_blank"></a>'); | ||
|
|
||
| class BrowserAssetWidget extends WidgetBase { | ||
|
||
| constructor(propertyDesc) { | ||
| super(propertyDesc); | ||
| if (propertyDesc.readOnly) { | ||
| this._alwaysReadOnly = true; | ||
| } | ||
| this.file = null; | ||
|
|
||
| this.logger = Logger.create('deepforge:viz:widgets:BrowserAssetWidget', | ||
| WebGMEGlobal.gmeConfig.client.log); | ||
|
|
||
| this.parentEl = ASSET_WIDGET_BASE.clone(); | ||
| this.el.append(this.parentEl); | ||
|
|
||
| this.assetLink = ASSET_LINK.clone(); | ||
| this.parentEl.append(this.assetLink); | ||
|
|
||
| this.fileDropTarget = this.parentEl; | ||
|
|
||
| this.btnAttach = BTN_ATTACH.clone(); | ||
| this.parentEl.append(this.btnAttach); | ||
|
|
||
| this.fileUploadInput = INPUT_FILE_UPLOAD.clone(); | ||
|
|
||
| this.attachFileDropHandlers(); | ||
| this.updateDisplay(); | ||
| } | ||
|
|
||
| attachFileDropHandlers() { | ||
| this.btnAttach.on('click', e => { | ||
| e.stopPropagation(); | ||
| e.preventDefault(); | ||
|
|
||
| this.fileUploadInput.click(); | ||
| }); | ||
|
|
||
| this.fileUploadInput.on('change', e => { | ||
| e.stopPropagation(); | ||
| e.preventDefault(); | ||
| this.fileSelectHandler(e.originalEvent); | ||
| }); | ||
|
|
||
| this.fileDropTarget.on('dragover', e => { | ||
| e.stopPropagation(); | ||
| e.preventDefault(); | ||
| }); | ||
|
|
||
| this.fileDropTarget.on('dragenter', e => { | ||
| e.stopPropagation(); | ||
| e.preventDefault(); | ||
| this.fileDropTarget.addClass('hover'); | ||
| }); | ||
|
|
||
| this.fileDropTarget.on('dragleave', e => { | ||
| e.stopPropagation(); | ||
| e.preventDefault(); | ||
| this.fileDropTarget.removeClass('hover'); | ||
| }); | ||
|
|
||
| this.fileDropTarget.on('drop', e => { | ||
| e.stopPropagation(); | ||
| e.preventDefault(); | ||
| this.fileDropTarget.removeClass('hover'); | ||
| this.fileSelectHandler(e.originalEvent); | ||
| }); | ||
| } | ||
|
|
||
| fileSelectHandler(event){ | ||
| const files = event.target.files || event.dataTransfer.files; | ||
| if(files){ | ||
| this.setFile(files[0]); | ||
| } | ||
| } | ||
|
|
||
| setFile(file) { | ||
| this.file = file; | ||
| this.assetLink.text(this.file.name); | ||
| this.assetLink.attr('href', URL.createObjectURL(this.file)); | ||
| } | ||
|
|
||
| detachFileHandlers(){ | ||
| this.fileUploadInput.off('change'); | ||
|
|
||
| this.fileDropTarget.off('dragover'); | ||
| this.fileDropTarget.off('dragenter'); | ||
| this.fileDropTarget.off('dragleave'); | ||
| this.fileDropTarget.off('drop'); | ||
|
|
||
| this.btnAttach.off('click'); | ||
| } | ||
|
|
||
| updateDisplay() { | ||
| if(this.file){ | ||
| this.setFile(this.file); | ||
| } | ||
| super.updateDisplay(); | ||
| } | ||
|
|
||
| async beforeSubmit(basedir, config) { | ||
| const storageConfig = findByKey(config, 'storage'); | ||
|
||
| const backendId = this.resolveBackendId(storageConfig); | ||
| if(backendId){ | ||
| const backend = Storage.getBackend(backendId); | ||
| const tgtStorageClient = await backend.getClient(this.logger, storageConfig.config); | ||
| const path = `${basedir}/${this.file.name}`; | ||
| const dataInfo = await tgtStorageClient.putFile(path, this.file); | ||
| this.setValue({name: this.file.name, dataInfo: dataInfo}); | ||
| } | ||
| } | ||
|
|
||
| resolveBackendId(config){ | ||
| if(config.name){ | ||
| const storageId = Storage.getAvailableBackends() | ||
| .map(id => Storage.getStorageMetadata(id)) | ||
| .filter(metadata => metadata.name === config.name) | ||
| .map(metadata => metadata.id); | ||
| return storageId.pop(); | ||
| } | ||
| } | ||
|
|
||
| destroy() { | ||
| this.detachFileHandlers(); | ||
| super.destroy(); | ||
| } | ||
| } | ||
|
|
||
| function findByKey(obj, key) { | ||
| if(Object.keys(obj).includes(key)){ | ||
| return obj[key]; | ||
| } else { | ||
| for(const childKey of Object.keys(obj)){ | ||
| if(typeof obj[childKey] === 'object'){ | ||
| return findByKey(obj[childKey], key); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return BrowserAssetWidget; | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,15 +52,12 @@ define([ | |
| */ | ||
| UploadArtifact.prototype.main = async function (callback) { | ||
| const config = this.getCurrentConfig(); | ||
| const hash = config.dataHash; | ||
| const {name, dataInfo} = config.assetInfo; | ||
| const baseName = config.dataTypeId; | ||
|
|
||
| try { | ||
| this.ensureCompatibleMeta(); | ||
| const name = await this.getAssetNameFromHash(hash) || | ||
| baseName[0].toLowerCase() + baseName.substring(1); | ||
| const assetInfo = await this.transfer(hash, config.storage, name); | ||
| await this.createArtifact({type: baseName, name: name, data: assetInfo}); | ||
| await this.createArtifact({type: baseName, name: name, data: dataInfo}); | ||
| await this.save(`Uploaded "${name}" data`); | ||
| this.result.setSuccess(true); | ||
| callback(null, this.result); | ||
|
|
@@ -69,16 +66,6 @@ define([ | |
| } | ||
| }; | ||
|
|
||
| UploadArtifact.prototype.transfer = async function (hash, storage, name) { | ||
| const filename = `${this.projectId}/artifacts/${name}`; | ||
| const gmeStorageClient = await Storage.getBackend('gme').getClient(this.logger); | ||
| const dataInfo = gmeStorageClient.createDataInfo(hash); | ||
| const content = await gmeStorageClient.getFile(dataInfo); | ||
|
|
||
| const {id, config} = storage; | ||
| const dstStorage = await Storage.getBackend(id).getClient(this.logger, config); | ||
| return await dstStorage.putFile(filename, content); | ||
| }; | ||
|
|
||
| UploadArtifact.prototype.getAssetNameFromHash = async function(hash){ | ||
|
||
| const metadata = await this.blobClient.getMetadata(hash); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm.. Does jquery support async event handlers correctly here? Maybe we should move
event.stopPropagation()andevent.preventDefault()to be called first.The issue I am concerned about is that I am not sure that the underlying implementation is something like this:
In the above case, the event would still propagate until the async function completes.