From 80243c8c813bdafd7d381f0af8eea3324ba351ea Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 5 Dec 2024 16:46:35 +0100 Subject: [PATCH 001/102] linting --- .../client/source/class/osparc/vipMarket/VipMarket.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 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 ff0af06af15..89f0d190892 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -85,7 +85,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { this._add(modelsLayout, { flex: 1 }); - + const modelsUIList = new qx.ui.form.List().set({ decorator: "no-border", spacing: 5, @@ -152,7 +152,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { found["leased"] = true; this.__populateModels(); anatomicModelDetails.setAnatomicalModelsData(found); - }; + } }, this); }) .catch(err => console.error(err)); @@ -184,16 +184,14 @@ qx.Class.define("osparc.vipMarket.VipMarket", { if (sortBy["order"] === "down") { // A -> Z return a["name"].localeCompare(b["name"]); - } else { - return b["name"].localeCompare(a["name"]); } + return b["name"].localeCompare(a["name"]); } else if (sortBy["sort"] === "date") { if (sortBy["order"] === "down") { // Now -> Yesterday return b["date"] - a["date"]; - } else { - return a["date"] - b["date"]; } + return a["date"] - b["date"]; } } // default criteria From 52209d7913223632b281f6841a49b7abb0218ebb Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 5 Dec 2024 16:58:47 +0100 Subject: [PATCH 002/102] add listing resource --- .../client/source/class/osparc/data/Resources.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 1332df084a9..86622354105 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -1239,6 +1239,19 @@ qx.Class.define("osparc.data.Resources", { url: statics.API + "/tags/{tagId}" } } + }, + + /* + * MARKET + */ + "market": { + useCache: false, + endpoints: { + get: { + method: "GET", + url: statics.API + "/catalog/licensed-items" + } + } } }; }, From 806f6535cc5ccddf9e7c648af8269d6d8659b165 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 5 Dec 2024 17:03:12 +0100 Subject: [PATCH 003/102] more props --- .../vipMarket/AnatomicalModelListItem.js | 16 +++++- .../class/osparc/vipMarket/VipMarket.js | 55 ++++++++++++------- 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js index 30a95396774..6100b33c110 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js @@ -57,7 +57,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelListItem", { check: "Number", init: null, nullable: false, - event: "changemodelId", + event: "changeModelId", }, thumbnail: { @@ -83,6 +83,20 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelListItem", { event: "changeDate", }, + licensedItemId: { + check: "String", + init: null, + nullable: false, + event: "changeLicensedItemId", + }, + + pricingPlanId: { + check: "Number", + init: null, + nullable: false, + event: "changePricingPlanId", + }, + leased: { check: "Boolean", init: false, 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 89f0d190892..525a4b09b27 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -47,7 +47,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { curatedModel[key] = model[key]; } if (key === "ID") { - curatedModel["leased"] = [22].includes(model[key]); + curatedModel["leased"] = model["ID"] < 4; } }); anatomicalModels.push(curatedModel); @@ -57,8 +57,9 @@ qx.Class.define("osparc.vipMarket.VipMarket", { }, members: { - __anatomicalModelsModel: null, __anatomicalModels: null, + __licensedItems: null, + __anatomicalModelsModel: null, __sortByButton: null, __buildLayout: function() { @@ -99,10 +100,12 @@ qx.Class.define("osparc.vipMarket.VipMarket", { membersCtrl.setDelegate({ createItem: () => new osparc.vipMarket.AnatomicalModelListItem(), bindItem: (ctrl, item, id) => { - ctrl.bindProperty("id", "modelId", null, item, id); + ctrl.bindProperty("modelId", "modelId", null, item, id); ctrl.bindProperty("thumbnail", "thumbnail", null, item, id); ctrl.bindProperty("name", "name", null, item, id); ctrl.bindProperty("date", "date", null, item, id); + ctrl.bindProperty("licensedItemId", "licensedItemId", null, item, id); + ctrl.bindProperty("pricingPlanId", "pricingPlanId", null, item, id); ctrl.bindProperty("leased", "leased", null, item, id); }, configureItem: item => { @@ -143,17 +146,22 @@ qx.Class.define("osparc.vipMarket.VipMarket", { .then(resp => resp.json()) .then(anatomicalModelsRaw => { this.__anatomicalModels = this.self().curateAnatomicalModels(anatomicalModelsRaw); - this.__populateModels(); - anatomicModelDetails.addListener("modelLeased", e => { - const modelId = e.getData(); - const found = this.__anatomicalModels.find(model => model["ID"] === modelId); - if (found) { - found["leased"] = true; + osparc.data.Resources.get("market") + .then(licensedItems => { + this.__licensedItems = licensedItems; this.__populateModels(); - anatomicModelDetails.setAnatomicalModelsData(found); - } - }, this); + + anatomicModelDetails.addListener("modelLeased", e => { + const modelId = e.getData(); + const found = this.__anatomicalModels.find(model => model["ID"] === modelId); + if (found) { + found["leased"] = true; + this.__populateModels(); + anatomicModelDetails.setAnatomicalModelsData(found); + } + }, this); + }); }) .catch(err => console.error(err)); }, @@ -161,13 +169,22 @@ qx.Class.define("osparc.vipMarket.VipMarket", { __populateModels: function() { const models = []; this.__anatomicalModels.forEach(model => { - const anatomicalModel = {}; - anatomicalModel["id"] = model["ID"]; - anatomicalModel["thumbnail"] = model["Thumbnail"]; - anatomicalModel["name"] = model["Features"]["name"] + " " + model["Features"]["version"]; - anatomicalModel["date"] = new Date(model["Features"]["date"]); - anatomicalModel["leased"] = model["leased"]; - models.push(anatomicalModel); + const modelId = model["ID"]; + const licensedItem = this.__licensedItems.find(licItem => licItem["name"] == modelId); + if (licensedItem) { + console.log(licensedItem); + const anatomicalModel = {}; + anatomicalModel["modelId"] = modelId; + anatomicalModel["thumbnail"] = model["Thumbnail"]; + anatomicalModel["name"] = model["Features"]["name"] + " " + model["Features"]["version"]; + anatomicalModel["date"] = new Date(model["Features"]["date"]); + // attach license data + anatomicalModel["licensedItemId"] = licensedItem["licensedItemId"]; + anatomicalModel["pricingPlanId"] = licensedItem["pricingPlanId"]; + // attach leased data + anatomicalModel["leased"] = model["leased"]; + models.push(anatomicalModel); + } }); this.__anatomicalModelsModel.removeAll(); From c20f48894bff393894ed1b12b6b6740d0c33237c Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 5 Dec 2024 17:19:02 +0100 Subject: [PATCH 004/102] more curated data --- .../vipMarket/AnatomicalModelDetails.js | 18 ++++--- .../class/osparc/vipMarket/VipMarket.js | 47 ++++++++++--------- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 40ffda37b27..56105b8ceb2 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -46,7 +46,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { const anatomicalModelsData = this.getAnatomicalModelsData(); if (anatomicalModelsData) { - const card = this.__createcCard(anatomicalModelsData); + const card = this.__createCard(anatomicalModelsData); this._add(card); } else { const selectModelLabel = new qx.ui.basic.Label().set({ @@ -61,13 +61,11 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { } }, - __createcCard: function(anatomicalModelsData) { - console.log(anatomicalModelsData); - + __createCard: function(anatomicalModelsData) { const cardGrid = new qx.ui.layout.Grid(16, 16); const cardLayout = new qx.ui.container.Composite(cardGrid); - const description = anatomicalModelsData["Description"]; + const description = anatomicalModelsData["description"]; description.split(" - ").forEach((desc, idx) => { const titleLabel = new qx.ui.basic.Label().set({ value: desc, @@ -85,7 +83,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }); const thumbnail = new qx.ui.basic.Image().set({ - source: anatomicalModelsData["Thumbnail"], + source: anatomicalModelsData["thumbnail"], alignY: "middle", scale: true, allowGrowX: true, @@ -100,7 +98,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { row: 2, }); - const features = anatomicalModelsData["Features"]; + const features = anatomicalModelsData["features"]; const featuresGrid = new qx.ui.layout.Grid(8, 8); const featuresLayout = new qx.ui.container.Composite(featuresGrid); let idx = 0; @@ -125,7 +123,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { column: 0, row: idx, }); - + const nameLabel = new qx.ui.basic.Label().set({ value: features[key.toLowerCase()], font: "text-14", @@ -135,7 +133,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { column: 1, row: idx, }); - + idx++; } }); @@ -188,7 +186,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { leaseModelButton.setFetching(true); setTimeout(() => { leaseModelButton.setFetching(false); - this.fireDataEvent("modelLeased", this.getAnatomicalModelsData()["ID"]); + this.fireDataEvent("modelLeased", this.getAnatomicalModelsData()["modelId"]); }, 2000); }); buttonsLayout.add(leaseModelButton, { 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 525a4b09b27..dbd1446577d 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -131,7 +131,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { const selection = e.getData(); if (selection.length) { const modelId = selection[0].getModelId(); - const modelFound = this.__anatomicalModels.find(anatomicalModel => anatomicalModel["ID"] === modelId); + const modelFound = this.__anatomicalModels.find(anatomicalModel => anatomicalModel["modelId"] === modelId); if (modelFound) { anatomicModelDetails.setAnatomicalModelsData(modelFound); return; @@ -145,11 +145,34 @@ qx.Class.define("osparc.vipMarket.VipMarket", { }) .then(resp => resp.json()) .then(anatomicalModelsRaw => { - this.__anatomicalModels = this.self().curateAnatomicalModels(anatomicalModelsRaw); + const allAnatomicalModels = this.self().curateAnatomicalModels(anatomicalModelsRaw); osparc.data.Resources.get("market") .then(licensedItems => { this.__licensedItems = licensedItems; + + this.__anatomicalModels = []; + allAnatomicalModels.forEach(model => { + const modelId = model["ID"]; + const licensedItem = this.__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"]; + // attach license data + anatomicalModel["licensedItemId"] = licensedItem["licensedItemId"]; + anatomicalModel["pricingPlanId"] = licensedItem["pricingPlanId"]; + // attach leased data + anatomicalModel["leased"] = model["leased"]; + this.__anatomicalModels.push(anatomicalModel); + } + }); + this.__populateModels(); anatomicModelDetails.addListener("modelLeased", e => { @@ -167,25 +190,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { }, __populateModels: function() { - const models = []; - this.__anatomicalModels.forEach(model => { - const modelId = model["ID"]; - const licensedItem = this.__licensedItems.find(licItem => licItem["name"] == modelId); - if (licensedItem) { - console.log(licensedItem); - const anatomicalModel = {}; - anatomicalModel["modelId"] = modelId; - anatomicalModel["thumbnail"] = model["Thumbnail"]; - anatomicalModel["name"] = model["Features"]["name"] + " " + model["Features"]["version"]; - anatomicalModel["date"] = new Date(model["Features"]["date"]); - // attach license data - anatomicalModel["licensedItemId"] = licensedItem["licensedItemId"]; - anatomicalModel["pricingPlanId"] = licensedItem["pricingPlanId"]; - // attach leased data - anatomicalModel["leased"] = model["leased"]; - models.push(anatomicalModel); - } - }); + const models = this.__anatomicalModels; this.__anatomicalModelsModel.removeAll(); const sortModel = sortBy => { From 3d4e4462a36db156019ee6d8aec52312c4f4a449 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 5 Dec 2024 17:23:24 +0100 Subject: [PATCH 005/102] change layout --- .../class/osparc/vipMarket/VipMarket.js | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 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 dbd1446577d..94732947079 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -21,7 +21,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { construct: function() { this.base(arguments); - this._setLayout(new qx.ui.layout.VBox(10)); + this._setLayout(new qx.ui.layout.HBox(10)); this.__buildLayout(); }, @@ -63,10 +63,15 @@ qx.Class.define("osparc.vipMarket.VipMarket", { __sortByButton: null, __buildLayout: function() { + const leftSide = new qx.ui.container.Composite(new qx.ui.layout.VBox(10)).set({ + alignY: "middle", + }); + this._add(leftSide); + const toolbarLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(10)).set({ alignY: "middle", }); - this._add(toolbarLayout); + leftSide.add(toolbarLayout) const sortModelsButtons = this.__sortByButton = new osparc.vipMarket.SortModelsButtons().set({ alignY: "bottom", @@ -77,13 +82,10 @@ qx.Class.define("osparc.vipMarket.VipMarket", { const filter = new osparc.filter.TextFilter("text", "vipModels").set({ alignY: "middle", allowGrowY: false, - minWidth: 170, + minWidth: 165, }); this.addListener("appear", () => filter.getChildControl("textfield").focus()); - toolbarLayout.add(filter); - - const modelsLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(10)); - this._add(modelsLayout, { + toolbarLayout.add(filter, { flex: 1 }); @@ -93,8 +95,16 @@ qx.Class.define("osparc.vipMarket.VipMarket", { minWidth: 250, maxWidth: 250 }); - modelsLayout.add(modelsUIList) + leftSide.add(modelsUIList, { + flex: 1 + }); + const rightSide = new qx.ui.container.Composite(new qx.ui.layout.VBox(10)).set({ + alignY: "middle", + }); + this._add(rightSide, { + flex: 1 + }); const anatomicalModelsModel = this.__anatomicalModelsModel = new qx.data.Array(); const membersCtrl = new qx.data.controller.List(anatomicalModelsModel, modelsUIList, "name"); membersCtrl.setDelegate({ @@ -123,7 +133,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { const anatomicModelDetails = new osparc.vipMarket.AnatomicalModelDetails().set({ padding: 20, }); - modelsLayout.add(anatomicModelDetails, { + rightSide.add(anatomicModelDetails, { flex: 1 }); From ceecb69b06a1b6953d544864d1f62fd86d955ad6 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 5 Dec 2024 17:27:45 +0100 Subject: [PATCH 006/102] refactoring --- .../vipMarket/AnatomicalModelDetails.js | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 56105b8ceb2..e2c10e1c8ca 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -21,7 +21,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { construct: function() { this.base(arguments); - const layout = new qx.ui.layout.Grow(); + const layout = new qx.ui.layout.VBox(10); this._setLayout(layout); this.__poplulateLayout(); @@ -46,8 +46,10 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { const anatomicalModelsData = this.getAnatomicalModelsData(); if (anatomicalModelsData) { - const card = this.__createCard(anatomicalModelsData); - this._add(card); + const modelInfo = this.__createModelInfo(anatomicalModelsData); + this._add(modelInfo); + const pricingUnits = this.__createPricingUnits(anatomicalModelsData); + this._add(pricingUnits); } else { const selectModelLabel = new qx.ui.basic.Label().set({ value: this.tr("Select a model for more details"), @@ -61,7 +63,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { } }, - __createCard: function(anatomicalModelsData) { + __createModelInfo: function(anatomicalModelsData) { const cardGrid = new qx.ui.layout.Grid(16, 16); const cardLayout = new qx.ui.container.Composite(cardGrid); @@ -165,6 +167,10 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { row: 2, }); + return cardLayout; + }, + + __createPricingUnits: function(anatomicalModelsData) { const buttonsLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); if (anatomicalModelsData["leased"]) { const leaseModelButton = new qx.ui.form.Button().set({ @@ -192,13 +198,8 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { buttonsLayout.add(leaseModelButton, { flex: 1 }); - cardLayout.add(buttonsLayout, { - column: 0, - row: 3, - colSpan: 2, - }); - return cardLayout; + return buttonsLayout; }, } }); From 9dcd8552b58035a4a32d14c2b02b47b3f27f2e63 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 5 Dec 2024 17:46:18 +0100 Subject: [PATCH 007/102] [skip ci] show pricing units --- .../source/class/osparc/pricing/UnitsList.js | 2 +- .../source/class/osparc/study/PricingUnit.js | 31 ++++++++++++++----- .../vipMarket/AnatomicalModelDetails.js | 29 +++++++++++++++-- 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js index c941aee8323..32be30c2f60 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js +++ b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js @@ -81,7 +81,7 @@ qx.Class.define("osparc.pricing.UnitsList", { pricingUnits.forEach(pricingUnit => { const pUnit = new osparc.study.PricingUnit(pricingUnit).set({ - showSpecificInfo: true, + showAwsSpecificInfo: true, showEditButton: true, allowGrowY: false }); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js index 311df5ea16b..c042b65568f 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js @@ -42,11 +42,18 @@ qx.Class.define("osparc.study.PricingUnit", { apply: "__buildLayout" }, - showSpecificInfo: { + showAwsSpecificInfo: { check: "Boolean", init: null, nullable: true, - event: "changeShowSpecificInfo" + event: "changeShowAwsSpecificInfo" + }, + + showUnitExtraInfo: { + check: "Boolean", + init: true, + nullable: true, + event: "changeShowUnitExtraInfo" }, showEditButton: { @@ -79,6 +86,13 @@ qx.Class.define("osparc.study.PricingUnit", { }); this._add(control); break; + case "unitExtraInfo": + control = new qx.ui.basic.Label().set({ + font: "text-13", + rich: true, + }); + this._add(control); + break; case "edit-button": control = new qx.ui.form.Button(qx.locale.Manager.tr("Edit")); this._add(control); @@ -106,18 +120,21 @@ qx.Class.define("osparc.study.PricingUnit", { pricingUnit.bind("awsSpecificInfo", specificInfo, "value", { converter: v => qx.locale.Manager.tr("EC2") + ": " + v, }); - this.bind("showSpecificInfo", specificInfo, "visibility", { + this.bind("showAwsSpecificInfo", specificInfo, "visibility", { converter: show => show ? "visible" : "excluded" }) } // add pricing unit extra info + const unitExtraInfo = this.getChildControl("unitExtraInfo"); + let text = ""; Object.entries(pricingUnit.getUnitExtraInfo()).forEach(([key, value]) => { - this._add(new qx.ui.basic.Label().set({ - value: key + ": " + value, - font: "text-13" - })); + text += `${key}: ${value}
`; }); + unitExtraInfo.setValue(text); + this.bind("showUnitExtraInfo", unitExtraInfo, "visibility", { + converter: show => show ? "visible" : "excluded" + }) // add edit button const editButton = this.getChildControl("edit-button"); diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index e2c10e1c8ca..94b5ffe379f 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -171,7 +171,30 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }, __createPricingUnits: function(anatomicalModelsData) { - const buttonsLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); + console.log(anatomicalModelsData); + const pricingUnitsLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); + + const params = { + url: { + pricingPlanId: anatomicalModelsData["pricingPlanId"] + } + }; + osparc.data.Resources.fetch("pricingPlans", "getOne", params) + .then(data => { + const pricingUnits = data["pricingUnits"]; + pricingUnits.forEach(pricingUnit => { + const pUnit = new osparc.study.PricingUnit(pricingUnit).set({ + showAwsSpecificInfo: false, + showUnitExtraInfo: false, + showEditButton: false, + allowGrowY: false + }); + pricingUnitsLayout.add(pUnit); + }); + }) + .catch(err => console.error(err)); + + /* if (anatomicalModelsData["leased"]) { const leaseModelButton = new qx.ui.form.Button().set({ label: this.tr("3 seats Leased (27 days left)"), @@ -198,8 +221,8 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { buttonsLayout.add(leaseModelButton, { flex: 1 }); - - return buttonsLayout; + */ + return pricingUnitsLayout; }, } }); From 3a98dc5608fa698a194aa63a149ae0326816a8b2 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 5 Dec 2024 17:57:43 +0100 Subject: [PATCH 008/102] [skip ci] Show rent buttons --- .../source/class/osparc/study/PricingUnit.js | 25 +++++++++++++++++-- .../vipMarket/AnatomicalModelDetails.js | 7 ++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js index c042b65568f..f9268131d32 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js @@ -58,10 +58,17 @@ qx.Class.define("osparc.study.PricingUnit", { showEditButton: { check: "Boolean", - init: null, + init: false, nullable: true, event: "changeShowEditButton" }, + + showRentButton: { + check: "Boolean", + init: false, + nullable: true, + event: "changeShowRentButton" + }, }, members: { @@ -97,6 +104,13 @@ qx.Class.define("osparc.study.PricingUnit", { control = new qx.ui.form.Button(qx.locale.Manager.tr("Edit")); this._add(control); break; + case "rent-button": + control = new qx.ui.form.Button(qx.locale.Manager.tr("Rent")).set({ + appearance: "strong-button", + center: true, + }); + this._add(control); + break; } return control || this.base(arguments, id); }, @@ -134,7 +148,7 @@ qx.Class.define("osparc.study.PricingUnit", { unitExtraInfo.setValue(text); this.bind("showUnitExtraInfo", unitExtraInfo, "visibility", { converter: show => show ? "visible" : "excluded" - }) + }); // add edit button const editButton = this.getChildControl("edit-button"); @@ -142,6 +156,13 @@ qx.Class.define("osparc.study.PricingUnit", { converter: show => show ? "visible" : "excluded" }) editButton.addListener("execute", () => this.fireEvent("editPricingUnit")); + + // add rent button + const rentButton = this.getChildControl("rent-button"); + this.bind("showRentButton", rentButton, "visibility", { + converter: show => show ? "visible" : "excluded" + }) + rentButton.addListener("execute", () => this.fireEvent("rentPricingUnit")); } } }); diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 94b5ffe379f..02f38b35476 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -21,7 +21,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { construct: function() { this.base(arguments); - const layout = new qx.ui.layout.VBox(10); + const layout = new qx.ui.layout.VBox(20); this._setLayout(layout); this.__poplulateLayout(); @@ -172,7 +172,9 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { __createPricingUnits: function(anatomicalModelsData) { console.log(anatomicalModelsData); - const pricingUnitsLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); + const pricingUnitsLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5).set({ + alignX: "center" + })); const params = { url: { @@ -187,6 +189,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { showAwsSpecificInfo: false, showUnitExtraInfo: false, showEditButton: false, + showRentButton: true, allowGrowY: false }); pricingUnitsLayout.add(pUnit); From 7e95307909a968322d7c7725c1de3dcf3f477791 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 5 Dec 2024 18:10:15 +0100 Subject: [PATCH 009/102] rentAnatomicalModel --- .../source/class/osparc/data/Resources.js | 6 +++- .../source/class/osparc/study/PricingUnit.js | 3 +- .../vipMarket/AnatomicalModelDetails.js | 33 +++---------------- 3 files changed, 12 insertions(+), 30 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 86622354105..88be975041d 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -1250,7 +1250,11 @@ qx.Class.define("osparc.data.Resources", { get: { method: "GET", url: statics.API + "/catalog/licensed-items" - } + }, + purchase: { + method: "GET", + url: statics.API + "/catalog/licensed-items/{licensedItemId}:purchase" + }, } } }; diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js index f9268131d32..c2de6606e30 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js @@ -31,7 +31,8 @@ qx.Class.define("osparc.study.PricingUnit", { }, events: { - "editPricingUnit": "qx.event.type.Event" + "editPricingUnit": "qx.event.type.Event", + "rentPricingUnit": "qx.event.type.Event", }, properties: { diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 02f38b35476..d456b6e46a5 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -192,40 +192,17 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { showRentButton: true, allowGrowY: false }); + pUnit.addListener("rentPricingUnit", () => this.__rentAnatomicalModel(anatomicalModelsData, pricingUnit)); pricingUnitsLayout.add(pUnit); }); }) .catch(err => console.error(err)); - /* - if (anatomicalModelsData["leased"]) { - const leaseModelButton = new qx.ui.form.Button().set({ - label: this.tr("3 seats Leased (27 days left)"), - appearance: "strong-button", - center: true, - enabled: false, - }); - buttonsLayout.add(leaseModelButton, { - flex: 1 - }); - } - const leaseModelButton = new osparc.ui.form.FetchButton().set({ - label: this.tr("Lease model (2 for months)"), - appearance: "strong-button", - center: true, - }); - leaseModelButton.addListener("execute", () => { - leaseModelButton.setFetching(true); - setTimeout(() => { - leaseModelButton.setFetching(false); - this.fireDataEvent("modelLeased", this.getAnatomicalModelsData()["modelId"]); - }, 2000); - }); - buttonsLayout.add(leaseModelButton, { - flex: 1 - }); - */ return pricingUnitsLayout; }, + + __rentAnatomicalModel: function(anatomicalModelsData, pricingUnit) { + console.log(":puschase", anatomicalModelsData["licensedItemId"], pricingUnit["pricingUnitId"]); + }, } }); From 228469bc9aa1e8d3a36446edefa21a25de7760e4 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 5 Dec 2024 18:12:24 +0100 Subject: [PATCH 010/102] minors --- .../client/source/class/osparc/navigation/UserMenu.js | 5 +++-- .../source/class/osparc/vipMarket/MarketWindow.js | 11 +++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/navigation/UserMenu.js b/services/static-webserver/client/source/class/osparc/navigation/UserMenu.js index 160ab65ae29..b96841de7d9 100644 --- a/services/static-webserver/client/source/class/osparc/navigation/UserMenu.js +++ b/services/static-webserver/client/source/class/osparc/navigation/UserMenu.js @@ -112,13 +112,14 @@ qx.Class.define("osparc.navigation.UserMenu", { this.add(control); break; } - case "license": + case "license": { control = new qx.ui.menu.Button(this.tr("License")); osparc.utils.Utils.setIdToWidget(control, "userMenuLicenseBtn"); const licenseURL = osparc.store.Support.getLicenseURL(); control.addListener("execute", () => window.open(licenseURL)); this.add(control); break; + } case "tip-lite-button": control = new qx.ui.menu.Button(this.tr("Access Full TIP")); osparc.utils.Utils.setIdToWidget(control, "userMenuAccessTIPBtn"); @@ -237,7 +238,7 @@ qx.Class.define("osparc.navigation.UserMenu", { this.addSeparator(); this.__addAnnouncements(); - + if (osparc.product.Utils.showS4LStore()) { this.getChildControl("market"); } diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js b/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js index d01207f883f..050a67d7dab 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js @@ -37,10 +37,13 @@ qx.Class.define("osparc.vipMarket.MarketWindow", { statics: { openWindow: function() { - const storeWindow = new osparc.vipMarket.MarketWindow(); - storeWindow.center(); - storeWindow.open(); - return storeWindow; + if (osparc.product.Utils.showS4LStore()) { + const storeWindow = new osparc.vipMarket.MarketWindow(); + storeWindow.center(); + storeWindow.open(); + return storeWindow; + } + return null; } }, From a08f4e76e1c2ff015adb96865d11cb3a6b0bd3c3 Mon Sep 17 00:00:00 2001 From: Odei Maiz <33152403+odeimaiz@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:15:40 +0100 Subject: [PATCH 011/102] @pcrespov review --- .../client/source/class/osparc/data/Resources.js | 2 +- .../source/class/osparc/vipMarket/AnatomicalModelDetails.js | 2 +- 2 files changed, 2 insertions(+), 2 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 88be975041d..1843481bfcc 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -1252,7 +1252,7 @@ qx.Class.define("osparc.data.Resources", { url: statics.API + "/catalog/licensed-items" }, purchase: { - method: "GET", + method: "POST", url: statics.API + "/catalog/licensed-items/{licensedItemId}:purchase" }, } diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index d456b6e46a5..3a2bc087eab 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -202,7 +202,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }, __rentAnatomicalModel: function(anatomicalModelsData, pricingUnit) { - console.log(":puschase", anatomicalModelsData["licensedItemId"], pricingUnit["pricingUnitId"]); + console.log(":purchase", anatomicalModelsData["licensedItemId"], pricingUnit["pricingUnitId"]); }, } }); From e43dc57f8bd734385ba13fa4ed9abf0d903ab9e0 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 10:54:28 +0100 Subject: [PATCH 012/102] PricingPlan and PricingUnit models --- .../class/osparc/data/model/PricingPlan.js | 92 +++++++++++++++++++ .../class/osparc/data/model/PricingUnit.js | 76 +++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js create mode 100644 services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js diff --git a/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js b/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js new file mode 100644 index 00000000000..12f9ee5d322 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js @@ -0,0 +1,92 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2024 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +/** + * Class that stores PricingPlan data. + */ + +qx.Class.define("osparc.data.model.PricingPlan", { + extend: qx.core.Object, + + /** + * @param pricingPlanData {Object} Object containing the serialized PricingPlan Data + */ + construct: function(pricingPlanData) { + this.base(arguments); + + this.set({ + pricingPlanId: pricingPlanData.pricingPlanId, + pricingPlanKey: pricingPlanData.pricingPlanKey, + pricingUnits: pricingPlanData.pricingUnits, + classification: pricingPlanData.classification, + name: pricingPlanData.displayName, + description: pricingPlanData.description, + isActive: pricingPlanData.isActive, + }); + }, + + properties: { + pricingPlanId: { + check: "Number", + nullable: false, + init: null, + event: "changePricingPlanId" + }, + + pricingPlanKey: { + check: "String", + nullable: true, + init: null, + event: "changePricingPlanKey" + }, + + pricingUnits: { + check: "Array", + nullable: true, + init: null, + event: "changePricingunits" + }, + + classification: { + check: "TIER, ", + nullable: false, + init: null, + event: "changeClassification" + }, + + name: { + check: "String", + nullable: false, + init: null, + event: "changeName" + }, + + description: { + check: "String", + nullable: true, + init: null, + event: "changeDescription" + }, + + isActive: { + check: "Boolean", + nullable: false, + init: false, + event: "changeIsActive" + }, + }, +}); diff --git a/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js b/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js new file mode 100644 index 00000000000..426397639e2 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js @@ -0,0 +1,76 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2024 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +/** + * Class that stores PricingUnit data. + */ + +qx.Class.define("osparc.data.model.PricingUnit", { + extend: qx.core.Object, + + /** + * @param pricingUnitData {Object} Object containing the serialized PricingUnit Data + */ + construct: function(pricingUnitData) { + this.base(arguments); + + this.set({ + pricingUnitId: pricingUnitData.pricingUnitId, + name: pricingUnitData.unitName, + cost: pricingUnitData.currentCostPerUnit, + isDefault: pricingUnitData.default, + extraInfo: pricingUnitData.unitExtraInfo, + }); + }, + + properties: { + pricingUnitId: { + check: "Number", + nullable: true, + init: null, + event: "changePricingUnitId" + }, + + name: { + check: "String", + nullable: false, + init: null, + event: "changeName" + }, + + cost: { + check: "Number", + nullable: false, + init: null, + event: "changeCost" + }, + + isDefault: { + check: "Boolean", + nullable: false, + init: false, + event: "changeIsDefault", + }, + + extraInfo: { + check: "Object", + nullable: false, + init: null, + event: "changeExtraInfo" + }, + }, +}); From 38cd47e2c4b7d6e2b98594216d430fcd6ced4c85 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 11:21:47 +0100 Subject: [PATCH 013/102] Pricing own store and models --- .../source/class/osparc/data/Resources.js | 4 +- .../class/osparc/data/model/PricingUnit.js | 2 +- .../source/class/osparc/pricing/PlanData.js | 89 -------------- .../source/class/osparc/pricing/PlanEditor.js | 14 +-- .../source/class/osparc/pricing/Plans.js | 6 +- .../source/class/osparc/pricing/UnitData.js | 90 -------------- .../source/class/osparc/store/Pricing.js | 116 ++++++++++++++++++ .../source/class/osparc/study/PricingUnit.js | 4 +- 8 files changed, 130 insertions(+), 195 deletions(-) delete mode 100644 services/static-webserver/client/source/class/osparc/pricing/PlanData.js delete mode 100644 services/static-webserver/client/source/class/osparc/pricing/UnitData.js create mode 100644 services/static-webserver/client/source/class/osparc/store/Pricing.js 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 1843481bfcc..01c0fee7b27 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -631,7 +631,7 @@ qx.Class.define("osparc.data.Resources", { * PRICING PLANS */ "pricingPlans": { - useCache: true, + useCache: false, // handled in osparc.store.Pricing endpoints: { get: { method: "GET", @@ -656,7 +656,7 @@ qx.Class.define("osparc.data.Resources", { * PRICING UNITS */ "pricingUnits": { - useCache: true, + useCache: false, // handled in osparc.store.Pricing endpoints: { getOne: { method: "GET", diff --git a/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js b/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js index 426397639e2..ebc71bbdad9 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js @@ -31,7 +31,7 @@ qx.Class.define("osparc.data.model.PricingUnit", { this.set({ pricingUnitId: pricingUnitData.pricingUnitId, name: pricingUnitData.unitName, - cost: pricingUnitData.currentCostPerUnit, + cost: parseFloat(pricingUnitData.currentCostPerUnit), isDefault: pricingUnitData.default, extraInfo: pricingUnitData.unitExtraInfo, }); diff --git a/services/static-webserver/client/source/class/osparc/pricing/PlanData.js b/services/static-webserver/client/source/class/osparc/pricing/PlanData.js deleted file mode 100644 index d2c3e00275c..00000000000 --- a/services/static-webserver/client/source/class/osparc/pricing/PlanData.js +++ /dev/null @@ -1,89 +0,0 @@ -/* ************************************************************************ - - osparc - the simcore frontend - - https://osparc.io - - Copyright: - 2024 IT'IS Foundation, https://itis.swiss - - License: - MIT: https://opensource.org/licenses/MIT - - Authors: - * Odei Maiz (odeimaiz) - -************************************************************************ */ - -/** - * Class that stores Pricing Plan data. - * - */ - -qx.Class.define("osparc.pricing.PlanData", { - extend: qx.core.Object, - - construct: function(planData) { - this.base(arguments); - - this.set({ - pricingPlanId: planData.pricingPlanId, - pricingPlanKey: planData.pricingPlanKey, - displayName: planData.displayName, - description: planData.description, - classification: planData.classification, - isActive: planData.isActive - }); - }, - - properties: { - pricingPlanId: { - check: "Number", - nullable: false, - init: 0, - event: "changePricingPlanId" - }, - - pricingPlanKey: { - check: "String", - nullable: false, - init: "", - event: "changePricingPlanKey" - }, - - displayName: { - check: "String", - init: "", - nullable: false, - event: "changeDisplayName" - }, - - description: { - check: "String", - init: "", - nullable: false, - event: "changeDescription" - }, - - classification: { - check: "String", - init: "TIER", - nullable: false, - event: "changeClassification" - }, - - isActive: { - check: "Boolean", - init: true, - nullable: false, - event: "changeIsActive" - }, - - pricingUnits: { - check: "Array", - init: [], - nullable: false, - event: "changePricingUnits" - } - } -}); diff --git a/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js b/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js index c638343ed88..905e423fa8d 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js +++ b/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js @@ -200,15 +200,13 @@ qx.Class.define("osparc.pricing.PlanEditor", { const name = this.getName(); const description = this.getDescription(); const classification = this.getClassification(); - const params = { - data: { - "pricingPlanKey": ppKey, - "displayName": name, - "description": description, - "classification": classification - } + const newPricingPlanData = { + "pricingPlanKey": ppKey, + "displayName": name, + "description": description, + "classification": classification }; - osparc.data.Resources.fetch("pricingPlans", "post", params) + osparc.store.Pricing.getInstance().postPricingPlan(newPricingPlanData) .then(() => { osparc.FlashMessenger.getInstance().logAs(name + this.tr(" successfully created")); this.fireEvent("done"); diff --git a/services/static-webserver/client/source/class/osparc/pricing/Plans.js b/services/static-webserver/client/source/class/osparc/pricing/Plans.js index d539698f8d8..7d50a7e0c66 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/Plans.js +++ b/services/static-webserver/client/source/class/osparc/pricing/Plans.js @@ -91,7 +91,7 @@ qx.Class.define("osparc.pricing.Plans", { ctrl.bindProperty("pricingPlanId", "model", null, item, id); ctrl.bindProperty("pricingPlanId", "ppId", null, item, id); ctrl.bindProperty("pricingPlanKey", "ppKey", null, item, id); - ctrl.bindProperty("displayName", "title", null, item, id); + ctrl.bindProperty("name", "title", null, item, id); ctrl.bindProperty("description", "description", null, item, id); ctrl.bindProperty("isActive", "isActive", null, item, id); }, @@ -103,13 +103,13 @@ qx.Class.define("osparc.pricing.Plans", { }, fetchPlans: function() { - osparc.data.Resources.fetch("pricingPlans", "get") + osparc.store.Pricing.getInstance().fetchPricingPlans() .then(data => this.__populateList(data)); }, __populateList: function(pricingPlans) { this.__model.removeAll(); - pricingPlans.forEach(pricingPlan => this.__model.append(new osparc.pricing.PlanData(pricingPlan))); + pricingPlans.forEach(pricingPlan => this.__model.append(pricingPlan)); }, __openCreatePricingPlan: function() { diff --git a/services/static-webserver/client/source/class/osparc/pricing/UnitData.js b/services/static-webserver/client/source/class/osparc/pricing/UnitData.js deleted file mode 100644 index 7f636ee9ab8..00000000000 --- a/services/static-webserver/client/source/class/osparc/pricing/UnitData.js +++ /dev/null @@ -1,90 +0,0 @@ -/* ************************************************************************ - - osparc - the simcore frontend - - https://osparc.io - - Copyright: - 2024 IT'IS Foundation, https://itis.swiss - - License: - MIT: https://opensource.org/licenses/MIT - - Authors: - * Odei Maiz (odeimaiz) - -************************************************************************ */ - -/** - * Class that stores Pricing Unit data. - * - */ - -qx.Class.define("osparc.pricing.UnitData", { - extend: qx.core.Object, - - construct: function(unitData) { - this.base(arguments); - - this.set({ - pricingUnitId: unitData.pricingUnitId ? unitData.pricingUnitId : null, - unitName: unitData.unitName, - currentCostPerUnit: parseFloat(unitData.currentCostPerUnit), - comment: unitData.comment ? unitData.comment : "", - awsSpecificInfo: unitData.specificInfo && unitData.specificInfo["aws_ec2_instances"] ? unitData.specificInfo["aws_ec2_instances"].toString() : "", - unitExtraInfo: unitData.unitExtraInfo, - default: unitData.default - }); - }, - - properties: { - pricingUnitId: { - check: "Number", - nullable: true, - init: null, - event: "changePricingUnitId" - }, - - unitName: { - check: "String", - init: "", - nullable: false, - event: "changeUnitName" - }, - - currentCostPerUnit: { - check: "Number", - nullable: false, - init: 0, - event: "changeCurrentCostPerUnit" - }, - - comment: { - check: "String", - init: "", - nullable: false, - event: "changeComment" - }, - - awsSpecificInfo: { - check: "String", - init: "", - nullable: false, - event: "changeAwsSpecificInfo" - }, - - unitExtraInfo: { - check: "Object", - init: {}, - nullable: false, - event: "changeUnitExtraInfo" - }, - - default: { - check: "Boolean", - init: true, - nullable: false, - event: "changeDefault" - } - } -}); diff --git a/services/static-webserver/client/source/class/osparc/store/Pricing.js b/services/static-webserver/client/source/class/osparc/store/Pricing.js new file mode 100644 index 00000000000..037badab44b --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/store/Pricing.js @@ -0,0 +1,116 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2024 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.store.Pricing", { + extend: qx.core.Object, + type: "singleton", + + construct: function() { + this.base(arguments); + + this.pricingPlansCached = []; + }, + + events: { + "pricingPlansChanged": "qx.event.type.Data", + }, + + members: { + pricingPlansCached: null, + + fetchPricingPlans: function() { + return osparc.data.Resources.fetch("pricingPlans", "get") + .then(pricingPlansData => { + const pricingPlans = []; + pricingPlansData.forEach(pricingPlanData => { + const pricingPlan = this.__addToCache(pricingPlanData); + pricingPlans.push(pricingPlan); + }); + return pricingPlans; + }); + }, + + postPricingPlan: function(newPricingPlanData) { + const params = { + data: newPricingPlanData + }; + return osparc.data.Resources.fetch("pricingPlans", "post", params) + .then(pricingPlanData => { + const pricingPlan = this.__addToCache(pricingPlanData); + this.fireDataEvent("pricingPlansChanged", pricingPlan); + return pricingPlan; + }); + }, + + putPricingPlan: function(pricingPlanId, updateData) { + const params = { + url: { + pricingPlanId + }, + data: updateData + }; + return osparc.data.Resources.getInstance().fetch("pricingPlans", "put", params) + .then(pricingPlanData => { + return this.__addToCache(pricingPlanData); + }) + .catch(console.error); + }, + + fetchPricingUnits: function(pricingPlanId) { + const params = { + url: { + pricingPlanId, + } + }; + return osparc.data.Resources.fetch("pricingUnits", "get", params) + .then(pricingPlansData => { + const pricingPlans = []; + pricingPlansData.forEach(pricingPlanData => { + const pricingPlan = this.__addToCache(pricingPlanData); + pricingPlans.push(pricingPlan); + }); + return pricingPlans; + }); + }, + + getPricingPlans: function() { + return this.pricingPlansCached; + }, + + getPricingPlan: function(pricingPlanId = null) { + return this.pricingPlansCached.find(f => f.getPricingPlanId() === pricingPlanId); + }, + + __addToCache: function(pricingPlanData) { + let pricingPlan = this.pricingPlansCached.find(f => f.getPricingPlanId() === pricingPlanData["pricingPlanId"]); + if (pricingPlan) { + const props = Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.PricingPlan)); + // put + Object.keys(pricingPlanData).forEach(key => { + if (props.includes(key)) { + pricingPlan.set(key, pricingPlanData[key]); + } + }); + } else { + // get and post + pricingPlan = new osparc.data.model.PricingPlan(pricingPlanData); + this.pricingPlansCached.unshift(pricingPlan); + } + return pricingPlan; + }, + } +}); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js index c2de6606e30..4f93907cea3 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js @@ -27,7 +27,7 @@ qx.Class.define("osparc.study.PricingUnit", { decorator: "rounded", }); - this.setUnitData(new osparc.pricing.UnitData(pricingUnit)); + this.setUnitData(new osparc.data.model.PricingUnit(pricingUnit)); }, events: { @@ -37,7 +37,7 @@ qx.Class.define("osparc.study.PricingUnit", { properties: { unitData: { - check: "osparc.pricing.UnitData", + check: "osparc.data.model.PricingUnit", nullable: false, init: null, apply: "__buildLayout" From cf5c35cadb2ee6ed4b7d959fd81881c7f3283d34 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 11:24:28 +0100 Subject: [PATCH 014/102] LICENSE --- .../client/source/class/osparc/data/model/PricingPlan.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js b/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js index 12f9ee5d322..226c2d4ff19 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js +++ b/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js @@ -32,7 +32,7 @@ qx.Class.define("osparc.data.model.PricingPlan", { pricingPlanId: pricingPlanData.pricingPlanId, pricingPlanKey: pricingPlanData.pricingPlanKey, pricingUnits: pricingPlanData.pricingUnits, - classification: pricingPlanData.classification, + classification: pricingPlanData.displayName.includes("ViP") ? "LICENSE" : pricingPlanData.classification, name: pricingPlanData.displayName, description: pricingPlanData.description, isActive: pricingPlanData.isActive, @@ -62,7 +62,7 @@ qx.Class.define("osparc.data.model.PricingPlan", { }, classification: { - check: "TIER, ", + check: ["TIER", "LICENSE"], nullable: false, init: null, event: "changeClassification" From 351fee5a820c85f5a05b2a92bbef717e3c6357e9 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 11:34:18 +0100 Subject: [PATCH 015/102] show classification prop --- .../class/osparc/pricing/PlanListItem.js | 32 ++++++++++++++++--- .../source/class/osparc/pricing/Plans.js | 1 + 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/pricing/PlanListItem.js b/services/static-webserver/client/source/class/osparc/pricing/PlanListItem.js index 522f9e35317..a76527e35fd 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/PlanListItem.js +++ b/services/static-webserver/client/source/class/osparc/pricing/PlanListItem.js @@ -30,6 +30,7 @@ qx.Class.define("osparc.pricing.PlanListItem", { this.getChildControl("title"); this.getChildControl("description"); + this.getChildControl("classification"); this.getChildControl("edit-button"); this.addListener("pointerover", this._onPointerOver, this); @@ -74,6 +75,12 @@ qx.Class.define("osparc.pricing.PlanListItem", { event: "changeDescription" }, + classification: { + check: "String", + nullable: true, + event: "changeClassification" + }, + isActive: { check: "Boolean", apply: "__applyIsActive", @@ -112,7 +119,8 @@ qx.Class.define("osparc.pricing.PlanListItem", { case "pp-id": control = new qx.ui.basic.Label().set({ font: "text-14", - alignY: "middle" + alignY: "middle", + width: 35, }); this._add(control, { row: 0, @@ -123,7 +131,8 @@ qx.Class.define("osparc.pricing.PlanListItem", { case "pp-key": control = new qx.ui.basic.Label().set({ font: "text-14", - alignY: "middle" + alignY: "middle", + width: 80, }); this._add(control, { row: 0, @@ -151,6 +160,19 @@ qx.Class.define("osparc.pricing.PlanListItem", { column: 2 }); break; + case "classification": + control = new qx.ui.basic.Label().set({ + font: "text-14", + alignY: "middle", + width: 60, + }); + this.bind("classification", control, "value"); + this._add(control, { + row: 0, + column: 3, + rowSpan: 2 + }); + break; case "is-active": control = new qx.ui.basic.Label().set({ font: "text-14", @@ -158,7 +180,7 @@ qx.Class.define("osparc.pricing.PlanListItem", { }); this._add(control, { row: 0, - column: 3, + column: 4, rowSpan: 2 }); break; @@ -170,7 +192,7 @@ qx.Class.define("osparc.pricing.PlanListItem", { control.addListener("tap", () => this.fireEvent("editPricingPlan")); this._add(control, { row: 0, - column: 4, + column: 5, rowSpan: 2 }); break; @@ -206,7 +228,7 @@ qx.Class.define("osparc.pricing.PlanListItem", { return; } const label = this.getChildControl("is-active"); - label.setValue("Active: " + value); + label.setValue(value ? "Active" : "Inactive"); }, /** diff --git a/services/static-webserver/client/source/class/osparc/pricing/Plans.js b/services/static-webserver/client/source/class/osparc/pricing/Plans.js index 7d50a7e0c66..9ebef3d3599 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/Plans.js +++ b/services/static-webserver/client/source/class/osparc/pricing/Plans.js @@ -93,6 +93,7 @@ qx.Class.define("osparc.pricing.Plans", { ctrl.bindProperty("pricingPlanKey", "ppKey", null, item, id); ctrl.bindProperty("name", "title", null, item, id); ctrl.bindProperty("description", "description", null, item, id); + ctrl.bindProperty("classification", "classification", null, item, id); ctrl.bindProperty("isActive", "isActive", null, item, id); }, configureItem: item => { From 0f534b97ab96f3618bf72cbab18d76d8e567be8b Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 13:11:40 +0100 Subject: [PATCH 016/102] __addPricingUnitToCache --- .../class/osparc/data/model/PricingPlan.js | 4 +- .../source/class/osparc/pricing/UnitsList.js | 7 +--- .../source/class/osparc/store/Pricing.js | 39 +++++++++++++++---- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js b/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js index 226c2d4ff19..8e7a0e0dddd 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js +++ b/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js @@ -31,7 +31,7 @@ qx.Class.define("osparc.data.model.PricingPlan", { this.set({ pricingPlanId: pricingPlanData.pricingPlanId, pricingPlanKey: pricingPlanData.pricingPlanKey, - pricingUnits: pricingPlanData.pricingUnits, + pricingUnits: pricingPlanData.pricingUnits || [], classification: pricingPlanData.displayName.includes("ViP") ? "LICENSE" : pricingPlanData.classification, name: pricingPlanData.displayName, description: pricingPlanData.description, @@ -57,7 +57,7 @@ qx.Class.define("osparc.data.model.PricingPlan", { pricingUnits: { check: "Array", nullable: true, - init: null, + init: [], event: "changePricingunits" }, diff --git a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js index 32be30c2f60..a6e67290c64 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js +++ b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js @@ -63,12 +63,7 @@ qx.Class.define("osparc.pricing.UnitsList", { }, __fetchUnits: function() { - const params = { - url: { - pricingPlanId: this.getPricingPlanId() - } - }; - osparc.data.Resources.fetch("pricingPlans", "getOne", params) + osparc.store.Pricing.getInstance().fetchPricingUnits(this.getPricingPlanId()) .then(data => this.__populateList(data["pricingUnits"])); }, diff --git a/services/static-webserver/client/source/class/osparc/store/Pricing.js b/services/static-webserver/client/source/class/osparc/store/Pricing.js index 037badab44b..ada09570bd1 100644 --- a/services/static-webserver/client/source/class/osparc/store/Pricing.js +++ b/services/static-webserver/client/source/class/osparc/store/Pricing.js @@ -76,14 +76,18 @@ qx.Class.define("osparc.store.Pricing", { pricingPlanId, } }; - return osparc.data.Resources.fetch("pricingUnits", "get", params) - .then(pricingPlansData => { - const pricingPlans = []; - pricingPlansData.forEach(pricingPlanData => { - const pricingPlan = this.__addToCache(pricingPlanData); - pricingPlans.push(pricingPlan); - }); - return pricingPlans; + return osparc.data.Resources.fetch("pricingPlans", "getOne", params) + .then(pricingPlanData => { + const pricingUnits = []; + const pricingPlan = this.getPricingPlan(pricingPlanId); + if (pricingPlan && "pricingUnits" in pricingPlanData) { + const pricingUnitsData = pricingPlanData["pricingUnits"]; + pricingUnitsData.forEach(pricingUnitData => { + const pricingUnit = this.__addPricingUnitToCache(pricingPlan, pricingUnitData); + pricingUnits.push(pricingUnit); + }); + } + return pricingUnits; }); }, @@ -112,5 +116,24 @@ qx.Class.define("osparc.store.Pricing", { } return pricingPlan; }, + + __addPricingUnitToCache: function(pricingPlan, pricingUnitData) { + const pricingUnits = pricingPlan.getPricingUnits(); + let pricingUnit = pricingUnits ? pricingUnits.find(unit => unit.getPricingUnitId() === pricingUnitData["pricingUnitId"]) : null; + if (pricingUnit) { + const props = Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.PricingPlan)); + // put + Object.keys(pricingUnitData).forEach(key => { + if (props.includes(key)) { + pricingPlan.set(key, pricingUnitData[key]); + } + }); + } else { + // get and post + pricingUnit = new osparc.data.model.PricingUnit(pricingUnitData); + pricingUnits.push(pricingUnit); + } + return pricingUnit; + }, } }); From c071a48af5b2712e9561c0f0503b1a4a39600f00 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 13:16:18 +0100 Subject: [PATCH 017/102] [skip ci] classification in unit --- .../client/source/class/osparc/data/model/PricingUnit.js | 7 +++++++ .../client/source/class/osparc/pricing/UnitsList.js | 2 +- .../client/source/class/osparc/store/Pricing.js | 1 + .../client/source/class/osparc/study/PricingUnit.js | 8 ++++---- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js b/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js index ebc71bbdad9..e52f67c1a1e 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js @@ -45,6 +45,13 @@ qx.Class.define("osparc.data.model.PricingUnit", { event: "changePricingUnitId" }, + classification: { + check: ["TIER", "LICENSE"], + nullable: false, + init: null, + event: "changeClassification" + }, + name: { check: "String", nullable: false, diff --git a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js index a6e67290c64..929c62250e4 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js +++ b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js @@ -64,7 +64,7 @@ qx.Class.define("osparc.pricing.UnitsList", { __fetchUnits: function() { osparc.store.Pricing.getInstance().fetchPricingUnits(this.getPricingPlanId()) - .then(data => this.__populateList(data["pricingUnits"])); + .then(pricingUnits => this.__populateList(pricingUnits)); }, __populateList: function(pricingUnits) { diff --git a/services/static-webserver/client/source/class/osparc/store/Pricing.js b/services/static-webserver/client/source/class/osparc/store/Pricing.js index ada09570bd1..c57cd74f953 100644 --- a/services/static-webserver/client/source/class/osparc/store/Pricing.js +++ b/services/static-webserver/client/source/class/osparc/store/Pricing.js @@ -131,6 +131,7 @@ qx.Class.define("osparc.store.Pricing", { } else { // get and post pricingUnit = new osparc.data.model.PricingUnit(pricingUnitData); + pricingPlan.bind("classification", pricingUnit, "classification"); pricingUnits.push(pricingUnit); } return pricingUnit; diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js index 4f93907cea3..0ef5fdcb952 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js @@ -27,7 +27,7 @@ qx.Class.define("osparc.study.PricingUnit", { decorator: "rounded", }); - this.setUnitData(new osparc.data.model.PricingUnit(pricingUnit)); + this.setUnitData(pricingUnit); }, events: { @@ -120,12 +120,12 @@ qx.Class.define("osparc.study.PricingUnit", { this._removeAll(); this._setLayout(new qx.ui.layout.VBox(5)); - const unitName = this.getChildControl("name"); - pricingUnit.bind("unitName", unitName, "value"); + const name = this.getChildControl("name"); + pricingUnit.bind("name", name, "value"); // add price info const price = this.getChildControl("price"); - pricingUnit.bind("currentCostPerUnit", price, "value", { + pricingUnit.bind("cost", price, "value", { converter: v => qx.locale.Manager.tr("Credits/h") + ": " + v, }); From 5f7ab7b173a5bdce231fe9d1729a509eb4737f2a Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 13:22:52 +0100 Subject: [PATCH 018/102] [skip ci] more refactoring --- .../client/source/class/osparc/pricing/UnitsList.js | 2 +- .../client/source/class/osparc/study/PricingUnit.js | 11 +++++++++-- .../client/source/class/osparc/study/PricingUnits.js | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js index 929c62250e4..05001f6b998 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js +++ b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js @@ -87,7 +87,7 @@ qx.Class.define("osparc.pricing.UnitsList", { const buttons = this.getChildControl("pricing-units-container").getChildren(); const keepDefaultSelected = () => { buttons.forEach(btn => { - btn.setValue(btn.getUnitData().isDefault()); + btn.setValue(btn.getUnitData().getIsDefault()); }); }; keepDefaultSelected(); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js index 0ef5fdcb952..746fb99f2ca 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js @@ -126,7 +126,14 @@ qx.Class.define("osparc.study.PricingUnit", { // add price info const price = this.getChildControl("price"); pricingUnit.bind("cost", price, "value", { - converter: v => qx.locale.Manager.tr("Credits/h") + ": " + v, + converter: v => { + if (pricingUnit.getClassification() === "TIER") { + return qx.locale.Manager.tr("Credits/h") + ": " + v; + } else if (pricingUnit.getClassification() === "LICENSE") { + return qx.locale.Manager.tr("Credits") + ": " + v; + } + return qx.locale.Manager.tr("Credits") + ": " + v; + }, }); // add aws specific info @@ -143,7 +150,7 @@ qx.Class.define("osparc.study.PricingUnit", { // add pricing unit extra info const unitExtraInfo = this.getChildControl("unitExtraInfo"); let text = ""; - Object.entries(pricingUnit.getUnitExtraInfo()).forEach(([key, value]) => { + Object.entries(pricingUnit.getExtraInfo()).forEach(([key, value]) => { text += `${key}: ${value}
`; }); unitExtraInfo.setValue(text); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnits.js b/services/static-webserver/client/source/class/osparc/study/PricingUnits.js index 02597e76760..16488ae23b9 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnits.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnits.js @@ -63,7 +63,7 @@ qx.Class.define("osparc.study.PricingUnits", { } else { // preselect default buttons.forEach(button => { - if (button.getUnitData().isDefault()) { + if (button.getUnitData().getIsDefault()) { button.setValue(true); } }); @@ -78,7 +78,7 @@ qx.Class.define("osparc.study.PricingUnits", { const selectedUnitId = button.getUnitData().getPricingUnitId(); this.setSelectedUnitId(selectedUnitId); } else { - buttons.forEach(btn => btn.setValue(btn.getUnitData().isDefault())); + buttons.forEach(btn => btn.setValue(btn.getUnitData().getIsDefault())); } }); }); From eac4e13b29987d5ec44521e5dcfa1210e35bbfcd Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 13:42:57 +0100 Subject: [PATCH 019/102] [skip ci] refactoring --- .../class/osparc/node/TierSelectionView.js | 13 +- .../class/osparc/service/PricingUnitsList.js | 6 +- .../class/osparc/study/NodePricingUnits.js | 10 +- .../source/class/osparc/study/PricingUnit.js | 111 +---------------- .../class/osparc/study/PricingUnitLicense.js | 67 +++++++++++ .../class/osparc/study/PricingUnitTier.js | 113 ++++++++++++++++++ .../source/class/osparc/study/PricingUnits.js | 2 +- .../vipMarket/AnatomicalModelDetails.js | 8 +- 8 files changed, 202 insertions(+), 128 deletions(-) create mode 100644 services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js create mode 100644 services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js diff --git a/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js b/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js index ffa1431a00e..cdb696a4da4 100644 --- a/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js +++ b/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js @@ -57,9 +57,12 @@ qx.Class.define("osparc.node.TierSelectionView", { osparc.data.Resources.fetch("services", "pricingPlans", plansParams) .then(pricingPlans => { if (pricingPlans && "pricingUnits" in pricingPlans && pricingPlans["pricingUnits"].length) { - const pUnits = pricingPlans["pricingUnits"]; - pUnits.forEach(pUnit => { - const tItem = new qx.ui.form.ListItem(pUnit.unitName, null, pUnit.pricingUnitId); + const pricingUnits = pricingPlans["pricingUnits"].map(princingUnitData => { + const pricingUnit = new osparc.data.model.PricingUnit(princingUnitData); + return pricingUnit; + }); + pricingUnits.forEach(pricingUnit => { + const tItem = new qx.ui.form.ListItem(pricingUnit.getName(), null, pricingUnit.getPricingUnitId()); tierBox.add(tItem); }); const unitParams = { @@ -81,8 +84,8 @@ qx.Class.define("osparc.node.TierSelectionView", { }) .finally(() => { const pUnitUIs = []; - pUnits.forEach(pUnit => { - const pUnitUI = new osparc.study.PricingUnit(pUnit).set({ + pricingUnits.forEach(pricingUnit => { + const pUnitUI = new osparc.study.PricingUnitTier(pricingUnit).set({ allowGrowX: false }); pUnitUI.getChildControl("name").exclude(); diff --git a/services/static-webserver/client/source/class/osparc/service/PricingUnitsList.js b/services/static-webserver/client/source/class/osparc/service/PricingUnitsList.js index f7dcc85a457..4e3587238b0 100644 --- a/services/static-webserver/client/source/class/osparc/service/PricingUnitsList.js +++ b/services/static-webserver/client/source/class/osparc/service/PricingUnitsList.js @@ -61,11 +61,11 @@ qx.Class.define("osparc.service.PricingUnitsList", { }); }, - __populateList: function(pricingUnits) { + __populateList: function(pricingUnitsData) { this.getChildControl("pricing-units-container").removeAll(); - if (pricingUnits.length) { - const pUnits = new osparc.study.PricingUnits(pricingUnits, null, false); + if (pricingUnitsData.length) { + const pUnits = new osparc.study.PricingUnits(pricingUnitsData, null, false); this.getChildControl("pricing-units-container").add(pUnits); } else { const notFound = new qx.ui.basic.Label().set({ diff --git a/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js b/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js index 76918e12b3e..aba4b8a48b2 100644 --- a/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js +++ b/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js @@ -107,8 +107,8 @@ qx.Class.define("osparc.study.NodePricingUnits", { ) }; osparc.data.Resources.fetch("services", "pricingPlans", plansParams) - .then(pricingPlan => { - if (pricingPlan) { + .then(pricingPlanData => { + if (pricingPlanData) { const unitParams = { url: { studyId, @@ -116,12 +116,12 @@ qx.Class.define("osparc.study.NodePricingUnits", { } }; this.set({ - pricingPlanId: pricingPlan["pricingPlanId"] + pricingPlanId: pricingPlanData["pricingPlanId"] }); osparc.data.Resources.fetch("studies", "getPricingUnit", unitParams) .then(preselectedPricingUnit => { - if (pricingPlan && "pricingUnits" in pricingPlan && pricingPlan["pricingUnits"].length) { - const pricingUnitButtons = this.__pricingUnits = new osparc.study.PricingUnits(pricingPlan["pricingUnits"], preselectedPricingUnit); + if (pricingPlanData && "pricingUnits" in pricingPlanData && pricingPlanData["pricingUnits"].length) { + const pricingUnitButtons = this.__pricingUnits = new osparc.study.PricingUnits(pricingPlanData["pricingUnits"], preselectedPricingUnit); if (inGroupBox) { const pricingUnitsLayout = osparc.study.StudyOptions.createGroupBox(nodeLabel); pricingUnitsLayout.add(pricingUnitButtons); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js index 746fb99f2ca..9337ccbd96a 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js @@ -17,6 +17,7 @@ qx.Class.define("osparc.study.PricingUnit", { extend: qx.ui.form.ToggleButton, + type: "abstract", construct: function(pricingUnit) { this.base(arguments); @@ -30,45 +31,12 @@ qx.Class.define("osparc.study.PricingUnit", { this.setUnitData(pricingUnit); }, - events: { - "editPricingUnit": "qx.event.type.Event", - "rentPricingUnit": "qx.event.type.Event", - }, - properties: { unitData: { check: "osparc.data.model.PricingUnit", nullable: false, init: null, - apply: "__buildLayout" - }, - - showAwsSpecificInfo: { - check: "Boolean", - init: null, - nullable: true, - event: "changeShowAwsSpecificInfo" - }, - - showUnitExtraInfo: { - check: "Boolean", - init: true, - nullable: true, - event: "changeShowUnitExtraInfo" - }, - - showEditButton: { - check: "Boolean", - init: false, - nullable: true, - event: "changeShowEditButton" - }, - - showRentButton: { - check: "Boolean", - init: false, - nullable: true, - event: "changeShowRentButton" + apply: "_buildLayout" }, }, @@ -88,89 +56,16 @@ qx.Class.define("osparc.study.PricingUnit", { }); this._add(control); break; - case "awsSpecificInfo": - control = new qx.ui.basic.Label().set({ - font: "text-14" - }); - this._add(control); - break; - case "unitExtraInfo": - control = new qx.ui.basic.Label().set({ - font: "text-13", - rich: true, - }); - this._add(control); - break; - case "edit-button": - control = new qx.ui.form.Button(qx.locale.Manager.tr("Edit")); - this._add(control); - break; - case "rent-button": - control = new qx.ui.form.Button(qx.locale.Manager.tr("Rent")).set({ - appearance: "strong-button", - center: true, - }); - this._add(control); - break; } return control || this.base(arguments, id); }, - __buildLayout: function(pricingUnit) { + _buildLayout: function(pricingUnit) { this._removeAll(); this._setLayout(new qx.ui.layout.VBox(5)); const name = this.getChildControl("name"); pricingUnit.bind("name", name, "value"); - - // add price info - const price = this.getChildControl("price"); - pricingUnit.bind("cost", price, "value", { - converter: v => { - if (pricingUnit.getClassification() === "TIER") { - return qx.locale.Manager.tr("Credits/h") + ": " + v; - } else if (pricingUnit.getClassification() === "LICENSE") { - return qx.locale.Manager.tr("Credits") + ": " + v; - } - return qx.locale.Manager.tr("Credits") + ": " + v; - }, - }); - - // add aws specific info - if ("specificInfo" in pricingUnit) { - const specificInfo = this.getChildControl("awsSpecificInfo"); - pricingUnit.bind("awsSpecificInfo", specificInfo, "value", { - converter: v => qx.locale.Manager.tr("EC2") + ": " + v, - }); - this.bind("showAwsSpecificInfo", specificInfo, "visibility", { - converter: show => show ? "visible" : "excluded" - }) - } - - // add pricing unit extra info - const unitExtraInfo = this.getChildControl("unitExtraInfo"); - let text = ""; - Object.entries(pricingUnit.getExtraInfo()).forEach(([key, value]) => { - text += `${key}: ${value}
`; - }); - unitExtraInfo.setValue(text); - this.bind("showUnitExtraInfo", unitExtraInfo, "visibility", { - converter: show => show ? "visible" : "excluded" - }); - - // add edit button - const editButton = this.getChildControl("edit-button"); - this.bind("showEditButton", editButton, "visibility", { - converter: show => show ? "visible" : "excluded" - }) - editButton.addListener("execute", () => this.fireEvent("editPricingUnit")); - - // add rent button - const rentButton = this.getChildControl("rent-button"); - this.bind("showRentButton", rentButton, "visibility", { - converter: show => show ? "visible" : "excluded" - }) - rentButton.addListener("execute", () => this.fireEvent("rentPricingUnit")); } } }); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js new file mode 100644 index 00000000000..84269820f53 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js @@ -0,0 +1,67 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2023 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.study.PricingUnitLicense", { + extend: osparc.study.PricingUnit, + + events: { + "rentPricingUnit": "qx.event.type.Event", + }, + + properties: { + showRentButton: { + check: "Boolean", + init: false, + nullable: true, + event: "changeShowRentButton" + }, + }, + + members: { + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "rent-button": + control = new qx.ui.form.Button(qx.locale.Manager.tr("Rent")).set({ + appearance: "strong-button", + center: true, + }); + this._add(control); + break; + } + return control || this.base(arguments, id); + }, + + // override + _buildLayout: function(pricingUnit) { + this.base(arguments); + + // add price info + const price = this.getChildControl("price"); + pricingUnit.bind("cost", price, "value", { + converter: v => qx.locale.Manager.tr("Credits") + ": " + v + }); + + // add rent button + const rentButton = this.getChildControl("rent-button"); + this.bind("showRentButton", rentButton, "visibility", { + converter: show => show ? "visible" : "excluded" + }) + rentButton.addListener("execute", () => this.fireEvent("rentPricingUnit")); + } + } +}); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js new file mode 100644 index 00000000000..81f8743903e --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js @@ -0,0 +1,113 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2023 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.study.PricingUnitTier", { + extend: osparc.study.PricingUnit, + + events: { + "editPricingUnit": "qx.event.type.Event", + }, + + properties: { + showAwsSpecificInfo: { + check: "Boolean", + init: null, + nullable: true, + event: "changeShowAwsSpecificInfo" + }, + + showUnitExtraInfo: { + check: "Boolean", + init: true, + nullable: true, + event: "changeShowUnitExtraInfo" + }, + + showEditButton: { + check: "Boolean", + init: false, + nullable: true, + event: "changeShowEditButton" + }, + }, + + members: { + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "awsSpecificInfo": + control = new qx.ui.basic.Label().set({ + font: "text-14" + }); + this._add(control); + break; + case "unitExtraInfo": + control = new qx.ui.basic.Label().set({ + font: "text-13", + rich: true, + }); + this._add(control); + break; + case "edit-button": + control = new qx.ui.form.Button(qx.locale.Manager.tr("Edit")); + this._add(control); + break; + } + return control || this.base(arguments, id); + }, + + // override + _buildLayout: function(pricingUnit) { + this.base(arguments); + + // add price info + const price = this.getChildControl("price"); + pricingUnit.bind("cost", price, "value", { + converter: v => qx.locale.Manager.tr("Credits/h") + ": " + v + }); + + // add aws specific info + if ("specificInfo" in pricingUnit) { + const specificInfo = this.getChildControl("awsSpecificInfo"); + pricingUnit.bind("awsSpecificInfo", specificInfo, "value", { + converter: v => qx.locale.Manager.tr("EC2") + ": " + v, + }); + this.bind("showAwsSpecificInfo", specificInfo, "visibility", { + converter: show => show ? "visible" : "excluded" + }) + } + + // add pricing unit extra info + const unitExtraInfo = this.getChildControl("unitExtraInfo"); + let text = ""; + Object.entries(pricingUnit.getExtraInfo()).forEach(([key, value]) => { + text += `${key}: ${value}
`; + }); + unitExtraInfo.setValue(text); + this.bind("showUnitExtraInfo", unitExtraInfo, "visibility", { + converter: show => show ? "visible" : "excluded" + }); + + // add edit button + const editButton = this.getChildControl("edit-button"); + this.bind("showEditButton", editButton, "visibility", { + converter: show => show ? "visible" : "excluded" + }) + editButton.addListener("execute", () => this.fireEvent("editPricingUnit")); + } + } +}); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnits.js b/services/static-webserver/client/source/class/osparc/study/PricingUnits.js index 16488ae23b9..607ca7b87fc 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnits.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnits.js @@ -42,7 +42,7 @@ qx.Class.define("osparc.study.PricingUnits", { __buildLayout: function(pricingUnits, preselectedPricingUnit, changeSelectionAllowed) { const buttons = []; pricingUnits.forEach(pricingUnit => { - const button = new osparc.study.PricingUnit(pricingUnit); + const button = new osparc.study.PricingUnitTier(pricingUnit); buttons.push(button); this._add(button); }); diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 3a2bc087eab..ea9308eb09d 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -185,12 +185,8 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { .then(data => { const pricingUnits = data["pricingUnits"]; pricingUnits.forEach(pricingUnit => { - const pUnit = new osparc.study.PricingUnit(pricingUnit).set({ - showAwsSpecificInfo: false, - showUnitExtraInfo: false, - showEditButton: false, - showRentButton: true, - allowGrowY: false + const pUnit = new osparc.study.PricingUnitLicense(pricingUnit).set({ + allowGrowY: false, }); pUnit.addListener("rentPricingUnit", () => this.__rentAnatomicalModel(anatomicalModelsData, pricingUnit)); pricingUnitsLayout.add(pUnit); From c0b74ff421709893463a9c96c5dec2bba3689333 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 13:50:08 +0100 Subject: [PATCH 020/102] [skip ci] more refactoring --- .../client/source/class/osparc/study/NodePricingUnits.js | 3 ++- .../source/class/osparc/study/PricingUnitLicense.js | 2 +- .../client/source/class/osparc/study/PricingUnitTier.js | 2 +- .../client/source/class/osparc/study/PricingUnits.js | 9 +++++---- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js b/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js index aba4b8a48b2..e158881c57f 100644 --- a/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js +++ b/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js @@ -121,7 +121,8 @@ qx.Class.define("osparc.study.NodePricingUnits", { osparc.data.Resources.fetch("studies", "getPricingUnit", unitParams) .then(preselectedPricingUnit => { if (pricingPlanData && "pricingUnits" in pricingPlanData && pricingPlanData["pricingUnits"].length) { - const pricingUnitButtons = this.__pricingUnits = new osparc.study.PricingUnits(pricingPlanData["pricingUnits"], preselectedPricingUnit); + const pricingUnitsData = pricingPlanData["pricingUnits"]; + const pricingUnitButtons = this.__pricingUnits = new osparc.study.PricingUnits(pricingUnitsData, preselectedPricingUnit); if (inGroupBox) { const pricingUnitsLayout = osparc.study.StudyOptions.createGroupBox(nodeLabel); pricingUnitsLayout.add(pricingUnitButtons); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js index 84269820f53..348a48a01b4 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js @@ -48,7 +48,7 @@ qx.Class.define("osparc.study.PricingUnitLicense", { // override _buildLayout: function(pricingUnit) { - this.base(arguments); + this.base(arguments, pricingUnit); // add price info const price = this.getChildControl("price"); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js index 81f8743903e..a3d631004a1 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js @@ -72,7 +72,7 @@ qx.Class.define("osparc.study.PricingUnitTier", { // override _buildLayout: function(pricingUnit) { - this.base(arguments); + this.base(arguments, pricingUnit); // add price info const price = this.getChildControl("price"); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnits.js b/services/static-webserver/client/source/class/osparc/study/PricingUnits.js index 607ca7b87fc..9cc451a30e3 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnits.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnits.js @@ -18,7 +18,7 @@ qx.Class.define("osparc.study.PricingUnits", { extend: qx.ui.container.Composite, - construct: function(pricingUnits, preselectedPricingUnit, changeSelectionAllowed = true) { + construct: function(pricingUnitsData, preselectedPricingUnit, changeSelectionAllowed = true) { this.base(arguments); this.set({ @@ -26,7 +26,7 @@ qx.Class.define("osparc.study.PricingUnits", { allowGrowY: false, }); - this.__buildLayout(pricingUnits, preselectedPricingUnit, changeSelectionAllowed); + this.__buildLayout(pricingUnitsData, preselectedPricingUnit, changeSelectionAllowed); }, properties: { @@ -39,9 +39,10 @@ qx.Class.define("osparc.study.PricingUnits", { }, members: { - __buildLayout: function(pricingUnits, preselectedPricingUnit, changeSelectionAllowed) { + __buildLayout: function(pricingUnitsData, preselectedPricingUnit, changeSelectionAllowed) { const buttons = []; - pricingUnits.forEach(pricingUnit => { + pricingUnitsData.forEach(pricingUnitData => { + const pricingUnit = new osparc.data.model.PricingUnit(pricingUnitData); const button = new osparc.study.PricingUnitTier(pricingUnit); buttons.push(button); this._add(button); From 3339b980dbc7c08c40ecafc48877740237855952 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 13:58:27 +0100 Subject: [PATCH 021/102] [skip ci] Units List working --- .../client/source/class/osparc/pricing/UnitsList.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js index 05001f6b998..41ac7b53940 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js +++ b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js @@ -75,9 +75,15 @@ qx.Class.define("osparc.pricing.UnitsList", { } pricingUnits.forEach(pricingUnit => { - const pUnit = new osparc.study.PricingUnit(pricingUnit).set({ - showAwsSpecificInfo: true, - showEditButton: true, + let pUnit = null; + if (pricingUnit.getClassification() === "LICENSE") { + pUnit = new osparc.study.PricingUnitLicense(pricingUnit); + } else { + pUnit = new osparc.study.PricingUnitTier(pricingUnit).set({ + showAwsSpecificInfo: true, + }); + } + pUnit.set({ allowGrowY: false }); pUnit.addListener("editPricingUnit", () => this.__openUpdatePricingUnit(pricingUnit)); From b53a1e2cfb077a1d6afb3be90e3dfca0eb0b7100 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 14:03:19 +0100 Subject: [PATCH 022/102] minor --- .../class/osparc/vipMarket/AnatomicalModelDetails.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index ea9308eb09d..bc6b2dd4cd2 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -183,8 +183,11 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }; osparc.data.Resources.fetch("pricingPlans", "getOne", params) .then(data => { - const pricingUnits = data["pricingUnits"]; - pricingUnits.forEach(pricingUnit => { + const pricingUnitsData = data["pricingUnits"]; + pricingUnitsData.forEach(pricingUnitData => { + const pricingUnit = new osparc.data.model.PricingUnit(pricingUnitData).set({ + classification: "LICENSE" + }); const pUnit = new osparc.study.PricingUnitLicense(pricingUnit).set({ allowGrowY: false, }); @@ -198,7 +201,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }, __rentAnatomicalModel: function(anatomicalModelsData, pricingUnit) { - console.log(":purchase", anatomicalModelsData["licensedItemId"], pricingUnit["pricingUnitId"]); + console.log(":purchase", anatomicalModelsData["licensedItemId"], pricingUnit.getPricingUnitId()); }, } }); From 0c7a0e9b9b3a83030e8fbc9cfdae423ebeb1511f Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 14:08:01 +0100 Subject: [PATCH 023/102] defaults --- .../source/class/osparc/study/PricingUnitLicense.js | 2 +- .../source/class/osparc/study/PricingUnitTier.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js index 348a48a01b4..8ae473577dc 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js @@ -25,7 +25,7 @@ qx.Class.define("osparc.study.PricingUnitLicense", { properties: { showRentButton: { check: "Boolean", - init: false, + init: true, nullable: true, event: "changeShowRentButton" }, diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js index a3d631004a1..15218ef0d6c 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js @@ -20,6 +20,7 @@ qx.Class.define("osparc.study.PricingUnitTier", { events: { "editPricingUnit": "qx.event.type.Event", + "selectPricingUnit": "qx.event.type.Event", }, properties: { @@ -66,6 +67,10 @@ qx.Class.define("osparc.study.PricingUnitTier", { control = new qx.ui.form.Button(qx.locale.Manager.tr("Edit")); this._add(control); break; + case "select-button": + control = new qx.ui.form.Button(qx.locale.Manager.tr("Select")); + this._add(control); + break; } return control || this.base(arguments, id); }, @@ -108,6 +113,13 @@ qx.Class.define("osparc.study.PricingUnitTier", { converter: show => show ? "visible" : "excluded" }) editButton.addListener("execute", () => this.fireEvent("editPricingUnit")); + + // add select button + const selectButton = this.getChildControl("edit-button"); + this.bind("showSelectButton", selectButton, "visibility", { + converter: show => show ? "visible" : "excluded" + }) + selectButton.addListener("execute", () => this.fireEvent("selectPricingUnit")); } } }); From bc7cea93f40be7dc7900ecd182f89dc8c49eba41 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 14:14:29 +0100 Subject: [PATCH 024/102] [skip ci] step by step --- .../source/class/osparc/node/TierSelectionView.js | 1 + .../client/source/class/osparc/pricing/UnitsList.js | 5 ++++- .../source/class/osparc/study/PricingUnitTier.js | 12 ------------ .../class/osparc/vipMarket/AnatomicalModelDetails.js | 1 + 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js b/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js index cdb696a4da4..c9fa6d26c14 100644 --- a/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js +++ b/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js @@ -86,6 +86,7 @@ qx.Class.define("osparc.node.TierSelectionView", { const pUnitUIs = []; pricingUnits.forEach(pricingUnit => { const pUnitUI = new osparc.study.PricingUnitTier(pricingUnit).set({ + showEditButton: false, allowGrowX: false }); pUnitUI.getChildControl("name").exclude(); diff --git a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js index 41ac7b53940..6601ec71237 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js +++ b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js @@ -77,10 +77,13 @@ qx.Class.define("osparc.pricing.UnitsList", { pricingUnits.forEach(pricingUnit => { let pUnit = null; if (pricingUnit.getClassification() === "LICENSE") { - pUnit = new osparc.study.PricingUnitLicense(pricingUnit); + pUnit = new osparc.study.PricingUnitLicense(pricingUnit).set({ + showRentButton: false, + }); } else { pUnit = new osparc.study.PricingUnitTier(pricingUnit).set({ showAwsSpecificInfo: true, + showEditButton: true, }); } pUnit.set({ diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js index 15218ef0d6c..a3d631004a1 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js @@ -20,7 +20,6 @@ qx.Class.define("osparc.study.PricingUnitTier", { events: { "editPricingUnit": "qx.event.type.Event", - "selectPricingUnit": "qx.event.type.Event", }, properties: { @@ -67,10 +66,6 @@ qx.Class.define("osparc.study.PricingUnitTier", { control = new qx.ui.form.Button(qx.locale.Manager.tr("Edit")); this._add(control); break; - case "select-button": - control = new qx.ui.form.Button(qx.locale.Manager.tr("Select")); - this._add(control); - break; } return control || this.base(arguments, id); }, @@ -113,13 +108,6 @@ qx.Class.define("osparc.study.PricingUnitTier", { converter: show => show ? "visible" : "excluded" }) editButton.addListener("execute", () => this.fireEvent("editPricingUnit")); - - // add select button - const selectButton = this.getChildControl("edit-button"); - this.bind("showSelectButton", selectButton, "visibility", { - converter: show => show ? "visible" : "excluded" - }) - selectButton.addListener("execute", () => this.fireEvent("selectPricingUnit")); } } }); diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index bc6b2dd4cd2..88e912f2849 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -189,6 +189,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { classification: "LICENSE" }); const pUnit = new osparc.study.PricingUnitLicense(pricingUnit).set({ + showRentButton: true, allowGrowY: false, }); pUnit.addListener("rentPricingUnit", () => this.__rentAnatomicalModel(anatomicalModelsData, pricingUnit)); From 9e4b9aaf66d43f75c53f3173beb78f63b409e4ec Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 14:17:59 +0100 Subject: [PATCH 025/102] edit to root --- .../client/source/class/osparc/pricing/UnitsList.js | 1 + .../client/source/class/osparc/study/PricingUnit.js | 11 +++++++++++ .../source/class/osparc/study/PricingUnitLicense.js | 9 ++++++++- .../source/class/osparc/study/PricingUnitTier.js | 11 ----------- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js index 6601ec71237..3bf292e8e58 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js +++ b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js @@ -78,6 +78,7 @@ qx.Class.define("osparc.pricing.UnitsList", { let pUnit = null; if (pricingUnit.getClassification() === "LICENSE") { pUnit = new osparc.study.PricingUnitLicense(pricingUnit).set({ + showEditButton: true, showRentButton: false, }); } else { diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js index 9337ccbd96a..f57d78d66d8 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js @@ -38,6 +38,13 @@ qx.Class.define("osparc.study.PricingUnit", { init: null, apply: "_buildLayout" }, + + showEditButton: { + check: "Boolean", + init: false, + nullable: true, + event: "changeShowEditButton" + }, }, members: { @@ -56,6 +63,10 @@ qx.Class.define("osparc.study.PricingUnit", { }); this._add(control); break; + case "edit-button": + control = new qx.ui.form.Button(qx.locale.Manager.tr("Edit")); + this._add(control); + break; } return control || this.base(arguments, id); }, diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js index 8ae473577dc..ffced18205d 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js @@ -25,7 +25,7 @@ qx.Class.define("osparc.study.PricingUnitLicense", { properties: { showRentButton: { check: "Boolean", - init: true, + init: false, nullable: true, event: "changeShowRentButton" }, @@ -56,6 +56,13 @@ qx.Class.define("osparc.study.PricingUnitLicense", { converter: v => qx.locale.Manager.tr("Credits") + ": " + v }); + // add edit button + const editButton = this.getChildControl("edit-button"); + this.bind("showEditButton", editButton, "visibility", { + converter: show => show ? "visible" : "excluded" + }) + editButton.addListener("execute", () => this.fireEvent("editPricingUnit")); + // add rent button const rentButton = this.getChildControl("rent-button"); this.bind("showRentButton", rentButton, "visibility", { diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js index a3d631004a1..0dce9ee1d5e 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js @@ -36,13 +36,6 @@ qx.Class.define("osparc.study.PricingUnitTier", { nullable: true, event: "changeShowUnitExtraInfo" }, - - showEditButton: { - check: "Boolean", - init: false, - nullable: true, - event: "changeShowEditButton" - }, }, members: { @@ -62,10 +55,6 @@ qx.Class.define("osparc.study.PricingUnitTier", { }); this._add(control); break; - case "edit-button": - control = new qx.ui.form.Button(qx.locale.Manager.tr("Edit")); - this._add(control); - break; } return control || this.base(arguments, id); }, From 721863b61ea39ca617c90fe6d335f76a19d6cf75 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 15:21:52 +0100 Subject: [PATCH 026/102] Unit Editor --- .../class/osparc/data/model/PricingUnit.js | 10 ++++++++- .../source/class/osparc/pricing/UnitEditor.js | 21 +++++++++++-------- .../source/class/osparc/study/PricingUnit.js | 8 +++++++ .../class/osparc/study/PricingUnitLicense.js | 16 ++++++-------- .../class/osparc/study/PricingUnitTier.js | 19 +++++++---------- 5 files changed, 42 insertions(+), 32 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js b/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js index e52f67c1a1e..91970b854af 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js @@ -34,6 +34,7 @@ qx.Class.define("osparc.data.model.PricingUnit", { cost: parseFloat(pricingUnitData.currentCostPerUnit), isDefault: pricingUnitData.default, extraInfo: pricingUnitData.unitExtraInfo, + specificInfo: pricingUnitData.specificInfo || null, }); }, @@ -77,7 +78,14 @@ qx.Class.define("osparc.data.model.PricingUnit", { check: "Object", nullable: false, init: null, - event: "changeExtraInfo" + event: "changeExtraInfo", + }, + + specificInfo: { + check: "Object", + nullable: true, + init: null, + event: "changeSpecificInfo", }, }, }); diff --git a/services/static-webserver/client/source/class/osparc/pricing/UnitEditor.js b/services/static-webserver/client/source/class/osparc/pricing/UnitEditor.js index a03fdc64d71..f8b7e038ae9 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/UnitEditor.js +++ b/services/static-webserver/client/source/class/osparc/pricing/UnitEditor.js @@ -18,7 +18,7 @@ qx.Class.define("osparc.pricing.UnitEditor", { extend: qx.ui.core.Widget, - construct: function(pricingUnitData) { + construct: function(pricingUnit) { this.base(arguments); this._setLayout(new qx.ui.layout.VBox(10)); @@ -46,16 +46,19 @@ qx.Class.define("osparc.pricing.UnitEditor", { manager.add(specificInfo); manager.add(unitExtraInfo); - if (pricingUnitData) { + if (pricingUnit) { this.set({ - pricingUnitId: pricingUnitData.pricingUnitId, - unitName: pricingUnitData.unitName, - costPerUnit: parseFloat(pricingUnitData.currentCostPerUnit), - comment: pricingUnitData.comment ? pricingUnitData.comment : "", - specificInfo: pricingUnitData.specificInfo && pricingUnitData.specificInfo["aws_ec2_instances"] ? pricingUnitData.specificInfo["aws_ec2_instances"].toString() : "", - default: pricingUnitData.default + pricingUnitId: pricingUnit.getPricingUnitId(), + unitName: pricingUnit.getName(), + costPerUnit: pricingUnit.getCost(), }); - const extraInfo = osparc.utils.Utils.deepCloneObject(pricingUnitData.unitExtraInfo); + if (pricingUnit.getClassification() === "TIER") { + this.set({ + specificInfo: pricingUnit.getSpecificInfo() && pricingUnit.getSpecificInfo()["aws_ec2_instances"] ? pricingUnit.getSpecificInfo()["aws_ec2_instances"].toString() : "", + default: pricingUnit.getIsDefault(), + }); + } + const extraInfo = osparc.utils.Utils.deepCloneObject(pricingUnit.getExtraInfo()); // extract the required fields from the unitExtraInfo this.set({ unitExtraInfoCPU: extraInfo["CPU"], diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js index f57d78d66d8..ce7c2ee456a 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js @@ -31,6 +31,10 @@ qx.Class.define("osparc.study.PricingUnit", { this.setUnitData(pricingUnit); }, + events: { + "editPricingUnit": "qx.event.type.Event", + }, + properties: { unitData: { check: "osparc.data.model.PricingUnit", @@ -65,6 +69,10 @@ qx.Class.define("osparc.study.PricingUnit", { break; case "edit-button": control = new qx.ui.form.Button(qx.locale.Manager.tr("Edit")); + this.bind("showEditButton", control, "visibility", { + converter: show => show ? "visible" : "excluded" + }); + control.addListener("execute", () => this.fireEvent("editPricingUnit")); this._add(control); break; } diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js index ffced18205d..d7fd9f04d27 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js @@ -40,6 +40,10 @@ qx.Class.define("osparc.study.PricingUnitLicense", { appearance: "strong-button", center: true, }); + this.bind("showRentButton", control, "visibility", { + converter: show => show ? "visible" : "excluded" + }); + control.addListener("execute", () => this.fireEvent("rentPricingUnit")); this._add(control); break; } @@ -57,18 +61,10 @@ qx.Class.define("osparc.study.PricingUnitLicense", { }); // add edit button - const editButton = this.getChildControl("edit-button"); - this.bind("showEditButton", editButton, "visibility", { - converter: show => show ? "visible" : "excluded" - }) - editButton.addListener("execute", () => this.fireEvent("editPricingUnit")); + this.getChildControl("edit-button"); // add rent button - const rentButton = this.getChildControl("rent-button"); - this.bind("showRentButton", rentButton, "visibility", { - converter: show => show ? "visible" : "excluded" - }) - rentButton.addListener("execute", () => this.fireEvent("rentPricingUnit")); + this.getChildControl("rent-button"); } } }); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js index 0dce9ee1d5e..93e4b73d037 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js @@ -19,7 +19,6 @@ qx.Class.define("osparc.study.PricingUnitTier", { extend: osparc.study.PricingUnit, events: { - "editPricingUnit": "qx.event.type.Event", }, properties: { @@ -46,6 +45,9 @@ qx.Class.define("osparc.study.PricingUnitTier", { control = new qx.ui.basic.Label().set({ font: "text-14" }); + this.bind("showAwsSpecificInfo", control, "visibility", { + converter: show => show ? "visible" : "excluded" + }) this._add(control); break; case "unitExtraInfo": @@ -53,6 +55,9 @@ qx.Class.define("osparc.study.PricingUnitTier", { font: "text-13", rich: true, }); + this.bind("showUnitExtraInfo", control, "visibility", { + converter: show => show ? "visible" : "excluded" + }); this._add(control); break; } @@ -75,9 +80,6 @@ qx.Class.define("osparc.study.PricingUnitTier", { pricingUnit.bind("awsSpecificInfo", specificInfo, "value", { converter: v => qx.locale.Manager.tr("EC2") + ": " + v, }); - this.bind("showAwsSpecificInfo", specificInfo, "visibility", { - converter: show => show ? "visible" : "excluded" - }) } // add pricing unit extra info @@ -87,16 +89,9 @@ qx.Class.define("osparc.study.PricingUnitTier", { text += `${key}: ${value}
`; }); unitExtraInfo.setValue(text); - this.bind("showUnitExtraInfo", unitExtraInfo, "visibility", { - converter: show => show ? "visible" : "excluded" - }); // add edit button - const editButton = this.getChildControl("edit-button"); - this.bind("showEditButton", editButton, "visibility", { - converter: show => show ? "visible" : "excluded" - }) - editButton.addListener("execute", () => this.fireEvent("editPricingUnit")); + this.getChildControl("edit-button"); } } }); From 48ca364e8b87fcdcb89b4c904a3d02887de2f395 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 15:36:37 +0100 Subject: [PATCH 027/102] [skip ci] update plan --- .../source/class/osparc/pricing/PlanEditor.js | 18 ++++++++---------- .../source/class/osparc/store/Pricing.js | 13 +++++++------ 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js b/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js index 905e423fa8d..5e89aff7f36 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js +++ b/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js @@ -204,7 +204,7 @@ qx.Class.define("osparc.pricing.PlanEditor", { "pricingPlanKey": ppKey, "displayName": name, "description": description, - "classification": classification + "classification": classification, }; osparc.store.Pricing.getInstance().postPricingPlan(newPricingPlanData) .then(() => { @@ -219,16 +219,14 @@ qx.Class.define("osparc.pricing.PlanEditor", { }, __updatePricingPlan: function() { - this.__pricingPlan["displayName"] = this.getName(); - this.__pricingPlan["description"] = this.getDescription(); - this.__pricingPlan["isActive"] = this.getIsActive(); - const params = { - url: { - "pricingPlanId": this.__pricingPlan["pricingPlanId"] - }, - data: this.__pricingPlan + const updateData = { + "pricingPlanKey": this.getPpKey(), + "displayName": this.getName(), + "description": this.getDescription(), + "classification": this.getClassification(), + "isActive": this.getIsActive(), }; - osparc.data.Resources.fetch("pricingPlans", "update", params) + osparc.store.Pricing.getInstance().putPricingPlan(this.__pricingPlan["pricingPlanId"], updateData) .then(() => { osparc.FlashMessenger.getInstance().logAs(this.tr("Successfully updated")); this.fireEvent("done"); diff --git a/services/static-webserver/client/source/class/osparc/store/Pricing.js b/services/static-webserver/client/source/class/osparc/store/Pricing.js index c57cd74f953..f30d3e2bf4c 100644 --- a/services/static-webserver/client/source/class/osparc/store/Pricing.js +++ b/services/static-webserver/client/source/class/osparc/store/Pricing.js @@ -63,7 +63,7 @@ qx.Class.define("osparc.store.Pricing", { }, data: updateData }; - return osparc.data.Resources.getInstance().fetch("pricingPlans", "put", params) + return osparc.data.Resources.getInstance().fetch("pricingPlans", "update", params) .then(pricingPlanData => { return this.__addToCache(pricingPlanData); }) @@ -102,12 +102,13 @@ qx.Class.define("osparc.store.Pricing", { __addToCache: function(pricingPlanData) { let pricingPlan = this.pricingPlansCached.find(f => f.getPricingPlanId() === pricingPlanData["pricingPlanId"]); if (pricingPlan) { - const props = Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.PricingPlan)); // put - Object.keys(pricingPlanData).forEach(key => { - if (props.includes(key)) { - pricingPlan.set(key, pricingPlanData[key]); - } + pricingPlan.set({ + pricingPlanKey: pricingPlanData["pricingPlanKey"], + name: pricingPlanData["displayName"], + description: pricingPlanData["description"], + classification: pricingPlanData["classification"], + isActive: pricingPlanData["isActive"], }); } else { // get and post From 61443d751d29b03180c69f224ec298e3c1091ee9 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 16:04:13 +0100 Subject: [PATCH 028/102] plan editor ready --- .../source/class/osparc/pricing/PlanEditor.js | 25 +++++++++++++----- .../source/class/osparc/pricing/UnitEditor.js | 26 +++++++++---------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js b/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js index 5e89aff7f36..e5ee877f78a 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js +++ b/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js @@ -75,8 +75,8 @@ qx.Class.define("osparc.pricing.PlanEditor", { }, classification: { - check: "String", - init: "TIER", + check: ["TIER", "LICENSE"], + init: "", nullable: false, event: "changeClassification" }, @@ -132,12 +132,21 @@ qx.Class.define("osparc.pricing.PlanEditor", { break; } case "classification": { - control = new qx.ui.form.TextField().set({ + control = new qx.ui.form.SelectBox().set({ font: "text-14", - enabled: false + }); + [ + "TIER", + "LICENSE", + ].forEach(c => { + const cItem = new qx.ui.form.ListItem(c); + control.add(cItem); }); this.bind("classification", control, "value"); - control.bind("value", this, "classification"); + control.addListener("changeValue", e => { + const currentSelection = e.getData(); + this.setClassification(currentSelection.getLabel()); + }, this); this._add(control); break; } @@ -212,7 +221,8 @@ qx.Class.define("osparc.pricing.PlanEditor", { this.fireEvent("done"); }) .catch(err => { - osparc.FlashMessenger.getInstance().logAs(this.tr("Something went wrong creating ") + name, "ERROR"); + const errorMsg = err.message || this.tr("Something went wrong creating ") + name; + osparc.FlashMessenger.getInstance().logAs(errorMsg, "ERROR"); console.error(err); }) .finally(() => this.getChildControl("create").setFetching(false)); @@ -232,7 +242,8 @@ qx.Class.define("osparc.pricing.PlanEditor", { this.fireEvent("done"); }) .catch(err => { - osparc.FlashMessenger.getInstance().logAs(this.tr("Something went wrong"), "ERROR"); + const errorMsg = err.message || this.tr("Something went wrong"); + osparc.FlashMessenger.getInstance().logAs(errorMsg, "ERROR"); console.error(err); }) .finally(() => this.getChildControl("save").setFetching(false)); diff --git a/services/static-webserver/client/source/class/osparc/pricing/UnitEditor.js b/services/static-webserver/client/source/class/osparc/pricing/UnitEditor.js index f8b7e038ae9..26469666570 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/UnitEditor.js +++ b/services/static-webserver/client/source/class/osparc/pricing/UnitEditor.js @@ -57,20 +57,20 @@ qx.Class.define("osparc.pricing.UnitEditor", { specificInfo: pricingUnit.getSpecificInfo() && pricingUnit.getSpecificInfo()["aws_ec2_instances"] ? pricingUnit.getSpecificInfo()["aws_ec2_instances"].toString() : "", default: pricingUnit.getIsDefault(), }); + const extraInfo = osparc.utils.Utils.deepCloneObject(pricingUnit.getExtraInfo()); + // extract the required fields from the unitExtraInfo + this.set({ + unitExtraInfoCPU: extraInfo["CPU"], + unitExtraInfoRAM: extraInfo["RAM"], + unitExtraInfoVRAM: extraInfo["VRAM"] + }); + delete extraInfo["CPU"]; + delete extraInfo["RAM"]; + delete extraInfo["VRAM"]; + this.set({ + unitExtraInfo: extraInfo + }); } - const extraInfo = osparc.utils.Utils.deepCloneObject(pricingUnit.getExtraInfo()); - // extract the required fields from the unitExtraInfo - this.set({ - unitExtraInfoCPU: extraInfo["CPU"], - unitExtraInfoRAM: extraInfo["RAM"], - unitExtraInfoVRAM: extraInfo["VRAM"] - }); - delete extraInfo["CPU"]; - delete extraInfo["RAM"]; - delete extraInfo["VRAM"]; - this.set({ - unitExtraInfo: extraInfo - }); this.getChildControl("save"); } else { this.getChildControl("create"); From 993487d64b2026052e05400356db1f1d9db28f9c Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 16:33:27 +0100 Subject: [PATCH 029/102] [skip ci] refactor --- .../class/osparc/node/TierSelectionView.js | 1 - .../client/source/class/osparc/store/Pricing.js | 2 +- .../source/class/osparc/study/PricingUnit.js | 2 ++ .../source/class/osparc/study/PricingUnitTier.js | 2 +- .../osparc/vipMarket/AnatomicalModelDetails.js | 16 ++++------------ 5 files changed, 8 insertions(+), 15 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js b/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js index c9fa6d26c14..0340c6aaedf 100644 --- a/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js +++ b/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js @@ -87,7 +87,6 @@ qx.Class.define("osparc.node.TierSelectionView", { pricingUnits.forEach(pricingUnit => { const pUnitUI = new osparc.study.PricingUnitTier(pricingUnit).set({ showEditButton: false, - allowGrowX: false }); pUnitUI.getChildControl("name").exclude(); pUnitUI.exclude(); diff --git a/services/static-webserver/client/source/class/osparc/store/Pricing.js b/services/static-webserver/client/source/class/osparc/store/Pricing.js index f30d3e2bf4c..275599bebf2 100644 --- a/services/static-webserver/client/source/class/osparc/store/Pricing.js +++ b/services/static-webserver/client/source/class/osparc/store/Pricing.js @@ -78,8 +78,8 @@ qx.Class.define("osparc.store.Pricing", { }; return osparc.data.Resources.fetch("pricingPlans", "getOne", params) .then(pricingPlanData => { + const pricingPlan = this.__addToCache(pricingPlanData); const pricingUnits = []; - const pricingPlan = this.getPricingPlan(pricingPlanId); if (pricingPlan && "pricingUnits" in pricingPlanData) { const pricingUnitsData = pricingPlanData["pricingUnits"]; pricingUnitsData.forEach(pricingUnitData => { diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js index ce7c2ee456a..54a3a83be56 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js @@ -26,6 +26,8 @@ qx.Class.define("osparc.study.PricingUnit", { padding: 10, center: true, decorator: "rounded", + allowGrowX: false, + allowGrowY: false, }); this.setUnitData(pricingUnit); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js index 93e4b73d037..53bee23f010 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js @@ -24,7 +24,7 @@ qx.Class.define("osparc.study.PricingUnitTier", { properties: { showAwsSpecificInfo: { check: "Boolean", - init: null, + init: false, nullable: true, event: "changeShowAwsSpecificInfo" }, diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 88e912f2849..a70d08291fe 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -171,26 +171,18 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }, __createPricingUnits: function(anatomicalModelsData) { - console.log(anatomicalModelsData); const pricingUnitsLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5).set({ alignX: "center" })); - const params = { - url: { - pricingPlanId: anatomicalModelsData["pricingPlanId"] - } - }; - osparc.data.Resources.fetch("pricingPlans", "getOne", params) - .then(data => { - const pricingUnitsData = data["pricingUnits"]; - pricingUnitsData.forEach(pricingUnitData => { - const pricingUnit = new osparc.data.model.PricingUnit(pricingUnitData).set({ + osparc.store.Pricing.getInstance().fetchPricingUnits(anatomicalModelsData["pricingPlanId"]) + .then(pricingUnits => { + pricingUnits.forEach(pricingUnit => { + pricingUnit.set({ classification: "LICENSE" }); const pUnit = new osparc.study.PricingUnitLicense(pricingUnit).set({ showRentButton: true, - allowGrowY: false, }); pUnit.addListener("rentPricingUnit", () => this.__rentAnatomicalModel(anatomicalModelsData, pricingUnit)); pricingUnitsLayout.add(pUnit); From 829eae9e3e4d1b6a49c4e5554a48e7c37dcf4c69 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Mon, 9 Dec 2024 16:42:39 +0100 Subject: [PATCH 030/102] db layer licensed items purchase --- .env-devel | 2 +- ...source_tracker_licensed_items_purchases.py | 4 + ...source_tracker_licensed_items_purchases.py | 18 ++- .../exceptions/errors.py | 7 + .../models/licensed_items_purchases.py | 45 ++++++ .../modules/db/licensed_items_purchases.py | 140 ++++++++++++++++++ .../tests/unit/isolated/test_tracing.py | 6 +- 7 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 packages/models-library/src/models_library/resource_tracker_licensed_items_purchases.py create mode 100644 services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_purchases.py create mode 100644 services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases.py diff --git a/.env-devel b/.env-devel index cc6609460da..1683e998509 100644 --- a/.env-devel +++ b/.env-devel @@ -389,6 +389,6 @@ WEBSERVER_SOCKETIO=1 WEBSERVER_STATICWEB={} WEBSERVER_STUDIES_DISPATCHER={} WEBSERVER_TAGS=1 -WEBSERVER_TRACING=null +WEBSERVER_TRACING={} WEBSERVER_USERS={} WEBSERVER_VERSION_CONTROL=1 diff --git a/packages/models-library/src/models_library/resource_tracker_licensed_items_purchases.py b/packages/models-library/src/models_library/resource_tracker_licensed_items_purchases.py new file mode 100644 index 00000000000..e5394b019d4 --- /dev/null +++ b/packages/models-library/src/models_library/resource_tracker_licensed_items_purchases.py @@ -0,0 +1,4 @@ +from typing import TypeAlias +from uuid import UUID + +LicensedItemPurchaseID: TypeAlias = UUID diff --git a/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_purchases.py b/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_purchases.py index 2a13e3d718e..43cf052eb7b 100644 --- a/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_purchases.py +++ b/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_purchases.py @@ -5,7 +5,7 @@ import sqlalchemy as sa from sqlalchemy.dialects.postgresql import UUID -from ._common import column_modified_datetime +from ._common import NUMERIC_KWARGS, column_modified_datetime from .base import metadata resource_tracker_licensed_items_purchases = sa.Table( @@ -34,6 +34,22 @@ sa.BigInteger, nullable=False, ), + sa.Column( + "wallet_name", + sa.String, + nullable=False, + ), + sa.Column( + "pricing_unit_cost_id", + sa.BigInteger, + nullable=False, + ), + sa.Column( + "pricing_unit_cost", + sa.Numeric(**NUMERIC_KWARGS), # type: ignore + nullable=True, + doc="Pricing unit cost used for billing purposes", + ), sa.Column( "start_at", sa.DateTime(timezone=True), diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py index fe620d99c62..55fde04b0f6 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/exceptions/errors.py @@ -1,4 +1,7 @@ from common_library.errors_classes import OsparcErrorMixin +from models_library.resource_tracker_licensed_items_purchases import ( + LicensedItemPurchaseID, +) class ResourceUsageTrackerBaseError(OsparcErrorMixin, Exception): @@ -68,3 +71,7 @@ class PricingPlanNotFoundForServiceError(RutNotFoundError): msg_template = ( "Pricing plan not found for service key {service_key} version {service_version}" ) + + +class LicensedItemPurchaseNotFoundError(RutNotFoundError): + licensed_item_purchase_id: LicensedItemPurchaseID diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_purchases.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_purchases.py new file mode 100644 index 00000000000..f42690ef347 --- /dev/null +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_purchases.py @@ -0,0 +1,45 @@ +from datetime import datetime +from decimal import Decimal + +from models_library.licensed_items import LicensedItemID +from models_library.products import ProductName +from models_library.resource_tracker import PricingUnitCostId +from models_library.resource_tracker_licensed_items_purchases import ( + LicensedItemPurchaseID, +) +from models_library.users import UserID +from models_library.wallets import WalletID +from pydantic import BaseModel, ConfigDict + + +class LicensedItemsPurchasesDB(BaseModel): + licensed_item_purchase_id: LicensedItemPurchaseID + product_name: ProductName + licensed_item_id: LicensedItemID + wallet_id: WalletID | None + wallet_name: str | None + pricing_unit_cost_id: PricingUnitCostId + pricing_unit_cost: Decimal + start_at: datetime + expire_at: datetime | None + purchased_by_user: UserID + purchased_at: datetime + modified: datetime + + model_config = ConfigDict(from_attributes=True) + + +class CreateLicensedItemsPurchasesDB(BaseModel): + product_name: ProductName + licensed_item_id: LicensedItemID + wallet_id: WalletID | None + wallet_name: str | None + pricing_unit_cost_id: PricingUnitCostId + pricing_unit_cost: Decimal + start_at: datetime + expire_at: datetime | None + purchased_by_user: UserID + purchased_at: datetime + modified: datetime + + model_config = ConfigDict(from_attributes=True) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases.py new file mode 100644 index 00000000000..b84f7311fe0 --- /dev/null +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases.py @@ -0,0 +1,140 @@ +from typing import cast + +import sqlalchemy as sa +from models_library.products import ProductName +from models_library.rest_ordering import OrderBy, OrderDirection +from pydantic import NonNegativeInt +from simcore_postgres_database.models.resource_tracker_licensed_items_purchases import ( + resource_tracker_licensed_items_purchases, +) +from simcore_postgres_database.utils_repos import ( + pass_or_acquire_connection, + transaction_context, +) +from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine + +from ....exceptions.errors import LicensedItemPurchaseNotFoundError +from ....models.licensed_items_purchases import ( + CreateLicensedItemsPurchasesDB, + LicensedItemPurchaseID, + LicensedItemsPurchasesDB, +) + +_SELECTION_ARGS = ( + resource_tracker_licensed_items_purchases.c.licensed_item_purchase_id, + resource_tracker_licensed_items_purchases.c.product_name, + resource_tracker_licensed_items_purchases.c.licensed_item_id, + resource_tracker_licensed_items_purchases.c.wallet_id, + resource_tracker_licensed_items_purchases.c.wallet_name, + resource_tracker_licensed_items_purchases.c.pricing_unit_cost_id, + resource_tracker_licensed_items_purchases.c.pricing_unit_cost, + resource_tracker_licensed_items_purchases.c.start_at, + resource_tracker_licensed_items_purchases.c.expire_at, + resource_tracker_licensed_items_purchases.c.purchased_by_user, + resource_tracker_licensed_items_purchases.c.purchased_at, + resource_tracker_licensed_items_purchases.c.modified, +) + +assert set(LicensedItemsPurchasesDB.model_fields) == { + c.name for c in _SELECTION_ARGS +} # nosec + + +async def create( + engine: AsyncEngine, + connection: AsyncConnection | None = None, + *, + data: CreateLicensedItemsPurchasesDB, +) -> LicensedItemsPurchasesDB: + async with transaction_context(engine, connection) as conn: + result = await conn.stream( + resource_tracker_licensed_items_purchases.insert() + .values( + product_name=data.product_name, + licensed_item_id=data.licensed_item_id, + wallet_id=data.wallet_id, + wallet_name=data.wallet_name, + pricing_unit_cost_id=data.pricing_unit_cost_id, + pricing_unit_cost=data.pricing_unit_cost, + start_at=data.start_at, + expire_at=data.expire_at, + purchased_by_user=data.purchased_by_user, + purchased_at=data.purchased_at, + modified=sa.func.now(), + ) + .returning(*_SELECTION_ARGS) + ) + row = await result.first() + return LicensedItemsPurchasesDB.model_validate(row) + + +async def list_( + engine: AsyncEngine, + connection: AsyncConnection | None = None, + *, + product_name: ProductName, + offset: NonNegativeInt, + limit: NonNegativeInt, + order_by: OrderBy, +) -> tuple[int, list[LicensedItemsPurchasesDB]]: + base_query = ( + sa.select(*_SELECTION_ARGS) + .select_from(resource_tracker_licensed_items_purchases) + .where(resource_tracker_licensed_items_purchases.c.product_name == product_name) + ) + + # Select total count from base_query + subquery = base_query.subquery() + count_query = sa.select(sa.func.count()).select_from(subquery) + + # Ordering and pagination + if order_by.direction == OrderDirection.ASC: + list_query = base_query.order_by( + sa.asc(getattr(resource_tracker_licensed_items_purchases.c, order_by.field)) + ) + else: + list_query = base_query.order_by( + sa.desc( + getattr(resource_tracker_licensed_items_purchases.c, order_by.field) + ) + ) + list_query = list_query.offset(offset).limit(limit) + + async with pass_or_acquire_connection(engine, connection) as conn: + total_count = await conn.scalar(count_query) + + result = await conn.stream(list_query) + items: list[LicensedItemsPurchasesDB] = [ + LicensedItemsPurchasesDB.model_validate(row) async for row in result + ] + + return cast(int, total_count), items + + +async def get( + engine: AsyncEngine, + connection: AsyncConnection | None = None, + *, + licensed_item_purchase_id: LicensedItemPurchaseID, + product_name: ProductName, +) -> LicensedItemsPurchasesDB: + base_query = ( + sa.select(*_SELECTION_ARGS) + .select_from(resource_tracker_licensed_items_purchases) + .where( + ( + resource_tracker_licensed_items_purchases.c.licensed_item_purchase_id + == licensed_item_purchase_id + ) + & (resource_tracker_licensed_items_purchases.c.product_name == product_name) + ) + ) + + async with pass_or_acquire_connection(engine, connection) as conn: + result = await conn.stream(base_query) + row = await result.first() + if row is None: + raise LicensedItemPurchaseNotFoundError( + licensed_item_purchase_id=licensed_item_purchase_id + ) + return LicensedItemsPurchasesDB.model_validate(row) diff --git a/services/web/server/tests/unit/isolated/test_tracing.py b/services/web/server/tests/unit/isolated/test_tracing.py index ddec0d10422..bac02e74a8c 100644 --- a/services/web/server/tests/unit/isolated/test_tracing.py +++ b/services/web/server/tests/unit/isolated/test_tracing.py @@ -18,7 +18,7 @@ def mock_webserver_service_environment( monkeypatch: pytest.MonkeyPatch, mock_webserver_service_environment: EnvVarsDict ) -> EnvVarsDict: - return mock_webserver_service_environment | setenvs_from_dict( + envs = mock_webserver_service_environment | setenvs_from_dict( monkeypatch, { "TRACING_OPENTELEMETRY_COLLECTOR_ENDPOINT": "http://opentelemetry-collector", @@ -26,6 +26,10 @@ def mock_webserver_service_environment( }, ) + envs.pop("WEBSERVER_TRACING") + + return envs + def test_middleware_restrictions_opentelemetry_is_second_middleware( mock_webserver_service_environment: EnvVarsDict, From 973e8b64d0e5c90c03a52f8ba9f1ef9deb9b4f28 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 16:50:23 +0100 Subject: [PATCH 031/102] [skip ci] enh --- .../source/class/osparc/data/model/PricingPlan.js | 9 ++++++++- .../client/source/class/osparc/store/Pricing.js | 14 +++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js b/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js index 8e7a0e0dddd..fe208aef697 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js +++ b/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js @@ -31,12 +31,19 @@ qx.Class.define("osparc.data.model.PricingPlan", { this.set({ pricingPlanId: pricingPlanData.pricingPlanId, pricingPlanKey: pricingPlanData.pricingPlanKey, - pricingUnits: pricingPlanData.pricingUnits || [], classification: pricingPlanData.displayName.includes("ViP") ? "LICENSE" : pricingPlanData.classification, name: pricingPlanData.displayName, description: pricingPlanData.description, isActive: pricingPlanData.isActive, + pricingUnits: [], }); + + if (pricingPlanData.pricingUnits) { + pricingPlanData.pricingUnits.forEach(pricingUnitData => { + const pricingUnit = new osparc.data.model.PricingUnit(pricingUnitData); + this.getPricingUnits().push(pricingUnit); + }); + } }, properties: { diff --git a/services/static-webserver/client/source/class/osparc/store/Pricing.js b/services/static-webserver/client/source/class/osparc/store/Pricing.js index 275599bebf2..f60e4260898 100644 --- a/services/static-webserver/client/source/class/osparc/store/Pricing.js +++ b/services/static-webserver/client/source/class/osparc/store/Pricing.js @@ -79,14 +79,10 @@ qx.Class.define("osparc.store.Pricing", { return osparc.data.Resources.fetch("pricingPlans", "getOne", params) .then(pricingPlanData => { const pricingPlan = this.__addToCache(pricingPlanData); - const pricingUnits = []; - if (pricingPlan && "pricingUnits" in pricingPlanData) { - const pricingUnitsData = pricingPlanData["pricingUnits"]; - pricingUnitsData.forEach(pricingUnitData => { - const pricingUnit = this.__addPricingUnitToCache(pricingPlan, pricingUnitData); - pricingUnits.push(pricingUnit); - }); - } + const pricingUnits = pricingPlan.getPricingUnits(); + pricingUnits.forEach(pricingUnit => { + pricingPlan.bind("classification", pricingUnit, "classification"); + }) return pricingUnits; }); }, @@ -120,7 +116,7 @@ qx.Class.define("osparc.store.Pricing", { __addPricingUnitToCache: function(pricingPlan, pricingUnitData) { const pricingUnits = pricingPlan.getPricingUnits(); - let pricingUnit = pricingUnits ? pricingUnits.find(unit => unit.getPricingUnitId() === pricingUnitData["pricingUnitId"]) : null; + let pricingUnit = pricingUnits ? pricingUnits.find(unit => ("getPricingUnitId" in unit) && unit.getPricingUnitId() === pricingUnitData["pricingUnitId"]) : null; if (pricingUnit) { const props = Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.PricingPlan)); // put From b80e61a094f62864295a749810af1fd49c93fc34 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 17:12:19 +0100 Subject: [PATCH 032/102] [skip ci] list units --- .../client/source/class/osparc/store/Pricing.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/store/Pricing.js b/services/static-webserver/client/source/class/osparc/store/Pricing.js index f60e4260898..2aedc0bc2a3 100644 --- a/services/static-webserver/client/source/class/osparc/store/Pricing.js +++ b/services/static-webserver/client/source/class/osparc/store/Pricing.js @@ -80,9 +80,10 @@ qx.Class.define("osparc.store.Pricing", { .then(pricingPlanData => { const pricingPlan = this.__addToCache(pricingPlanData); const pricingUnits = pricingPlan.getPricingUnits(); - pricingUnits.forEach(pricingUnit => { - pricingPlan.bind("classification", pricingUnit, "classification"); - }) + pricingUnits.length = 0; + pricingPlanData["pricingUnits"].forEach(pricingUnitData => { + this.__addPricingUnitToCache(pricingPlan, pricingUnitData); + }); return pricingUnits; }); }, From 3487e47a30eb5ccb09f1dff93f2bc3d2ccf32277 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 17:17:58 +0100 Subject: [PATCH 033/102] last call to Store --- .../client/source/class/osparc/pricing/PlanEditor.js | 10 +++++----- .../client/source/class/osparc/pricing/Plans.js | 7 +------ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js b/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js index e5ee877f78a..50543ee77e5 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js +++ b/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js @@ -38,11 +38,11 @@ qx.Class.define("osparc.pricing.PlanEditor", { if (pricingPlan) { this.__pricingPlan = osparc.utils.Utils.deepCloneObject(pricingPlan); this.set({ - ppKey: pricingPlan.pricingPlanKey, - name: pricingPlan.displayName, - description: pricingPlan.description, - classification: pricingPlan.classification, - isActive: pricingPlan.isActive + ppKey: pricingPlan.getPricingPlanKey(), + name: pricingPlan.getName(), + description: pricingPlan.getDescription(), + classification: pricingPlan.getClassification(), + isActive: pricingPlan.getIsActive(), }); ppKey.setEnabled(false); this.getChildControl("save"); diff --git a/services/static-webserver/client/source/class/osparc/pricing/Plans.js b/services/static-webserver/client/source/class/osparc/pricing/Plans.js index 9ebef3d3599..7067d418e45 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/Plans.js +++ b/services/static-webserver/client/source/class/osparc/pricing/Plans.js @@ -125,12 +125,7 @@ qx.Class.define("osparc.pricing.Plans", { }, __openUpdatePricingPlan: function(pricingPlanId) { - const params = { - url: { - pricingPlanId - } - } - osparc.data.Resources.fetch("pricingPlans", "getOne", params) + osparc.store.Pricing.getInstance().fetchPricingUnits(pricingPlanId) .then(pricingPlan => { const ppEditor = new osparc.pricing.PlanEditor(pricingPlan); const title = this.tr("Pricing Plan Editor"); From 660d6946cca95893050037f66c5d64636960c95b Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 17:22:50 +0100 Subject: [PATCH 034/102] fetchPricingPlansService --- .../source/class/osparc/service/PricingUnitsList.js | 9 ++------- .../client/source/class/osparc/store/Pricing.js | 10 ++++++++++ .../source/class/osparc/study/NodePricingUnits.js | 9 ++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/service/PricingUnitsList.js b/services/static-webserver/client/source/class/osparc/service/PricingUnitsList.js index 4e3587238b0..40b81e03695 100644 --- a/services/static-webserver/client/source/class/osparc/service/PricingUnitsList.js +++ b/services/static-webserver/client/source/class/osparc/service/PricingUnitsList.js @@ -47,13 +47,8 @@ qx.Class.define("osparc.service.PricingUnitsList", { }, __fetchUnits: function() { - const plansParams = { - url: osparc.data.Resources.getServiceUrl( - this.__serviceMetadata["key"], - this.__serviceMetadata["version"] - ) - }; - osparc.data.Resources.fetch("services", "pricingPlans", plansParams) + const pricingStore = osparc.store.Pricing.getInatance(); + pricingStore.fetchPricingPlansService(this.__serviceMetadata["key"], this.__serviceMetadata["version"]) .then(data => this.__populateList(data["pricingUnits"])) .catch(err => { console.error(err); diff --git a/services/static-webserver/client/source/class/osparc/store/Pricing.js b/services/static-webserver/client/source/class/osparc/store/Pricing.js index 2aedc0bc2a3..bf35811acfe 100644 --- a/services/static-webserver/client/source/class/osparc/store/Pricing.js +++ b/services/static-webserver/client/source/class/osparc/store/Pricing.js @@ -70,6 +70,16 @@ qx.Class.define("osparc.store.Pricing", { .catch(console.error); }, + fetchPricingPlansService: function(serviceKey, serviceVersion) { + const plansParams = { + url: osparc.data.Resources.getServiceUrl(serviceKey, serviceVersion) + }; + return osparc.data.Resources.fetch("services", "pricingPlans", plansParams) + .then(pricingPlansData => { + return pricingPlansData; + }); + }, + fetchPricingUnits: function(pricingPlanId) { const params = { url: { diff --git a/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js b/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js index e158881c57f..e1ab4b2929e 100644 --- a/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js +++ b/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js @@ -100,13 +100,8 @@ qx.Class.define("osparc.study.NodePricingUnits", { const studyId = this.getStudyId(); const nodeId = this.getNodeId(); - const plansParams = { - url: osparc.data.Resources.getServiceUrl( - nodeKey, - nodeVersion - ) - }; - osparc.data.Resources.fetch("services", "pricingPlans", plansParams) + const pricingStore = osparc.store.Pricing.getInatance(); + pricingStore.fetchPricingPlansService(nodeKey, nodeVersion) .then(pricingPlanData => { if (pricingPlanData) { const unitParams = { From d76006c0e7f7e6336f0680c1b6e13d877f723410 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 17:25:22 +0100 Subject: [PATCH 035/102] last call to Store --- .../source/class/osparc/node/TierSelectionView.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js b/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js index 0340c6aaedf..92121065e22 100644 --- a/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js +++ b/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js @@ -46,15 +46,8 @@ qx.Class.define("osparc.node.TierSelectionView", { tiersLayout.add(tierBox); const node = this.getNode(); - const plansParams = { - url: osparc.data.Resources.getServiceUrl( - node.getKey(), - node.getVersion() - ) - }; - const studyId = node.getStudy().getUuid(); - const nodeId = node.getNodeId(); - osparc.data.Resources.fetch("services", "pricingPlans", plansParams) + const pricingStore = osparc.store.Pricing.getInatance(); + pricingStore.fetchPricingPlansService(node.getKey(), node.getVersion()) .then(pricingPlans => { if (pricingPlans && "pricingUnits" in pricingPlans && pricingPlans["pricingUnits"].length) { const pricingUnits = pricingPlans["pricingUnits"].map(princingUnitData => { @@ -65,6 +58,8 @@ qx.Class.define("osparc.node.TierSelectionView", { const tItem = new qx.ui.form.ListItem(pricingUnit.getName(), null, pricingUnit.getPricingUnitId()); tierBox.add(tItem); }); + const studyId = node.getStudy().getUuid(); + const nodeId = node.getNodeId(); const unitParams = { url: { studyId, From ba99088c6b0b4c86d645da8e741e9b01ab8c335c Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 17:27:12 +0100 Subject: [PATCH 036/102] [skip ci] typo --- .../client/source/class/osparc/node/TierSelectionView.js | 2 +- .../client/source/class/osparc/service/PricingUnitsList.js | 2 +- .../client/source/class/osparc/study/NodePricingUnits.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js b/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js index 92121065e22..f23b6077499 100644 --- a/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js +++ b/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js @@ -46,7 +46,7 @@ qx.Class.define("osparc.node.TierSelectionView", { tiersLayout.add(tierBox); const node = this.getNode(); - const pricingStore = osparc.store.Pricing.getInatance(); + const pricingStore = osparc.store.Pricing.getInstance(); pricingStore.fetchPricingPlansService(node.getKey(), node.getVersion()) .then(pricingPlans => { if (pricingPlans && "pricingUnits" in pricingPlans && pricingPlans["pricingUnits"].length) { diff --git a/services/static-webserver/client/source/class/osparc/service/PricingUnitsList.js b/services/static-webserver/client/source/class/osparc/service/PricingUnitsList.js index 40b81e03695..cb138fd43c6 100644 --- a/services/static-webserver/client/source/class/osparc/service/PricingUnitsList.js +++ b/services/static-webserver/client/source/class/osparc/service/PricingUnitsList.js @@ -47,7 +47,7 @@ qx.Class.define("osparc.service.PricingUnitsList", { }, __fetchUnits: function() { - const pricingStore = osparc.store.Pricing.getInatance(); + const pricingStore = osparc.store.Pricing.getInstance(); pricingStore.fetchPricingPlansService(this.__serviceMetadata["key"], this.__serviceMetadata["version"]) .then(data => this.__populateList(data["pricingUnits"])) .catch(err => { diff --git a/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js b/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js index e1ab4b2929e..6bb2d7180d8 100644 --- a/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js +++ b/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js @@ -100,7 +100,7 @@ qx.Class.define("osparc.study.NodePricingUnits", { const studyId = this.getStudyId(); const nodeId = this.getNodeId(); - const pricingStore = osparc.store.Pricing.getInatance(); + const pricingStore = osparc.store.Pricing.getInstance(); pricingStore.fetchPricingPlansService(nodeKey, nodeVersion) .then(pricingPlanData => { if (pricingPlanData) { From 2712dfe792358f66f97cd5dc11758de0cb5cba0b Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 17:28:59 +0100 Subject: [PATCH 037/102] minor --- .../client/source/class/osparc/pricing/UnitsList.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js index 3bf292e8e58..65edb40dcfe 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js +++ b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js @@ -78,17 +78,15 @@ qx.Class.define("osparc.pricing.UnitsList", { let pUnit = null; if (pricingUnit.getClassification() === "LICENSE") { pUnit = new osparc.study.PricingUnitLicense(pricingUnit).set({ - showEditButton: true, showRentButton: false, }); } else { pUnit = new osparc.study.PricingUnitTier(pricingUnit).set({ showAwsSpecificInfo: true, - showEditButton: true, }); } pUnit.set({ - allowGrowY: false + showEditButton: true, }); pUnit.addListener("editPricingUnit", () => this.__openUpdatePricingUnit(pricingUnit)); this.getChildControl("pricing-units-container").add(pUnit); From 4c0e3c53a4fac905148da74cabb9d70946b02470 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 9 Dec 2024 17:49:14 +0100 Subject: [PATCH 038/102] Select button --- .../class/osparc/study/PricingUnitTier.js | 31 +++++++++++++++++++ .../source/class/osparc/study/PricingUnits.js | 23 +++++++++----- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js index 53bee23f010..85d51c1a88d 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js @@ -19,6 +19,7 @@ qx.Class.define("osparc.study.PricingUnitTier", { extend: osparc.study.PricingUnit, events: { + "selectPricingUnit": "qx.event.type.Event", }, properties: { @@ -35,6 +36,13 @@ qx.Class.define("osparc.study.PricingUnitTier", { nullable: true, event: "changeShowUnitExtraInfo" }, + + showSelectButton: { + check: "Boolean", + init: false, + nullable: true, + event: "changeShowSelectButton" + }, }, members: { @@ -60,6 +68,26 @@ qx.Class.define("osparc.study.PricingUnitTier", { }); this._add(control); break; + case "select-button": + control = new qx.ui.form.Button().set({ + appearance: "strong-button", + center: true, + }); + this.bind("value", control, "label", { + converter: value => value ? "Selected" : "Select" + }); + this.bind("value", control, "enabled", { + converter: value => !value + }); + this.bind("showSelectButton", control, "visibility", { + converter: show => show ? "visible" : "excluded" + }); + control.addListener("execute", () => { + this.setValue(true); + this.fireEvent("selectPricingUnit"); + }); + this._add(control); + break; } return control || this.base(arguments, id); }, @@ -90,6 +118,9 @@ qx.Class.define("osparc.study.PricingUnitTier", { }); unitExtraInfo.setValue(text); + // add select button + this.getChildControl("select-button"); + // add edit button this.getChildControl("edit-button"); } diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnits.js b/services/static-webserver/client/source/class/osparc/study/PricingUnits.js index 9cc451a30e3..5f626f7cce8 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnits.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnits.js @@ -43,7 +43,9 @@ qx.Class.define("osparc.study.PricingUnits", { const buttons = []; pricingUnitsData.forEach(pricingUnitData => { const pricingUnit = new osparc.data.model.PricingUnit(pricingUnitData); - const button = new osparc.study.PricingUnitTier(pricingUnit); + const button = new osparc.study.PricingUnitTier(pricingUnit).set({ + showSelectButton: changeSelectionAllowed, + }); buttons.push(button); this._add(button); }); @@ -74,13 +76,18 @@ qx.Class.define("osparc.study.PricingUnits", { if (!changeSelectionAllowed) { button.setCursor("default"); } - button.addListener("execute", () => { - if (changeSelectionAllowed) { - const selectedUnitId = button.getUnitData().getPricingUnitId(); - this.setSelectedUnitId(selectedUnitId); - } else { - buttons.forEach(btn => btn.setValue(btn.getUnitData().getIsDefault())); - } + [ + "execute", + "selectPricingUnit", + ].forEach(ev => { + button.addListener(ev, () => { + if (changeSelectionAllowed) { + const selectedUnitId = button.getUnitData().getPricingUnitId(); + this.setSelectedUnitId(selectedUnitId); + } else { + buttons.forEach(btn => btn.setValue(btn.getUnitData().getIsDefault())); + } + }); }); }); } From a975a63369800b40d5303878f91136f8d4c1e6b6 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 10 Dec 2024 08:32:58 +0100 Subject: [PATCH 039/102] rut part --- .env-devel | 2 +- .../licensed_items_purchases.py | 55 +++++++ ...source_tracker_licensed_items_purchases.py | 26 ++++ ...7_add_cols_to_licensed_items_purchases_.py | 45 ++++++ ...source_tracker_licensed_items_purchases.py | 5 + .../api/rpc/_licensed_items.py | 60 ++++++++ .../api/rpc/routes.py | 3 +- .../models/licensed_items_purchases.py | 15 +- .../services/licensed_items_purchases.py | 134 ++++++++++++++++++ ...ases.py => licensed_items_purchases_db.py} | 12 +- .../catalog/licenses/_models.py | 2 +- 11 files changed, 348 insertions(+), 11 deletions(-) create mode 100644 packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py create mode 100644 packages/postgres-database/src/simcore_postgres_database/migration/versions/8fa15c4c3977_add_cols_to_licensed_items_purchases_.py create mode 100644 services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items.py create mode 100644 services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_purchases.py rename services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/{licensed_items_purchases.py => licensed_items_purchases_db.py} (92%) diff --git a/.env-devel b/.env-devel index 1683e998509..cc6609460da 100644 --- a/.env-devel +++ b/.env-devel @@ -389,6 +389,6 @@ WEBSERVER_SOCKETIO=1 WEBSERVER_STATICWEB={} WEBSERVER_STUDIES_DISPATCHER={} WEBSERVER_TAGS=1 -WEBSERVER_TRACING={} +WEBSERVER_TRACING=null WEBSERVER_USERS={} WEBSERVER_VERSION_CONTROL=1 diff --git a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py new file mode 100644 index 00000000000..69c3c64c440 --- /dev/null +++ b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py @@ -0,0 +1,55 @@ +from datetime import datetime +from decimal import Decimal + +from models_library.licensed_items import LicensedItemID +from models_library.products import ProductName +from models_library.resource_tracker import PricingUnitCostId +from models_library.resource_tracker_licensed_items_purchases import ( + LicensedItemPurchaseID, +) +from models_library.users import UserID +from models_library.wallets import WalletID +from pydantic import BaseModel, ConfigDict, PositiveInt + + +class LicensedItemPurchaseGet(BaseModel): + licensed_item_purchase_id: LicensedItemPurchaseID + product_name: ProductName + licensed_item_id: LicensedItemID + wallet_id: WalletID | None + wallet_name: str | None + pricing_unit_cost_id: PricingUnitCostId + pricing_unit_cost: Decimal + start_at: datetime + expire_at: datetime + num_of_seats: int + purchased_by_user: UserID + purchased_at: datetime + modified: datetime + + model_config = ConfigDict( + json_schema_extra={ + "examples": [ + { + "licensed_item_purchase_id": 1, + "product_name": "osparc", + "licensed_item_id": "Special Pricing Plan for Sleeper", + "wallet_id": 1, + "wallet_name": "My Wallet", + "pricing_unit_cost_id": 1, + "pricing_unit_cost": Decimal(10), + "start_at": "2023-01-11 13:11:47.293595", + "expire_at": "2023-01-11 13:11:47.293595", + "num_of_seats": 1, + "purchased_by_user": 1, + "purchased_at": "2023-01-11 13:11:47.293595", + "modified": "2023-01-11 13:11:47.293595", + } # type: ignore[index,union-attr] + ] + } + ) + + +class LicensedItemsPurchasesPage(NamedTuple): + items: list[LicensedItemPurchaseGet] + total: PositiveInt diff --git a/packages/models-library/src/models_library/resource_tracker_licensed_items_purchases.py b/packages/models-library/src/models_library/resource_tracker_licensed_items_purchases.py index e5394b019d4..d1ab2d88dc8 100644 --- a/packages/models-library/src/models_library/resource_tracker_licensed_items_purchases.py +++ b/packages/models-library/src/models_library/resource_tracker_licensed_items_purchases.py @@ -1,4 +1,30 @@ +from datetime import datetime +from decimal import Decimal from typing import TypeAlias from uuid import UUID +from pydantic import BaseModel, ConfigDict + +from .licensed_items import LicensedItemID +from .products import ProductName +from .resource_tracker import PricingUnitCostId +from .users import UserID +from .wallets import WalletID + LicensedItemPurchaseID: TypeAlias = UUID + + +class LicensedItemsPurchasesCreate(BaseModel): + product_name: ProductName + licensed_item_id: LicensedItemID + wallet_id: WalletID + wallet_name: str + pricing_unit_cost_id: PricingUnitCostId + pricing_unit_cost: Decimal + start_at: datetime + expire_at: datetime + num_of_seats: int + purchased_by_user: UserID + purchased_at: datetime + + model_config = ConfigDict(from_attributes=True) diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/8fa15c4c3977_add_cols_to_licensed_items_purchases_.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/8fa15c4c3977_add_cols_to_licensed_items_purchases_.py new file mode 100644 index 00000000000..39f2ba32ea3 --- /dev/null +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/8fa15c4c3977_add_cols_to_licensed_items_purchases_.py @@ -0,0 +1,45 @@ +"""add cols to licensed_items_purchases table + +Revision ID: 8fa15c4c3977 +Revises: 38c9ac332c58 +Create Date: 2024-12-10 06:42:23.319239+00:00 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "8fa15c4c3977" +down_revision = "38c9ac332c58" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "resource_tracker_licensed_items_purchases", + sa.Column("wallet_name", sa.String(), nullable=False), + ) + op.add_column( + "resource_tracker_licensed_items_purchases", + sa.Column("pricing_unit_cost_id", sa.BigInteger(), nullable=False), + ) + op.add_column( + "resource_tracker_licensed_items_purchases", + sa.Column("pricing_unit_cost", sa.Numeric(scale=2), nullable=True), + ) + op.add_column( + "resource_tracker_licensed_items_purchases", + sa.Column("num_of_seats", sa.SmallInteger(), nullable=False), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("resource_tracker_licensed_items_purchases", "num_of_seats") + op.drop_column("resource_tracker_licensed_items_purchases", "pricing_unit_cost") + op.drop_column("resource_tracker_licensed_items_purchases", "pricing_unit_cost_id") + op.drop_column("resource_tracker_licensed_items_purchases", "wallet_name") + # ### end Alembic commands ### diff --git a/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_purchases.py b/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_purchases.py index 43cf052eb7b..c5c3e2b57ec 100644 --- a/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_purchases.py +++ b/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_purchases.py @@ -62,6 +62,11 @@ nullable=False, server_default=sa.sql.func.now(), ), + sa.Column( + "num_of_seats", + sa.SmallInteger, + nullable=False, + ), sa.Column( "purchased_by_user", sa.BigInteger, diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items.py new file mode 100644 index 00000000000..c835848b219 --- /dev/null +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items.py @@ -0,0 +1,60 @@ +from fastapi import FastAPI +from models_library.api_schemas_resource_usage_tracker.licensed_items_purchases import ( + LicensedItemPurchaseGet, + LicensedItemsPurchasesPage, +) +from models_library.products import ProductName +from models_library.resource_tracker_licensed_items_purchases import ( + LicensedItemPurchaseID, + LicensedItemsPurchasesCreate, +) +from models_library.rest_ordering import OrderBy +from models_library.wallets import WalletID +from servicelib.rabbitmq import RPCRouter + +from ...services import licensed_items_purchases + +router = RPCRouter() + + +@router.expose(reraise_if_error_type=()) +async def get_licensed_items_purchases_page( + app: FastAPI, + *, + product_name: ProductName, + wallet_id: WalletID, + offset: int = 0, + limit: int = 20, + order_by: OrderBy = OrderBy(field="purchased_at"), +) -> LicensedItemsPurchasesPage: + return await licensed_items_purchases.list_licensed_items_purchases( + db_engine=app.state.engine, + product_name=product_name, + offset=offset, + limit=limit, + filter_wallet_id=wallet_id, + order_by=order_by, + ) + + +@router.expose(reraise_if_error_type=()) +async def get_licensed_item_purchase( + app: FastAPI, + *, + product_name: ProductName, + licensed_item_purchase_id: LicensedItemPurchaseID, +) -> LicensedItemPurchaseGet: + return await licensed_items_purchases.get_licensed_item_purchase( + db_engine=app.state.engine, + product_name=product_name, + licensed_item_purchase_id=licensed_item_purchase_id, + ) + + +@router.expose(reraise_if_error_type=()) +async def create_licensed_item_purchase( + app: FastAPI, *, data: LicensedItemsPurchasesCreate +) -> LicensedItemPurchaseGet: + return await licensed_items_purchases.create_licensed_item_purchase( + db_engine=app.state.engine, data=data + ) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/routes.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/routes.py index ff2e1cdb0bb..349f86f4a01 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/routes.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/routes.py @@ -8,13 +8,14 @@ from servicelib.rabbitmq import RPCRouter from ...services.modules.rabbitmq import get_rabbitmq_rpc_server -from . import _resource_tracker +from . import _licensed_items, _resource_tracker _logger = logging.getLogger(__name__) ROUTERS: list[RPCRouter] = [ _resource_tracker.router, + _licensed_items.router, ] diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_purchases.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_purchases.py index f42690ef347..4458bd2c258 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_purchases.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/licensed_items_purchases.py @@ -16,12 +16,13 @@ class LicensedItemsPurchasesDB(BaseModel): licensed_item_purchase_id: LicensedItemPurchaseID product_name: ProductName licensed_item_id: LicensedItemID - wallet_id: WalletID | None - wallet_name: str | None + wallet_id: WalletID + wallet_name: str pricing_unit_cost_id: PricingUnitCostId pricing_unit_cost: Decimal start_at: datetime - expire_at: datetime | None + expire_at: datetime + num_of_seats: int purchased_by_user: UserID purchased_at: datetime modified: datetime @@ -32,14 +33,14 @@ class LicensedItemsPurchasesDB(BaseModel): class CreateLicensedItemsPurchasesDB(BaseModel): product_name: ProductName licensed_item_id: LicensedItemID - wallet_id: WalletID | None - wallet_name: str | None + wallet_id: WalletID + wallet_name: str pricing_unit_cost_id: PricingUnitCostId pricing_unit_cost: Decimal start_at: datetime - expire_at: datetime | None + expire_at: datetime + num_of_seats: int purchased_by_user: UserID purchased_at: datetime - modified: datetime model_config = ConfigDict(from_attributes=True) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_purchases.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_purchases.py new file mode 100644 index 00000000000..3e106559b9e --- /dev/null +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_purchases.py @@ -0,0 +1,134 @@ +from typing import Annotated + +from fastapi import Depends +from models_library.api_schemas_resource_usage_tracker.licensed_items_purchases import ( + LicensedItemPurchaseGet, + LicensedItemsPurchasesPage, +) +from models_library.products import ProductName +from models_library.resource_tracker_licensed_items_purchases import ( + LicensedItemPurchaseID, + LicensedItemsPurchasesCreate, +) +from models_library.rest_ordering import OrderBy +from models_library.wallets import WalletID +from sqlalchemy.ext.asyncio import AsyncEngine + +from ..api.rest.dependencies import get_resource_tracker_db_engine +from ..models.licensed_items_purchases import ( + CreateLicensedItemsPurchasesDB, + LicensedItemsPurchasesDB, +) +from .modules.db import licensed_items_purchases_db + + +async def list_licensed_items_purchases( + db_engine: Annotated[AsyncEngine, Depends(get_resource_tracker_db_engine)], + *, + product_name: ProductName, + filter_wallet_id: WalletID, + offset: int = 0, + limit: int = 20, + order_by: OrderBy, +) -> LicensedItemsPurchasesPage: + total, licensed_items_purchases_list_db = await licensed_items_purchases_db.list_( + db_engine, + product_name=product_name, + filter_wallet_id=filter_wallet_id, + offset=offset, + limit=limit, + order_by=order_by, + ) + return LicensedItemsPurchasesPage( + total=total, + items=[ + LicensedItemPurchaseGet( + licensed_item_purchase_id=licensed_item_purchase_db.licensed_item_purchase_id, + product_name=licensed_item_purchase_db.product_name, + licensed_item_id=licensed_item_purchase_db.licensed_item_id, + wallet_id=licensed_item_purchase_db.wallet_id, + wallet_name=licensed_item_purchase_db.wallet_name, + pricing_unit_cost_id=licensed_item_purchase_db.pricing_unit_cost_id, + pricing_unit_cost=licensed_item_purchase_db.pricing_unit_cost, + start_at=licensed_item_purchase_db.start_at, + expire_at=licensed_item_purchase_db.expire_at, + num_of_seats=licensed_item_purchase_db.num_of_seats, + purchased_by_user=licensed_item_purchase_db.purchased_by_user, + purchased_at=licensed_item_purchase_db.purchased_at, + modified=licensed_item_purchase_db.modified, + ) + for licensed_item_purchase_db in licensed_items_purchases_list_db + ], + ) + + +async def get_licensed_item_purchase( + db_engine: Annotated[AsyncEngine, Depends(get_resource_tracker_db_engine)], + *, + product_name: ProductName, + licensed_item_purchase_id: LicensedItemPurchaseID, +) -> LicensedItemPurchaseGet: + licensed_item_purchase_db: LicensedItemsPurchasesDB = ( + await licensed_items_purchases_db.get( + db_engine, + product_name=product_name, + licensed_item_purchase_id=licensed_item_purchase_id, + ) + ) + + return LicensedItemPurchaseGet( + licensed_item_purchase_id=licensed_item_purchase_db.licensed_item_purchase_id, + product_name=licensed_item_purchase_db.product_name, + licensed_item_id=licensed_item_purchase_db.licensed_item_id, + wallet_id=licensed_item_purchase_db.wallet_id, + wallet_name=licensed_item_purchase_db.wallet_name, + pricing_unit_cost_id=licensed_item_purchase_db.pricing_unit_cost_id, + pricing_unit_cost=licensed_item_purchase_db.pricing_unit_cost, + start_at=licensed_item_purchase_db.start_at, + expire_at=licensed_item_purchase_db.expire_at, + num_of_seats=licensed_item_purchase_db.num_of_seats, + purchased_by_user=licensed_item_purchase_db.purchased_by_user, + purchased_at=licensed_item_purchase_db.purchased_at, + modified=licensed_item_purchase_db.modified, + ) + + +async def create_licensed_item_purchase( + db_engine: Annotated[AsyncEngine, Depends(get_resource_tracker_db_engine)], + *, + data: LicensedItemsPurchasesCreate, +) -> LicensedItemPurchaseGet: + + _create_db_data = CreateLicensedItemsPurchasesDB( + product_name=data.product_name, + licensed_item_id=data.licensed_item_id, + wallet_id=data.wallet_id, + wallet_name=data.wallet_name, + pricing_unit_cost_id=data.pricing_unit_cost_id, + pricing_unit_cost=data.pricing_unit_cost, + start_at=data.start_at, + expire_at=data.expire_at, + num_of_seats=data.num_of_seats, + purchased_by_user=data.purchased_by_user, + purchased_at=data.purchased_at, + ) + + licensed_item_purchase_db: LicensedItemsPurchasesDB = ( + await licensed_items_purchases_db.create(db_engine, data=_create_db_data) + ) + + return LicensedItemPurchaseGet( + licensed_item_purchase_id=licensed_item_purchase_db.licensed_item_purchase_id, + product_name=licensed_item_purchase_db.product_name, + licensed_item_id=licensed_item_purchase_db.licensed_item_id, + wallet_id=licensed_item_purchase_db.wallet_id, + wallet_name=licensed_item_purchase_db.wallet_name, + pricing_unit_cost_id=licensed_item_purchase_db.pricing_unit_cost_id, + pricing_unit_cost=licensed_item_purchase_db.pricing_unit_cost, + start_at=licensed_item_purchase_db.start_at, + expire_at=licensed_item_purchase_db.expire_at, + num_of_seats=licensed_item_purchase_db.num_of_seats, + purchased_by_user=licensed_item_purchase_db.purchased_by_user, + purchased_at=licensed_item_purchase_db.purchased_at, + modified=licensed_item_purchase_db.modified, + ) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py similarity index 92% rename from services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases.py rename to services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py index b84f7311fe0..e507da9f7e2 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py @@ -3,6 +3,7 @@ import sqlalchemy as sa from models_library.products import ProductName from models_library.rest_ordering import OrderBy, OrderDirection +from models_library.wallets import WalletID from pydantic import NonNegativeInt from simcore_postgres_database.models.resource_tracker_licensed_items_purchases import ( resource_tracker_licensed_items_purchases, @@ -30,6 +31,7 @@ resource_tracker_licensed_items_purchases.c.pricing_unit_cost, resource_tracker_licensed_items_purchases.c.start_at, resource_tracker_licensed_items_purchases.c.expire_at, + resource_tracker_licensed_items_purchases.c.num_of_seats, resource_tracker_licensed_items_purchases.c.purchased_by_user, resource_tracker_licensed_items_purchases.c.purchased_at, resource_tracker_licensed_items_purchases.c.modified, @@ -58,6 +60,7 @@ async def create( pricing_unit_cost=data.pricing_unit_cost, start_at=data.start_at, expire_at=data.expire_at, + num_of_seats=data.num_of_seats, purchased_by_user=data.purchased_by_user, purchased_at=data.purchased_at, modified=sa.func.now(), @@ -73,6 +76,7 @@ async def list_( connection: AsyncConnection | None = None, *, product_name: ProductName, + filter_wallet_id: WalletID, offset: NonNegativeInt, limit: NonNegativeInt, order_by: OrderBy, @@ -80,7 +84,13 @@ async def list_( base_query = ( sa.select(*_SELECTION_ARGS) .select_from(resource_tracker_licensed_items_purchases) - .where(resource_tracker_licensed_items_purchases.c.product_name == product_name) + .where( + (resource_tracker_licensed_items_purchases.c.product_name == product_name) + & ( + resource_tracker_licensed_items_purchases.c.wallet_id + == filter_wallet_id + ) + ) ) # Select total count from base_query diff --git a/services/web/server/src/simcore_service_webserver/catalog/licenses/_models.py b/services/web/server/src/simcore_service_webserver/catalog/licenses/_models.py index 40d287faa92..884cc291431 100644 --- a/services/web/server/src/simcore_service_webserver/catalog/licenses/_models.py +++ b/services/web/server/src/simcore_service_webserver/catalog/licenses/_models.py @@ -49,6 +49,6 @@ class LicensedItemsListQueryParams( class LicensedItemsBodyParams(BaseModel): wallet_id: WalletID - num_of_seeds: int + num_of_seats: int model_config = ConfigDict(extra="forbid") From 78ef2ccdeb371bfd5b392550ee6ec3f78b285107 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 10 Dec 2024 08:44:32 +0100 Subject: [PATCH 040/102] renaming tests --- ...ker_credit_transactions.py => test_api_credit_transactions.py} | 0 ...esource_tracker_pricing_plans.py => test_api_pricing_plans.py} | 0 ...tracker_pricing_plans_rpc.py => test_api_pricing_plans_rpc.py} | 0 ...r_service_runs__export.py => test_api_service_runs__export.py} | 0 ...usages.py => test_api_service_runs__list_aggregated_usages.py} | 0 ...__list_billable.py => test_api_service_runs__list_billable.py} | 0 ..._with_wallet.py => test_api_service_runs__list_with_wallet.py} | 0 ...ut_wallet.py => test_api_service_runs__list_without_wallet.py} | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename services/resource-usage-tracker/tests/unit/with_dbs/{test_api_resource_tracker_credit_transactions.py => test_api_credit_transactions.py} (100%) rename services/resource-usage-tracker/tests/unit/with_dbs/{test_api_resource_tracker_pricing_plans.py => test_api_pricing_plans.py} (100%) rename services/resource-usage-tracker/tests/unit/with_dbs/{test_api_resource_tracker_pricing_plans_rpc.py => test_api_pricing_plans_rpc.py} (100%) rename services/resource-usage-tracker/tests/unit/with_dbs/{test_api_resource_tracker_service_runs__export.py => test_api_service_runs__export.py} (100%) rename services/resource-usage-tracker/tests/unit/with_dbs/{test_api_resource_tracker_service_runs__list_aggregated_usages.py => test_api_service_runs__list_aggregated_usages.py} (100%) rename services/resource-usage-tracker/tests/unit/with_dbs/{test_api_resource_tracker_service_runs__list_billable.py => test_api_service_runs__list_billable.py} (100%) rename services/resource-usage-tracker/tests/unit/with_dbs/{test_api_resource_tracker_service_runs__list_with_wallet.py => test_api_service_runs__list_with_wallet.py} (100%) rename services/resource-usage-tracker/tests/unit/with_dbs/{test_api_resource_tracker_service_runs__list_without_wallet.py => test_api_service_runs__list_without_wallet.py} (100%) diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_resource_tracker_credit_transactions.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_credit_transactions.py similarity index 100% rename from services/resource-usage-tracker/tests/unit/with_dbs/test_api_resource_tracker_credit_transactions.py rename to services/resource-usage-tracker/tests/unit/with_dbs/test_api_credit_transactions.py diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_resource_tracker_pricing_plans.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_pricing_plans.py similarity index 100% rename from services/resource-usage-tracker/tests/unit/with_dbs/test_api_resource_tracker_pricing_plans.py rename to services/resource-usage-tracker/tests/unit/with_dbs/test_api_pricing_plans.py diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_resource_tracker_pricing_plans_rpc.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_pricing_plans_rpc.py similarity index 100% rename from services/resource-usage-tracker/tests/unit/with_dbs/test_api_resource_tracker_pricing_plans_rpc.py rename to services/resource-usage-tracker/tests/unit/with_dbs/test_api_pricing_plans_rpc.py diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_resource_tracker_service_runs__export.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_service_runs__export.py similarity index 100% rename from services/resource-usage-tracker/tests/unit/with_dbs/test_api_resource_tracker_service_runs__export.py rename to services/resource-usage-tracker/tests/unit/with_dbs/test_api_service_runs__export.py diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_resource_tracker_service_runs__list_aggregated_usages.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_service_runs__list_aggregated_usages.py similarity index 100% rename from services/resource-usage-tracker/tests/unit/with_dbs/test_api_resource_tracker_service_runs__list_aggregated_usages.py rename to services/resource-usage-tracker/tests/unit/with_dbs/test_api_service_runs__list_aggregated_usages.py diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_resource_tracker_service_runs__list_billable.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_service_runs__list_billable.py similarity index 100% rename from services/resource-usage-tracker/tests/unit/with_dbs/test_api_resource_tracker_service_runs__list_billable.py rename to services/resource-usage-tracker/tests/unit/with_dbs/test_api_service_runs__list_billable.py diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_resource_tracker_service_runs__list_with_wallet.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_service_runs__list_with_wallet.py similarity index 100% rename from services/resource-usage-tracker/tests/unit/with_dbs/test_api_resource_tracker_service_runs__list_with_wallet.py rename to services/resource-usage-tracker/tests/unit/with_dbs/test_api_service_runs__list_with_wallet.py diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_resource_tracker_service_runs__list_without_wallet.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_service_runs__list_without_wallet.py similarity index 100% rename from services/resource-usage-tracker/tests/unit/with_dbs/test_api_resource_tracker_service_runs__list_without_wallet.py rename to services/resource-usage-tracker/tests/unit/with_dbs/test_api_service_runs__list_without_wallet.py From a6f49cd3a52241e4289f7ba92816e683deecd521 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 10 Dec 2024 08:55:44 +0100 Subject: [PATCH 041/102] rpc interface --- .../licensed_items_purchases.py | 86 +++++++++++++++++++ ..._items.py => _licensed_items_purchases.py} | 0 .../test_api_licensed_items_purchases.py | 80 +++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py rename services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/{_licensed_items.py => _licensed_items_purchases.py} (100%) create mode 100644 services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py new file mode 100644 index 00000000000..b77c586f3cf --- /dev/null +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py @@ -0,0 +1,86 @@ +import logging +from typing import Final + +from models_library.api_schemas_resource_usage_tracker import ( + RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, +) +from models_library.api_schemas_resource_usage_tracker.licensed_items_purchases import ( + LicensedItemPurchaseGet, +) +from models_library.api_schemas_resource_usage_tracker.service_runs import ( + ServiceRunPage, +) +from models_library.products import ProductName +from models_library.rabbitmq_basic_types import RPCMethodName +from models_library.resource_tracker_licensed_items_purchases import ( + LicensedItemsPurchasesCreate, +) +from models_library.rest_ordering import OrderBy +from models_library.wallets import WalletID +from pydantic import AnyUrl, NonNegativeInt, TypeAdapter + +from ....logging_utils import log_decorator +from ....rabbitmq import RabbitMQRPCClient + +_logger = logging.getLogger(__name__) + + +_DEFAULT_TIMEOUT_S: Final[NonNegativeInt] = 30 + +_RPC_METHOD_NAME_ADAPTER: TypeAdapter[RPCMethodName] = TypeAdapter(RPCMethodName) + + +@log_decorator(_logger, level=logging.DEBUG) +async def get_licensed_items_purchases_page( + rabbitmq_rpc_client: RabbitMQRPCClient, + *, + product_name: ProductName, + wallet_id: WalletID, + offset: int = 0, + limit: int = 20, + order_by: OrderBy = OrderBy(field="purchased_at"), +) -> ServiceRunPage: + result = await rabbitmq_rpc_client.request( + RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, + _RPC_METHOD_NAME_ADAPTER.validate_python("get_licensed_items_purchases_page"), + product_name=product_name, + wallet_id=wallet_id, + limit=limit, + offset=offset, + order_by=order_by, + timeout_s=_DEFAULT_TIMEOUT_S, + ) + assert isinstance(result, ServiceRunPage) # nosec + return result + + +@log_decorator(_logger, level=logging.DEBUG) +async def get_licensed_item_purchase( + rabbitmq_rpc_client: RabbitMQRPCClient, + *, + product_name: ProductName, + wallet_id: WalletID, +) -> LicensedItemPurchaseGet: + result = await rabbitmq_rpc_client.request( + RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, + _RPC_METHOD_NAME_ADAPTER.validate_python("get_licensed_item_purchase"), + product_name=product_name, + wallet_id=wallet_id, + timeout_s=_DEFAULT_TIMEOUT_S, + ) + assert isinstance(result, LicensedItemPurchaseGet) # nosec + return result + + +@log_decorator(_logger, level=logging.DEBUG) +async def create_licensed_item_purchase( + rabbitmq_rpc_client: RabbitMQRPCClient, *, data: LicensedItemsPurchasesCreate +) -> LicensedItemPurchaseGet: + result: AnyUrl = await rabbitmq_rpc_client.request( + RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, + _RPC_METHOD_NAME_ADAPTER.validate_python("create_licensed_item_purchase"), + data=data, + timeout_s=_DEFAULT_TIMEOUT_S, + ) + assert isinstance(result, LicensedItemPurchaseGet) # nosec + return result diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_purchases.py similarity index 100% rename from services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items.py rename to services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_purchases.py diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py new file mode 100644 index 00000000000..44a6ce56016 --- /dev/null +++ b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py @@ -0,0 +1,80 @@ +# pylint:disable=unused-variable +# pylint:disable=unused-argument +# pylint:disable=redefined-outer-name +# pylint:disable=too-many-arguments + +import os +from unittest.mock import AsyncMock, Mock + +import pytest +import sqlalchemy as sa +from moto.server import ThreadedMotoServer +from pydantic import AnyUrl, TypeAdapter +from pytest_mock import MockerFixture +from pytest_simcore.helpers.typing_env import EnvVarsDict +from servicelib.rabbitmq import RabbitMQRPCClient +from servicelib.rabbitmq.rpc_interfaces.resource_usage_tracker import service_runs +from settings_library.s3 import S3Settings +from types_aiobotocore_s3 import S3Client + +pytest_simcore_core_services_selection = [ + "postgres", + "rabbit", +] +pytest_simcore_ops_services_selection = [ + "adminer", +] + +_USER_ID = 1 + + +@pytest.fixture +async def mocked_export(mocker: MockerFixture) -> AsyncMock: + return mocker.patch( + "simcore_service_resource_usage_tracker.services.service_runs.service_runs_db.export_service_runs_table_to_s3", + autospec=True, + ) + + +@pytest.fixture +async def mocked_presigned_link(mocker: MockerFixture) -> AsyncMock: + return mocker.patch( + "simcore_service_resource_usage_tracker.services.service_runs.SimcoreS3API.create_single_presigned_download_link", + return_value=TypeAdapter(AnyUrl).validate_python("https://www.testing.com/"), + ) + + +@pytest.fixture +async def enable_resource_usage_tracker_s3( + mock_env: EnvVarsDict, + mocked_aws_server: ThreadedMotoServer, + mocked_s3_server_envs: EnvVarsDict, + mocked_s3_server_settings: S3Settings, + s3_client: S3Client, + monkeypatch: pytest.MonkeyPatch, +) -> None: + # Create bucket + await s3_client.create_bucket(Bucket=mocked_s3_server_settings.S3_BUCKET_NAME) + + # Remove the environment variable + if "RESOURCE_USAGE_TRACKER_S3" in os.environ: + monkeypatch.delenv("RESOURCE_USAGE_TRACKER_S3") + + +@pytest.mark.rpc_test() +async def test_rpc_list_service_runs_which_was_billed( + enable_resource_usage_tracker_s3: None, + mocked_redis_server: None, + postgres_db: sa.engine.Engine, + rpc_client: RabbitMQRPCClient, + mocked_export: Mock, + mocked_presigned_link: Mock, +): + download_url = await service_runs.export_service_runs( + rpc_client, + user_id=_USER_ID, + product_name="osparc", + ) + assert isinstance(download_url, AnyUrl) # nosec + assert mocked_export.called + assert mocked_presigned_link.called From 859396f6f7d828c2c394e5f466953011831b5a81 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 10 Dec 2024 10:04:51 +0100 Subject: [PATCH 042/102] [skip ci] no hacks --- .../client/source/class/osparc/data/model/PricingPlan.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js b/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js index fe208aef697..b6fc4031552 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js +++ b/services/static-webserver/client/source/class/osparc/data/model/PricingPlan.js @@ -31,7 +31,7 @@ qx.Class.define("osparc.data.model.PricingPlan", { this.set({ pricingPlanId: pricingPlanData.pricingPlanId, pricingPlanKey: pricingPlanData.pricingPlanKey, - classification: pricingPlanData.displayName.includes("ViP") ? "LICENSE" : pricingPlanData.classification, + classification: pricingPlanData.classification, name: pricingPlanData.displayName, description: pricingPlanData.description, isActive: pricingPlanData.isActive, From 13d259c1ae43594f37c8656b141be71e43eee71b Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 10 Dec 2024 10:24:10 +0100 Subject: [PATCH 043/102] [skip ci] Not a Toggle Button --- .../source/class/osparc/pricing/UnitsList.js | 4 +- .../source/class/osparc/study/PricingUnit.js | 13 +++-- .../class/osparc/study/PricingUnitTier.js | 13 ++--- .../source/class/osparc/study/PricingUnits.js | 52 ++++++++----------- 4 files changed, 38 insertions(+), 44 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js index 65edb40dcfe..c2188c5d972 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js +++ b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js @@ -43,7 +43,7 @@ qx.Class.define("osparc.pricing.UnitsList", { let control; switch (id) { case "pricing-units-container": - control = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); + control = new qx.ui.container.Composite(new qx.ui.layout.HBox(10)); this._addAt(control, 0, { flex: 1 }); @@ -95,7 +95,7 @@ qx.Class.define("osparc.pricing.UnitsList", { const buttons = this.getChildControl("pricing-units-container").getChildren(); const keepDefaultSelected = () => { buttons.forEach(btn => { - btn.setValue(btn.getUnitData().getIsDefault()); + btn.setSelected(btn.getUnitData().getIsDefault()); }); }; keepDefaultSelected(); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js index 54a3a83be56..b89ede98166 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js @@ -16,15 +16,16 @@ ************************************************************************ */ qx.Class.define("osparc.study.PricingUnit", { - extend: qx.ui.form.ToggleButton, + extend: qx.ui.core.Widget, type: "abstract", construct: function(pricingUnit) { this.base(arguments); + this._setLayout(new qx.ui.layout.VBox(5)); + this.set({ padding: 10, - center: true, decorator: "rounded", allowGrowX: false, allowGrowY: false, @@ -38,6 +39,13 @@ qx.Class.define("osparc.study.PricingUnit", { }, properties: { + selected: { + check: "Boolean", + init: false, + nullable: false, + event: "changeSelected", + }, + unitData: { check: "osparc.data.model.PricingUnit", nullable: false, @@ -83,7 +91,6 @@ qx.Class.define("osparc.study.PricingUnit", { _buildLayout: function(pricingUnit) { this._removeAll(); - this._setLayout(new qx.ui.layout.VBox(5)); const name = this.getChildControl("name"); pricingUnit.bind("name", name, "value"); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js index 85d51c1a88d..292a44efabf 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitTier.js @@ -73,19 +73,16 @@ qx.Class.define("osparc.study.PricingUnitTier", { appearance: "strong-button", center: true, }); - this.bind("value", control, "label", { - converter: value => value ? "Selected" : "Select" + this.bind("selected", control, "label", { + converter: selected => selected ? "Selected" : "Select" }); - this.bind("value", control, "enabled", { - converter: value => !value + this.bind("selected", control, "enabled", { + converter: selected => !selected }); this.bind("showSelectButton", control, "visibility", { converter: show => show ? "visible" : "excluded" }); - control.addListener("execute", () => { - this.setValue(true); - this.fireEvent("selectPricingUnit"); - }); + control.addListener("execute", () => this.fireEvent("selectPricingUnit")); this._add(control); break; } diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnits.js b/services/static-webserver/client/source/class/osparc/study/PricingUnits.js index 5f626f7cce8..8af8e767fbf 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnits.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnits.js @@ -22,7 +22,7 @@ qx.Class.define("osparc.study.PricingUnits", { this.base(arguments); this.set({ - layout: new qx.ui.layout.HBox(5), + layout: new qx.ui.layout.HBox(10), allowGrowY: false, }); @@ -40,54 +40,44 @@ qx.Class.define("osparc.study.PricingUnits", { members: { __buildLayout: function(pricingUnitsData, preselectedPricingUnit, changeSelectionAllowed) { - const buttons = []; + const pricingUnitTiers = []; pricingUnitsData.forEach(pricingUnitData => { const pricingUnit = new osparc.data.model.PricingUnit(pricingUnitData); - const button = new osparc.study.PricingUnitTier(pricingUnit).set({ + const pricingUnitTier = new osparc.study.PricingUnitTier(pricingUnit).set({ showSelectButton: changeSelectionAllowed, }); - buttons.push(button); - this._add(button); + pricingUnitTiers.push(pricingUnitTier); + this._add(pricingUnitTier); }); - const groupOptions = new qx.ui.form.RadioGroup(); - buttons.forEach(btn => { - groupOptions.add(btn); - btn.bind("value", btn, "backgroundColor", { + pricingUnitTiers.forEach(pricingUnitTier => { + pricingUnitTier.bind("selected", pricingUnitTier, "backgroundColor", { converter: selected => selected ? "background-main-1" : "transparent" }); }); if (preselectedPricingUnit) { - const buttonFound = buttons.find(button => button.getUnitData().getPricingUnitId() === preselectedPricingUnit["pricingUnitId"]); - if (buttonFound) { - buttonFound.setValue(true); + const pricingUnitTierFound = pricingUnitTiers.find(pricingUnitTier => pricingUnitTier.getUnitData().getPricingUnitId() === preselectedPricingUnit["pricingUnitId"]); + if (pricingUnitTierFound) { + pricingUnitTierFound.setSelected(true); } } else { // preselect default - buttons.forEach(button => { - if (button.getUnitData().getIsDefault()) { - button.setValue(true); + pricingUnitTiers.forEach(pricingUnitTier => { + if (pricingUnitTier.getUnitData().getIsDefault()) { + pricingUnitTier.setSelected(true); } }); } - buttons.forEach(button => { - if (!changeSelectionAllowed) { - button.setCursor("default"); - } - [ - "execute", - "selectPricingUnit", - ].forEach(ev => { - button.addListener(ev, () => { - if (changeSelectionAllowed) { - const selectedUnitId = button.getUnitData().getPricingUnitId(); - this.setSelectedUnitId(selectedUnitId); - } else { - buttons.forEach(btn => btn.setValue(btn.getUnitData().getIsDefault())); - } - }); + pricingUnitTiers.forEach(pricingUnitTier => { + pricingUnitTier.addListener("selectPricingUnit", () => { + if (changeSelectionAllowed) { + const selectedUnitId = pricingUnitTier.getUnitData().getPricingUnitId(); + this.setSelectedUnitId(selectedUnitId); + } else { + pricingUnitTiers.forEach(btn => btn.setSelected(btn.getUnitData().getIsDefault())); + } }); }); } From bfb0b0992f03527e110ed106b593a2e5e437602b Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 10 Dec 2024 10:32:55 +0100 Subject: [PATCH 044/102] [skip ci] border --- .../client/source/class/osparc/study/PricingUnit.js | 10 +++++++++- .../client/source/class/osparc/study/PricingUnits.js | 6 ------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js index b89ede98166..4c4c76fca24 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js @@ -32,6 +32,8 @@ qx.Class.define("osparc.study.PricingUnit", { }); this.setUnitData(pricingUnit); + + osparc.utils.Utils.addBorder(this); }, events: { @@ -44,6 +46,7 @@ qx.Class.define("osparc.study.PricingUnit", { init: false, nullable: false, event: "changeSelected", + apply: "__applySelected", }, unitData: { @@ -94,6 +97,11 @@ qx.Class.define("osparc.study.PricingUnit", { const name = this.getChildControl("name"); pricingUnit.bind("name", name, "value"); - } + }, + + __applySelected: function(selected) { + const strong = qx.theme.manager.Color.getInstance().resolve("strong-main"); + osparc.utils.Utils.updateBorderColor(this, selected ? strong : "transparent"); + }, } }); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnits.js b/services/static-webserver/client/source/class/osparc/study/PricingUnits.js index 8af8e767fbf..ec86dfdaf55 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnits.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnits.js @@ -50,12 +50,6 @@ qx.Class.define("osparc.study.PricingUnits", { this._add(pricingUnitTier); }); - pricingUnitTiers.forEach(pricingUnitTier => { - pricingUnitTier.bind("selected", pricingUnitTier, "backgroundColor", { - converter: selected => selected ? "background-main-1" : "transparent" - }); - }); - if (preselectedPricingUnit) { const pricingUnitTierFound = pricingUnitTiers.find(pricingUnitTier => pricingUnitTier.getUnitData().getPricingUnitId() === preselectedPricingUnit["pricingUnitId"]); if (pricingUnitTierFound) { From e5f3648d22d5ea0580aea9bbe8f71afbb6f41432 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 10 Dec 2024 10:40:34 +0100 Subject: [PATCH 045/102] renaming --- .../client/source/class/osparc/service/PricingUnitsList.js | 2 +- .../client/source/class/osparc/study/NodePricingUnits.js | 2 +- .../class/osparc/study/{PricingUnits.js => PricingUnitTiers.js} | 2 +- .../source/class/osparc/vipMarket/AnatomicalModelDetails.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename services/static-webserver/client/source/class/osparc/study/{PricingUnits.js => PricingUnitTiers.js} (97%) diff --git a/services/static-webserver/client/source/class/osparc/service/PricingUnitsList.js b/services/static-webserver/client/source/class/osparc/service/PricingUnitsList.js index cb138fd43c6..215b17d935b 100644 --- a/services/static-webserver/client/source/class/osparc/service/PricingUnitsList.js +++ b/services/static-webserver/client/source/class/osparc/service/PricingUnitsList.js @@ -60,7 +60,7 @@ qx.Class.define("osparc.service.PricingUnitsList", { this.getChildControl("pricing-units-container").removeAll(); if (pricingUnitsData.length) { - const pUnits = new osparc.study.PricingUnits(pricingUnitsData, null, false); + const pUnits = new osparc.study.PricingUnitTiers(pricingUnitsData, null, false); this.getChildControl("pricing-units-container").add(pUnits); } else { const notFound = new qx.ui.basic.Label().set({ diff --git a/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js b/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js index 6bb2d7180d8..14f2f01dfdc 100644 --- a/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js +++ b/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js @@ -117,7 +117,7 @@ qx.Class.define("osparc.study.NodePricingUnits", { .then(preselectedPricingUnit => { if (pricingPlanData && "pricingUnits" in pricingPlanData && pricingPlanData["pricingUnits"].length) { const pricingUnitsData = pricingPlanData["pricingUnits"]; - const pricingUnitButtons = this.__pricingUnits = new osparc.study.PricingUnits(pricingUnitsData, preselectedPricingUnit); + const pricingUnitButtons = this.__pricingUnits = new osparc.study.PricingUnitTiers(pricingUnitsData, preselectedPricingUnit); if (inGroupBox) { const pricingUnitsLayout = osparc.study.StudyOptions.createGroupBox(nodeLabel); pricingUnitsLayout.add(pricingUnitButtons); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnits.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitTiers.js similarity index 97% rename from services/static-webserver/client/source/class/osparc/study/PricingUnits.js rename to services/static-webserver/client/source/class/osparc/study/PricingUnitTiers.js index ec86dfdaf55..9f58215af53 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnits.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitTiers.js @@ -15,7 +15,7 @@ ************************************************************************ */ -qx.Class.define("osparc.study.PricingUnits", { +qx.Class.define("osparc.study.PricingUnitTiers", { extend: qx.ui.container.Composite, construct: function(pricingUnitsData, preselectedPricingUnit, changeSelectionAllowed = true) { diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index a70d08291fe..7e273175a0b 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -171,7 +171,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }, __createPricingUnits: function(anatomicalModelsData) { - const pricingUnitsLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5).set({ + const pricingUnitsLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(10).set({ alignX: "center" })); From 7a6c3e4775c1cc806c10ad2b6ca20b3fbb18ace8 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 10 Dec 2024 10:44:55 +0100 Subject: [PATCH 046/102] select and unselect the rest --- .../client/source/class/osparc/study/PricingUnitTiers.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitTiers.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitTiers.js index 9f58215af53..bb41ed0a6d5 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitTiers.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitTiers.js @@ -67,10 +67,11 @@ qx.Class.define("osparc.study.PricingUnitTiers", { pricingUnitTiers.forEach(pricingUnitTier => { pricingUnitTier.addListener("selectPricingUnit", () => { if (changeSelectionAllowed) { + // select and unselect the rest + pricingUnitTiers.forEach(puTIer => puTIer.setSelected(puTIer === pricingUnitTier)); + // and save selection const selectedUnitId = pricingUnitTier.getUnitData().getPricingUnitId(); this.setSelectedUnitId(selectedUnitId); - } else { - pricingUnitTiers.forEach(btn => btn.setSelected(btn.getUnitData().getIsDefault())); } }); }); From 7e8355abc0dd3363716a5221e507307254768835 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 10 Dec 2024 10:55:51 +0100 Subject: [PATCH 047/102] [skip ci] show error message --- .../class/osparc/study/NodePricingUnits.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js b/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js index 14f2f01dfdc..601a1131c0b 100644 --- a/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js +++ b/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js @@ -117,21 +117,26 @@ qx.Class.define("osparc.study.NodePricingUnits", { .then(preselectedPricingUnit => { if (pricingPlanData && "pricingUnits" in pricingPlanData && pricingPlanData["pricingUnits"].length) { const pricingUnitsData = pricingPlanData["pricingUnits"]; - const pricingUnitButtons = this.__pricingUnits = new osparc.study.PricingUnitTiers(pricingUnitsData, preselectedPricingUnit); + const pricingUnitTiers = this.__pricingUnits = new osparc.study.PricingUnitTiers(pricingUnitsData, preselectedPricingUnit); if (inGroupBox) { const pricingUnitsLayout = osparc.study.StudyOptions.createGroupBox(nodeLabel); - pricingUnitsLayout.add(pricingUnitButtons); + pricingUnitsLayout.add(pricingUnitTiers); this._add(pricingUnitsLayout); } else { - this._add(pricingUnitButtons); + this._add(pricingUnitTiers); } - pricingUnitButtons.addListener("changeSelectedUnitId", e => { + pricingUnitTiers.addListener("changeSelectedUnitId", e => { if (this.isPatchNode()) { - pricingUnitButtons.setEnabled(false); + pricingUnitTiers.setEnabled(false); const pricingPlanId = this.getPricingPlanId(); const selectedPricingUnitId = e.getData(); this.self().patchPricingUnitSelection(studyId, nodeId, pricingPlanId, selectedPricingUnitId) - .finally(() => pricingUnitButtons.setEnabled(true)); + // .then(() => ) + .catch(err => { + const msg = err.message || this.tr("Cannot change Tier"); + osparc.FlashMessenger.getInstance().logAs(msg, "ERROR"); + }) + .finally(() => pricingUnitTiers.setEnabled(true)); } }); } From 7109bbbae899daa2a53c14f281a1873ac85bdc8c Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 10 Dec 2024 10:58:22 +0100 Subject: [PATCH 048/102] purchased --- .../source/class/osparc/data/model/PricingUnit.js | 7 +++++++ .../osparc/vipMarket/AnatomicalModelDetails.js | 2 +- .../source/class/osparc/vipMarket/VipMarket.js | 14 +++++++------- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js b/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js index 91970b854af..238b1077495 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js @@ -87,5 +87,12 @@ qx.Class.define("osparc.data.model.PricingUnit", { init: null, event: "changeSpecificInfo", }, + + purchased: { + check: "Boolean", + nullable: true, + init: false, + event: "changePurchased", + }, }, }); diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 7e273175a0b..79f7e6125d3 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -28,7 +28,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }, events: { - "modelLeased": "qx.event.type.Event", + "modelPurchased": "qx.event.type.Event", }, properties: { 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 94732947079..e7992b48be4 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -47,7 +47,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { curatedModel[key] = model[key]; } if (key === "ID") { - curatedModel["leased"] = model["ID"] < 4; + curatedModel["purchased"] = model["ID"] < 4; } }); anatomicalModels.push(curatedModel); @@ -116,7 +116,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { ctrl.bindProperty("date", "date", null, item, id); ctrl.bindProperty("licensedItemId", "licensedItemId", null, item, id); ctrl.bindProperty("pricingPlanId", "pricingPlanId", null, item, id); - ctrl.bindProperty("leased", "leased", null, item, id); + ctrl.bindProperty("purchased", "purchased", null, item, id); }, configureItem: item => { item.subscribeToFilterGroup("vipModels"); @@ -178,18 +178,18 @@ qx.Class.define("osparc.vipMarket.VipMarket", { anatomicalModel["licensedItemId"] = licensedItem["licensedItemId"]; anatomicalModel["pricingPlanId"] = licensedItem["pricingPlanId"]; // attach leased data - anatomicalModel["leased"] = model["leased"]; + anatomicalModel["purchased"] = model["purchased"]; this.__anatomicalModels.push(anatomicalModel); } }); this.__populateModels(); - anatomicModelDetails.addListener("modelLeased", e => { + anatomicModelDetails.addListener("modelPurchased", e => { const modelId = e.getData(); const found = this.__anatomicalModels.find(model => model["ID"] === modelId); if (found) { - found["leased"] = true; + found["purchased"] = true; this.__populateModels(); anatomicModelDetails.setAnatomicalModelsData(found); } @@ -206,9 +206,9 @@ qx.Class.define("osparc.vipMarket.VipMarket", { const sortModel = sortBy => { models.sort((a, b) => { // first criteria - if (b["leased"] !== a["leased"]) { + if (b["purchased"] !== a["purchased"]) { // leased first - return b["leased"] - a["leased"]; + return b["purchased"] - a["purchased"]; } // second criteria if (sortBy) { From f5de79b0d343a436ff877762972fa3279e0af291 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 10 Dec 2024 11:08:11 +0100 Subject: [PATCH 049/102] RUT unit tests --- .../licensed_items_purchases.py | 1 + .../licensed_items_purchases.py | 13 +- .../api/rpc/routes.py | 4 +- .../test_api_licensed_items_purchases.py | 121 +++++++++++------- 4 files changed, 83 insertions(+), 56 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py index 69c3c64c440..6afe928e88d 100644 --- a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py +++ b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py @@ -1,5 +1,6 @@ from datetime import datetime from decimal import Decimal +from typing import NamedTuple from models_library.licensed_items import LicensedItemID from models_library.products import ProductName diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py index b77c586f3cf..95c002df7cb 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py @@ -6,9 +6,8 @@ ) from models_library.api_schemas_resource_usage_tracker.licensed_items_purchases import ( LicensedItemPurchaseGet, -) -from models_library.api_schemas_resource_usage_tracker.service_runs import ( - ServiceRunPage, + LicensedItemPurchaseID, + LicensedItemsPurchasesPage, ) from models_library.products import ProductName from models_library.rabbitmq_basic_types import RPCMethodName @@ -39,7 +38,7 @@ async def get_licensed_items_purchases_page( offset: int = 0, limit: int = 20, order_by: OrderBy = OrderBy(field="purchased_at"), -) -> ServiceRunPage: +) -> LicensedItemsPurchasesPage: result = await rabbitmq_rpc_client.request( RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, _RPC_METHOD_NAME_ADAPTER.validate_python("get_licensed_items_purchases_page"), @@ -50,7 +49,7 @@ async def get_licensed_items_purchases_page( order_by=order_by, timeout_s=_DEFAULT_TIMEOUT_S, ) - assert isinstance(result, ServiceRunPage) # nosec + assert isinstance(result, LicensedItemsPurchasesPage) # nosec return result @@ -59,13 +58,13 @@ async def get_licensed_item_purchase( rabbitmq_rpc_client: RabbitMQRPCClient, *, product_name: ProductName, - wallet_id: WalletID, + licensed_item_purchase_id: LicensedItemPurchaseID, ) -> LicensedItemPurchaseGet: result = await rabbitmq_rpc_client.request( RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, _RPC_METHOD_NAME_ADAPTER.validate_python("get_licensed_item_purchase"), product_name=product_name, - wallet_id=wallet_id, + licensed_item_purchase_id=licensed_item_purchase_id, timeout_s=_DEFAULT_TIMEOUT_S, ) assert isinstance(result, LicensedItemPurchaseGet) # nosec diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/routes.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/routes.py index 349f86f4a01..f1fd1276161 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/routes.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/routes.py @@ -8,14 +8,14 @@ from servicelib.rabbitmq import RPCRouter from ...services.modules.rabbitmq import get_rabbitmq_rpc_server -from . import _licensed_items, _resource_tracker +from . import _licensed_items_purchases, _resource_tracker _logger = logging.getLogger(__name__) ROUTERS: list[RPCRouter] = [ _resource_tracker.router, - _licensed_items.router, + _licensed_items_purchases.router, ] diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py index 44a6ce56016..915b86db7c2 100644 --- a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py +++ b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py @@ -3,19 +3,23 @@ # pylint:disable=redefined-outer-name # pylint:disable=too-many-arguments -import os -from unittest.mock import AsyncMock, Mock +from datetime import datetime, timezone +from decimal import Decimal -import pytest +# # Remove the environment variable +# if "RESOURCE_USAGE_TRACKER_S3" in os.environ: +# monkeypatch.delenv("RESOURCE_USAGE_TRACKER_S3") import sqlalchemy as sa -from moto.server import ThreadedMotoServer -from pydantic import AnyUrl, TypeAdapter -from pytest_mock import MockerFixture -from pytest_simcore.helpers.typing_env import EnvVarsDict +from models_library.api_schemas_resource_usage_tracker.licensed_items_purchases import ( + LicensedItemPurchaseGet, +) +from models_library.resource_tracker_licensed_items_purchases import ( + LicensedItemsPurchasesCreate, +) from servicelib.rabbitmq import RabbitMQRPCClient -from servicelib.rabbitmq.rpc_interfaces.resource_usage_tracker import service_runs -from settings_library.s3 import S3Settings -from types_aiobotocore_s3 import S3Client +from servicelib.rabbitmq.rpc_interfaces.resource_usage_tracker import ( + licensed_items_purchases, +) pytest_simcore_core_services_selection = [ "postgres", @@ -25,56 +29,79 @@ "adminer", ] -_USER_ID = 1 +_USER_ID = 1 -@pytest.fixture -async def mocked_export(mocker: MockerFixture) -> AsyncMock: - return mocker.patch( - "simcore_service_resource_usage_tracker.services.service_runs.service_runs_db.export_service_runs_table_to_s3", - autospec=True, - ) +# @pytest.fixture +# async def mocked_export(mocker: MockerFixture) -> AsyncMock: +# return mocker.patch( +# "simcore_service_resource_usage_tracker.services.service_runs.service_runs_db.export_service_runs_table_to_s3", +# autospec=True, +# ) -@pytest.fixture -async def mocked_presigned_link(mocker: MockerFixture) -> AsyncMock: - return mocker.patch( - "simcore_service_resource_usage_tracker.services.service_runs.SimcoreS3API.create_single_presigned_download_link", - return_value=TypeAdapter(AnyUrl).validate_python("https://www.testing.com/"), - ) +# @pytest.fixture +# async def mocked_presigned_link(mocker: MockerFixture) -> AsyncMock: +# return mocker.patch( +# "simcore_service_resource_usage_tracker.services.service_runs.SimcoreS3API.create_single_presigned_download_link", +# return_value=TypeAdapter(AnyUrl).validate_python("https://www.testing.com/"), +# ) -@pytest.fixture -async def enable_resource_usage_tracker_s3( - mock_env: EnvVarsDict, - mocked_aws_server: ThreadedMotoServer, - mocked_s3_server_envs: EnvVarsDict, - mocked_s3_server_settings: S3Settings, - s3_client: S3Client, - monkeypatch: pytest.MonkeyPatch, -) -> None: - # Create bucket - await s3_client.create_bucket(Bucket=mocked_s3_server_settings.S3_BUCKET_NAME) - # Remove the environment variable - if "RESOURCE_USAGE_TRACKER_S3" in os.environ: - monkeypatch.delenv("RESOURCE_USAGE_TRACKER_S3") +# @pytest.fixture +# async def enable_resource_usage_tracker_s3( +# mock_env: EnvVarsDict, +# mocked_aws_server: ThreadedMotoServer, +# mocked_s3_server_envs: EnvVarsDict, +# mocked_s3_server_settings: S3Settings, +# s3_client: S3Client, +# monkeypatch: pytest.MonkeyPatch, +# ) -> None: +# # Create bucket +# await s3_client.create_bucket(Bucket=mocked_s3_server_settings.S3_BUCKET_NAME) -@pytest.mark.rpc_test() -async def test_rpc_list_service_runs_which_was_billed( - enable_resource_usage_tracker_s3: None, +async def test_rpc_licensed_items_purchases_workflow( + # enable_resource_usage_tracker_s3: None, mocked_redis_server: None, postgres_db: sa.engine.Engine, rpc_client: RabbitMQRPCClient, - mocked_export: Mock, - mocked_presigned_link: Mock, + # mocked_export: Mock, + # mocked_presigned_link: Mock, ): - download_url = await service_runs.export_service_runs( + result = await licensed_items_purchases.get_licensed_items_purchases_page( + rpc_client, product_name="osparc", wallet_id=1 + ) + assert isinstance(result, list) # nosec + + _create_data = LicensedItemsPurchasesCreate( + product_name="osparc", + licensed_item_id="beb16d18-d57d-44aa-a638-9727fa4a72ef", + wallet_id=1, + wallet_name="My Wallet", + pricing_unit_cost_id=1, + pricing_unit_cost=Decimal(10), + start_at=datetime.now(tz=timezone.utc), + expire_at=datetime.now(tz=timezone.utc), + num_of_seats=1, + purchased_by_user=1, + purchased_at=datetime.now(tz=timezone.utc), + ) + + result = await licensed_items_purchases.create_licensed_item_purchase( + rpc_client, data=_create_data + ) + assert isinstance(result, LicensedItemPurchaseGet) # nosec + + result = await licensed_items_purchases.get_licensed_item_purchase( rpc_client, - user_id=_USER_ID, product_name="osparc", + licensed_item_purchase_id=result.licensed_item_purchase_id, + ) + assert isinstance(result, LicensedItemPurchaseGet) # nosec + + result = await licensed_items_purchases.get_licensed_items_purchases_page( + rpc_client, product_name="osparc", wallet_id=_create_data.wallet_id ) - assert isinstance(download_url, AnyUrl) # nosec - assert mocked_export.called - assert mocked_presigned_link.called + assert isinstance(result, list) # nosec From cda64e2934eead32cfa9131de371fcc6133d03c9 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 10 Dec 2024 11:18:37 +0100 Subject: [PATCH 050/102] Import button --- .../source/class/osparc/study/PricingUnit.js | 1 + .../vipMarket/AnatomicalModelDetails.js | 31 ++++++++++++++++--- .../vipMarket/AnatomicalModelListItem.js | 8 ++--- .../class/osparc/vipMarket/VipMarket.js | 16 ++++++++-- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js index 4c4c76fca24..f257af307d5 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnit.js @@ -27,6 +27,7 @@ qx.Class.define("osparc.study.PricingUnit", { this.set({ padding: 10, decorator: "rounded", + minWidth: 100, allowGrowX: false, allowGrowY: false, }); diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 79f7e6125d3..77009569300 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -28,7 +28,8 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }, events: { - "modelPurchased": "qx.event.type.Event", + "modelPurchaseRequested": "qx.event.type.Data", + "modelImportRequested": "qx.event.type.Data", }, properties: { @@ -47,9 +48,11 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { const anatomicalModelsData = this.getAnatomicalModelsData(); if (anatomicalModelsData) { const modelInfo = this.__createModelInfo(anatomicalModelsData); - this._add(modelInfo); const pricingUnits = this.__createPricingUnits(anatomicalModelsData); + const importButton = this.__createImportButton(anatomicalModelsData); + this._add(modelInfo); this._add(pricingUnits); + this._add(importButton); } else { const selectModelLabel = new qx.ui.basic.Label().set({ value: this.tr("Select a model for more details"), @@ -184,7 +187,13 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { const pUnit = new osparc.study.PricingUnitLicense(pricingUnit).set({ showRentButton: true, }); - pUnit.addListener("rentPricingUnit", () => this.__rentAnatomicalModel(anatomicalModelsData, pricingUnit)); + pUnit.addListener("rentPricingUnit", () => { + this.fireDataEvent("modelPurchaseRequested", { + modelId: anatomicalModelsData["modelId"], + licensedItemId: anatomicalModelsData["licensedItemId"], + pricingUnitId: pricingUnit.getPricingUnitId(), + }); + }, this); pricingUnitsLayout.add(pUnit); }); }) @@ -193,8 +202,20 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { return pricingUnitsLayout; }, - __rentAnatomicalModel: function(anatomicalModelsData, pricingUnit) { - console.log(":purchase", anatomicalModelsData["licensedItemId"], pricingUnit.getPricingUnitId()); + __createImportButton: function(anatomicalModelsData) { + const importButton = new qx.ui.form.Button().set({ + label: this.tr("Import"), + appearance: "strong-button", + center: true, + maxWidth: 200, + alignX: "center", + }); + importButton.addListener("execute", () => { + this.fireDataEvent("modelImportRequested", { + modelId: anatomicalModelsData["modelId"] + }); + }, this); + return importButton; }, } }); diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js index 6100b33c110..b55d6704c2d 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js @@ -97,12 +97,12 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelListItem", { event: "changePricingPlanId", }, - leased: { + purchased: { check: "Boolean", init: false, nullable: true, - event: "changeLeased", - apply: "__applyLeased", + event: "changePurchased", + apply: "__applyPurchased", }, }, @@ -161,7 +161,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelListItem", { this.getChildControl("name").setValue(value); }, - __applyLeased: function(value) { + __applyPurchased: function(value) { if (value) { this.setBackgroundColor("strong-main"); } 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 e7992b48be4..44d705d9023 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -185,8 +185,13 @@ qx.Class.define("osparc.vipMarket.VipMarket", { this.__populateModels(); - anatomicModelDetails.addListener("modelPurchased", e => { - const modelId = e.getData(); + anatomicModelDetails.addListener("modelPurchaseRequested", e => { + const { + modelId, + licensedItemId, + pricingUnitId, + } = e.getData(); + console.log("purchase", licensedItemId, pricingUnitId); const found = this.__anatomicalModels.find(model => model["ID"] === modelId); if (found) { found["purchased"] = true; @@ -194,6 +199,13 @@ qx.Class.define("osparc.vipMarket.VipMarket", { anatomicModelDetails.setAnatomicalModelsData(found); } }, this); + + anatomicModelDetails.addListener("modelImportRequested", e => { + const { + modelId + } = e.getData(); + console.log("Import", modelId); + }, this); }); }) .catch(err => console.error(err)); From acd4c881a0c32ea40c9409fc42f29cfff5bc4519 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 10 Dec 2024 11:37:40 +0100 Subject: [PATCH 051/102] RUT unit tests --- ...7_add_cols_to_licensed_items_purchases_.py | 4 +-- ...b_add_cols_to_licensed_items_purchases_.py | 28 +++++++++++++++++++ ...source_tracker_licensed_items_purchases.py | 2 +- .../modules/db/licensed_items_purchases_db.py | 4 +-- .../test_api_licensed_items_purchases.py | 22 +++++++++------ 5 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 packages/postgres-database/src/simcore_postgres_database/migration/versions/d68b8128c23b_add_cols_to_licensed_items_purchases_.py diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/8fa15c4c3977_add_cols_to_licensed_items_purchases_.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/8fa15c4c3977_add_cols_to_licensed_items_purchases_.py index 39f2ba32ea3..ee47dcb5d4a 100644 --- a/packages/postgres-database/src/simcore_postgres_database/migration/versions/8fa15c4c3977_add_cols_to_licensed_items_purchases_.py +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/8fa15c4c3977_add_cols_to_licensed_items_purchases_.py @@ -1,7 +1,7 @@ """add cols to licensed_items_purchases table Revision ID: 8fa15c4c3977 -Revises: 38c9ac332c58 +Revises: 4d007819e61a Create Date: 2024-12-10 06:42:23.319239+00:00 """ @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. revision = "8fa15c4c3977" -down_revision = "38c9ac332c58" +down_revision = "4d007819e61a" branch_labels = None depends_on = None diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/d68b8128c23b_add_cols_to_licensed_items_purchases_.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/d68b8128c23b_add_cols_to_licensed_items_purchases_.py new file mode 100644 index 00000000000..da729aec544 --- /dev/null +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/d68b8128c23b_add_cols_to_licensed_items_purchases_.py @@ -0,0 +1,28 @@ +"""add cols to licensed_items_purchases table 2 + +Revision ID: d68b8128c23b +Revises: 8fa15c4c3977 +Create Date: 2024-12-10 10:24:28.071216+00:00 + +""" +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "d68b8128c23b" +down_revision = "8fa15c4c3977" +branch_labels = None +depends_on = None + + +def upgrade(): + op.drop_column("resource_tracker_licensed_items_purchases", "licensed_item_id") + op.add_column( + "resource_tracker_licensed_items_purchases", + sa.Column("licensed_item_id", postgresql.UUID(as_uuid=True), nullable=False), + ) + + +def downgrade(): + ... diff --git a/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_purchases.py b/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_purchases.py index c5c3e2b57ec..bfcca3b52e8 100644 --- a/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_purchases.py +++ b/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_licensed_items_purchases.py @@ -26,7 +26,7 @@ ), sa.Column( "licensed_item_id", - sa.BigInteger, + UUID(as_uuid=True), nullable=False, ), sa.Column( diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py index e507da9f7e2..67950b7b73d 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py @@ -49,7 +49,7 @@ async def create( data: CreateLicensedItemsPurchasesDB, ) -> LicensedItemsPurchasesDB: async with transaction_context(engine, connection) as conn: - result = await conn.stream( + result = await conn.execute( resource_tracker_licensed_items_purchases.insert() .values( product_name=data.product_name, @@ -67,7 +67,7 @@ async def create( ) .returning(*_SELECTION_ARGS) ) - row = await result.first() + row = result.first() return LicensedItemsPurchasesDB.model_validate(row) diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py index 915b86db7c2..aaf235351c3 100644 --- a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py +++ b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py @@ -3,7 +3,7 @@ # pylint:disable=redefined-outer-name # pylint:disable=too-many-arguments -from datetime import datetime, timezone +from datetime import UTC, datetime from decimal import Decimal # # Remove the environment variable @@ -12,6 +12,7 @@ import sqlalchemy as sa from models_library.api_schemas_resource_usage_tracker.licensed_items_purchases import ( LicensedItemPurchaseGet, + LicensedItemsPurchasesPage, ) from models_library.resource_tracker_licensed_items_purchases import ( LicensedItemsPurchasesCreate, @@ -73,7 +74,9 @@ async def test_rpc_licensed_items_purchases_workflow( result = await licensed_items_purchases.get_licensed_items_purchases_page( rpc_client, product_name="osparc", wallet_id=1 ) - assert isinstance(result, list) # nosec + assert isinstance(result, LicensedItemsPurchasesPage) # nosec + assert result.items == [] + assert result.total == 0 _create_data = LicensedItemsPurchasesCreate( product_name="osparc", @@ -82,14 +85,14 @@ async def test_rpc_licensed_items_purchases_workflow( wallet_name="My Wallet", pricing_unit_cost_id=1, pricing_unit_cost=Decimal(10), - start_at=datetime.now(tz=timezone.utc), - expire_at=datetime.now(tz=timezone.utc), + start_at=datetime.now(tz=UTC), + expire_at=datetime.now(tz=UTC), num_of_seats=1, purchased_by_user=1, - purchased_at=datetime.now(tz=timezone.utc), + purchased_at=datetime.now(tz=UTC), ) - result = await licensed_items_purchases.create_licensed_item_purchase( + created_item = await licensed_items_purchases.create_licensed_item_purchase( rpc_client, data=_create_data ) assert isinstance(result, LicensedItemPurchaseGet) # nosec @@ -97,11 +100,14 @@ async def test_rpc_licensed_items_purchases_workflow( result = await licensed_items_purchases.get_licensed_item_purchase( rpc_client, product_name="osparc", - licensed_item_purchase_id=result.licensed_item_purchase_id, + licensed_item_purchase_id=created_item.licensed_item_purchase_id, ) assert isinstance(result, LicensedItemPurchaseGet) # nosec + assert result.licensed_item_purchase_id == created_item.licensed_item_purchase_id result = await licensed_items_purchases.get_licensed_items_purchases_page( rpc_client, product_name="osparc", wallet_id=_create_data.wallet_id ) - assert isinstance(result, list) # nosec + assert isinstance(result, LicensedItemsPurchasesPage) # nosec + assert len(result.items) == 1 + assert result.total == 1 From 317cf1dd8ae20e5ebbb35a5779f4ec7e54cb9001 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 10 Dec 2024 11:38:17 +0100 Subject: [PATCH 052/102] RUT unit tests --- .../test_api_licensed_items_purchases.py | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py index aaf235351c3..aad656d1728 100644 --- a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py +++ b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py @@ -6,9 +6,6 @@ from datetime import UTC, datetime from decimal import Decimal -# # Remove the environment variable -# if "RESOURCE_USAGE_TRACKER_S3" in os.environ: -# monkeypatch.delenv("RESOURCE_USAGE_TRACKER_S3") import sqlalchemy as sa from models_library.api_schemas_resource_usage_tracker.licensed_items_purchases import ( LicensedItemPurchaseGet, @@ -31,45 +28,10 @@ ] -_USER_ID = 1 - - -# @pytest.fixture -# async def mocked_export(mocker: MockerFixture) -> AsyncMock: -# return mocker.patch( -# "simcore_service_resource_usage_tracker.services.service_runs.service_runs_db.export_service_runs_table_to_s3", -# autospec=True, -# ) - - -# @pytest.fixture -# async def mocked_presigned_link(mocker: MockerFixture) -> AsyncMock: -# return mocker.patch( -# "simcore_service_resource_usage_tracker.services.service_runs.SimcoreS3API.create_single_presigned_download_link", -# return_value=TypeAdapter(AnyUrl).validate_python("https://www.testing.com/"), -# ) - - -# @pytest.fixture -# async def enable_resource_usage_tracker_s3( -# mock_env: EnvVarsDict, -# mocked_aws_server: ThreadedMotoServer, -# mocked_s3_server_envs: EnvVarsDict, -# mocked_s3_server_settings: S3Settings, -# s3_client: S3Client, -# monkeypatch: pytest.MonkeyPatch, -# ) -> None: -# # Create bucket -# await s3_client.create_bucket(Bucket=mocked_s3_server_settings.S3_BUCKET_NAME) - - async def test_rpc_licensed_items_purchases_workflow( - # enable_resource_usage_tracker_s3: None, mocked_redis_server: None, postgres_db: sa.engine.Engine, rpc_client: RabbitMQRPCClient, - # mocked_export: Mock, - # mocked_presigned_link: Mock, ): result = await licensed_items_purchases.get_licensed_items_purchases_page( rpc_client, product_name="osparc", wallet_id=1 From 16650d847c2e76991b2f32190823d9c90897af93 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 10 Dec 2024 11:43:50 +0100 Subject: [PATCH 053/102] refactor and 4 tabs --- .../source/class/osparc/vipMarket/Market.js | 35 ++++- .../class/osparc/vipMarket/VipMarket.js | 133 +++++++++++------- 2 files changed, 116 insertions(+), 52 deletions(-) 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 dd6a2250c44..beb6c8240a8 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js @@ -26,7 +26,27 @@ qx.Class.define("osparc.vipMarket.Market", { }); this.addWidgetOnTopOfTheTabs(miniWallet); - this.__vipMarketPage = this.__getVipMarketPage(); + [{ + category: "humanWhole", + label: "Humans", + url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/HumanWholeBody", + }, { + category: "humanRegion", + label: "Humans (Region)", + url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/HumanBodyRegion", + }, { + category: "animalWhole", + label: "Animals", + url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/AnimalWholeBody", + }, { + category: "compPhantom", + label: "Phantoms", + url: "https://speag.swiss/PD_DirectDownload/getDownloadableItems/ComputationalPhantom", + }].forEach(marketInfo => { + this.__buildViPMarketPage(marketInfo); + }) + + // this.__vipMarketPage = this.__getVipMarketPage(); }, members: { @@ -34,8 +54,19 @@ qx.Class.define("osparc.vipMarket.Market", { __getVipMarketPage: function() { const title = this.tr("ViP Models"); - const iconSrc = "@FontAwesome5Solid/users/22"; + const iconSrc = "@FontAwesome5Solid/users/20"; + const vipMarketView = new osparc.vipMarket.VipMarket(); + const page = this.addTab(title, iconSrc, vipMarketView); + return page; + }, + + __buildViPMarketPage: function(marketInfo) { + const title = marketInfo["label"]; + const iconSrc = "@FontAwesome5Solid/users/20"; const vipMarketView = new osparc.vipMarket.VipMarket(); + vipMarketView.set({ + metadataUrl: marketInfo["url"], + }); const page = this.addTab(title, iconSrc, vipMarketView); return page; }, 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 44d705d9023..7d04853f118 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -26,6 +26,15 @@ qx.Class.define("osparc.vipMarket.VipMarket", { this.__buildLayout(); }, + properties: { + metadataUrl: { + check: "String", + init: null, + nullable: false, + apply: "__fetchModels", + } + }, + statics: { curateAnatomicalModels: function(anatomicalModelsRaw) { const anatomicalModels = []; @@ -60,51 +69,77 @@ qx.Class.define("osparc.vipMarket.VipMarket", { __anatomicalModels: null, __licensedItems: null, __anatomicalModelsModel: null, - __sortByButton: null, - - __buildLayout: function() { - const leftSide = new qx.ui.container.Composite(new qx.ui.layout.VBox(10)).set({ - alignY: "middle", - }); - this._add(leftSide); - const toolbarLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(10)).set({ - alignY: "middle", - }); - leftSide.add(toolbarLayout) - - const sortModelsButtons = this.__sortByButton = new osparc.vipMarket.SortModelsButtons().set({ - alignY: "bottom", - maxHeight: 27, - }); - toolbarLayout.add(sortModelsButtons); - - const filter = new osparc.filter.TextFilter("text", "vipModels").set({ - alignY: "middle", - allowGrowY: false, - minWidth: 165, - }); - this.addListener("appear", () => filter.getChildControl("textfield").focus()); - toolbarLayout.add(filter, { - flex: 1 - }); + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "left-side": + control = new qx.ui.container.Composite(new qx.ui.layout.VBox(10)).set({ + alignY: "middle", + }); + this._add(control); + break; + case "right-side": + control = new qx.ui.container.Composite(new qx.ui.layout.VBox(10)).set({ + alignY: "middle", + }); + this._add(control, { + flex: 1 + }); + break; + case "toolbar-layout": + control = new qx.ui.container.Composite(new qx.ui.layout.HBox(10)).set({ + alignY: "middle", + }); + this.getChildControl("left-side").add(control); + break; + case "sort-button": + control = new osparc.vipMarket.SortModelsButtons().set({ + alignY: "bottom", + maxHeight: 27, + }); + this.getChildControl("toolbar-layout").add(control); + break; + case "filter-text": + control = new osparc.filter.TextFilter("text", "vipModels").set({ + alignY: "middle", + allowGrowY: false, + minWidth: 160, + backgroundColor: "transparent", + }); + this.addListener("appear", () => control.getChildControl("textfield").focus()); + this.getChildControl("toolbar-layout").add(control, { + flex: 1 + }); + break; + case "models-list": + control = new qx.ui.form.List().set({ + decorator: "no-border", + spacing: 5, + minWidth: 250, + maxWidth: 250 + }); + this.getChildControl("left-side").add(control, { + flex: 1 + }); + break; + case "models-details": + control = new osparc.vipMarket.AnatomicalModelDetails().set({ + padding: 10, + }); + this.getChildControl("right-side").add(control, { + flex: 1 + }); + break; + } + return control || this.base(arguments, id); + }, - const modelsUIList = new qx.ui.form.List().set({ - decorator: "no-border", - spacing: 5, - minWidth: 250, - maxWidth: 250 - }); - leftSide.add(modelsUIList, { - flex: 1 - }); + __buildLayout: function() { + this.getChildControl("sort-button"); + this.getChildControl("filter-text"); + const modelsUIList = this.getChildControl("models-list"); - const rightSide = new qx.ui.container.Composite(new qx.ui.layout.VBox(10)).set({ - alignY: "middle", - }); - this._add(rightSide, { - flex: 1 - }); const anatomicalModelsModel = this.__anatomicalModelsModel = new qx.data.Array(); const membersCtrl = new qx.data.controller.List(anatomicalModelsModel, modelsUIList, "name"); membersCtrl.setDelegate({ @@ -130,12 +165,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { }; this.__anatomicalModelsModel.append(qx.data.marshal.Json.createModel(loadingModel)); - const anatomicModelDetails = new osparc.vipMarket.AnatomicalModelDetails().set({ - padding: 20, - }); - rightSide.add(anatomicModelDetails, { - flex: 1 - }); + const anatomicModelDetails = this.getChildControl("models-details"); modelsUIList.addListener("changeSelection", e => { const selection = e.getData(); @@ -149,8 +179,10 @@ qx.Class.define("osparc.vipMarket.VipMarket", { } anatomicModelDetails.setAnatomicalModelsData(null); }, this); + }, - fetch("https://itis.swiss/PD_DirectDownload/getDownloadableItems/AnatomicalModels", { + __fetchModels: function(url) { + fetch(url, { method:"POST" }) .then(resp => resp.json()) @@ -185,6 +217,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { this.__populateModels(); + const anatomicModelDetails = this.getChildControl("models-details"); anatomicModelDetails.addListener("modelPurchaseRequested", e => { const { modelId, @@ -246,7 +279,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { sortModel(); models.forEach(model => this.__anatomicalModelsModel.append(qx.data.marshal.Json.createModel(model))); - this.__sortByButton.addListener("sortBy", e => { + this.getChildControl("sort-button").addListener("sortBy", e => { this.__anatomicalModelsModel.removeAll(); const sortBy = e.getData(); sortModel(sortBy); From 51c2b2897f89aae83d93c37d0a8753ebbbfcd107 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 10 Dec 2024 11:58:04 +0100 Subject: [PATCH 054/102] minor --- .../source/class/osparc/vipMarket/Market.js | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) 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 beb6c8240a8..316cd151518 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js @@ -44,22 +44,10 @@ qx.Class.define("osparc.vipMarket.Market", { url: "https://speag.swiss/PD_DirectDownload/getDownloadableItems/ComputationalPhantom", }].forEach(marketInfo => { this.__buildViPMarketPage(marketInfo); - }) - - // this.__vipMarketPage = this.__getVipMarketPage(); + }); }, members: { - __vipMarketPage: null, - - __getVipMarketPage: function() { - const title = this.tr("ViP Models"); - const iconSrc = "@FontAwesome5Solid/users/20"; - const vipMarketView = new osparc.vipMarket.VipMarket(); - const page = this.addTab(title, iconSrc, vipMarketView); - return page; - }, - __buildViPMarketPage: function(marketInfo) { const title = marketInfo["label"]; const iconSrc = "@FontAwesome5Solid/users/20"; @@ -68,6 +56,7 @@ qx.Class.define("osparc.vipMarket.Market", { metadataUrl: marketInfo["url"], }); const page = this.addTab(title, iconSrc, vipMarketView); + page.category = marketInfo["category"]; return page; }, } From bb97dec502edece028c21abb7ee4999a53d05119 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 10 Dec 2024 12:58:28 +0100 Subject: [PATCH 055/102] all models --- .../class/osparc/study/NodePricingUnits.js | 6 ++--- .../class/osparc/study/PricingUnitTiers.js | 24 ++++++++++++------- .../source/class/osparc/vipMarket/Market.js | 8 ++++--- .../class/osparc/vipMarket/VipMarket.js | 2 ++ 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js b/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js index 601a1131c0b..f6bc409fb39 100644 --- a/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js +++ b/services/static-webserver/client/source/class/osparc/study/NodePricingUnits.js @@ -125,13 +125,13 @@ qx.Class.define("osparc.study.NodePricingUnits", { } else { this._add(pricingUnitTiers); } - pricingUnitTiers.addListener("changeSelectedUnitId", e => { + pricingUnitTiers.addListener("selectPricingUnitRequested", e => { + const selectedPricingUnitId = e.getData(); if (this.isPatchNode()) { pricingUnitTiers.setEnabled(false); const pricingPlanId = this.getPricingPlanId(); - const selectedPricingUnitId = e.getData(); this.self().patchPricingUnitSelection(studyId, nodeId, pricingPlanId, selectedPricingUnitId) - // .then(() => ) + .then(() => pricingUnitTiers.setSelectedUnitId(selectedPricingUnitId)) .catch(err => { const msg = err.message || this.tr("Cannot change Tier"); osparc.FlashMessenger.getInstance().logAs(msg, "ERROR"); diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitTiers.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitTiers.js index bb41ed0a6d5..028ff5740ff 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitTiers.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitTiers.js @@ -34,13 +34,20 @@ qx.Class.define("osparc.study.PricingUnitTiers", { check: "Number", init: null, nullable: false, - event: "changeSelectedUnitId" + event: "changeSelectedUnitId", + apply: "__applySelectedUnitId", } }, + events: { + "selectPricingUnitRequested": "qx.event.type.Event", + }, + members: { + __pricingUnitTiers: null, + __buildLayout: function(pricingUnitsData, preselectedPricingUnit, changeSelectionAllowed) { - const pricingUnitTiers = []; + const pricingUnitTiers = this.__pricingUnitTiers = []; pricingUnitsData.forEach(pricingUnitData => { const pricingUnit = new osparc.data.model.PricingUnit(pricingUnitData); const pricingUnitTier = new osparc.study.PricingUnitTier(pricingUnit).set({ @@ -67,14 +74,15 @@ qx.Class.define("osparc.study.PricingUnitTiers", { pricingUnitTiers.forEach(pricingUnitTier => { pricingUnitTier.addListener("selectPricingUnit", () => { if (changeSelectionAllowed) { - // select and unselect the rest - pricingUnitTiers.forEach(puTIer => puTIer.setSelected(puTIer === pricingUnitTier)); - // and save selection - const selectedUnitId = pricingUnitTier.getUnitData().getPricingUnitId(); - this.setSelectedUnitId(selectedUnitId); + this.fireDataEvent("selectPricingUnitRequested", pricingUnitTier.getUnitData().getPricingUnitId()); } }); }); - } + }, + + __applySelectedUnitId: function(selectedUnitId) { + // select and unselect the rest + this.__pricingUnitTiers.forEach(puTIer => puTIer.setSelected(puTIer.getUnitData().getPricingUnitId() === selectedUnitId)); + }, } }); 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 316cd151518..871b3c3c2d3 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js @@ -29,18 +29,22 @@ qx.Class.define("osparc.vipMarket.Market", { [{ category: "humanWhole", label: "Humans", + icon: "@FontAwesome5Solid/users/20", url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/HumanWholeBody", }, { category: "humanRegion", label: "Humans (Region)", + icon: "@FontAwesome5Solid/users/20", url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/HumanBodyRegion", }, { category: "animalWhole", label: "Animals", + icon: "@FontAwesome5Solid/users/20", url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/AnimalWholeBody", }, { category: "compPhantom", label: "Phantoms", + icon: "@FontAwesome5Solid/users/20", url: "https://speag.swiss/PD_DirectDownload/getDownloadableItems/ComputationalPhantom", }].forEach(marketInfo => { this.__buildViPMarketPage(marketInfo); @@ -49,13 +53,11 @@ qx.Class.define("osparc.vipMarket.Market", { members: { __buildViPMarketPage: function(marketInfo) { - const title = marketInfo["label"]; - const iconSrc = "@FontAwesome5Solid/users/20"; const vipMarketView = new osparc.vipMarket.VipMarket(); vipMarketView.set({ metadataUrl: marketInfo["url"], }); - const page = this.addTab(title, iconSrc, vipMarketView); + const page = this.addTab(marketInfo["label"], marketInfo["icon"], vipMarketView); page.category = marketInfo["category"]; return page; }, 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 7d04853f118..6c42435bf35 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -105,6 +105,8 @@ qx.Class.define("osparc.vipMarket.VipMarket", { alignY: "middle", allowGrowY: false, minWidth: 160, + }); + control.getChildControl("textfield").set({ backgroundColor: "transparent", }); this.addListener("appear", () => control.getChildControl("textfield").focus()); From edc2c430150aca6a1c08ac3360ca32fdfec22336 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 10 Dec 2024 13:05:37 +0100 Subject: [PATCH 056/102] openCategory --- .../client/source/class/osparc/vipMarket/Market.js | 9 +++++++++ .../client/source/class/osparc/vipMarket/MarketWindow.js | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) 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 871b3c3c2d3..5a6585423e0 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js @@ -61,5 +61,14 @@ qx.Class.define("osparc.vipMarket.Market", { page.category = marketInfo["category"]; return page; }, + + openCategory: function(category) { + const viewFound = this.getChildControl("tabs-view").getChildren().find(view => view.category === category); + if (viewFound) { + this._openPage(viewFound); + return true; + } + return false; + }, } }); diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js b/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js index 050a67d7dab..4050b28a71c 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js @@ -50,8 +50,8 @@ qx.Class.define("osparc.vipMarket.MarketWindow", { members: { __vipMarket: null, - openVipMarket: function() { - return this.__vipMarket.openVipMarket(); + openCategory: function(category) { + return this.__vipMarket.openCategory(category); }, } }); From 7b742e0b946ad0935b2c6aab04a32dd9d551434f Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 10 Dec 2024 13:25:29 +0100 Subject: [PATCH 057/102] minor --- .../source/class/osparc/vipMarket/AnatomicalModelDetails.js | 1 + 1 file changed, 1 insertion(+) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 77009569300..e9ce769011e 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -209,6 +209,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { center: true, maxWidth: 200, alignX: "center", + visibility: anatomicalModelsData["purchased"] ? "visible" : "excluded", }); importButton.addListener("execute", () => { this.fireDataEvent("modelImportRequested", { From 932fb00f89d107501501a4afdef226fa65e5cdb2 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 10 Dec 2024 13:40:47 +0100 Subject: [PATCH 058/102] webserver part --- .../licensed_items_purchases.py | 4 +- .../licensed_items_purchases.py | 35 +++++++ .../simcore_service_webserver/application.py | 4 + .../catalog/plugin.py | 3 - .../{catalog => }/licenses/__init__.py | 0 .../licenses/_exceptions_handlers.py | 2 +- .../licenses/_licensed_items_api.py | 0 .../licenses/_licensed_items_db.py | 2 +- .../licenses/_licensed_items_handlers.py | 12 +-- .../licenses/_licensed_items_purchases_api.py | 92 +++++++++++++++++++ .../_licensed_items_purchases_handlers.py | 91 ++++++++++++++++++ .../{catalog => }/licenses/_models.py | 4 + .../{catalog => }/licenses/api.py | 0 .../{catalog => }/licenses/errors.py | 0 .../{catalog => }/licenses/plugin.py | 0 15 files changed, 236 insertions(+), 13 deletions(-) create mode 100644 packages/models-library/src/models_library/api_schemas_webserver/licensed_items_purchases.py rename services/web/server/src/simcore_service_webserver/{catalog => }/licenses/__init__.py (100%) rename services/web/server/src/simcore_service_webserver/{catalog => }/licenses/_exceptions_handlers.py (94%) rename services/web/server/src/simcore_service_webserver/{catalog => }/licenses/_licensed_items_api.py (100%) rename services/web/server/src/simcore_service_webserver/{catalog => }/licenses/_licensed_items_db.py (99%) rename services/web/server/src/simcore_service_webserver/{catalog => }/licenses/_licensed_items_handlers.py (92%) create mode 100644 services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_api.py create mode 100644 services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_handlers.py rename services/web/server/src/simcore_service_webserver/{catalog => }/licenses/_models.py (92%) rename services/web/server/src/simcore_service_webserver/{catalog => }/licenses/api.py (100%) rename services/web/server/src/simcore_service_webserver/{catalog => }/licenses/errors.py (100%) rename services/web/server/src/simcore_service_webserver/{catalog => }/licenses/plugin.py (100%) diff --git a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py index 6afe928e88d..c147b411465 100644 --- a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py +++ b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py @@ -17,8 +17,8 @@ class LicensedItemPurchaseGet(BaseModel): licensed_item_purchase_id: LicensedItemPurchaseID product_name: ProductName licensed_item_id: LicensedItemID - wallet_id: WalletID | None - wallet_name: str | None + wallet_id: WalletID + wallet_name: str pricing_unit_cost_id: PricingUnitCostId pricing_unit_cost: Decimal start_at: datetime diff --git a/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_purchases.py b/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_purchases.py new file mode 100644 index 00000000000..1005019c58d --- /dev/null +++ b/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_purchases.py @@ -0,0 +1,35 @@ +from datetime import datetime +from decimal import Decimal +from typing import NamedTuple + +from models_library.licensed_items import LicensedItemID +from models_library.products import ProductName +from models_library.resource_tracker import PricingUnitCostId +from models_library.resource_tracker_licensed_items_purchases import ( + LicensedItemPurchaseID, +) +from models_library.users import UserID +from models_library.wallets import WalletID +from pydantic import PositiveInt + +from ._base import OutputSchema + + +class LicensedItemPurchaseGet(OutputSchema): + licensed_item_purchase_id: LicensedItemPurchaseID + product_name: ProductName + licensed_item_id: LicensedItemID + wallet_id: WalletID + pricing_unit_cost_id: PricingUnitCostId + pricing_unit_cost: Decimal + start_at: datetime + expire_at: datetime + num_of_seats: int + purchased_by_user: UserID + purchased_at: datetime + modified: datetime + + +class LicensedItemPurchaseGetPage(NamedTuple): + items: list[LicensedItemPurchaseGet] + total: PositiveInt diff --git a/services/web/server/src/simcore_service_webserver/application.py b/services/web/server/src/simcore_service_webserver/application.py index 79477051ddb..a868e345380 100644 --- a/services/web/server/src/simcore_service_webserver/application.py +++ b/services/web/server/src/simcore_service_webserver/application.py @@ -26,6 +26,7 @@ from .garbage_collector.plugin import setup_garbage_collector from .groups.plugin import setup_groups from .invitations.plugin import setup_invitations +from .licenses.plugin import setup_licenses from .login.plugin import setup_login from .long_running_tasks import setup_long_running_tasks from .meta_modeling.plugin import setup_meta_modeling @@ -139,6 +140,9 @@ def create_application() -> web.Application: setup_version_control(app) setup_meta_modeling(app) + # licenses + setup_licenses(app) + # tagging setup_scicrunch(app) setup_tags(app) diff --git a/services/web/server/src/simcore_service_webserver/catalog/plugin.py b/services/web/server/src/simcore_service_webserver/catalog/plugin.py index 74c36bcbcb4..2af8da917f0 100644 --- a/services/web/server/src/simcore_service_webserver/catalog/plugin.py +++ b/services/web/server/src/simcore_service_webserver/catalog/plugin.py @@ -9,7 +9,6 @@ from servicelib.aiohttp.application_setup import ModuleCategory, app_module_setup from . import _handlers, _tags_handlers -from .licenses.plugin import setup_licenses _logger = logging.getLogger(__name__) @@ -28,8 +27,6 @@ def setup_catalog(app: web.Application): for route_def in _handlers.routes ) - setup_licenses(app) - app.add_routes(_handlers.routes) app.add_routes(_tags_handlers.routes) diff --git a/services/web/server/src/simcore_service_webserver/catalog/licenses/__init__.py b/services/web/server/src/simcore_service_webserver/licenses/__init__.py similarity index 100% rename from services/web/server/src/simcore_service_webserver/catalog/licenses/__init__.py rename to services/web/server/src/simcore_service_webserver/licenses/__init__.py diff --git a/services/web/server/src/simcore_service_webserver/catalog/licenses/_exceptions_handlers.py b/services/web/server/src/simcore_service_webserver/licenses/_exceptions_handlers.py similarity index 94% rename from services/web/server/src/simcore_service_webserver/catalog/licenses/_exceptions_handlers.py rename to services/web/server/src/simcore_service_webserver/licenses/_exceptions_handlers.py index 0abb7671b16..a4b5ada8925 100644 --- a/services/web/server/src/simcore_service_webserver/catalog/licenses/_exceptions_handlers.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_exceptions_handlers.py @@ -2,7 +2,7 @@ from servicelib.aiohttp import status -from ...exception_handling import ( +from ..exception_handling import ( ExceptionToHttpErrorMap, HttpErrorInfo, exception_handling_decorator, diff --git a/services/web/server/src/simcore_service_webserver/catalog/licenses/_licensed_items_api.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py similarity index 100% rename from services/web/server/src/simcore_service_webserver/catalog/licenses/_licensed_items_api.py rename to services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py diff --git a/services/web/server/src/simcore_service_webserver/catalog/licenses/_licensed_items_db.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_db.py similarity index 99% rename from services/web/server/src/simcore_service_webserver/catalog/licenses/_licensed_items_db.py rename to services/web/server/src/simcore_service_webserver/licenses/_licensed_items_db.py index fc14221ff91..e468c10f55d 100644 --- a/services/web/server/src/simcore_service_webserver/catalog/licenses/_licensed_items_db.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_db.py @@ -27,7 +27,7 @@ from sqlalchemy.ext.asyncio import AsyncConnection from sqlalchemy.sql import select -from ...db.plugin import get_asyncpg_engine +from ..db.plugin import get_asyncpg_engine from .errors import LicensedItemNotFoundError _logger = logging.getLogger(__name__) diff --git a/services/web/server/src/simcore_service_webserver/catalog/licenses/_licensed_items_handlers.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_handlers.py similarity index 92% rename from services/web/server/src/simcore_service_webserver/catalog/licenses/_licensed_items_handlers.py rename to services/web/server/src/simcore_service_webserver/licenses/_licensed_items_handlers.py index 6ed227500e5..355d9658ebb 100644 --- a/services/web/server/src/simcore_service_webserver/catalog/licenses/_licensed_items_handlers.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_handlers.py @@ -17,10 +17,10 @@ from servicelib.mimetype_constants import MIMETYPE_APPLICATION_JSON from servicelib.rest_constants import RESPONSE_MODEL_POLICY -from ..._meta import API_VTAG as VTAG -from ...login.decorators import login_required -from ...security.decorators import permission_required -from ...utils_aiohttp import envelope_json_response +from .._meta import API_VTAG as VTAG +from ..login.decorators import login_required +from ..security.decorators import permission_required +from ..utils_aiohttp import envelope_json_response from . import _licensed_items_api from ._exceptions_handlers import handle_plugin_requests_exceptions from ._models import ( @@ -40,7 +40,7 @@ @login_required @permission_required("catalog/licensed-items.*") @handle_plugin_requests_exceptions -async def list_workspaces(request: web.Request): +async def list_licensed_items(request: web.Request): req_ctx = LicensedItemsRequestContext.model_validate(request) query_params: LicensedItemsListQueryParams = parse_request_query_parameters_as( LicensedItemsListQueryParams, request @@ -77,7 +77,7 @@ async def list_workspaces(request: web.Request): @login_required @permission_required("catalog/licensed-items.*") @handle_plugin_requests_exceptions -async def get_workspace(request: web.Request): +async def get_licensed_item(request: web.Request): req_ctx = LicensedItemsRequestContext.model_validate(request) path_params = parse_request_path_parameters_as(LicensedItemsPathParams, request) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_api.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_api.py new file mode 100644 index 00000000000..b4e3b16a0ef --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_api.py @@ -0,0 +1,92 @@ +import logging + +from aiohttp import web +from models_library.api_schemas_resource_usage_tracker import ( + licensed_items_purchases as rut_licensed_items_purchases, +) +from models_library.api_schemas_webserver import ( + licensed_items_purchases as webserver_licensed_items_purchases, +) +from models_library.products import ProductName +from models_library.resource_tracker_licensed_items_purchases import ( + LicensedItemPurchaseID, +) +from models_library.rest_ordering import OrderBy +from models_library.wallets import WalletID +from servicelib.rabbitmq.rpc_interfaces.resource_usage_tracker import ( + licensed_items_purchases, +) + +from ..rabbitmq import get_rabbitmq_rpc_client + +_logger = logging.getLogger(__name__) + + +async def list_licensed_items_purchases( + app: web.Application, + product_name: ProductName, + wallet_id: WalletID, + offset: int, + limit: int, + order_by: OrderBy, +) -> webserver_licensed_items_purchases.LicensedItemPurchaseGetPage: + rpc_client = get_rabbitmq_rpc_client(app) + result: rut_licensed_items_purchases.LicensedItemsPurchasesPage = ( + await licensed_items_purchases.get_licensed_items_purchases_page( + rpc_client, + product_name=product_name, + wallet_id=wallet_id, + offset=offset, + limit=limit, + order_by=order_by, + ) + ) + return webserver_licensed_items_purchases.LicensedItemPurchaseGetPage( + total=result.total, + items=[ + webserver_licensed_items_purchases.LicensedItemPurchaseGet( + licensed_item_purchase_id=item.licensed_item_purchase_id, + product_name=item.product_name, + licensed_item_id=item.licensed_item_id, + wallet_id=item.wallet_id, + pricing_unit_cost_id=item.pricing_unit_cost_id, + pricing_unit_cost=item.pricing_unit_cost, + start_at=item.start_at, + expire_at=item.expire_at, + num_of_seats=item.num_of_seats, + purchased_by_user=item.purchased_by_user, + purchased_at=item.purchased_at, + modified=item.modified, + ) + for item in result.items + ], + ) + + +async def get_licensed_item_purchase( + app: web.Application, + product_name: ProductName, + licensed_item_purchase_id: LicensedItemPurchaseID, +) -> webserver_licensed_items_purchases.LicensedItemPurchaseGet: + rpc_client = get_rabbitmq_rpc_client(app) + licensed_item_get: rut_licensed_items_purchases.LicensedItemPurchaseGet = ( + await licensed_items_purchases.get_licensed_item_purchase( + rpc_client, + product_name=product_name, + licensed_item_purchase_id=licensed_item_purchase_id, + ) + ) + return webserver_licensed_items_purchases.LicensedItemPurchaseGet( + licensed_item_purchase_id=licensed_item_get.licensed_item_purchase_id, + product_name=licensed_item_get.product_name, + licensed_item_id=licensed_item_get.licensed_item_id, + wallet_id=licensed_item_get.wallet_id, + pricing_unit_cost_id=licensed_item_get.pricing_unit_cost_id, + pricing_unit_cost=licensed_item_get.pricing_unit_cost, + start_at=licensed_item_get.start_at, + expire_at=licensed_item_get.expire_at, + num_of_seats=licensed_item_get.num_of_seats, + purchased_by_user=licensed_item_get.purchased_by_user, + purchased_at=licensed_item_get.purchased_at, + modified=licensed_item_get.modified, + ) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_handlers.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_handlers.py new file mode 100644 index 00000000000..990942b883c --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_handlers.py @@ -0,0 +1,91 @@ +import logging + +from aiohttp import web +from models_library.api_schemas_webserver.licensed_items import LicensedItemGet +from models_library.api_schemas_webserver.licensed_items_purchases import ( + LicensedItemPurchaseGet, + LicensedItemPurchaseGetPage, +) +from models_library.rest_ordering import OrderBy +from models_library.rest_pagination import Page +from models_library.rest_pagination_utils import paginate_data +from servicelib.aiohttp.requests_validation import ( + parse_request_path_parameters_as, + parse_request_query_parameters_as, +) +from servicelib.mimetype_constants import MIMETYPE_APPLICATION_JSON +from servicelib.rest_constants import RESPONSE_MODEL_POLICY + +from .._meta import API_VTAG as VTAG +from ..login.decorators import login_required +from ..security.decorators import permission_required +from ..utils_aiohttp import envelope_json_response +from . import _licensed_items_purchases_api +from ._exceptions_handlers import handle_plugin_requests_exceptions +from ._models import LicensedItemsPurchasesPathParams, LicensedItemsRequestContext + +_logger = logging.getLogger(__name__) + + +routes = web.RouteTableDef() + + +@routes.get( + f"/{VTAG}/catalog/licensed-items-purchases", name="list_licensed_items_purchases" +) +@login_required +@permission_required("catalog/licensed-items.*") +@handle_plugin_requests_exceptions +async def list_licensed_items_purchases(request: web.Request): + req_ctx = LicensedItemsRequestContext.model_validate(request) + query_params: LicensedItemsListQueryParams = parse_request_query_parameters_as( + LicensedItemsListQueryParams, request + ) + + licensed_item_purchase_get_page: LicensedItemPurchaseGetPage = ( + await _licensed_items_purchases_api.list_licensed_items_purchases( + app=request.app, + product_name=req_ctx.product_name, + offset=query_params.offset, + limit=query_params.limit, + order_by=OrderBy.model_construct(**query_params.order_by.model_dump()), + ) + ) + + page = Page[LicensedItemGet].model_validate( + paginate_data( + chunk=licensed_item_purchase_get_page.items, + request_url=request.url, + total=licensed_item_purchase_get_page.total, + limit=query_params.limit, + offset=query_params.offset, + ) + ) + return web.Response( + text=page.model_dump_json(**RESPONSE_MODEL_POLICY), + content_type=MIMETYPE_APPLICATION_JSON, + ) + + +@routes.get( + f"/{VTAG}/catalog/licensed-items-purchases/{{licensed_item_purchase_id}}", + name="get_licensed_item_purchase", +) +@login_required +@permission_required("catalog/licensed-items.*") +@handle_plugin_requests_exceptions +async def get_licensed_item_purchase(request: web.Request): + req_ctx = LicensedItemsRequestContext.model_validate(request) + path_params = parse_request_path_parameters_as( + LicensedItemsPurchasesPathParams, request + ) + + licensed_item_purchase_get: LicensedItemPurchaseGet = ( + await _licensed_items_purchases_api.get_licensed_item_purchase( + app=request.app, + product_name=req_ctx.product_name, + licensed_item_purchase_id=path_params.licensed_item_purchase_id, + ) + ) + + return envelope_json_response(licensed_item_purchase_get) diff --git a/services/web/server/src/simcore_service_webserver/catalog/licenses/_models.py b/services/web/server/src/simcore_service_webserver/licenses/_models.py similarity index 92% rename from services/web/server/src/simcore_service_webserver/catalog/licenses/_models.py rename to services/web/server/src/simcore_service_webserver/licenses/_models.py index 884cc291431..5c91caa45e0 100644 --- a/services/web/server/src/simcore_service_webserver/catalog/licenses/_models.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_models.py @@ -52,3 +52,7 @@ class LicensedItemsBodyParams(BaseModel): num_of_seats: int model_config = ConfigDict(extra="forbid") + + +class LicensedItemsPurchasesPathParams(StrictRequestParameters): + licensed_item_purchase_id: LicensedItemPurchaseID diff --git a/services/web/server/src/simcore_service_webserver/catalog/licenses/api.py b/services/web/server/src/simcore_service_webserver/licenses/api.py similarity index 100% rename from services/web/server/src/simcore_service_webserver/catalog/licenses/api.py rename to services/web/server/src/simcore_service_webserver/licenses/api.py diff --git a/services/web/server/src/simcore_service_webserver/catalog/licenses/errors.py b/services/web/server/src/simcore_service_webserver/licenses/errors.py similarity index 100% rename from services/web/server/src/simcore_service_webserver/catalog/licenses/errors.py rename to services/web/server/src/simcore_service_webserver/licenses/errors.py diff --git a/services/web/server/src/simcore_service_webserver/catalog/licenses/plugin.py b/services/web/server/src/simcore_service_webserver/licenses/plugin.py similarity index 100% rename from services/web/server/src/simcore_service_webserver/catalog/licenses/plugin.py rename to services/web/server/src/simcore_service_webserver/licenses/plugin.py From 06f5743883958abc7f1804b77aaf90dc28b0ccb0 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 10 Dec 2024 14:12:00 +0100 Subject: [PATCH 059/102] webserver part --- .../licensed_items_purchases.py | 2 +- .../licenses/_licensed_items_purchases_api.py | 23 +++++- .../_licensed_items_purchases_handlers.py | 72 +++++++++++-------- .../licenses/_models.py | 25 ++++++- 4 files changed, 87 insertions(+), 35 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_purchases.py b/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_purchases.py index 1005019c58d..0264e713256 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_purchases.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/licensed_items_purchases.py @@ -27,7 +27,7 @@ class LicensedItemPurchaseGet(OutputSchema): num_of_seats: int purchased_by_user: UserID purchased_at: datetime - modified: datetime + modified_at: datetime class LicensedItemPurchaseGetPage(NamedTuple): diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_api.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_api.py index b4e3b16a0ef..4aae82ae768 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_api.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_api.py @@ -12,12 +12,14 @@ LicensedItemPurchaseID, ) from models_library.rest_ordering import OrderBy +from models_library.users import UserID from models_library.wallets import WalletID from servicelib.rabbitmq.rpc_interfaces.resource_usage_tracker import ( licensed_items_purchases, ) from ..rabbitmq import get_rabbitmq_rpc_client +from ..wallets.api import get_wallet_by_user _logger = logging.getLogger(__name__) @@ -25,11 +27,18 @@ async def list_licensed_items_purchases( app: web.Application, product_name: ProductName, + user_id: UserID, wallet_id: WalletID, offset: int, limit: int, order_by: OrderBy, ) -> webserver_licensed_items_purchases.LicensedItemPurchaseGetPage: + + # Check whether user has access to the wallet + await get_wallet_by_user( + app, user_id=user_id, wallet_id=wallet_id, product_name=product_name + ) + rpc_client = get_rabbitmq_rpc_client(app) result: rut_licensed_items_purchases.LicensedItemsPurchasesPage = ( await licensed_items_purchases.get_licensed_items_purchases_page( @@ -56,7 +65,7 @@ async def list_licensed_items_purchases( num_of_seats=item.num_of_seats, purchased_by_user=item.purchased_by_user, purchased_at=item.purchased_at, - modified=item.modified, + modified_at=item.modified, ) for item in result.items ], @@ -66,6 +75,7 @@ async def list_licensed_items_purchases( async def get_licensed_item_purchase( app: web.Application, product_name: ProductName, + user_id: UserID, licensed_item_purchase_id: LicensedItemPurchaseID, ) -> webserver_licensed_items_purchases.LicensedItemPurchaseGet: rpc_client = get_rabbitmq_rpc_client(app) @@ -76,6 +86,15 @@ async def get_licensed_item_purchase( licensed_item_purchase_id=licensed_item_purchase_id, ) ) + + # Check whether user has access to the wallet + await get_wallet_by_user( + app, + user_id=user_id, + wallet_id=licensed_item_get.wallet_id, + product_name=product_name, + ) + return webserver_licensed_items_purchases.LicensedItemPurchaseGet( licensed_item_purchase_id=licensed_item_get.licensed_item_purchase_id, product_name=licensed_item_get.product_name, @@ -88,5 +107,5 @@ async def get_licensed_item_purchase( num_of_seats=licensed_item_get.num_of_seats, purchased_by_user=licensed_item_get.purchased_by_user, purchased_at=licensed_item_get.purchased_at, - modified=licensed_item_get.modified, + modified_at=licensed_item_get.modified, ) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_handlers.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_handlers.py index 990942b883c..bd1c0197d09 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_handlers.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_handlers.py @@ -1,7 +1,6 @@ import logging from aiohttp import web -from models_library.api_schemas_webserver.licensed_items import LicensedItemGet from models_library.api_schemas_webserver.licensed_items_purchases import ( LicensedItemPurchaseGet, LicensedItemPurchaseGetPage, @@ -20,39 +19,74 @@ from ..login.decorators import login_required from ..security.decorators import permission_required from ..utils_aiohttp import envelope_json_response +from ..wallets._handlers import WalletsPathParams from . import _licensed_items_purchases_api from ._exceptions_handlers import handle_plugin_requests_exceptions -from ._models import LicensedItemsPurchasesPathParams, LicensedItemsRequestContext +from ._models import ( + LicensedItemsPurchasesListQueryParams, + LicensedItemsPurchasesPathParams, + LicensedItemsRequestContext, +) _logger = logging.getLogger(__name__) - routes = web.RouteTableDef() @routes.get( - f"/{VTAG}/catalog/licensed-items-purchases", name="list_licensed_items_purchases" + f"/{VTAG}/catalog/licensed-items-purchases/{{licensed_item_purchase_id}}", + name="get_licensed_item_purchase", +) +@login_required +@permission_required("catalog/licensed-items.*") +@handle_plugin_requests_exceptions +async def get_licensed_item_purchase(request: web.Request): + req_ctx = LicensedItemsRequestContext.model_validate(request) + path_params = parse_request_path_parameters_as( + LicensedItemsPurchasesPathParams, request + ) + + licensed_item_purchase_get: LicensedItemPurchaseGet = ( + await _licensed_items_purchases_api.get_licensed_item_purchase( + app=request.app, + product_name=req_ctx.product_name, + user_id=req_ctx.user_id, + licensed_item_purchase_id=path_params.licensed_item_purchase_id, + ) + ) + + return envelope_json_response(licensed_item_purchase_get) + + +@routes.get( + f"/{VTAG}/wallets/{{wallet_id}}/licensed-items-purchases", + name="list_wallet_licensed_items_purchases", ) @login_required @permission_required("catalog/licensed-items.*") @handle_plugin_requests_exceptions async def list_licensed_items_purchases(request: web.Request): req_ctx = LicensedItemsRequestContext.model_validate(request) - query_params: LicensedItemsListQueryParams = parse_request_query_parameters_as( - LicensedItemsListQueryParams, request + path_params = parse_request_path_parameters_as(WalletsPathParams, request) + query_params: LicensedItemsPurchasesListQueryParams = ( + parse_request_query_parameters_as( + LicensedItemsPurchasesListQueryParams, request + ) ) licensed_item_purchase_get_page: LicensedItemPurchaseGetPage = ( await _licensed_items_purchases_api.list_licensed_items_purchases( app=request.app, product_name=req_ctx.product_name, + user_id=req_ctx.user_id, + wallet_id=path_params.wallet_id, offset=query_params.offset, limit=query_params.limit, order_by=OrderBy.model_construct(**query_params.order_by.model_dump()), ) ) - page = Page[LicensedItemGet].model_validate( + page = Page[LicensedItemPurchaseGet].model_validate( paginate_data( chunk=licensed_item_purchase_get_page.items, request_url=request.url, @@ -65,27 +99,3 @@ async def list_licensed_items_purchases(request: web.Request): text=page.model_dump_json(**RESPONSE_MODEL_POLICY), content_type=MIMETYPE_APPLICATION_JSON, ) - - -@routes.get( - f"/{VTAG}/catalog/licensed-items-purchases/{{licensed_item_purchase_id}}", - name="get_licensed_item_purchase", -) -@login_required -@permission_required("catalog/licensed-items.*") -@handle_plugin_requests_exceptions -async def get_licensed_item_purchase(request: web.Request): - req_ctx = LicensedItemsRequestContext.model_validate(request) - path_params = parse_request_path_parameters_as( - LicensedItemsPurchasesPathParams, request - ) - - licensed_item_purchase_get: LicensedItemPurchaseGet = ( - await _licensed_items_purchases_api.get_licensed_item_purchase( - app=request.app, - product_name=req_ctx.product_name, - licensed_item_purchase_id=path_params.licensed_item_purchase_id, - ) - ) - - return envelope_json_response(licensed_item_purchase_get) 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 5c91caa45e0..2d8514e28e9 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_models.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_models.py @@ -2,6 +2,9 @@ from models_library.basic_types import IDStr from models_library.licensed_items import LicensedItemID +from models_library.resource_tracker_licensed_items_purchases import ( + LicensedItemPurchaseID, +) from models_library.rest_base import RequestParameters, StrictRequestParameters from models_library.rest_ordering import ( OrderBy, @@ -14,7 +17,7 @@ from pydantic import BaseModel, ConfigDict, Field from servicelib.request_keys import RQT_USERID_KEY -from ..._constants import RQ_PRODUCT_KEY +from .._constants import RQ_PRODUCT_KEY _logger = logging.getLogger(__name__) @@ -56,3 +59,23 @@ class LicensedItemsBodyParams(BaseModel): class LicensedItemsPurchasesPathParams(StrictRequestParameters): licensed_item_purchase_id: LicensedItemPurchaseID + + +_LicensedItemsPurchasesListOrderQueryParams: type[ + RequestParameters +] = create_ordering_query_model_class( + ordering_fields={ + "purchased_at", + "modified_at", + "name", + }, + default=OrderBy(field=IDStr("purchased_at"), direction=OrderDirection.DESC), + ordering_fields_api_to_column_map={"modified_at": "modified"}, +) + + +class LicensedItemsPurchasesListQueryParams( + PageQueryParameters, + _LicensedItemsPurchasesListOrderQueryParams, # type: ignore[misc, valid-type] +): + ... From dbd1ffe7f3c7ae16de8cb00d377f1db2dcaa7a0e Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 10 Dec 2024 14:25:59 +0100 Subject: [PATCH 060/102] open api specs --- ...g_licensed_items.py => _licensed_items.py} | 6 +- .../web-server/_licensed_items_purchases.py | 55 +++ api/specs/web-server/openapi.py | 3 +- .../api/v0/openapi.yaml | 382 +++++++++++++----- .../_licensed_items_purchases_handlers.py | 4 +- .../licenses/errors.py | 2 +- 6 files changed, 339 insertions(+), 113 deletions(-) rename api/specs/web-server/{_catalog_licensed_items.py => _licensed_items.py} (90%) create mode 100644 api/specs/web-server/_licensed_items_purchases.py diff --git a/api/specs/web-server/_catalog_licensed_items.py b/api/specs/web-server/_licensed_items.py similarity index 90% rename from api/specs/web-server/_catalog_licensed_items.py rename to api/specs/web-server/_licensed_items.py index 29b39853c95..377d6b9ab94 100644 --- a/api/specs/web-server/_catalog_licensed_items.py +++ b/api/specs/web-server/_licensed_items.py @@ -14,10 +14,8 @@ from models_library.generics import Envelope from models_library.rest_error import EnvelopedError from simcore_service_webserver._meta import API_VTAG -from simcore_service_webserver.catalog.licenses._exceptions_handlers import ( - _TO_HTTP_ERROR_MAP, -) -from simcore_service_webserver.catalog.licenses._models import ( +from simcore_service_webserver.licenses._exceptions_handlers import _TO_HTTP_ERROR_MAP +from simcore_service_webserver.licenses._models import ( LicensedItemsBodyParams, LicensedItemsListQueryParams, LicensedItemsPathParams, diff --git a/api/specs/web-server/_licensed_items_purchases.py b/api/specs/web-server/_licensed_items_purchases.py new file mode 100644 index 00000000000..1b3f4b7cf71 --- /dev/null +++ b/api/specs/web-server/_licensed_items_purchases.py @@ -0,0 +1,55 @@ +""" Helper script to generate OAS automatically +""" + +# pylint: disable=redefined-outer-name +# pylint: disable=unused-argument +# pylint: disable=unused-variable +# pylint: disable=too-many-arguments + +from typing import Annotated + +from _common import as_query +from fastapi import APIRouter, Depends +from models_library.api_schemas_webserver.licensed_items_purchases import ( + LicensedItemPurchaseGet, +) +from models_library.generics import Envelope +from models_library.rest_error import EnvelopedError +from simcore_service_webserver._meta import API_VTAG +from simcore_service_webserver.licenses._exceptions_handlers import _TO_HTTP_ERROR_MAP +from simcore_service_webserver.licenses._models import ( + LicensedItemsPurchasesListQueryParams, + LicensedItemsPurchasesPathParams, +) +from simcore_service_webserver.wallets._handlers import WalletsPathParams + +router = APIRouter( + prefix=f"/{API_VTAG}", + tags=[ + "licenses", + ], + responses={ + i.status_code: {"model": EnvelopedError} for i in _TO_HTTP_ERROR_MAP.values() + }, +) + + +@router.get( + "/wallets/{wallet_id}/licensed-items-purchases", + response_model=Envelope[list[LicensedItemPurchaseGet]], +) +async def list_wallet_licensed_items_purchases( + _path: Annotated[WalletsPathParams, Depends()], + _query: Annotated[as_query(LicensedItemsPurchasesListQueryParams), Depends()], +): + ... + + +@router.get( + "/licensed-items-purchases/{licensed_item_purchase_id}", + response_model=Envelope[LicensedItemPurchaseGet], +) +async def get_licensed_item_purchase( + _path: Annotated[LicensedItemsPurchasesPathParams, Depends()], +): + ... diff --git a/api/specs/web-server/openapi.py b/api/specs/web-server/openapi.py index 77e656efdaa..54b0aa4361e 100644 --- a/api/specs/web-server/openapi.py +++ b/api/specs/web-server/openapi.py @@ -31,11 +31,12 @@ "_announcements", "_catalog", "_catalog_tags", # MUST BE after _catalog - "_catalog_licensed_items", "_computations", "_exporter", "_folders", "_long_running_tasks", + "_licensed_items", + "_licensed_items_purchases", "_metamodeling", "_nih_sparc", "_nih_sparc_redirections", diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index 0ab1f87a5c1..890d5b77af7 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -2357,108 +2357,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Envelope_CatalogServiceGet_' - /v0/catalog/licensed-items: - get: - tags: - - licenses - - catalog - summary: List Licensed Items - operationId: list_licensed_items - parameters: - - name: order_by - in: query - required: false - schema: - type: string - contentMediaType: application/json - contentSchema: {} - default: '{"field":"modified","direction":"desc"}' - title: Order By - - name: limit - in: query - required: false - schema: - type: integer - default: 20 - title: Limit - - name: offset - in: query - required: false - schema: - type: integer - default: 0 - title: Offset - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Envelope_list_LicensedItemGet__' - '404': - content: - application/json: - schema: - $ref: '#/components/schemas/EnvelopedError' - description: Not Found - /v0/catalog/licensed-items/{licensed_item_id}: - get: - tags: - - licenses - - catalog - summary: Get Licensed Item - operationId: get_licensed_item - parameters: - - name: licensed_item_id - in: path - required: true - schema: - type: string - format: uuid - title: Licensed Item Id - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Envelope_LicensedItemGet_' - '404': - content: - application/json: - schema: - $ref: '#/components/schemas/EnvelopedError' - description: Not Found - /v0/catalog/licensed-items/{licensed_item_id}:purchase: - post: - tags: - - licenses - - catalog - summary: Purchase Licensed Item - operationId: purchase_licensed_item - parameters: - - name: licensed_item_id - in: path - required: true - schema: - type: string - format: uuid - title: Licensed Item Id - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/LicensedItemsBodyParams' - responses: - '204': - description: Successful Response - '404': - content: - application/json: - schema: - $ref: '#/components/schemas/EnvelopedError' - description: Not Found /v0/computations/{project_id}: get: tags: @@ -3027,6 +2925,186 @@ paths: content: application/json: schema: {} + /v0/catalog/licensed-items: + get: + tags: + - licenses + - catalog + summary: List Licensed Items + operationId: list_licensed_items + parameters: + - name: order_by + in: query + required: false + schema: + type: string + contentMediaType: application/json + contentSchema: {} + default: '{"field":"modified","direction":"desc"}' + title: Order By + - name: limit + in: query + required: false + schema: + type: integer + default: 20 + title: Limit + - name: offset + in: query + required: false + schema: + type: integer + default: 0 + title: Offset + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_LicensedItemGet__' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + /v0/catalog/licensed-items/{licensed_item_id}: + get: + tags: + - licenses + - catalog + summary: Get Licensed Item + operationId: get_licensed_item + parameters: + - name: licensed_item_id + in: path + required: true + schema: + type: string + format: uuid + title: Licensed Item Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_LicensedItemGet_' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + /v0/catalog/licensed-items/{licensed_item_id}:purchase: + post: + tags: + - licenses + - catalog + summary: Purchase Licensed Item + operationId: purchase_licensed_item + parameters: + - name: licensed_item_id + in: path + required: true + schema: + type: string + format: uuid + title: Licensed Item Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LicensedItemsBodyParams' + responses: + '204': + description: Successful Response + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + /v0/wallets/{wallet_id}/licensed-items-purchases: + get: + tags: + - licenses + summary: List Wallet Licensed Items Purchases + operationId: list_wallet_licensed_items_purchases + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + - name: order_by + in: query + required: false + schema: + type: string + contentMediaType: application/json + contentSchema: {} + default: '{"field":"purchased_at","direction":"desc"}' + title: Order By + - name: limit + in: query + required: false + schema: + type: integer + default: 20 + title: Limit + - name: offset + in: query + required: false + schema: + type: integer + default: 0 + title: Offset + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_LicensedItemPurchaseGet__' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + /v0/licensed-items-purchases/{licensed_item_purchase_id}: + get: + tags: + - licenses + summary: Get Licensed Item Purchase + operationId: get_licensed_item_purchase + parameters: + - name: licensed_item_purchase_id + in: path + required: true + schema: + type: string + format: uuid + title: Licensed Item Purchase Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_LicensedItemPurchaseGet_' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found /v0/projects/{project_uuid}/checkpoint/{ref_id}/iterations: get: tags: @@ -7835,6 +7913,19 @@ components: title: Error type: object title: Envelope[LicensedItemGet] + Envelope_LicensedItemPurchaseGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/LicensedItemPurchaseGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[LicensedItemPurchaseGet] Envelope_Log_: properties: data: @@ -8616,6 +8707,22 @@ components: title: Error type: object title: Envelope[list[LicensedItemGet]] + Envelope_list_LicensedItemPurchaseGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/LicensedItemPurchaseGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[LicensedItemPurchaseGet]] Envelope_list_OsparcCreditsAggregatedByServiceGet__: properties: data: @@ -10029,6 +10136,71 @@ components: - createdAt - modifiedAt title: LicensedItemGet + LicensedItemPurchaseGet: + properties: + licensedItemPurchaseId: + type: string + format: uuid + title: Licenseditempurchaseid + productName: + type: string + title: Productname + licensedItemId: + type: string + format: uuid + title: Licenseditemid + walletId: + type: integer + exclusiveMinimum: true + title: Walletid + minimum: 0 + pricingUnitCostId: + type: integer + exclusiveMinimum: true + title: Pricingunitcostid + minimum: 0 + pricingUnitCost: + type: string + title: Pricingunitcost + startAt: + type: string + format: date-time + title: Startat + expireAt: + type: string + format: date-time + title: Expireat + numOfSeats: + type: integer + title: Numofseats + purchasedByUser: + type: integer + exclusiveMinimum: true + title: Purchasedbyuser + minimum: 0 + purchasedAt: + type: string + format: date-time + title: Purchasedat + modifiedAt: + type: string + format: date-time + title: Modifiedat + type: object + required: + - licensedItemPurchaseId + - productName + - licensedItemId + - walletId + - pricingUnitCostId + - pricingUnitCost + - startAt + - expireAt + - numOfSeats + - purchasedByUser + - purchasedAt + - modifiedAt + title: LicensedItemPurchaseGet LicensedItemsBodyParams: properties: wallet_id: @@ -10036,14 +10208,14 @@ components: exclusiveMinimum: true title: Wallet Id minimum: 0 - num_of_seeds: + num_of_seats: type: integer - title: Num Of Seeds + title: Num Of Seats additionalProperties: false type: object required: - wallet_id - - num_of_seeds + - num_of_seats title: LicensedItemsBodyParams LicensedResourceType: type: string diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_handlers.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_handlers.py index bd1c0197d09..95f48ebbd0e 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_handlers.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_purchases_handlers.py @@ -34,7 +34,7 @@ @routes.get( - f"/{VTAG}/catalog/licensed-items-purchases/{{licensed_item_purchase_id}}", + f"/{VTAG}/licensed-items-purchases/{{licensed_item_purchase_id}}", name="get_licensed_item_purchase", ) @login_required @@ -65,7 +65,7 @@ async def get_licensed_item_purchase(request: web.Request): @login_required @permission_required("catalog/licensed-items.*") @handle_plugin_requests_exceptions -async def list_licensed_items_purchases(request: web.Request): +async def list_wallet_licensed_items_purchases(request: web.Request): req_ctx = LicensedItemsRequestContext.model_validate(request) path_params = parse_request_path_parameters_as(WalletsPathParams, request) query_params: LicensedItemsPurchasesListQueryParams = ( diff --git a/services/web/server/src/simcore_service_webserver/licenses/errors.py b/services/web/server/src/simcore_service_webserver/licenses/errors.py index 0c8bae69b03..0313499429e 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/errors.py +++ b/services/web/server/src/simcore_service_webserver/licenses/errors.py @@ -1,4 +1,4 @@ -from ...errors import WebServerBaseError +from ..errors import WebServerBaseError class LicensesValueError(WebServerBaseError, ValueError): From 6bec5fff3a54de8beeab8b9fddfbf92d0a12569e Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 10 Dec 2024 15:15:28 +0100 Subject: [PATCH 061/102] webserver part tests --- .../licensed_items_purchases.py | 4 +- .../licenses/_exceptions_handlers.py | 7 +- .../licenses/plugin.py | 3 +- .../04/licenses/test_licensed_items_db.py | 4 +- .../licenses/test_licensed_items_handlers.py | 4 +- .../test_licensed_items_purchases_handlers.py | 101 ++++++++++++++++++ 6 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_purchases_handlers.py diff --git a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py index c147b411465..c755d4954b3 100644 --- a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py +++ b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py @@ -32,9 +32,9 @@ class LicensedItemPurchaseGet(BaseModel): json_schema_extra={ "examples": [ { - "licensed_item_purchase_id": 1, + "licensed_item_purchase_id": "beb16d18-d57d-44aa-a638-9727fa4a72ef", "product_name": "osparc", - "licensed_item_id": "Special Pricing Plan for Sleeper", + "licensed_item_id": "303942ef-6d31-4ba8-afbe-dbb1fce2a953", "wallet_id": 1, "wallet_name": "My Wallet", "pricing_unit_cost_id": 1, diff --git a/services/web/server/src/simcore_service_webserver/licenses/_exceptions_handlers.py b/services/web/server/src/simcore_service_webserver/licenses/_exceptions_handlers.py index a4b5ada8925..720e7611671 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_exceptions_handlers.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_exceptions_handlers.py @@ -1,6 +1,7 @@ import logging from servicelib.aiohttp import status +from simcore_service_webserver.wallets.errors import WalletAccessForbiddenError from ..exception_handling import ( ExceptionToHttpErrorMap, @@ -17,7 +18,11 @@ LicensedItemNotFoundError: HttpErrorInfo( status.HTTP_404_NOT_FOUND, "Market item {licensed_item_id} not found.", - ) + ), + WalletAccessForbiddenError: HttpErrorInfo( + status.HTTP_403_FORBIDDEN, + "Wallet {wallet_id} forbidden.", + ), } diff --git a/services/web/server/src/simcore_service_webserver/licenses/plugin.py b/services/web/server/src/simcore_service_webserver/licenses/plugin.py index ef124c69fad..6c2ea7ce0d9 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/plugin.py +++ b/services/web/server/src/simcore_service_webserver/licenses/plugin.py @@ -7,7 +7,7 @@ from servicelib.aiohttp.application_keys import APP_SETTINGS_KEY from servicelib.aiohttp.application_setup import ModuleCategory, app_module_setup -from . import _licensed_items_handlers +from . import _licensed_items_handlers, _licensed_items_purchases_handlers _logger = logging.getLogger(__name__) @@ -24,3 +24,4 @@ def setup_licenses(app: web.Application): # routes app.router.add_routes(_licensed_items_handlers.routes) + app.router.add_routes(_licensed_items_purchases_handlers.routes) diff --git a/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_db.py b/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_db.py index 5455c280cd7..910e1bdf3f4 100644 --- a/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_db.py +++ b/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_db.py @@ -15,9 +15,9 @@ from models_library.rest_ordering import OrderBy from pytest_simcore.helpers.webserver_login import UserInfoDict from servicelib.aiohttp import status -from simcore_service_webserver.catalog.licenses import _licensed_items_db -from simcore_service_webserver.catalog.licenses.errors import LicensedItemNotFoundError from simcore_service_webserver.db.models import UserRole +from simcore_service_webserver.licenses import _licensed_items_db +from simcore_service_webserver.licenses.errors import LicensedItemNotFoundError from simcore_service_webserver.projects.models import ProjectDict diff --git a/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_handlers.py b/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_handlers.py index eb63d9bb75a..64f433d33dc 100644 --- a/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_handlers.py +++ b/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_handlers.py @@ -12,8 +12,8 @@ from pytest_simcore.helpers.assert_checks import assert_status from pytest_simcore.helpers.webserver_login import UserInfoDict from servicelib.aiohttp import status -from simcore_service_webserver.catalog.licenses import _licensed_items_db from simcore_service_webserver.db.models import UserRole +from simcore_service_webserver.licenses import _licensed_items_db from simcore_service_webserver.projects.models import ProjectDict @@ -62,5 +62,5 @@ async def test_licensed_items_db_crud( url = client.app.router["purchase_licensed_item"].url_for( licensed_item_id=f"{_licensed_item_id}" ) - resp = await client.post(f"{url}", json={"wallet_id": 1, "num_of_seeds": 5}) + resp = await client.post(f"{url}", json={"wallet_id": 1, "num_of_seats": 5}) # NOTE: Not yet implemented diff --git a/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_purchases_handlers.py b/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_purchases_handlers.py new file mode 100644 index 00000000000..ce0fddeca19 --- /dev/null +++ b/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_purchases_handlers.py @@ -0,0 +1,101 @@ +# pylint: disable=redefined-outer-name +# pylint: disable=unused-argument +# pylint: disable=unused-variable +# pylint: disable=too-many-arguments +# pylint: disable=too-many-statements +from decimal import Decimal +from http import HTTPStatus + +import pytest +from aiohttp.test_utils import TestClient +from models_library.api_schemas_resource_usage_tracker import ( + licensed_items_purchases as rut_licensed_items_purchases, +) +from models_library.api_schemas_webserver.licensed_items_purchases import ( + LicensedItemPurchaseGet, +) +from pytest_mock.plugin import MockerFixture +from pytest_simcore.helpers.assert_checks import assert_status +from pytest_simcore.helpers.webserver_login import UserInfoDict +from servicelib.aiohttp import status +from simcore_service_webserver.db.models import UserRole + +_LICENSED_ITEM_PURCHASE_GET = ( + rut_licensed_items_purchases.LicensedItemPurchaseGet.model_validate( + { + "licensed_item_purchase_id": "beb16d18-d57d-44aa-a638-9727fa4a72ef", + "product_name": "osparc", + "licensed_item_id": "303942ef-6d31-4ba8-afbe-dbb1fce2a953", + "wallet_id": 1, + "wallet_name": "My Wallet", + "pricing_unit_cost_id": 1, + "pricing_unit_cost": Decimal(10), + "start_at": "2023-01-11 13:11:47.293595", + "expire_at": "2023-01-11 13:11:47.293595", + "num_of_seats": 1, + "purchased_by_user": 1, + "purchased_at": "2023-01-11 13:11:47.293595", + "modified": "2023-01-11 13:11:47.293595", + } + ) +) + +_LICENSED_ITEM_PURCHASE_PAGE = rut_licensed_items_purchases.LicensedItemsPurchasesPage( + items=[_LICENSED_ITEM_PURCHASE_GET], + total=1, +) + + +@pytest.fixture +def mock_get_licensed_items_purchases_page(mocker: MockerFixture) -> tuple: + return mocker.patch( + "simcore_service_webserver.licenses._licensed_items_purchases_api.licensed_items_purchases.get_licensed_items_purchases_page", + spec=True, + return_value=_LICENSED_ITEM_PURCHASE_PAGE, + ) + + +@pytest.fixture +def mock_get_licensed_item_purchase(mocker: MockerFixture) -> tuple: + return mocker.patch( + "simcore_service_webserver.licenses._licensed_items_purchases_api.licensed_items_purchases.get_licensed_item_purchase", + spec=True, + return_value=_LICENSED_ITEM_PURCHASE_GET, + ) + + +@pytest.fixture +def mock_get_wallet_by_user(mocker: MockerFixture) -> tuple: + return mocker.patch( + "simcore_service_webserver.licenses._licensed_items_purchases_api.get_wallet_by_user", + spec=True, + ) + + +@pytest.mark.parametrize("user_role,expected", [(UserRole.USER, status.HTTP_200_OK)]) +async def test_licensed_items_db_crud( + client: TestClient, + logged_user: UserInfoDict, + expected: HTTPStatus, + mock_get_licensed_items_purchases_page: MockerFixture, + mock_get_licensed_item_purchase: MockerFixture, + mock_get_wallet_by_user: MockerFixture, +): + assert client.app + + # list + url = client.app.router["list_wallet_licensed_items_purchases"].url_for( + wallet_id="1" + ) + resp = await client.get(f"{url}") + data, _ = await assert_status(resp, status.HTTP_200_OK) + assert len(data) == 1 + assert LicensedItemPurchaseGet(**data[0]) + + # get + url = client.app.router["get_licensed_item_purchase"].url_for( + licensed_item_purchase_id=f"{_LICENSED_ITEM_PURCHASE_PAGE.items[0].licensed_item_purchase_id}" + ) + resp = await client.get(f"{url}") + data, _ = await assert_status(resp, status.HTTP_200_OK) + assert LicensedItemPurchaseGet(**data) From 3755dce4b61da4849cf1fb13ead9349c3f1723dd Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 10 Dec 2024 15:20:55 +0100 Subject: [PATCH 062/102] open api specs --- .../api/v0/openapi.yaml | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index 890d5b77af7..54c2bfeaa16 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -2969,6 +2969,12 @@ paths: schema: $ref: '#/components/schemas/EnvelopedError' description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden /v0/catalog/licensed-items/{licensed_item_id}: get: tags: @@ -2997,6 +3003,12 @@ paths: schema: $ref: '#/components/schemas/EnvelopedError' description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden /v0/catalog/licensed-items/{licensed_item_id}:purchase: post: tags: @@ -3027,6 +3039,12 @@ paths: schema: $ref: '#/components/schemas/EnvelopedError' description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden /v0/wallets/{wallet_id}/licensed-items-purchases: get: tags: @@ -3078,6 +3096,12 @@ paths: schema: $ref: '#/components/schemas/EnvelopedError' description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden /v0/licensed-items-purchases/{licensed_item_purchase_id}: get: tags: @@ -3105,6 +3129,12 @@ paths: schema: $ref: '#/components/schemas/EnvelopedError' description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden /v0/projects/{project_uuid}/checkpoint/{ref_id}/iterations: get: tags: From 418585271f35f0f42307574b3dc7ee7cb533fd50 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 10 Dec 2024 15:39:47 +0100 Subject: [PATCH 063/102] fix type --- .../licensed_items_purchases.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py index c755d4954b3..e75965a1b53 100644 --- a/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py +++ b/packages/models-library/src/models_library/api_schemas_resource_usage_tracker/licensed_items_purchases.py @@ -38,14 +38,14 @@ class LicensedItemPurchaseGet(BaseModel): "wallet_id": 1, "wallet_name": "My Wallet", "pricing_unit_cost_id": 1, - "pricing_unit_cost": Decimal(10), + "pricing_unit_cost": 10, "start_at": "2023-01-11 13:11:47.293595", "expire_at": "2023-01-11 13:11:47.293595", "num_of_seats": 1, "purchased_by_user": 1, "purchased_at": "2023-01-11 13:11:47.293595", "modified": "2023-01-11 13:11:47.293595", - } # type: ignore[index,union-attr] + } ] } ) From 4c31cd001011c0c22cabdea9cb94c35c88a34494 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 10 Dec 2024 15:42:31 +0100 Subject: [PATCH 064/102] fix type --- .../resource_usage_tracker/licensed_items_purchases.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py index 95c002df7cb..8cdeef79d60 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py @@ -9,6 +9,7 @@ LicensedItemPurchaseID, LicensedItemsPurchasesPage, ) +from models_library.basic_types import IDStr from models_library.products import ProductName from models_library.rabbitmq_basic_types import RPCMethodName from models_library.resource_tracker_licensed_items_purchases import ( @@ -37,7 +38,7 @@ async def get_licensed_items_purchases_page( wallet_id: WalletID, offset: int = 0, limit: int = 20, - order_by: OrderBy = OrderBy(field="purchased_at"), + order_by: OrderBy = OrderBy(field=IDStr("purchased_at")), ) -> LicensedItemsPurchasesPage: result = await rabbitmq_rpc_client.request( RESOURCE_USAGE_TRACKER_RPC_NAMESPACE, From 530fda09b465d24201ab8ca24d190fc66bfd1385 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 10 Dec 2024 15:43:16 +0100 Subject: [PATCH 065/102] fix type --- .../api/rpc/_licensed_items_purchases.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_purchases.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_purchases.py index c835848b219..1245ab3f6b4 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_purchases.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_purchases.py @@ -3,6 +3,7 @@ LicensedItemPurchaseGet, LicensedItemsPurchasesPage, ) +from models_library.basic_types import IDStr from models_library.products import ProductName from models_library.resource_tracker_licensed_items_purchases import ( LicensedItemPurchaseID, @@ -25,7 +26,7 @@ async def get_licensed_items_purchases_page( wallet_id: WalletID, offset: int = 0, limit: int = 20, - order_by: OrderBy = OrderBy(field="purchased_at"), + order_by: OrderBy = OrderBy(field=IDStr("purchased_at")), ) -> LicensedItemsPurchasesPage: return await licensed_items_purchases.list_licensed_items_purchases( db_engine=app.state.engine, From 00d40bda3036946af3b77c76761db26b2eadd9fb Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 10 Dec 2024 15:44:34 +0100 Subject: [PATCH 066/102] fix type --- .../services/modules/db/licensed_items_purchases_db.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py index 67950b7b73d..e9951042ddc 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/licensed_items_purchases_db.py @@ -2,6 +2,9 @@ import sqlalchemy as sa from models_library.products import ProductName +from models_library.resource_tracker_licensed_items_purchases import ( + LicensedItemPurchaseID, +) from models_library.rest_ordering import OrderBy, OrderDirection from models_library.wallets import WalletID from pydantic import NonNegativeInt @@ -17,7 +20,6 @@ from ....exceptions.errors import LicensedItemPurchaseNotFoundError from ....models.licensed_items_purchases import ( CreateLicensedItemsPurchasesDB, - LicensedItemPurchaseID, LicensedItemsPurchasesDB, ) From 969f982bc4e373f9112b34f524ce8d1d031ee3cb Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 11 Dec 2024 05:58:35 +0100 Subject: [PATCH 067/102] review @pcrespov --- .../web-server/_licensed_items_purchases.py | 4 +- .../api/v0/openapi.yaml | 37 ++++++++++--------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/api/specs/web-server/_licensed_items_purchases.py b/api/specs/web-server/_licensed_items_purchases.py index 1b3f4b7cf71..8a993cef688 100644 --- a/api/specs/web-server/_licensed_items_purchases.py +++ b/api/specs/web-server/_licensed_items_purchases.py @@ -15,6 +15,7 @@ ) from models_library.generics import Envelope from models_library.rest_error import EnvelopedError +from models_library.rest_pagination import Page from simcore_service_webserver._meta import API_VTAG from simcore_service_webserver.licenses._exceptions_handlers import _TO_HTTP_ERROR_MAP from simcore_service_webserver.licenses._models import ( @@ -36,7 +37,8 @@ @router.get( "/wallets/{wallet_id}/licensed-items-purchases", - response_model=Envelope[list[LicensedItemPurchaseGet]], + response_model=Page[LicensedItemPurchaseGet], + tags=["wallets"], ) async def list_wallet_licensed_items_purchases( _path: Annotated[WalletsPathParams, Depends()], diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index ad1c9e49d11..6e40be15b8a 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -3105,6 +3105,7 @@ paths: get: tags: - licenses + - wallets summary: List Wallet Licensed Items Purchases operationId: list_wallet_licensed_items_purchases parameters: @@ -3145,7 +3146,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Envelope_list_LicensedItemPurchaseGet__' + $ref: '#/components/schemas/Page_LicensedItemPurchaseGet_' '404': content: application/json: @@ -8869,22 +8870,6 @@ components: title: Error type: object title: Envelope[list[LicensedItemGet]] - Envelope_list_LicensedItemPurchaseGet__: - properties: - data: - anyOf: - - items: - $ref: '#/components/schemas/LicensedItemPurchaseGet' - type: array - - type: 'null' - title: Data - error: - anyOf: - - {} - - type: 'null' - title: Error - type: object - title: Envelope[list[LicensedItemPurchaseGet]] Envelope_list_OsparcCreditsAggregatedByServiceGet__: properties: data: @@ -11431,6 +11416,24 @@ components: - _links - data title: Page[CheckpointApiModel] + Page_LicensedItemPurchaseGet_: + properties: + _meta: + $ref: '#/components/schemas/PageMetaInfoLimitOffset' + _links: + $ref: '#/components/schemas/PageLinks' + data: + items: + $ref: '#/components/schemas/LicensedItemPurchaseGet' + type: array + title: Data + additionalProperties: false + type: object + required: + - _meta + - _links + - data + title: Page[LicensedItemPurchaseGet] Page_PaymentTransaction_: properties: _meta: From 138412e32736abbc1cbf67188daa30a9ffd5abc9 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 11 Dec 2024 06:12:52 +0100 Subject: [PATCH 068/102] adding purchase item functionality --- .../src/models_library/resource_tracker.py | 1 + ...source_tracker_licensed_items_purchases.py | 5 +- ...f_add_cols_to_licensed_items_purchases_.py | 38 ++++++++++ ...7_add_cols_to_licensed_items_purchases_.py | 4 +- .../resource_tracker_credit_transactions.py | 10 ++- .../api/rpc/_licensed_items_purchases.py | 2 +- .../models/credit_transactions.py | 4 ++ .../services/credit_transactions.py | 1 + .../services/licensed_items_purchases.py | 72 ++++++++++++++----- .../modules/db/credit_transactions_db.py | 1 + .../process_message_running_service.py | 1 + .../test_api_licensed_items_purchases.py | 5 +- .../licenses/_licensed_items_api.py | 58 ++++++++++++++- .../licenses/_models.py | 3 + 14 files changed, 181 insertions(+), 24 deletions(-) create mode 100644 packages/postgres-database/src/simcore_postgres_database/migration/versions/77ac824a77ff_add_cols_to_licensed_items_purchases_.py diff --git a/packages/models-library/src/models_library/resource_tracker.py b/packages/models-library/src/models_library/resource_tracker.py index c94755817b3..20e35b7e614 100644 --- a/packages/models-library/src/models_library/resource_tracker.py +++ b/packages/models-library/src/models_library/resource_tracker.py @@ -48,6 +48,7 @@ class CreditTransactionStatus(StrAutoEnum): class CreditClassification(StrAutoEnum): ADD_WALLET_TOP_UP = auto() # user top up credits DEDUCT_SERVICE_RUN = auto() # computational/dynamic service run costs) + DEDUCT_LICENSE_PURCHASE = auto() class PricingPlanClassification(StrAutoEnum): diff --git a/packages/models-library/src/models_library/resource_tracker_licensed_items_purchases.py b/packages/models-library/src/models_library/resource_tracker_licensed_items_purchases.py index d1ab2d88dc8..8cddc1d98aa 100644 --- a/packages/models-library/src/models_library/resource_tracker_licensed_items_purchases.py +++ b/packages/models-library/src/models_library/resource_tracker_licensed_items_purchases.py @@ -7,7 +7,7 @@ from .licensed_items import LicensedItemID from .products import ProductName -from .resource_tracker import PricingUnitCostId +from .resource_tracker import PricingPlanId, PricingUnitCostId, PricingUnitId from .users import UserID from .wallets import WalletID @@ -19,12 +19,15 @@ class LicensedItemsPurchasesCreate(BaseModel): licensed_item_id: LicensedItemID wallet_id: WalletID wallet_name: str + pricing_plan_id: PricingPlanId + pricing_unit_id: PricingUnitId pricing_unit_cost_id: PricingUnitCostId pricing_unit_cost: Decimal start_at: datetime expire_at: datetime num_of_seats: int purchased_by_user: UserID + user_email: str purchased_at: datetime model_config = ConfigDict(from_attributes=True) diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/77ac824a77ff_add_cols_to_licensed_items_purchases_.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/77ac824a77ff_add_cols_to_licensed_items_purchases_.py new file mode 100644 index 00000000000..d829ece7e7a --- /dev/null +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/77ac824a77ff_add_cols_to_licensed_items_purchases_.py @@ -0,0 +1,38 @@ +"""add cols to licensed_items_purchases table 3 + +Revision ID: 77ac824a77ff +Revises: d68b8128c23b +Create Date: 2024-12-10 16:42:14.041313+00:00 + +""" +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "77ac824a77ff" +down_revision = "d68b8128c23b" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "resource_tracker_credit_transactions", + sa.Column( + "licensed_item_purchase_id", postgresql.UUID(as_uuid=True), nullable=True + ), + ) + # ### end Alembic commands ### + op.execute( + sa.DDL( + "ALTER TYPE credittransactionclassification ADD VALUE 'DEDUCT_LICENSE_PURCHASE'" + ) + ) + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("resource_tracker_credit_transactions", "licensed_item_purchase_id") + # ### end Alembic commands ### diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/8fa15c4c3977_add_cols_to_licensed_items_purchases_.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/8fa15c4c3977_add_cols_to_licensed_items_purchases_.py index ee47dcb5d4a..6f425116490 100644 --- a/packages/postgres-database/src/simcore_postgres_database/migration/versions/8fa15c4c3977_add_cols_to_licensed_items_purchases_.py +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/8fa15c4c3977_add_cols_to_licensed_items_purchases_.py @@ -1,7 +1,7 @@ """add cols to licensed_items_purchases table Revision ID: 8fa15c4c3977 -Revises: 4d007819e61a +Revises: 5e27063c3ac9 Create Date: 2024-12-10 06:42:23.319239+00:00 """ @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. revision = "8fa15c4c3977" -down_revision = "4d007819e61a" +down_revision = "5e27063c3ac9" branch_labels = None depends_on = None diff --git a/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_credit_transactions.py b/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_credit_transactions.py index d1501a42431..ca4cc470b5f 100644 --- a/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_credit_transactions.py +++ b/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_credit_transactions.py @@ -4,6 +4,7 @@ import enum import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import UUID from ._common import ( NUMERIC_KWARGS, @@ -26,6 +27,7 @@ class CreditTransactionClassification(str, enum.Enum): DEDUCT_SERVICE_RUN = ( "DEDUCT_SERVICE_RUN" # computational/dynamic service run costs) ) + DEDUCT_LICENSE_PURCHASE = "DEDUCT_LICENSE_PURCHASE" resource_tracker_credit_transactions = sa.Table( @@ -117,7 +119,13 @@ class CreditTransactionClassification(str, enum.Enum): "payment_transaction_id", sa.String, nullable=True, - doc="Service run id connected with this transaction", + doc="Payment transaction id connected with this transaction", + ), + sa.Column( + "licensed_item_purchase_id", + UUID(as_uuid=True), + nullable=True, + doc="Licensed item purchase id connected with this transaction", ), column_created_datetime(timezone=True), sa.Column( diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_purchases.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_purchases.py index 1245ab3f6b4..e8f71dfb97d 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_purchases.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/api/rpc/_licensed_items_purchases.py @@ -57,5 +57,5 @@ async def create_licensed_item_purchase( app: FastAPI, *, data: LicensedItemsPurchasesCreate ) -> LicensedItemPurchaseGet: return await licensed_items_purchases.create_licensed_item_purchase( - db_engine=app.state.engine, data=data + rabbitmq_client=app.state.rabbitmq_client, db_engine=app.state.engine, data=data ) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/credit_transactions.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/credit_transactions.py index 4cdf74b6429..b9fd942fee0 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/credit_transactions.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/models/credit_transactions.py @@ -11,6 +11,9 @@ PricingUnitId, ServiceRunId, ) +from models_library.resource_tracker_licensed_items_purchases import ( + LicensedItemPurchaseID, +) from models_library.users import UserID from models_library.wallets import WalletID from pydantic import BaseModel, ConfigDict @@ -32,6 +35,7 @@ class CreditTransactionCreate(BaseModel): payment_transaction_id: str | None created_at: datetime last_heartbeat_at: datetime + licensed_item_purchase_id: LicensedItemPurchaseID | None class CreditTransactionCreditsUpdate(BaseModel): diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/credit_transactions.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/credit_transactions.py index c58eb76be8a..fa314ee2550 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/credit_transactions.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/credit_transactions.py @@ -43,6 +43,7 @@ async def create_credit_transaction( transaction_classification=CreditClassification.ADD_WALLET_TOP_UP, service_run_id=None, payment_transaction_id=credit_transaction_create_body.payment_transaction_id, + licensed_item_purchase_id=None, created_at=credit_transaction_create_body.created_at, last_heartbeat_at=credit_transaction_create_body.created_at, ) diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_purchases.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_purchases.py index 3e106559b9e..f88316e095b 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_purchases.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/licensed_items_purchases.py @@ -6,20 +6,28 @@ LicensedItemsPurchasesPage, ) from models_library.products import ProductName +from models_library.resource_tracker import ( + CreditClassification, + CreditTransactionStatus, +) from models_library.resource_tracker_licensed_items_purchases import ( LicensedItemPurchaseID, LicensedItemsPurchasesCreate, ) from models_library.rest_ordering import OrderBy from models_library.wallets import WalletID +from simcore_postgres_database.utils_repos import transaction_context from sqlalchemy.ext.asyncio import AsyncEngine from ..api.rest.dependencies import get_resource_tracker_db_engine +from ..models.credit_transactions import CreditTransactionCreate from ..models.licensed_items_purchases import ( CreateLicensedItemsPurchasesDB, LicensedItemsPurchasesDB, ) -from .modules.db import licensed_items_purchases_db +from .modules.db import credit_transactions_db, licensed_items_purchases_db +from .modules.rabbitmq import RabbitMQClient, get_rabbitmq_client +from .utils import make_negative, sum_credit_transactions_and_publish_to_rabbitmq async def list_licensed_items_purchases( @@ -94,27 +102,59 @@ async def get_licensed_item_purchase( async def create_licensed_item_purchase( + rabbitmq_client: Annotated[RabbitMQClient, Depends(get_rabbitmq_client)], db_engine: Annotated[AsyncEngine, Depends(get_resource_tracker_db_engine)], *, data: LicensedItemsPurchasesCreate, ) -> LicensedItemPurchaseGet: - _create_db_data = CreateLicensedItemsPurchasesDB( - product_name=data.product_name, - licensed_item_id=data.licensed_item_id, - wallet_id=data.wallet_id, - wallet_name=data.wallet_name, - pricing_unit_cost_id=data.pricing_unit_cost_id, - pricing_unit_cost=data.pricing_unit_cost, - start_at=data.start_at, - expire_at=data.expire_at, - num_of_seats=data.num_of_seats, - purchased_by_user=data.purchased_by_user, - purchased_at=data.purchased_at, - ) + async with transaction_context(db_engine) as conn: + item_purchase_create = CreateLicensedItemsPurchasesDB( + product_name=data.product_name, + licensed_item_id=data.licensed_item_id, + wallet_id=data.wallet_id, + wallet_name=data.wallet_name, + pricing_unit_cost_id=data.pricing_unit_cost_id, + pricing_unit_cost=data.pricing_unit_cost, + start_at=data.start_at, + expire_at=data.expire_at, + num_of_seats=data.num_of_seats, + purchased_by_user=data.purchased_by_user, + purchased_at=data.purchased_at, + ) - licensed_item_purchase_db: LicensedItemsPurchasesDB = ( - await licensed_items_purchases_db.create(db_engine, data=_create_db_data) + licensed_item_purchase_db: LicensedItemsPurchasesDB = ( + await licensed_items_purchases_db.create( + db_engine, connection=conn, data=item_purchase_create + ) + ) + + # Deduct credits from credit_transactions table + transaction_create = CreditTransactionCreate( + product_name=data.product_name, + wallet_id=data.wallet_id, + wallet_name=data.wallet_name, + pricing_plan_id=data.pricing_plan_id, + pricing_unit_id=data.pricing_unit_id, + pricing_unit_cost_id=data.pricing_unit_cost_id, + user_id=data.purchased_by_user, + user_email=data.user_email, + osparc_credits=make_negative(data.pricing_unit_cost), + transaction_status=CreditTransactionStatus.BILLED, + transaction_classification=CreditClassification.DEDUCT_LICENSE_PURCHASE, + service_run_id=None, + payment_transaction_id=None, + licensed_item_purchase_id=licensed_item_purchase_db.licensed_item_purchase_id, + created_at=data.start_at, + last_heartbeat_at=data.start_at, + ) + await credit_transactions_db.create_credit_transaction( + db_engine, connection=conn, data=transaction_create + ) + + # Publish wallet total credits to RabbitMQ + await sum_credit_transactions_and_publish_to_rabbitmq( + db_engine, rabbitmq_client, data.product_name, data.wallet_id ) return LicensedItemPurchaseGet( diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/credit_transactions_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/credit_transactions_db.py index 76a8e9f1dfe..254a36a9732 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/credit_transactions_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/credit_transactions_db.py @@ -48,6 +48,7 @@ async def create_credit_transaction( transaction_classification=data.transaction_classification, service_run_id=data.service_run_id, payment_transaction_id=data.payment_transaction_id, + licensed_item_purchase_id=data.licensed_item_purchase_id, created=data.created_at, last_heartbeat_at=data.last_heartbeat_at, modified=sa.func.now(), diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/process_message_running_service.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/process_message_running_service.py index 8300ede8283..e9234f65435 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/process_message_running_service.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/process_message_running_service.py @@ -143,6 +143,7 @@ async def _process_start_event( transaction_classification=CreditClassification.DEDUCT_SERVICE_RUN, service_run_id=service_run_id, payment_transaction_id=None, + licensed_item_purchase_id=None, created_at=msg.created_at, last_heartbeat_at=msg.created_at, ) diff --git a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py index aad656d1728..e5920728d3c 100644 --- a/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py +++ b/services/resource-usage-tracker/tests/unit/with_dbs/test_api_licensed_items_purchases.py @@ -45,19 +45,22 @@ async def test_rpc_licensed_items_purchases_workflow( licensed_item_id="beb16d18-d57d-44aa-a638-9727fa4a72ef", wallet_id=1, wallet_name="My Wallet", + pricing_plan_id=1, + pricing_unit_id=1, pricing_unit_cost_id=1, pricing_unit_cost=Decimal(10), start_at=datetime.now(tz=UTC), expire_at=datetime.now(tz=UTC), num_of_seats=1, purchased_by_user=1, + user_email="test@test.com", purchased_at=datetime.now(tz=UTC), ) created_item = await licensed_items_purchases.create_licensed_item_purchase( rpc_client, data=_create_data ) - assert isinstance(result, LicensedItemPurchaseGet) # nosec + assert isinstance(created_item, LicensedItemPurchaseGet) # nosec result = await licensed_items_purchases.get_licensed_item_purchase( rpc_client, diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py index bb024b0423b..a9ee7f1ef0b 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py @@ -1,6 +1,7 @@ # pylint: disable=unused-argument import logging +from datetime import UTC, datetime, timedelta from aiohttp import web from models_library.api_schemas_webserver.licensed_items import ( @@ -9,11 +10,21 @@ ) from models_library.licensed_items import LicensedItemID from models_library.products import ProductName +from models_library.resource_tracker_licensed_items_purchases import ( + LicensedItemsPurchasesCreate, +) from models_library.rest_ordering import OrderBy from models_library.users import UserID from pydantic import NonNegativeInt +from servicelib.rabbitmq.rpc_interfaces.resource_usage_tracker import ( + licensed_items_purchases, +) -from . import _licensed_items_db +from ..rabbitmq import get_rabbitmq_rpc_client +from ..resource_usage.api import get_pricing_plan_unit +from ..users.api import get_user +from ..wallets.api import get_wallet_with_available_credits_by_user_and_wallet +from . import _licensed_items_api, _licensed_items_db from ._models import LicensedItemsBodyParams _logger = logging.getLogger(__name__) @@ -74,4 +85,47 @@ async def purchase_licensed_item( licensed_item_id: LicensedItemID, body_params: LicensedItemsBodyParams, ) -> None: - raise NotImplementedError + # Check user wallet permissions + wallet = await get_wallet_with_available_credits_by_user_and_wallet( + app, user_id=user_id, wallet_id=body_params.wallet_id, product_name=product_name + ) + + licensed_item = await _licensed_items_api.get_licensed_item( + app, licensed_item_id=licensed_item_id, product_name=product_name + ) + + if licensed_item.pricing_plan_id != body_params.pricing_plan_id: + raise ValueError("You are lying!") + + pricing_unit = await get_pricing_plan_unit( + app, + product_name=product_name, + pricing_plan_id=body_params.pricing_plan_id, + pricing_unit_id=body_params.pricing_unit_id, + ) + + # Check whether wallet has enough credits + if wallet.available_credits - pricing_unit.current_cost_per_unit < 0: + raise ValueError("Not enough credits!") + + user = await get_user(app, user_id=user_id) + + _data = LicensedItemsPurchasesCreate( + product_name=product_name, + licensed_item_id=licensed_item_id, + wallet_id=wallet.wallet_id, + wallet_name=wallet.name, + pricing_plan_id=body_params.pricing_plan_id, + pricing_unit_id=body_params.pricing_unit_id, + pricing_unit_cost_id=pricing_unit.current_cost_per_unit_id, + pricing_unit_cost=pricing_unit.current_cost_per_unit, + start_at=datetime.now(tz=UTC), + expire_at=datetime.now(tz=UTC) + + timedelta(days=30), # <-- Temporary agreement with OM for proof of concept + num_of_seats=body_params.num_of_seats, + purchased_by_user=user_id, + user_email=user["email"], + purchased_at=datetime.now(tz=UTC), + ) + rpc_client = get_rabbitmq_rpc_client(app) + await licensed_items_purchases.create_licensed_item_purchase(rpc_client, data=_data) 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 2d8514e28e9..d5c2ac0947e 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_models.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_models.py @@ -2,6 +2,7 @@ from models_library.basic_types import IDStr from models_library.licensed_items import LicensedItemID +from models_library.resource_tracker import PricingPlanId, PricingUnitId from models_library.resource_tracker_licensed_items_purchases import ( LicensedItemPurchaseID, ) @@ -52,6 +53,8 @@ class LicensedItemsListQueryParams( class LicensedItemsBodyParams(BaseModel): wallet_id: WalletID + pricing_plan_id: PricingPlanId + pricing_unit_id: PricingUnitId num_of_seats: int model_config = ConfigDict(extra="forbid") From 7e9de14334e18e5b8be1395b511f02e74ef4c847 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 11 Dec 2024 06:17:55 +0100 Subject: [PATCH 069/102] open api specs --- .../simcore_service_webserver/api/v0/openapi.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index 6e40be15b8a..62e9ebe076e 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -10372,6 +10372,16 @@ components: exclusiveMinimum: true title: Wallet Id minimum: 0 + pricing_plan_id: + type: integer + exclusiveMinimum: true + title: Pricing Plan Id + minimum: 0 + pricing_unit_id: + type: integer + exclusiveMinimum: true + title: Pricing Unit Id + minimum: 0 num_of_seats: type: integer title: Num Of Seats @@ -10379,6 +10389,8 @@ components: type: object required: - wallet_id + - pricing_plan_id + - pricing_unit_id - num_of_seats title: LicensedItemsBodyParams LicensedResourceType: From 4f1c5d6a2b925046ef5003a2b40e054b278e0115 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 11 Dec 2024 06:31:35 +0100 Subject: [PATCH 070/102] improve error handling --- .../licenses/_exceptions_handlers.py | 11 ++++++++++- .../licenses/_licensed_items_api.py | 11 +++++++++-- .../src/simcore_service_webserver/licenses/errors.py | 4 ++++ .../simcore_service_webserver/wallets/_handlers.py | 9 ++++++++- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_exceptions_handlers.py b/services/web/server/src/simcore_service_webserver/licenses/_exceptions_handlers.py index 720e7611671..d12b95fafa0 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_exceptions_handlers.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_exceptions_handlers.py @@ -9,7 +9,8 @@ exception_handling_decorator, to_exceptions_handlers_map, ) -from .errors import LicensedItemNotFoundError +from ..wallets.errors import WalletNotEnoughCreditsError +from .errors import LicensedItemNotFoundError, LicensedItemPricingPlanMatchError _logger = logging.getLogger(__name__) @@ -23,6 +24,14 @@ status.HTTP_403_FORBIDDEN, "Wallet {wallet_id} forbidden.", ), + WalletNotEnoughCreditsError: HttpErrorInfo( + status.HTTP_402_PAYMENT_REQUIRED, + "Not enough credits in the wallet.", + ), + LicensedItemPricingPlanMatchError: HttpErrorInfo( + status.HTTP_400_BAD_REQUEST, + "The provided pricing plan does not match the one associated with the licensed item.", + ), } diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py index a9ee7f1ef0b..1e196de47d8 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py @@ -24,8 +24,10 @@ from ..resource_usage.api import get_pricing_plan_unit from ..users.api import get_user from ..wallets.api import get_wallet_with_available_credits_by_user_and_wallet +from ..wallets.errors import WalletNotEnoughCreditsError from . import _licensed_items_api, _licensed_items_db from ._models import LicensedItemsBodyParams +from .errors import LicensedItemPricingPlanMatchError _logger = logging.getLogger(__name__) @@ -95,7 +97,10 @@ async def purchase_licensed_item( ) if licensed_item.pricing_plan_id != body_params.pricing_plan_id: - raise ValueError("You are lying!") + raise LicensedItemPricingPlanMatchError( + pricing_plan_id=body_params.pricing_plan_id, + licensed_item_id=licensed_item_id, + ) pricing_unit = await get_pricing_plan_unit( app, @@ -106,7 +111,9 @@ async def purchase_licensed_item( # Check whether wallet has enough credits if wallet.available_credits - pricing_unit.current_cost_per_unit < 0: - raise ValueError("Not enough credits!") + raise WalletNotEnoughCreditsError( + reason=f"Wallet '{wallet.name}' has {wallet.available_credits} credits." + ) user = await get_user(app, user_id=user_id) diff --git a/services/web/server/src/simcore_service_webserver/licenses/errors.py b/services/web/server/src/simcore_service_webserver/licenses/errors.py index 0313499429e..18c57966123 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/errors.py +++ b/services/web/server/src/simcore_service_webserver/licenses/errors.py @@ -7,3 +7,7 @@ class LicensesValueError(WebServerBaseError, ValueError): class LicensedItemNotFoundError(LicensesValueError): msg_template = "License good {licensed_item_id} not found" + + +class LicensedItemPricingPlanMatchError(LicensesValueError): + msg_template = "The provided pricing plan {pricing_plan_id} does not match the one associated with the licensed item {licensed_item_id}." diff --git a/services/web/server/src/simcore_service_webserver/wallets/_handlers.py b/services/web/server/src/simcore_service_webserver/wallets/_handlers.py index 093edf71c21..9afcdb7c437 100644 --- a/services/web/server/src/simcore_service_webserver/wallets/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/wallets/_handlers.py @@ -47,7 +47,11 @@ MSG_BILLING_DETAILS_NOT_DEFINED_ERROR, MSG_PRICE_NOT_DEFINED_ERROR, ) -from .errors import WalletAccessForbiddenError, WalletNotFoundError +from .errors import ( + WalletAccessForbiddenError, + WalletNotEnoughCreditsError, + WalletNotFoundError, +) _logger = logging.getLogger(__name__) @@ -87,6 +91,9 @@ async def wrapper(request: web.Request) -> web.StreamResponse: except ProductPriceNotDefinedError as exc: raise web.HTTPConflict(reason=MSG_PRICE_NOT_DEFINED_ERROR) from exc + except WalletNotEnoughCreditsError as exc: + raise web.HTTPPaymentRequired(reason=f"{exc}") from exc + except BillingDetailsNotFoundError as exc: error_code = create_error_code(exc) From 26f9c57c2c3582712079f97a1fecdf41094312f4 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 11 Dec 2024 06:46:57 +0100 Subject: [PATCH 071/102] open api specs --- .../api/v0/openapi.yaml | 60 +++++++++++++++++++ .../licenses/_licensed_items_api.py | 4 +- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index 62e9ebe076e..fe6f65828b9 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -3031,6 +3031,18 @@ paths: schema: $ref: '#/components/schemas/EnvelopedError' description: Forbidden + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request /v0/catalog/licensed-items/{licensed_item_id}: get: tags: @@ -3065,6 +3077,18 @@ paths: schema: $ref: '#/components/schemas/EnvelopedError' description: Forbidden + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request /v0/catalog/licensed-items/{licensed_item_id}:purchase: post: tags: @@ -3101,6 +3125,18 @@ paths: schema: $ref: '#/components/schemas/EnvelopedError' description: Forbidden + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request /v0/wallets/{wallet_id}/licensed-items-purchases: get: tags: @@ -3159,6 +3195,18 @@ paths: schema: $ref: '#/components/schemas/EnvelopedError' description: Forbidden + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request /v0/licensed-items-purchases/{licensed_item_purchase_id}: get: tags: @@ -3192,6 +3240,18 @@ paths: schema: $ref: '#/components/schemas/EnvelopedError' description: Forbidden + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request /v0/projects/{project_uuid}/checkpoint/{ref_id}/iterations: get: tags: diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py index 1e196de47d8..6feacf24b1d 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_api.py @@ -25,7 +25,7 @@ from ..users.api import get_user from ..wallets.api import get_wallet_with_available_credits_by_user_and_wallet from ..wallets.errors import WalletNotEnoughCreditsError -from . import _licensed_items_api, _licensed_items_db +from . import _licensed_items_db from ._models import LicensedItemsBodyParams from .errors import LicensedItemPricingPlanMatchError @@ -92,7 +92,7 @@ async def purchase_licensed_item( app, user_id=user_id, wallet_id=body_params.wallet_id, product_name=product_name ) - licensed_item = await _licensed_items_api.get_licensed_item( + licensed_item = await get_licensed_item( app, licensed_item_id=licensed_item_id, product_name=product_name ) From 583b2b4e4991457a61a315aa180aa7c056cbb82e Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 11 Dec 2024 07:05:04 +0100 Subject: [PATCH 072/102] fix import --- .../api_schemas_webserver/wallets.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/models-library/src/models_library/api_schemas_webserver/wallets.py b/packages/models-library/src/models_library/api_schemas_webserver/wallets.py index c9460ab74c1..ef5a4d5395d 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/wallets.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/wallets.py @@ -20,12 +20,40 @@ class WalletGet(OutputSchema): created: datetime modified: datetime - model_config = ConfigDict(from_attributes=True, frozen=False) + model_config = ConfigDict( + from_attributes=True, + frozen=False, + json_schema_extra={ + "examples": [ + { + "wallet_id": 1, + "name": "pm_0987654321", + "description": "https://example.com/payment-method/form", + "owner": "https://example.com/payment-method/form", + "thumbnail": "https://example.com/payment-method/form", + "status": "https://example.com/payment-method/form", + "created": "https://example.com/payment-method/form", + "modified": "https://example.com/payment-method/form", + } + ] + }, + ) class WalletGetWithAvailableCredits(WalletGet): available_credits: Decimal + model_config = ConfigDict( + json_schema_extra={ + "examples": [ + { + **WalletGet.model_config["json_schema_extra"]["examples"][0], + "available_credits": 10.5, + } + ] + } + ) + class WalletGetPermissions(WalletGet): read: bool From 4a1008f94af0856f68b8edc88267d76580828619 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 11 Dec 2024 07:22:28 +0100 Subject: [PATCH 073/102] fix import --- .../api_schemas_webserver/wallets.py | 12 +-- .../licensed_items_purchases.py | 2 +- .../licenses/test_licensed_items_handlers.py | 79 ++++++++++++++++++- 3 files changed, 83 insertions(+), 10 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_webserver/wallets.py b/packages/models-library/src/models_library/api_schemas_webserver/wallets.py index ef5a4d5395d..84f1b38d7f3 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/wallets.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/wallets.py @@ -27,13 +27,13 @@ class WalletGet(OutputSchema): "examples": [ { "wallet_id": 1, - "name": "pm_0987654321", - "description": "https://example.com/payment-method/form", - "owner": "https://example.com/payment-method/form", + "name": "My wallet", + "description": "My description", + "owner": 1, "thumbnail": "https://example.com/payment-method/form", - "status": "https://example.com/payment-method/form", - "created": "https://example.com/payment-method/form", - "modified": "https://example.com/payment-method/form", + "status": "ACTIVE", + "created": "2024-03-25T00:00:00", + "modified": "2024-03-25T00:00:00", } ] }, diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py index 8cdeef79d60..a9463271d75 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/licensed_items_purchases.py @@ -6,13 +6,13 @@ ) from models_library.api_schemas_resource_usage_tracker.licensed_items_purchases import ( LicensedItemPurchaseGet, - LicensedItemPurchaseID, LicensedItemsPurchasesPage, ) from models_library.basic_types import IDStr from models_library.products import ProductName from models_library.rabbitmq_basic_types import RPCMethodName from models_library.resource_tracker_licensed_items_purchases import ( + LicensedItemPurchaseID, LicensedItemsPurchasesCreate, ) from models_library.rest_ordering import OrderBy diff --git a/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_handlers.py b/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_handlers.py index 64f433d33dc..b1fee67dafa 100644 --- a/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_handlers.py +++ b/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_handlers.py @@ -7,8 +7,13 @@ import pytest from aiohttp.test_utils import TestClient +from models_library.api_schemas_resource_usage_tracker.pricing_plans import ( + PricingUnitGet, +) from models_library.api_schemas_webserver.licensed_items import LicensedItemGet +from models_library.api_schemas_webserver.wallets import WalletGetWithAvailableCredits from models_library.licensed_items import LicensedResourceType +from pytest_mock.plugin import MockerFixture from pytest_simcore.helpers.assert_checks import assert_status from pytest_simcore.helpers.webserver_login import UserInfoDict from servicelib.aiohttp import status @@ -18,7 +23,7 @@ @pytest.mark.parametrize("user_role,expected", [(UserRole.USER, status.HTTP_200_OK)]) -async def test_licensed_items_db_crud( +async def test_licensed_items_listing( client: TestClient, logged_user: UserInfoDict, user_project: ProjectDict, @@ -58,9 +63,77 @@ async def test_licensed_items_db_crud( data, _ = await assert_status(resp, status.HTTP_200_OK) assert LicensedItemGet(**data) + +@pytest.fixture +def mock_licensed_items_purchase_functions(mocker: MockerFixture) -> tuple: + mock_wallet_credits = mocker.patch( + "simcore_service_webserver.licenses._licensed_items_api.get_wallet_with_available_credits_by_user_and_wallet", + spec=True, + return_value=WalletGetWithAvailableCredits.model_validate( + WalletGetWithAvailableCredits.model_config["json_schema_extra"]["examples"][ + 0 + ] + ), + ) + mock_get_pricing_unit = mocker.patch( + "simcore_service_webserver.licenses._licensed_items_api.get_pricing_plan_unit", + spec=True, + return_value=PricingUnitGet.model_validate( + PricingUnitGet.model_config["json_schema_extra"]["examples"][0] + ), + ) + mock_create_licensed_item_purchase = mocker.patch( + "simcore_service_webserver.licenses._licensed_items_api.licensed_items_purchases.create_licensed_item_purchase", + spec=True, + ) + + return ( + mock_wallet_credits, + mock_get_pricing_unit, + mock_create_licensed_item_purchase, + ) + + +@pytest.mark.parametrize("user_role,expected", [(UserRole.USER, status.HTTP_200_OK)]) +async def test_licensed_items_purchase( + client: TestClient, + logged_user: UserInfoDict, + user_project: ProjectDict, + osparc_product_name: str, + expected: HTTPStatus, + pricing_plan_id: int, + mock_licensed_items_purchase_functions: tuple, +): + assert client.app + + licensed_item_db = await _licensed_items_db.create( + client.app, + product_name=osparc_product_name, + name="Model A", + licensed_resource_type=LicensedResourceType.VIP_MODEL, + pricing_plan_id=pricing_plan_id, + ) + _licensed_item_id = licensed_item_db.licensed_item_id + + # get + url = client.app.router["get_licensed_item"].url_for( + licensed_item_id=f"{_licensed_item_id}" + ) + resp = await client.get(f"{url}") + data, _ = await assert_status(resp, status.HTTP_200_OK) + assert LicensedItemGet(**data) + # purchase url = client.app.router["purchase_licensed_item"].url_for( licensed_item_id=f"{_licensed_item_id}" ) - resp = await client.post(f"{url}", json={"wallet_id": 1, "num_of_seats": 5}) - # NOTE: Not yet implemented + resp = await client.post( + f"{url}", + json={ + "wallet_id": 1, + "num_of_seats": 5, + "pricing_plan_id": pricing_plan_id, + "pricing_unit_id": 1, + }, + ) + await assert_status(resp, status.HTTP_204_NO_CONTENT) From 776806bb014806e4b247b33ce8ed8e5af6d2a7ab Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 11 Dec 2024 07:54:16 +0100 Subject: [PATCH 074/102] fix typecheck --- .../src/models_library/api_schemas_webserver/wallets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/models-library/src/models_library/api_schemas_webserver/wallets.py b/packages/models-library/src/models_library/api_schemas_webserver/wallets.py index 84f1b38d7f3..9cb0c3c7374 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/wallets.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/wallets.py @@ -47,7 +47,7 @@ class WalletGetWithAvailableCredits(WalletGet): json_schema_extra={ "examples": [ { - **WalletGet.model_config["json_schema_extra"]["examples"][0], + **WalletGet.model_config["json_schema_extra"]["examples"][0], # type: ignore "available_credits": 10.5, } ] From fadfa1fd957be2a86abaeb0fa4834f243a6e006a Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 11 Dec 2024 08:16:25 +0100 Subject: [PATCH 075/102] fix typecheck --- .../models_library/api_schemas_webserver/wallets.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/models-library/src/models_library/api_schemas_webserver/wallets.py b/packages/models-library/src/models_library/api_schemas_webserver/wallets.py index 9cb0c3c7374..a4f33ab3cad 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/wallets.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/wallets.py @@ -60,6 +60,19 @@ class WalletGetPermissions(WalletGet): write: bool delete: bool + model_config = ConfigDict( + json_schema_extra={ + "examples": [ + { + **WalletGet.model_config["json_schema_extra"]["examples"][0], # type: ignore + "read": True, + "write": True, + "delete": True, + } + ] + } + ) + class CreateWalletBodyParams(OutputSchema): name: str From eff2f88d344adefcd3fa8ccd4b255564aa755214 Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero <32402063+pcrespov@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:33:59 +0100 Subject: [PATCH 076/102] fixes @odeimaiz issue --- .../src/simcore_service_webserver/groups/_groups_api.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/groups/_groups_api.py b/services/web/server/src/simcore_service_webserver/groups/_groups_api.py index 9b9e712df54..18491ec60bf 100644 --- a/services/web/server/src/simcore_service_webserver/groups/_groups_api.py +++ b/services/web/server/src/simcore_service_webserver/groups/_groups_api.py @@ -258,14 +258,11 @@ async def add_user_in_group( ) new_by_user_id = user.id - if not new_by_user_id: - msg = "Missing new user in arguments" - raise GroupsError(msg=msg) - return await _groups_db.add_new_user_in_group( app, user_id=user_id, group_id=group_id, new_user_id=new_by_user_id, + new_user_name=new_by_user_name, access_rights=access_rights, ) From 046c628df1d18b70c3271334249bdefec183a855 Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero <32402063+pcrespov@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:39:28 +0100 Subject: [PATCH 077/102] adds tests --- .../with_dbs/01/groups/test_groups_handlers_crud.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/services/web/server/tests/unit/with_dbs/01/groups/test_groups_handlers_crud.py b/services/web/server/tests/unit/with_dbs/01/groups/test_groups_handlers_crud.py index 5adaf33d9af..684f8726089 100644 --- a/services/web/server/tests/unit/with_dbs/01/groups/test_groups_handlers_crud.py +++ b/services/web/server/tests/unit/with_dbs/01/groups/test_groups_handlers_crud.py @@ -85,7 +85,7 @@ async def test_list_user_groups_and_try_modify_organizations( key=by_gid, ) == sorted(standard_groups, key=by_gid) - for group in standard_groups: + for i, group in enumerate(standard_groups): # try to delete a group url = client.app.router["delete_group"].url_for(gid=f"{group['gid']}") response = await client.delete(f"{url}") @@ -93,7 +93,15 @@ async def test_list_user_groups_and_try_modify_organizations( # try to add some user in the group url = client.app.router["add_group_user"].url_for(gid=f"{group['gid']}") - response = await client.post(f"{url}", json={"uid": logged_user["id"]}) + + if i % 2 == 0: + # by user-id + params = {"uid": logged_user["id"]} + else: + # by user name + params = {"userName": logged_user["name"]} + + response = await client.post(f"{url}", json=params) await assert_status(response, status.HTTP_403_FORBIDDEN) # try to modify the user in the group From 243ad4850f8f866700cc20ae08fd05817f19ba57 Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero <32402063+pcrespov@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:48:34 +0100 Subject: [PATCH 078/102] types --- .../models_library/api_schemas_webserver/groups.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_webserver/groups.py b/packages/models-library/src/models_library/api_schemas_webserver/groups.py index d595447c3d3..9439d0ba371 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/groups.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/groups.py @@ -14,16 +14,16 @@ model_validator, ) -from ..basic_types import IDStr from ..emails import LowerCaseEmailStr from ..groups import ( AccessRightsDict, Group, + GroupID, GroupMember, StandardGroupCreate, StandardGroupUpdate, ) -from ..users import UserID +from ..users import UserID, UserNameID from ..utils.common_validators import create__check_only_one_is_set__root_validator from ._base import InputSchema, OutputSchema @@ -55,7 +55,7 @@ class GroupAccessRights(BaseModel): class GroupGet(OutputSchema): - gid: int = Field(..., description="the group ID") + gid: GroupID = Field(..., description="the group ID") label: str = Field(..., description="the group name") description: str = Field(..., description="the group description") thumbnail: AnyUrl | None = Field( @@ -229,11 +229,11 @@ class GroupUserGet(BaseModel): # Identifiers id: Annotated[ - str | None, Field(description="the user id", coerce_numbers_to_str=True) + UserID | None, Field(description="the user id", coerce_numbers_to_str=True) ] = None - user_name: Annotated[IDStr, Field(alias="userName")] + user_name: Annotated[UserNameID, Field(alias="userName")] gid: Annotated[ - str | None, + GroupID | None, Field(description="the user primary gid", coerce_numbers_to_str=True), ] = None @@ -296,7 +296,7 @@ class GroupUserAdd(InputSchema): """ uid: UserID | None = None - user_name: Annotated[IDStr | None, Field(alias="userName")] = None + user_name: Annotated[UserNameID | None, Field(alias="userName")] = None email: Annotated[ LowerCaseEmailStr | None, Field( From 33f6bfbc92e1e13bb639cd62ef8883c98a1c0a03 Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero <32402063+pcrespov@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:48:53 +0100 Subject: [PATCH 079/102] fixes OAS --- .../server/src/simcore_service_webserver/api/v0/openapi.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index 9a92419e514..a967b8aefd4 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -9851,8 +9851,10 @@ components: properties: gid: type: integer + exclusiveMinimum: true title: Gid description: the group ID + minimum: 0 label: type: string title: Label From 62e0688017fdfb644ef8fb703d4e3c994ac0692f Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero <32402063+pcrespov@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:50:13 +0100 Subject: [PATCH 080/102] =?UTF-8?q?services/webserver=20api=20version:=200?= =?UTF-8?q?.49.0=20=E2=86=92=200.50.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- services/web/server/VERSION | 2 +- services/web/server/setup.cfg | 2 +- .../server/src/simcore_service_webserver/api/v0/openapi.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/services/web/server/VERSION b/services/web/server/VERSION index 5c4503b7043..564edf82ddf 100644 --- a/services/web/server/VERSION +++ b/services/web/server/VERSION @@ -1 +1 @@ -0.49.0 +0.50.0 diff --git a/services/web/server/setup.cfg b/services/web/server/setup.cfg index 0e40e2535ee..9a74fe46b3d 100644 --- a/services/web/server/setup.cfg +++ b/services/web/server/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.49.0 +current_version = 0.50.0 commit = True message = services/webserver api version: {current_version} → {new_version} tag = False diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index a967b8aefd4..0aab228525b 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.1.0 info: title: simcore-service-webserver description: Main service with an interface (http-API & websockets) to the web front-end - version: 0.49.0 + version: 0.50.0 servers: - url: '' description: webserver From 5fbe4e81a7fe41e2d899edb539f281d660e443ae Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero <32402063+pcrespov@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:56:22 +0100 Subject: [PATCH 081/102] fixes OAS --- .../src/models_library/api_schemas_webserver/groups.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_webserver/groups.py b/packages/models-library/src/models_library/api_schemas_webserver/groups.py index 9439d0ba371..fcf89919b9b 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/groups.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/groups.py @@ -228,13 +228,11 @@ class GroupUserGet(BaseModel): # OutputSchema # Identifiers - id: Annotated[ - UserID | None, Field(description="the user id", coerce_numbers_to_str=True) - ] = None + id: Annotated[UserID | None, Field(description="the user's id")] = None user_name: Annotated[UserNameID, Field(alias="userName")] gid: Annotated[ GroupID | None, - Field(description="the user primary gid", coerce_numbers_to_str=True), + Field(description="the user primary gid"), ] = None # Private Profile From 739fad7c479470d32a63547dfb442ac3ca687bc8 Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero <32402063+pcrespov@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:56:48 +0100 Subject: [PATCH 082/102] update oas --- .../src/simcore_service_webserver/api/v0/openapi.yaml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index 0aab228525b..5bf3e31784e 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -9936,10 +9936,12 @@ components: properties: id: anyOf: - - type: string + - type: integer + exclusiveMinimum: true + minimum: 0 - type: 'null' title: Id - description: the user id + description: the user's id userName: type: string maxLength: 100 @@ -9947,7 +9949,9 @@ components: title: Username gid: anyOf: - - type: string + - type: integer + exclusiveMinimum: true + minimum: 0 - type: 'null' title: Gid description: the user primary gid From 3f83740e824b93195fa0aa8ad174ef211c07c520 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 11:46:10 +0100 Subject: [PATCH 083/102] get purchases --- .../source/class/osparc/data/Resources.js | 6 +- .../class/osparc/data/model/PricingUnit.js | 4 +- .../vipMarket/AnatomicalModelDetails.js | 1 + .../vipMarket/AnatomicalModelListItem.js | 4 +- .../class/osparc/vipMarket/VipMarket.js | 78 +++++++++++++++---- 5 files changed, 71 insertions(+), 22 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 d8febb21182..aa9d653fda7 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -918,7 +918,11 @@ qx.Class.define("osparc.data.Resources", { putAutoRecharge: { method: "PUT", url: statics.API + "/wallets/{walletId}/auto-recharge" - } + }, + purchases: { + method: "GET", + url: statics.API + "/wallets/{walletId}/licensed-items-purchases" + }, } }, /* diff --git a/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js b/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js index 238b1077495..cea6c910116 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js @@ -89,9 +89,9 @@ qx.Class.define("osparc.data.model.PricingUnit", { }, purchased: { - check: "Boolean", + check: "Object", nullable: true, - init: false, + init: null, event: "changePurchased", }, }, diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index e9ce769011e..cbac5676352 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -191,6 +191,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { this.fireDataEvent("modelPurchaseRequested", { modelId: anatomicalModelsData["modelId"], licensedItemId: anatomicalModelsData["licensedItemId"], + pricingPlanId: anatomicalModelsData["pricingPlanId"], pricingUnitId: pricingUnit.getPricingUnitId(), }); }, this); diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js index b55d6704c2d..4715cfb296d 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js @@ -98,9 +98,9 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelListItem", { }, purchased: { - check: "Boolean", - init: false, + check: "Object", nullable: true, + init: null, event: "changePurchased", apply: "__applyPurchased", }, 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 6c42435bf35..3d832ebac9e 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -55,9 +55,6 @@ qx.Class.define("osparc.vipMarket.VipMarket", { } else { curatedModel[key] = model[key]; } - if (key === "ID") { - curatedModel["purchased"] = model["ID"] < 4; - } }); anatomicalModels.push(curatedModel); }); @@ -67,7 +64,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { members: { __anatomicalModels: null, - __licensedItems: null, + __purchasedItems: null, __anatomicalModelsModel: null, _createChildControlImpl: function(id) { @@ -191,14 +188,30 @@ qx.Class.define("osparc.vipMarket.VipMarket", { .then(anatomicalModelsRaw => { const allAnatomicalModels = this.self().curateAnatomicalModels(anatomicalModelsRaw); - osparc.data.Resources.get("market") - .then(licensedItems => { - this.__licensedItems = licensedItems; + const store = osparc.store.Store.getInstance(); + const contextWallet = store.getContextWallet(); + if (!contextWallet) { + return; + } + const walletId = contextWallet.getWalletId(); + const purchasesParams = { + url: { + walletId + } + }; + Promise.all([ + osparc.data.Resources.get("market"), + osparc.data.Resources.fetch("wallets", "purchases", purchasesParams), + ]) + .then(values => { + const licensedItems = values[0]; + const purchasedItems = values[1]; + this.__purchasedItems = purchasedItems; this.__anatomicalModels = []; allAnatomicalModels.forEach(model => { const modelId = model["ID"]; - const licensedItem = this.__licensedItems.find(licItem => licItem["name"] == modelId); + const licensedItem = licensedItems.find(licItem => licItem["name"] == modelId); if (licensedItem) { const anatomicalModel = {}; anatomicalModel["modelId"] = model["ID"]; @@ -212,7 +225,14 @@ qx.Class.define("osparc.vipMarket.VipMarket", { anatomicalModel["licensedItemId"] = licensedItem["licensedItemId"]; anatomicalModel["pricingPlanId"] = licensedItem["pricingPlanId"]; // attach leased data - anatomicalModel["purchased"] = model["purchased"]; + anatomicalModel["purchased"] = null; // default + const purchasedItemFound = purchasedItems.find(purchasedItem => purchasedItem["licensedItemId"] === licensedItem["licensedItemId"]) + if (purchasedItemFound) { + anatomicalModel["purchased"] = { + expiresAt: new Date(purchasedItemFound["expireAt"]), + numberOfSeats: purchasedItemFound["numOfSeats"], + } + } this.__anatomicalModels.push(anatomicalModel); } }); @@ -221,18 +241,42 @@ qx.Class.define("osparc.vipMarket.VipMarket", { const anatomicModelDetails = this.getChildControl("models-details"); anatomicModelDetails.addListener("modelPurchaseRequested", e => { + if (!contextWallet) { + return; + } const { modelId, licensedItemId, + pricingPlanId, pricingUnitId, } = e.getData(); - console.log("purchase", licensedItemId, pricingUnitId); - const found = this.__anatomicalModels.find(model => model["ID"] === modelId); - if (found) { - found["purchased"] = true; - this.__populateModels(); - anatomicModelDetails.setAnatomicalModelsData(found); + const params = { + url: { + licensedItemId + }, + data: { + "wallet_id": walletId, + "pricing_plan_id": pricingPlanId, + "pricing_unit_id": pricingUnitId, + "num_of_seats": 1, // it might not be used + }, } + osparc.data.Resources.fetch("market", "purchase", params) + .then(() => { + const found = this.__anatomicalModels.find(model => model["ID"] === modelId); + if (found) { + found["purchased"] = { + expiresAt: new Date(), + numberOfSeats: 1, + }; + this.__populateModels(); + anatomicModelDetails.setAnatomicalModelsData(found); + } + }) + .catch(err => { + const msg = err.message || this.tr("Cannot purchase model"); + osparc.FlashMessenger.getInstance().logAs(msg, "ERROR"); + }); }, this); anatomicModelDetails.addListener("modelImportRequested", e => { @@ -253,9 +297,9 @@ qx.Class.define("osparc.vipMarket.VipMarket", { const sortModel = sortBy => { models.sort((a, b) => { // first criteria - if (b["purchased"] !== a["purchased"]) { + if (Boolean(b["purchased"]) !== Boolean(a["purchased"])) { // leased first - return b["purchased"] - a["purchased"]; + return Boolean(b["purchased"]) - Boolean(a["purchased"]); } // second criteria if (sortBy) { From d0ba25458de346f5d634dee5f75491c24f0b5a90 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 11:58:35 +0100 Subject: [PATCH 084/102] numberOfSeats --- .../client/source/class/osparc/store/Pricing.js | 16 ++++++++++++++++ .../source/class/osparc/vipMarket/VipMarket.js | 12 +++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/store/Pricing.js b/services/static-webserver/client/source/class/osparc/store/Pricing.js index bf35811acfe..08d01f9e3c8 100644 --- a/services/static-webserver/client/source/class/osparc/store/Pricing.js +++ b/services/static-webserver/client/source/class/osparc/store/Pricing.js @@ -106,6 +106,22 @@ qx.Class.define("osparc.store.Pricing", { return this.pricingPlansCached.find(f => f.getPricingPlanId() === pricingPlanId); }, + getPricingUnits: function(pricingPlanId) { + const pricingPlan = this.getPricingPlan(pricingPlanId); + if (pricingPlan) { + return pricingPlan.getPricingUnits(); + } + return null; + }, + + getPricingUnit: function(pricingPlanId, pricingUnitId) { + const pricingPlan = this.getPricingPlan(pricingPlanId); + if (pricingPlan) { + return pricingPlan.getPricingUnits().find(pricingUnit => pricingUnit.getPricingUnitId() === pricingUnitId); + } + return null; + }, + __addToCache: function(pricingPlanData) { let pricingPlan = this.pricingPlansCached.find(f => f.getPricingPlanId() === pricingPlanData["pricingPlanId"]); if (pricingPlan) { 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 3d832ebac9e..97facd4ca5a 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -250,6 +250,12 @@ qx.Class.define("osparc.vipMarket.VipMarket", { pricingPlanId, pricingUnitId, } = e.getData(); + let numberOfSeats = null; + const pricingUnit = osparc.store.Pricing.getInstance().getPricingUnit(pricingPlanId, pricingUnitId); + if (pricingUnit) { + const split = pricingUnit.getName().split(" "); + numberOfSeats = parseInt(split[0]); + } const params = { url: { licensedItemId @@ -258,7 +264,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { "wallet_id": walletId, "pricing_plan_id": pricingPlanId, "pricing_unit_id": pricingUnitId, - "num_of_seats": 1, // it might not be used + "num_of_seats": numberOfSeats, // this should go away }, } osparc.data.Resources.fetch("market", "purchase", params) @@ -266,8 +272,8 @@ qx.Class.define("osparc.vipMarket.VipMarket", { const found = this.__anatomicalModels.find(model => model["ID"] === modelId); if (found) { found["purchased"] = { - expiresAt: new Date(), - numberOfSeats: 1, + expiresAt: new Date(), // get this info from the response + numberOfSeats, // get this info from the response }; this.__populateModels(); anatomicModelDetails.setAnatomicalModelsData(found); From cdec8e6baa7942bdb0685e458bcfe387695e3333 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 12:25:22 +0100 Subject: [PATCH 085/102] [skip ci] expiration date --- .../client/source/class/osparc/vipMarket/VipMarket.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 97facd4ca5a..dd013858ce8 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -271,8 +271,10 @@ qx.Class.define("osparc.vipMarket.VipMarket", { .then(() => { const found = this.__anatomicalModels.find(model => model["ID"] === modelId); if (found) { + const expirationDate = new Date(); + expirationDate.setMonth(expirationDate.getMonth() + 1) found["purchased"] = { - expiresAt: new Date(), // get this info from the response + expiresAt: expirationDate, // get this info from the response numberOfSeats, // get this info from the response }; this.__populateModels(); From b10e4563b123ebf5ad4090ada5bd6f752b9e10ea Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 13:11:13 +0100 Subject: [PATCH 086/102] purchases is an Array --- .../class/osparc/data/model/PricingUnit.js | 7 ------ .../source/class/osparc/pricing/UnitsList.js | 10 +++----- .../vipMarket/AnatomicalModelDetails.js | 7 +++++- .../vipMarket/AnatomicalModelListItem.js | 10 ++++---- .../class/osparc/vipMarket/VipMarket.js | 24 ++++++++++--------- 5 files changed, 27 insertions(+), 31 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js b/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js index cea6c910116..91970b854af 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js +++ b/services/static-webserver/client/source/class/osparc/data/model/PricingUnit.js @@ -87,12 +87,5 @@ qx.Class.define("osparc.data.model.PricingUnit", { init: null, event: "changeSpecificInfo", }, - - purchased: { - check: "Object", - nullable: true, - init: null, - event: "changePurchased", - }, }, }); diff --git a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js index c2188c5d972..054d4e4f11b 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js +++ b/services/static-webserver/client/source/class/osparc/pricing/UnitsList.js @@ -93,13 +93,9 @@ qx.Class.define("osparc.pricing.UnitsList", { }); const buttons = this.getChildControl("pricing-units-container").getChildren(); - const keepDefaultSelected = () => { - buttons.forEach(btn => { - btn.setSelected(btn.getUnitData().getIsDefault()); - }); - }; - keepDefaultSelected(); - buttons.forEach(btn => btn.addListener("execute", () => keepDefaultSelected())); + buttons.forEach(btn => { + btn.setSelected(btn.getUnitData().getIsDefault()); + }); }, __openCreatePricingUnit: function() { diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index cbac5676352..30eec1d877a 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -49,6 +49,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { if (anatomicalModelsData) { const modelInfo = this.__createModelInfo(anatomicalModelsData); const pricingUnits = this.__createPricingUnits(anatomicalModelsData); + // const purchasedSection = this.__createPurchasedSection(anatomicalModelsData); const importButton = this.__createImportButton(anatomicalModelsData); this._add(modelInfo); this._add(pricingUnits); @@ -203,6 +204,10 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { return pricingUnitsLayout; }, + __createPurchasedSection: function(anatomicalModelsData) { + console.log("anatomicalModelsData", anatomicalModelsData); + }, + __createImportButton: function(anatomicalModelsData) { const importButton = new qx.ui.form.Button().set({ label: this.tr("Import"), @@ -210,7 +215,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { center: true, maxWidth: 200, alignX: "center", - visibility: anatomicalModelsData["purchased"] ? "visible" : "excluded", + visibility: anatomicalModelsData["purchased"].length ? "visible" : "excluded", }); importButton.addListener("execute", () => { this.fireDataEvent("modelImportRequested", { diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js index 4715cfb296d..63ba0b407d5 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js @@ -98,9 +98,9 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelListItem", { }, purchased: { - check: "Object", - nullable: true, - init: null, + check: "Array", + nullable: false, + init: [], event: "changePurchased", apply: "__applyPurchased", }, @@ -161,8 +161,8 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelListItem", { this.getChildControl("name").setValue(value); }, - __applyPurchased: function(value) { - if (value) { + __applyPurchased: function(purchases) { + if (purchases.length) { this.setBackgroundColor("strong-main"); } }, 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 dd013858ce8..e73914ef55a 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -225,13 +225,15 @@ qx.Class.define("osparc.vipMarket.VipMarket", { anatomicalModel["licensedItemId"] = licensedItem["licensedItemId"]; anatomicalModel["pricingPlanId"] = licensedItem["pricingPlanId"]; // attach leased data - anatomicalModel["purchased"] = null; // default - const purchasedItemFound = purchasedItems.find(purchasedItem => purchasedItem["licensedItemId"] === licensedItem["licensedItemId"]) - if (purchasedItemFound) { - anatomicalModel["purchased"] = { - expiresAt: new Date(purchasedItemFound["expireAt"]), - numberOfSeats: purchasedItemFound["numOfSeats"], - } + anatomicalModel["purchased"] = []; // default + const purchasedItemsFound = purchasedItems.filter(purchasedItem => purchasedItem["licensedItemId"] === licensedItem["licensedItemId"]); + if (purchasedItemsFound.length) { + purchasedItemsFound.forEach(purchasedItemFound => { + anatomicalModel["purchased"].push({ + expiresAt: new Date(purchasedItemFound["expireAt"]), + numberOfSeats: purchasedItemFound["numOfSeats"], + }) + }); } this.__anatomicalModels.push(anatomicalModel); } @@ -273,10 +275,10 @@ qx.Class.define("osparc.vipMarket.VipMarket", { if (found) { const expirationDate = new Date(); expirationDate.setMonth(expirationDate.getMonth() + 1) - found["purchased"] = { + found["purchased"].push({ expiresAt: expirationDate, // get this info from the response numberOfSeats, // get this info from the response - }; + }); this.__populateModels(); anatomicModelDetails.setAnatomicalModelsData(found); } @@ -305,9 +307,9 @@ qx.Class.define("osparc.vipMarket.VipMarket", { const sortModel = sortBy => { models.sort((a, b) => { // first criteria - if (Boolean(b["purchased"]) !== Boolean(a["purchased"])) { + if (b["purchased"].length !== a["purchased"].length) { // leased first - return Boolean(b["purchased"]) - Boolean(a["purchased"]); + return b["purchased"].length - a["purchased"].length; } // second criteria if (sortBy) { From a51c8a1ad2f5959a22708067e5c83619aebe214d Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 13:13:36 +0100 Subject: [PATCH 087/102] [skip ci] purchases --- .../osparc/vipMarket/AnatomicalModelDetails.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 30eec1d877a..389f8f34ea0 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -49,10 +49,11 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { if (anatomicalModelsData) { const modelInfo = this.__createModelInfo(anatomicalModelsData); const pricingUnits = this.__createPricingUnits(anatomicalModelsData); - // const purchasedSection = this.__createPurchasedSection(anatomicalModelsData); + const purchasedSection = this.__createPurchasesSection(anatomicalModelsData); const importButton = this.__createImportButton(anatomicalModelsData); this._add(modelInfo); this._add(pricingUnits); + this._add(purchasedSection); this._add(importButton); } else { const selectModelLabel = new qx.ui.basic.Label().set({ @@ -204,8 +205,16 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { return pricingUnitsLayout; }, - __createPurchasedSection: function(anatomicalModelsData) { + __createPurchasesSection: function(anatomicalModelsData) { console.log("anatomicalModelsData", anatomicalModelsData); + const purchasesSection = new qx.ui.container.Composite(new qx.ui.layout.VBox(5).set({ + alignX: "center" + })); + anatomicalModelsData["purchases"].forEach(purchase => { + const entry = new qx.ui.basic.Label("hey"); + purchasesSection.push(entry); + }); + return purchasesSection; }, __createImportButton: function(anatomicalModelsData) { From beccf2d7f020c087d53b7578b0afb7da4d087fcc Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 13:15:08 +0100 Subject: [PATCH 088/102] renaming --- .../vipMarket/AnatomicalModelDetails.js | 6 ++-- .../vipMarket/AnatomicalModelListItem.js | 8 +++--- .../class/osparc/vipMarket/VipMarket.js | 28 +++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 389f8f34ea0..af81181a09d 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -49,11 +49,11 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { if (anatomicalModelsData) { const modelInfo = this.__createModelInfo(anatomicalModelsData); const pricingUnits = this.__createPricingUnits(anatomicalModelsData); - const purchasedSection = this.__createPurchasesSection(anatomicalModelsData); + const purchasesSection = this.__createPurchasesSection(anatomicalModelsData); const importButton = this.__createImportButton(anatomicalModelsData); this._add(modelInfo); this._add(pricingUnits); - this._add(purchasedSection); + this._add(purchasesSection); this._add(importButton); } else { const selectModelLabel = new qx.ui.basic.Label().set({ @@ -224,7 +224,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { center: true, maxWidth: 200, alignX: "center", - visibility: anatomicalModelsData["purchased"].length ? "visible" : "excluded", + visibility: anatomicalModelsData["purchases"].length ? "visible" : "excluded", }); importButton.addListener("execute", () => { this.fireDataEvent("modelImportRequested", { diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js index 63ba0b407d5..75b33a3229d 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js @@ -97,12 +97,12 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelListItem", { event: "changePricingPlanId", }, - purchased: { + purchases: { check: "Array", nullable: false, init: [], - event: "changePurchased", - apply: "__applyPurchased", + event: "changePurchases", + apply: "__applyPurchases", }, }, @@ -161,7 +161,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelListItem", { this.getChildControl("name").setValue(value); }, - __applyPurchased: function(purchases) { + __applyPurchases: function(purchases) { if (purchases.length) { this.setBackgroundColor("strong-main"); } 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 e73914ef55a..016a32cc1ff 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -64,7 +64,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { members: { __anatomicalModels: null, - __purchasedItems: null, + __purchasesItems: null, __anatomicalModelsModel: null, _createChildControlImpl: function(id) { @@ -150,7 +150,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { ctrl.bindProperty("date", "date", null, item, id); ctrl.bindProperty("licensedItemId", "licensedItemId", null, item, id); ctrl.bindProperty("pricingPlanId", "pricingPlanId", null, item, id); - ctrl.bindProperty("purchased", "purchased", null, item, id); + ctrl.bindProperty("purchases", "purchases", null, item, id); }, configureItem: item => { item.subscribeToFilterGroup("vipModels"); @@ -205,8 +205,8 @@ qx.Class.define("osparc.vipMarket.VipMarket", { ]) .then(values => { const licensedItems = values[0]; - const purchasedItems = values[1]; - this.__purchasedItems = purchasedItems; + const purchasesItems = values[1]; + this.__purchasesItems = purchasesItems; this.__anatomicalModels = []; allAnatomicalModels.forEach(model => { @@ -225,13 +225,13 @@ qx.Class.define("osparc.vipMarket.VipMarket", { anatomicalModel["licensedItemId"] = licensedItem["licensedItemId"]; anatomicalModel["pricingPlanId"] = licensedItem["pricingPlanId"]; // attach leased data - anatomicalModel["purchased"] = []; // default - const purchasedItemsFound = purchasedItems.filter(purchasedItem => purchasedItem["licensedItemId"] === licensedItem["licensedItemId"]); - if (purchasedItemsFound.length) { - purchasedItemsFound.forEach(purchasedItemFound => { - anatomicalModel["purchased"].push({ - expiresAt: new Date(purchasedItemFound["expireAt"]), - numberOfSeats: purchasedItemFound["numOfSeats"], + anatomicalModel["purchases"] = []; // default + const purchasesItemsFound = purchasesItems.filter(purchasesItem => purchasesItem["licensedItemId"] === licensedItem["licensedItemId"]); + if (purchasesItemsFound.length) { + purchasesItemsFound.forEach(purchasesItemFound => { + anatomicalModel["purchases"].push({ + expiresAt: new Date(purchasesItemFound["expireAt"]), + numberOfSeats: purchasesItemFound["numOfSeats"], }) }); } @@ -275,7 +275,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { if (found) { const expirationDate = new Date(); expirationDate.setMonth(expirationDate.getMonth() + 1) - found["purchased"].push({ + found["purchases"].push({ expiresAt: expirationDate, // get this info from the response numberOfSeats, // get this info from the response }); @@ -307,9 +307,9 @@ qx.Class.define("osparc.vipMarket.VipMarket", { const sortModel = sortBy => { models.sort((a, b) => { // first criteria - if (b["purchased"].length !== a["purchased"].length) { + if (b["purchases"].length !== a["purchases"].length) { // leased first - return b["purchased"].length - a["purchased"].length; + return b["purchases"].length - a["purchases"].length; } // second criteria if (sortBy) { From 40a9b5366fed5e28fb82ec8a1d5e841492c0d84d Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 13:22:32 +0100 Subject: [PATCH 089/102] aesthetics --- .../vipMarket/AnatomicalModelDetails.js | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index af81181a09d..3303afc06a3 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -21,7 +21,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { construct: function() { this.base(arguments); - const layout = new qx.ui.layout.VBox(20); + const layout = new qx.ui.layout.VBox(15); this._setLayout(layout); this.__poplulateLayout(); @@ -49,11 +49,9 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { if (anatomicalModelsData) { const modelInfo = this.__createModelInfo(anatomicalModelsData); const pricingUnits = this.__createPricingUnits(anatomicalModelsData); - const purchasesSection = this.__createPurchasesSection(anatomicalModelsData); - const importButton = this.__createImportButton(anatomicalModelsData); + const importButton = this.__createImportSection(anatomicalModelsData); this._add(modelInfo); this._add(pricingUnits); - this._add(purchasesSection); this._add(importButton); } else { const selectModelLabel = new qx.ui.basic.Label().set({ @@ -205,19 +203,19 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { return pricingUnitsLayout; }, - __createPurchasesSection: function(anatomicalModelsData) { - console.log("anatomicalModelsData", anatomicalModelsData); - const purchasesSection = new qx.ui.container.Composite(new qx.ui.layout.VBox(5).set({ + __createImportSection: function(anatomicalModelsData) { + const importSection = new qx.ui.container.Composite(new qx.ui.layout.VBox(5).set({ alignX: "center" })); + anatomicalModelsData["purchases"].forEach(purchase => { - const entry = new qx.ui.basic.Label("hey"); - purchasesSection.push(entry); + const seatsText = "seat" + purchase["numberOfSeats"] > 1 ? "s" : ""; + const entry = new qx.ui.basic.Label().set({ + value: `${purchase["numberOfSeats"]} ${seatsText} avaialable until ${osparc.utils.Utils.formatDate(purchase["expiresAt"])}` + }); + importSection.add(entry); }); - return purchasesSection; - }, - __createImportButton: function(anatomicalModelsData) { const importButton = new qx.ui.form.Button().set({ label: this.tr("Import"), appearance: "strong-button", @@ -231,7 +229,8 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { modelId: anatomicalModelsData["modelId"] }); }, this); - return importButton; + importSection.add(importButton); + return importSection; }, } }); From 879ebfe079334f9ff28ce0a76a3806fca269cba9 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 13:23:05 +0100 Subject: [PATCH 090/102] [skip ci] aesthtics --- .../source/class/osparc/vipMarket/AnatomicalModelDetails.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 3303afc06a3..2a9c2109d98 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -209,7 +209,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { })); anatomicalModelsData["purchases"].forEach(purchase => { - const seatsText = "seat" + purchase["numberOfSeats"] > 1 ? "s" : ""; + const seatsText = "seat" + (purchase["numberOfSeats"] > 1 ? "s" : ""); const entry = new qx.ui.basic.Label().set({ value: `${purchase["numberOfSeats"]} ${seatsText} avaialable until ${osparc.utils.Utils.formatDate(purchase["expiresAt"])}` }); From 24b5fcf402ac2d67ff78345f08c4dc58827bced1 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 13:24:59 +0100 Subject: [PATCH 091/102] minor --- .../source/class/osparc/vipMarket/AnatomicalModelDetails.js | 2 +- .../client/source/class/osparc/vipMarket/VipMarket.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 2a9c2109d98..363cb9c41c1 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -211,7 +211,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { anatomicalModelsData["purchases"].forEach(purchase => { const seatsText = "seat" + (purchase["numberOfSeats"] > 1 ? "s" : ""); const entry = new qx.ui.basic.Label().set({ - value: `${purchase["numberOfSeats"]} ${seatsText} avaialable until ${osparc.utils.Utils.formatDate(purchase["expiresAt"])}` + value: `${purchase["numberOfSeats"]} ${seatsText} available until ${osparc.utils.Utils.formatDate(purchase["expiresAt"])}` }); importSection.add(entry); }); 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 016a32cc1ff..5dddc1943b7 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -124,7 +124,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { break; case "models-details": control = new osparc.vipMarket.AnatomicalModelDetails().set({ - padding: 10, + padding: 5, }); this.getChildControl("right-side").add(control, { flex: 1 From a381ac4707e1e6444a70d80d87670b2377f09986 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 13:30:52 +0100 Subject: [PATCH 092/102] working --- .../client/source/class/osparc/vipMarket/VipMarket.js | 4 ++-- 1 file changed, 2 insertions(+), 2 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 5dddc1943b7..8bc089fe941 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -271,10 +271,10 @@ qx.Class.define("osparc.vipMarket.VipMarket", { } osparc.data.Resources.fetch("market", "purchase", params) .then(() => { - const found = this.__anatomicalModels.find(model => model["ID"] === modelId); + const found = this.__anatomicalModels.find(model => model["modelId"] === modelId); if (found) { const expirationDate = new Date(); - expirationDate.setMonth(expirationDate.getMonth() + 1) + expirationDate.setMonth(expirationDate.getMonth() + 1); // rented for one month found["purchases"].push({ expiresAt: expirationDate, // get this info from the response numberOfSeats, // get this info from the response From c8ac2bc41c8d00b137fcdcf11097e1886efa38a9 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 13:40:29 +0100 Subject: [PATCH 093/102] Feedback after purchase and hide Import --- .../vipMarket/AnatomicalModelDetails.js | 5 ++++- .../class/osparc/vipMarket/VipMarket.js | 19 +++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 363cb9c41c1..9916ad2c11d 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -211,11 +211,13 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { anatomicalModelsData["purchases"].forEach(purchase => { const seatsText = "seat" + (purchase["numberOfSeats"] > 1 ? "s" : ""); const entry = new qx.ui.basic.Label().set({ - value: `${purchase["numberOfSeats"]} ${seatsText} available until ${osparc.utils.Utils.formatDate(purchase["expiresAt"])}` + value: `${purchase["numberOfSeats"]} ${seatsText} available until ${osparc.utils.Utils.formatDate(purchase["expiresAt"])}`, + font: "text-14", }); importSection.add(entry); }); + /* const importButton = new qx.ui.form.Button().set({ label: this.tr("Import"), appearance: "strong-button", @@ -230,6 +232,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }); }, this); importSection.add(importButton); + */ return importSection; }, } 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 8bc089fe941..3e026fe6c54 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -271,14 +271,21 @@ qx.Class.define("osparc.vipMarket.VipMarket", { } osparc.data.Resources.fetch("market", "purchase", params) .then(() => { + const expirationDate = new Date(); + expirationDate.setMonth(expirationDate.getMonth() + 1); // rented for one month + const purchaseData = { + expiresAt: expirationDate, // get this info from the response + numberOfSeats, // get this info from the response + }; + + let msg = numberOfSeats; + msg += " seat" + (purchaseData["numberOfSeats"] > 1 ? "s" : ""); + msg += " rented until " + osparc.utils.Utils.formatDate(purchaseData["expiresAt"]); + osparc.FlashMessenger.getInstance().logAs(msg, "INFO"); + const found = this.__anatomicalModels.find(model => model["modelId"] === modelId); if (found) { - const expirationDate = new Date(); - expirationDate.setMonth(expirationDate.getMonth() + 1); // rented for one month - found["purchases"].push({ - expiresAt: expirationDate, // get this info from the response - numberOfSeats, // get this info from the response - }); + found["purchases"].push(purchaseData); this.__populateModels(); anatomicModelDetails.setAnatomicalModelsData(found); } From fda7fb60913df024173d7b203bd78c7d4ec302ae Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 13:52:07 +0100 Subject: [PATCH 094/102] openMarket from sim4life --- .../class/osparc/widget/PersistentIframe.js | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js b/services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js index 83b128e82c9..c11fae0c225 100644 --- a/services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js +++ b/services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js @@ -292,17 +292,28 @@ qx.Class.define("osparc.widget.PersistentIframe", { __handleIframeMessage: function(data) { if (data["type"] && data["message"]) { - if (data["type"] === "theme") { - // switch theme driven by the iframe - const message = data["message"]; - if (message.includes("osparc;theme=")) { - const themeName = message.replace("osparc;theme=", ""); - const validThemes = osparc.ui.switch.ThemeSwitcher.getValidThemes(); - const themeFound = validThemes.find(theme => theme.basename === themeName); - const themeManager = qx.theme.manager.Meta.getInstance(); - if (themeFound !== themeManager.getTheme()) { - themeManager.setTheme(themeFound); + switch (data["type"]) { + case "theme": { + // switch theme driven by the iframe + const message = data["message"]; + if (message.includes("osparc;theme=")) { + const themeName = message.replace("osparc;theme=", ""); + const validThemes = osparc.ui.switch.ThemeSwitcher.getValidThemes(); + const themeFound = validThemes.find(theme => theme.basename === themeName); + const themeManager = qx.theme.manager.Meta.getInstance(); + if (themeFound !== themeManager.getTheme()) { + themeManager.setTheme(themeFound); + } + } + break; + } + case "openMarket": { + const marketWindow = osparc.vipMarket.MarketWindow.openWindow(); + const category = data["message"] && data["message"]["category"]; + if (category) { + marketWindow.openCategory(category); } + break; } } } From 7b6e32fb41eef6eb859f5a07ce1f13447e8a16bb Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 14:26:23 +0100 Subject: [PATCH 095/102] sendImportModelMessage --- .../vipMarket/AnatomicalModelDetails.js | 7 ++-- .../source/class/osparc/vipMarket/Market.js | 10 +++++ .../class/osparc/vipMarket/MarketWindow.js | 25 +++++------ .../class/osparc/vipMarket/VipMarket.js | 30 +++++++++++++- .../class/osparc/widget/PersistentIframe.js | 41 ++++++++++--------- 5 files changed, 75 insertions(+), 38 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 9916ad2c11d..4eb9486004b 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -217,22 +217,21 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { importSection.add(entry); }); - /* const importButton = new qx.ui.form.Button().set({ label: this.tr("Import"), appearance: "strong-button", center: true, maxWidth: 200, alignX: "center", - visibility: anatomicalModelsData["purchases"].length ? "visible" : "excluded", }); importButton.addListener("execute", () => { this.fireDataEvent("modelImportRequested", { modelId: anatomicalModelsData["modelId"] }); }, this); - importSection.add(importButton); - */ + if (anatomicalModelsData["purchases"].length) { + importSection.add(importButton); + } return importSection; }, } 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 5a6585423e0..8128d37c989 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js @@ -51,12 +51,22 @@ qx.Class.define("osparc.vipMarket.Market", { }); }, + properties: { + openBy: { + check: "String", + init: null, + nullable: true, + event: "changeOpenBy", + }, + }, + members: { __buildViPMarketPage: function(marketInfo) { const vipMarketView = new osparc.vipMarket.VipMarket(); vipMarketView.set({ metadataUrl: marketInfo["url"], }); + this.bind("openBy", vipMarketView, "openBy"); const page = this.addTab(marketInfo["label"], marketInfo["icon"], vipMarketView); page.category = marketInfo["category"]; return page; diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js b/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js index 4050b28a71c..1cf3c2034de 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js @@ -18,10 +18,9 @@ qx.Class.define("osparc.vipMarket.MarketWindow", { extend: osparc.ui.window.TabbedWindow, - construct: function() { + construct: function(nodeId, category) { this.base(arguments, "store", this.tr("Market")); - osparc.utils.Utils.setIdToWidget(this, "storeWindow"); const width = 1035; @@ -29,16 +28,22 @@ qx.Class.define("osparc.vipMarket.MarketWindow", { this.set({ width, height - }) + }); - const vipMarket = this.__vipMarket = new osparc.vipMarket.Market(); + const vipMarket = this.__vipMarket = new osparc.vipMarket.Market().set({ + openBy: nodeId, + }); this._setTabbedView(vipMarket); + + if (category) { + vipMarket.openCategory(category); + } }, statics: { - openWindow: function() { + openWindow: function(nodeId, category) { if (osparc.product.Utils.showS4LStore()) { - const storeWindow = new osparc.vipMarket.MarketWindow(); + const storeWindow = new osparc.vipMarket.MarketWindow(nodeId, category); storeWindow.center(); storeWindow.open(); return storeWindow; @@ -46,12 +51,4 @@ qx.Class.define("osparc.vipMarket.MarketWindow", { return null; } }, - - members: { - __vipMarket: null, - - openCategory: function(category) { - return this.__vipMarket.openCategory(category); - }, - } }); 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 3e026fe6c54..2d2d1a22677 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -27,6 +27,13 @@ qx.Class.define("osparc.vipMarket.VipMarket", { }, properties: { + openBy: { + check: "String", + init: null, + nullable: true, + event: "changeOpenBy", + }, + metadataUrl: { check: "String", init: null, @@ -300,7 +307,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { const { modelId } = e.getData(); - console.log("Import", modelId); + this.__sendImportModelMessage(modelId); }, this); }); }) @@ -349,5 +356,26 @@ qx.Class.define("osparc.vipMarket.VipMarket", { models.forEach(model => this.__anatomicalModelsModel.append(qx.data.marshal.Json.createModel(model))); }, this); }, + + __sendImportModelMessage: function(modelId) { + const nodeId = this.getOpenBy(); + if (nodeId) { + const store = osparc.store.Store.getInstance(); + const currentStudy = store.getCurrentStudy(); + if (!currentStudy) { + return; + } + const node = currentStudy.getWorkbench().getNode(nodeId); + if (node && node.getIFrame()) { + const msg = { + "type": "importModel", + "message": { + "modelId": modelId, + }, + }; + node.getIFrame().sendMessageToIframe(msg); + } + } + }, } }); diff --git a/services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js b/services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js index c11fae0c225..f5f48cf30d8 100644 --- a/services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js +++ b/services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js @@ -252,19 +252,8 @@ qx.Class.define("osparc.widget.PersistentIframe", { __attachTriggerers: function() { this.postThemeSwitch = theme => { - const iframe = this._getIframeElement(); - if (iframe) { - const iframeDomEl = iframe.getDomElement(); - const iframeSource = iframe.getSource(); - if (iframeDomEl && iframeSource) { - const msg = "osparc;theme=" + theme; - try { - iframeDomEl.contentWindow.postMessage(msg, iframeSource); - } catch (err) { - console.log(`Failed posting message ${msg} to iframe ${iframeSource}\n${err.message}`); - } - } - } + const msg = "osparc;theme=" + theme; + this.sendMessageToIframe(msg); }; this.themeSwitchHandler = msg => { @@ -273,6 +262,21 @@ qx.Class.define("osparc.widget.PersistentIframe", { qx.event.message.Bus.getInstance().subscribe("themeSwitch", this.themeSwitchHandler); }, + sendMessageToIframe: function(msg) { + const iframe = this._getIframeElement(); + if (iframe) { + const iframeDomEl = iframe.getDomElement(); + const iframeSource = iframe.getSource(); + if (iframeDomEl && iframeSource) { + try { + iframeDomEl.contentWindow.postMessage(msg, iframeSource); + } catch (err) { + console.log(`Failed posting message ${msg} to iframe ${iframeSource}\n${err.message}`); + } + } + } + }, + __attachListeners: function() { this.__iframe.addListener("load", () => { const iframe = this._getIframeElement(); @@ -282,7 +286,9 @@ qx.Class.define("osparc.widget.PersistentIframe", { window.addEventListener("message", message => { const data = message.data; if (data) { - this.__handleIframeMessage(data); + const origin = new URL(message.origin).hostname; // nodeId.services.deployment + const nodeId = origin.split(".")[0]; + this.__handleIframeMessage(data, nodeId); } }); } @@ -290,7 +296,7 @@ qx.Class.define("osparc.widget.PersistentIframe", { }, this); }, - __handleIframeMessage: function(data) { + __handleIframeMessage: function(data, nodeId) { if (data["type"] && data["message"]) { switch (data["type"]) { case "theme": { @@ -308,11 +314,8 @@ qx.Class.define("osparc.widget.PersistentIframe", { break; } case "openMarket": { - const marketWindow = osparc.vipMarket.MarketWindow.openWindow(); const category = data["message"] && data["message"]["category"]; - if (category) { - marketWindow.openCategory(category); - } + osparc.vipMarket.MarketWindow.openWindow(nodeId, category); break; } } From 07a5547a9824dc9187c20e5340e13ceb65515e43 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 14:31:01 +0100 Subject: [PATCH 096/102] minor --- .../client/source/class/osparc/vipMarket/MarketWindow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js b/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js index 1cf3c2034de..c610abf36b3 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js @@ -31,7 +31,7 @@ qx.Class.define("osparc.vipMarket.MarketWindow", { }); const vipMarket = this.__vipMarket = new osparc.vipMarket.Market().set({ - openBy: nodeId, + openBy: nodeId ? nodeId : null, }); this._setTabbedView(vipMarket); From 134acf0b29ccde9503d6c0a1f6eda7e2fbcd0185 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 14:31:15 +0100 Subject: [PATCH 097/102] Import button visibility --- .../class/osparc/vipMarket/AnatomicalModelDetails.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 4eb9486004b..5e1f87c81ee 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -33,6 +33,13 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }, properties: { + openBy: { + check: "String", + init: null, + nullable: true, + event: "changeOpenBy", + }, + anatomicalModelsData: { check: "Object", init: null, @@ -224,6 +231,9 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { maxWidth: 200, alignX: "center", }); + this.bind("openBy", importButton, "visibility", { + converter: openBy => openBy ? "visible" : "excluded" + }); importButton.addListener("execute", () => { this.fireDataEvent("modelImportRequested", { modelId: anatomicalModelsData["modelId"] From 87362a38e92736a3023b85402dd16028d538fd82 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 14:36:28 +0100 Subject: [PATCH 098/102] gid string to int --- .../server/src/simcore_service_webserver/api/v0/openapi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index d27cbd9537e..ce36e2e6e93 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -10636,7 +10636,7 @@ components: read: true write: false description: Open to all users - gid: '0' + gid: 1 label: All me: accessRights: From 436898e3780d428d442ad6b073846be012cc21e6 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 14:42:25 +0100 Subject: [PATCH 099/102] minor binding --- .../client/source/class/osparc/vipMarket/VipMarket.js | 1 + 1 file changed, 1 insertion(+) 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 2d2d1a22677..1f57faa80f5 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -133,6 +133,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { control = new osparc.vipMarket.AnatomicalModelDetails().set({ padding: 5, }); + this.bind("openBy", control, "openBy"); this.getChildControl("right-side").add(control, { flex: 1 }); From 3f5ab006def417494425585565dc44c782dc30e0 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 15:06:28 +0100 Subject: [PATCH 100/102] get all licensedItems --- .../source/class/osparc/data/Resources.js | 10 ++-- .../client/source/class/osparc/store/Store.js | 6 ++- .../source/class/osparc/vipMarket/Market.js | 49 ++++++++++--------- .../class/osparc/vipMarket/VipMarket.js | 4 +- 4 files changed, 40 insertions(+), 29 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 aa9d653fda7..534e9bd723a 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -1254,15 +1254,19 @@ qx.Class.define("osparc.data.Resources", { }, /* - * MARKET + * LICENSED ITEMS */ - "market": { - useCache: false, + "licensedItems": { + useCache: true, endpoints: { get: { method: "GET", url: statics.API + "/catalog/licensed-items" }, + getPage: { + method: "GET", + url: statics.API + "/catalog/licensed-items?offset={offset}&limit={limit}" + }, purchase: { method: "POST", url: statics.API + "/catalog/licensed-items/{licensedItemId}:purchase" diff --git a/services/static-webserver/client/source/class/osparc/store/Store.js b/services/static-webserver/client/source/class/osparc/store/Store.js index 6b986a0a34d..ea05e789754 100644 --- a/services/static-webserver/client/source/class/osparc/store/Store.js +++ b/services/static-webserver/client/source/class/osparc/store/Store.js @@ -221,7 +221,11 @@ qx.Class.define("osparc.store.Store", { tasks: { check: "Array", init: [] - } + }, + market: { + check: "Array", + init: [] + }, }, members: { 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 8128d37c989..a543fa2cfa6 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js @@ -26,29 +26,32 @@ qx.Class.define("osparc.vipMarket.Market", { }); this.addWidgetOnTopOfTheTabs(miniWallet); - [{ - category: "humanWhole", - label: "Humans", - icon: "@FontAwesome5Solid/users/20", - url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/HumanWholeBody", - }, { - category: "humanRegion", - label: "Humans (Region)", - icon: "@FontAwesome5Solid/users/20", - url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/HumanBodyRegion", - }, { - category: "animalWhole", - label: "Animals", - icon: "@FontAwesome5Solid/users/20", - url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/AnimalWholeBody", - }, { - category: "compPhantom", - label: "Phantoms", - icon: "@FontAwesome5Solid/users/20", - url: "https://speag.swiss/PD_DirectDownload/getDownloadableItems/ComputationalPhantom", - }].forEach(marketInfo => { - this.__buildViPMarketPage(marketInfo); - }); + osparc.data.Resources.getInstance().getAllPages("licensedItems") + .then(() => { + [{ + category: "humanWhole", + label: "Humans", + icon: "@FontAwesome5Solid/users/20", + url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/HumanWholeBody", + }, { + category: "humanRegion", + label: "Humans (Region)", + icon: "@FontAwesome5Solid/users/20", + url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/HumanBodyRegion", + }, { + category: "animalWhole", + label: "Animals", + icon: "@FontAwesome5Solid/users/20", + url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/AnimalWholeBody", + }, { + category: "compPhantom", + label: "Phantoms", + icon: "@FontAwesome5Solid/users/20", + url: "https://speag.swiss/PD_DirectDownload/getDownloadableItems/ComputationalPhantom", + }].forEach(marketInfo => { + this.__buildViPMarketPage(marketInfo); + }); + }); }, properties: { 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 1f57faa80f5..1385264933e 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -208,7 +208,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { } }; Promise.all([ - osparc.data.Resources.get("market"), + osparc.data.Resources.get("licensedItems"), osparc.data.Resources.fetch("wallets", "purchases", purchasesParams), ]) .then(values => { @@ -277,7 +277,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { "num_of_seats": numberOfSeats, // this should go away }, } - osparc.data.Resources.fetch("market", "purchase", params) + osparc.data.Resources.fetch("licensedItems", "purchase", params) .then(() => { const expirationDate = new Date(); expirationDate.setMonth(expirationDate.getMonth() + 1); // rented for one month From 0f0c6e4b3e8bce058cce310e4a3a4e939af913e8 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 15:08:23 +0100 Subject: [PATCH 101/102] minor --- .../client/source/class/osparc/desktop/WorkbenchView.js | 2 +- .../client/source/class/osparc/node/slideshow/NodeView.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js b/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js index d6df7d06b28..4dfad42c6e9 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js +++ b/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js @@ -752,7 +752,7 @@ qx.Class.define("osparc.desktop.WorkbenchView", { __iFrameChanged: function(node) { this.__iframePage.removeAll(); - if (node) { + if (node && node.getIFrame()) { const loadingPage = node.getLoadingPage(); const iFrame = node.getIFrame(); const src = iFrame.getSource(); diff --git a/services/static-webserver/client/source/class/osparc/node/slideshow/NodeView.js b/services/static-webserver/client/source/class/osparc/node/slideshow/NodeView.js index ce84b75556d..4aeb8b0b4a6 100644 --- a/services/static-webserver/client/source/class/osparc/node/slideshow/NodeView.js +++ b/services/static-webserver/client/source/class/osparc/node/slideshow/NodeView.js @@ -143,7 +143,7 @@ qx.Class.define("osparc.node.slideshow.NodeView", { this._iFrameLayout.removeAll(); const node = this.getNode(); - if (node) { + if (node && node.getIFrame()) { const loadingPage = node.getLoadingPage(); const iFrame = node.getIFrame(); const src = iFrame.getSource(); From e6266396971506acfdcf898e7f5d1bd78be58386 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 11 Dec 2024 15:09:43 +0100 Subject: [PATCH 102/102] categories --- .../client/source/class/osparc/vipMarket/Market.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 a543fa2cfa6..8bd65242eb2 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js @@ -29,22 +29,22 @@ qx.Class.define("osparc.vipMarket.Market", { osparc.data.Resources.getInstance().getAllPages("licensedItems") .then(() => { [{ - category: "humanWhole", + category: "human", label: "Humans", icon: "@FontAwesome5Solid/users/20", url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/HumanWholeBody", }, { - category: "humanRegion", + category: "human_region", label: "Humans (Region)", icon: "@FontAwesome5Solid/users/20", url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/HumanBodyRegion", }, { - category: "animalWhole", + category: "animal", label: "Animals", icon: "@FontAwesome5Solid/users/20", url: "https://itis.swiss/PD_DirectDownload/getDownloadableItems/AnimalWholeBody", }, { - category: "compPhantom", + category: "phantom", label: "Phantoms", icon: "@FontAwesome5Solid/users/20", url: "https://speag.swiss/PD_DirectDownload/getDownloadableItems/ComputationalPhantom",