diff --git a/services/web/client/source/class/osparc/component/node/BaseNodeView.js b/services/web/client/source/class/osparc/component/node/BaseNodeView.js index 4540cfb4a6b..c7b658f6508 100644 --- a/services/web/client/source/class/osparc/component/node/BaseNodeView.js +++ b/services/web/client/source/class/osparc/component/node/BaseNodeView.js @@ -58,6 +58,7 @@ qx.Class.define("osparc.component.node.BaseNodeView", { __pane2: null, __title: null, __serviceInfoLayout: null, + __nodeStatusUI: null, __header: null, _mainView: null, __inputsView: null, @@ -66,8 +67,9 @@ qx.Class.define("osparc.component.node.BaseNodeView", { _settingsLayout: null, _mapperLayout: null, _iFrameLayout: null, + _loggerLayout: null, __buttonContainer: null, - __filesButton: null, + __outFilesButton: null, populateLayout: function() { this.__cleanLayout(); @@ -77,6 +79,8 @@ qx.Class.define("osparc.component.node.BaseNodeView", { this.__addOutputPortsUIs(); this._addSettings(); this._addIFrame(); + // this._addLogger(); + this._addButtons(); }, @@ -193,6 +197,7 @@ qx.Class.define("osparc.component.node.BaseNodeView", { this._settingsLayout = this.self().createSettingsGroupBox(this.tr("Settings")); this._mapperLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(10)); this._iFrameLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox()); + this._loggerLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox()); return mainView; }, @@ -237,8 +242,14 @@ qx.Class.define("osparc.component.node.BaseNodeView", { header.addSpacer(); + // just a placeholder until the node is set + const nodeStatusUI = this.__nodeStatusUI = new qx.ui.core.Widget(); + header.add(nodeStatusUI); + + header.addSpacer(); + const buttonsPart = this.__buttonContainer = new qx.ui.toolbar.Part(); - const filesBtn = this.__filesButton = new qx.ui.toolbar.Button(this.tr("Output Files"), "@FontAwesome5Solid/folder-open/14"); + const filesBtn = this.__outFilesButton = new qx.ui.toolbar.Button(this.tr("Output Files"), "@FontAwesome5Solid/folder-open/14"); osparc.utils.Utils.setIdToWidget(filesBtn, "nodeViewFilesBtn"); filesBtn.addListener("execute", () => this.__openNodeDataManager(), this); buttonsPart.add(filesBtn); @@ -334,7 +345,7 @@ qx.Class.define("osparc.component.node.BaseNodeView", { retrieveBtn.setEnabled(Boolean(this.getNode().getServiceUrl())); this.__buttonContainer.add(retrieveBtn); } - this.__buttonContainer.add(this.__filesButton); + this.__buttonContainer.add(this.__outFilesButton); this.__header.add(this.__buttonContainer); }, @@ -346,6 +357,7 @@ qx.Class.define("osparc.component.node.BaseNodeView", { const othersStatus2 = isSettingsGroupShowable && !maximize ? "visible" : "excluded"; this._settingsLayout.setVisibility(othersStatus2); this._mapperLayout.setVisibility(othersStatus); + this._loggerLayout.setVisibility(othersStatus); this.__header.setVisibility(othersStatus); }, @@ -475,6 +487,10 @@ qx.Class.define("osparc.component.node.BaseNodeView", { throw new Error("Abstract method called!"); }, + _addLogger: function() { + return; + }, + /** * @abstract */ @@ -482,6 +498,16 @@ qx.Class.define("osparc.component.node.BaseNodeView", { throw new Error("Abstract method called!"); }, + __createNodeStatusUI: function(node) { + const nodeStatusUI = new osparc.ui.basic.NodeStatusUI(node).set({ + backgroundColor: "material-button-background" + }); + nodeStatusUI.getChildControl("label").set({ + font: "text-16" + }); + return nodeStatusUI; + }, + /** * @param node {osparc.data.model.Node} node */ @@ -498,6 +524,13 @@ qx.Class.define("osparc.component.node.BaseNodeView", { const infoButton = this.__getInfoButton(); this.__serviceInfoLayout.add(infoButton); } + + const idx = this.__header.indexOf(this.__nodeStatusUI); + if (idx > -1) { + this.__header.remove(this.__nodeStatusUI); + } + this.__nodeStatusUI = this.__createNodeStatusUI(node); + this.__header.addAt(this.__nodeStatusUI, idx); } } }); diff --git a/services/web/client/source/class/osparc/component/node/NodeView.js b/services/web/client/source/class/osparc/component/node/NodeView.js index e1e1e20c53e..18d0e466cef 100644 --- a/services/web/client/source/class/osparc/component/node/NodeView.js +++ b/services/web/client/source/class/osparc/component/node/NodeView.js @@ -100,34 +100,55 @@ qx.Class.define("osparc.component.node.NodeView", { const loadingPage = this.getNode().getLoadingPage(); const iFrame = this.getNode().getIFrame(); - if (loadingPage === null && iFrame === null) { - return; - } - [ - loadingPage, - iFrame - ].forEach(widget => { - if (widget) { - widget.addListener("maximize", e => { - this._maximizeIFrame(true); - }, this); - widget.addListener("restore", e => { - this._maximizeIFrame(false); - }, this); - this._maximizeIFrame(widget.hasState("maximized")); - } - }); - this.__iFrameChanged(); - - iFrame.addListener("load", () => { + if (loadingPage && iFrame) { + [ + loadingPage, + iFrame + ].forEach(widget => { + if (widget) { + widget.addListener("maximize", e => { + this._maximizeIFrame(true); + }, this); + widget.addListener("restore", e => { + this._maximizeIFrame(false); + }, this); + this._maximizeIFrame(widget.hasState("maximized")); + } + }); this.__iFrameChanged(); - }); + + iFrame.addListener("load", () => { + this.__iFrameChanged(); + }); + } else { + // This will keep what comes after at the bottom + this._iFrameLayout.add(new qx.ui.core.Spacer(), { + flex: 1 + }); + } this._addToMainView(this._iFrameLayout, { flex: 1 }); }, + _addLogger: function() { + this._loggerLayout.removeAll(); + + const loggerView = this.__loggerView = this.getNode().getLogger().set({ + maxHeight: 250 + }); + loggerView.getChildControl("pin-node").exclude(); + const loggerPanel = new osparc.desktop.PanelView(this.tr("Logger"), loggerView).set({ + collapsed: true, + backgroundColor: "background-main-lighter" + }); + osparc.utils.Utils.setIdToWidget(loggerPanel.getTitleLabel(), "nodeLoggerTitleLabel"); + this._loggerLayout.add(loggerPanel); + + this._addToMainView(this._loggerLayout); + }, + _openEditAccessLevel: function() { const settingsEditorLayout = osparc.component.node.BaseNodeView.createSettingsGroupBox(this.tr("Settings")); const propsFormEditor = this.getNode().getPropsFormEditor(); diff --git a/services/web/client/source/class/osparc/component/widget/CollapsibleView.js b/services/web/client/source/class/osparc/component/widget/CollapsibleView.js index c95e7347af7..b31323a7ab2 100644 --- a/services/web/client/source/class/osparc/component/widget/CollapsibleView.js +++ b/services/web/client/source/class/osparc/component/widget/CollapsibleView.js @@ -100,12 +100,18 @@ qx.Class.define("osparc.component.widget.CollapsibleView", { } case "title": { const header = this.getChildControl("header"); - control = new qx.ui.basic.Atom(this.getTitle()); + control = new qx.ui.basic.Label(this.getTitle()); header.addAt(control, 1); // Attach handler this.__attachToggler(control); break; } + case "icon": { + const header = this.getChildControl("header"); + control = new qx.ui.basic.Image(); + header.addAt(control, 2); + break; + } } return control || this.base(arguments, id); }, @@ -187,12 +193,16 @@ qx.Class.define("osparc.component.widget.CollapsibleView", { } }, + _applyCaretSize: function(size) { + this.getChildControl("caret").setSource(this.__getCaretId(this.getCollapsed())); + }, + _applyTitle: function(title) { - this.getChildControl("title").setLabel(title); + this.getChildControl("title").setValue(title); }, - _applyCaretSize: function(size) { - this.getChildControl("caret").setSource(this.__getCaretId(this.getCollapsed())); + _applyIcon: function(icon) { + this.getChildControl("icon").setSource(icon); }, __getCaretId: function(collapsed) { diff --git a/services/web/client/source/class/osparc/component/widget/NodePorts.js b/services/web/client/source/class/osparc/component/widget/NodePorts.js index 7fba70ed21e..3518e512f87 100644 --- a/services/web/client/source/class/osparc/component/widget/NodePorts.js +++ b/services/web/client/source/class/osparc/component/widget/NodePorts.js @@ -44,7 +44,30 @@ qx.Class.define("osparc.component.widget.NodePorts", { this.base(arguments, node.getLabel()); + this.getTitleBar().set({ + height: 30 + }); node.bind("label", this, "title"); + + node.getStatus().bind("modified", this.getChildControl("icon"), "source", { + converter: modified => { + if (modified === null) { + return osparc.utils.StatusUI.getIconSource(); + } + return osparc.utils.StatusUI.getIconSource(modified === true ? "modified" : "up-to-date"); + } + }, this); + node.getStatus().bind("modified", this.getChildControl("icon"), "textColor", { + converter: modified => { + if (modified === null) { + return osparc.utils.StatusUI.getColor(); + } + return osparc.utils.StatusUI.getColor(modified === true ? "modified" : "up-to-date"); + } + }, this); + node.getStatus().bind("modified", this.getChildControl("icon"), "toolTipText", { + converter: modified => modified === true ? this.tr("Out of date") : "" + }, this); }, properties: { diff --git a/services/web/client/source/class/osparc/component/widget/logger/RemoteTableModel.js b/services/web/client/source/class/osparc/component/widget/logger/LoggerTable.js similarity index 82% rename from services/web/client/source/class/osparc/component/widget/logger/RemoteTableModel.js rename to services/web/client/source/class/osparc/component/widget/logger/LoggerTable.js index 881c33b7642..52e84060195 100644 --- a/services/web/client/source/class/osparc/component/widget/logger/RemoteTableModel.js +++ b/services/web/client/source/class/osparc/component/widget/logger/LoggerTable.js @@ -25,7 +25,7 @@ * Here is a little example of how to use the widget. * *
- *   let tableModel = this.__logModel = new osparc.component.widget.logger.RemoteTableModel();
+ *   let tableModel = this.__logModel = new osparc.component.widget.logger.LoggerTable();
  *   tableModel.setColumns(["Origin", "Message"], ["whoRich", "whatRich"]);
  *   let custom = {
  *     tableColumnModel : function(obj) {
@@ -42,14 +42,19 @@
  * @asset(demobrowser/backend/remote_table.php)
  */
 
-qx.Class.define("osparc.component.widget.logger.RemoteTableModel", {
-
+qx.Class.define("osparc.component.widget.logger.LoggerTable", {
   extend : qx.ui.table.model.Remote,
 
   construct : function() {
     this.base(arguments);
 
-    this.setColumns(["Origin", "Message"], ["whoRich", "msgRich"]);
+    this.setColumns([
+      "Origin",
+      "Message"
+    ], [
+      "whoRich",
+      "msgRich"
+    ]);
 
     this.__rawData = [];
   },
@@ -60,15 +65,11 @@ qx.Class.define("osparc.component.widget.logger.RemoteTableModel", {
       check : "Number",
       init: -1
     },
+
     filterString: {
       nullable: true,
       check : "String",
       init: ""
-    },
-    caseSensitive: {
-      nullable: false,
-      check : "Boolean",
-      init: false
     }
   },
 
@@ -85,8 +86,8 @@ qx.Class.define("osparc.component.widget.logger.RemoteTableModel", {
     addRows: function(newRows) {
       for (let i=0; i
- *   let loggerView = new osparc.component.widget.logger.LoggerView(workbench);
+ *   let loggerView = new osparc.component.widget.logger.LoggerView();
  *   this.getRoot().add(loggerView);
  *   loggerView.info(null, "Hello world");
  * 
@@ -62,8 +62,7 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", { this._setLayout(new qx.ui.layout.VBox()); - const filterToolbar = this.__createFilterToolbar(); - this._add(filterToolbar); + this.__createFilterToolbar(); const table = this.__createTableLayout(); this._add(table, { @@ -71,14 +70,8 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", { }); this.__messengerColors = new Set(); - - this.__createInitMsg(); - - this.__textFilterField.addListener("changeValue", this.__applyFilters, this); }, - events: {}, - properties: { logLevel: { apply : "__applyFilters", @@ -87,12 +80,6 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", { init: LOG_LEVEL[0].debug }, - caseSensitive: { - nullable: false, - check : "Boolean", - init: false - }, - currentNodeId: { check: "String", nullable: true, @@ -129,68 +116,99 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", { }, members: { - __currentNodeButton: null, __textFilterField: null, - __logModel: null, + __loggerModel: null, __logView: null, __messengerColors: null, + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "toolbar": + control = new qx.ui.toolbar.ToolBar(); + this._add(control); + break; + case "pin-node": { + const toolbar = this.getChildControl("toolbar"); + control = new qx.ui.form.ToggleButton().set({ + icon: "@FontAwesome5Solid/thumbtack/14", + toolTipText: this.tr("Show logs only from current node"), + appearance: "toolbar-button" + }); + toolbar.add(control); + break; + } + case "filter-text": { + const toolbar = this.getChildControl("toolbar"); + control = new qx.ui.form.TextField().set({ + appearance: "toolbar-textfield", + liveUpdate: true, + placeholder: this.tr("Filter") + }); + osparc.utils.Utils.setIdToWidget(control, "logsFilterField"); + toolbar.add(control, { + flex: 1 + }); + break; + } + case "log-level": { + const toolbar = this.getChildControl("toolbar"); + control = new qx.ui.form.SelectBox().set({ + appearance: "toolbar-selectbox", + maxWidth: 80 + }); + let logLevelSet = false; + for (let i=0; i { - // this.currectNodeClicked(currentNodeButton.getValue()); + const pinNode = this.getChildControl("pin-node"); + pinNode.addListener("changeValue", e => { this.currectNodeClicked(e.getData()); }, this); - toolbar.add(currentNodeButton); - toolbar.add(new qx.ui.toolbar.Separator()); - const textFilterField = this.__textFilterField = new qx.ui.form.TextField().set({ - appearance: "toolbar-textfield", - liveUpdate: true, - placeholder: this.tr("Filter") - }); - osparc.utils.Utils.setIdToWidget(textFilterField, "logsFilterField"); - toolbar.add(textFilterField, { - flex: 1 - }); + const textFilterField = this.__textFilterField = this.getChildControl("filter-text"); + textFilterField.addListener("changeValue", this.__applyFilters, this); - const logLevelSelectBox = new qx.ui.form.SelectBox().set({ - appearance: "toolbar-selectbox", - maxWidth: 80 - }); - let logLevelSet = false; - for (let i=0; i { this.setLogLevel(e.getData().logLevel); }, this); toolbar.add(logLevelSelectBox); - const copyToClipboardButton = new qx.ui.form.Button().set({ - icon: "@FontAwesome5Solid/copy/14", - toolTipText: this.tr("Copy logs to clipboard"), - appearance: "toolbar-button" - }); - osparc.utils.Utils.setIdToWidget(copyToClipboardButton, "copyLogsToClipboardButton"); + const copyToClipboardButton = this.getChildControl("copy-to-clipboard"); copyToClipboardButton.addListener("execute", e => { this.__copyLogsToClipboard(); }, this); @@ -200,7 +218,7 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", { }, __createTableLayout: function() { - const tableModel = this.__logModel = new osparc.component.widget.logger.RemoteTableModel(); + const loggerModel = this.__loggerModel = new osparc.component.widget.logger.LoggerTable(); const custom = { tableColumnModel : function(obj) { @@ -209,7 +227,7 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", { }; // table - const table = this.__logView = new qx.ui.table.Table(tableModel, custom).set({ + const table = this.__logView = new qx.ui.table.Table(loggerModel, custom).set({ selectable: true, statusBarVisible: false, showCellFocusIndicator: false @@ -230,7 +248,7 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", { }, __currentNodeIdChanged: function() { - this.__currentNodeButton.setValue(false); + this.getChildControl("pin-node").setValue(false); }, currectNodeClicked: function(checked) { @@ -254,7 +272,7 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", { __copyLogsToClipboard: function() { let logs = ""; - this.__logModel.getRows().forEach(row => { + this.__loggerModel.getRows().forEach(row => { logs += `(${row.nodeId}) ${row.label}: ${row.msg} \n`; }); osparc.utils.Utils.copyTextToClipboard(logs); @@ -293,7 +311,7 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", { label = node.getLabel(); node.addListener("changeLabel", e => { const newLabel = e.getData(); - this.__logModel.nodeLabelChanged(nodeId, newLabel); + this.__loggerModel.nodeLabelChanged(nodeId, newLabel); this.__updateTable(); }, this); } else { @@ -314,14 +332,14 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", { }; msgLogs.push(msgLog); } - this.__logModel.addRows(msgLogs); + this.__loggerModel.addRows(msgLogs); this.__updateTable(); }, __updateTable: function() { - this.__logModel.reloadData(); - const nFilteredRows = this.__logModel.getFilteredRowCount(); + this.__loggerModel.reloadData(); + const nFilteredRows = this.__loggerModel.getFilteredRowCount(); this.__logView.scrollCellVisible(0, nFilteredRows); }, @@ -337,19 +355,13 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", { }, __applyFilters: function() { - if (this.__logModel === null) { + if (this.__loggerModel === null) { return; } - this.__logModel.setFilterString(this.__textFilterField.getValue()); - this.__logModel.setFilterLogLevel(this.getLogLevel()); - this.__logModel.reloadData(); - }, - - __createInitMsg: function() { - const nodeId = null; - const msg = "Logger initialized"; - this.debug(nodeId, msg); + this.__loggerModel.setFilterString(this.__textFilterField.getValue()); + this.__loggerModel.setFilterLogLevel(this.getLogLevel()); + this.__loggerModel.reloadData(); } } }); diff --git a/services/web/client/source/class/osparc/component/workbench/EdgeUI.js b/services/web/client/source/class/osparc/component/workbench/EdgeUI.js index b79b515a946..2b1dad1a9be 100644 --- a/services/web/client/source/class/osparc/component/workbench/EdgeUI.js +++ b/services/web/client/source/class/osparc/component/workbench/EdgeUI.js @@ -44,6 +44,11 @@ qx.Class.define("osparc.component.workbench.EdgeUI", { this.setEdge(edge); this.setRepresentation(representation); + edge.getInputNode().getStatus().addListener("changeModified", () => { + this.__updateCurveColor(); + }); + this.__updateCurveColor(); + this.subscribeToFilterGroup("workbench"); }, @@ -59,6 +64,28 @@ qx.Class.define("osparc.component.workbench.EdgeUI", { }, members: { + __updateCurveColor: function() { + const modified = this.getEdge().getInputNode().getStatus() + .getModified(); + let newColor = null; + if (modified === null) { + newColor = qx.theme.manager.Color.getInstance().resolve("workbench-edge-comp-active"); + } else { + newColor = osparc.utils.StatusUI.getColor(modified ? "failed" : "ready"); + } + const newColorHex = qx.theme.manager.Color.getInstance().resolve(newColor); + osparc.component.workbench.SvgWidget.updateCurveColor(this.getRepresentation(), newColorHex); + }, + + setSelected: function(selected) { + if (selected) { + const selectedColor = qx.theme.manager.Color.getInstance().resolve("workbench-edge-selected"); + osparc.component.workbench.SvgWidget.updateCurveColor(this.getRepresentation(), selectedColor); + } else { + this.__updateCurveColor(); + } + }, + getEdgeId: function() { return this.getEdge().getEdgeId(); }, diff --git a/services/web/client/source/class/osparc/component/workbench/NodeUI.js b/services/web/client/source/class/osparc/component/workbench/NodeUI.js index 71307adfaa7..69323220986 100644 --- a/services/web/client/source/class/osparc/component/workbench/NodeUI.js +++ b/services/web/client/source/class/osparc/component/workbench/NodeUI.js @@ -192,6 +192,12 @@ qx.Class.define("osparc.component.workbench.NodeUI", { if (node.isComputational() || node.isFilePicker()) { node.getStatus().bind("progress", this.__progressBar, "value"); } + /* + node.getStatus().bind("running", this, "decorator", { + // Paint borders + converter: state => osparc.utils.StatusUI.getBorderDecorator(state) + }); + */ }, getInputPort: function() { @@ -212,6 +218,25 @@ qx.Class.define("osparc.component.workbench.NodeUI", { isInput: isInput, ui: portLabel }; + if (isInput) { + this.getNode().getStatus().bind("dependencies", portLabel, "textColor", { + converter: dependencies => { + if (dependencies !== null) { + return osparc.utils.StatusUI.getColor(dependencies.length ? "failed" : "ready"); + } + return osparc.utils.StatusUI.getColor(); + } + }); + } else { + this.getNode().getStatus().bind("modified", portLabel, "textColor", { + converter: modified => { + if (modified === null) { + return osparc.utils.StatusUI.getColor(); + } + return osparc.utils.StatusUI.getColor(modified ? "failed" : "ready"); + } + }); + } label.ui.isInput = isInput; this.__addDragDropMechanism(label.ui, isInput); if (isInput) { diff --git a/services/web/client/source/class/osparc/component/workbench/WorkbenchUI.js b/services/web/client/source/class/osparc/component/workbench/WorkbenchUI.js index 222a140d37b..bb4203ec021 100644 --- a/services/web/client/source/class/osparc/component/workbench/WorkbenchUI.js +++ b/services/web/client/source/class/osparc/component/workbench/WorkbenchUI.js @@ -969,17 +969,15 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { const oldId = this.__selectedItemId; if (oldId) { if (this.__isSelectedItemAnEdge()) { - const unselectedEdge = this.__getEdgeUI(oldId); - const unselectedColor = qx.theme.manager.Color.getInstance().getTheme().colors["workbench-edge-comp-active"]; - osparc.component.workbench.SvgWidget.updateCurveColor(unselectedEdge.getRepresentation(), unselectedColor); + const edge = this.__getEdgeUI(oldId); + edge.setSelected(false); } } this.__selectedItemId = newID; if (this.__isSelectedItemAnEdge()) { - const selectedEdge = this.__getEdgeUI(newID); - const selectedColor = qx.theme.manager.Color.getInstance().getTheme().colors["workbench-edge-selected"]; - osparc.component.workbench.SvgWidget.updateCurveColor(selectedEdge.getRepresentation(), selectedColor); + const edge = this.__getEdgeUI(newID); + edge.setSelected(true); } else if (newID) { this.fireDataEvent("changeSelectedNode", newID); } diff --git a/services/web/client/source/class/osparc/dashboard/StudyBrowser.js b/services/web/client/source/class/osparc/dashboard/StudyBrowser.js index 04629ce5a81..09ee7a3620d 100644 --- a/services/web/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/web/client/source/class/osparc/dashboard/StudyBrowser.js @@ -840,7 +840,10 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { __createConfirmWindow: function(isMulti) { const msg = isMulti ? this.tr("Are you sure you want to delete the studies?") : this.tr("Are you sure you want to delete the study?"); - return new osparc.ui.window.Confirmation(msg); + const confirmationWin = new osparc.ui.window.Confirmation(msg); + const confirmButton = confirmationWin.getChildControl("confirm-button"); + osparc.utils.Utils.setIdToWidget(confirmButton, "confirmDeleteStudyBtn"); + return confirmationWin; } } }); diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js index 61d2ef5df59..e8b6b730099 100644 --- a/services/web/client/source/class/osparc/data/model/Node.js +++ b/services/web/client/source/class/osparc/data/model/Node.js @@ -141,6 +141,12 @@ qx.Class.define("osparc.data.model.Node", { event: "changeOutputs" }, + status: { + check: "osparc.data.model.NodeStatus", + nullable: false + }, + + // GUI elements // propsForm: { check: "osparc.component.form.renderer.PropForm", init: null, @@ -171,10 +177,12 @@ qx.Class.define("osparc.data.model.Node", { nullable: true }, - status: { - check: "osparc.data.model.NodeStatus", - nullable: false + logger: { + check: "osparc.component.widget.logger.LoggerView", + init: null, + nullable: true } + // GUI elements // }, events: { @@ -333,25 +341,11 @@ qx.Class.define("osparc.data.model.Node", { if (nodeData.label) { this.setLabel(nodeData.label); } - this.populateInputOutputData(nodeData); - - if (nodeData.state) { - if (nodeData.state.currentStatus) { - this.getStatus().setRunningStatus(nodeData.state.currentStatus); - } - if (nodeData.state.modified) { - this.getStatus().setModifiedStatus(nodeData.state.modified); - } - if (nodeData.state.dependencies) { - this.getStatus().setDependenciesStatus(nodeData.state.dependencies); - } - } - if ("progress" in nodeData) { this.getStatus().setProgress(nodeData.progress); } - + this.populateStates(nodeData); if (nodeData.thumbnail) { this.setThumbnail(nodeData.thumbnail); } @@ -364,8 +358,8 @@ qx.Class.define("osparc.data.model.Node", { this.__outputWidget.populatePortsData(); } + this.__initLogger(); if (this.isDynamic()) { - this.__initLoadingIPage(); this.__initIFrame(); } }, @@ -384,6 +378,24 @@ qx.Class.define("osparc.data.model.Node", { this.addOutputNodes(nodeData.outputNodes); }, + populateStates: function(nodeData) { + if ("state" in nodeData) { + if ("dependencies" in nodeData.state) { + this.getStatus().setDependencies(nodeData.state.dependencies); + } + if ("currentStatus" in nodeData.state && this.isComputational()) { + this.getStatus().setRunning(nodeData.state.currentStatus); + } + if ("modified" in nodeData.state) { + if (this.getStatus().getHasOutputs()) { + this.getStatus().setModified(nodeData.state.modified || this.getStatus().hasDependencies()); + } else { + this.getStatus().setModified(null); + } + } + } + }, + giveUniqueName: function() { const label = this.getLabel(); this.__giveUniqueName(label, 2); @@ -428,7 +440,7 @@ qx.Class.define("osparc.data.model.Node", { msg: errorMsg }; this.fireDataEvent("showInLogger", errorMsgData); - this.getStatus().setInteractiveStatus("failed"); + this.getStatus().setInteractive("failed"); osparc.component.message.FlashMessenger.getInstance().logAs(this.tr("There was an error while starting the node."), "ERROR"); }); }, @@ -641,6 +653,8 @@ qx.Class.define("osparc.data.model.Node", { this.getOutputs()[outputKey]["value"] = ""; } } + this.getStatus().setHasOutputs(true); + this.fireDataEvent("changeOutputs", this.getOutputs()); } }, @@ -778,8 +792,12 @@ qx.Class.define("osparc.data.model.Node", { return true; }, + __initLogger: function() { + this.setLogger(new osparc.component.widget.logger.LoggerView()); + }, + __getLoadingPageHeader: function() { - const status = this.getStatus().getInteractiveStatus(); + const status = this.getStatus().getInteractive(); const label = this.getLabel(); if (status) { const sta = status.charAt(0).toUpperCase() + status.slice(1); @@ -794,13 +812,15 @@ qx.Class.define("osparc.data.model.Node", { this.addListener("changeLabel", e => { loadingPage.setHeader(this.__getLoadingPageHeader()); }, this); - this.getStatus().addListener("changeInteractiveStatus", e => { + this.getStatus().addListener("changeInteractive", e => { loadingPage.setHeader(this.__getLoadingPageHeader()); }, this); this.setLoadingPage(loadingPage); }, __initIFrame: function() { + this.__initLoadingIPage(); + const iframe = new osparc.component.widget.PersistentIframe(); osparc.utils.Utils.setIdToWidget(iframe, "PersistentIframe"); iframe.addListener("restart", () => { @@ -933,7 +953,7 @@ qx.Class.define("osparc.data.model.Node", { const status = this.getStatus(); status.setProgress(0); - status.setInteractiveStatus("starting"); + status.setInteractive("starting"); this.__nodeState(); } @@ -943,20 +963,20 @@ qx.Class.define("osparc.data.model.Node", { const status = this.getStatus(); switch (serviceState) { case "idle": { - status.setInteractiveStatus("idle"); + status.setInteractive("idle"); const interval = 1000; qx.event.Timer.once(() => this.__nodeState(), this, interval); break; } case "starting": case "pulling": { - status.setInteractiveStatus(serviceState); + status.setInteractive(serviceState); const interval = 5000; qx.event.Timer.once(() => this.__nodeState(), this, interval); break; } case "pending": { - status.setInteractiveStatus("pending"); + status.setInteractive("pending"); const interval = 10000; qx.event.Timer.once(() => this.__nodeState(), this, interval); break; @@ -978,7 +998,7 @@ qx.Class.define("osparc.data.model.Node", { case "complete": break; case "failed": { - status.setInteractiveStatus("failed"); + status.setInteractive("failed"); const msg = "Service failed: " + data["service_message"]; const msgData = { nodeId: this.getNodeId(), @@ -1019,7 +1039,7 @@ qx.Class.define("osparc.data.model.Node", { msg: errorMsg }; this.fireDataEvent("showInLogger", errorMsgData); - this.getStatus().setInteractiveStatus("failed"); + this.getStatus().setInteractive("failed"); osparc.component.message.FlashMessenger.getInstance().logAs(this.tr("There was an error while starting the node."), "ERROR"); }); }, @@ -1049,7 +1069,7 @@ qx.Class.define("osparc.data.model.Node", { }, this); pingRequest.addListenerOnce("fail", e => { const error = e.getTarget().getResponse(); - this.getStatus().setInteractiveStatus("connecting"); + this.getStatus().setInteractive("connecting"); console.log("service not ready yet, waiting... " + error); // Check if node is still there const study = osparc.store.Store.getInstance().getCurrentStudy(); @@ -1063,7 +1083,7 @@ qx.Class.define("osparc.data.model.Node", { }, __serviceReadyIn: function(srvUrl) { this.setServiceUrl(srvUrl); - this.getStatus().setInteractiveStatus("ready"); + this.getStatus().setInteractive("ready"); const msg = "Service ready on " + srvUrl; const msgData = { nodeId: this.getNodeId(), diff --git a/services/web/client/source/class/osparc/data/model/NodeStatus.js b/services/web/client/source/class/osparc/data/model/NodeStatus.js index f88cf97573e..8beb877109d 100644 --- a/services/web/client/source/class/osparc/data/model/NodeStatus.js +++ b/services/web/client/source/class/osparc/data/model/NodeStatus.js @@ -34,28 +34,62 @@ qx.Class.define("osparc.data.model.NodeStatus", { event: "changeProgress" }, - runningStatus: { + running: { check: ["UNKNOWN", "NOT_STARTED", "PUBLISHED", "PENDING", "STARTED", "RETRY", "SUCCESS", "FAILED", "ABORTED"], nullable: true, - event: "changeRunningStatus" + init: null, + event: "changeRunning" }, - interactiveStatus: { + interactive: { check: ["idle", "starting", "pulling", "pending", "connecting", "ready", "failed"], nullable: true, - event: "changeInteractiveStatus" + init: null, + event: "changeInteractive" }, - modifiedStatus: { - check: "Boolean", + dependencies: { + check: "Array", nullable: true, - event: "changeModifiedStatus" + init: null, + event: "changeDependencies", + apply: "__applyDependencies" }, - dependenciesStatus: { - check: "Array", + modified: { + check: "Boolean", nullable: true, - event: "changeDependenciesStatus" + init: null, + event: "changeModified", + apply: "__applyModified" + }, + + hasOutputs: { + check: "Boolean", + init: false, + apply: "__applyModified" + } + }, + + members: { + hasDependencies: function() { + const dependencies = this.getDependencies(); + if (dependencies && dependencies.length) { + return true; + } + return false; + }, + + __applyDependencies: function() { + this.__applyModified(this.hasDependencies()); + }, + + __applyModified: function(modified) { + if (this.getHasOutputs()) { + this.setModified(modified || this.hasDependencies()); + } else { + this.setModified(null); + } } } }); diff --git a/services/web/client/source/class/osparc/data/model/Study.js b/services/web/client/source/class/osparc/data/model/Study.js index 23705509cb0..668d2dd1748 100644 --- a/services/web/client/source/class/osparc/data/model/Study.js +++ b/services/web/client/source/class/osparc/data/model/Study.js @@ -154,7 +154,8 @@ qx.Class.define("osparc.data.model.Study", { state: { check: "Object", - nullable: true + nullable: true, + event: "changeState" }, quality: { @@ -189,20 +190,6 @@ qx.Class.define("osparc.data.model.Study", { return myNewStudyObject; }, - updateStudy: function(params) { - return new Promise(resolve => { - osparc.data.Resources.fetch("studies", "put", { - url: { - projectId: params.uuid - }, - data: params - }).then(data => { - qx.event.message.Bus.getInstance().dispatchByName("updateStudy", data); - resolve(data); - }); - }); - }, - getProperties: function() { return Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.Study)); }, @@ -313,16 +300,22 @@ qx.Class.define("osparc.data.model.Study", { return jsonObject; }, - updateStudy: function(params) { + updateStudy: function(params, run = false) { return new Promise(resolve => { - this.self().updateStudy({ - ...this.serialize(), - ...params - }) - .then(data => { - this.__updateModel(data); - resolve(data); - }); + osparc.data.Resources.fetch("studies", "put", { + url: { + projectId: this.getUuid(), + run + }, + data: { + ...this.serialize(), + ...params + } + }).then(data => { + this.__updateModel(data); + qx.event.message.Bus.getInstance().dispatchByName("updateStudy", data); + resolve(data); + }); }); }, @@ -338,6 +331,14 @@ qx.Class.define("osparc.data.model.Study", { ui: this.getUi(), sweeper: this.getSweeper() }); + + const nodes = this.getWorkbench().getNodes(true); + Object.values(nodes).forEach(node => { + const nodeId = node.getNodeId(); + if (nodeId in data.workbench) { + node.populateStates(data.workbench[nodeId]); + } + }); } } }); diff --git a/services/web/client/source/class/osparc/data/model/Workbench.js b/services/web/client/source/class/osparc/data/model/Workbench.js index 72ad28f4506..1de5580ee52 100644 --- a/services/web/client/source/class/osparc/data/model/Workbench.js +++ b/services/web/client/source/class/osparc/data/model/Workbench.js @@ -91,7 +91,7 @@ qx.Class.define("osparc.data.model.Workbench", { getNodes: function(recursive = false) { let nodes = Object.assign({}, this.__rootNodes); - if (recursive) { + if (recursive && this.__rootNodes) { let topLevelNodes = Object.values(this.__rootNodes); for (const topLevelNode of topLevelNodes) { let innerNodes = topLevelNode.getInnerNodes(true); @@ -340,20 +340,20 @@ qx.Class.define("osparc.data.model.Workbench", { }, __deserializeNodes: function(workbenchData, workbenchUIData = {}) { - let keys = Object.keys(workbenchData); + const nodeIds = Object.keys(workbenchData); // Create first all the nodes - for (let i=0; i 1) { - if (keys[nKeys-1] === keys[nKeys-2]) { + if (nodeIds[nKeys-1] === nodeIds[nKeys-2]) { console.log(nodeId, "will never be created, parent missing", nodeData.parent); return; } @@ -371,21 +371,23 @@ qx.Class.define("osparc.data.model.Workbench", { } // Then populate them (this will avoid issues of connecting nodes that might not be created yet) - for (let i=0; i { + this.getNode(nodeId).giveUniqueName(); + }); + }, + + __populateNodesData: function(workbenchData, workbenchUIData) { + Object.entries(workbenchData).forEach(([nodeId, nodeData]) => { this.getNode(nodeId).populateNodeData(nodeData); if ("position" in nodeData) { this.getNode(nodeId).populateNodeUIData(nodeData); } - if ("workbench" in workbenchUIData && nodeId in workbenchUIData.workbench) { + if (workbenchUIData && "workbench" in workbenchUIData && nodeId in workbenchUIData.workbench) { this.getNode(nodeId).populateNodeUIData(workbenchUIData.workbench[nodeId]); } - } - for (let i=0; i { + this.fireDataEvent("nodeSelected", e.getData()); + }, this); + control = new qx.ui.container.Scroll(); + control.add(breadcrumbNavigation); + this._add(control, { + flex: 1 + }); + break; + } case "prev-next-btns": { control = new osparc.navigation.PrevNextButtons(); control.addListener("nodeSelected", e => { diff --git a/services/web/client/source/class/osparc/desktop/SlideShowView.js b/services/web/client/source/class/osparc/desktop/SlideShowView.js index 8b53c0eeb17..a64a1fc7994 100644 --- a/services/web/client/source/class/osparc/desktop/SlideShowView.js +++ b/services/web/client/source/class/osparc/desktop/SlideShowView.js @@ -31,6 +31,10 @@ qx.Class.define("osparc.desktop.SlideShowView", { this._add(slideShowToolbar); }, + events: { + "startPartialPipeline": "qx.event.type.Data" + }, + properties: { study: { check: "osparc.data.model.Study", @@ -52,12 +56,36 @@ qx.Class.define("osparc.desktop.SlideShowView", { return [this.__currentNodeId]; }, - nodeSelected: function(nodeId) { - this.__currentNodeId = nodeId; - this.getStudy().getUi().setCurrentNodeId(nodeId); + __isNodeReady: function(node, oldCurrentNodeId) { + const dependencies = node.getStatus().getDependencies(); + if (dependencies && dependencies.length) { + const msg = this.tr("Do you want to run the required steps?"); + const win = new osparc.ui.window.Confirmation(msg); + win.center(); + win.open(); + win.addListener("close", () => { + if (win.getConfirmed()) { + this.fireDataEvent("startPartialPipeline", dependencies); + } + // bring the user back to the old node or to the first dependency + if (oldCurrentNodeId === this.__currentNodeId) { + this.nodeSelected(dependencies[0]); + } else { + this.nodeSelected(oldCurrentNodeId); + } + }, this); + return false; + } + return true; + }, + nodeSelected: function(nodeId) { const node = this.getStudy().getWorkbench().getNode(nodeId); if (node) { + const oldCurrentNodeId = this.__currentNodeId; + this.__currentNodeId = nodeId; + this.getStudy().getUi().setCurrentNodeId(nodeId); + let view; if (node.isContainer()) { view = new osparc.component.node.GroupNodeView(); @@ -79,6 +107,10 @@ qx.Class.define("osparc.desktop.SlideShowView", { }); this.__lastView = view; } + // check if upstream has to be run + if (!this.__isNodeReady(node, oldCurrentNodeId)) { + return; + } } this.getStudy().getUi().setCurrentNodeId(nodeId); diff --git a/services/web/client/source/class/osparc/desktop/StartStopButtons.js b/services/web/client/source/class/osparc/desktop/StartStopButtons.js index 78b5f172fc3..ac1da0d49dc 100644 --- a/services/web/client/source/class/osparc/desktop/StartStopButtons.js +++ b/services/web/client/source/class/osparc/desktop/StartStopButtons.js @@ -41,6 +41,15 @@ qx.Class.define("osparc.desktop.StartStopButtons", { this.__initDefault(); }, + properties: { + study: { + check: "osparc.data.model.Study", + apply: "__applyStudy", + init: null, + nullable: false + } + }, + events: { "startPipeline": "qx.event.type.Event", "startPartialPipeline": "qx.event.type.Event", @@ -83,36 +92,6 @@ qx.Class.define("osparc.desktop.StartStopButtons", { visibility: "excluded" }); this._add(startSplitButton); - - osparc.store.Store.getInstance().addListener("changeCurrentStudy", e => { - const study = e.getData(); - this.__updateRunButtonsStatus(study); - }); - }, - - __updateRunButtonsStatus: function(study) { - if (study) { - const startButtons = [this.__startButton, this.__startSelectionButton.getChildControl("button"), this.__startAllButton]; - const stopButton = this.__stopButton; - if (study.getState() && study.getState().state) { - const pipelineState = study.getState().state; - switch (pipelineState.value) { - case "PENDING": - case "PUBLISHED": - case "STARTED": - startButtons.forEach(startButton => startButton.setFetching(true)); - stopButton.setEnabled(true); - break; - case "NOT_STARTED": - case "SUCCESS": - case "FAILED": - default: - startButtons.forEach(startButton => startButton.setFetching(false)); - stopButton.setEnabled(false); - break; - } - } - } }, __createStartButton: function() { @@ -153,6 +132,44 @@ qx.Class.define("osparc.desktop.StartStopButtons", { this.fireEvent("stopPipeline"); }, this); return stopButton; + }, + + __applyStudy: function(study) { + this.__checkButtonsVisible(); + + study.addListener("changeState", () => { + this.__updateRunButtonsStatus(); + }, this); + }, + + __checkButtonsVisible: function() { + this.setVisibility(this.getStudy().isReadOnly() ? "excluded" : "visible"); + }, + + __updateRunButtonsStatus: function() { + const study = this.getStudy(); + if (study) { + const startButtons = [this.__startButton, this.__startSelectionButton.getChildControl("button"), this.__startAllButton]; + const stopButton = this.__stopButton; + if (study.getState() && study.getState().state) { + const pipelineState = study.getState().state.value; + switch (pipelineState) { + case "PENDING": + case "PUBLISHED": + case "STARTED": + startButtons.forEach(startButton => startButton.setFetching(true)); + stopButton.setEnabled(true); + break; + case "NOT_STARTED": + case "SUCCESS": + case "FAILED": + default: + startButtons.forEach(startButton => startButton.setFetching(false)); + stopButton.setEnabled(false); + break; + } + } + } } } }); diff --git a/services/web/client/source/class/osparc/desktop/StudyEditor.js b/services/web/client/source/class/osparc/desktop/StudyEditor.js index 2dd1b64da67..1cc6ade2ce5 100644 --- a/services/web/client/source/class/osparc/desktop/StudyEditor.js +++ b/services/web/client/source/class/osparc/desktop/StudyEditor.js @@ -34,12 +34,22 @@ qx.Class.define("osparc.desktop.StudyEditor", { const slideshowView = this.__slideshowView = new osparc.desktop.SlideShowView(); viewsStack.add(slideshowView); + slideshowView.addListener("startPartialPipeline", e => { + const partialPipeline = e.getData(); + this.__startPipeline(partialPipeline); + }, this); + [ workbenchView.getStartStopButtons(), slideshowView.getStartStopButtons() ].forEach(startStopButtons => { - startStopButtons.addListener("startPipeline", this.__startPipeline, this); - startStopButtons.addListener("startPartialPipeline", () => this.__startPipeline(false), this); + startStopButtons.addListener("startPipeline", () => { + this.__startPipeline([]); + }, this); + startStopButtons.addListener("startPartialPipeline", () => { + const partialPipeline = this.getPageContext() === "workbench" ? this.__workbenchView.getSelectedNodeIDs() : this.__slideshowView.getSelectedNodeIDs(); + this.__startPipeline(partialPipeline); + }, this); startStopButtons.addListener("stopPipeline", this.__stopPipeline, this); }); @@ -156,7 +166,7 @@ qx.Class.define("osparc.desktop.StudyEditor", { // ------------------ START/STOP PIPELINE ------------------ - __startPipeline: function(runAll = true) { + __startPipeline: function(partialPipeline = []) { if (!osparc.data.Permissions.getInstance().canDo("study.start", true)) { return; } @@ -167,64 +177,67 @@ qx.Class.define("osparc.desktop.StudyEditor", { startStopButtonsSS.setRunning(true); this.updateStudyDocument(true) .then(() => { - this.__doStartPipeline(runAll); + this.__doStartPipeline(partialPipeline); }) .catch(() => { - this.getLogger().error(null, "Run failed"); + this.__getStudyLogger().error(null, "Run failed"); startStopButtonsWB.setRunning(false); startStopButtonsSS.setRunning(false); }); }, - __doStartPipeline: function(runAll) { + __doStartPipeline: function(partialPipeline) { if (this.getStudy().getSweeper().hasSecondaryStudies()) { const secondaryStudyIds = this.getStudy().getSweeper().getSecondaryStudyIds(); secondaryStudyIds.forEach(secondaryStudyId => { this.__requestStartPipeline(secondaryStudyId); }); - } else if (runAll) { - this.__requestStartPipeline(this.getStudy().getUuid()); } else { - const selectedNodeIDs = this.getPageContext() === "workbench" ? this.__workbenchView.getSelectedNodeIDs() : this.__slideshowView.getSelectedNodeIDs(); - if (selectedNodeIDs === null || selectedNodeIDs.length === 0) { - this.__requestStartPipeline(this.getStudy().getUuid()); - } else { - this.__requestStartPipeline(this.getStudy().getUuid(), selectedNodeIDs); - } + this.__requestStartPipeline(this.getStudy().getUuid(), partialPipeline); } }, - __requestStartPipeline: function(studyId, selectedNodeIDs = []) { + __requestStartPipeline: function(studyId, partialPipeline = [], forceRestart = false) { const url = "/computation/pipeline/" + encodeURIComponent(studyId) + ":start"; const req = new osparc.io.request.ApiRequest(url, "POST"); const startStopButtonsWB = this.__workbenchView.getStartStopButtons(); const startStopButtonsSS = this.__slideshowView.getStartStopButtons(); req.addListener("success", this.__onPipelinesubmitted, this); req.addListener("error", e => { - this.getLogger().error(null, "Error submitting pipeline"); + this.__getStudyLogger().error(null, "Error submitting pipeline"); startStopButtonsWB.setRunning(false); startStopButtonsSS.setRunning(false); }, this); req.addListener("fail", e => { if (e.getTarget().getStatus() == "403") { - this.getLogger().error(null, "Pipeline is already running"); + this.__getStudyLogger().error(null, "Pipeline is already running"); } else if (e.getTarget().getStatus() == "422") { - this.getLogger().info(null, "The pipeline is up-to-date"); + this.__getStudyLogger().info(null, "The pipeline is up-to-date"); + const msg = this.tr("The pipeline is up-to-date. Do you want to re-run it?"); + const win = new osparc.ui.window.Confirmation(msg); + win.center(); + win.open(); + win.addListener("close", () => { + if (win.getConfirmed()) { + this.__requestStartPipeline(studyId, partialPipeline, true); + } + }, this); } else { - this.getLogger().error(null, "Failed submitting pipeline"); + this.__getStudyLogger().error(null, "Failed submitting pipeline"); } startStopButtonsWB.setRunning(false); startStopButtonsSS.setRunning(false); }, this); - if (selectedNodeIDs.length) { - req.setRequestData({ - "subgraph": selectedNodeIDs - }); - req.send(); - this.getLogger().info(null, "Starting partial pipeline"); + + req.setRequestData({ + "subgraph": partialPipeline, + "force_restart": forceRestart + }); + req.send(); + if (partialPipeline.length) { + this.__getStudyLogger().info(null, "Starting partial pipeline"); } else { - req.send(); - this.getLogger().info(null, "Starting pipeline"); + this.__getStudyLogger().info(null, "Starting pipeline"); } return true; @@ -233,12 +246,12 @@ qx.Class.define("osparc.desktop.StudyEditor", { __onPipelinesubmitted: function(e) { const resp = e.getTarget().getResponse(); const pipelineId = resp.data["pipeline_id"]; - this.getLogger().debug(null, "Pipeline ID " + pipelineId); + this.__getStudyLogger().debug(null, "Pipeline ID " + pipelineId); const notGood = [null, undefined, -1]; if (notGood.includes(pipelineId)) { - this.getLogger().error(null, "Submission failed"); + this.__getStudyLogger().error(null, "Submission failed"); } else { - this.getLogger().info(null, "Pipeline started"); + this.__getStudyLogger().info(null, "Pipeline started"); /* If no projectStateUpdated comes in 60 seconds, client must check state of pipeline and update button accordingly. */ const timer = setTimeout(() => { @@ -277,17 +290,17 @@ qx.Class.define("osparc.desktop.StudyEditor", { const url = "/computation/pipeline/" + encodeURIComponent(studyId) + ":stop"; const req = new osparc.io.request.ApiRequest(url, "POST"); req.addListener("success", e => { - this.getLogger().debug(null, "Pipeline aborting"); + this.__getStudyLogger().debug(null, "Pipeline aborting"); }, this); req.addListener("error", e => { - this.getLogger().error(null, "Error stopping pipeline"); + this.__getStudyLogger().error(null, "Error stopping pipeline"); }, this); req.addListener("fail", e => { - this.getLogger().error(null, "Failed stopping pipeline"); + this.__getStudyLogger().error(null, "Failed stopping pipeline"); }, this); req.send(); - this.getLogger().info(null, "Stopping pipeline"); + this.__getStudyLogger().info(null, "Stopping pipeline"); return true; }, // ------------------ START/STOP PIPELINE ------------------ @@ -295,12 +308,12 @@ qx.Class.define("osparc.desktop.StudyEditor", { __updatePipelineAndRetrieve: function(node, portKey = null) { this.updateStudyDocument(false) .then(() => { - this.getLogger().debug(null, "Retrieveing inputs"); + this.__getStudyLogger().debug(null, "Retrieveing inputs"); if (node) { node.retrieveInputs(portKey); } }); - this.getLogger().debug(null, "Updating pipeline"); + this.__getStudyLogger().debug(null, "Updating pipeline"); }, // overridden @@ -321,7 +334,7 @@ qx.Class.define("osparc.desktop.StudyEditor", { this.__slideshowView.nodeSelected(nodeId); }, - getLogger: function() { + __getStudyLogger: function() { return this.__workbenchView.getLogger(); }, @@ -340,19 +353,24 @@ qx.Class.define("osparc.desktop.StudyEditor", { __startAutoSaveTimer: function() { let diffPatcher = osparc.wrapper.JsonDiffPatch.getInstance(); - // Save every 5 seconds - const interval = 5000; + // Save every 3 seconds + const interval = 3000; let timer = this.__autoSaveTimer = new qx.event.Timer(interval); timer.addListener("interval", () => { const newObj = this.getStudy().serialize(); const delta = diffPatcher.diff(this.__lastSavedStudy, newObj); if (delta) { let deltaKeys = Object.keys(delta); - // lastChangeDate should not be taken into account as data change - const index = deltaKeys.indexOf("lastChangeDate"); - if (index > -1) { - deltaKeys.splice(index, 1); - } + // lastChangeDate and creationDate should not be taken into account as data change + [ + "creationDate", + "lastChangeDate" + ].forEach(prop => { + const index = deltaKeys.indexOf(prop); + if (index > -1) { + deltaKeys.splice(index, 1); + } + }); if (deltaKeys.length > 0) { this.updateStudyDocument(false); } @@ -376,24 +394,15 @@ qx.Class.define("osparc.desktop.StudyEditor", { }); } - this.getStudy().setLastChangeDate(new Date()); const newObj = this.getStudy().serialize(); - const prjUuid = this.getStudy().getUuid(); - - const params = { - url: { - projectId: prjUuid, - run - }, - data: newObj - }; - return osparc.data.Resources.fetch("studies", "put", params) + return this.getStudy().updateStudy(newObj, run) .then(data => { this.__lastSavedStudy = osparc.wrapper.JsonDiffPatch.getInstance().clone(newObj); - }).catch(error => { + }) + .catch(error => { console.error(error); osparc.component.message.FlashMessenger.getInstance().logAs(this.tr("Error saving the study"), "ERROR"); - this.getLogger().error(null, "Error updating pipeline"); + this.__getStudyLogger().error(null, "Error updating pipeline"); // Need to throw the error to be able to handle it later throw error; }); diff --git a/services/web/client/source/class/osparc/desktop/Toolbar.js b/services/web/client/source/class/osparc/desktop/Toolbar.js index f0a6cd70047..a17db525221 100644 --- a/services/web/client/source/class/osparc/desktop/Toolbar.js +++ b/services/web/client/source/class/osparc/desktop/Toolbar.js @@ -59,29 +59,17 @@ qx.Class.define("osparc.desktop.Toolbar", { _createChildControlImpl: function(id) { let control; switch (id) { - case "breadcrumb-navigation": { - const breadcrumbNavigation = this._navNodes = new osparc.navigation.BreadcrumbNavigation(); - breadcrumbNavigation.addListener("nodeSelected", e => { - this.fireDataEvent("nodeSelected", e.getData()); - }, this); - control = new qx.ui.container.Scroll(); - control.add(breadcrumbNavigation); - this._add(control, { - flex: 1 - }); - break; - } case "start-stop-btns": { control = new osparc.desktop.StartStopButtons(); - control.addListener("startPipeline", () => { - this.fireEvent("startPipeline"); - }, this); - control.addListener("startPartialPipeline", () => { - this.fireEvent("startPartialPipeline"); - }, this); - control.addListener("stopPipeline", () => { - this.fireEvent("stopPipeline"); - }, this); + [ + "startPipeline", + "startPartialPipeline", + "stopPipeline" + ].forEach(signalName => { + control.addListener(signalName, () => { + this.fireEvent(signalName); + }, this); + }); this._add(control); break; } @@ -94,7 +82,7 @@ qx.Class.define("osparc.desktop.Toolbar", { study.getUi().addListener("changeCurrentNodeId", () => { this._populateNodesNavigationLayout(); }); - this._startStopBtns.setVisibility(study.isReadOnly() ? "excluded" : "visible"); + this._startStopBtns.setStudy(study); this._populateNodesNavigationLayout(); } diff --git a/services/web/client/source/class/osparc/desktop/WorkbenchToolbar.js b/services/web/client/source/class/osparc/desktop/WorkbenchToolbar.js index 337b001ec79..1209c1913ba 100644 --- a/services/web/client/source/class/osparc/desktop/WorkbenchToolbar.js +++ b/services/web/client/source/class/osparc/desktop/WorkbenchToolbar.js @@ -32,6 +32,18 @@ qx.Class.define("osparc.desktop.WorkbenchToolbar", { _createChildControlImpl: function(id) { let control; switch (id) { + case "breadcrumb-navigation": { + const breadcrumbNavigation = this._navNodes = new osparc.navigation.BreadcrumbsWorkbench(); + breadcrumbNavigation.addListener("nodeSelected", e => { + this.fireDataEvent("nodeSelected", e.getData()); + }, this); + control = new qx.ui.container.Scroll(); + control.add(breadcrumbNavigation); + this._add(control, { + flex: 1 + }); + break; + } case "sweeper-btn": { control = new qx.ui.form.Button(this.tr("Sweeper"), "@FontAwesome5Solid/paw/14").set({ toolTipText: this.tr("Sweeper"), diff --git a/services/web/client/source/class/osparc/desktop/WorkbenchView.js b/services/web/client/source/class/osparc/desktop/WorkbenchView.js index ab5f2747682..f32b5f5ccf6 100644 --- a/services/web/client/source/class/osparc/desktop/WorkbenchView.js +++ b/services/web/client/source/class/osparc/desktop/WorkbenchView.js @@ -85,7 +85,10 @@ qx.Class.define("osparc.desktop.WorkbenchView", { }, getSelectedNodeIDs: function() { - return this.__workbenchUI.getSelectedNodeIDs(); + if (this.__mainPanel.getMainView() === this.__workbenchUI) { + return this.__workbenchUI.getSelectedNodeIDs(); + } + return [this.__currentNodeId]; }, nodeSelected: function(nodeId) { @@ -132,6 +135,16 @@ qx.Class.define("osparc.desktop.WorkbenchView", { return this.__loggerView; }, + __getNodeLogger: function(nodeId) { + const nodes = this.getStudy().getWorkbench().getNodes(true); + for (const node of Object.values(nodes)) { + if (nodeId === node.getNodeId()) { + return node.getLogger(); + } + } + return null; + }, + __editSlides: function() { const uiData = this.getStudy().getUi(); const nodesSlidesTree = new osparc.component.widget.NodesSlidesTree(uiData.getSlideshow()); @@ -316,9 +329,9 @@ qx.Class.define("osparc.desktop.WorkbenchView", { flex: 1 }); - const loggerView = this.__loggerView = new osparc.component.widget.logger.LoggerView(study.getWorkbench()); + const loggerView = this.__loggerView = new osparc.component.widget.logger.LoggerView(); const loggerPanel = new osparc.desktop.PanelView(this.tr("Logger"), loggerView); - osparc.utils.Utils.setIdToWidget(loggerPanel.getTitleLabel(), "loggerTitleLabel"); + osparc.utils.Utils.setIdToWidget(loggerPanel.getTitleLabel(), "studyLoggerTitleLabel"); this.__sidePanel.addOrReplaceAt(loggerPanel, 2, { flex: 1 }); @@ -501,7 +514,13 @@ qx.Class.define("osparc.desktop.WorkbenchView", { // Filtering out logs from other studies return; } - this.getLogger().infos(data["Node"], data["Messages"]); + const nodeId = data["Node"]; + const messages = data["Messages"]; + this.getLogger().infos(nodeId, messages); + const nodeLogger = this.__getNodeLogger(nodeId); + if (nodeLogger) { + nodeLogger.infos(nodeId, messages); + } }, this); } socket.emit(slotName); @@ -534,13 +553,11 @@ qx.Class.define("osparc.desktop.WorkbenchView", { const node = workbench.getNode(nodeId); if (node && nodeData) { node.setOutputData(nodeData.outputs); - if ("state" in nodeData && node.isComputational()) { - node.getStatus().setRunningStatus(nodeData["state"]["currentStatus"]); - } if ("progress" in nodeData) { const progress = Number.parseInt(nodeData["progress"]); node.getStatus().setProgress(progress); } + node.populateStates(nodeData); } else if (osparc.data.Permissions.getInstance().isTester()) { console.log("Ignored ws 'nodeUpdated' msg", d); } diff --git a/services/web/client/source/class/osparc/navigation/BreadcrumbNavigation.js b/services/web/client/source/class/osparc/navigation/BreadcrumbNavigation.js index 299d6f808c6..e352c4b3952 100644 --- a/services/web/client/source/class/osparc/navigation/BreadcrumbNavigation.js +++ b/services/web/client/source/class/osparc/navigation/BreadcrumbNavigation.js @@ -37,33 +37,21 @@ qx.Class.define("osparc.navigation.BreadcrumbNavigation", { }, members: { - populateButtons: function(nodesIds = [], shape = "slash") { - const btns = []; - if (shape === "slash") { - for (let i=0; i (pos+1).toString() + "- " + val - }); - node.bind("label", btn, "toolTipText"); - - const nsUI = new osparc.ui.basic.NodeStatusUI(node); - const nsUIIcon = nsUI.getChildControl("icon"); - // Hacky, aber schön - // eslint-disable-next-line no-underscore-dangle - btn._add(nsUIIcon); - const nsUILabel = nsUI.getChildControl("label"); - nsUILabel.addListener("changeValue", e => { - const statusLabel = e.getData(); - if (statusLabel) { - btn.setToolTipText(`${node.getLabel()} - ${statusLabel}`); - } - }, this); - if (nsUILabel.getValue()) { - btn.setToolTipText(`${node.getLabel()} - ${nsUILabel.getValue()}`); - } - } - return btn; - }, - - __buttonsToBreadcrumb: function(btns, shape = "slash") { + _buttonsToBreadcrumb: function(btns, shape = "slash") { this._removeAll(); for (let i=0; i `${pos+1}- ${val}` + }); + node.getStatus().bind("dependencies", btn.getChildControl("label"), "textColor", { + converter: dependencies => { + let textColor = "material-button-text"; + if (dependencies && dependencies.length) { + textColor = "material-button-text-disabled"; + } + return textColor; + } + }); + node.getStatus().bind("modified", btn, "label", { + converter: modified => { + const label = btn.getLabel(); + const lastCharacter = label.slice(-1); + if (modified === true && lastCharacter !== "*") { + return label + "*"; // add star suffix + } else if ((modified === false || modified === null) && lastCharacter === "*") { + return label.slice(0, -1); // remove star suffix + } + return label; + } + }); + + const statusUI = new osparc.ui.basic.NodeStatusUI(node); + const statusLabel = statusUI.getChildControl("label"); + const statusIcon = statusUI.getChildControl("icon"); + // eslint-disable-next-line no-underscore-dangle + btn._add(statusIcon); + + statusLabel.bind("value", btn, "toolTipText", { + converter: status => `${node.getLabel()} - ${status}` + }); + } + return btn; + } + } +}); diff --git a/services/web/client/source/class/osparc/navigation/BreadcrumbsWorkbench.js b/services/web/client/source/class/osparc/navigation/BreadcrumbsWorkbench.js new file mode 100644 index 00000000000..918d6623c1a --- /dev/null +++ b/services/web/client/source/class/osparc/navigation/BreadcrumbsWorkbench.js @@ -0,0 +1,55 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2021 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +/** + * + */ + +qx.Class.define("osparc.navigation.BreadcrumbsWorkbench", { + extend: osparc.navigation.BreadcrumbNavigation, + + members: { + populateButtons: function(nodesIds = []) { + const btns = []; + for (let i=0; i { if (state) { this.show(); - if (state === "STARTED") { - state = "Running"; - } - return qx.lang.String.firstUp(state.toLowerCase()); + const labelValue = osparc.utils.StatusUI.getLabelValue(state); + return qx.lang.String.firstUp(labelValue.toLowerCase()); } this.exclude(); return null; + }, + onUpdate: (source, target) => { + const state = source.getRunning(); + target.setTextColor(osparc.utils.StatusUI.getColor(state)); } }); - this.__node.getStatus().bind("runningStatus", this.__icon, "source", { - converter: state => { - switch (state) { - case "SUCCESS": - return "@FontAwesome5Solid/check/12"; - case "FAILED": - case "ABORTED": - return "@FontAwesome5Solid/exclamation-circle/12"; - case "PENDING": - case "PUBLISHED": - case "STARTED": - case "RETRY": - return "@FontAwesome5Solid/circle-notch/12"; - case "UNKNOWN": - case "NOT_STARTED": - default: - return ""; - } - }, + this.__node.getStatus().bind("running", this.__icon, "source", { + converter: state => osparc.utils.StatusUI.getIconSource(state), onUpdate: (source, target) => { target.show(); - const state = source.getRunningStatus(); + const state = source.getRunning(); switch (state) { case "SUCCESS": - this.__removeClass(this.__icon.getContentElement(), "rotate"); - target.setTextColor("ready-green"); - return; case "FAILED": case "ABORTED": - this.__removeClass(this.__icon.getContentElement(), "rotate"); - target.setTextColor("failed-red"); + this.self().removeClass(this.__icon.getContentElement(), "rotate"); + target.setTextColor(osparc.utils.StatusUI.getColor(state)); return; case "PENDING": case "PUBLISHED": case "STARTED": case "RETRY": - this.__addClass(this.__icon.getContentElement(), "rotate"); - target.resetTextColor(); + this.self().addClass(this.__icon.getContentElement(), "rotate"); + target.setTextColor(osparc.utils.StatusUI.getColor(state)); return; case "UNKNOWN": case "NOT_STARTED": @@ -119,54 +103,39 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { }, __setupInteractive: function() { - this.__node.getStatus().bind("interactiveStatus", this.__label, "value", { - converter: status => { - if (status === "ready") { - return this.tr("Ready"); - } else if (status === "failed") { - return this.tr("Error"); - } else if (status === "starting") { - return this.tr("Starting..."); - } else if (status === "pending") { - return this.tr("Pending..."); - } else if (status === "pulling") { - return this.tr("Pulling..."); - } else if (status === "connecting") { - return this.tr("Connecting..."); - } - return status; + this.__node.getStatus().bind("interactive", this.__label, "value", { + converter: state => osparc.utils.StatusUI.getLabelValue(state), + onUpdate: (source, target) => { + const state = source.getInteractive(); + target.setTextColor(osparc.utils.StatusUI.getColor(state)); } }); - this.__node.getStatus().bind("interactiveStatus", this.__icon, "source", { - converter: status => { - if (status === "ready") { - return "@FontAwesome5Solid/check/12"; - } else if (status === "failed") { - return "@FontAwesome5Solid/exclamation-circle/12"; - } else if (status === "starting") { - return "@FontAwesome5Solid/circle-notch/12"; - } else if (status === "pending") { - return "@FontAwesome5Solid/circle-notch/12"; - } else if (status === "pulling") { - return "@FontAwesome5Solid/circle-notch/12"; - } else if (status === "connecting") { - return "@FontAwesome5Solid/circle-notch/12"; - } - return ""; - }, + this.__node.getStatus().bind("interactive", this.__icon, "source", { + converter: state => osparc.utils.StatusUI.getIconSource(state), onUpdate: (source, target) => { - if (source.getInteractiveStatus() == null) { - this.__removeClass(this.__icon.getContentElement(), "rotate"); - } else if (source.getInteractiveStatus() === "ready") { - this.__removeClass(this.__icon.getContentElement(), "rotate"); - target.setTextColor("ready-green"); - } else if (source.getInteractiveStatus() === "failed") { - this.__removeClass(this.__icon.getContentElement(), "rotate"); - target.setTextColor("failed-red"); - } else { - this.__addClass(this.__icon.getContentElement(), "rotate"); - target.resetTextColor(); + const state = source.getInteractive(); + switch (state) { + case "ready": + case "failed": + this.self().removeClass(this.__icon.getContentElement(), "rotate"); + target.setTextColor(osparc.utils.StatusUI.getColor(state)); + break; + case "idle": + this.self().removeClass(this.__icon.getContentElement(), "rotate"); + target.setTextColor(osparc.utils.StatusUI.getColor(state)); + break; + case "starting": + case "pulling": + case "pending": + case "connecting": + this.self().addClass(this.__icon.getContentElement(), "rotate"); + target.setTextColor(osparc.utils.StatusUI.getColor(state)); + break; + default: + this.self().removeClass(this.__icon.getContentElement(), "rotate"); + target.resetTextColor(); + break; } } }); diff --git a/services/web/client/source/class/osparc/ui/window/Confirmation.js b/services/web/client/source/class/osparc/ui/window/Confirmation.js index 714997f96a8..e433d9d10bf 100644 --- a/services/web/client/source/class/osparc/ui/window/Confirmation.js +++ b/services/web/client/source/class/osparc/ui/window/Confirmation.js @@ -16,18 +16,14 @@ qx.Class.define("osparc.ui.window.Confirmation", { * @extends osparc.ui.window.Dialog * @param {String} message Message that will be displayed to the user. */ - construct: function(message) { + construct: function(message, confirmBtnText = this.tr("Yes")) { this.base(arguments, this.tr("Confirmation"), null, message); this.addCancelButton(); - const btnYes = new qx.ui.toolbar.Button("Yes"); - osparc.utils.Utils.setIdToWidget(btnYes, "confirmDeleteStudyBtn"); - btnYes.addListener("execute", e => { - this.setConfirmed(true); - this.close(1); - }, this); - this.addButton(btnYes); + this._createChildControlImpl("confirm-button").set({ + label: confirmBtnText + }); }, properties: { @@ -35,5 +31,23 @@ qx.Class.define("osparc.ui.window.Confirmation", { check: "Boolean", init: false } + }, + + members: { + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "confirm-button": { + control = new qx.ui.toolbar.Button(); + control.addListener("execute", e => { + this.setConfirmed(true); + this.close(1); + }, this); + this.addButton(control); + break; + } + } + return control || this.base(arguments, id); + } } }); diff --git a/services/web/client/source/class/osparc/utils/StatusUI.js b/services/web/client/source/class/osparc/utils/StatusUI.js new file mode 100644 index 00000000000..a7d47e7f6ea --- /dev/null +++ b/services/web/client/source/class/osparc/utils/StatusUI.js @@ -0,0 +1,152 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2021 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +/** + * Collection of methods for dealing status decorators. + * + */ + +qx.Class.define("osparc.utils.StatusUI", { + type: "static", + + statics: { + getIconSource: function(state) { + switch (state) { + // computationals + case "SUCCESS": + return "@FontAwesome5Solid/check/12"; + case "FAILED": + case "ABORTED": + return "@FontAwesome5Solid/exclamation-circle/12"; + case "PENDING": + case "PUBLISHED": + case "STARTED": + case "RETRY": + return "@FontAwesome5Solid/circle-notch/12"; + case "UNKNOWN": + case "NOT_STARTED": + return ""; + + // dynamics + case "ready": + return "@FontAwesome5Solid/check/12"; + case "failed": + return "@FontAwesome5Solid/exclamation-circle/12"; + case "starting": + case "pending": + case "pulling": + case "connecting": + return "@FontAwesome5Solid/circle-notch/12"; + + // ports + case "modified": + return "@FontAwesome5Solid/exclamation-circle/12"; + case "up-to-date": + return "@FontAwesome5Solid/check/12"; + + default: + return ""; + } + }, + + getLabelValue: function(state) { + switch (state) { + // computationals + case "STARTED": + return qx.locale.Manager.tr("Running"); + + // dynamics + case "ready": + return qx.locale.Manager.tr("Ready"); + case "failed": + return qx.locale.Manager.tr("Error"); + case "starting": + return qx.locale.Manager.tr("Starting..."); + case "pending": + return qx.locale.Manager.tr("Pending..."); + case "pulling": + return qx.locale.Manager.tr("Pulling..."); + case "connecting": + return qx.locale.Manager.tr("Connecting..."); + + default: + return state; + } + }, + + getColor: function(state) { + switch (state) { + // computationals + case "SUCCESS": + return "ready-green"; + case "FAILED": + case "ABORTED": + return "failed-red"; + case "PENDING": + case "PUBLISHED": + case "STARTED": + case "RETRY": + return "busy-orange"; + case "UNKNOWN": + case "NOT_STARTED": + return "text"; + + // dynamics + case "ready": + return "ready-green"; + case "failed": + return "failed-red"; + case "idle": + case "starting": + case "pulling": + case "pending": + case "connecting": + return "busy-orange"; + + // ports + case "modified": + return "busy-orange"; + case "up-to-date": + return "ready-green"; + + default: + return "text"; + } + }, + + getBorderDecorator: function(state) { + switch (state) { + case "SUCCESS": + return "border-ok"; + case "FAILED": + case "ABORTED": + return "border-error"; + case "PENDING": + case "PUBLISHED": + case "STARTED": + case "RETRY": + return "border-busy"; + case "UNKNOWN": + case "NOT_STARTED": + return "no-border"; + + default: + return "no-border"; + } + } + } +}); diff --git a/services/web/client/source/class/osparc/viewer/NodeViewer.js b/services/web/client/source/class/osparc/viewer/NodeViewer.js index 134f0548abf..288cade9258 100644 --- a/services/web/client/source/class/osparc/viewer/NodeViewer.js +++ b/services/web/client/source/class/osparc/viewer/NodeViewer.js @@ -23,9 +23,7 @@ qx.Class.define("osparc.viewer.NodeViewer", { this._setLayout(new qx.ui.layout.VBox()); - this.__initLoadingPage(); this.__initIFrame(); - this.__iFrameChanged(); this.set({ @@ -85,6 +83,8 @@ qx.Class.define("osparc.viewer.NodeViewer", { }, __initIFrame: function() { + this.__initLoadingPage(); + const iframe = new osparc.component.widget.PersistentIframe().set({ showActionButton: false, showRestartButton: false diff --git a/tests/e2e/tutorials/jupyters.js b/tests/e2e/tutorials/jupyters.js index b7e7088fcb3..1fe216aaa3e 100644 --- a/tests/e2e/tutorials/jupyters.js +++ b/tests/e2e/tutorials/jupyters.js @@ -55,7 +55,8 @@ async function runTutorial() { await tutorial.takeScreenshot("pressRunNB_" + (i+1)); } - await tutorial.retrieve(); + // TODO: Better check that the kernel is finished + await tutorial.waitFor(3000); console.log('Checking results for the notebook:'); await tutorial.openNodeFiles(1); @@ -69,8 +70,6 @@ async function runTutorial() { // open jupyter lab await tutorial.openNode(2); - await tutorial.retrieve(); - const iframeHandles2 = await tutorial.getIframe(); // expected three iframes = loading + jupyterNB + jupyterLab const jLabIframe = await iframeHandles2[2].contentFrame(); diff --git a/tests/e2e/utils/utils.js b/tests/e2e/utils/utils.js index c807294fa3b..53d7ced521d 100644 --- a/tests/e2e/utils/utils.js +++ b/tests/e2e/utils/utils.js @@ -369,7 +369,7 @@ function isElementVisible (page, selector) { async function clickLoggerTitle(page) { console.log("Click LoggerTitle"); - await this.waitAndClick(page, '[osparc-test-id="loggerTitleLabel"]') + await this.waitAndClick(page, '[osparc-test-id="studyLoggerTitleLabel"]') }