Skip to content

Commit 5a62fea

Browse files
authored
Add InteractiveExplorer, InteractiveEditor. Closes #1768 (#1770)
* WIP Add initial commit of DatasetExplorer * WIP use artifact name in DataExplorer * WIP Add plot editor to dataset explorer * WIP add PlotEditor and css * Add UI elements for adding/removing data * Fix UI issue with editing plotted data * WIP Add utility for parsing python slices * Add slice string validation * UI support for validating data slice * Update figure updates given the figure data * Add explorer_helpers.py for DatasetExplorer * Add explorer helpers to session for getting metadata * dynamically create variable names for dropdown * Add tests for variable name creation from metadata * Plot actual data selected from "Add Data" button * Fix plot height * Add basic 3D plot support to DataExplorer * Fix async issue with multiple lines * Fix x-axis, y-axis labels * Add validation for plotting data * Remove .only from PythonSlice test suite * Add basic artifact loader to DatasetExplorer * Only show artifacts with data in artifact loader * Update metadata on artifact load into session * Removed hardcoded examples from html * Add some support for colors (uniform only) * Add color support for individual points * Fix selection of keys from artifacts * WIP Use session with queue in dataset explorer * WIP code cleanup dataset explorer * Add compute creation (and shield) for DatasetExplorer * Don't show slice syntax errors until change event * Fix artifacts with extensions. minor code cleanup * Increase territory for access to initialization code (custom serializer support) * Rename DatasetVisualizer -> TensorPlotter * Rename scss,css files * Add "save" action to floating action button * Only load jscolor in the browser * Skip jscolor library when linting * Add InteractiveEditor base class * Update to use InteractiveEditor base classes * Add getSnapshot to tensor plotter * Fix setting the data dialog on open * WIP working on operation code... * Include all artifacts in TensorPlotter * Fixed python slice parsing * Add InteractiveExplorer base class * Use inform dialog w/ auth errors * Update TensorPlotter to inherit from InteractiveExplorer * fix css linting issue * Remove TensorPlotter * Remove tensor plotter from registries * Remove more tensorplotter things * Remove TensorPlotter tests * Remove old comments and minor fixes * remove old comment
1 parent d383d00 commit 5a62fea

File tree

11 files changed

+595
-0
lines changed

11 files changed

