From 5488979928c1b09393637ccc47d11e8846d0bddb Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 14 Jan 2025 16:05:04 +0100 Subject: [PATCH 01/33] Rentals in Billing Center --- .../osparc/desktop/credits/BillingCenter.js | 10 + .../class/osparc/desktop/credits/Rentals.js | 130 ++++++++++++ .../osparc/desktop/credits/RentalsTable.js | 126 +++++++++++ .../desktop/credits/RentalsTableModel.js | 199 ++++++++++++++++++ 4 files changed, 465 insertions(+) create mode 100644 services/static-webserver/client/source/class/osparc/desktop/credits/Rentals.js create mode 100644 services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTable.js create mode 100644 services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js b/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js index 8351877752c..65f889350b2 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js @@ -32,6 +32,7 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", { if (osparc.data.Permissions.getInstance().canDo("usage.all.read")) { this.__usagePage = this.__addUsagePage(); + this.__rentalsPage = this.__addRentalsPage(); } }, @@ -59,6 +60,7 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", { __paymentMethodsPage: null, __transactionsPage: null, __usagePage: null, + __rentalsPage: null, __paymentMethods: null, __transactionsTable: null, @@ -95,6 +97,14 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", { return page; }, + __addRentalsPage: function() { + const title = this.tr("Rentals"); + const iconSrc = "@FontAwesome5Solid/list/22"; + const rentals = new osparc.desktop.credits.Rentals(); + const page = this.addTab(title, iconSrc, rentals); + return page; + }, + openWallets: function() { if (this.__walletsPage) { return this._openPage(this.__walletsPage); diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Rentals.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Rentals.js new file mode 100644 index 00000000000..d15516f9415 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Rentals.js @@ -0,0 +1,130 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2025 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + + +qx.Class.define("osparc.desktop.credits.Rentals", { + extend: qx.ui.core.Widget, + + construct: function() { + this.base(arguments); + + this._setLayout(new qx.ui.layout.VBox(15)); + + const store = osparc.store.Store.getInstance(); + this.__userWallets = store.getWallets(); + + this.__buildLayout() + }, + + members: { + __buildLayout: function() { + this._removeAll(); + + const container = new qx.ui.container.Composite(new qx.ui.layout.VBox(5)); + + const lbl = new qx.ui.basic.Label("Select a Credit Account:"); + container.add(lbl); + + const selectBoxContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); + const walletSelectBox = new qx.ui.form.SelectBox().set({ + allowStretchX: false, + width: 200 + }); + selectBoxContainer.add(walletSelectBox); + this.__fetchingImg = new qx.ui.basic.Image().set({ + source: "@FontAwesome5Solid/circle-notch/12", + alignX: "center", + alignY: "middle", + visibility: "excluded" + }); + this.__fetchingImg.getContentElement().addClass("rotate"); + selectBoxContainer.add(this.__fetchingImg); + container.add(selectBoxContainer); + + const filterContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)) + this.__dateFilters = new osparc.desktop.credits.DateFilters(); + this.__dateFilters.addListener("change", e => { + this.__table.getTableModel().setFilters(e.getData()) + this.__table.getTableModel().reloadData() + }); + filterContainer.add(this.__dateFilters); + filterContainer.add(new qx.ui.core.Spacer(), { + flex: 1 + }); + this.__exportButton = new qx.ui.form.Button(this.tr("Export")).set({ + allowStretchY: false, + alignY: "bottom" + }); + this.__exportButton.addListener("execute", () => { + this.__handleExport() + }); + filterContainer.add(this.__exportButton); + const refreshButton = new qx.ui.form.Button(this.tr("Reload"), "@FontAwesome5Solid/sync-alt/14").set({ + allowStretchY: false, + alignY: "bottom" + }); + refreshButton.addListener("execute", () => this.__table && this.__table.getTableModel().reloadData()); + filterContainer.add(refreshButton) + container.add(filterContainer); + + this._add(container); + + walletSelectBox.addListener("changeSelection", e => { + const selection = e.getData(); + if (selection.length) { + this.__selectedWallet = selection[0].getModel() + if (this.__table) { + this.__table.getTableModel().setWalletId(this.__selectedWallet.getWalletId()) + this.__table.getTableModel().reloadData() + } else { + // qx: changeSelection is triggered after the first item is added to SelectBox + this.__table = new osparc.desktop.credits.RentalsTable(this.__selectedWallet.getWalletId(), this.__dateFilters.getValue()).set({ + marginTop: 10 + }) + this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", { + converter: isFetching => isFetching ? "visible" : "excluded" + }) + container.add(this.__table, { flex: 1 }) + } + } + }); + + if (osparc.desktop.credits.Utils.areWalletsEnabled()) { + this.__userWallets.forEach(wallet => { + walletSelectBox.add(new qx.ui.form.ListItem(wallet.getName(), null, wallet)); + }); + } else { + lbl.setVisibility("excluded") + walletSelectBox.setVisibility("excluded") + this.__exportButton.setVisibility("excluded") + this.__table = new osparc.desktop.credits.RentalsTable(null, this.__dateFilters.getValue()).set({ + marginTop: 10 + }) + this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", { + converter: isFetching => isFetching ? "visible" : "excluded" + }) + container.add(this.__table, { flex: 1 }) + } + }, + __handleExport() { + const reportUrl = new URL("/v0/services/-/rentals-report", window.location.origin) + reportUrl.searchParams.append("wallet_id", this.__selectedWallet.getWalletId()) + reportUrl.searchParams.append("filters", JSON.stringify({ "started_at": this.__dateFilters.getValue() })) + window.open(reportUrl, "_blank") + } + } +}); diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTable.js b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTable.js new file mode 100644 index 00000000000..01d7089157a --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTable.js @@ -0,0 +1,126 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2025 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + + +qx.Class.define("osparc.desktop.credits.RentalsTable", { + extend: qx.ui.table.Table, + + construct: function(walletId, filters) { + this.base(arguments) + const model = new osparc.desktop.credits.RentalsTableModel(walletId, filters) + this.setTableModel(model) + this.setStatusBarVisible(false) + + this.setHeaderCellHeight(26); + this.setRowHeight(26); + + const columnModel = this.getTableColumnModel(); + + columnModel.setDataCellRenderer(this.self().COLS.COST.column, new qx.ui.table.cellrenderer.Number()); + + if (!osparc.desktop.credits.Utils.areWalletsEnabled()) { + columnModel.setColumnVisible(this.self().COLS.COST.column, false); + columnModel.setColumnVisible(this.self().COLS.USER.column, false); + } + columnModel.setColumnVisible(this.self().COLS.SERVICE.column, false); + + // Array [0, 1, ..., N] where N is column_count - 1 (default column order) + this.__columnOrder = [...Array(columnModel.getOverallColumnCount()).keys()] + + if ( + osparc.Preferences.getInstance().getBillingCenterRentalsColumnOrder() && + osparc.Preferences.getInstance().getBillingCenterRentalsColumnOrder().length === this.__columnOrder.length + ) { + columnModel.setColumnsOrder(osparc.Preferences.getInstance().getBillingCenterRentalsColumnOrder()) + this.__columnOrder = osparc.Preferences.getInstance().getBillingCenterRentalsColumnOrder() + } else { + osparc.Preferences.getInstance().setBillingCenterRentalsColumnOrder(this.__columnOrder) + } + + columnModel.addListener("orderChanged", e => { + // Save new order into preferences + if (e.getData()) { + const { fromOverXPos, toOverXPos } = e.getData() + // Edit current order + this.__columnOrder = this.__columnOrder.toSpliced(toOverXPos, 0, this.__columnOrder.splice(fromOverXPos, 1)[0]) + // Save order + osparc.Preferences.getInstance().setBillingCenterRentalsColumnOrder(this.__columnOrder) + } + }, this) + + Object.values(this.self().COLS).forEach(col => columnModel.setColumnWidth(col.column, col.width)); + }, + + statics: { + COLS: { + PROJECT: { + id: "project", + column: 0, + label: osparc.product.Utils.getStudyAlias({firstUpperCase: true}), + width: 140 + }, + NODE: { + id: "node", + column: 1, + label: qx.locale.Manager.tr("Node"), + width: 140 + }, + SERVICE: { + id: "service", + column: 2, + label: qx.locale.Manager.tr("Service"), + width: 140 + }, + START: { + id: "start", + column: 3, + label: qx.locale.Manager.tr("Start"), + width: 130 + }, + DURATION: { + id: "duration", + column: 4, + label: qx.locale.Manager.tr("Duration"), + width: 70 + }, + STATUS: { + id: "status", + column: 5, + label: qx.locale.Manager.tr("Status"), + width: 70 + }, + COST: { + id: "cost", + column: 6, + label: qx.locale.Manager.tr("Credits"), + width: 56 + }, + USER: { + id: "user", + column: 7, + label: qx.locale.Manager.tr("User"), + width: 140 + }, + TAGS: { + id: "tags", + column: 7, + label: qx.locale.Manager.tr("Tags"), + width: 140 + }, + } + } +}); diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js new file mode 100644 index 00000000000..2ebcf733325 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js @@ -0,0 +1,199 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2025 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + + +qx.Class.define("osparc.desktop.credits.RentalsTableModel", { + extend: qx.ui.table.model.Remote, + + construct(walletId, filters) { + this.base(arguments); + + const rentalsCols = osparc.desktop.credits.RentalsTable.COLS; + const colLabels = Object.values(rentalsCols).map(col => col.label); + const colIDs = Object.values(rentalsCols).map(col => col.id); + + this.setColumns(colLabels, colIDs); + this.setWalletId(walletId) + if (filters) { + this.setFilters(filters) + } + this.setSortColumnIndexWithoutSortingData(rentalsCols.START.column); + this.setSortAscendingWithoutSortingData(false) + this.setColumnSortable(rentalsCols.DURATION.column, false); + }, + + properties: { + walletId: { + check: "Number", + nullable: true + }, + filters: { + check: "Object", + init: null + }, + isFetching: { + check: "Boolean", + init: false, + event: "changeFetching" + }, + orderBy: { + check: "Object", + init: { + field: "started_at", + direction: "desc" + } + } + }, + + statics: { + SERVER_MAX_LIMIT: 49, + COLUMN_ID_TO_DB_COLUMN_MAP: { + 0: "root_parent_project_name", + 1: "node_name", + 2: "service_key", + 3: "started_at", + // 4: (not used) SORTING BY DURATION + 5: "service_run_status", + 6: "credit_cost", + 7: "user_email", + 8: "projects_tags", + } + }, + + members: { + // overridden + sortByColumn(columnIndex, ascending) { + this.setOrderBy({ + field: this.self().COLUMN_ID_TO_DB_COLUMN_MAP[columnIndex], + direction: ascending ? "asc" : "desc" + }) + this.base(arguments, columnIndex, ascending) + }, + + // overridden + _loadRowCount() { + const endpoint = this.getWalletId() == null ? "get" : "getWithWalletFiltered" + const params = { + url: { + walletId: this.getWalletId(), + limit: 1, + offset: 0, + filters: this.getFilters() ? + JSON.stringify({ + "started_at": this.getFilters() + }) : + null, + orderBy: JSON.stringify(this.getOrderBy()) + } + }; + const options = { + resolveWResponse: true + }; + osparc.data.Resources.fetch("resourceRentals", endpoint, params, options) + .then(resp => { + this._onRowCountLoaded(resp["_meta"].total) + }) + .catch(() => { + this._onRowCountLoaded(null) + }) + }, + + // overridden + _loadRowData(firstRow, qxLastRow) { + this.setIsFetching(true) + // Please Qloocloox don't ask for more rows than there are + const lastRow = Math.min(qxLastRow, this._rowCount - 1) + // Returns a request promise with given offset and limit + const getFetchPromise = (offset, limit=this.self().SERVER_MAX_LIMIT) => { + const endpoint = this.getWalletId() == null ? "get" : "getWithWalletFiltered" + return osparc.data.Resources.fetch("resourceRentals", endpoint, { + url: { + walletId: this.getWalletId(), + limit, + offset, + filters: this.getFilters() ? + JSON.stringify({ + "started_at": this.getFilters() + }) : + null, + orderBy: JSON.stringify(this.getOrderBy()) + } + }) + .then(rawData => { + const data = [] + const rentalsCols = osparc.desktop.credits.RentalsTable.COLS; + rawData.forEach(rawRow => { + let service = "" + if (rawRow["service_key"]) { + const serviceName = rawRow["service_key"].split("/").pop() + service = `${serviceName}:${rawRow["service_version"]}` + } + let start = "" + let duration = "" + if (rawRow["started_at"]) { + start = osparc.utils.Utils.formatDateAndTime(new Date(rawRow["started_at"])) + if (rawRow["stopped_at"]) { + duration = osparc.utils.Utils.formatMsToHHMMSS(new Date(rawRow["stopped_at"]) - new Date(rawRow["started_at"])) + } + } + data.push({ + // root_parent_project is the same as project if it has no parent + [rentalsCols.PROJECT.id]: rawRow["root_parent_project_name"] || rawRow["root_parent_project_id"] || rawRow["project_name"] || rawRow["project_id"], + [rentalsCols.NODE.id]: rawRow["node_name"] || rawRow["node_id"], + [rentalsCols.SERVICE.id]: service, + [rentalsCols.START.id]: start, + [rentalsCols.DURATION.id]: duration, + [rentalsCols.STATUS.id]: qx.lang.String.firstUp(rawRow["service_run_status"].toLowerCase()), + [rentalsCols.COST.id]: rawRow["credit_cost"] ? parseFloat(rawRow["credit_cost"]).toFixed(2) : "", + [rentalsCols.USER.id]: rawRow["user_email"], + [rentalsCols.TAGS.id]: rawRow["project_tags"], + }) + }) + return data + }) + } + // Divides the model row request into several server requests to comply with the number of rows server limit + const reqLimit = lastRow - firstRow + 1 // Number of requested rows + const nRequests = Math.ceil(reqLimit / this.self().SERVER_MAX_LIMIT) + if (nRequests > 1) { + let requests = [] + for (let i=firstRow; i <= lastRow; i += this.self().SERVER_MAX_LIMIT) { + requests.push(getFetchPromise(i, i > lastRow - this.self().SERVER_MAX_LIMIT + 1 ? reqLimit % this.self().SERVER_MAX_LIMIT : this.self().SERVER_MAX_LIMIT)) + } + Promise.all(requests) + .then(responses => { + this._onRowDataLoaded(responses.flat()) + }) + .catch(err => { + console.error(err) + this._onRowDataLoaded(null) + }) + .finally(() => this.setIsFetching(false)) + } else { + getFetchPromise(firstRow, reqLimit) + .then(data => { + this._onRowDataLoaded(data) + }) + .catch(err => { + console.error(err) + this._onRowDataLoaded(null) + }) + .finally(() => this.setIsFetching(false)) + } + } + } +}) From 038f41110f1bc3d30839720b29bca2fffcb19591 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 14 Jan 2025 17:24:45 +0100 Subject: [PATCH 02/33] Show pruchases --- .../class/osparc/desktop/credits/Rentals.js | 44 ++------ .../osparc/desktop/credits/RentalsTable.js | 100 ++++++------------ .../desktop/credits/RentalsTableModel.js | 82 ++++++-------- 3 files changed, 78 insertions(+), 148 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Rentals.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Rentals.js index d15516f9415..59269ea06bc 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/Rentals.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Rentals.js @@ -22,22 +22,15 @@ qx.Class.define("osparc.desktop.credits.Rentals", { construct: function() { this.base(arguments); - this._setLayout(new qx.ui.layout.VBox(15)); - - const store = osparc.store.Store.getInstance(); - this.__userWallets = store.getWallets(); + this._setLayout(new qx.ui.layout.VBox(5)); this.__buildLayout() }, members: { __buildLayout: function() { - this._removeAll(); - - const container = new qx.ui.container.Composite(new qx.ui.layout.VBox(5)); - - const lbl = new qx.ui.basic.Label("Select a Credit Account:"); - container.add(lbl); + const lbl = new qx.ui.basic.Label(this.tr("Select a Credit Account:")); + this._add(lbl); const selectBoxContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); const walletSelectBox = new qx.ui.form.SelectBox().set({ @@ -53,7 +46,7 @@ qx.Class.define("osparc.desktop.credits.Rentals", { }); this.__fetchingImg.getContentElement().addClass("rotate"); selectBoxContainer.add(this.__fetchingImg); - container.add(selectBoxContainer); + this._add(selectBoxContainer); const filterContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)) this.__dateFilters = new osparc.desktop.credits.DateFilters(); @@ -62,26 +55,13 @@ qx.Class.define("osparc.desktop.credits.Rentals", { this.__table.getTableModel().reloadData() }); filterContainer.add(this.__dateFilters); - filterContainer.add(new qx.ui.core.Spacer(), { - flex: 1 - }); - this.__exportButton = new qx.ui.form.Button(this.tr("Export")).set({ - allowStretchY: false, - alignY: "bottom" - }); - this.__exportButton.addListener("execute", () => { - this.__handleExport() - }); - filterContainer.add(this.__exportButton); const refreshButton = new qx.ui.form.Button(this.tr("Reload"), "@FontAwesome5Solid/sync-alt/14").set({ allowStretchY: false, alignY: "bottom" }); refreshButton.addListener("execute", () => this.__table && this.__table.getTableModel().reloadData()); filterContainer.add(refreshButton) - container.add(filterContainer); - - this._add(container); + this._add(filterContainer); walletSelectBox.addListener("changeSelection", e => { const selection = e.getData(); @@ -98,33 +78,27 @@ qx.Class.define("osparc.desktop.credits.Rentals", { this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", { converter: isFetching => isFetching ? "visible" : "excluded" }) - container.add(this.__table, { flex: 1 }) + this._add(this.__table, { flex: 1 }) } } }); if (osparc.desktop.credits.Utils.areWalletsEnabled()) { - this.__userWallets.forEach(wallet => { + const store = osparc.store.Store.getInstance(); + store.getWallets().forEach(wallet => { walletSelectBox.add(new qx.ui.form.ListItem(wallet.getName(), null, wallet)); }); } else { lbl.setVisibility("excluded") walletSelectBox.setVisibility("excluded") - this.__exportButton.setVisibility("excluded") this.__table = new osparc.desktop.credits.RentalsTable(null, this.__dateFilters.getValue()).set({ marginTop: 10 }) this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", { converter: isFetching => isFetching ? "visible" : "excluded" }) - container.add(this.__table, { flex: 1 }) + this._add(this.__table, { flex: 1 }) } }, - __handleExport() { - const reportUrl = new URL("/v0/services/-/rentals-report", window.location.origin) - reportUrl.searchParams.append("wallet_id", this.__selectedWallet.getWalletId()) - reportUrl.searchParams.append("filters", JSON.stringify({ "started_at": this.__dateFilters.getValue() })) - window.open(reportUrl, "_blank") - } } }); diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTable.js b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTable.js index 01d7089157a..9c5f5695910 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTable.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTable.js @@ -20,106 +20,74 @@ qx.Class.define("osparc.desktop.credits.RentalsTable", { extend: qx.ui.table.Table, construct: function(walletId, filters) { - this.base(arguments) - const model = new osparc.desktop.credits.RentalsTableModel(walletId, filters) - this.setTableModel(model) - this.setStatusBarVisible(false) + this.base(arguments); - this.setHeaderCellHeight(26); - this.setRowHeight(26); + const model = new osparc.desktop.credits.RentalsTableModel(walletId, filters); + this.setTableModel(model); - const columnModel = this.getTableColumnModel(); + this.set({ + statusBarVisible: false, + headerCellHeight: 26, + rowHeight: 26, + }); + const columnModel = this.getTableColumnModel(); + columnModel.setColumnVisible(this.self().COLS.PURCHASE_ID.column, false); + columnModel.setColumnVisible(this.self().COLS.ITEM_ID.column, false); columnModel.setDataCellRenderer(this.self().COLS.COST.column, new qx.ui.table.cellrenderer.Number()); - if (!osparc.desktop.credits.Utils.areWalletsEnabled()) { - columnModel.setColumnVisible(this.self().COLS.COST.column, false); - columnModel.setColumnVisible(this.self().COLS.USER.column, false); - } - columnModel.setColumnVisible(this.self().COLS.SERVICE.column, false); - - // Array [0, 1, ..., N] where N is column_count - 1 (default column order) - this.__columnOrder = [...Array(columnModel.getOverallColumnCount()).keys()] - - if ( - osparc.Preferences.getInstance().getBillingCenterRentalsColumnOrder() && - osparc.Preferences.getInstance().getBillingCenterRentalsColumnOrder().length === this.__columnOrder.length - ) { - columnModel.setColumnsOrder(osparc.Preferences.getInstance().getBillingCenterRentalsColumnOrder()) - this.__columnOrder = osparc.Preferences.getInstance().getBillingCenterRentalsColumnOrder() - } else { - osparc.Preferences.getInstance().setBillingCenterRentalsColumnOrder(this.__columnOrder) - } - - columnModel.addListener("orderChanged", e => { - // Save new order into preferences - if (e.getData()) { - const { fromOverXPos, toOverXPos } = e.getData() - // Edit current order - this.__columnOrder = this.__columnOrder.toSpliced(toOverXPos, 0, this.__columnOrder.splice(fromOverXPos, 1)[0]) - // Save order - osparc.Preferences.getInstance().setBillingCenterRentalsColumnOrder(this.__columnOrder) - } - }, this) - Object.values(this.self().COLS).forEach(col => columnModel.setColumnWidth(col.column, col.width)); }, statics: { COLS: { - PROJECT: { - id: "project", + PURCHASE_ID: { + id: "purchaseId", column: 0, - label: osparc.product.Utils.getStudyAlias({firstUpperCase: true}), - width: 140 + label: qx.locale.Manager.tr("PurchaseId"), + width: 150 }, - NODE: { - id: "node", + ITEM_ID: { + id: "itemId", column: 1, - label: qx.locale.Manager.tr("Node"), - width: 140 + label: qx.locale.Manager.tr("ItemId"), + width: 150 }, - SERVICE: { - id: "service", + ITEM_LABEL: { + id: "itemLabel", column: 2, - label: qx.locale.Manager.tr("Service"), - width: 140 + label: qx.locale.Manager.tr("Name"), + width: 150 }, START: { id: "start", column: 3, label: qx.locale.Manager.tr("Start"), - width: 130 + width: 150 }, - DURATION: { - id: "duration", + END: { + id: "end", column: 4, - label: qx.locale.Manager.tr("Duration"), - width: 70 + label: qx.locale.Manager.tr("End"), + width: 150 }, - STATUS: { - id: "status", + SEATS: { + id: "seats", column: 5, - label: qx.locale.Manager.tr("Status"), - width: 70 + label: qx.locale.Manager.tr("Seats"), + width: 50 }, COST: { id: "cost", column: 6, label: qx.locale.Manager.tr("Credits"), - width: 56 + width: 60 }, USER: { id: "user", column: 7, label: qx.locale.Manager.tr("User"), - width: 140 - }, - TAGS: { - id: "tags", - column: 7, - label: qx.locale.Manager.tr("Tags"), - width: 140 + width: 100 }, } } diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js index 2ebcf733325..e1908e91034 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js @@ -32,8 +32,7 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { this.setFilters(filters) } this.setSortColumnIndexWithoutSortingData(rentalsCols.START.column); - this.setSortAscendingWithoutSortingData(false) - this.setColumnSortable(rentalsCols.DURATION.column, false); + this.setSortAscendingWithoutSortingData(false); }, properties: { @@ -61,17 +60,22 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { statics: { SERVER_MAX_LIMIT: 49, + COLUMN_ID_TO_DB_COLUMN_MAP: Object.values(osparc.desktop.credits.RentalsTable.COLS).reduce((acc, { id, column }) => { + acc[column] = id; + return acc; + }, {}), + /* COLUMN_ID_TO_DB_COLUMN_MAP: { - 0: "root_parent_project_name", - 1: "node_name", - 2: "service_key", - 3: "started_at", - // 4: (not used) SORTING BY DURATION - 5: "service_run_status", - 6: "credit_cost", - 7: "user_email", - 8: "projects_tags", - } + 0: "purchaseId", + 1: "itemId", + 2: "itemLabel", + 3: "start", + 4: "duration", + 5: "seats", + 6: "cost", + 7: "user", + }, + */ }, members: { @@ -86,7 +90,6 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { // overridden _loadRowCount() { - const endpoint = this.getWalletId() == null ? "get" : "getWithWalletFiltered" const params = { url: { walletId: this.getWalletId(), @@ -103,24 +106,23 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { const options = { resolveWResponse: true }; - osparc.data.Resources.fetch("resourceRentals", endpoint, params, options) + osparc.data.Resources.fetch("wallets", "purchases", params, options) .then(resp => { this._onRowCountLoaded(resp["_meta"].total) }) .catch(() => { this._onRowCountLoaded(null) - }) + }); }, // overridden _loadRowData(firstRow, qxLastRow) { - this.setIsFetching(true) - // Please Qloocloox don't ask for more rows than there are + this.setIsFetching(true); + const lastRow = Math.min(qxLastRow, this._rowCount - 1) // Returns a request promise with given offset and limit const getFetchPromise = (offset, limit=this.self().SERVER_MAX_LIMIT) => { - const endpoint = this.getWalletId() == null ? "get" : "getWithWalletFiltered" - return osparc.data.Resources.fetch("resourceRentals", endpoint, { + return osparc.data.Resources.fetch("wallets", "purchases", { url: { walletId: this.getWalletId(), limit, @@ -137,38 +139,24 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { const data = [] const rentalsCols = osparc.desktop.credits.RentalsTable.COLS; rawData.forEach(rawRow => { - let service = "" - if (rawRow["service_key"]) { - const serviceName = rawRow["service_key"].split("/").pop() - service = `${serviceName}:${rawRow["service_version"]}` - } - let start = "" - let duration = "" - if (rawRow["started_at"]) { - start = osparc.utils.Utils.formatDateAndTime(new Date(rawRow["started_at"])) - if (rawRow["stopped_at"]) { - duration = osparc.utils.Utils.formatMsToHHMMSS(new Date(rawRow["stopped_at"]) - new Date(rawRow["started_at"])) - } - } data.push({ - // root_parent_project is the same as project if it has no parent - [rentalsCols.PROJECT.id]: rawRow["root_parent_project_name"] || rawRow["root_parent_project_id"] || rawRow["project_name"] || rawRow["project_id"], - [rentalsCols.NODE.id]: rawRow["node_name"] || rawRow["node_id"], - [rentalsCols.SERVICE.id]: service, - [rentalsCols.START.id]: start, - [rentalsCols.DURATION.id]: duration, - [rentalsCols.STATUS.id]: qx.lang.String.firstUp(rawRow["service_run_status"].toLowerCase()), - [rentalsCols.COST.id]: rawRow["credit_cost"] ? parseFloat(rawRow["credit_cost"]).toFixed(2) : "", - [rentalsCols.USER.id]: rawRow["user_email"], - [rentalsCols.TAGS.id]: rawRow["project_tags"], + [rentalsCols.PURCHASE_ID.id]: rawRow["licensedItemPurchaseId"], + [rentalsCols.ITEM_ID.id]: rawRow["licensedItemId"], + [rentalsCols.ITEM_LABEL.id]: "Hello", + [rentalsCols.START.id]: osparc.utils.Utils.formatDateAndTime(new Date(rawRow["startAt"])), + [rentalsCols.END.id]: osparc.utils.Utils.formatDateAndTime(new Date(rawRow["expireAt"])), + [rentalsCols.SEATS.id]: rawRow["numOfSeats"], + [rentalsCols.COST.id]: rawRow["pricingUnitCost"] ? parseFloat(rawRow["pricingUnitCost"]).toFixed(2) : "", + [rentalsCols.USER.id]: rawRow["purchasedByUser"], }) }) - return data - }) - } + return data; + }); + }; + // Divides the model row request into several server requests to comply with the number of rows server limit - const reqLimit = lastRow - firstRow + 1 // Number of requested rows - const nRequests = Math.ceil(reqLimit / this.self().SERVER_MAX_LIMIT) + const reqLimit = lastRow - firstRow + 1; // Number of requested rows + const nRequests = Math.ceil(reqLimit / this.self().SERVER_MAX_LIMIT); if (nRequests > 1) { let requests = [] for (let i=firstRow; i <= lastRow; i += this.self().SERVER_MAX_LIMIT) { From 110f7671993608838ca803eb7320910d5cb9cd29 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 14 Jan 2025 17:31:27 +0100 Subject: [PATCH 03/33] sorting --- .../osparc/desktop/credits/RentalsTableModel.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js index e1908e91034..3be09290d26 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js @@ -60,22 +60,9 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { statics: { SERVER_MAX_LIMIT: 49, - COLUMN_ID_TO_DB_COLUMN_MAP: Object.values(osparc.desktop.credits.RentalsTable.COLS).reduce((acc, { id, column }) => { - acc[column] = id; - return acc; - }, {}), - /* COLUMN_ID_TO_DB_COLUMN_MAP: { - 0: "purchaseId", - 1: "itemId", - 2: "itemLabel", - 3: "start", - 4: "duration", - 5: "seats", - 6: "cost", - 7: "user", + 0: "purchased_at", }, - */ }, members: { From 7d20373928aa34e0865a14c767e89a3e56abbc9d Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 14 Jan 2025 17:48:54 +0100 Subject: [PATCH 04/33] minor --- .../source/class/osparc/desktop/credits/BillingCenter.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js b/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js index 65f889350b2..eaf64612f97 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js @@ -32,7 +32,10 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", { if (osparc.data.Permissions.getInstance().canDo("usage.all.read")) { this.__usagePage = this.__addUsagePage(); - this.__rentalsPage = this.__addRentalsPage(); + } + + if (osparc.product.Utils.showS4LStore()) { + this.__addRentalsPage(); } }, @@ -60,7 +63,6 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", { __paymentMethodsPage: null, __transactionsPage: null, __usagePage: null, - __rentalsPage: null, __paymentMethods: null, __transactionsTable: null, From dd52466a4049fa3cc3d5c30b7d7c6f5574c4ee47 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 14 Jan 2025 18:00:02 +0100 Subject: [PATCH 05/33] show name --- .../desktop/credits/RentalsTableModel.js | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js index 3be09290d26..dead285cc9c 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js @@ -109,32 +109,40 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { const lastRow = Math.min(qxLastRow, this._rowCount - 1) // Returns a request promise with given offset and limit const getFetchPromise = (offset, limit=this.self().SERVER_MAX_LIMIT) => { - return osparc.data.Resources.fetch("wallets", "purchases", { - url: { - walletId: this.getWalletId(), - limit, - offset, - filters: this.getFilters() ? - JSON.stringify({ - "started_at": this.getFilters() - }) : - null, - orderBy: JSON.stringify(this.getOrderBy()) - } - }) - .then(rawData => { - const data = [] + return Promise.all([ + osparc.data.Resources.get("licensedItems"), + osparc.data.Resources.fetch("wallets", "purchases", { + url: { + walletId: this.getWalletId(), + limit, + offset, + filters: this.getFilters() ? + JSON.stringify({ + "started_at": this.getFilters() + }) : + null, + orderBy: JSON.stringify(this.getOrderBy()) + } + }), + ]) + .then(values => { + const licensedItems = values[0]; + const purchasesItems = values[1]; + + const data = []; const rentalsCols = osparc.desktop.credits.RentalsTable.COLS; - rawData.forEach(rawRow => { + purchasesItems.forEach(purchasesItem => { + const licensedItemId = purchasesItem["licensedItemId"]; + const licensedItem = licensedItems.find(licItem => licItem["licensedItemId"] === licensedItemId); data.push({ - [rentalsCols.PURCHASE_ID.id]: rawRow["licensedItemPurchaseId"], - [rentalsCols.ITEM_ID.id]: rawRow["licensedItemId"], - [rentalsCols.ITEM_LABEL.id]: "Hello", - [rentalsCols.START.id]: osparc.utils.Utils.formatDateAndTime(new Date(rawRow["startAt"])), - [rentalsCols.END.id]: osparc.utils.Utils.formatDateAndTime(new Date(rawRow["expireAt"])), - [rentalsCols.SEATS.id]: rawRow["numOfSeats"], - [rentalsCols.COST.id]: rawRow["pricingUnitCost"] ? parseFloat(rawRow["pricingUnitCost"]).toFixed(2) : "", - [rentalsCols.USER.id]: rawRow["purchasedByUser"], + [rentalsCols.PURCHASE_ID.id]: purchasesItem["licensedItemPurchaseId"], + [rentalsCols.ITEM_ID.id]: licensedItemId, + [rentalsCols.ITEM_LABEL.id]: licensedItem ? licensedItem["name"] : "unknown model", + [rentalsCols.START.id]: osparc.utils.Utils.formatDateAndTime(new Date(purchasesItem["startAt"])), + [rentalsCols.END.id]: osparc.utils.Utils.formatDateAndTime(new Date(purchasesItem["expireAt"])), + [rentalsCols.SEATS.id]: purchasesItem["numOfSeats"], + [rentalsCols.COST.id]: purchasesItem["pricingUnitCost"] ? parseFloat(purchasesItem["pricingUnitCost"]).toFixed(2) : "", + [rentalsCols.USER.id]: purchasesItem["purchasedByUser"], }) }) return data; From d06227c595802fbe4c8be70811a4ef58451904bb Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 14 Jan 2025 18:03:36 +0100 Subject: [PATCH 06/33] Transactions -> Payments --- .../client/source/class/osparc/desktop/credits/BillingCenter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js b/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js index eaf64612f97..2ded92c4a88 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js @@ -84,7 +84,7 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", { }, __addTransactionsPage: function() { - const title = this.tr("Transactions"); + const title = this.tr("Payments"); const iconSrc = "@FontAwesome5Solid/exchange-alt/22"; const transactions = this.__transactionsTable = new osparc.desktop.credits.Transactions(); const page = this.addTab(title, iconSrc, transactions); From 17d4f20f9ce8298a72c6b4b46a9fe905e7ba3e1f Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 14 Jan 2025 18:05:27 +0100 Subject: [PATCH 07/33] minor --- .../source/class/osparc/desktop/credits/RentalsTableModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js index dead285cc9c..572ef2ae024 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js @@ -110,7 +110,7 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { // Returns a request promise with given offset and limit const getFetchPromise = (offset, limit=this.self().SERVER_MAX_LIMIT) => { return Promise.all([ - osparc.data.Resources.get("licensedItems"), + osparc.data.Resources.getInstance().getAllPages("licensedItems"), osparc.data.Resources.fetch("wallets", "purchases", { url: { walletId: this.getWalletId(), From 87512a20abf0b6bb5eb255886ac180432ed0970f Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 10:35:05 +0100 Subject: [PATCH 08/33] LicensedItems store --- .../class/osparc/store/LicensedItems.js | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 services/static-webserver/client/source/class/osparc/store/LicensedItems.js diff --git a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js new file mode 100644 index 00000000000..d6b17201f34 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js @@ -0,0 +1,112 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2025 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.store.LicensedItems", { + extend: qx.core.Object, + type: "singleton", + + construct: function() { + this.base(arguments); + + this.__modelsCache = {}; + }, + + statics: { + VIP_MODELS: { + HUMAN_BODY: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/HumanWholeBody", + HUMAN_BODY_REGION: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/HumanBodyRegion", + ANIMAL_BODY: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/AnimalWholeBody", + PHANTOM: "https://speag.swiss/PD_DirectDownload/getDownloadableItems/ComputationalPhantom", + }, + + curateAnatomicalModels: function(anatomicalModelsRaw) { + const anatomicalModels = []; + const models = anatomicalModelsRaw["availableDownloads"]; + models.forEach(model => { + const curatedModel = {}; + Object.keys(model).forEach(key => { + if (key === "Features") { + let featuresRaw = model["Features"]; + featuresRaw = featuresRaw.substring(1, featuresRaw.length-1); // remove brackets + featuresRaw = featuresRaw.split(","); // split the string by commas + const features = {}; + featuresRaw.forEach(pair => { // each pair is "key: value" + const keyValue = pair.split(":"); + features[keyValue[0].trim()] = keyValue[1].trim() + }); + curatedModel["Features"] = features; + } else { + curatedModel[key] = model[key]; + } + }); + anatomicalModels.push(curatedModel); + }); + return anatomicalModels; + }, + }, + + members: { + __modelsCache: null, + + __fetchVipModels: async function(vipSubset) { + if (!(vipSubset in this.VIP_MODELS)) { + return []; + } + + if (vipSubset in this.__modelsCache) { + return this.__modelsCache[vipSubset]; + } + + return await fetch(this.VIP_MODELS[vipSubset], { + method:"POST" + }) + .then(resp => resp.json()) + .then(anatomicalModelsRaw => { + const allAnatomicalModels = this.self().curateAnatomicalModels(anatomicalModelsRaw); + const anatomicalModels = []; + allAnatomicalModels.forEach(model => { + const anatomicalModel = {}; + anatomicalModel["modelId"] = model["ID"]; + anatomicalModel["thumbnail"] = model["Thumbnail"]; + anatomicalModel["name"] = model["Features"]["name"] + " " + model["Features"]["version"]; + anatomicalModel["description"] = model["Description"]; + anatomicalModel["features"] = model["Features"]; + anatomicalModel["date"] = new Date(model["Features"]["date"]); + anatomicalModel["DOI"] = model["DOI"]; + anatomicalModels.push(anatomicalModel); + }); + this.__modelsCache[vipSubset] = anatomicalModels; + return anatomicalModels; + }); + }, + + fetchVipModels: async function(vipSubset) { + const vipModels = this.self().VIP_MODELS; + if (vipSubset && vipSubset in vipModels) { + return await this.__fetchVipModels(vipSubset); + } + const promises = []; + Object.keys(vipModels).forEach(sbs => promises.push(this.__fetchVipModels(sbs))); + return await Promise.all(promises) + .then(values => { + const allVipModels = []; + values.forEach(value => allVipModels.push(...value)); + return allVipModels; + }); + }, + } +}); From 8e0f469bf3408f122c2cd42174aff7f71c6f4a58 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 10:50:53 +0100 Subject: [PATCH 09/33] fetchVipModels from Store --- .../class/osparc/store/LicensedItems.js | 8 ++--- .../source/class/osparc/vipMarket/Market.js | 10 +++--- .../class/osparc/vipMarket/VipMarket.js | 31 ++++++------------- 3 files changed, 19 insertions(+), 30 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js index d6b17201f34..7a23479dd4e 100644 --- a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js +++ b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js @@ -29,7 +29,7 @@ qx.Class.define("osparc.store.LicensedItems", { VIP_MODELS: { HUMAN_BODY: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/HumanWholeBody", HUMAN_BODY_REGION: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/HumanBodyRegion", - ANIMAL_BODY: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/AnimalWholeBody", + ANIMAL: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/AnimalWholeBody", PHANTOM: "https://speag.swiss/PD_DirectDownload/getDownloadableItems/ComputationalPhantom", }, @@ -63,7 +63,7 @@ qx.Class.define("osparc.store.LicensedItems", { __modelsCache: null, __fetchVipModels: async function(vipSubset) { - if (!(vipSubset in this.VIP_MODELS)) { + if (!(vipSubset in this.self().VIP_MODELS)) { return []; } @@ -71,7 +71,7 @@ qx.Class.define("osparc.store.LicensedItems", { return this.__modelsCache[vipSubset]; } - return await fetch(this.VIP_MODELS[vipSubset], { + return await fetch(this.self().VIP_MODELS[vipSubset], { method:"POST" }) .then(resp => resp.json()) @@ -85,7 +85,7 @@ qx.Class.define("osparc.store.LicensedItems", { anatomicalModel["name"] = model["Features"]["name"] + " " + model["Features"]["version"]; anatomicalModel["description"] = model["Description"]; anatomicalModel["features"] = model["Features"]; - anatomicalModel["date"] = new Date(model["Features"]["date"]); + anatomicalModel["date"] = model["Features"]["date"]; anatomicalModel["DOI"] = model["DOI"]; anatomicalModels.push(anatomicalModel); }); diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js index dd4f9567f4d..d90cb9caaea 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js @@ -33,22 +33,22 @@ qx.Class.define("osparc.vipMarket.Market", { category: "human", label: "Humans", icon: "@FontAwesome5Solid/users/20", - url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/HumanWholeBody", + vipSubset: "HUMAN_BODY", }, { category: "human_region", label: "Humans (Region)", icon: "@FontAwesome5Solid/users/20", - url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/HumanBodyRegion", + vipSubset: "HUMAN_BODY_REGION", }, { category: "animal", label: "Animals", icon: "@FontAwesome5Solid/users/20", - url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/AnimalWholeBody", + vipSubset: "ANIMAL", }, { category: "phantom", label: "Phantoms", icon: "@FontAwesome5Solid/users/20", - url: "https://speag.swiss/PD_DirectDownload/getDownloadableItems/ComputationalPhantom", + vipSubset: "PHANTOM", }].forEach(marketInfo => { this.__buildViPMarketPage(marketInfo); }); @@ -76,7 +76,7 @@ qx.Class.define("osparc.vipMarket.Market", { __buildViPMarketPage: function(marketInfo) { const vipMarketView = new osparc.vipMarket.VipMarket(); vipMarketView.set({ - metadataUrl: marketInfo["url"], + vipSubset: marketInfo["vipSubset"], }); this.bind("openBy", vipMarketView, "openBy"); vipMarketView.addListener("importMessageSent", () => this.fireEvent("importMessageSent")); diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js index 79b2626f260..bfba9c39b77 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -38,12 +38,12 @@ qx.Class.define("osparc.vipMarket.VipMarket", { event: "changeOpenBy", }, - metadataUrl: { - check: "String", + vipSubset: { + check: ["HUMAN_BODY", "HUMAN_BODY_REGION", "ANIMAL", "PHANTOM"], init: null, - nullable: false, + nullable: true, apply: "__fetchModels", - } + }, }, statics: { @@ -192,14 +192,9 @@ qx.Class.define("osparc.vipMarket.VipMarket", { }, this); }, - __fetchModels: function(url) { - fetch(url, { - method:"POST" - }) - .then(resp => resp.json()) - .then(anatomicalModelsRaw => { - const allAnatomicalModels = this.self().curateAnatomicalModels(anatomicalModelsRaw); - + __fetchModels: function(vipSubset) { + osparc.store.LicensedItems.getInstance().fetchVipModels(vipSubset) + .then(allAnatomicalModels => { const store = osparc.store.Store.getInstance(); const contextWallet = store.getContextWallet(); if (!contextWallet) { @@ -221,17 +216,11 @@ qx.Class.define("osparc.vipMarket.VipMarket", { this.__anatomicalModels = []; allAnatomicalModels.forEach(model => { - const modelId = model["ID"]; + const modelId = model["modelId"]; const licensedItem = licensedItems.find(licItem => licItem["name"] == modelId); if (licensedItem) { - const anatomicalModel = {}; - anatomicalModel["modelId"] = model["ID"]; - anatomicalModel["thumbnail"] = model["Thumbnail"]; - anatomicalModel["name"] = model["Features"]["name"] + " " + model["Features"]["version"]; - anatomicalModel["description"] = model["Description"]; - anatomicalModel["features"] = model["Features"]; - anatomicalModel["date"] = new Date(model["Features"]["date"]); - anatomicalModel["DOI"] = model["DOI"]; + const anatomicalModel = osparc.utils.Utils.deepCloneObject(model); + anatomicalModel["date"] = new Date(anatomicalModel["date"]); // attach license data anatomicalModel["licensedItemId"] = licensedItem["licensedItemId"]; anatomicalModel["pricingPlanId"] = licensedItem["pricingPlanId"]; From 4b3d523f1bf69303535c1b339598794accb152d9 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 11:01:31 +0100 Subject: [PATCH 10/33] getLicensedItems --- .../osparc/desktop/credits/RentalsTableModel.js | 2 +- .../source/class/osparc/store/LicensedItems.js | 16 +++++++++++++++- .../source/class/osparc/vipMarket/Market.js | 2 +- .../source/class/osparc/vipMarket/VipMarket.js | 4 ++-- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js index 572ef2ae024..40673c7a66c 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js @@ -110,7 +110,7 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { // Returns a request promise with given offset and limit const getFetchPromise = (offset, limit=this.self().SERVER_MAX_LIMIT) => { return Promise.all([ - osparc.data.Resources.getInstance().getAllPages("licensedItems"), + osparc.data.LicensedItems.getInstance().getLicensedItems(), osparc.data.Resources.fetch("wallets", "purchases", { url: { walletId: this.getWalletId(), diff --git a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js index 7a23479dd4e..238bdf3b259 100644 --- a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js +++ b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js @@ -22,6 +22,7 @@ qx.Class.define("osparc.store.LicensedItems", { construct: function() { this.base(arguments); + this.__licensedItems = []; this.__modelsCache = {}; }, @@ -60,8 +61,21 @@ qx.Class.define("osparc.store.LicensedItems", { }, members: { + __licensedItems: null, __modelsCache: null, + getLicensedItems: function() { + if (this.__licensedItems) { + return new Promise(resolve => resolve(this.__licensedItems)); + } + + return osparc.data.Resources.getInstance().getAllPages("licensedItems") + .then(licensedItems => { + this.__licensedItems = licensedItems; + return this.__licensedItems; + }) + }, + __fetchVipModels: async function(vipSubset) { if (!(vipSubset in this.self().VIP_MODELS)) { return []; @@ -94,7 +108,7 @@ qx.Class.define("osparc.store.LicensedItems", { }); }, - fetchVipModels: async function(vipSubset) { + getVipModels: async function(vipSubset) { const vipModels = this.self().VIP_MODELS; if (vipSubset && vipSubset in vipModels) { return await this.__fetchVipModels(vipSubset); diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js index d90cb9caaea..f0543742af8 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js @@ -27,7 +27,7 @@ qx.Class.define("osparc.vipMarket.Market", { }); this.addWidgetOnTopOfTheTabs(miniWallet); - osparc.data.Resources.getInstance().getAllPages("licensedItems") + osparc.data.LicensedItems.getInstance().getLicensedItems() .then(() => { [{ category: "human", diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js index bfba9c39b77..485ba7aa61a 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -193,7 +193,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { }, __fetchModels: function(vipSubset) { - osparc.store.LicensedItems.getInstance().fetchVipModels(vipSubset) + osparc.store.LicensedItems.getInstance().getVipModels(vipSubset) .then(allAnatomicalModels => { const store = osparc.store.Store.getInstance(); const contextWallet = store.getContextWallet(); @@ -207,7 +207,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { } }; Promise.all([ - osparc.data.Resources.get("licensedItems"), + osparc.data.LicensedItems.getInstance().getLicensedItems(), osparc.data.Resources.fetch("wallets", "purchases", purchasesParams), ]) .then(values => { From 6235e083052f18c0bc32a91152c7bc676657bbd2 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 11:08:51 +0100 Subject: [PATCH 11/33] LicensedItems cached --- .../source/class/osparc/desktop/credits/RentalsTableModel.js | 2 +- .../client/source/class/osparc/store/LicensedItems.js | 2 +- .../client/source/class/osparc/vipMarket/Market.js | 2 +- .../client/source/class/osparc/vipMarket/VipMarket.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js index 40673c7a66c..721fa963319 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js @@ -110,7 +110,7 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { // Returns a request promise with given offset and limit const getFetchPromise = (offset, limit=this.self().SERVER_MAX_LIMIT) => { return Promise.all([ - osparc.data.LicensedItems.getInstance().getLicensedItems(), + osparc.store.LicensedItems.getInstance().getLicensedItems(), osparc.data.Resources.fetch("wallets", "purchases", { url: { walletId: this.getWalletId(), diff --git a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js index 238bdf3b259..f212422f1ca 100644 --- a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js +++ b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js @@ -22,7 +22,7 @@ qx.Class.define("osparc.store.LicensedItems", { construct: function() { this.base(arguments); - this.__licensedItems = []; + this.__licensedItems = null; this.__modelsCache = {}; }, diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js index f0543742af8..2d88ab85ad8 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js @@ -27,7 +27,7 @@ qx.Class.define("osparc.vipMarket.Market", { }); this.addWidgetOnTopOfTheTabs(miniWallet); - osparc.data.LicensedItems.getInstance().getLicensedItems() + osparc.store.LicensedItems.getInstance().getLicensedItems() .then(() => { [{ category: "human", diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js index 485ba7aa61a..2ec47c66994 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -207,7 +207,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { } }; Promise.all([ - osparc.data.LicensedItems.getInstance().getLicensedItems(), + osparc.store.LicensedItems.getInstance().getLicensedItems(), osparc.data.Resources.fetch("wallets", "purchases", purchasesParams), ]) .then(values => { From f27ad0767f2a1433e04cf8a2ff4e0de57607f7b7 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 11:12:51 +0100 Subject: [PATCH 12/33] [skip ci] more to Store --- .../source/class/osparc/store/LicensedItems.js | 17 ++++++++++++++++- .../source/class/osparc/vipMarket/VipMarket.js | 18 ++++-------------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js index f212422f1ca..f216b479b01 100644 --- a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js +++ b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js @@ -73,7 +73,22 @@ qx.Class.define("osparc.store.LicensedItems", { .then(licensedItems => { this.__licensedItems = licensedItems; return this.__licensedItems; - }) + }); + }, + + purchaseLicensedItem: function(licensedItemId, walletId, pricingPlanId, pricingUnitId, numberOfSeats) { + const params = { + url: { + licensedItemId + }, + data: { + "wallet_id": walletId, + "pricing_plan_id": pricingPlanId, + "pricing_unit_id": pricingUnitId, + "num_of_seats": numberOfSeats, // this should go away + }, + } + return osparc.data.Resources.fetch("licensedItems", "purchase", params); }, __fetchVipModels: async function(vipSubset) { diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js index 2ec47c66994..a25a49cc57b 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -193,7 +193,8 @@ qx.Class.define("osparc.vipMarket.VipMarket", { }, __fetchModels: function(vipSubset) { - osparc.store.LicensedItems.getInstance().getVipModels(vipSubset) + const licensedItemsStore = osparc.store.LicensedItems.getInstance(); + licensedItemsStore.getVipModels(vipSubset) .then(allAnatomicalModels => { const store = osparc.store.Store.getInstance(); const contextWallet = store.getContextWallet(); @@ -207,7 +208,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { } }; Promise.all([ - osparc.store.LicensedItems.getInstance().getLicensedItems(), + licensedItemsStore.getLicensedItems(), osparc.data.Resources.fetch("wallets", "purchases", purchasesParams), ]) .then(values => { @@ -258,18 +259,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { const split = pricingUnit.getName().split(" "); numberOfSeats = parseInt(split[0]); } - const params = { - url: { - licensedItemId - }, - data: { - "wallet_id": walletId, - "pricing_plan_id": pricingPlanId, - "pricing_unit_id": pricingUnitId, - "num_of_seats": numberOfSeats, // this should go away - }, - } - osparc.data.Resources.fetch("licensedItems", "purchase", params) + licensedItemsStore.purchaseLicensedItem(licensedItemId, walletId, pricingPlanId, pricingUnitId, numberOfSeats) .then(() => { const expirationDate = new Date(); expirationDate.setMonth(expirationDate.getMonth() + 1); // rented for one month From 82cd24d76cc4dcf9e3c8f1d212fa1a46a454d59f Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 11:15:19 +0100 Subject: [PATCH 13/33] more to Store --- .../client/source/class/osparc/store/LicensedItems.js | 9 +++++++++ .../client/source/class/osparc/vipMarket/VipMarket.js | 7 +------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js index f216b479b01..bdd822d3f0b 100644 --- a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js +++ b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js @@ -76,6 +76,15 @@ qx.Class.define("osparc.store.LicensedItems", { }); }, + getPurchasedLicensedItems: function(walletId) { + const purchasesParams = { + url: { + walletId + } + }; + return osparc.data.Resources.fetch("wallets", "purchases", purchasesParams) + }, + purchaseLicensedItem: function(licensedItemId, walletId, pricingPlanId, pricingUnitId, numberOfSeats) { const params = { url: { diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js index a25a49cc57b..6d547668335 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -202,14 +202,9 @@ qx.Class.define("osparc.vipMarket.VipMarket", { return; } const walletId = contextWallet.getWalletId(); - const purchasesParams = { - url: { - walletId - } - }; Promise.all([ licensedItemsStore.getLicensedItems(), - osparc.data.Resources.fetch("wallets", "purchases", purchasesParams), + licensedItemsStore.getPurchasedLicensedItems(walletId), ]) .then(values => { const licensedItems = values[0]; From ecb8d226343dd4f050617c269a0ed115b23c3bfc Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 11:36:58 +0100 Subject: [PATCH 14/33] [skip ci] purchases with pagination --- .../source/class/osparc/data/Resources.js | 2 +- .../desktop/credits/RentalsTableModel.js | 49 +++++++++---------- .../class/osparc/store/LicensedItems.js | 13 +++-- 3 files changed, 33 insertions(+), 31 deletions(-) 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 afd3b79c0b8..9659f2a5f2c 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -938,7 +938,7 @@ qx.Class.define("osparc.data.Resources", { }, purchases: { method: "GET", - url: statics.API + "/wallets/{walletId}/licensed-items-purchases" + url: statics.API + "/wallets/{walletId}/licensed-items-purchases?offset={offset}&limit={limit}" }, } }, diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js index 721fa963319..b0b6793b2e4 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js @@ -77,23 +77,21 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { // overridden _loadRowCount() { - const params = { - url: { - walletId: this.getWalletId(), - limit: 1, - offset: 0, - filters: this.getFilters() ? - JSON.stringify({ - "started_at": this.getFilters() - }) : - null, - orderBy: JSON.stringify(this.getOrderBy()) - } + const walletId = this.getWalletId(); + const urlParams = { + offset: 0, + limit: 1, + filters: this.getFilters() ? + JSON.stringify({ + "started_at": this.getFilters() + }) : + null, + orderBy: JSON.stringify(this.getOrderBy()), }; const options = { resolveWResponse: true }; - osparc.data.Resources.fetch("wallets", "purchases", params, options) + osparc.store.LicensedItems.getInstance().getPurchasedLicensedItems(walletId, urlParams, options) .then(resp => { this._onRowCountLoaded(resp["_meta"].total) }) @@ -109,21 +107,20 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { const lastRow = Math.min(qxLastRow, this._rowCount - 1) // Returns a request promise with given offset and limit const getFetchPromise = (offset, limit=this.self().SERVER_MAX_LIMIT) => { + const walletId = this.getWalletId(); + const urlParams = { + limit, + offset, + filters: this.getFilters() ? + JSON.stringify({ + "started_at": this.getFilters() + }) : + null, + orderBy: JSON.stringify(this.getOrderBy()) + } return Promise.all([ osparc.store.LicensedItems.getInstance().getLicensedItems(), - osparc.data.Resources.fetch("wallets", "purchases", { - url: { - walletId: this.getWalletId(), - limit, - offset, - filters: this.getFilters() ? - JSON.stringify({ - "started_at": this.getFilters() - }) : - null, - orderBy: JSON.stringify(this.getOrderBy()) - } - }), + osparc.store.LicensedItems.getInstance().getPurchasedLicensedItems(walletId, urlParams), ]) .then(values => { const licensedItems = values[0]; diff --git a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js index bdd822d3f0b..2055c1195d2 100644 --- a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js +++ b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js @@ -76,13 +76,18 @@ qx.Class.define("osparc.store.LicensedItems", { }); }, - getPurchasedLicensedItems: function(walletId) { - const purchasesParams = { + getPurchasedLicensedItems: function(walletId, urlParams, options = {}) { + let purchasesParams = { url: { - walletId + walletId, + offset: 0, + limit: 49, } }; - return osparc.data.Resources.fetch("wallets", "purchases", purchasesParams) + if (urlParams) { + purchasesParams.url = Object.assign(purchasesParams.url, urlParams); + } + return osparc.data.Resources.fetch("wallets", "purchases", purchasesParams, options); }, purchaseLicensedItem: function(licensedItemId, walletId, pricingPlanId, pricingUnitId, numberOfSeats) { From 559ed08ef361a86beb9b4a3ae7d387b05b825756 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 11:38:46 +0100 Subject: [PATCH 15/33] minor --- .../source/class/osparc/desktop/credits/RentalsTableModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js index b0b6793b2e4..9cdcf59cb48 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js @@ -52,7 +52,7 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { orderBy: { check: "Object", init: { - field: "started_at", + field: "purchased_at", direction: "desc" } } From 6331c54805d6b30259a874fc1c57e292c4e07bc0 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 11:43:45 +0100 Subject: [PATCH 16/33] [skip ci] flat() --- .../desktop/credits/RentalsTableModel.js | 24 +++++++++---------- .../class/osparc/store/LicensedItems.js | 6 +---- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js index 9cdcf59cb48..0c85be8b4ef 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js @@ -104,7 +104,7 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { _loadRowData(firstRow, qxLastRow) { this.setIsFetching(true); - const lastRow = Math.min(qxLastRow, this._rowCount - 1) + const lastRow = Math.min(qxLastRow, this._rowCount - 1); // Returns a request promise with given offset and limit const getFetchPromise = (offset, limit=this.self().SERVER_MAX_LIMIT) => { const walletId = this.getWalletId(); @@ -117,7 +117,7 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { }) : null, orderBy: JSON.stringify(this.getOrderBy()) - } + }; return Promise.all([ osparc.store.LicensedItems.getInstance().getLicensedItems(), osparc.store.LicensedItems.getInstance().getPurchasedLicensedItems(walletId, urlParams), @@ -140,8 +140,8 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { [rentalsCols.SEATS.id]: purchasesItem["numOfSeats"], [rentalsCols.COST.id]: purchasesItem["pricingUnitCost"] ? parseFloat(purchasesItem["pricingUnitCost"]).toFixed(2) : "", [rentalsCols.USER.id]: purchasesItem["purchasedByUser"], - }) - }) + }); + }); return data; }); }; @@ -150,29 +150,29 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { const reqLimit = lastRow - firstRow + 1; // Number of requested rows const nRequests = Math.ceil(reqLimit / this.self().SERVER_MAX_LIMIT); if (nRequests > 1) { - let requests = [] + const requests = []; for (let i=firstRow; i <= lastRow; i += this.self().SERVER_MAX_LIMIT) { requests.push(getFetchPromise(i, i > lastRow - this.self().SERVER_MAX_LIMIT + 1 ? reqLimit % this.self().SERVER_MAX_LIMIT : this.self().SERVER_MAX_LIMIT)) } Promise.all(requests) .then(responses => { - this._onRowDataLoaded(responses.flat()) + this._onRowDataLoaded(responses.flat()); }) .catch(err => { - console.error(err) - this._onRowDataLoaded(null) + console.error(err); + this._onRowDataLoaded(null); }) - .finally(() => this.setIsFetching(false)) + .finally(() => this.setIsFetching(false)); } else { getFetchPromise(firstRow, reqLimit) .then(data => { - this._onRowDataLoaded(data) + this._onRowDataLoaded(data); }) .catch(err => { console.error(err) - this._onRowDataLoaded(null) + this._onRowDataLoaded(null); }) - .finally(() => this.setIsFetching(false)) + .finally(() => this.setIsFetching(false)); } } } diff --git a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js index 2055c1195d2..0acb0b50318 100644 --- a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js +++ b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js @@ -145,11 +145,7 @@ qx.Class.define("osparc.store.LicensedItems", { const promises = []; Object.keys(vipModels).forEach(sbs => promises.push(this.__fetchVipModels(sbs))); return await Promise.all(promises) - .then(values => { - const allVipModels = []; - values.forEach(value => allVipModels.push(...value)); - return allVipModels; - }); + .then(values => values.flat()); }, } }); From d30baaf855e66955be0b8d1e4abf800f9d7ff390 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 11:47:27 +0100 Subject: [PATCH 17/33] moved --- .../class/osparc/vipMarket/VipMarket.js | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js index 6d547668335..6ff8e1b71d0 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -46,33 +46,6 @@ qx.Class.define("osparc.vipMarket.VipMarket", { }, }, - statics: { - curateAnatomicalModels: function(anatomicalModelsRaw) { - const anatomicalModels = []; - const models = anatomicalModelsRaw["availableDownloads"]; - models.forEach(model => { - const curatedModel = {}; - Object.keys(model).forEach(key => { - if (key === "Features") { - let featuresRaw = model["Features"]; - featuresRaw = featuresRaw.substring(1, featuresRaw.length-1); // remove brackets - featuresRaw = featuresRaw.split(","); // split the string by commas - const features = {}; - featuresRaw.forEach(pair => { // each pair is "key: value" - const keyValue = pair.split(":"); - features[keyValue[0].trim()] = keyValue[1].trim() - }); - curatedModel["Features"] = features; - } else { - curatedModel[key] = model[key]; - } - }); - anatomicalModels.push(curatedModel); - }); - return anatomicalModels; - }, - }, - members: { __anatomicalModels: null, __anatomicalModelsModel: null, From a87880850d75527111b758151fe71ef1b8abe626 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 11:54:33 +0100 Subject: [PATCH 18/33] Show model's name --- .../class/osparc/desktop/credits/RentalsTableModel.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js index 0c85be8b4ef..dbc51a7a72f 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js @@ -118,23 +118,27 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { null, orderBy: JSON.stringify(this.getOrderBy()) }; + const licensedItemsStore = osparc.store.LicensedItems.getInstance(); return Promise.all([ - osparc.store.LicensedItems.getInstance().getLicensedItems(), - osparc.store.LicensedItems.getInstance().getPurchasedLicensedItems(walletId, urlParams), + licensedItemsStore.getLicensedItems(), + licensedItemsStore.getPurchasedLicensedItems(walletId, urlParams), + licensedItemsStore.getVipModels(), ]) .then(values => { const licensedItems = values[0]; const purchasesItems = values[1]; + const vipModels = values[2]; const data = []; const rentalsCols = osparc.desktop.credits.RentalsTable.COLS; purchasesItems.forEach(purchasesItem => { const licensedItemId = purchasesItem["licensedItemId"]; const licensedItem = licensedItems.find(licItem => licItem["licensedItemId"] === licensedItemId); + const vipModel = vipModels.find(vipMdl => vipMdl["modelId"] == licensedItem["name"]); data.push({ [rentalsCols.PURCHASE_ID.id]: purchasesItem["licensedItemPurchaseId"], [rentalsCols.ITEM_ID.id]: licensedItemId, - [rentalsCols.ITEM_LABEL.id]: licensedItem ? licensedItem["name"] : "unknown model", + [rentalsCols.ITEM_LABEL.id]: vipModel ? vipModel["name"] : "unknown model", [rentalsCols.START.id]: osparc.utils.Utils.formatDateAndTime(new Date(purchasesItem["startAt"])), [rentalsCols.END.id]: osparc.utils.Utils.formatDateAndTime(new Date(purchasesItem["expireAt"])), [rentalsCols.SEATS.id]: purchasesItem["numOfSeats"], From 4cc33cb80b93d606a8d7ceb304b6a9c5bf41a727 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 11:56:14 +0100 Subject: [PATCH 19/33] minor --- .../source/class/osparc/desktop/credits/RentalsTableModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js index dbc51a7a72f..deb29cf1bee 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js @@ -142,7 +142,7 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { [rentalsCols.START.id]: osparc.utils.Utils.formatDateAndTime(new Date(purchasesItem["startAt"])), [rentalsCols.END.id]: osparc.utils.Utils.formatDateAndTime(new Date(purchasesItem["expireAt"])), [rentalsCols.SEATS.id]: purchasesItem["numOfSeats"], - [rentalsCols.COST.id]: purchasesItem["pricingUnitCost"] ? parseFloat(purchasesItem["pricingUnitCost"]).toFixed(2) : "", + [rentalsCols.COST.id]: purchasesItem["pricingUnitCost"] ? ("-" + parseFloat(purchasesItem["pricingUnitCost"]).toFixed(2)) : "", // show it negative [rentalsCols.USER.id]: purchasesItem["purchasedByUser"], }); }); From 9be4b92e0751c702a48d6925a31ee526a86e316a Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 12:52:26 +0100 Subject: [PATCH 20/33] [skip ci] minor --- .../client/source/class/osparc/data/Resources.js | 12 ++++++++---- .../source/class/osparc/store/LicensedItems.js | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) 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 9659f2a5f2c..ecec050daae 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -936,10 +936,6 @@ qx.Class.define("osparc.data.Resources", { method: "PUT", url: statics.API + "/wallets/{walletId}/auto-recharge" }, - purchases: { - method: "GET", - url: statics.API + "/wallets/{walletId}/licensed-items-purchases?offset={offset}&limit={limit}" - }, } }, /* @@ -1300,10 +1296,18 @@ qx.Class.define("osparc.data.Resources", { method: "GET", url: statics.API + "/catalog/licensed-items?offset={offset}&limit={limit}" }, + purchases: { + method: "GET", + url: statics.API + "/wallets/{walletId}/licensed-items-purchases?offset={offset}&limit={limit}" + }, purchase: { method: "POST", url: statics.API + "/catalog/licensed-items/{licensedItemId}:purchase" }, + checkouts: { + method: "GET", + url: statics.API + "/wallets/{walletId}/licensed-items-checkouts?offset={offset}&limit={limit}" + }, } } }; diff --git a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js index 0acb0b50318..17e968c9f03 100644 --- a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js +++ b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js @@ -87,7 +87,7 @@ qx.Class.define("osparc.store.LicensedItems", { if (urlParams) { purchasesParams.url = Object.assign(purchasesParams.url, urlParams); } - return osparc.data.Resources.fetch("wallets", "purchases", purchasesParams, options); + return osparc.data.Resources.fetch("licensedItems", "purchases", purchasesParams, options); }, purchaseLicensedItem: function(licensedItemId, walletId, pricingPlanId, pricingUnitId, numberOfSeats) { From 3a15b3990b57b7e519dcd72f01af975a2fe6d367 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 14:22:29 +0100 Subject: [PATCH 21/33] INFO --- .../client/source/class/osparc/file/FileLabelWithActions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/file/FileLabelWithActions.js b/services/static-webserver/client/source/class/osparc/file/FileLabelWithActions.js index 93df9367450..43113f880e2 100644 --- a/services/static-webserver/client/source/class/osparc/file/FileLabelWithActions.js +++ b/services/static-webserver/client/source/class/osparc/file/FileLabelWithActions.js @@ -183,7 +183,7 @@ qx.Class.define("osparc.file.FileLabelWithActions", { .then(datas => { if (datas.length) { this.fireDataEvent("fileDeleted", datas[0]); - osparc.FlashMessenger.getInstance().logAs(this.tr("Files successfully deleted"), "ERROR"); + osparc.FlashMessenger.getInstance().logAs(this.tr("Files successfully deleted"), "INFO"); } }); requests From 1d555ed03a7ce143abc47cbc6e5b4883c0c3d478 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 15:13:22 +0100 Subject: [PATCH 22/33] Checkouts --- .../osparc/desktop/credits/BillingCenter.js | 19 +- .../credits/{Rentals.js => Checkouts.js} | 6 +- .../{RentalsTable.js => CheckoutsTable.js} | 4 +- .../desktop/credits/CheckoutsTableModel.js | 183 ++++++++++++++++++ .../class/osparc/desktop/credits/Purchases.js | 104 ++++++++++ .../osparc/desktop/credits/PurchasesTable.js | 94 +++++++++ ...lsTableModel.js => PurchasesTableModel.js} | 28 +-- 7 files changed, 414 insertions(+), 24 deletions(-) rename services/static-webserver/client/source/class/osparc/desktop/credits/{Rentals.js => Checkouts.js} (91%) rename services/static-webserver/client/source/class/osparc/desktop/credits/{RentalsTable.js => CheckoutsTable.js} (93%) create mode 100644 services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js create mode 100644 services/static-webserver/client/source/class/osparc/desktop/credits/Purchases.js create mode 100644 services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTable.js rename services/static-webserver/client/source/class/osparc/desktop/credits/{RentalsTableModel.js => PurchasesTableModel.js} (79%) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js b/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js index 2ded92c4a88..8e68c41070c 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js @@ -35,7 +35,8 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", { } if (osparc.product.Utils.showS4LStore()) { - this.__addRentalsPage(); + this.__addPurchasesPage(); + this.__addCheckoutsPage(); } }, @@ -99,11 +100,19 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", { return page; }, - __addRentalsPage: function() { - const title = this.tr("Rentals"); + __addPurchasesPage: function() { + const title = this.tr("Purchases"); const iconSrc = "@FontAwesome5Solid/list/22"; - const rentals = new osparc.desktop.credits.Rentals(); - const page = this.addTab(title, iconSrc, rentals); + const purchases = new osparc.desktop.credits.Purchases(); + const page = this.addTab(title, iconSrc, purchases); + return page; + }, + + __addCheckoutsPage: function() { + const title = this.tr("Checkouts"); + const iconSrc = "@FontAwesome5Solid/list/22"; + const purchases = new osparc.desktop.credits.Checkouts(); + const page = this.addTab(title, iconSrc, purchases); return page; }, diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Rentals.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Checkouts.js similarity index 91% rename from services/static-webserver/client/source/class/osparc/desktop/credits/Rentals.js rename to services/static-webserver/client/source/class/osparc/desktop/credits/Checkouts.js index 59269ea06bc..bf7834c0707 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/Rentals.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Checkouts.js @@ -16,7 +16,7 @@ ************************************************************************ */ -qx.Class.define("osparc.desktop.credits.Rentals", { +qx.Class.define("osparc.desktop.credits.Checkouts", { extend: qx.ui.core.Widget, construct: function() { @@ -72,7 +72,7 @@ qx.Class.define("osparc.desktop.credits.Rentals", { this.__table.getTableModel().reloadData() } else { // qx: changeSelection is triggered after the first item is added to SelectBox - this.__table = new osparc.desktop.credits.RentalsTable(this.__selectedWallet.getWalletId(), this.__dateFilters.getValue()).set({ + this.__table = new osparc.desktop.credits.CheckoutsTable(this.__selectedWallet.getWalletId(), this.__dateFilters.getValue()).set({ marginTop: 10 }) this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", { @@ -91,7 +91,7 @@ qx.Class.define("osparc.desktop.credits.Rentals", { } else { lbl.setVisibility("excluded") walletSelectBox.setVisibility("excluded") - this.__table = new osparc.desktop.credits.RentalsTable(null, this.__dateFilters.getValue()).set({ + this.__table = new osparc.desktop.credits.CheckoutsTable(null, this.__dateFilters.getValue()).set({ marginTop: 10 }) this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", { diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTable.js b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTable.js similarity index 93% rename from services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTable.js rename to services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTable.js index 9c5f5695910..8c590bc779c 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTable.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTable.js @@ -16,13 +16,13 @@ ************************************************************************ */ -qx.Class.define("osparc.desktop.credits.RentalsTable", { +qx.Class.define("osparc.desktop.credits.CheckoutsTable", { extend: qx.ui.table.Table, construct: function(walletId, filters) { this.base(arguments); - const model = new osparc.desktop.credits.RentalsTableModel(walletId, filters); + const model = new osparc.desktop.credits.CheckoutsTableModel(walletId, filters); this.setTableModel(model); this.set({ diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js new file mode 100644 index 00000000000..e8591d4cab0 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js @@ -0,0 +1,183 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2025 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + + +qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { + extend: qx.ui.table.model.Remote, + + construct(walletId, filters) { + this.base(arguments); + + const checkoutsCols = osparc.desktop.credits.CheckoutsTable.COLS; + const colLabels = Object.values(checkoutsCols).map(col => col.label); + const colIDs = Object.values(checkoutsCols).map(col => col.id); + + this.setColumns(colLabels, colIDs); + this.setWalletId(walletId) + if (filters) { + this.setFilters(filters) + } + this.setSortColumnIndexWithoutSortingData(checkoutsCols.START.column); + this.setSortAscendingWithoutSortingData(false); + }, + + properties: { + walletId: { + check: "Number", + nullable: true + }, + filters: { + check: "Object", + init: null + }, + isFetching: { + check: "Boolean", + init: false, + event: "changeFetching" + }, + orderBy: { + check: "Object", + init: { + field: "purchased_at", + direction: "desc" + } + } + }, + + statics: { + SERVER_MAX_LIMIT: 49, + COLUMN_ID_TO_DB_COLUMN_MAP: { + 0: "purchased_at", + }, + }, + + members: { + // overridden + sortByColumn(columnIndex, ascending) { + this.setOrderBy({ + field: this.self().COLUMN_ID_TO_DB_COLUMN_MAP[columnIndex], + direction: ascending ? "asc" : "desc" + }) + this.base(arguments, columnIndex, ascending) + }, + + // overridden + _loadRowCount() { + const walletId = this.getWalletId(); + const urlParams = { + offset: 0, + limit: 1, + filters: this.getFilters() ? + JSON.stringify({ + "started_at": this.getFilters() + }) : + null, + orderBy: JSON.stringify(this.getOrderBy()), + }; + const options = { + resolveWResponse: true + }; + osparc.store.LicensedItems.getInstance().getPurchasedLicensedItems(walletId, urlParams, options) + .then(resp => { + this._onRowCountLoaded(resp["_meta"].total) + }) + .catch(() => { + this._onRowCountLoaded(null) + }); + }, + + // overridden + _loadRowData(firstRow, qxLastRow) { + this.setIsFetching(true); + + const lastRow = Math.min(qxLastRow, this._rowCount - 1); + // Returns a request promise with given offset and limit + const getFetchPromise = (offset, limit=this.self().SERVER_MAX_LIMIT) => { + const walletId = this.getWalletId(); + const urlParams = { + limit, + offset, + filters: this.getFilters() ? + JSON.stringify({ + "started_at": this.getFilters() + }) : + null, + orderBy: JSON.stringify(this.getOrderBy()) + }; + const licensedItemsStore = osparc.store.LicensedItems.getInstance(); + return Promise.all([ + licensedItemsStore.getLicensedItems(), + licensedItemsStore.getPurchasedLicensedItems(walletId, urlParams), + licensedItemsStore.getVipModels(), + ]) + .then(values => { + const licensedItems = values[0]; + const checkoutsItems = values[1]; + const vipModels = values[2]; + + const data = []; + const checkoutsCols = osparc.desktop.credits.CheckoutsTable.COLS; + checkoutsItems.forEach(checkoutsItem => { + const licensedItemId = checkoutsItem["licensedItemId"]; + const licensedItem = licensedItems.find(licItem => licItem["licensedItemId"] === licensedItemId); + const vipModel = vipModels.find(vipMdl => vipMdl["modelId"] == licensedItem["name"]); + data.push({ + [checkoutsCols.PURCHASE_ID.id]: checkoutsItem["licensedItemPurchaseId"], + [checkoutsCols.ITEM_ID.id]: licensedItemId, + [checkoutsCols.ITEM_LABEL.id]: vipModel ? vipModel["name"] : "unknown model", + [checkoutsCols.START.id]: osparc.utils.Utils.formatDateAndTime(new Date(checkoutsItem["startAt"])), + [checkoutsCols.END.id]: osparc.utils.Utils.formatDateAndTime(new Date(checkoutsItem["expireAt"])), + [checkoutsCols.SEATS.id]: checkoutsItem["numOfSeats"], + [checkoutsCols.COST.id]: checkoutsItem["pricingUnitCost"] ? ("-" + parseFloat(checkoutsItem["pricingUnitCost"]).toFixed(2)) : "", // show it negative + [checkoutsCols.USER.id]: checkoutsItem["purchasedByUser"], + }); + }); + return data; + }); + }; + + // Divides the model row request into several server requests to comply with the number of rows server limit + const reqLimit = lastRow - firstRow + 1; // Number of requested rows + const nRequests = Math.ceil(reqLimit / this.self().SERVER_MAX_LIMIT); + if (nRequests > 1) { + const requests = []; + for (let i=firstRow; i <= lastRow; i += this.self().SERVER_MAX_LIMIT) { + requests.push(getFetchPromise(i, i > lastRow - this.self().SERVER_MAX_LIMIT + 1 ? reqLimit % this.self().SERVER_MAX_LIMIT : this.self().SERVER_MAX_LIMIT)) + } + Promise.all(requests) + .then(responses => { + this._onRowDataLoaded(responses.flat()); + }) + .catch(err => { + console.error(err); + this._onRowDataLoaded(null); + }) + .finally(() => this.setIsFetching(false)); + } else { + getFetchPromise(firstRow, reqLimit) + .then(data => { + this._onRowDataLoaded(data); + }) + .catch(err => { + console.error(err) + this._onRowDataLoaded(null); + }) + .finally(() => this.setIsFetching(false)); + } + } + } +}) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Purchases.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Purchases.js new file mode 100644 index 00000000000..944be0c51fb --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Purchases.js @@ -0,0 +1,104 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2025 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + + +qx.Class.define("osparc.desktop.credits.Purchases", { + extend: qx.ui.core.Widget, + + construct: function() { + this.base(arguments); + + this._setLayout(new qx.ui.layout.VBox(5)); + + this.__buildLayout() + }, + + members: { + __buildLayout: function() { + const lbl = new qx.ui.basic.Label(this.tr("Select a Credit Account:")); + this._add(lbl); + + const selectBoxContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); + const walletSelectBox = new qx.ui.form.SelectBox().set({ + allowStretchX: false, + width: 200 + }); + selectBoxContainer.add(walletSelectBox); + this.__fetchingImg = new qx.ui.basic.Image().set({ + source: "@FontAwesome5Solid/circle-notch/12", + alignX: "center", + alignY: "middle", + visibility: "excluded" + }); + this.__fetchingImg.getContentElement().addClass("rotate"); + selectBoxContainer.add(this.__fetchingImg); + this._add(selectBoxContainer); + + const filterContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)) + this.__dateFilters = new osparc.desktop.credits.DateFilters(); + this.__dateFilters.addListener("change", e => { + this.__table.getTableModel().setFilters(e.getData()) + this.__table.getTableModel().reloadData() + }); + filterContainer.add(this.__dateFilters); + const refreshButton = new qx.ui.form.Button(this.tr("Reload"), "@FontAwesome5Solid/sync-alt/14").set({ + allowStretchY: false, + alignY: "bottom" + }); + refreshButton.addListener("execute", () => this.__table && this.__table.getTableModel().reloadData()); + filterContainer.add(refreshButton) + this._add(filterContainer); + + walletSelectBox.addListener("changeSelection", e => { + const selection = e.getData(); + if (selection.length) { + this.__selectedWallet = selection[0].getModel() + if (this.__table) { + this.__table.getTableModel().setWalletId(this.__selectedWallet.getWalletId()) + this.__table.getTableModel().reloadData() + } else { + // qx: changeSelection is triggered after the first item is added to SelectBox + this.__table = new osparc.desktop.credits.PurchasesTable(this.__selectedWallet.getWalletId(), this.__dateFilters.getValue()).set({ + marginTop: 10 + }) + this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", { + converter: isFetching => isFetching ? "visible" : "excluded" + }) + this._add(this.__table, { flex: 1 }) + } + } + }); + + if (osparc.desktop.credits.Utils.areWalletsEnabled()) { + const store = osparc.store.Store.getInstance(); + store.getWallets().forEach(wallet => { + walletSelectBox.add(new qx.ui.form.ListItem(wallet.getName(), null, wallet)); + }); + } else { + lbl.setVisibility("excluded") + walletSelectBox.setVisibility("excluded") + this.__table = new osparc.desktop.credits.PurchasesTable(null, this.__dateFilters.getValue()).set({ + marginTop: 10 + }) + this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", { + converter: isFetching => isFetching ? "visible" : "excluded" + }) + this._add(this.__table, { flex: 1 }) + } + }, + } +}); diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTable.js b/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTable.js new file mode 100644 index 00000000000..8cd31c0287c --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTable.js @@ -0,0 +1,94 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2025 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + + +qx.Class.define("osparc.desktop.credits.PurchasesTable", { + extend: qx.ui.table.Table, + + construct: function(walletId, filters) { + this.base(arguments); + + const model = new osparc.desktop.credits.PurchasesTableModel(walletId, filters); + this.setTableModel(model); + + this.set({ + statusBarVisible: false, + headerCellHeight: 26, + rowHeight: 26, + }); + + const columnModel = this.getTableColumnModel(); + columnModel.setColumnVisible(this.self().COLS.PURCHASE_ID.column, false); + columnModel.setColumnVisible(this.self().COLS.ITEM_ID.column, false); + columnModel.setDataCellRenderer(this.self().COLS.COST.column, new qx.ui.table.cellrenderer.Number()); + + Object.values(this.self().COLS).forEach(col => columnModel.setColumnWidth(col.column, col.width)); + }, + + statics: { + COLS: { + PURCHASE_ID: { + id: "purchaseId", + column: 0, + label: qx.locale.Manager.tr("PurchaseId"), + width: 150 + }, + ITEM_ID: { + id: "itemId", + column: 1, + label: qx.locale.Manager.tr("ItemId"), + width: 150 + }, + ITEM_LABEL: { + id: "itemLabel", + column: 2, + label: qx.locale.Manager.tr("Name"), + width: 150 + }, + START: { + id: "start", + column: 3, + label: qx.locale.Manager.tr("Start"), + width: 150 + }, + END: { + id: "end", + column: 4, + label: qx.locale.Manager.tr("End"), + width: 150 + }, + SEATS: { + id: "seats", + column: 5, + label: qx.locale.Manager.tr("Seats"), + width: 50 + }, + COST: { + id: "cost", + column: 6, + label: qx.locale.Manager.tr("Credits"), + width: 60 + }, + USER: { + id: "user", + column: 7, + label: qx.locale.Manager.tr("User"), + width: 100 + }, + } + } +}); diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTableModel.js similarity index 79% rename from services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js rename to services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTableModel.js index deb29cf1bee..eef00778ea1 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTableModel.js @@ -16,22 +16,22 @@ ************************************************************************ */ -qx.Class.define("osparc.desktop.credits.RentalsTableModel", { +qx.Class.define("osparc.desktop.credits.PurchasesTableModel", { extend: qx.ui.table.model.Remote, construct(walletId, filters) { this.base(arguments); - const rentalsCols = osparc.desktop.credits.RentalsTable.COLS; - const colLabels = Object.values(rentalsCols).map(col => col.label); - const colIDs = Object.values(rentalsCols).map(col => col.id); + const purchasesCols = osparc.desktop.credits.PurchasesTable.COLS; + const colLabels = Object.values(purchasesCols).map(col => col.label); + const colIDs = Object.values(purchasesCols).map(col => col.id); this.setColumns(colLabels, colIDs); this.setWalletId(walletId) if (filters) { this.setFilters(filters) } - this.setSortColumnIndexWithoutSortingData(rentalsCols.START.column); + this.setSortColumnIndexWithoutSortingData(purchasesCols.START.column); this.setSortAscendingWithoutSortingData(false); }, @@ -130,20 +130,20 @@ qx.Class.define("osparc.desktop.credits.RentalsTableModel", { const vipModels = values[2]; const data = []; - const rentalsCols = osparc.desktop.credits.RentalsTable.COLS; + const purchasesCols = osparc.desktop.credits.PurchasesTable.COLS; purchasesItems.forEach(purchasesItem => { const licensedItemId = purchasesItem["licensedItemId"]; const licensedItem = licensedItems.find(licItem => licItem["licensedItemId"] === licensedItemId); const vipModel = vipModels.find(vipMdl => vipMdl["modelId"] == licensedItem["name"]); data.push({ - [rentalsCols.PURCHASE_ID.id]: purchasesItem["licensedItemPurchaseId"], - [rentalsCols.ITEM_ID.id]: licensedItemId, - [rentalsCols.ITEM_LABEL.id]: vipModel ? vipModel["name"] : "unknown model", - [rentalsCols.START.id]: osparc.utils.Utils.formatDateAndTime(new Date(purchasesItem["startAt"])), - [rentalsCols.END.id]: osparc.utils.Utils.formatDateAndTime(new Date(purchasesItem["expireAt"])), - [rentalsCols.SEATS.id]: purchasesItem["numOfSeats"], - [rentalsCols.COST.id]: purchasesItem["pricingUnitCost"] ? ("-" + parseFloat(purchasesItem["pricingUnitCost"]).toFixed(2)) : "", // show it negative - [rentalsCols.USER.id]: purchasesItem["purchasedByUser"], + [purchasesCols.PURCHASE_ID.id]: purchasesItem["licensedItemPurchaseId"], + [purchasesCols.ITEM_ID.id]: licensedItemId, + [purchasesCols.ITEM_LABEL.id]: vipModel ? vipModel["name"] : "unknown model", + [purchasesCols.START.id]: osparc.utils.Utils.formatDateAndTime(new Date(purchasesItem["startAt"])), + [purchasesCols.END.id]: osparc.utils.Utils.formatDateAndTime(new Date(purchasesItem["expireAt"])), + [purchasesCols.SEATS.id]: purchasesItem["numOfSeats"], + [purchasesCols.COST.id]: purchasesItem["pricingUnitCost"] ? ("-" + parseFloat(purchasesItem["pricingUnitCost"]).toFixed(2)) : "", // show it negative + [purchasesCols.USER.id]: purchasesItem["purchasedByUser"], }); }); return data; From 811e56039a8f58a9dbe67be3a308fa901bb208a9 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 15:33:45 +0100 Subject: [PATCH 23/33] minors --- .../osparc/desktop/credits/CheckoutsTableModel.js | 10 +++++----- .../osparc/desktop/credits/PurchasesTableModel.js | 6 +++--- .../source/class/osparc/store/LicensedItems.js | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js index e8591d4cab0..61636f893b1 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js @@ -52,7 +52,7 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { orderBy: { check: "Object", init: { - field: "purchased_at", + field: "startAt", direction: "desc" } } @@ -61,7 +61,7 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { statics: { SERVER_MAX_LIMIT: 49, COLUMN_ID_TO_DB_COLUMN_MAP: { - 0: "purchased_at", + 0: "startAt", }, }, @@ -83,7 +83,7 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { limit: 1, filters: this.getFilters() ? JSON.stringify({ - "started_at": this.getFilters() + "startAt": this.getFilters() }) : null, orderBy: JSON.stringify(this.getOrderBy()), @@ -91,7 +91,7 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { const options = { resolveWResponse: true }; - osparc.store.LicensedItems.getInstance().getPurchasedLicensedItems(walletId, urlParams, options) + osparc.store.LicensedItems.getInstance().getCheckedOutLicensedItems(walletId, urlParams, options) .then(resp => { this._onRowCountLoaded(resp["_meta"].total) }) @@ -120,7 +120,7 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { }; const licensedItemsStore = osparc.store.LicensedItems.getInstance(); return Promise.all([ - licensedItemsStore.getLicensedItems(), + licensedItemsStore.getCheckedOutLicensedItems(), licensedItemsStore.getPurchasedLicensedItems(walletId, urlParams), licensedItemsStore.getVipModels(), ]) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTableModel.js index eef00778ea1..04ff324c81e 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTableModel.js @@ -52,7 +52,7 @@ qx.Class.define("osparc.desktop.credits.PurchasesTableModel", { orderBy: { check: "Object", init: { - field: "purchased_at", + field: "startAt", direction: "desc" } } @@ -61,7 +61,7 @@ qx.Class.define("osparc.desktop.credits.PurchasesTableModel", { statics: { SERVER_MAX_LIMIT: 49, COLUMN_ID_TO_DB_COLUMN_MAP: { - 0: "purchased_at", + 0: "startAt", }, }, @@ -83,7 +83,7 @@ qx.Class.define("osparc.desktop.credits.PurchasesTableModel", { limit: 1, filters: this.getFilters() ? JSON.stringify({ - "started_at": this.getFilters() + "startAt": this.getFilters() }) : null, orderBy: JSON.stringify(this.getOrderBy()), diff --git a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js index 17e968c9f03..7e86d835d98 100644 --- a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js +++ b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js @@ -105,6 +105,20 @@ qx.Class.define("osparc.store.LicensedItems", { return osparc.data.Resources.fetch("licensedItems", "purchase", params); }, + getCheckedOutLicensedItems: function(walletId, urlParams, options = {}) { + let purchasesParams = { + url: { + walletId, + offset: 0, + limit: 49, + } + }; + if (urlParams) { + purchasesParams.url = Object.assign(purchasesParams.url, urlParams); + } + return osparc.data.Resources.fetch("licensedItems", "checkouts", purchasesParams, options); + }, + __fetchVipModels: async function(vipSubset) { if (!(vipSubset in this.self().VIP_MODELS)) { return []; From a2e6ffc7dfea68ea5a9a790a39c02f689852fecf Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 15:49:24 +0100 Subject: [PATCH 24/33] fix checkouts sorting --- .../src/simcore_service_webserver/licenses/_models.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_models.py b/services/web/server/src/simcore_service_webserver/licenses/_models.py index d5c2ac0947e..d9ded98b02f 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_models.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_models.py @@ -68,12 +68,9 @@ class LicensedItemsPurchasesPathParams(StrictRequestParameters): RequestParameters ] = create_ordering_query_model_class( ordering_fields={ - "purchased_at", - "modified_at", - "name", + "started_at", }, - default=OrderBy(field=IDStr("purchased_at"), direction=OrderDirection.DESC), - ordering_fields_api_to_column_map={"modified_at": "modified"}, + default=OrderBy(field=IDStr("started_at"), direction=OrderDirection.DESC), ) From edc686acdc5bda82a9205bbb437902b3f14f73d5 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 15:52:08 +0100 Subject: [PATCH 25/33] one more check --- .../source/class/osparc/desktop/credits/CheckoutsTableModel.js | 2 +- .../source/class/osparc/desktop/credits/PurchasesTableModel.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js index 61636f893b1..8297259c9ec 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js @@ -134,7 +134,7 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { checkoutsItems.forEach(checkoutsItem => { const licensedItemId = checkoutsItem["licensedItemId"]; const licensedItem = licensedItems.find(licItem => licItem["licensedItemId"] === licensedItemId); - const vipModel = vipModels.find(vipMdl => vipMdl["modelId"] == licensedItem["name"]); + const vipModel = vipModels.find(vipMdl => licensedItem && (vipMdl["modelId"] == licensedItem["name"])); data.push({ [checkoutsCols.PURCHASE_ID.id]: checkoutsItem["licensedItemPurchaseId"], [checkoutsCols.ITEM_ID.id]: licensedItemId, diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTableModel.js index 04ff324c81e..e6a168083ac 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTableModel.js @@ -134,7 +134,7 @@ qx.Class.define("osparc.desktop.credits.PurchasesTableModel", { purchasesItems.forEach(purchasesItem => { const licensedItemId = purchasesItem["licensedItemId"]; const licensedItem = licensedItems.find(licItem => licItem["licensedItemId"] === licensedItemId); - const vipModel = vipModels.find(vipMdl => vipMdl["modelId"] == licensedItem["name"]); + const vipModel = vipModels.find(vipMdl => licensedItem && (vipMdl["modelId"] == licensedItem["name"])); data.push({ [purchasesCols.PURCHASE_ID.id]: purchasesItem["licensedItemPurchaseId"], [purchasesCols.ITEM_ID.id]: licensedItemId, From 844196ba15c1fdb8353f138c0d53105bb854f3fd Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 16:06:39 +0100 Subject: [PATCH 26/33] duration --- .../osparc/desktop/credits/CheckoutsTable.js | 22 +++++++------------ .../desktop/credits/CheckoutsTableModel.js | 16 ++++++++++---- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTable.js b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTable.js index 8c590bc779c..82f38559161 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTable.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTable.js @@ -32,7 +32,7 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTable", { }); const columnModel = this.getTableColumnModel(); - columnModel.setColumnVisible(this.self().COLS.PURCHASE_ID.column, false); + columnModel.setColumnVisible(this.self().COLS.CHECKOUT_ID.column, false); columnModel.setColumnVisible(this.self().COLS.ITEM_ID.column, false); columnModel.setDataCellRenderer(this.self().COLS.COST.column, new qx.ui.table.cellrenderer.Number()); @@ -41,10 +41,10 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTable", { statics: { COLS: { - PURCHASE_ID: { - id: "purchaseId", + CHECKOUT_ID: { + id: "checkoutId", column: 0, - label: qx.locale.Manager.tr("PurchaseId"), + label: qx.locale.Manager.tr("CheckoutId"), width: 150 }, ITEM_ID: { @@ -65,10 +65,10 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTable", { label: qx.locale.Manager.tr("Start"), width: 150 }, - END: { - id: "end", + DURATION: { + id: "duration", column: 4, - label: qx.locale.Manager.tr("End"), + label: qx.locale.Manager.tr("Duration"), width: 150 }, SEATS: { @@ -77,15 +77,9 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTable", { label: qx.locale.Manager.tr("Seats"), width: 50 }, - COST: { - id: "cost", - column: 6, - label: qx.locale.Manager.tr("Credits"), - width: 60 - }, USER: { id: "user", - column: 7, + column: 6, label: qx.locale.Manager.tr("User"), width: 100 }, diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js index 8297259c9ec..54785a2f932 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js @@ -33,6 +33,7 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { } this.setSortColumnIndexWithoutSortingData(checkoutsCols.START.column); this.setSortAscendingWithoutSortingData(false); + this.setColumnSortable(checkoutsCols.DURATION.column, false); }, properties: { @@ -135,14 +136,21 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { const licensedItemId = checkoutsItem["licensedItemId"]; const licensedItem = licensedItems.find(licItem => licItem["licensedItemId"] === licensedItemId); const vipModel = vipModels.find(vipMdl => licensedItem && (vipMdl["modelId"] == licensedItem["name"])); + let start = ""; + let duration = ""; + if (checkoutsItem["startedAt"]) { + start = osparc.utils.Utils.formatDateAndTime(new Date(checkoutsItem["startedAt"])); + if (checkoutsItem["stoppedAt"]) { + duration = osparc.utils.Utils.formatMsToHHMMSS(new Date(checkoutsItem["stoppedAt"]) - new Date(checkoutsItem["startedAt"])); + } + } data.push({ - [checkoutsCols.PURCHASE_ID.id]: checkoutsItem["licensedItemPurchaseId"], + [checkoutsCols.CHECKOUT_ID.id]: checkoutsItem["licensed_item_checkout_id"], [checkoutsCols.ITEM_ID.id]: licensedItemId, [checkoutsCols.ITEM_LABEL.id]: vipModel ? vipModel["name"] : "unknown model", - [checkoutsCols.START.id]: osparc.utils.Utils.formatDateAndTime(new Date(checkoutsItem["startAt"])), - [checkoutsCols.END.id]: osparc.utils.Utils.formatDateAndTime(new Date(checkoutsItem["expireAt"])), + [checkoutsCols.START.id]: start, + [checkoutsCols.DURATION.id]: duration, [checkoutsCols.SEATS.id]: checkoutsItem["numOfSeats"], - [checkoutsCols.COST.id]: checkoutsItem["pricingUnitCost"] ? ("-" + parseFloat(checkoutsItem["pricingUnitCost"]).toFixed(2)) : "", // show it negative [checkoutsCols.USER.id]: checkoutsItem["purchasedByUser"], }); }); From 4ee83b86197868910c27772cc7591f676feeed77 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 16:51:58 +0100 Subject: [PATCH 27/33] minor fixes --- .../source/class/osparc/desktop/credits/CheckoutsTable.js | 1 - .../class/osparc/desktop/credits/CheckoutsTableModel.js | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTable.js b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTable.js index 82f38559161..a757d6e3f2f 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTable.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTable.js @@ -34,7 +34,6 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTable", { const columnModel = this.getTableColumnModel(); columnModel.setColumnVisible(this.self().COLS.CHECKOUT_ID.column, false); columnModel.setColumnVisible(this.self().COLS.ITEM_ID.column, false); - columnModel.setDataCellRenderer(this.self().COLS.COST.column, new qx.ui.table.cellrenderer.Number()); Object.values(this.self().COLS).forEach(col => columnModel.setColumnWidth(col.column, col.width)); }, diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js index 54785a2f932..085fe6be788 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js @@ -121,8 +121,8 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { }; const licensedItemsStore = osparc.store.LicensedItems.getInstance(); return Promise.all([ - licensedItemsStore.getCheckedOutLicensedItems(), - licensedItemsStore.getPurchasedLicensedItems(walletId, urlParams), + licensedItemsStore.getLicensedItems(), + licensedItemsStore.getCheckedOutLicensedItems(walletId, urlParams), licensedItemsStore.getVipModels(), ]) .then(values => { @@ -151,7 +151,7 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { [checkoutsCols.START.id]: start, [checkoutsCols.DURATION.id]: duration, [checkoutsCols.SEATS.id]: checkoutsItem["numOfSeats"], - [checkoutsCols.USER.id]: checkoutsItem["purchasedByUser"], + [checkoutsCols.USER.id]: checkoutsItem["userId"], }); }); return data; From 6584eaa0883031004b851834d26af0c81c1d6fd4 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 16:56:20 +0100 Subject: [PATCH 28/33] undo --- .../src/simcore_service_webserver/licenses/_models.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_models.py b/services/web/server/src/simcore_service_webserver/licenses/_models.py index d9ded98b02f..d5c2ac0947e 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_models.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_models.py @@ -68,9 +68,12 @@ class LicensedItemsPurchasesPathParams(StrictRequestParameters): RequestParameters ] = create_ordering_query_model_class( ordering_fields={ - "started_at", + "purchased_at", + "modified_at", + "name", }, - default=OrderBy(field=IDStr("started_at"), direction=OrderDirection.DESC), + default=OrderBy(field=IDStr("purchased_at"), direction=OrderDirection.DESC), + ordering_fields_api_to_column_map={"modified_at": "modified"}, ) From 17054d4f4f2b8be22c1f94d11fc493e983cb52d7 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 17:28:14 +0100 Subject: [PATCH 29/33] fix backend --- .../licenses/_licensed_items_checkouts_rest.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_checkouts_rest.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_checkouts_rest.py index 1a9c7285d0a..0bcdbe9636c 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_checkouts_rest.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_checkouts_rest.py @@ -26,8 +26,9 @@ LicensedItemCheckoutGet, LicensedItemCheckoutGetPage, LicensedItemCheckoutPathParams, + LicensedItemsCheckoutsListQueryParams, ) -from ._models import LicensedItemsPurchasesListQueryParams, LicensedItemsRequestContext +from ._models import LicensedItemsRequestContext _logger = logging.getLogger(__name__) @@ -81,9 +82,9 @@ async def get_licensed_item_checkout(request: web.Request): async def list_licensed_item_checkouts_for_wallet(request: web.Request): req_ctx = LicensedItemsRequestContext.model_validate(request) path_params = parse_request_path_parameters_as(WalletsPathParams, request) - query_params: LicensedItemsPurchasesListQueryParams = ( + query_params: LicensedItemsCheckoutsListQueryParams = ( parse_request_query_parameters_as( - LicensedItemsPurchasesListQueryParams, request + LicensedItemsCheckoutsListQueryParams, request ) ) From 19f6a678b8d757271c266a9ca5416db870eecce4 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 18:00:41 +0100 Subject: [PATCH 30/33] minor --- .../client/source/class/osparc/desktop/credits/Checkouts.js | 3 +++ .../client/source/class/osparc/desktop/credits/Purchases.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Checkouts.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Checkouts.js index bf7834c0707..ca420207e5e 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/Checkouts.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Checkouts.js @@ -55,6 +55,9 @@ qx.Class.define("osparc.desktop.credits.Checkouts", { this.__table.getTableModel().reloadData() }); filterContainer.add(this.__dateFilters); + filterContainer.add(new qx.ui.core.Spacer(), { + flex: 1 + }); const refreshButton = new qx.ui.form.Button(this.tr("Reload"), "@FontAwesome5Solid/sync-alt/14").set({ allowStretchY: false, alignY: "bottom" diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Purchases.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Purchases.js index 944be0c51fb..ea9e2a37fa4 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/Purchases.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Purchases.js @@ -55,6 +55,9 @@ qx.Class.define("osparc.desktop.credits.Purchases", { this.__table.getTableModel().reloadData() }); filterContainer.add(this.__dateFilters); + filterContainer.add(new qx.ui.core.Spacer(), { + flex: 1 + }); const refreshButton = new qx.ui.form.Button(this.tr("Reload"), "@FontAwesome5Solid/sync-alt/14").set({ allowStretchY: false, alignY: "bottom" From bdfb4c8205f6bed298ec733f6f8272f0f9ce6a0b Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 18:43:26 +0100 Subject: [PATCH 31/33] refactoring --- .../class/osparc/desktop/credits/Checkouts.js | 103 +++--------- .../desktop/credits/CheckoutsTableModel.js | 2 +- .../class/osparc/desktop/credits/Purchases.js | 103 +++--------- .../desktop/credits/ResourceInTableViewer.js | 154 ++++++++++++++++++ 4 files changed, 199 insertions(+), 163 deletions(-) create mode 100644 services/static-webserver/client/source/class/osparc/desktop/credits/ResourceInTableViewer.js diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Checkouts.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Checkouts.js index ca420207e5e..e2b7c891913 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/Checkouts.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Checkouts.js @@ -17,91 +17,32 @@ qx.Class.define("osparc.desktop.credits.Checkouts", { - extend: qx.ui.core.Widget, - - construct: function() { - this.base(arguments); - - this._setLayout(new qx.ui.layout.VBox(5)); - - this.__buildLayout() - }, + extend: osparc.desktop.credits.ResourceInTableViewer, members: { - __buildLayout: function() { - const lbl = new qx.ui.basic.Label(this.tr("Select a Credit Account:")); - this._add(lbl); - - const selectBoxContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); - const walletSelectBox = new qx.ui.form.SelectBox().set({ - allowStretchX: false, - width: 200 - }); - selectBoxContainer.add(walletSelectBox); - this.__fetchingImg = new qx.ui.basic.Image().set({ - source: "@FontAwesome5Solid/circle-notch/12", - alignX: "center", - alignY: "middle", - visibility: "excluded" - }); - this.__fetchingImg.getContentElement().addClass("rotate"); - selectBoxContainer.add(this.__fetchingImg); - this._add(selectBoxContainer); - - const filterContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)) - this.__dateFilters = new osparc.desktop.credits.DateFilters(); - this.__dateFilters.addListener("change", e => { - this.__table.getTableModel().setFilters(e.getData()) - this.__table.getTableModel().reloadData() - }); - filterContainer.add(this.__dateFilters); - filterContainer.add(new qx.ui.core.Spacer(), { - flex: 1 - }); - const refreshButton = new qx.ui.form.Button(this.tr("Reload"), "@FontAwesome5Solid/sync-alt/14").set({ - allowStretchY: false, - alignY: "bottom" - }); - refreshButton.addListener("execute", () => this.__table && this.__table.getTableModel().reloadData()); - filterContainer.add(refreshButton) - this._add(filterContainer); - - walletSelectBox.addListener("changeSelection", e => { - const selection = e.getData(); - if (selection.length) { - this.__selectedWallet = selection[0].getModel() - if (this.__table) { - this.__table.getTableModel().setWalletId(this.__selectedWallet.getWalletId()) - this.__table.getTableModel().reloadData() - } else { - // qx: changeSelection is triggered after the first item is added to SelectBox - this.__table = new osparc.desktop.credits.CheckoutsTable(this.__selectedWallet.getWalletId(), this.__dateFilters.getValue()).set({ - marginTop: 10 - }) - this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", { - converter: isFetching => isFetching ? "visible" : "excluded" - }) - this._add(this.__table, { flex: 1 }) - } + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "table": { + const dateFilters = this.getChildControl("date-filters"); + control = new osparc.desktop.credits.CheckoutsTable(this._getSelectWalletId(), dateFilters.getValue()).set({ + marginTop: 10 + }); + const fetchingImage = this.getChildControl("fetching-image"); + control.getTableModel().bind("isFetching", fetchingImage, "visibility", { + converter: isFetching => isFetching ? "visible" : "excluded" + }) + this._add(control, { flex: 1 }) + break; } - }); - - if (osparc.desktop.credits.Utils.areWalletsEnabled()) { - const store = osparc.store.Store.getInstance(); - store.getWallets().forEach(wallet => { - walletSelectBox.add(new qx.ui.form.ListItem(wallet.getName(), null, wallet)); - }); - } else { - lbl.setVisibility("excluded") - walletSelectBox.setVisibility("excluded") - this.__table = new osparc.desktop.credits.CheckoutsTable(null, this.__dateFilters.getValue()).set({ - marginTop: 10 - }) - this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", { - converter: isFetching => isFetching ? "visible" : "excluded" - }) - this._add(this.__table, { flex: 1 }) } + return control || this.base(arguments, id); + }, + + _buildLayout: function() { + this.base(arguments); + + this.getChildControl("export-button").exclude(); }, } }); diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js index 085fe6be788..24c165fbad8 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js @@ -145,7 +145,7 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { } } data.push({ - [checkoutsCols.CHECKOUT_ID.id]: checkoutsItem["licensed_item_checkout_id"], + [checkoutsCols.CHECKOUT_ID.id]: checkoutsItem["licensedItemCheckoutId"], [checkoutsCols.ITEM_ID.id]: licensedItemId, [checkoutsCols.ITEM_LABEL.id]: vipModel ? vipModel["name"] : "unknown model", [checkoutsCols.START.id]: start, diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Purchases.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Purchases.js index ea9e2a37fa4..fdc2640621b 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/Purchases.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Purchases.js @@ -17,91 +17,32 @@ qx.Class.define("osparc.desktop.credits.Purchases", { - extend: qx.ui.core.Widget, - - construct: function() { - this.base(arguments); - - this._setLayout(new qx.ui.layout.VBox(5)); - - this.__buildLayout() - }, + extend: osparc.desktop.credits.ResourceInTableViewer, members: { - __buildLayout: function() { - const lbl = new qx.ui.basic.Label(this.tr("Select a Credit Account:")); - this._add(lbl); - - const selectBoxContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); - const walletSelectBox = new qx.ui.form.SelectBox().set({ - allowStretchX: false, - width: 200 - }); - selectBoxContainer.add(walletSelectBox); - this.__fetchingImg = new qx.ui.basic.Image().set({ - source: "@FontAwesome5Solid/circle-notch/12", - alignX: "center", - alignY: "middle", - visibility: "excluded" - }); - this.__fetchingImg.getContentElement().addClass("rotate"); - selectBoxContainer.add(this.__fetchingImg); - this._add(selectBoxContainer); - - const filterContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)) - this.__dateFilters = new osparc.desktop.credits.DateFilters(); - this.__dateFilters.addListener("change", e => { - this.__table.getTableModel().setFilters(e.getData()) - this.__table.getTableModel().reloadData() - }); - filterContainer.add(this.__dateFilters); - filterContainer.add(new qx.ui.core.Spacer(), { - flex: 1 - }); - const refreshButton = new qx.ui.form.Button(this.tr("Reload"), "@FontAwesome5Solid/sync-alt/14").set({ - allowStretchY: false, - alignY: "bottom" - }); - refreshButton.addListener("execute", () => this.__table && this.__table.getTableModel().reloadData()); - filterContainer.add(refreshButton) - this._add(filterContainer); - - walletSelectBox.addListener("changeSelection", e => { - const selection = e.getData(); - if (selection.length) { - this.__selectedWallet = selection[0].getModel() - if (this.__table) { - this.__table.getTableModel().setWalletId(this.__selectedWallet.getWalletId()) - this.__table.getTableModel().reloadData() - } else { - // qx: changeSelection is triggered after the first item is added to SelectBox - this.__table = new osparc.desktop.credits.PurchasesTable(this.__selectedWallet.getWalletId(), this.__dateFilters.getValue()).set({ - marginTop: 10 - }) - this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", { - converter: isFetching => isFetching ? "visible" : "excluded" - }) - this._add(this.__table, { flex: 1 }) - } + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "table": { + const dateFilters = this.getChildControl("date-filters"); + control = new osparc.desktop.credits.PurchasesTable(this._getSelectWalletId(), dateFilters.getValue()).set({ + marginTop: 10 + }); + const fetchingImage = this.getChildControl("fetching-image"); + control.getTableModel().bind("isFetching", fetchingImage, "visibility", { + converter: isFetching => isFetching ? "visible" : "excluded" + }) + this._add(control, { flex: 1 }) + break; } - }); - - if (osparc.desktop.credits.Utils.areWalletsEnabled()) { - const store = osparc.store.Store.getInstance(); - store.getWallets().forEach(wallet => { - walletSelectBox.add(new qx.ui.form.ListItem(wallet.getName(), null, wallet)); - }); - } else { - lbl.setVisibility("excluded") - walletSelectBox.setVisibility("excluded") - this.__table = new osparc.desktop.credits.PurchasesTable(null, this.__dateFilters.getValue()).set({ - marginTop: 10 - }) - this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", { - converter: isFetching => isFetching ? "visible" : "excluded" - }) - this._add(this.__table, { flex: 1 }) } + return control || this.base(arguments, id); + }, + + _buildLayout: function() { + this.base(arguments); + + this.getChildControl("export-button").exclude(); }, } }); diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/ResourceInTableViewer.js b/services/static-webserver/client/source/class/osparc/desktop/credits/ResourceInTableViewer.js new file mode 100644 index 00000000000..5fdd957f6db --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/ResourceInTableViewer.js @@ -0,0 +1,154 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2025 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + * Ignacio Pascual (ignapas) + +************************************************************************ */ + +qx.Class.define("osparc.desktop.credits.ResourceInTableViewer", { + extend: qx.ui.core.Widget, + type: "abstract", + + construct: function() { + this.base(arguments); + + this._setLayout(new qx.ui.layout.VBox(5)); + + this._buildLayout(); + }, + + members: { + _createChildControlImpl: function(id) { + let layout; + let control; + switch (id) { + case "intro-text": + control = new qx.ui.basic.Label("Select a Credit Account:"); + this._add(control); + break; + case "wallet-selector-layout": + control = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); + this._add(control); + break; + case "wallet-selector": + control = new qx.ui.form.SelectBox().set({ + allowStretchX: false, + width: 200 + }); + layout = this.getChildControl("wallet-selector-layout"); + layout.add(control); + break; + case "fetching-image": + control = new qx.ui.basic.Image().set({ + source: "@FontAwesome5Solid/circle-notch/12", + alignX: "center", + alignY: "middle", + visibility: "excluded" + }); + control.getContentElement().addClass("rotate"); + layout = this.getChildControl("wallet-selector-layout"); + layout.add(control); + break; + case "filter-layout": + control = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); + this._add(control); + break; + case "date-filters": + control = new osparc.desktop.credits.DateFilters(); + control.addListener("change", e => { + const table = this.getChildControl("table"); + table.getTableModel().setFilters(e.getData()); + table.getTableModel().reloadData(); + }); + layout = this.getChildControl("filter-layout"); + layout.add(control); + break; + case "export-button": + control = new qx.ui.form.Button(this.tr("Export")).set({ + allowStretchY: false, + alignY: "bottom", + visibility: "excluded", + }); + control.addListener("execute", () => this._handleExport()); + layout = this.getChildControl("filter-layout"); + layout.add(control); + break; + case "reload-button": + control = new qx.ui.form.Button(this.tr("Reload"), "@FontAwesome5Solid/sync-alt/14").set({ + allowStretchY: false, + alignY: "bottom" + }); + control.addListener("execute", () => { + const table = this.getChildControl("table"); + table.getTableModel().reloadData(); + }); + layout = this.getChildControl("filter-layout"); + layout.add(control); + break; + } + return control || this.base(arguments, id); + }, + + _buildLayout: function() { + const introText = this.getChildControl("intro-text"); + const walletSelectBox = this.getChildControl("wallet-selector"); + this.getChildControl("fetching-image"); + + const filterLayout = this.getChildControl("filter-layout"); + this.getChildControl("date-filters"); + filterLayout.add(new qx.ui.core.Spacer(), { + flex: 1 + }); + const exportButton = this.getChildControl("export-button"); + this.getChildControl("reload-button"); + + walletSelectBox.addListener("changeSelection", e => { + const selection = e.getData(); + if (selection.length) { + const table = this.getChildControl("table"); + table.getTableModel().setWalletId(this._getSelectWalletId()); + table.getTableModel().reloadData(); + } + }); + + if (osparc.desktop.credits.Utils.areWalletsEnabled()) { + const store = osparc.store.Store.getInstance(); + store.getWallets().forEach(wallet => { + walletSelectBox.add(new qx.ui.form.ListItem(wallet.getName(), null, wallet)); + }); + } else { + introText.setVisibility("excluded"); + walletSelectBox.setVisibility("excluded"); + exportButton.setVisibility("excluded"); + this.getChildControl("table"); + } + }, + + _getSelectWalletId: function() { + if (osparc.desktop.credits.Utils.areWalletsEnabled()) { + const walletSelectBox = this.getChildControl("wallet-selector"); + const selectedWallet = walletSelectBox.getSelection()[0].getModel(); + return selectedWallet.getWalletId(); + } + return null; + }, + + _handleExport() { + const reportUrl = new URL("/v0/services/-/usage-report", window.location.origin) + reportUrl.searchParams.append("wallet_id", this._getSelectWalletId()) + reportUrl.searchParams.append("filters", JSON.stringify({ "started_at": this.__dateFilters.getValue() })) + window.open(reportUrl, "_blank") + } + } +}); From d8616dd2f5a61f36b31bd830a150855ba1c5e1d7 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 18:51:02 +0100 Subject: [PATCH 32/33] minors --- .../desktop/credits/CheckoutsTableModel.js | 27 ++++++++----------- .../desktop/credits/PurchasesTableModel.js | 27 ++++++++----------- 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js index 24c165fbad8..fe59f001d04 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTableModel.js @@ -27,9 +27,9 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { const colIDs = Object.values(checkoutsCols).map(col => col.id); this.setColumns(colLabels, colIDs); - this.setWalletId(walletId) + this.setWalletId(walletId); if (filters) { - this.setFilters(filters) + this.setFilters(filters); } this.setSortColumnIndexWithoutSortingData(checkoutsCols.START.column); this.setSortAscendingWithoutSortingData(false); @@ -41,22 +41,25 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { check: "Number", nullable: true }, + filters: { check: "Object", init: null }, + isFetching: { check: "Boolean", init: false, event: "changeFetching" }, + orderBy: { check: "Object", init: { field: "startAt", direction: "desc" } - } + }, }, statics: { @@ -73,7 +76,7 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { field: this.self().COLUMN_ID_TO_DB_COLUMN_MAP[columnIndex], direction: ascending ? "asc" : "desc" }) - this.base(arguments, columnIndex, ascending) + this.base(arguments, columnIndex, ascending); }, // overridden @@ -93,12 +96,8 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { resolveWResponse: true }; osparc.store.LicensedItems.getInstance().getCheckedOutLicensedItems(walletId, urlParams, options) - .then(resp => { - this._onRowCountLoaded(resp["_meta"].total) - }) - .catch(() => { - this._onRowCountLoaded(null) - }); + .then(resp => this._onRowCountLoaded(resp["_meta"].total)) + .catch(() => this._onRowCountLoaded(null)); }, // overridden @@ -167,9 +166,7 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { requests.push(getFetchPromise(i, i > lastRow - this.self().SERVER_MAX_LIMIT + 1 ? reqLimit % this.self().SERVER_MAX_LIMIT : this.self().SERVER_MAX_LIMIT)) } Promise.all(requests) - .then(responses => { - this._onRowDataLoaded(responses.flat()); - }) + .then(responses => this._onRowDataLoaded(responses.flat())) .catch(err => { console.error(err); this._onRowDataLoaded(null); @@ -177,9 +174,7 @@ qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", { .finally(() => this.setIsFetching(false)); } else { getFetchPromise(firstRow, reqLimit) - .then(data => { - this._onRowDataLoaded(data); - }) + .then(data => this._onRowDataLoaded(data)) .catch(err => { console.error(err) this._onRowDataLoaded(null); diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTableModel.js index e6a168083ac..b1b054071ce 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTableModel.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/PurchasesTableModel.js @@ -27,9 +27,9 @@ qx.Class.define("osparc.desktop.credits.PurchasesTableModel", { const colIDs = Object.values(purchasesCols).map(col => col.id); this.setColumns(colLabels, colIDs); - this.setWalletId(walletId) + this.setWalletId(walletId); if (filters) { - this.setFilters(filters) + this.setFilters(filters); } this.setSortColumnIndexWithoutSortingData(purchasesCols.START.column); this.setSortAscendingWithoutSortingData(false); @@ -40,22 +40,25 @@ qx.Class.define("osparc.desktop.credits.PurchasesTableModel", { check: "Number", nullable: true }, + filters: { check: "Object", init: null }, + isFetching: { check: "Boolean", init: false, event: "changeFetching" }, + orderBy: { check: "Object", init: { field: "startAt", direction: "desc" } - } + }, }, statics: { @@ -72,7 +75,7 @@ qx.Class.define("osparc.desktop.credits.PurchasesTableModel", { field: this.self().COLUMN_ID_TO_DB_COLUMN_MAP[columnIndex], direction: ascending ? "asc" : "desc" }) - this.base(arguments, columnIndex, ascending) + this.base(arguments, columnIndex, ascending); }, // overridden @@ -92,12 +95,8 @@ qx.Class.define("osparc.desktop.credits.PurchasesTableModel", { resolveWResponse: true }; osparc.store.LicensedItems.getInstance().getPurchasedLicensedItems(walletId, urlParams, options) - .then(resp => { - this._onRowCountLoaded(resp["_meta"].total) - }) - .catch(() => { - this._onRowCountLoaded(null) - }); + .then(resp => this._onRowCountLoaded(resp["_meta"].total)) + .catch(() => this._onRowCountLoaded(null)); }, // overridden @@ -159,9 +158,7 @@ qx.Class.define("osparc.desktop.credits.PurchasesTableModel", { requests.push(getFetchPromise(i, i > lastRow - this.self().SERVER_MAX_LIMIT + 1 ? reqLimit % this.self().SERVER_MAX_LIMIT : this.self().SERVER_MAX_LIMIT)) } Promise.all(requests) - .then(responses => { - this._onRowDataLoaded(responses.flat()); - }) + .then(responses => this._onRowDataLoaded(responses.flat())) .catch(err => { console.error(err); this._onRowDataLoaded(null); @@ -169,9 +166,7 @@ qx.Class.define("osparc.desktop.credits.PurchasesTableModel", { .finally(() => this.setIsFetching(false)); } else { getFetchPromise(firstRow, reqLimit) - .then(data => { - this._onRowDataLoaded(data); - }) + .then(data => this._onRowDataLoaded(data)) .catch(err => { console.error(err) this._onRowDataLoaded(null); From c097033e9f698a270b19a25cc7ff8b7099fca4da Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 15 Jan 2025 19:02:28 +0100 Subject: [PATCH 33/33] refactor --- .../desktop/credits/ResourceInTableViewer.js | 10 +- .../class/osparc/desktop/credits/Usage.js | 128 ++++-------------- .../osparc/desktop/credits/UsageTable.js | 4 +- 3 files changed, 29 insertions(+), 113 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/ResourceInTableViewer.js b/services/static-webserver/client/source/class/osparc/desktop/credits/ResourceInTableViewer.js index 5fdd957f6db..baf94205c9b 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/ResourceInTableViewer.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/ResourceInTableViewer.js @@ -78,7 +78,6 @@ qx.Class.define("osparc.desktop.credits.ResourceInTableViewer", { control = new qx.ui.form.Button(this.tr("Export")).set({ allowStretchY: false, alignY: "bottom", - visibility: "excluded", }); control.addListener("execute", () => this._handleExport()); layout = this.getChildControl("filter-layout"); @@ -144,11 +143,8 @@ qx.Class.define("osparc.desktop.credits.ResourceInTableViewer", { return null; }, - _handleExport() { - const reportUrl = new URL("/v0/services/-/usage-report", window.location.origin) - reportUrl.searchParams.append("wallet_id", this._getSelectWalletId()) - reportUrl.searchParams.append("filters", JSON.stringify({ "started_at": this.__dateFilters.getValue() })) - window.open(reportUrl, "_blank") - } + _handleExport: function() { + throw new Error("Abstract method called!"); + }, } }); diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Usage.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Usage.js index 8f4eaf1075c..99af443b8e3 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/Usage.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Usage.js @@ -17,114 +17,34 @@ ************************************************************************ */ qx.Class.define("osparc.desktop.credits.Usage", { - extend: qx.ui.core.Widget, - - construct: function() { - this.base(arguments); - - this._setLayout(new qx.ui.layout.VBox(15)); - - const store = osparc.store.Store.getInstance(); - this.__userWallets = store.getWallets(); - - this.__buildLayout() - }, + extend: osparc.desktop.credits.ResourceInTableViewer, members: { - __buildLayout: function() { - this._removeAll(); - - const container = new qx.ui.container.Composite(new qx.ui.layout.VBox(5)); - - const lbl = new qx.ui.basic.Label("Select a Credit Account:"); - container.add(lbl); - - const selectBoxContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); - const walletSelectBox = new qx.ui.form.SelectBox().set({ - allowStretchX: false, - width: 200 - }); - selectBoxContainer.add(walletSelectBox); - this.__fetchingImg = new qx.ui.basic.Image().set({ - source: "@FontAwesome5Solid/circle-notch/12", - alignX: "center", - alignY: "middle", - visibility: "excluded" - }); - this.__fetchingImg.getContentElement().addClass("rotate"); - selectBoxContainer.add(this.__fetchingImg); - container.add(selectBoxContainer); - - const filterContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)) - this.__dateFilters = new osparc.desktop.credits.DateFilters(); - this.__dateFilters.addListener("change", e => { - this.__table.getTableModel().setFilters(e.getData()) - this.__table.getTableModel().reloadData() - }); - filterContainer.add(this.__dateFilters); - filterContainer.add(new qx.ui.core.Spacer(), { - flex: 1 - }); - this.__exportButton = new qx.ui.form.Button(this.tr("Export")).set({ - allowStretchY: false, - alignY: "bottom" - }); - this.__exportButton.addListener("execute", () => { - this.__handleExport() - }); - filterContainer.add(this.__exportButton); - const refreshButton = new qx.ui.form.Button(this.tr("Reload"), "@FontAwesome5Solid/sync-alt/14").set({ - allowStretchY: false, - alignY: "bottom" - }); - refreshButton.addListener("execute", () => this.__table && this.__table.getTableModel().reloadData()); - filterContainer.add(refreshButton) - container.add(filterContainer); - - this._add(container); - - walletSelectBox.addListener("changeSelection", e => { - const selection = e.getData(); - if (selection.length) { - this.__selectedWallet = selection[0].getModel() - if (this.__table) { - this.__table.getTableModel().setWalletId(this.__selectedWallet.getWalletId()) - this.__table.getTableModel().reloadData() - } else { - // qx: changeSelection is triggered after the first item is added to SelectBox - this.__table = new osparc.desktop.credits.UsageTable(this.__selectedWallet.getWalletId(), this.__dateFilters.getValue()).set({ - marginTop: 10 - }) - this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", { - converter: isFetching => isFetching ? "visible" : "excluded" - }) - container.add(this.__table, { flex: 1 }) - } + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "table": { + const dateFilters = this.getChildControl("date-filters"); + control = new osparc.desktop.credits.UsageTable(this._getSelectWalletId(), dateFilters.getValue()).set({ + marginTop: 10 + }); + const fetchingImage = this.getChildControl("fetching-image"); + control.getTableModel().bind("isFetching", fetchingImage, "visibility", { + converter: isFetching => isFetching ? "visible" : "excluded" + }) + this._add(control, { flex: 1 }) + break; } - }); - - if (osparc.desktop.credits.Utils.areWalletsEnabled()) { - this.__userWallets.forEach(wallet => { - walletSelectBox.add(new qx.ui.form.ListItem(wallet.getName(), null, wallet)); - }); - } else { - lbl.setVisibility("excluded") - walletSelectBox.setVisibility("excluded") - this.__exportButton.setVisibility("excluded") - this.__table = new osparc.desktop.credits.UsageTable(null, this.__dateFilters.getValue()).set({ - marginTop: 10 - }) - this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", { - converter: isFetching => isFetching ? "visible" : "excluded" - }) - container.add(this.__table, { flex: 1 }) } + return control || this.base(arguments, id); + }, + + _handleExport: function() { + const reportUrl = new URL("/v0/services/-/usage-report", window.location.origin); + reportUrl.searchParams.append("wallet_id", this._getSelectWalletId()); + const dateFilters = this.getChildControl("date-filters"); + reportUrl.searchParams.append("filters", JSON.stringify({ "started_at": dateFilters.getValue() })); + window.open(reportUrl, "_blank"); }, - __handleExport() { - const reportUrl = new URL("/v0/services/-/usage-report", window.location.origin) - reportUrl.searchParams.append("wallet_id", this.__selectedWallet.getWalletId()) - reportUrl.searchParams.append("filters", JSON.stringify({ "started_at": this.__dateFilters.getValue() })) - window.open(reportUrl, "_blank") - } } }); diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/UsageTable.js b/services/static-webserver/client/source/class/osparc/desktop/credits/UsageTable.js index 324f4dcb419..d08615d1a04 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/UsageTable.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/UsageTable.js @@ -77,7 +77,7 @@ qx.Class.define("osparc.desktop.credits.UsageTable", { id: "node", column: 1, label: qx.locale.Manager.tr("Node"), - width: 140 + width: 100 }, SERVICE: { id: "service", @@ -119,7 +119,7 @@ qx.Class.define("osparc.desktop.credits.UsageTable", { id: "tags", column: 7, label: qx.locale.Manager.tr("Tags"), - width: 140 + width: 80 }, } }