Skip to content

Commit 1d555ed

Browse files
committed
Checkouts
1 parent 3a15b39 commit 1d555ed

File tree

7 files changed

+414
-24
lines changed

7 files changed

+414
-24
lines changed

services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", {
3535
}
3636

3737
if (osparc.product.Utils.showS4LStore()) {
38-
this.__addRentalsPage();
38+
this.__addPurchasesPage();
39+
this.__addCheckoutsPage();
3940
}
4041
},
4142

@@ -99,11 +100,19 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", {
99100
return page;
100101
},
101102

102-
__addRentalsPage: function() {
103-
const title = this.tr("Rentals");
103+
__addPurchasesPage: function() {
104+
const title = this.tr("Purchases");
104105
const iconSrc = "@FontAwesome5Solid/list/22";
105-
const rentals = new osparc.desktop.credits.Rentals();
106-
const page = this.addTab(title, iconSrc, rentals);
106+
const purchases = new osparc.desktop.credits.Purchases();
107+
const page = this.addTab(title, iconSrc, purchases);
108+
return page;
109+
},
110+
111+
__addCheckoutsPage: function() {
112+
const title = this.tr("Checkouts");
113+
const iconSrc = "@FontAwesome5Solid/list/22";
114+
const purchases = new osparc.desktop.credits.Checkouts();
115+
const page = this.addTab(title, iconSrc, purchases);
107116
return page;
108117
},
109118

services/static-webserver/client/source/class/osparc/desktop/credits/Rentals.js renamed to services/static-webserver/client/source/class/osparc/desktop/credits/Checkouts.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
************************************************************************ */
1717

1818

19-
qx.Class.define("osparc.desktop.credits.Rentals", {
19+
qx.Class.define("osparc.desktop.credits.Checkouts", {
2020
extend: qx.ui.core.Widget,
2121

2222
construct: function() {
@@ -72,7 +72,7 @@ qx.Class.define("osparc.desktop.credits.Rentals", {
7272
this.__table.getTableModel().reloadData()
7373
} else {
7474
// qx: changeSelection is triggered after the first item is added to SelectBox
75-
this.__table = new osparc.desktop.credits.RentalsTable(this.__selectedWallet.getWalletId(), this.__dateFilters.getValue()).set({
75+
this.__table = new osparc.desktop.credits.CheckoutsTable(this.__selectedWallet.getWalletId(), this.__dateFilters.getValue()).set({
7676
marginTop: 10
7777
})
7878
this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", {
@@ -91,7 +91,7 @@ qx.Class.define("osparc.desktop.credits.Rentals", {
9191
} else {
9292
lbl.setVisibility("excluded")
9393
walletSelectBox.setVisibility("excluded")
94-
this.__table = new osparc.desktop.credits.RentalsTable(null, this.__dateFilters.getValue()).set({
94+
this.__table = new osparc.desktop.credits.CheckoutsTable(null, this.__dateFilters.getValue()).set({
9595
marginTop: 10
9696
})
9797
this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", {

services/static-webserver/client/source/class/osparc/desktop/credits/RentalsTable.js renamed to services/static-webserver/client/source/class/osparc/desktop/credits/CheckoutsTable.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
************************************************************************ */
1717

1818

19-
qx.Class.define("osparc.desktop.credits.RentalsTable", {
19+
qx.Class.define("osparc.desktop.credits.CheckoutsTable", {
2020
extend: qx.ui.table.Table,
2121

2222
construct: function(walletId, filters) {
2323
this.base(arguments);
2424

25-
const model = new osparc.desktop.credits.RentalsTableModel(walletId, filters);
25+
const model = new osparc.desktop.credits.CheckoutsTableModel(walletId, filters);
2626
this.setTableModel(model);
2727

2828
this.set({
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/* ************************************************************************
2+
3+
osparc - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2025 IT'IS Foundation, https://itis.swiss
9+
10+
License:
11+
MIT: https://opensource.org/licenses/MIT
12+
13+
Authors:
14+
* Odei Maiz (odeimaiz)
15+
16+
************************************************************************ */
17+
18+
19+
qx.Class.define("osparc.desktop.credits.CheckoutsTableModel", {
20+
extend: qx.ui.table.model.Remote,
21+
22+
construct(walletId, filters) {
23+
this.base(arguments);
24+
25+
const checkoutsCols = osparc.desktop.credits.CheckoutsTable.COLS;
26+
const colLabels = Object.values(checkoutsCols).map(col => col.label);
27+
const colIDs = Object.values(checkoutsCols).map(col => col.id);
28+
29+
this.setColumns(colLabels, colIDs);
30+
this.setWalletId(walletId)
31+
if (filters) {
32+
this.setFilters(filters)
33+
}
34+
this.setSortColumnIndexWithoutSortingData(checkoutsCols.START.column);
35+
this.setSortAscendingWithoutSortingData(false);
36+
},
37+
38+
properties: {
39+
walletId: {
40+
check: "Number",
41+
nullable: true
42+
},
43+
filters: {
44+
check: "Object",
45+
init: null
46+
},
47+
isFetching: {
48+
check: "Boolean",
49+
init: false,
50+
event: "changeFetching"
51+
},
52+
orderBy: {
53+
check: "Object",
54+
init: {
55+
field: "purchased_at",
56+
direction: "desc"
57+
}
58+
}
59+
},
60+
61+
statics: {
62+
SERVER_MAX_LIMIT: 49,
63+
COLUMN_ID_TO_DB_COLUMN_MAP: {
64+
0: "purchased_at",
65+
},
66+
},
67+
68+
members: {
69+
// overridden
70+
sortByColumn(columnIndex, ascending) {
71+
this.setOrderBy({
72+
field: this.self().COLUMN_ID_TO_DB_COLUMN_MAP[columnIndex],
73+
direction: ascending ? "asc" : "desc"
74+
})
75+
this.base(arguments, columnIndex, ascending)
76+
},
77+
78+
// overridden
79+
_loadRowCount() {
80+
const walletId = this.getWalletId();
81+
const urlParams = {
82+
offset: 0,
83+
limit: 1,
84+
filters: this.getFilters() ?
85+
JSON.stringify({
86+
"started_at": this.getFilters()
87+
}) :
88+
null,
89+
orderBy: JSON.stringify(this.getOrderBy()),
90+
};
91+
const options = {
92+
resolveWResponse: true
93+
};
94+
osparc.store.LicensedItems.getInstance().getPurchasedLicensedItems(walletId, urlParams, options)
95+
.then(resp => {
96+
this._onRowCountLoaded(resp["_meta"].total)
97+
})
98+
.catch(() => {
99+
this._onRowCountLoaded(null)
100+
});
101+
},
102+
103+
// overridden
104+
_loadRowData(firstRow, qxLastRow) {
105+
this.setIsFetching(true);
106+
107+
const lastRow = Math.min(qxLastRow, this._rowCount - 1);
108+
// Returns a request promise with given offset and limit
109+
const getFetchPromise = (offset, limit=this.self().SERVER_MAX_LIMIT) => {
110+
const walletId = this.getWalletId();
111+
const urlParams = {
112+
limit,
113+
offset,
114+
filters: this.getFilters() ?
115+
JSON.stringify({
116+
"started_at": this.getFilters()
117+
}) :
118+
null,
119+
orderBy: JSON.stringify(this.getOrderBy())
120+
};
121+
const licensedItemsStore = osparc.store.LicensedItems.getInstance();
122+
return Promise.all([
123+
licensedItemsStore.getLicensedItems(),
124+
licensedItemsStore.getPurchasedLicensedItems(walletId, urlParams),
125+
licensedItemsStore.getVipModels(),
126+
])
127+
.then(values => {
128+
const licensedItems = values[0];
129+
const checkoutsItems = values[1];
130+
const vipModels = values[2];
131+
132+
const data = [];
133+
const checkoutsCols = osparc.desktop.credits.CheckoutsTable.COLS;
134+
checkoutsItems.forEach(checkoutsItem => {
135+
const licensedItemId = checkoutsItem["licensedItemId"];
136+
const licensedItem = licensedItems.find(licItem => licItem["licensedItemId"] === licensedItemId);
137+
const vipModel = vipModels.find(vipMdl => vipMdl["modelId"] == licensedItem["name"]);
138+
data.push({
139+
[checkoutsCols.PURCHASE_ID.id]: checkoutsItem["licensedItemPurchaseId"],
140+
[checkoutsCols.ITEM_ID.id]: licensedItemId,
141+
[checkoutsCols.ITEM_LABEL.id]: vipModel ? vipModel["name"] : "unknown model",
142+
[checkoutsCols.START.id]: osparc.utils.Utils.formatDateAndTime(new Date(checkoutsItem["startAt"])),
143+
[checkoutsCols.END.id]: osparc.utils.Utils.formatDateAndTime(new Date(checkoutsItem["expireAt"])),
144+
[checkoutsCols.SEATS.id]: checkoutsItem["numOfSeats"],
145+
[checkoutsCols.COST.id]: checkoutsItem["pricingUnitCost"] ? ("-" + parseFloat(checkoutsItem["pricingUnitCost"]).toFixed(2)) : "", // show it negative
146+
[checkoutsCols.USER.id]: checkoutsItem["purchasedByUser"],
147+
});
148+
});
149+
return data;
150+
});
151+
};
152+
153+
// Divides the model row request into several server requests to comply with the number of rows server limit
154+
const reqLimit = lastRow - firstRow + 1; // Number of requested rows
155+
const nRequests = Math.ceil(reqLimit / this.self().SERVER_MAX_LIMIT);
156+
if (nRequests > 1) {
157+
const requests = [];
158+
for (let i=firstRow; i <= lastRow; i += this.self().SERVER_MAX_LIMIT) {
159+
requests.push(getFetchPromise(i, i > lastRow - this.self().SERVER_MAX_LIMIT + 1 ? reqLimit % this.self().SERVER_MAX_LIMIT : this.self().SERVER_MAX_LIMIT))
160+
}
161+
Promise.all(requests)
162+
.then(responses => {
163+
this._onRowDataLoaded(responses.flat());
164+
})
165+
.catch(err => {
166+
console.error(err);
167+
this._onRowDataLoaded(null);
168+
})
169+
.finally(() => this.setIsFetching(false));
170+
} else {
171+
getFetchPromise(firstRow, reqLimit)
172+
.then(data => {
173+
this._onRowDataLoaded(data);
174+
})
175+
.catch(err => {
176+
console.error(err)
177+
this._onRowDataLoaded(null);
178+
})
179+
.finally(() => this.setIsFetching(false));
180+
}
181+
}
182+
}
183+
})
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/* ************************************************************************
2+
3+
osparc - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2025 IT'IS Foundation, https://itis.swiss
9+
10+
License:
11+
MIT: https://opensource.org/licenses/MIT
12+
13+
Authors:
14+
* Odei Maiz (odeimaiz)
15+
16+
************************************************************************ */
17+
18+
19+
qx.Class.define("osparc.desktop.credits.Purchases", {
20+
extend: qx.ui.core.Widget,
21+
22+
construct: function() {
23+
this.base(arguments);
24+
25+
this._setLayout(new qx.ui.layout.VBox(5));
26+
27+
this.__buildLayout()
28+
},
29+
30+
members: {
31+
__buildLayout: function() {
32+
const lbl = new qx.ui.basic.Label(this.tr("Select a Credit Account:"));
33+
this._add(lbl);
34+
35+
const selectBoxContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5));
36+
const walletSelectBox = new qx.ui.form.SelectBox().set({
37+
allowStretchX: false,
38+
width: 200
39+
});
40+
selectBoxContainer.add(walletSelectBox);
41+
this.__fetchingImg = new qx.ui.basic.Image().set({
42+
source: "@FontAwesome5Solid/circle-notch/12",
43+
alignX: "center",
44+
alignY: "middle",
45+
visibility: "excluded"
46+
});
47+
this.__fetchingImg.getContentElement().addClass("rotate");
48+
selectBoxContainer.add(this.__fetchingImg);
49+
this._add(selectBoxContainer);
50+
51+
const filterContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5))
52+
this.__dateFilters = new osparc.desktop.credits.DateFilters();
53+
this.__dateFilters.addListener("change", e => {
54+
this.__table.getTableModel().setFilters(e.getData())
55+
this.__table.getTableModel().reloadData()
56+
});
57+
filterContainer.add(this.__dateFilters);
58+
const refreshButton = new qx.ui.form.Button(this.tr("Reload"), "@FontAwesome5Solid/sync-alt/14").set({
59+
allowStretchY: false,
60+
alignY: "bottom"
61+
});
62+
refreshButton.addListener("execute", () => this.__table && this.__table.getTableModel().reloadData());
63+
filterContainer.add(refreshButton)
64+
this._add(filterContainer);
65+
66+
walletSelectBox.addListener("changeSelection", e => {
67+
const selection = e.getData();
68+
if (selection.length) {
69+
this.__selectedWallet = selection[0].getModel()
70+
if (this.__table) {
71+
this.__table.getTableModel().setWalletId(this.__selectedWallet.getWalletId())
72+
this.__table.getTableModel().reloadData()
73+
} else {
74+
// qx: changeSelection is triggered after the first item is added to SelectBox
75+
this.__table = new osparc.desktop.credits.PurchasesTable(this.__selectedWallet.getWalletId(), this.__dateFilters.getValue()).set({
76+
marginTop: 10
77+
})
78+
this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", {
79+
converter: isFetching => isFetching ? "visible" : "excluded"
80+
})
81+
this._add(this.__table, { flex: 1 })
82+
}
83+
}
84+
});
85+
86+
if (osparc.desktop.credits.Utils.areWalletsEnabled()) {
87+
const store = osparc.store.Store.getInstance();
88+
store.getWallets().forEach(wallet => {
89+
walletSelectBox.add(new qx.ui.form.ListItem(wallet.getName(), null, wallet));
90+
});
91+
} else {
92+
lbl.setVisibility("excluded")
93+
walletSelectBox.setVisibility("excluded")
94+
this.__table = new osparc.desktop.credits.PurchasesTable(null, this.__dateFilters.getValue()).set({
95+
marginTop: 10
96+
})
97+
this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", {
98+
converter: isFetching => isFetching ? "visible" : "excluded"
99+
})
100+
this._add(this.__table, { flex: 1 })
101+
}
102+
},
103+
}
104+
});

0 commit comments

Comments
 (0)