diff --git a/api/specs/web-server/_resource_usage.py b/api/specs/web-server/_resource_usage.py
index 5df12b4807d..8c4b36b3c01 100644
--- a/api/specs/web-server/_resource_usage.py
+++ b/api/specs/web-server/_resource_usage.py
@@ -73,8 +73,8 @@ async def list_resource_usage_services(
)
-@router.post(
- "/services/-/resource-usages:export",
+@router.get(
+ "/services/-/usage-report",
status_code=status.HTTP_302_FOUND,
responses={
status.HTTP_302_FOUND: {
diff --git a/services/static-webserver/client/.eslintrc.json b/services/static-webserver/client/.eslintrc.json
index e128edb014a..39bbadd185f 100644
--- a/services/static-webserver/client/.eslintrc.json
+++ b/services/static-webserver/client/.eslintrc.json
@@ -39,7 +39,8 @@
"semi": "off",
"comma-dangle": "off",
"object-curly-spacing": "off",
- "no-implicit-coercion": "off"
+ "no-implicit-coercion": "off",
+ "arrow-body-style": "off"
},
"env": {
"browser": true
diff --git a/services/static-webserver/client/source/class/osparc/Preferences.js b/services/static-webserver/client/source/class/osparc/Preferences.js
index d52b9e8b7e7..415d6eb9cd1 100644
--- a/services/static-webserver/client/source/class/osparc/Preferences.js
+++ b/services/static-webserver/client/source/class/osparc/Preferences.js
@@ -122,6 +122,13 @@ qx.Class.define("osparc.Preferences", {
check: "Boolean",
event: "changeAllowMetricsCollection",
apply: "__patchPreference"
+ },
+
+ billingCenterUsageColumnOrder: {
+ nullable: true,
+ check: "Array",
+ event: "changeBillingCenterUsageColumnOrder",
+ apply: "__patchPreference"
}
},
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 c7e33a113eb..11ff1399840 100644
--- a/services/static-webserver/client/source/class/osparc/data/Resources.js
+++ b/services/static-webserver/client/source/class/osparc/data/Resources.js
@@ -263,16 +263,15 @@ qx.Class.define("osparc.data.Resources", {
"resourceUsage": {
useCache: false,
endpoints: {
- getPage: {
+ get: {
method: "GET",
- url: statics.API + "/services/-/resource-usages?offset={offset}&limit={limit}"
- }
- }
- },
- "resourceUsagePerWallet": {
- useCache: false,
- endpoints: {
- getPage: {
+ url: statics.API + "/services/-/resource-usages?offset={offset}&limit={limit}&filters={filters}&order_by={orderBy}"
+ },
+ getWithWallet: {
+ method: "GET",
+ url: statics.API + "/services/-/resource-usages?wallet_id={walletId}&offset={offset}&limit={limit}&filters={filters}&order_by={orderBy}"
+ },
+ getWithWallet2: {
method: "GET",
url: statics.API + "/services/-/resource-usages?wallet_id={walletId}&offset={offset}&limit={limit}"
}
@@ -735,7 +734,7 @@ qx.Class.define("osparc.data.Resources", {
endpoints: {
get: {
method: "GET",
- url: statics.API + "/wallets/-/payments"
+ url: statics.API + "/wallets/-/payments?offset={offset}&limit={limit}"
},
startPayment: {
method: "POST",
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/AutoRecharge.js b/services/static-webserver/client/source/class/osparc/desktop/credits/AutoRecharge.js
index 079c5f1278d..10153748cfa 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/AutoRecharge.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/AutoRecharge.js
@@ -12,19 +12,19 @@
Authors:
* Odei Maiz (odeimaiz)
+ * Ignacio Pascual
************************************************************************ */
qx.Class.define("osparc.desktop.credits.AutoRecharge", {
- extend: qx.ui.core.Widget,
+ extend: qx.ui.container.Stack,
construct: function(walletId) {
this.base(arguments);
- this._setLayout(new qx.ui.layout.VBox(15));
-
const store = osparc.store.Store.getInstance();
const wallet = store.getWallets().find(w => w.getWalletId() == walletId);
+
this.__buildLayout();
this.setWallet(wallet);
},
@@ -48,40 +48,38 @@ qx.Class.define("osparc.desktop.credits.AutoRecharge", {
__topUpAmountField: null,
__monthlyLimitField: null,
__paymentMethodField: null,
- __topUpAmountHelper: null,
-
- _createChildControlImpl: function(id) {
- let control;
- switch (id) {
- case "auto-recharge-description":
- control = new qx.ui.basic.Label().set({
- value: this.tr("Keep your balance running smoothly by automatically setting your credits to be recharged when it runs low."),
- font: "text-14",
- rich: true,
- wrap: true
- });
- this._add(control);
- break;
- case "auto-recharge-form":
- control = this.__getAutoRechargeForm();
- this._add(control);
- break;
- case "buttons-layout-2":
- control = new qx.ui.container.Composite(new qx.ui.layout.HBox(5));
- this._add(control);
- break;
- case "save-auto-recharge-button":
- control = this.__getSaveAutoRechargeButton();
- this.getChildControl("buttons-layout-2").add(control);
- break;
- }
- return control || this.base(arguments, id);
- },
__buildLayout: function() {
- this.getChildControl("auto-recharge-description");
- this.getChildControl("auto-recharge-form");
- this.getChildControl("save-auto-recharge-button");
+ this.removeAll()
+
+ this.__mainContent = new qx.ui.container.Composite(new qx.ui.layout.VBox(15).set({
+ alignX: "center"
+ }))
+ const title = new qx.ui.basic.Label("Auto-recharge").set({
+ marginTop: 25,
+ font: "title-18"
+ });
+ const subtitle = new qx.ui.basic.Label("Keep your balance running smoothly by automatically setting your credits to be recharged when it runs low.").set({
+ rich: true,
+ font: "text-14",
+ textAlign: "center"
+ });
+ this.__mainContent.add(title);
+ this.__mainContent.add(subtitle);
+ this.__mainContent.add(this.__getAutoRechargeForm())
+ this.__mainContent.add(this.__getButtons())
+ this.add(this.__mainContent)
+
+ this.__fetchingView = new qx.ui.container.Composite(new qx.ui.layout.VBox().set({
+ alignX: "center",
+ alignY: "middle"
+ }))
+ const image = new qx.ui.basic.Image("@FontAwesome5Solid/circle-notch/26")
+ image.getContentElement().addClass("rotate")
+ this.__fetchingView.add(image)
+ this.add(this.__fetchingView)
+
+ this.setSelection([this.__fetchingView])
},
__applyWallet: function(wallet) {
@@ -100,6 +98,8 @@ qx.Class.define("osparc.desktop.credits.AutoRecharge", {
const paymentMethodSB = this.__paymentMethodField;
await osparc.desktop.credits.Utils.populatePaymentMethodSelector(wallet, paymentMethodSB);
+ this.setSelection([this.__fetchingView])
+
// populate the form
const params = {
url: {
@@ -114,7 +114,6 @@ qx.Class.define("osparc.desktop.credits.AutoRecharge", {
__populateForm: function(arData) {
this.__enabledField.setValue(arData.enabled);
this.__topUpAmountField.setValue(arData["topUpAmountInUsd"]);
- this.__topUpAmountHelper.setValue(this.tr(`When your account reaches ${arData["minBalanceInUsd"]} credits, it gets recharged by this amount`));
if (arData["monthlyLimitInUsd"]) {
this.__monthlyLimitField.setValue(arData["monthlyLimitInUsd"] > 0 ? arData["monthlyLimitInUsd"] : 0);
} else {
@@ -125,52 +124,57 @@ qx.Class.define("osparc.desktop.credits.AutoRecharge", {
if (paymentMethodFound) {
paymentMethodSB.setSelection([paymentMethodFound]);
}
+ this.setSelection([this.__mainContent])
},
__getAutoRechargeForm: function() {
- const autoRechargeLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(15));
-
+ const autoRechargeLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(10).set({
+ alignX: "center"
+ })).set({
+ marginTop: 20
+ });
- const enabledLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(5));
- const enabledTitle = new qx.ui.basic.Label().set({
- value: this.tr("ENABLED"),
- font: "text-14"
+ const enabledLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(5)).set({
+ allowStretchX: false,
+ width: 274
+ });
+ const enabledTitle = new qx.ui.basic.Label(this.tr("Enabled")).set({
+ marginLeft: 2
+ })
+ const enabledCheckbox = this.__enabledField = new qx.ui.form.CheckBox().set({
+ appearance: "appmotion-buy-credits-checkbox"
});
- const enabledCheckbox = this.__enabledField = new qx.ui.form.CheckBox();
enabledLayout.add(enabledTitle);
enabledLayout.add(enabledCheckbox);
autoRechargeLayout.add(enabledLayout);
-
- const topUpAmountLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(5));
+ const topUpAmountLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(5)).set({
+ allowStretchX: false,
+ marginTop: 15
+ });
const topUpAmountTitleLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5));
- const topUpAmountTitle = new qx.ui.basic.Label().set({
- value: this.tr("RECHARGING AMOUNT (US$)"),
- font: "text-14"
+ const topUpAmountTitle = new qx.ui.basic.Label(this.tr("Recharging amount (USD)")).set({
+ marginLeft: 15
});
topUpAmountTitleLayout.add(topUpAmountTitle);
- const topUpAmountInfo = new osparc.ui.hint.InfoHint("Amount in US$ payed when auto-recharge condition is satisfied.");
+ const topUpAmountInfo = new osparc.ui.hint.InfoHint("Amount in USD payed when auto-recharge condition is satisfied.");
topUpAmountTitleLayout.add(topUpAmountInfo);
topUpAmountLayout.add(topUpAmountTitleLayout);
const topUpAmountField = this.__topUpAmountField = new qx.ui.form.Spinner().set({
minimum: 10,
maximum: 10000,
- maxWidth: 200
+ width: 300,
+ appearance: "appmotion-buy-credits-spinner"
});
topUpAmountLayout.add(topUpAmountField);
- const topUpAmountHelper = this.__topUpAmountHelper = new qx.ui.basic.Label().set({
- font: "text-12",
- rich: true,
- wrap: true
- });
- topUpAmountLayout.add(topUpAmountHelper);
autoRechargeLayout.add(topUpAmountLayout);
- const monthlyLimitLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(5));
+ const monthlyLimitLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(5)).set({
+ allowStretchX: false
+ });
const monthlyLimitTitleLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5));
- const monthlyLimitTitle = new qx.ui.basic.Label().set({
- value: this.tr("MONTHLY LIMIT (US$)"),
- font: "text-14"
+ const monthlyLimitTitle = new qx.ui.basic.Label(this.tr("Monthly limit (USD)")).set({
+ marginLeft: 15
});
monthlyLimitTitleLayout.add(monthlyLimitTitle);
const monthlyLimitTitleInfo = new osparc.ui.hint.InfoHint(this.tr("Maximum amount in US$ charged within a natural month."));
@@ -179,31 +183,32 @@ qx.Class.define("osparc.desktop.credits.AutoRecharge", {
const monthlyLimitField = this.__monthlyLimitField = new qx.ui.form.Spinner().set({
minimum: 0,
maximum: 100000,
- maxWidth: 200
+ width: 300,
+ appearance: "appmotion-buy-credits-spinner"
});
monthlyLimitLayout.add(monthlyLimitField);
- const monthlyLimitHelper = new qx.ui.basic.Label().set({
- value: this.tr("To disable spending limit, clear input field"),
+ const monthlyLimitHelper = new qx.ui.basic.Label(this.tr("To disable spending limit, clear input field")).set({
font: "text-12",
- rich: true,
- wrap: true
+ marginLeft: 15,
+ rich: true
});
monthlyLimitLayout.add(monthlyLimitHelper);
autoRechargeLayout.add(monthlyLimitLayout);
- const paymentMethodLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(5));
- const paymentMethodTitle = new qx.ui.basic.Label().set({
- value: this.tr("PAY WITH"),
- font: "text-14"
+ const paymentMethodLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(5)).set({
+ allowStretchX: false
+ });
+ const paymentMethodTitle = new qx.ui.basic.Label(this.tr("Pay with")).set({
+ marginLeft: 15
});
paymentMethodLayout.add(paymentMethodTitle);
const paymentMethodField = this.__paymentMethodField = new qx.ui.form.SelectBox().set({
- minWidth: 200,
- maxWidth: 200
+ width: 300,
+ appearance: "appmotion-buy-credits-select"
});
paymentMethodLayout.add(paymentMethodField);
const addNewPaymentMethod = new qx.ui.basic.Label(this.tr("Add Payment Method")).set({
- padding: 0,
+ marginLeft: 15,
cursor: "pointer",
font: "link-label-12"
});
@@ -211,6 +216,16 @@ qx.Class.define("osparc.desktop.credits.AutoRecharge", {
paymentMethodLayout.add(addNewPaymentMethod);
autoRechargeLayout.add(paymentMethodLayout);
+ enabledCheckbox.bind("value", monthlyLimitField, "enabled")
+ enabledCheckbox.bind("value", paymentMethodField, "enabled")
+ enabledCheckbox.bind("value", topUpAmountField, "enabled")
+ enabledCheckbox.bind("value", monthlyLimitHelper, "visibility", {
+ converter: value => value ? "visible" : "hidden"
+ })
+ enabledCheckbox.bind("value", addNewPaymentMethod, "visibility", {
+ converter: value => value ? "visible" : "hidden"
+ })
+
return autoRechargeLayout;
},
@@ -243,17 +258,25 @@ qx.Class.define("osparc.desktop.credits.AutoRecharge", {
.finally(() => fetchButton.setFetching(false));
},
- __getSaveAutoRechargeButton: function() {
- const saveAutoRechargeBtn = new osparc.ui.form.FetchButton().set({
- label: this.tr("Save and close"),
- font: "text-14",
- appearance: "strong-button",
- maxWidth: 200,
- center: true
+ __getButtons: function() {
+ const btnContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(10).set({
+ alignX: "center"
+ })).set({
+ marginTop: 30,
+ marginBottom: 15
+ });
+ const saveAutoRechargeBtn = new osparc.ui.form.FetchButton(this.tr("Save and close")).set({
+ appearance: "appmotion-button-action"
});
const successfulMsg = this.tr("Changes on the Auto recharge were successfully saved");
saveAutoRechargeBtn.addListener("execute", () => this.__updateAutoRecharge(this.__enabledField.getValue(), saveAutoRechargeBtn, successfulMsg));
- return saveAutoRechargeBtn;
+ btnContainer.add(saveAutoRechargeBtn)
+ const cancelBtn = new qx.ui.form.Button("Cancel").set({
+ appearance: "appmotion-button"
+ });
+ cancelBtn.addListener("execute", () => this.fireEvent("close"))
+ btnContainer.add(cancelBtn)
+ return btnContainer;
}
}
});
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js b/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js
index 5b0d4fba1c3..e8f258a7248 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js
@@ -114,21 +114,6 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", {
return page;
},
- __getBuyCreditsPage: function() {
- const title = this.tr("Buy Credits");
- const iconSrc = "@FontAwesome5Solid/dollar-sign/22";
- const page = new osparc.desktop.preferences.pages.BasePage(title, iconSrc);
- page.showLabelOnTab();
- const buyCredits = this.__buyCredits = new osparc.desktop.credits.BuyCredits();
- buyCredits.set({
- margin: 10
- });
- buyCredits.addListener("addNewPaymentMethod", () => this.openPaymentMethods(true), this);
- buyCredits.addListener("transactionCompleted", () => this.openTransactions(true), this);
- page.add(buyCredits);
- return page;
- },
-
__getTransactionsPage: function() {
const title = this.tr("Transactions");
const iconSrc = "@FontAwesome5Solid/exchange-alt/22";
@@ -138,7 +123,7 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", {
transactions.set({
margin: 10
});
- page.add(transactions);
+ page.add(transactions, { flex: 1 });
return page;
},
@@ -151,7 +136,7 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", {
usage.set({
margin: 10
});
- page.add(usage);
+ page.add(usage, { flex: 1 });
return page;
},
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCredits.js b/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCredits.js
deleted file mode 100644
index 5a5251fcd34..00000000000
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCredits.js
+++ /dev/null
@@ -1,139 +0,0 @@
-/* ************************************************************************
-
- 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.desktop.credits.BuyCredits", {
- extend: qx.ui.core.Widget,
-
- construct: function() {
- this.base(arguments);
-
- this._setLayout(new qx.ui.layout.VBox(15));
-
- const store = osparc.store.Store.getInstance();
- store.bind("contextWallet", this, "contextWallet");
- },
-
- properties: {
- contextWallet: {
- check: "osparc.data.model.Wallet",
- init: null,
- nullable: false,
- event: "changeContextWallet",
- apply: "__buildLayout"
- }
- },
-
- events: {
- "addNewPaymentMethod": "qx.event.type.Event",
- "transactionCompleted": "qx.event.type.Event"
- },
-
- members: {
- _createChildControlImpl: function(id) {
- let control;
- switch (id) {
- case "credits-intro":
- control = this.__getCreditsExplanation();
- this._add(control);
- break;
- case "payment-mode-layout":
- control = new qx.ui.container.Composite(new qx.ui.layout.HBox(5));
- this._add(control);
- break;
- case "payment-mode-title":
- control = new qx.ui.basic.Label(this.tr("Payment mode")).set({
- font: "text-14"
- });
- this.getChildControl("payment-mode-layout").add(control);
- break;
- case "payment-mode": {
- this.getChildControl("payment-mode-title");
- control = new qx.ui.form.SelectBox().set({
- allowGrowX: false,
- allowGrowY: false
- });
- const autoItem = new qx.ui.form.ListItem(this.tr("Automatic"), null, "automatic");
- control.add(autoItem);
- const manualItem = new qx.ui.form.ListItem(this.tr("Manual"), null, "manual");
- control.add(manualItem);
- this.getChildControl("payment-mode-layout").add(control);
- break;
- }
- case "one-time-payment":
- control = new osparc.desktop.credits.OneTimePayment().set({
- margin: 10,
- maxWidth: 400
- });
- this.bind("contextWallet", control, "wallet");
- control.addListener("addNewPaymentMethod", () => this.fireEvent("addNewPaymentMethod"));
- control.addListener("transactionCompleted", () => this.fireEvent("transactionCompleted"));
- this._add(control);
- break;
- case "auto-recharge":
- control = new osparc.desktop.credits.AutoRecharge().set({
- margin: 10,
- maxWidth: 400
- });
- control.addListener("addNewPaymentMethod", () => this.fireEvent("addNewPaymentMethod"));
- this.bind("contextWallet", control, "wallet");
- this._add(control);
- break;
- }
- return control || this.base(arguments, id);
- },
-
- __buildLayout: function() {
- this._removeAll();
- this._createChildControlImpl("credits-intro");
- const wallet = this.getContextWallet();
- if (wallet.getMyAccessRights()["write"]) {
- this._createChildControlImpl("wallet-billing-settings");
- const paymentMode = this._createChildControlImpl("payment-mode");
- const autoRecharge = this._createChildControlImpl("auto-recharge");
- const oneTime = this._createChildControlImpl("one-time-payment");
- autoRecharge.show();
- oneTime.exclude();
- paymentMode.addListener("changeSelection", e => {
- const model = e.getData()[0].getModel();
- if (model === "manual") {
- autoRecharge.exclude();
- oneTime.show();
- } else {
- autoRecharge.show();
- oneTime.exclude();
- }
- });
- } else {
- this._add(osparc.desktop.credits.Utils.getNoWriteAccessOperationsLabel());
- }
- },
-
- __getCreditsExplanation: function() {
- const layout = new qx.ui.container.Composite(new qx.ui.layout.VBox(20));
-
- const label = new qx.ui.basic.Label().set({
- value: "Explain here what a Credit is and what one can run/do with them.",
- font: "text-14",
- rich: true,
- wrap: true
- });
- layout.add(label);
-
- return layout;
- }
- }
-});
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCreditsForm.js b/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCreditsForm.js
index 9f187412019..6492b7f1a54 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCreditsForm.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCreditsForm.js
@@ -19,7 +19,9 @@ qx.Class.define("osparc.desktop.credits.BuyCreditsForm", {
font: "title-18"
});
const subtitle = new qx.ui.basic.Label("A one-off, non recurring payment.").set({
- font: "text-14"
+ rich: true,
+ font: "text-14",
+ textAlign: "center"
});
this._add(title);
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCreditsInput.js b/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCreditsInput.js
index bb3e1606589..d4f121b9241 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCreditsInput.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCreditsInput.js
@@ -62,7 +62,6 @@ qx.Class.define("osparc.desktop.credits.BuyCreditsInput", {
}))
const input = new qx.ui.form.TextField().set({
appearance: "appmotion-buy-credits-input",
- decorator: "appmotion-buy-credits-input",
textAlign: "center",
width: 80,
...inputProps
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCreditsStepper.js b/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCreditsStepper.js
index e6141de2183..4ad8f07e952 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCreditsStepper.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCreditsStepper.js
@@ -58,7 +58,9 @@ qx.Class.define("osparc.desktop.credits.BuyCreditsStepper", {
osparc.data.Resources.fetch("payments", "startPayment", params)
.then(data => {
const { paymentId, paymentFormUrl } = data;
- this.__iframe = new qx.ui.embed.Iframe(paymentFormUrl);
+ this.__iframe = new qx.ui.embed.Iframe(paymentFormUrl).set({
+ decorator: "no-border-2"
+ });
this.add(this.__iframe);
osparc.wrapper.WebSocket.getInstance().getSocket().once("paymentCompleted", wsData => {
const paymentData = JSON.parse(wsData);
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/CurrentUsage.js b/services/static-webserver/client/source/class/osparc/desktop/credits/CurrentUsage.js
index 243922015d1..1d303405901 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/CurrentUsage.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/CurrentUsage.js
@@ -66,7 +66,7 @@ qx.Class.define("osparc.desktop.credits.CurrentUsage", {
limit: 10
}
};
- osparc.data.Resources.fetch("resourceUsagePerWallet", "getPage", params)
+ osparc.data.Resources.fetch("resourceUsage", "getWithWallet2", params)
.then(data => {
const currentTasks = data.filter(d => (d.project_id === currentStudy.getUuid()) && d.service_run_status === "RUNNING");
let cost = 0;
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/DateFilters.js b/services/static-webserver/client/source/class/osparc/desktop/credits/DateFilters.js
index 7f2d7654cc7..b661a16b45f 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/DateFilters.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/DateFilters.js
@@ -7,18 +7,24 @@
qx.Class.define("osparc.desktop.credits.DateFilters", {
extend: qx.ui.core.Widget,
+
construct() {
this.base(arguments);
this._setLayout(new qx.ui.layout.HBox(7));
this._buildLayout();
},
+
events: {
"change": "qx.event.type.Data"
},
+
members: {
_buildLayout() {
this._removeAll();
- this.__from = this.__addDateInput("From");
+ const defaultFrom = new Date()
+ defaultFrom.setMonth(defaultFrom.getMonth() - 1)
+ // Range defaults to previous month
+ this.__from = this.__addDateInput("From", defaultFrom);
this.__until = this.__addDateInput("Until");
const lastWeekBtn = new qx.ui.form.Button("Last week").set({
allowStretchY: false,
@@ -57,17 +63,19 @@ qx.Class.define("osparc.desktop.credits.DateFilters", {
})
this._add(lastYearBtn);
},
- __addDateInput(label) {
+
+ __addDateInput(label, initDate) {
const container = new qx.ui.container.Composite(new qx.ui.layout.VBox());
const lbl = new qx.ui.basic.Label(label);
container.add(lbl);
const datepicker = new qx.ui.form.DateField();
- datepicker.setValue(new Date());
+ datepicker.setValue(initDate ? initDate : new Date());
datepicker.addListener("changeValue", e => this._changeHandler(e));
container.add(datepicker);
this._add(container);
return datepicker;
},
+
_changeHandler(e) {
const timestampFrom = this.__from.getValue().getTime();
const timestampUntil = this.__until.getValue().getTime();
@@ -87,6 +95,15 @@ qx.Class.define("osparc.desktop.credits.DateFilters", {
from,
until
});
+ },
+
+ getValue() {
+ const from = osparc.utils.Utils.formatDateYyyyMmDd(this.__from.getValue())
+ const until = osparc.utils.Utils.formatDateYyyyMmDd(this.__until.getValue())
+ return {
+ from,
+ until
+ }
}
}
});
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Transactions.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Transactions.js
index cbeccb3f2ab..4228977d0c0 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/Transactions.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Transactions.js
@@ -41,17 +41,6 @@ qx.Class.define("osparc.desktop.credits.Transactions", {
},
members: {
- _createChildControlImpl: function(id) {
- let control;
- switch (id) {
- case "transactions-table":
- control = new osparc.desktop.credits.TransactionsTable();
- this._add(control);
- break;
- }
- return control || this.base(arguments, id);
- },
-
__buildLayout: function() {
this._removeAll();
@@ -63,52 +52,36 @@ qx.Class.define("osparc.desktop.credits.Transactions", {
});
this._add(this.__introLabel);
- const filterContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox())
- this.__dateFilters = new osparc.desktop.credits.DateFilters();
- this.__dateFilters.addListener("change", e => this.__saveFilters(e.getData()));
- filterContainer.add(this.__dateFilters);
-
- filterContainer.add(new qx.ui.core.Spacer(), {
- flex: 1
- });
-
- this.__exportButton = new qx.ui.form.Button(this.tr("Export")).set({
- allowStretchY: false,
- alignY: "bottom"
- });
- this.__exportButton.addListener("execute", () => {
- console.log("export", this.__params);
- });
- filterContainer.add(this.__exportButton);
-
// FEATURE TOGGLE
+ // const filterContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox())
+ // this.__dateFilters = new osparc.desktop.credits.DateFilters();
+ // this.__dateFilters.addListener("change", e => this.__saveFilters(e.getData()));
+ // filterContainer.add(this.__dateFilters);
+
+ // filterContainer.add(new qx.ui.core.Spacer(), {
+ // flex: 1
+ // });
+
+ // this.__exportButton = new qx.ui.form.Button(this.tr("Export")).set({
+ // allowStretchY: false,
+ // alignY: "bottom"
+ // });
+ // this.__exportButton.addListener("execute", () => {
+ // console.log("export", this.__params);
+ // });
+ // filterContainer.add(this.__exportButton);
+
// this._add(filterContainer);
- const wallet = this.__personalWallet;
- if (wallet && wallet.getMyAccessRights()["write"]) {
- const transactionsTable = this._createChildControlImpl("transactions-table");
- osparc.data.Resources.fetch("payments", "get")
- .then(transactions => {
- if ("data" in transactions) {
- transactionsTable.addData(transactions["data"]);
- }
- })
- .catch(err => console.error(err));
+ this.__table = new osparc.desktop.credits.TransactionsTable().set({
+ marginTop: 10
+ })
+ if (this.__personalWallet && this.__personalWallet.getMyAccessRights()["write"]) {
+ this._add(this.__table, { flex: 1 })
+ this.__table.getTableModel().reloadData()
} else {
- this._add(osparc.desktop.credits.Utils.getNoWriteAccessInformationLabel());
+ this._add(osparc.desktop.credits.Utils.getNoWriteAccessInformationLabel())
}
- },
-
- refresh: function() {
- console.log(this.__params);
- },
-
- __saveFilters: function(filters) {
- this.__params = {
- ...this.__params,
- filters
- };
- this.refresh();
}
}
});
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/TransactionsTable.js b/services/static-webserver/client/source/class/osparc/desktop/credits/TransactionsTable.js
index 49a7460b046..4f253366c5e 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/TransactionsTable.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/TransactionsTable.js
@@ -12,122 +12,34 @@
Authors:
* Odei Maiz (odeimaiz)
+ * Ignacio Pascual (ignapas)
************************************************************************ */
qx.Class.define("osparc.desktop.credits.TransactionsTable", {
- extend: osparc.ui.table.Table,
+ extend: qx.ui.table.Table,
construct: function() {
- const model = new qx.ui.table.model.Simple();
- const cols = this.self().COLUMNS;
- const colNames = Object.values(cols).map(col => col.title);
- model.setColumns(colNames);
-
- this.base(arguments, model, {
- tableColumnModel: obj => new qx.ui.table.columnmodel.Resize(obj),
- statusBarVisible: false
- });
+ this.base(arguments)
+ const model = new osparc.desktop.credits.TransactionsTableModel();
+ this.setTableModel(model)
+ this.setStatusBarVisible(false)
const columnModel = this.getTableColumnModel();
- columnModel.setDataCellRenderer(cols.credits.pos, new qx.ui.table.cellrenderer.Number());
- columnModel.setDataCellRenderer(cols.status.pos, new qx.ui.table.cellrenderer.Html());
- columnModel.setDataCellRenderer(cols.invoice.pos, new qx.ui.table.cellrenderer.Html());
- this.setColumnWidth(cols.invoice.pos, 50);
- this.makeItLoose();
- },
-
- statics: {
- COLUMNS: {
- date: {
- pos: 0,
- title: qx.locale.Manager.tr("Date")
- },
- price: {
- pos: 1,
- title: qx.locale.Manager.tr("Price USD")
- },
- credits: {
- pos: 2,
- title: qx.locale.Manager.tr("Credits")
- },
- status: {
- pos: 3,
- title: qx.locale.Manager.tr("Status")
- },
- comment: {
- pos: 4,
- title: qx.locale.Manager.tr("Comment")
- },
- invoice: {
- pos: 5,
- title: qx.locale.Manager.tr("Invoice")
- }
- },
-
- addColorTag: function(status) {
- const color = this.getLevelColor(status);
- status = osparc.utils.Utils.onlyFirstsUp(status);
- return ("" + status + "");
- },
-
- getLevelColor: function(status) {
- const colorManager = qx.theme.manager.Color.getInstance();
- let logLevel = null;
- switch (status) {
- case "SUCCESS":
- logLevel = "info";
- break;
- case "PENDING":
- logLevel = "warning";
- break;
- case "CANCELED":
- case "FAILED":
- logLevel = "error";
- break;
- default:
- console.error("completedStatus unknown");
- break;
- }
- return colorManager.resolve("logger-"+logLevel+"-message");
- },
-
- createPdfIconWithLink: function(link) {
- return ``;
- },
-
- respDataToTableRow: function(data) {
- const cols = this.COLUMNS;
- const newData = [];
- newData[cols["date"].pos] = osparc.utils.Utils.formatDateAndTime(new Date(data["createdAt"]));
- newData[cols["price"].pos] = data["priceDollars"] ? data["priceDollars"].toFixed(2) : 0;
- newData[cols["credits"].pos] = data["osparcCredits"] ? data["osparcCredits"].toFixed(2) : 0;
- if (data["completedStatus"]) {
- newData[cols["status"].pos] = this.addColorTag(data["completedStatus"]);
- }
- newData[cols["comment"].pos] = data["comment"];
- const invoiceUrl = data["invoiceUrl"];
- newData[cols["invoice"].pos] = invoiceUrl? this.createPdfIconWithLink(invoiceUrl) : "";
- return newData;
- },
+ columnModel.setColumnWidth(0, 130);
+ columnModel.setColumnWidth(1, 70);
+ columnModel.setColumnWidth(2, 70);
+ columnModel.setColumnWidth(3, 70);
+ columnModel.setColumnWidth(4, 320);
+ columnModel.setColumnWidth(5, 60);
- respDataToTableData: function(datas) {
- const newDatas = [];
- if (datas) {
- datas.forEach(data => {
- const newData = this.respDataToTableRow(data);
- newDatas.push(newData);
- });
- }
- return newDatas;
- }
- },
+ columnModel.setDataCellRenderer(1, new qx.ui.table.cellrenderer.Number());
+ columnModel.setDataCellRenderer(2, new qx.ui.table.cellrenderer.Number());
+ columnModel.setDataCellRenderer(3, new qx.ui.table.cellrenderer.Html());
+ columnModel.setDataCellRenderer(5, new qx.ui.table.cellrenderer.Html());
- members: {
- addData: function(datas) {
- const newDatas = this.self().respDataToTableData(datas);
- this.setData(newDatas);
- }
+ this.setHeaderCellHeight(26);
+ this.setRowHeight(26);
}
});
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/TransactionsTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/TransactionsTableModel.js
new file mode 100644
index 00000000000..a6a40fb3972
--- /dev/null
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/TransactionsTableModel.js
@@ -0,0 +1,154 @@
+/*
+ * oSPARC - The SIMCORE frontend - https://osparc.io
+ * Copyright: 2024 IT'IS Foundation - https://itis.swiss
+ * License: MIT - https://opensource.org/licenses/MIT
+ * Authors: Ignacio Pascual (ignapas)
+ */
+const SERVER_MAX_LIMIT = 49
+
+qx.Class.define("osparc.desktop.credits.TransactionsTableModel", {
+ extend: qx.ui.table.model.Remote,
+
+ construct() {
+ this.base(arguments)
+ this.setColumns([
+ qx.locale.Manager.tr("Date"),
+ qx.locale.Manager.tr("Price USD"),
+ qx.locale.Manager.tr("Credits"),
+ qx.locale.Manager.tr("Status"),
+ qx.locale.Manager.tr("Comment"),
+ qx.locale.Manager.tr("Invoice")
+ ], [
+ "date",
+ "price",
+ "credits",
+ "status",
+ "comment",
+ "invoice"
+ ])
+ this.setColumnSortable(0, false)
+ this.setColumnSortable(1, false)
+ this.setColumnSortable(2, false)
+ this.setColumnSortable(3, false)
+ this.setColumnSortable(4, false)
+ this.setColumnSortable(5, false)
+ },
+
+ properties: {
+ walletId: {
+ check: "Number",
+ nullable: false
+ },
+ filters: {
+ check: "Object",
+ init: null
+ },
+ isFetching: {
+ check: "Boolean",
+ init: false,
+ event: "changeFetching"
+ }
+ },
+
+ members: {
+ // overridden
+ _loadRowCount() {
+ osparc.data.Resources.fetch("payments", "get", {
+ url: {
+ limit: 1,
+ offset: 0
+ }
+ }, undefined, {
+ resolveWResponse: true
+ })
+ .then(({ data: resp }) => {
+ this._onRowCountLoaded(resp["_meta"].total)
+ })
+ .catch(() => {
+ this._onRowCountLoaded(null)
+ })
+ },
+ // overridden
+ _loadRowData(firstRow, qxLastRow) {
+ this.setIsFetching(true)
+ // Please Qloocloox don't ask for more rows than there are
+ const lastRow = Math.min(qxLastRow, this._rowCount - 1)
+ const getFetchPromise = (offset, limit=SERVER_MAX_LIMIT) => {
+ return osparc.data.Resources.fetch("payments", "get", {
+ url: {
+ limit,
+ offset
+ }
+ })
+ .then(({ data: rawData }) => {
+ const data = []
+ rawData.forEach(rawRow => {
+ data.push({
+ date: osparc.utils.Utils.formatDateAndTime(new Date(rawRow.createdAt)),
+ price: rawRow.priceDollars ? rawRow.priceDollars.toFixed(2) : 0,
+ credits: rawRow.osparcCredits ? rawRow.osparcCredits.toFixed(2) * 1 : 0,
+ status: this.__addColorTag(rawRow.completedStatus),
+ comment: rawRow.comment,
+ invoice: rawRow.invoiceUrl ? this.__createPdfIconWithLink(rawRow.invoiceUrl) : ""
+ })
+ })
+ return data
+ })
+ }
+ // Divides the model row request into several server requests to comply with the number of rows server limit
+ const reqLimit = lastRow - firstRow + 1 // Number of requested rows
+ const nRequests = Math.ceil(reqLimit / SERVER_MAX_LIMIT)
+ if (nRequests > 1) {
+ let requests = []
+ for (let i=firstRow; i <= lastRow; i += SERVER_MAX_LIMIT) {
+ requests.push(getFetchPromise(i, i > lastRow - SERVER_MAX_LIMIT + 1 ? reqLimit % SERVER_MAX_LIMIT : SERVER_MAX_LIMIT))
+ }
+ Promise.all(requests)
+ .then(responses => {
+ this._onRowDataLoaded(responses.flat())
+ })
+ .catch(err => {
+ console.error(err)
+ this._onRowDataLoaded(null)
+ })
+ .finally(() => this.setIsFetching(false))
+ } else {
+ getFetchPromise(firstRow, reqLimit)
+ .then(data => {
+ this._onRowDataLoaded(data)
+ })
+ .catch(err => {
+ console.error(err)
+ this._onRowDataLoaded(null)
+ })
+ .finally(() => this.setIsFetching(false))
+ }
+ },
+ __getLevelColor: function(status) {
+ const colorManager = qx.theme.manager.Color.getInstance();
+ let logLevel = null;
+ switch (status) {
+ case "SUCCESS":
+ logLevel = "info";
+ break;
+ case "PENDING":
+ logLevel = "warning";
+ break;
+ case "CANCELED":
+ case "FAILED":
+ logLevel = "error";
+ break;
+ default:
+ console.error("completedStatus unknown");
+ break;
+ }
+ return colorManager.resolve(`logger-${logLevel}-message`);
+ },
+ __addColorTag: function(status) {
+ return `${osparc.utils.Utils.onlyFirstsUp(status)}`;
+ },
+ __createPdfIconWithLink: function(link) {
+ return ``;
+ }
+ }
+})
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Usage.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Usage.js
index 1413b6ef8ed..b3e344976c8 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/Usage.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Usage.js
@@ -12,6 +12,7 @@
Authors:
* Odei Maiz (odeimaiz)
+ * Ignacio Pascual (ignapas)
************************************************************************ */
@@ -29,71 +30,15 @@ qx.Class.define("osparc.desktop.credits.Usage", {
this.__buildLayout()
},
- statics: {
- ITEMS_PER_PAGE: 15
- },
-
members: {
- __prevRequestParams: null,
- __nextRequestParams: null,
-
- _createChildControlImpl: function(id) {
- let control;
- switch (id) {
- case "usage-table":
- control = new osparc.desktop.credits.UsageTable().set({
- height: (this.self().ITEMS_PER_PAGE*20 + 40)
- });
- this._add(control);
- break;
- case "page-buttons":
- control = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)).set({
- allowGrowX: true,
- alignX: "center",
- alignY: "middle"
- });
- this._add(control);
- break;
- case "prev-page-button": {
- control = new qx.ui.form.Button().set({
- icon: "@FontAwesome5Solid/chevron-left/12",
- allowGrowX: false
- });
- control.addListener("execute", () => this.__fetchData(this.__getPrevRequest()));
- const pageButtons = this.getChildControl("page-buttons");
- pageButtons.add(control);
- break;
- }
- case "current-page-label": {
- control = new qx.ui.basic.Label().set({
- font: "text-14",
- textAlign: "center",
- alignY: "middle"
- });
- const pageButtons = this.getChildControl("page-buttons");
- pageButtons.add(control);
- break;
- }
- case "next-page-button": {
- control = new qx.ui.form.Button().set({
- icon: "@FontAwesome5Solid/chevron-right/12",
- allowGrowX: false
- });
- control.addListener("execute", () => this.__fetchData(this.__getNextRequest()));
- const pageButtons = this.getChildControl("page-buttons");
- pageButtons.add(control);
- break;
- }
- }
- return control || this.base(arguments, id);
- },
-
__buildLayout: function() {
this._removeAll();
const container = new qx.ui.container.Composite(new qx.ui.layout.VBox(5));
+
const lbl = new qx.ui.basic.Label("Select a credit account:");
container.add(lbl);
+
const selectBoxContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5));
const walletSelectBox = new qx.ui.form.SelectBox().set({
allowStretchX: false,
@@ -109,9 +54,13 @@ qx.Class.define("osparc.desktop.credits.Usage", {
this.__fetchingImg.getContentElement().addClass("rotate");
selectBoxContainer.add(this.__fetchingImg);
container.add(selectBoxContainer);
+
const filterContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox())
this.__dateFilters = new osparc.desktop.credits.DateFilters();
- this.__dateFilters.addListener("change", e => console.log(e.getData()));
+ this.__dateFilters.addListener("change", e => {
+ this.__table.getTableModel().setFilters(e.getData())
+ this.__table.getTableModel().reloadData()
+ });
filterContainer.add(this.__dateFilters);
filterContainer.add(new qx.ui.core.Spacer(), {
flex: 1
@@ -121,95 +70,54 @@ qx.Class.define("osparc.desktop.credits.Usage", {
alignY: "bottom"
});
this.__exportButton.addListener("execute", () => {
- console.log("export");
+ this.__handleExport()
});
filterContainer.add(this.__exportButton);
- // FEATURE TOGGLE
- // container.add(filterContainer);
+ container.add(filterContainer);
+
this._add(container);
+
walletSelectBox.addListener("changeSelection", e => {
if (walletSelectBox.getSelection().length) {
- const selectedWallet = walletSelectBox.getSelection()[0].getModel();
- this.__selectedWallet = selectedWallet;
- this.__fetchData();
+ this.__selectedWallet = walletSelectBox.getSelection()[0].getModel()
+ if (this.__table) {
+ this.__table.getTableModel().setWalletId(this.__selectedWallet.getWalletId())
+ this.__table.getTableModel().reloadData()
+ } else {
+ // qx: changeSelection is triggered after the first item is added to SelectBox
+ this.__table = new osparc.desktop.credits.UsageTable(this.__selectedWallet.getWalletId(), this.__dateFilters.getValue()).set({
+ marginTop: 10
+ })
+ this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", {
+ converter: isFetching => isFetching ? "visible" : "excluded"
+ })
+ container.add(this.__table, { flex: 1 })
+ }
}
});
- this.__userWallets.forEach(wallet => {
- walletSelectBox.add(new qx.ui.form.ListItem(wallet.getName(), null, wallet));
- });
- },
- __fetchData: function(request) {
- this.__fetchingImg.show();
-
- if (request === undefined) {
- request = this.__getNextRequest();
- }
- request
- .then(resp => {
- const data = resp["data"];
- this.__setData(data);
- this.__prevRequestParams = resp["_links"]["prev"];
- this.__nextRequestParams = resp["_links"]["next"];
- this.__evaluatePageButtons(resp);
- })
- .finally(() => {
- this.__fetchingImg.exclude();
+ if (osparc.desktop.credits.Utils.areWalletsEnabled()) {
+ this.__userWallets.forEach(wallet => {
+ walletSelectBox.add(new qx.ui.form.ListItem(wallet.getName(), null, wallet));
});
- },
-
- __getPrevRequest: function() {
- const params = {
- url: {
- offset: this.self().ITEMS_PER_PAGE,
- limit: this.self().ITEMS_PER_PAGE
- }
- };
- if (this.__prevRequestParams) {
- params.url.offset = osparc.utils.Utils.getParamFromURL(this.__prevRequestParams, "offset");
- params.url.limit = osparc.utils.Utils.getParamFromURL(this.__prevRequestParams, "limit");
- }
- return this.__getCommonRequest(params);
- },
-
- __getNextRequest: function() {
- const params = {
- url: {
- offset: 0,
- limit: this.self().ITEMS_PER_PAGE
- }
- };
- if (this.__nextRequestParams) {
- params.url.offset = osparc.utils.Utils.getParamFromURL(this.__nextRequestParams, "offset");
- params.url.limit = osparc.utils.Utils.getParamFromURL(this.__nextRequestParams, "limit");
- }
- return this.__getCommonRequest(params);
- },
-
- __getCommonRequest: function(params) {
- const options = {
- resolveWResponse: true
- };
-
- const selectedWallet = this.__selectedWallet;
- if (selectedWallet) {
- const walletId = selectedWallet.getWalletId();
- params.url["walletId"] = walletId.toString();
- return osparc.data.Resources.fetch("resourceUsagePerWallet", "getPage", params, undefined, options);
+ } else {
+ lbl.setVisibility("excluded")
+ walletSelectBox.setVisibility("excluded")
+ this.__exportButton.setVisibility("excluded")
+ this.__table = new osparc.desktop.credits.UsageTable(null, this.__dateFilters.getValue()).set({
+ marginTop: 10
+ })
+ this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", {
+ converter: isFetching => isFetching ? "visible" : "excluded"
+ })
+ container.add(this.__table, { flex: 1 })
}
- // Usage supports the non wallet enabled products
- return osparc.data.Resources.fetch("resourceUsage", "getPage", params, undefined, options);
- },
-
- __setData: function(data) {
- const table = this.getChildControl("usage-table");
- table.addData(data);
},
-
- __evaluatePageButtons:function(resp) {
- this.getChildControl("prev-page-button").setEnabled(Boolean(this.__prevRequestParams));
- this.getChildControl("current-page-label").setValue(((resp["_meta"]["offset"]/this.self().ITEMS_PER_PAGE)+1).toString());
- this.getChildControl("next-page-button").setEnabled(Boolean(this.__nextRequestParams));
+ __handleExport() {
+ const reportUrl = new URL("/v0/services/-/usage-report", window.location.origin)
+ reportUrl.searchParams.append("wallet_id", this.__selectedWallet.getWalletId())
+ reportUrl.searchParams.append("filters", JSON.stringify({ "started_at": this.__dateFilters.getValue() }))
+ window.open(reportUrl, "_blank")
}
}
});
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/UsageTable.js b/services/static-webserver/client/source/class/osparc/desktop/credits/UsageTable.js
index c4c3be15ea9..06798f25d56 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/UsageTable.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/UsageTable.js
@@ -12,117 +12,62 @@
Authors:
* Odei Maiz (odeimaiz)
+ * Ignacio Pascual (ignapas)
************************************************************************ */
qx.Class.define("osparc.desktop.credits.UsageTable", {
- extend: osparc.ui.table.Table,
+ extend: qx.ui.table.Table,
- construct: function() {
- const model = new qx.ui.table.model.Simple();
- const cols = this.self().COLUMNS;
- const colNames = Object.values(cols).map(col => col.title);
- model.setColumns(colNames);
+ construct: function(walletId, filters) {
+ this.base(arguments)
+ const model = new osparc.desktop.credits.UsageTableModel(walletId, filters)
+ this.setTableModel(model)
+ this.setStatusBarVisible(false)
- this.base(arguments, model, {
- tableColumnModel: obj => new qx.ui.table.columnmodel.Resize(obj),
- statusBarVisible: false
- });
- this.makeItLoose();
+ this.setHeaderCellHeight(26);
+ this.setRowHeight(26);
const columnModel = this.getTableColumnModel();
- columnModel.getBehavior().setWidth(cols.duration.pos, 70);
- columnModel.getBehavior().setWidth(cols.status.pos, 70);
- columnModel.getBehavior().setWidth(cols.cost.pos, 60);
- columnModel.setDataCellRenderer(cols.cost.pos, new qx.ui.table.cellrenderer.Number());
+ columnModel.setDataCellRenderer(6, new qx.ui.table.cellrenderer.Number());
if (!osparc.desktop.credits.Utils.areWalletsEnabled()) {
- columnModel.setColumnVisible(cols.cost.pos, false);
- columnModel.setColumnVisible(cols.user.pos, false);
+ columnModel.setColumnVisible(6, false);
+ columnModel.setColumnVisible(7, false);
}
- },
-
- statics: {
- COLUMNS: {
- project: {
- pos: 0,
- title: osparc.product.Utils.getStudyAlias({firstUpperCase: true})
- },
- node: {
- pos: 1,
- title: qx.locale.Manager.tr("Node")
- },
- service: {
- pos: 2,
- title: qx.locale.Manager.tr("Service")
- },
- start: {
- pos: 3,
- title: qx.locale.Manager.tr("Start")
- },
- duration: {
- pos: 4,
- title: qx.locale.Manager.tr("Duration")
- },
- status: {
- pos: 5,
- title: qx.locale.Manager.tr("Status")
- },
- cost: {
- pos: 6,
- title: qx.locale.Manager.tr("Credits")
- },
- user: {
- pos: 7,
- title: qx.locale.Manager.tr("User")
- }
- },
-
- respDataToTableRow: async function(data) {
- const cols = this.COLUMNS;
- const newData = [];
- newData[cols["project"].pos] = data["project_name"] ? data["project_name"] : data["project_id"];
- newData[cols["node"].pos] = data["node_name"] ? data["node_name"] : data["node_id"];
- if (data["service_key"]) {
- const parts = data["service_key"].split("/");
- const serviceName = parts.pop();
- newData[cols["service"].pos] = serviceName + ":" + data["service_version"];
- }
- if (data["started_at"]) {
- const startTime = new Date(data["started_at"]);
- newData[cols["start"].pos] = osparc.utils.Utils.formatDateAndTime(startTime);
- if (data["stopped_at"]) {
- const stopTime = new Date(data["stopped_at"]);
- const durationTime = stopTime - startTime;
- newData[cols["duration"].pos] = osparc.utils.Utils.formatMilliSeconds(durationTime);
- }
- }
- newData[cols["status"].pos] = qx.lang.String.firstUp(data["service_run_status"].toLowerCase());
- newData[cols["cost"].pos] = data["credit_cost"] ? data["credit_cost"].toFixed(2) : "-";
- const user = await osparc.store.Store.getInstance().getUser(data["user_id"]);
- if (user) {
- newData[cols["user"].pos] = user ? user["label"] : data["user_id"];
- }
- return newData;
- },
-
- respDataToTableData: async function(datas) {
- const newDatas = [];
- if (datas) {
- for (const data of datas) {
- const newData = await this.respDataToTableRow(data);
- newDatas.push(newData);
- }
- }
- return newDatas;
+ columnModel.setColumnVisible(2, false)
+
+ // Array [0, 1, ..., N] where N is column_count - 1 (default column order)
+ this.__columnOrder = [...Array(columnModel.getOverallColumnCount()).keys()]
+
+ if (
+ osparc.Preferences.getInstance().getBillingCenterUsageColumnOrder() &&
+ osparc.Preferences.getInstance().getBillingCenterUsageColumnOrder().length === this.__columnOrder.length
+ ) {
+ columnModel.setColumnsOrder(osparc.Preferences.getInstance().getBillingCenterUsageColumnOrder())
+ this.__columnOrder = osparc.Preferences.getInstance().getBillingCenterUsageColumnOrder()
+ } else {
+ osparc.Preferences.getInstance().setBillingCenterUsageColumnOrder(this.__columnOrder)
}
- },
- members: {
- addData: async function(datas) {
- const newDatas = await this.self().respDataToTableData(datas);
- this.setData(newDatas);
- }
+ columnModel.addListener("orderChanged", e => {
+ // Save new order into preferences
+ if (e.getData()) {
+ const { fromOverXPos, toOverXPos } = e.getData()
+ // Edit current order
+ this.__columnOrder = this.__columnOrder.toSpliced(toOverXPos, 0, this.__columnOrder.splice(fromOverXPos, 1)[0])
+ // Save order
+ osparc.Preferences.getInstance().setBillingCenterUsageColumnOrder(this.__columnOrder)
+ }
+ }, this)
+
+ columnModel.setColumnWidth(0, 130)
+ columnModel.setColumnWidth(1, 130)
+ columnModel.setColumnWidth(3, 125)
+ columnModel.setColumnWidth(4, 70)
+ columnModel.setColumnWidth(5, 70)
+ columnModel.setColumnWidth(6, 56)
+ columnModel.setColumnWidth(7, 130)
}
-});
+})
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/UsageTableModel.js b/services/static-webserver/client/source/class/osparc/desktop/credits/UsageTableModel.js
new file mode 100644
index 00000000000..2b8dffa4471
--- /dev/null
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/UsageTableModel.js
@@ -0,0 +1,189 @@
+/*
+ * oSPARC - The SIMCORE frontend - https://osparc.io
+ * Copyright: 2024 IT'IS Foundation - https://itis.swiss
+ * License: MIT - https://opensource.org/licenses/MIT
+ * Authors: Ignacio Pascual (ignapas)
+ */
+const SERVER_MAX_LIMIT = 49
+const COLUMN_ID_TO_DB_COLUMN_MAP = {
+ 0: "project_name",
+ 1: "node_name",
+ 2: "service_key",
+ 3: "started_at",
+ 5: "service_run_status",
+ 6: "credit_cost",
+ 7: "user_email"
+}
+
+qx.Class.define("osparc.desktop.credits.UsageTableModel", {
+ extend: qx.ui.table.model.Remote,
+
+ construct(walletId, filters) {
+ this.base(arguments)
+ this.setColumns([
+ osparc.product.Utils.getStudyAlias({firstUpperCase: true}),
+ qx.locale.Manager.tr("Node"),
+ qx.locale.Manager.tr("Service"),
+ qx.locale.Manager.tr("Start"),
+ qx.locale.Manager.tr("Duration"),
+ qx.locale.Manager.tr("Status"),
+ qx.locale.Manager.tr("Credits"),
+ qx.locale.Manager.tr("User")
+ ], [
+ "project",
+ "node",
+ "service",
+ "start",
+ "duration",
+ "status",
+ "cost",
+ "user"
+ ])
+ this.setWalletId(walletId)
+ if (filters) {
+ this.setFilters(filters)
+ }
+ this.setSortColumnIndexWithoutSortingData(3)
+ this.setSortAscendingWithoutSortingData(false)
+ this.setColumnSortable(4, false)
+ },
+
+ properties: {
+ walletId: {
+ check: "Number",
+ nullable: true
+ },
+ filters: {
+ check: "Object",
+ init: null
+ },
+ isFetching: {
+ check: "Boolean",
+ init: false,
+ event: "changeFetching"
+ },
+ orderBy: {
+ check: "Object",
+ init: {
+ field: "started_at",
+ direction: "desc"
+ }
+ }
+ },
+
+ members: {
+ // overrriden
+ sortByColumn(columnIndex, ascending) {
+ this.setOrderBy({
+ field: COLUMN_ID_TO_DB_COLUMN_MAP[columnIndex],
+ direction: ascending ? "asc" : "desc"
+ })
+ this.base(arguments, columnIndex, ascending)
+ },
+ // overridden
+ _loadRowCount() {
+ const endpoint = this.getWalletId() == null ? "get" : "getWithWallet"
+ osparc.data.Resources.fetch("resourceUsage", endpoint, {
+ url: {
+ walletId: this.getWalletId(),
+ limit: 1,
+ offset: 0,
+ filters: this.getFilters() ?
+ JSON.stringify({
+ "started_at": this.getFilters()
+ }) :
+ null,
+ orderBy: JSON.stringify(this.getOrderBy())
+ }
+ }, undefined, {
+ resolveWResponse: true
+ })
+ .then(resp => {
+ this._onRowCountLoaded(resp["_meta"].total)
+ })
+ .catch(() => {
+ this._onRowCountLoaded(null)
+ })
+ },
+ // overridden
+ _loadRowData(firstRow, qxLastRow) {
+ this.setIsFetching(true)
+ // Please Qloocloox don't ask for more rows than there are
+ const lastRow = Math.min(qxLastRow, this._rowCount - 1)
+ // Returns a request promise with given offset and limit
+ const getFetchPromise = (offset, limit=SERVER_MAX_LIMIT) => {
+ const endpoint = this.getWalletId() == null ? "get" : "getWithWallet"
+ return osparc.data.Resources.fetch("resourceUsage", endpoint, {
+ url: {
+ walletId: this.getWalletId(),
+ limit,
+ offset,
+ filters: this.getFilters() ?
+ JSON.stringify({
+ "started_at": this.getFilters()
+ }) :
+ null,
+ orderBy: JSON.stringify(this.getOrderBy())
+ }
+ })
+ .then(rawData => {
+ const data = []
+ rawData.forEach(rawRow => {
+ let service = ""
+ if (rawRow["service_key"]) {
+ const serviceName = rawRow["service_key"].split("/").pop()
+ service = `${serviceName}:${rawRow["service_version"]}`
+ }
+ let start = ""
+ let duration = ""
+ if (rawRow["started_at"]) {
+ start = osparc.utils.Utils.formatDateAndTime(new Date(rawRow["started_at"]))
+ if (rawRow["stopped_at"]) {
+ duration = osparc.utils.Utils.formatMilliSeconds(new Date(rawRow["stopped_at"]) - new Date(rawRow["started_at"]))
+ }
+ }
+ data.push({
+ project: rawRow["project_name"] || rawRow["project_id"],
+ node: rawRow["node_name"] || rawRow["node_id"],
+ service,
+ start,
+ duration,
+ status: qx.lang.String.firstUp(rawRow["service_run_status"].toLowerCase()),
+ cost: rawRow["credit_cost"] ? rawRow["credit_cost"].toFixed(2) : "",
+ user: rawRow["user_email"]
+ })
+ })
+ return data
+ })
+ }
+ // Divides the model row request into several server requests to comply with the number of rows server limit
+ const reqLimit = lastRow - firstRow + 1 // Number of requested rows
+ const nRequests = Math.ceil(reqLimit / SERVER_MAX_LIMIT)
+ if (nRequests > 1) {
+ let requests = []
+ for (let i=firstRow; i <= lastRow; i += SERVER_MAX_LIMIT) {
+ requests.push(getFetchPromise(i, i > lastRow - SERVER_MAX_LIMIT + 1 ? reqLimit % SERVER_MAX_LIMIT : SERVER_MAX_LIMIT))
+ }
+ Promise.all(requests)
+ .then(responses => {
+ this._onRowDataLoaded(responses.flat())
+ })
+ .catch(err => {
+ console.error(err)
+ this._onRowDataLoaded(null)
+ })
+ .finally(() => this.setIsFetching(false))
+ } else {
+ getFetchPromise(firstRow, reqLimit)
+ .then(data => {
+ this._onRowDataLoaded(data)
+ })
+ .catch(err => {
+ console.error(err)
+ this._onRowDataLoaded(null)
+ })
+ .finally(() => this.setIsFetching(false))
+ }
+ }
+ }
+})
diff --git a/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletListItem.js b/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletListItem.js
index 33de815740c..1c8ad029cb8 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletListItem.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletListItem.js
@@ -154,9 +154,17 @@ qx.Class.define("osparc.desktop.wallets.WalletListItem", {
});
this.__autorechargeBtn.addListener("execute", () => {
const autorecharge = new osparc.desktop.credits.AutoRecharge(this.getKey());
- const win = osparc.ui.window.Window.popUpInWindow(autorecharge, "Autorecharge", 400, 550);
+ const win = osparc.ui.window.Window.popUpInWindow(autorecharge, "Auto-recharge", 400, 550).set({
+ resizable: false,
+ movable: false
+ });
autorecharge.addListener("close", () => win.close());
- // Revert default execute action (toggle the buttons's vale)
+ autorecharge.addListener("addNewPaymentMethod", () => {
+ win.close()
+ const newBillingCenter = osparc.desktop.credits.BillingCenterWindow.openWindow()
+ newBillingCenter.openPaymentMethods()
+ })
+ // Revert default execute action (toggle the buttons's value)
this.__autorechargeBtn.toggleValue();
});
this.bind("autoRecharge", this.__autorechargeBtn, "value", {
diff --git a/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletsList.js b/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletsList.js
index 8e72e5329f6..d73ef79a187 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletsList.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletsList.js
@@ -26,7 +26,7 @@ qx.Class.define("osparc.desktop.wallets.WalletsList", {
this.__addHeader("Personal")
this.__personalWalletsModel = this.__addWalletsList()
- this.__addHeader("Shared with me")
+ this.__sharedHeader = this.__addHeader("Shared with me")
this.__sharedWalletsModel = this.__addWalletsList({ flex: 1 })
this.loadWallets();
@@ -132,6 +132,11 @@ qx.Class.define("osparc.desktop.wallets.WalletsList", {
}
});
this.setWalletsLoaded(true);
+ if (this.__sharedWalletsModel.getLength() === 0) {
+ this.__sharedHeader.exclude()
+ } else {
+ this.__sharedHeader.show()
+ }
},
__openEditWallet: function(walletId) {
@@ -212,6 +217,7 @@ qx.Class.define("osparc.desktop.wallets.WalletsList", {
});
header.add(selectColumn)
this._add(header);
+ return header
}
}
});
diff --git a/services/static-webserver/client/source/class/osparc/form/AppMotionSelect.js b/services/static-webserver/client/source/class/osparc/form/AppMotionSelect.js
index 10da294e3dd..b8c0fc1a605 100644
--- a/services/static-webserver/client/source/class/osparc/form/AppMotionSelect.js
+++ b/services/static-webserver/client/source/class/osparc/form/AppMotionSelect.js
@@ -10,8 +10,7 @@ qx.Class.define("osparc.form.AppMotionSelect", {
construct: function() {
this.base(arguments);
this.set({
- appearance: "appmotion-buy-credits-select",
- decorator: "appmotion-buy-credits-input"
+ appearance: "appmotion-buy-credits-select"
});
}
});
diff --git a/services/static-webserver/client/source/class/osparc/theme/Appearance.js b/services/static-webserver/client/source/class/osparc/theme/Appearance.js
index d722882a98f..bb2b1e84f51 100644
--- a/services/static-webserver/client/source/class/osparc/theme/Appearance.js
+++ b/services/static-webserver/client/source/class/osparc/theme/Appearance.js
@@ -1180,9 +1180,10 @@ qx.Theme.define("osparc.theme.Appearance", {
"appmotion-buy-credits-input": {
include: "textfield",
style: state => ({
- backgroundColor: state.readonly ? "transparent" : "background-main-1",
+ backgroundColor: state.disabled ? "transparent" : "background-main-1",
padding: [10, 15],
- font: "text-18"
+ font: "text-18",
+ decorator: "appmotion-buy-credits-input"
})
},
@@ -1190,9 +1191,10 @@ qx.Theme.define("osparc.theme.Appearance", {
include: "selectbox",
alias: "selectbox",
style: state => ({
- backgroundColor: "background-main-1",
+ backgroundColor: state.disabled ? "transparent" : "background-main-1",
padding: [10, 15],
- font: "text-14"
+ font: "text-14",
+ decorator: "appmotion-buy-credits-input"
})
},
@@ -1202,6 +1204,27 @@ qx.Theme.define("osparc.theme.Appearance", {
style: state => ({
backgroundColor: "background-main-1"
})
+ },
+
+ "appmotion-buy-credits-spinner": {
+ include: "spinner",
+ alias: "spinner"
+ },
+
+ "appmotion-buy-credits-spinner/textfield": {
+ include: "appmotion-buy-credits-input",
+ alias: "appmotion-buy-credits-input",
+ style: state => ({
+ font: "text-14"
+ })
+ },
+
+ "appmotion-buy-credits-checkbox": {
+ include: "checkbox",
+ alias: "checkbox",
+ style: state => ({
+ icon: state.checked ? "@MaterialIcons/check_box/20" : "@MaterialIcons/check_box_outline_blank/20"
+ })
}
}
});
diff --git a/services/static-webserver/client/source/class/osparc/theme/Decoration.js b/services/static-webserver/client/source/class/osparc/theme/Decoration.js
index 800d32a6b2b..3848cb7f2e0 100644
--- a/services/static-webserver/client/source/class/osparc/theme/Decoration.js
+++ b/services/static-webserver/client/source/class/osparc/theme/Decoration.js
@@ -230,6 +230,12 @@ qx.Theme.define("osparc.theme.Decoration", {
}
},
+ "no-border-2": {
+ style: {
+ width: 0
+ }
+ },
+
"border-status": {
decorator: qx.ui.decoration.MSingleBorder,
style: {
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 802373fce74..e3b77eaeec4 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
@@ -3152,8 +3152,8 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Envelope_list_models_library.api_schemas_webserver.resource_usage.ServiceRunGet__'
- /v0/services/-/resource-usages:export:
- post:
+ /v0/services/-/usage-report:
+ get:
tags:
- usage
summary: Redirects to download CSV link. CSV obtains finished and currently
diff --git a/services/web/server/src/simcore_service_webserver/director_v2/_api_utils.py b/services/web/server/src/simcore_service_webserver/director_v2/_api_utils.py
index b52321cc3bd..c72f69e0ec9 100644
--- a/services/web/server/src/simcore_service_webserver/director_v2/_api_utils.py
+++ b/services/web/server/src/simcore_service_webserver/director_v2/_api_utils.py
@@ -56,6 +56,6 @@ async def get_wallet_info(
)
if wallet.available_credits <= ZERO_CREDITS:
raise WalletNotEnoughCreditsError(
- reason=f"Wallet {wallet.wallet_id} credit balance {wallet.available_credits}"
+ reason=f"Wallet '{wallet.name}' has {wallet.available_credits} credits."
)
return WalletInfo(wallet_id=project_wallet_id, wallet_name=wallet.name)
diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py
index 6f84ec83c4f..62a0a6b209b 100644
--- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py
+++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py
@@ -481,7 +481,7 @@ async def _start_dynamic_service(
)
if wallet.available_credits <= ZERO_CREDITS:
raise WalletNotEnoughCreditsError(
- reason=f"Wallet {wallet.wallet_id} credit balance {wallet.available_credits}"
+ reason=f"Wallet '{wallet.name}' has {wallet.available_credits} credits."
)
wallet_info = WalletInfo(
wallet_id=project_wallet_id, wallet_name=wallet.name
diff --git a/services/web/server/src/simcore_service_webserver/resource_usage/_service_runs_handlers.py b/services/web/server/src/simcore_service_webserver/resource_usage/_service_runs_handlers.py
index 1eb490fafad..a605fe1f853 100644
--- a/services/web/server/src/simcore_service_webserver/resource_usage/_service_runs_handlers.py
+++ b/services/web/server/src/simcore_service_webserver/resource_usage/_service_runs_handlers.py
@@ -167,9 +167,7 @@ async def list_resource_usage_services(request: web.Request):
)
-@routes.post(
- f"/{VTAG}/services/-/resource-usages:export", name="export_resource_usage_services"
-)
+@routes.get(f"/{VTAG}/services/-/usage-report", name="export_resource_usage_services")
@login_required
@permission_required("resource-usage.read")
@_handle_resource_usage_exceptions
diff --git a/services/web/server/src/simcore_service_webserver/users/_preferences_models.py b/services/web/server/src/simcore_service_webserver/users/_preferences_models.py
index 70383358f00..23ec71bad18 100644
--- a/services/web/server/src/simcore_service_webserver/users/_preferences_models.py
+++ b/services/web/server/src/simcore_service_webserver/users/_preferences_models.py
@@ -95,6 +95,11 @@ class TelemetryLowDiskSpaceWarningThresholdFrontendUserPreference(
value: int = 5 # in gigabytes
+class BillingCenterUsageColumnOrderFrontendUserPreference(FrontendUserPreference):
+ preference_identifier: PreferenceIdentifier = "billingCenterUsageColumnOrder"
+ value: list[int] | None = None
+
+
ALL_FRONTEND_PREFERENCES: list[type[FrontendUserPreference]] = [
ConfirmationBackToDashboardFrontendUserPreference,
ConfirmationDeleteStudyFrontendUserPreference,
@@ -113,6 +118,7 @@ class TelemetryLowDiskSpaceWarningThresholdFrontendUserPreference(
JobConcurrencyLimitFrontendUserPreference,
AllowMetricsCollectionFrontendUserPreference,
TelemetryLowDiskSpaceWarningThresholdFrontendUserPreference,
+ BillingCenterUsageColumnOrderFrontendUserPreference,
]
_PREFERENCE_NAME_TO_IDENTIFIER_MAPPING: dict[PreferenceName, PreferenceIdentifier] = {
diff --git a/services/web/server/tests/unit/with_dbs/03/resource_usage/test_usage_services__export.py b/services/web/server/tests/unit/with_dbs/03/resource_usage/test_usage_services__export.py
index 1511fae2291..53ff02997e5 100644
--- a/services/web/server/tests/unit/with_dbs/03/resource_usage/test_usage_services__export.py
+++ b/services/web/server/tests/unit/with_dbs/03/resource_usage/test_usage_services__export.py
@@ -72,7 +72,7 @@ async def test_export_service_usage_redirection(
expected: type[web.HTTPException],
):
url = client.app.router["export_resource_usage_services"].url_for()
- resp = await client.post(f"{url}")
+ resp = await client.get(f"{url}")
assert resp.status == expected.status_code
if resp.status == web.HTTPOk:
@@ -102,7 +102,7 @@ async def test_list_service_usage(
order_by=json.dumps(_order_by),
)
)
- resp = await client.post(f"{url}")
+ resp = await client.get(f"{url}")
# checks is a redirection
assert len(resp.history) == 1
diff --git a/services/web/server/tests/unit/with_dbs/03/test_users__preferences_api.py b/services/web/server/tests/unit/with_dbs/03/test_users__preferences_api.py
index 488d6f9cf36..f52cc497e7c 100644
--- a/services/web/server/tests/unit/with_dbs/03/test_users__preferences_api.py
+++ b/services/web/server/tests/unit/with_dbs/03/test_users__preferences_api.py
@@ -1,6 +1,7 @@
# pylint: disable=inconsistent-return-statements
# pylint: disable=redefined-outer-name
# pylint: disable=unused-argument
+# pylint: disable=too-many-return-statements
from collections.abc import AsyncIterator
from typing import Any
@@ -12,6 +13,7 @@
from faker import Faker
from models_library.api_schemas_webserver.users_preferences import Preference
from models_library.products import ProductName
+from models_library.user_preferences import FrontendUserPreference
from models_library.users import UserID
from pydantic import BaseModel
from pydantic.fields import ModelField
@@ -22,11 +24,14 @@
)
from simcore_postgres_database.models.users import UserStatus
from simcore_service_webserver.users._preferences_api import (
- ALL_FRONTEND_PREFERENCES,
_get_frontend_user_preferences,
get_frontend_user_preferences_aggregation,
set_frontend_user_preference,
)
+from simcore_service_webserver.users._preferences_models import (
+ ALL_FRONTEND_PREFERENCES,
+ BillingCenterUsageColumnOrderFrontendUserPreference,
+)
@pytest.fixture
@@ -72,7 +77,9 @@ def _get_default_field_value(model_class: type[BaseModel]) -> Any:
)
-def _get_non_default_value(model_class: type[BaseModel]) -> Any:
+def _get_non_default_value(
+ model_class: type[FrontendUserPreference],
+) -> Any:
"""given a default value transforms into something that is different"""
model_field = _get_model_field(model_class, "value")
@@ -85,9 +92,15 @@ def _get_non_default_value(model_class: type[BaseModel]) -> Any:
return {**value, "non_default_key": "non_default_value"}
if isinstance(value, list):
return [*value, "non_default_value"]
- if isinstance(value, (int, str)):
+ if isinstance(value, int | str):
return value
+
if value is None:
+ if (
+ model_class.get_preference_name()
+ == BillingCenterUsageColumnOrderFrontendUserPreference.get_preference_name()
+ ):
+ return None
if value_type == int:
return 0
if value_type == str: