diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js index 8fbf22b7c31f..bd6580564902 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js @@ -44,7 +44,12 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { latestPromise .then(latestResourceData => { - this.__resourceData = latestResourceData; + if (!latestResourceData) { + const msg = this.tr("Data not found, please try again"); + osparc.FlashMessenger.logAs(msg, "WARNING"); + return; + } + this.__resourceData = osparc.utils.Utils.deepCloneObject(latestResourceData); this.__resourceData["resourceType"] = resourceData["resourceType"]; switch (resourceData["resourceType"]) { case "study": diff --git a/services/static-webserver/client/source/class/osparc/data/Resources.js b/services/static-webserver/client/source/class/osparc/data/Resources.js index eeba96b1c001..b1acdedc8b7e 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -347,19 +347,23 @@ qx.Class.define("osparc.data.Resources", { }, } }, - "jobsActive": { + "jobs": { useCache: false, // handled in osparc.store.Jobs endpoints: { - getPage: { + getPageLatestActive: { method: "GET", url: statics.API + "/computations/-/iterations/latest?offset={offset}&limit={limit}&order_by=%7B%22field%22:%22submitted_at%22,%22direction%22:%22desc%22%7D&filter_only_running=true" }, + getPageHistory: { + method: "GET", + url: statics.API + "/computations/{studyId}/iterations?offset={offset}&limit={limit}&order_by=%7B%22field%22:%22submitted_at%22,%22direction%22:%22desc%22%7D" + }, } }, "subJobs": { useCache: false, // handled in osparc.store.Jobs endpoints: { - getPage: { + getPageLatest: { method: "GET", url: statics.API + "/computations/{studyId}/iterations/latest/tasks?offset={offset}&limit={limit}" }, @@ -608,7 +612,7 @@ qx.Class.define("osparc.data.Resources", { "functions": { useCache: false, endpoints: { - getPage: { + create: { method: "POST", url: statics.API + "/functions" } diff --git a/services/static-webserver/client/source/class/osparc/jobs/ActivityOverview.js b/services/static-webserver/client/source/class/osparc/jobs/ActivityOverview.js index 0f985f4e03da..951845c29c8c 100644 --- a/services/static-webserver/client/source/class/osparc/jobs/ActivityOverview.js +++ b/services/static-webserver/client/source/class/osparc/jobs/ActivityOverview.js @@ -21,7 +21,7 @@ qx.Class.define("osparc.jobs.ActivityOverview", { construct: function(projectData) { this.base(arguments); - this._setLayout(new qx.ui.layout.VBox(15)); + this._setLayout(new qx.ui.layout.VBox(10)); this.__buildLayout(projectData); }, @@ -30,13 +30,41 @@ qx.Class.define("osparc.jobs.ActivityOverview", { popUpInWindow: function(projectData) { const activityOverview = new osparc.jobs.ActivityOverview(projectData); const title = qx.locale.Manager.tr("Activity Overview"); - return osparc.ui.window.Window.popUpInWindow(activityOverview, title, osparc.jobs.ActivityCenterWindow.WIDTH, osparc.jobs.ActivityCenterWindow.HEIGHT); + const win = osparc.ui.window.Window.popUpInWindow(activityOverview, title, osparc.jobs.ActivityCenterWindow.WIDTH, osparc.jobs.ActivityCenterWindow.HEIGHT); + win.set({ + maxHeight: 700, + }); + return win; }, }, members: { __buildLayout: function(projectData) { + this._add(new qx.ui.basic.Label(this.tr("Runs History")).set({ + font: "text-14" + })); + + const latestOnly = false; + const projectUuid = projectData["uuid"]; + const runsTable = new osparc.jobs.RunsTable(latestOnly, projectUuid); + const columnModel = runsTable.getTableColumnModel(); + // Hide project name column + columnModel.setColumnVisible(osparc.jobs.RunsTable.COLS.PROJECT_NAME.column, false); + // Hide cancel column + columnModel.setColumnVisible(osparc.jobs.RunsTable.COLS.ACTION_CANCEL.column, false); + runsTable.set({ + maxHeight: 250, + }) + this._add(runsTable); + + this._add(new qx.ui.basic.Label(this.tr("Latest Tasks")).set({ + font: "text-14" + })); + const subRunsTable = new osparc.jobs.SubRunsTable(projectData["uuid"]); + subRunsTable.set({ + maxHeight: 250, + }) this._add(subRunsTable); }, } diff --git a/services/static-webserver/client/source/class/osparc/jobs/RunsBrowser.js b/services/static-webserver/client/source/class/osparc/jobs/RunsBrowser.js index ff9e5414b840..4ce0a15ae602 100644 --- a/services/static-webserver/client/source/class/osparc/jobs/RunsBrowser.js +++ b/services/static-webserver/client/source/class/osparc/jobs/RunsBrowser.js @@ -60,11 +60,14 @@ qx.Class.define("osparc.jobs.RunsBrowser", { flex: 1 }); break; - case "runs-table": - control = new osparc.jobs.RunsTable(); + case "runs-table": { + const latestOnly = true; + const projectUuid = null; + control = new osparc.jobs.RunsTable(latestOnly, projectUuid); control.addListener("runSelected", e => this.fireDataEvent("runSelected", e.getData())); this._add(control); break; + } } return control || this.base(arguments, id); diff --git a/services/static-webserver/client/source/class/osparc/jobs/RunsTable.js b/services/static-webserver/client/source/class/osparc/jobs/RunsTable.js index 81244349a299..393be95f1099 100644 --- a/services/static-webserver/client/source/class/osparc/jobs/RunsTable.js +++ b/services/static-webserver/client/source/class/osparc/jobs/RunsTable.js @@ -19,10 +19,10 @@ qx.Class.define("osparc.jobs.RunsTable", { extend: qx.ui.table.Table, - construct: function(filters) { + construct: function(latestOnly = true, projectUuid = null) { this.base(arguments); - const model = new osparc.jobs.RunsTableModel(filters); + const model = new osparc.jobs.RunsTableModel(latestOnly, projectUuid); this.setTableModel(model); this.set({ diff --git a/services/static-webserver/client/source/class/osparc/jobs/RunsTableModel.js b/services/static-webserver/client/source/class/osparc/jobs/RunsTableModel.js index da829b670989..4dd2e241d707 100644 --- a/services/static-webserver/client/source/class/osparc/jobs/RunsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/jobs/RunsTableModel.js @@ -19,9 +19,12 @@ qx.Class.define("osparc.jobs.RunsTableModel", { extend: qx.ui.table.model.Remote, - construct: function() { + construct: function(latestOnly = true, projectUuid = null) { this.base(arguments); + this.__latestOnly = latestOnly; + this.__projectUuid = projectUuid; + const jobsCols = osparc.jobs.RunsTable.COLS; const colLabels = Object.values(jobsCols).map(col => col.label); const colIDs = Object.values(jobsCols).map(col => col.id); @@ -60,7 +63,13 @@ qx.Class.define("osparc.jobs.RunsTableModel", { const offset = 0; const limit = 1; const resolveWResponse = true; - osparc.store.Jobs.getInstance().fetchJobsActive(offset, limit, JSON.stringify(this.getOrderBy()), resolveWResponse) + let promise; + if (this.__latestOnly && this.__projectUuid === null) { + promise = osparc.store.Jobs.getInstance().fetchJobsActive(offset, limit, JSON.stringify(this.getOrderBy()), resolveWResponse); + } else { + promise = osparc.store.Jobs.getInstance().fetchJobsHistory(this.__projectUuid, offset, limit, JSON.stringify(this.getOrderBy()), resolveWResponse); + } + promise .then(resp => { this._onRowCountLoaded(resp["_meta"].total) }) @@ -76,7 +85,13 @@ qx.Class.define("osparc.jobs.RunsTableModel", { const lastRow = Math.min(qxLastRow, this._rowCount - 1); // Returns a request promise with given offset and limit const getFetchPromise = (offset, limit) => { - return osparc.store.Jobs.getInstance().fetchJobsActive(offset, limit, JSON.stringify(this.getOrderBy())) + let promise; + if (this.__latestOnly && this.__projectUuid === null) { + promise = osparc.store.Jobs.getInstance().fetchJobsActive(offset, limit, JSON.stringify(this.getOrderBy())); + } else { + promise = osparc.store.Jobs.getInstance().fetchJobsHistory(this.__projectUuid, offset, limit, JSON.stringify(this.getOrderBy())); + } + return promise .then(jobs => { const data = []; const jobsCols = osparc.jobs.RunsTable.COLS; diff --git a/services/static-webserver/client/source/class/osparc/store/Jobs.js b/services/static-webserver/client/source/class/osparc/store/Jobs.js index 87ea782e18a6..197b1ff0637c 100644 --- a/services/static-webserver/client/source/class/osparc/store/Jobs.js +++ b/services/static-webserver/client/source/class/osparc/store/Jobs.js @@ -56,7 +56,7 @@ qx.Class.define("osparc.store.Jobs", { const options = { resolveWResponse: true }; - return osparc.data.Resources.fetch("jobsActive", "getPage", params, options) + return osparc.data.Resources.fetch("jobs", "getPageLatestActive", params, options) .then(jobsResp => { this.fireDataEvent("changeJobsActive", jobsResp["_meta"]["total"]); const jobsActive = []; @@ -73,13 +73,51 @@ qx.Class.define("osparc.store.Jobs", { .catch(err => console.error(err)); }, + fetchJobsHistory: function( + studyId, + offset = 0, + limit = this.self().SERVER_MAX_LIMIT, + orderBy = { + field: "submitted_at", + direction: "desc" + }, + resolveWResponse = false + ) { + const params = { + url: { + studyId, + offset, + limit, + orderBy: JSON.stringify(orderBy), + } + }; + const options = { + resolveWResponse: true + }; + return osparc.data.Resources.fetch("jobs", "getPageHistory", params, options) + .then(jobsResp => { + if (resolveWResponse) { + return jobsResp; + } + const jobs = []; + if ("data" in jobsResp) { + jobsResp["data"].forEach(jobData => { + const job = new osparc.data.Job(jobData); + jobs.push(job); + }); + } + return jobs; + }) + .catch(err => console.error(err)); + }, + fetchSubJobs: function(projectUuid) { const params = { url: { studyId: projectUuid, } }; - return osparc.data.Resources.getInstance().getAllPages("subJobs", params) + return osparc.data.Resources.getInstance().getAllPages("subJobs", params, "getPageLatest") .then(subJobsData => { const subJobs = []; subJobsData.forEach(subJobData => { diff --git a/services/static-webserver/client/source/class/osparc/store/Services.js b/services/static-webserver/client/source/class/osparc/store/Services.js index 47983eba6e4f..eac4a2ba3384 100644 --- a/services/static-webserver/client/source/class/osparc/store/Services.js +++ b/services/static-webserver/client/source/class/osparc/store/Services.js @@ -106,7 +106,8 @@ qx.Class.define("osparc.store.Services", { return this.__servicesPromisesCached[key][version]; } - return new Promise((resolve, reject) => { + // Create a new promise + const promise = new Promise((resolve, reject) => { if ( useCache && this.__isInCache(key, version) && @@ -133,17 +134,26 @@ qx.Class.define("osparc.store.Services", { this.__addServiceToCache(service); // Resolve the promise locally before deleting it resolve(service); - // Remove the promise from the cache - delete this.__servicesPromisesCached[key][version]; }) .catch(err => { - // store it in cache to avoid asking again + // Store null in cache to avoid repeated failed requests this.__addToCache(key, version, null); - delete this.__servicesPromisesCached[key][version]; console.error(err); reject(err); + }) + .finally(() => { + // Remove the promise from the cache + delete this.__servicesPromisesCached[key][version]; }); }); + + // Store the promise in the cache + // The point of keeping this assignment outside of the main Promise block is to + // ensure that the promise is immediately stored in the cache before any asynchronous + // operations (like fetch) are executed. This prevents duplicate requests for the + // same key and version when multiple consumers call getService concurrently. + this.__servicesPromisesCached[key][version] = promise; + return promise; }, getStudyServices: function(studyId) {