+595
-0
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/*globals define, WebGMEGlobal*/
2+
3+
define([
4+
'deepforge/viz/ConfigDialog',
5+
'js/Constants',
6+
], function (
7+
ConfigDialog,
8+
CONSTANTS,
9+
) {
10+
11+
'use strict';
12+
13+
class InteractiveEditorControl {
14+
constructor(options) {
15+
this._logger = options.logger.fork('Control');
16+
this.client = options.client;
17+
this._embedded = options.embedded;
18+
this._widget = options.widget;
19+
this.initializeWidgetHandlers(this._widget);
20+
this.territoryEventFilters = [];
21+
22+
this._currentNodeId = null;
23+
24+
this._logger.debug('ctor finished');
25+
}
26+
27+
initializeWidgetHandlers (widget) {
28+
const features = widget.getCapabilities();
29+
if (features.save) {
30+
widget.save = () => this.save();
31+
}
32+
widget.getConfigDialog = () => new ConfigDialog(this.client);
33+
}
34+
35+
selectedObjectChanged (nodeId) {
36+
const desc = this.getObjectDescriptor(nodeId);
37+
38+
this._logger.debug('activeObject nodeId \'' + nodeId + '\'');
39+
40+
if (this._currentNodeId) {
41+
this.client.removeUI(this._territoryId);
42+
}
43+
44+
this._currentNodeId = nodeId;
45+
46+
if (typeof this._currentNodeId === 'string') {
47+
const territory = this.getTerritory(nodeId);
48+
this._widget.setTitle(desc.name.toUpperCase());
49+
50+
this._territoryId = this.client
51+
.addUI(this, events => this._eventCallback(events));
52+
53+
this.client.updateTerritory(this._territoryId, territory);
54+
}
55+
}
56+
57+
getTerritory(nodeId) {
58+
const territory = {};
59+
territory[nodeId] = {children: 0};
60+
return territory;
61+
}
62+
63+
getMetaNode(name) {
64+
const metanodes = this.client.getAllMetaNodes();
65+
return metanodes
66+
.find(node => {
67+
const namespace = node.getNamespace();
68+
const fullName = namespace ? namespace + '.' + node.getAttribute('name') :
69+
node.getAttribute('name');
70+
71+
return fullName === name;
72+
});
73+
}
74+
75+
createNode(desc, parentId) {
76+
if (!parentId) {
77+
parentId = this._currentNodeId;
78+
}
79+
desc.pointers = desc.pointers || {};
80+
desc.attributes = desc.attributes || {};
81+
82+
const base = this.getMetaNode(desc.type) || this.client.getNode(desc.pointers.base);
83+
const nodeId = this.client.createNode({
84+
parentId: parentId,
85+
baseId: base.getId()
86+
});
87+
88+
const attributes = Object.entries(desc.attributes);
89+
attributes.forEach(entry => {
90+
const [name, value] = entry;
91+
this.client.setAttribute(nodeId, name, value);
92+
});
93+
94+
const pointers = Object.entries(desc.pointers);
95+
pointers.forEach(entry => {
96+
const [name, id] = entry;
97+
this.client.setPointer(nodeId, name, id);
98+
});
99+
100+
return nodeId;
101+
}
102+
103+
save() {
104+
this.client.startTransaction();
105+
const dataId = this.createNode(this._widget.getSnapshot());
106+
const implicitOpId = this.createNode(this._widget.getEditorState(), dataId);
107+
this.client.setPointer(dataId, 'provenance', implicitOpId);
108+
const operationId = this.createNode(this._widget.getOperation(), implicitOpId);
109+
this.client.setPointer(implicitOpId, 'operation', operationId);
110+
this.client.completeTransaction();
111+
}
112+
113+
getObjectDescriptor (nodeId) {
114+
const node = this.client.getNode(nodeId);
115+
116+
if (node) {
117+
return {
118+
id: node.getId(),
119+
name: node.getAttribute('name'),
120+
childrenIds: node.getChildrenIds(),
121+
parentId: node.getParentId(),
122+
};
123+
}
124+
}
125+
126+
/* * * * * * * * Node Event Handling * * * * * * * */
127+
_eventCallback (events=[]) {
128+
this._logger.debug('_eventCallback \'' + events.length + '\' items');
129+
130+
events
131+
.filter(event => this.isRelevantEvent(event))
132+
.forEach(event => {
133+
switch (event.etype) {
134+
135+
case CONSTANTS.TERRITORY_EVENT_LOAD:
136+
this.onNodeLoad(event.eid);
137+
break;
138+
case CONSTANTS.TERRITORY_EVENT_UPDATE:
139+
this.onNodeUpdate(event.eid);
140+
break;
141+
case CONSTANTS.TERRITORY_EVENT_UNLOAD:
142+
this.onNodeUnload(event.eid);
143+
break;
144+
default:
145+
break;
146+
}
147+
});
148+
149+
this._logger.debug('_eventCallback \'' + events.length + '\' items - DONE');
150+
}
151+
152+
onNodeLoad (gmeId) {
153+
const description = this.getObjectDescriptor(gmeId);
154+
this._widget.addNode(description);
155+
}
156+
157+
onNodeUpdate (gmeId) {
158+
const description = this.getObjectDescriptor(gmeId);
159+
this._widget.updateNode(description);
160+
}
161+
162+
onNodeUnload (gmeId) {
163+
this._widget.removeNode(gmeId);
164+
}
165+
166+
isRelevantEvent (event) {
167+
return this.territoryEventFilters
168+
.reduce((keep, fn) => keep && fn(event), true);
169+
}
170+
171+
_stateActiveObjectChanged (model, activeObjectId) {
172+
if (this._currentNodeId === activeObjectId) {
173+
// The same node selected as before - do not trigger
174+
} else {
175+
this.selectedObjectChanged(activeObjectId);
176+
}
177+
}
178+
179+
/* * * * * * * * Visualizer life cycle callbacks * * * * * * * */
180+
destroy () {
181+
this._detachClientEventListeners();
182+
}
183+
184+
_attachClientEventListeners () {
185+
this._detachClientEventListeners();
186+
if (!this._embedded) {
187+
WebGMEGlobal.State.on(
188+
'change:' + CONSTANTS.STATE_ACTIVE_OBJECT,
189+
this._stateActiveObjectChanged,
190+
this
191+
);
192+
}
193+
}
194+
195+
_detachClientEventListeners () {
196+
if (!this._embedded) {
197+
WebGMEGlobal.State.off(
198+
'change:' + CONSTANTS.STATE_ACTIVE_OBJECT,
199+
this._stateActiveObjectChanged
200+
);
201+
}
202+
}
203+
204+
onActivate () {
205+
this._attachClientEventListeners();
206+
207+
if (typeof this._currentNodeId === 'string') {
208+
WebGMEGlobal.State.registerActiveObject(this._currentNodeId, {suppressVisualizerFromNode: true});
209+
}
210+
}
211+
212+
onDeactivate () {
213+
this._detachClientEventListeners();
214+
}
215+
}
216+
217+
return InteractiveEditorControl;
218+
});
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*globals define, _, WebGMEGlobal*/
2+
3+
define([
4+
'js/PanelBase/PanelBaseWithHeader',
5+
'js/PanelManager/IActivePanel',
6+
'widgets/InteractiveEditor/InteractiveEditorWidget',
7+
'./InteractiveEditorControl'
8+
], function (
9+
PanelBaseWithHeader,
10+
IActivePanel,
11+
InteractiveEditorWidget,
12+
InteractiveEditorControl
13+
) {
14+
'use strict';
15+
16+
function InteractiveEditorPanel(layoutManager, params) {
17+
var options = {};
18+
//set properties from options
19+
options[PanelBaseWithHeader.OPTIONS.LOGGER_INSTANCE_NAME] = 'InteractiveEditorPanel';
20+
options[PanelBaseWithHeader.OPTIONS.FLOATING_TITLE] = true;
21+
22+
//call parent's constructor
23+
PanelBaseWithHeader.apply(this, [options, layoutManager]);
24+
25+
this._client = params.client;
26+
this._embedded = params.embedded;
27+
28+
this.initialize();
29+
30+
this.logger.debug('ctor finished');
31+
}
32+
33+
//inherit from PanelBaseWithHeader
34+
_.extend(InteractiveEditorPanel.prototype, PanelBaseWithHeader.prototype);
35+
_.extend(InteractiveEditorPanel.prototype, IActivePanel.prototype);
36+
37+
InteractiveEditorPanel.prototype.initialize = function () {
38+
var self = this;
39+
40+
//set Widget title
41+
this.setTitle('');
42+
43+
this.widget = new InteractiveEditorWidget(this.logger, this.$el);
44+
45+
this.widget.setTitle = function (title) {
46+
self.setTitle(title);
47+
};
48+
49+
this.control = new InteractiveEditorControl({
50+
logger: this.logger,
51+
client: this._client,
52+
embedded: this._embedded,
53+
widget: this.widget
54+
});
55+
56+
this.onActivate();
57+
};
58+
59+
/* OVERRIDE FROM WIDGET-WITH-HEADER */
60+
/* METHOD CALLED WHEN THE WIDGET'S READ-ONLY PROPERTY CHANGES */
61+
InteractiveEditorPanel.prototype.onReadOnlyChanged = function (isReadOnly) {
62+
//apply parent's onReadOnlyChanged
63+
PanelBaseWithHeader.prototype.onReadOnlyChanged.call(this, isReadOnly);
64+
65+
};
66+
67+
InteractiveEditorPanel.prototype.onResize = function (width, height) {
68+
this.logger.debug('onResize --> width: ' + width + ', height: ' + height);
69+
this.widget.onWidgetContainerResize(width, height);
70+
};
71+
72+
/* * * * * * * * Visualizer life cycle callbacks * * * * * * * */
73+
InteractiveEditorPanel.prototype.destroy = function () {
74+
this.control.destroy();
75+
this.widget.destroy();
76+
77+
PanelBaseWithHeader.prototype.destroy.call(this);
78+
WebGMEGlobal.KeyboardManager.setListener(undefined);
79+
WebGMEGlobal.Toolbar.refresh();
80+
};
81+
82+
InteractiveEditorPanel.prototype.onActivate = function () {
83+
this.widget.onActivate();
84+
this.control.onActivate();
85+
WebGMEGlobal.KeyboardManager.setListener(this.widget);
86+
WebGMEGlobal.Toolbar.refresh();
87+
};
88+
89+
InteractiveEditorPanel.prototype.onDeactivate = function () {
90+
this.widget.onDeactivate();
91+
this.control.onDeactivate();
92+
WebGMEGlobal.KeyboardManager.setListener(undefined);
93+
WebGMEGlobal.Toolbar.refresh();
94+
};
95+
96+
return InteractiveEditorPanel;
97+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*globals define */
2+
3+
define([
4+
'panels/InteractiveEditor/InteractiveEditorControl',
5+
], function (
6+
InteractiveEditorControl,
7+
) {
8+
9+
'use strict';
10+
11+
class InteractiveExplorerControl extends InteractiveEditorControl {
12+
ensureValidSnapshot(desc) {
13+
const metadata = this.getMetaNode('pipeline.Metadata');
14+
const type = this.getMetaNode(desc.type);
15+
16+
if (!type) {
17+
throw new Error(`Invalid metadata type: ${type}`);
18+
}
19+
20+
if (!type.isTypeOf(metadata.getId())) {
21+
throw new Error('Explorer can only create artifact metadata');
22+
}
23+
}
24+
25+
save() {
26+
const snapshotDesc = this._widget.getSnapshot();
27+
this.ensureValidSnapshot(snapshotDesc);
28+
29+
const features = this._widget.getCapabilities();
30+
this.client.startTransaction();
31+
const data = this.createNode(snapshotDesc);
32+
if (features.provenance) {
33+
const implicitOp = this.createNode(this._widget.getEditorState(), data);
34+
this.client.setPointer(data.getId(), 'provenance', implicitOp.getId());
35+
const operation = this.createNode(this._widget.getOperation(), implicitOp);
36+
this.client.setPointer(implicitOp.getId(), 'operation', operation.getId());
37+
}
38+
this.client.completeTransaction();
39+
}
40+
41+
}
42+
43+
return InteractiveExplorerControl;
44+
});
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*globals define */
2+
3+
define([
4+
'panels/InteractiveEditor/InteractiveEditorPanel',
5+
'widgets/InteractiveExplorer/InteractiveExplorerWidget',
6+
'./InteractiveExplorerControl',
7+
], function (
8+
InteractiveEditorPanel,
9+
InteractiveExplorerWidget,
10+
InteractiveExplorerControl,
11+
) {
12+
'use strict';
13+
14+
class InteractiveExplorerPanel extends InteractiveEditorPanel {
15+
16+
initialize() {
17+
this.setTitle('');
18+
this.widget = new InteractiveExplorerWidget(this.logger, this.$el);
19+
this.widget.setTitle = title => this.setTitle(title);
20+
21+
this.control = new InteractiveExplorerControl({
22+
logger: this.logger,
23+
client: this._client,
24+
embedded: this._embedded,
25+
widget: this.widget
26+
});
27+
28+
this.onActivate();
29+
}
30+
}
31+
32+
return InteractiveExplorerPanel;
33+
});

0 commit comments

Comments
 (0)