Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ exclude_paths:
- src/visualizers/widgets/TensorPlotter/lib/
- src/visualizers/widgets/TensorPlotter/styles/simple-grid.min.css
- src/visualizers/widgets/TrainKeras/build
- src/visualizers/panels/ForgeActionButton/build
- src/visualizers/panels/TrainKeras/JSONImporter.js
- src/visualizers/panels/TrainKeras/changeset.js
- src/visualizers/widgets/InteractiveWorkspace/lib
62 changes: 62 additions & 0 deletions src/plugins/UploadLibraryModelToBlob/UploadLibraryModelToBlob.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*globals define*/

define([
'text!./metadata.json',
'plugin/PluginBase',
'fs',
'../../visualizers/panels/ForgeActionButton/Libraries.json',
], function (
pluginMetadata,
PluginBase,
fs,
Libraries,
) {
'use strict';

const fsp = fs.promises;
pluginMetadata = JSON.parse(pluginMetadata);

class UploadLibraryModelToBlob extends PluginBase {
constructor(libraries=Libraries) {
super();
this.pluginMetadata = pluginMetadata;
this.libraries = libraries;
}

async main(/*callback*/) {
const config = this.getCurrentConfig();
const {libraryName, modelName} = config;
const hash = await this.uploadLibraryModel(libraryName, modelName);
this.createMessage(this.rootNode, hash);
this.result.setSuccess(true);
//callback(null, this.result);
}

async uploadLibraryModel(libraryName, modelName) {
const data = await fsp.readFile(this.getLibraryModelPath(libraryName, modelName));
const hash = await this.blobClient.putFile(`${modelName}.webgmexm`, data);
return hash;
}

getLibraryModelPath(libraryName, modelName) {
const modelInfo = this.getLibraryModelInfo(libraryName, modelName);
return modelInfo.path;
}

getLibraryModelInfo(libraryName, modelName) {
const libraryInfo = this.libraries.find(libraryInfo => libraryInfo.name === libraryName);
if (!libraryInfo) {
throw new Error(`Library not found: ${libraryName}`);
}
const modelInfo = libraryInfo.models.find(modelInfo => modelInfo.name === modelName);
if (!modelInfo) {
throw new Error(`Model not found in ${libraryName}: ${modelName}`);
}
return modelInfo;
}
}

UploadLibraryModelToBlob.metadata = pluginMetadata;

return UploadLibraryModelToBlob;
});
15 changes: 15 additions & 0 deletions src/plugins/UploadLibraryModelToBlob/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"id": "UploadLibraryModelToBlob",
"name": "UploadLibraryModelToBlob",
"version": "0.1.0",
"description": "",
"icon": {
"class": "glyphicon glyphicon-cog",
"src": ""
},
"disableServerSideExecution": false,
"disableBrowserSideExecution": false,
"dependencies": [],
"writeAccessRequired": false,
"configStructure": []
}
81 changes: 80 additions & 1 deletion src/visualizers/panels/ForgeActionButton/Actions.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*globals define, WebGMEGlobal*/
/*globals define, WebGMEGlobal, $*/
// These are actions defined for specific meta types. They are evaluated from
// the context of the ForgeActionButton
define([
Expand All @@ -9,6 +9,8 @@ define([
'deepforge/globals',
'deepforge/viz/TextPrompter',
'deepforge/viz/StorageHelpers',
'text!./Libraries.json',
'./build/ExamplesDialog',
], function(
LibraryDialog,
Materialize,
Expand All @@ -17,12 +19,40 @@ define([
DeepForge,
TextPrompter,
StorageHelpers,
Libraries,
ExamplesDialog,
) {
Libraries = JSON.parse(Libraries);
var returnToLast = (place) => {
var returnId = DeepForge.last[place];
WebGMEGlobal.State.registerActiveObject(returnId);
};

async function importExample(client, example, parentId) {
const hash = await uploadExampleToBlob(client, example);
await Q.ninvoke(
client,
'importSelectionFromFile',
client.getActiveProjectId(),
client.getActiveBranchName(),
parentId,
hash,
);
}

async function uploadExampleToBlob(client, example) {
const pluginName = 'UploadLibraryModelToBlob';
const context = client.getCurrentPluginContext(pluginName);
context.pluginConfig = {
libraryName: example.library,
modelName: example.name,
};
const result = await Q.ninvoke(client, 'runServerPlugin', pluginName, context);
const [hashMessage] = result.messages;
const hash = hashMessage.message;
return hash;
}

var prototypeButtons = function(type, fromType) {
return [
{
Expand Down Expand Up @@ -120,6 +150,55 @@ define([
// TODO: Add support for adding (inherited) children

buttons = addButtons.concat(buttons);

const installedLibs = client.getLibraryNames()
.map(name => Libraries.find(lib => lib.name === name))
.filter(lib => !!lib);
const hasExampleModels = installedLibs.flatMap(lib => lib.models).length > 0;
if (hasExampleModels) {
buttons.unshift({
name: 'Import Example...',
icon: 'view_list',
action: function() {
const installedLibs = client.getLibraryNames()
.map(name => Libraries.find(lib => lib.name === name))
.filter(lib => !!lib);

installedLibs
.forEach(info => info.models.forEach(model => model.library = info.name));
const exampleModels = installedLibs.flatMap(lib => lib.models);

if (this.examplesDialog) {
this.examplesDialog.destroy();
}
this.examplesDialog = new ExamplesDialog(
{
target: document.body,
props: {
examples: exampleModels,
jquery: $,
client,
}
}
);
this.examplesDialog.events().addEventListener(
'importExample',
async event => {
const example = event.detail;
try {
Materialize.toast(`Importing ${example.name} from ${example.library}...`, 2000);
await importExample(client, example, this._currentNodeId);
Materialize.toast('Import complete!', 2000);
} catch(err) {
Materialize.toast(`Import failed: ${err.message}`, 3000);
throw err;
}
}
);

}
});
}
return buttons;
},
MyOperations_META: [
Expand Down
73 changes: 73 additions & 0 deletions src/visualizers/panels/ForgeActionButton/ExamplesDialog.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<script>
let element;
export let examples = [];
export let jquery;
export let client;
import { onMount } from 'svelte';

onMount(() => jquery(element).modal('show'));

export function destroy() {
jquery(element).modal('hide');
}

export function events() {
return element;
}

async function importExample(example) {
const event = new CustomEvent('importExample', {detail: example});
element.dispatchEvent(event);
}

</script>

<div bind:this={element} class="examples-modal modal fade in" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" on:click|stopPropagation|preventDefault={destroy}>x</button>
<span class="title">Available Examples</span>
</div>
<div class="modal-body">
<div>
<table class="table highlight">
<thead>
<tr>
<th >Name</th>
<th >Library</th>
<th >Description</th>
</tr>
</thead>
<tbody>
{#each examples as example}
<tr>
<td>{example.name}</td>
<td>{example.library}</td>
<td class="description">{example.description}</td>
<!-- TODO: add loading icon? -->
<td on:click|stopPropagation|preventDefault={() => importExample(example)}><i class="material-icons">get_app</i></td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>

<style>
.description {
font-style: italic;
}

.title {
font-size: 28px;
vertical-align: middle;
}

.examples-modal th {
text-align: left;
}
</style>
3 changes: 2 additions & 1 deletion src/visualizers/panels/ForgeActionButton/Libraries.json.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
description: ext.description,
nodeTypes: ext.nodeTypes,
initCode: ext.initCode,
seed: ext.seed
seed: ext.seed,
models: ext.models,
};
}), null, 2) %>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Large diffs are not rendered by default.

Binary file added test/assets/TestOperation.webgmexm
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*eslint-env node, mocha*/
/**
* Generated by PluginGenerator 2.20.5 from webgme on Thu Feb 11 2021 12:20:02 GMT-0600 (Central Standard Time).
*/

const testFixture = require('../../../globals');

describe('UploadLibraryModelToBlob', function () {
const gmeConfig = testFixture.getGmeConfig();
const logger = testFixture.logger.fork('UploadLibraryModelToBlob');
const PluginCliManager = testFixture.WebGME.PluginCliManager;

const assert = require('assert');
const {promisify} = require('util');
const manager = new PluginCliManager(null, logger, gmeConfig);
const pluginName = 'UploadLibraryModelToBlob';
const projectName = 'testProject';
const PIPELINES = '/f';
manager.executePlugin = promisify(manager.executePlugin);
manager.runPluginMain = promisify(manager.runPluginMain);
let context,
gmeAuth,
storage;

before(async function () {
gmeAuth = await testFixture.clearDBAndGetGMEAuth(gmeConfig, projectName);
storage = testFixture.getMemoryStorage(logger, gmeConfig, gmeAuth);
await storage.openDatabase();
const importParam = {
projectSeed: testFixture.path.join(testFixture.DF_SEED_DIR, 'devProject', 'devProject.webgmex'),
projectName: projectName,
branchName: 'master',
logger: logger,
gmeConfig: gmeConfig
};

const importResult = await testFixture.importProject(storage, importParam);
const {project, commitHash} = importResult;
await project.createBranch('test', commitHash);
context = {
project: project,
commitHash: commitHash,
branchName: 'test',
activeNode: PIPELINES,
namespace: 'pipeline',
};

});

after(async function () {
await storage.closeDatabase();
await gmeAuth.unload();
});

it('should return the hash in the first message', async function () {
const plugin = await manager.initializePlugin(pluginName);
plugin.libraries = [{
name: 'testlib',
models: [{
name: 'TestOperation',
path: 'test/assets/TestOperation.webgmexm'
}]
}];
const pluginConfig = {
libraryName: 'testlib',
modelName: 'TestOperation',
};
await manager.configurePlugin(plugin, pluginConfig, context);
const {messages} = await manager.runPluginMain(plugin);
assert.equal(messages.length, 1);
assert.equal(messages[0].message.length, 40);
const alphnum = /^[a-z0-9]+$/;
assert(alphnum.test(messages[0].message));
});

it('should throw error if library not found', async function () {
const plugin = await manager.initializePlugin(pluginName);
const pluginConfig = {
libraryName: 'IDontExist',
modelName: 'unused',
};
await manager.configurePlugin(plugin, pluginConfig, context);
await assert.rejects(
() => manager.runPluginMain(plugin),
/Library not found/
);
});

it('should throw error if model not found', async function () {
const plugin = await manager.initializePlugin(pluginName);
plugin.libraries = [{
name: 'testlib',
models: []
}];
const pluginConfig = {
libraryName: 'testlib',
modelName: 'unused',
};
await manager.configurePlugin(plugin, pluginConfig, context);
await assert.rejects(
() => manager.runPluginMain(plugin),
/Model not found/
);
});
});
Loading