From b4ffb500ba5c58932d9fe508186f8fbced3f3b71 Mon Sep 17 00:00:00 2001 From: HugoCasa Date: Fri, 29 Mar 2024 10:56:44 +0100 Subject: [PATCH] feat: add workspace free-tier usage (#3489) * feat: separate workspace and user usage * fix: sqlx build --- ...6dceb195df0fb43a8ac03bc8b35939fc941dd.json | 6 - ...e9a8da57b5204bb6b1207aed47143c17a20bc.json | 22 +++ ...076c565f3add98b6768b5476bd3afed11ea31.json | 22 +++ ...5b692815f1594bf302dbebd5f80d5d22b98e.json} | 7 +- ...38a3209b811324dba4ac661938e0523210682.json | 28 ---- ...5093133c5cbc96414000ac364583114d0ce77.json | 22 +++ ...e312f43b3e5a48f7599133831dbd65886f5d8.json | 22 +++ ...eaf00d4c38ee7ba75141b40fc9fcd2ffc0b8.json} | 4 +- ...0ba1be406ee5167f7b8aa90213ef52c97441f.json | 22 +++ ...20cfeee64fd47ded72fce55cc75e0bbb291a8.json | 15 ++ ...c1cb55aadaf1e6609a1aad09b0515c93a667a.json | 22 --- ...eb41dcef121ed161db04b2568ac2d990aed7c.json | 15 ++ ...63e11422eed9a60e0472c68971f4e8f8735d9.json | 16 -- ...a9d8d1a502796842f185374d2d0f69043086b.json | 22 +++ ...73ad728f7f786c7f447df6cac3e65d5a53bf1.json | 6 - .../20240328145425_remove_overquota.down.sql | 1 + .../20240328145425_remove_overquota.up.sql | 2 + backend/windmill-api/openapi.yaml | 16 ++ backend/windmill-api/src/workspaces.rs | 24 ++- backend/windmill-queue/src/jobs.rs | 145 +++++++++++++----- .../lib/components/sidebar/UserMenu.svelte | 13 +- frontend/src/lib/stores.ts | 1 + .../src/routes/(root)/(logged)/+layout.svelte | 4 + 23 files changed, 332 insertions(+), 125 deletions(-) create mode 100644 backend/.sqlx/query-12a0fd7d8d99fb73b01bc24774fe9a8da57b5204bb6b1207aed47143c17a20bc.json create mode 100644 backend/.sqlx/query-2e9b3e718440f3c5269e9217a13076c565f3add98b6768b5476bd3afed11ea31.json rename backend/.sqlx/{query-bd086c56ad3a58163619122641b93b941ef75880eeeab0756d40f68f87760d5b.json => query-621e9a2a53187dac3ebed62f0d645b692815f1594bf302dbebd5f80d5d22b98e.json} (55%) delete mode 100644 backend/.sqlx/query-6e816fdba0d281d5147ddc5d4d438a3209b811324dba4ac661938e0523210682.json create mode 100644 backend/.sqlx/query-7d93eb90163516718c85f28f8f05093133c5cbc96414000ac364583114d0ce77.json create mode 100644 backend/.sqlx/query-82b16e771b6e21c4587b5ebf059e312f43b3e5a48f7599133831dbd65886f5d8.json rename backend/.sqlx/{query-6d89ac43c4ae3a17167a44c802df72bc506803b79fe93ab3b203fb0690b1bef1.json => query-8f0031533f1bf407bd5d8af4d364eaf00d4c38ee7ba75141b40fc9fcd2ffc0b8.json} (66%) create mode 100644 backend/.sqlx/query-a405e637f5f3b3203de6d65dfcb0ba1be406ee5167f7b8aa90213ef52c97441f.json create mode 100644 backend/.sqlx/query-ca3ba808e020c8c7a35eaef842b20cfeee64fd47ded72fce55cc75e0bbb291a8.json delete mode 100644 backend/.sqlx/query-d918e1d3a1ccc36ca8b4b25aeeec1cb55aadaf1e6609a1aad09b0515c93a667a.json create mode 100644 backend/.sqlx/query-e38240e6d50bfe60e1c2b649588eb41dcef121ed161db04b2568ac2d990aed7c.json delete mode 100644 backend/.sqlx/query-e7418515b88d14d1fd79078342963e11422eed9a60e0472c68971f4e8f8735d9.json create mode 100644 backend/.sqlx/query-e7a1c2b5d79e72f557181782419a9d8d1a502796842f185374d2d0f69043086b.json create mode 100644 backend/migrations/20240328145425_remove_overquota.down.sql create mode 100644 backend/migrations/20240328145425_remove_overquota.up.sql diff --git a/backend/.sqlx/query-1289e7278d2a289bfaa53f00e0b6dceb195df0fb43a8ac03bc8b35939fc941dd.json b/backend/.sqlx/query-1289e7278d2a289bfaa53f00e0b6dceb195df0fb43a8ac03bc8b35939fc941dd.json index d5bfd9ba75..fc01ba95e3 100644 --- a/backend/.sqlx/query-1289e7278d2a289bfaa53f00e0b6dceb195df0fb43a8ac03bc8b35939fc941dd.json +++ b/backend/.sqlx/query-1289e7278d2a289bfaa53f00e0b6dceb195df0fb43a8ac03bc8b35939fc941dd.json @@ -27,11 +27,6 @@ "ordinal": 4, "name": "premium", "type_info": "Bool" - }, - { - "ordinal": 5, - "name": "is_overquota", - "type_info": "Bool" } ], "parameters": { @@ -45,7 +40,6 @@ false, false, false, - false, false ] }, diff --git a/backend/.sqlx/query-12a0fd7d8d99fb73b01bc24774fe9a8da57b5204bb6b1207aed47143c17a20bc.json b/backend/.sqlx/query-12a0fd7d8d99fb73b01bc24774fe9a8da57b5204bb6b1207aed47143c17a20bc.json new file mode 100644 index 0000000000..e60e67da63 --- /dev/null +++ b/backend/.sqlx/query-12a0fd7d8d99fb73b01bc24774fe9a8da57b5204bb6b1207aed47143c17a20bc.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(id) FROM queue WHERE running = true AND workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "12a0fd7d8d99fb73b01bc24774fe9a8da57b5204bb6b1207aed47143c17a20bc" +} diff --git a/backend/.sqlx/query-2e9b3e718440f3c5269e9217a13076c565f3add98b6768b5476bd3afed11ea31.json b/backend/.sqlx/query-2e9b3e718440f3c5269e9217a13076c565f3add98b6768b5476bd3afed11ea31.json new file mode 100644 index 0000000000..70c5674cdf --- /dev/null +++ b/backend/.sqlx/query-2e9b3e718440f3c5269e9217a13076c565f3add98b6768b5476bd3afed11ea31.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "usage", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Varchar" + ] + }, + "nullable": [ + false + ] + }, + "hash": "2e9b3e718440f3c5269e9217a13076c565f3add98b6768b5476bd3afed11ea31" +} diff --git a/backend/.sqlx/query-bd086c56ad3a58163619122641b93b941ef75880eeeab0756d40f68f87760d5b.json b/backend/.sqlx/query-621e9a2a53187dac3ebed62f0d645b692815f1594bf302dbebd5f80d5d22b98e.json similarity index 55% rename from backend/.sqlx/query-bd086c56ad3a58163619122641b93b941ef75880eeeab0756d40f68f87760d5b.json rename to backend/.sqlx/query-621e9a2a53187dac3ebed62f0d645b692815f1594bf302dbebd5f80d5d22b98e.json index 9214257461..6a66bb6f22 100644 --- a/backend/.sqlx/query-bd086c56ad3a58163619122641b93b941ef75880eeeab0756d40f68f87760d5b.json +++ b/backend/.sqlx/query-621e9a2a53187dac3ebed62f0d645b692815f1594bf302dbebd5f80d5d22b98e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, $2, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 0) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage", + "query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage", "describe": { "columns": [ { @@ -11,13 +11,12 @@ ], "parameters": { "Left": [ - "Varchar", - "Bool" + "Varchar" ] }, "nullable": [ false ] }, - "hash": "bd086c56ad3a58163619122641b93b941ef75880eeeab0756d40f68f87760d5b" + "hash": "621e9a2a53187dac3ebed62f0d645b692815f1594bf302dbebd5f80d5d22b98e" } diff --git a/backend/.sqlx/query-6e816fdba0d281d5147ddc5d4d438a3209b811324dba4ac661938e0523210682.json b/backend/.sqlx/query-6e816fdba0d281d5147ddc5d4d438a3209b811324dba4ac661938e0523210682.json deleted file mode 100644 index 779d8e3c93..0000000000 --- a/backend/.sqlx/query-6e816fdba0d281d5147ddc5d4d438a3209b811324dba4ac661938e0523210682.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT premium, is_overquota FROM workspace WHERE id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "premium", - "type_info": "Bool" - }, - { - "ordinal": 1, - "name": "is_overquota", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "6e816fdba0d281d5147ddc5d4d438a3209b811324dba4ac661938e0523210682" -} diff --git a/backend/.sqlx/query-7d93eb90163516718c85f28f8f05093133c5cbc96414000ac364583114d0ce77.json b/backend/.sqlx/query-7d93eb90163516718c85f28f8f05093133c5cbc96414000ac364583114d0ce77.json new file mode 100644 index 0000000000..f032408662 --- /dev/null +++ b/backend/.sqlx/query-7d93eb90163516718c85f28f8f05093133c5cbc96414000ac364583114d0ce77.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT usage.usage + 1 FROM usage \n WHERE is_workspace IS FALSE AND\n month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "7d93eb90163516718c85f28f8f05093133c5cbc96414000ac364583114d0ce77" +} diff --git a/backend/.sqlx/query-82b16e771b6e21c4587b5ebf059e312f43b3e5a48f7599133831dbd65886f5d8.json b/backend/.sqlx/query-82b16e771b6e21c4587b5ebf059e312f43b3e5a48f7599133831dbd65886f5d8.json new file mode 100644 index 0000000000..880e79a957 --- /dev/null +++ b/backend/.sqlx/query-82b16e771b6e21c4587b5ebf059e312f43b3e5a48f7599133831dbd65886f5d8.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT usage.usage FROM usage \n WHERE is_workspace = true \n AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "usage", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "82b16e771b6e21c4587b5ebf059e312f43b3e5a48f7599133831dbd65886f5d8" +} diff --git a/backend/.sqlx/query-6d89ac43c4ae3a17167a44c802df72bc506803b79fe93ab3b203fb0690b1bef1.json b/backend/.sqlx/query-8f0031533f1bf407bd5d8af4d364eaf00d4c38ee7ba75141b40fc9fcd2ffc0b8.json similarity index 66% rename from backend/.sqlx/query-6d89ac43c4ae3a17167a44c802df72bc506803b79fe93ab3b203fb0690b1bef1.json rename to backend/.sqlx/query-8f0031533f1bf407bd5d8af4d364eaf00d4c38ee7ba75141b40fc9fcd2ffc0b8.json index 39e3fffec8..86f46e27e5 100644 --- a/backend/.sqlx/query-6d89ac43c4ae3a17167a44c802df72bc506803b79fe93ab3b203fb0690b1bef1.json +++ b/backend/.sqlx/query-8f0031533f1bf407bd5d8af4d364eaf00d4c38ee7ba75141b40fc9fcd2ffc0b8.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO workspace SELECT $1, $2, owner, deleted, premium, is_overquota FROM workspace WHERE id = $3", + "query": "INSERT INTO workspace SELECT $1, $2, owner, deleted, premium FROM workspace WHERE id = $3", "describe": { "columns": [], "parameters": { @@ -12,5 +12,5 @@ }, "nullable": [] }, - "hash": "6d89ac43c4ae3a17167a44c802df72bc506803b79fe93ab3b203fb0690b1bef1" + "hash": "8f0031533f1bf407bd5d8af4d364eaf00d4c38ee7ba75141b40fc9fcd2ffc0b8" } diff --git a/backend/.sqlx/query-a405e637f5f3b3203de6d65dfcb0ba1be406ee5167f7b8aa90213ef52c97441f.json b/backend/.sqlx/query-a405e637f5f3b3203de6d65dfcb0ba1be406ee5167f7b8aa90213ef52c97441f.json new file mode 100644 index 0000000000..21dacfcc9b --- /dev/null +++ b/backend/.sqlx/query-a405e637f5f3b3203de6d65dfcb0ba1be406ee5167f7b8aa90213ef52c97441f.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT usage.usage + 1 FROM usage \n WHERE is_workspace IS TRUE AND\n month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "a405e637f5f3b3203de6d65dfcb0ba1be406ee5167f7b8aa90213ef52c97441f" +} diff --git a/backend/.sqlx/query-ca3ba808e020c8c7a35eaef842b20cfeee64fd47ded72fce55cc75e0bbb291a8.json b/backend/.sqlx/query-ca3ba808e020c8c7a35eaef842b20cfeee64fd47ded72fce55cc75e0bbb291a8.json new file mode 100644 index 0000000000..6d44fb2840 --- /dev/null +++ b/backend/.sqlx/query-ca3ba808e020c8c7a35eaef842b20cfeee64fd47ded72fce55cc75e0bbb291a8.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "ca3ba808e020c8c7a35eaef842b20cfeee64fd47ded72fce55cc75e0bbb291a8" +} diff --git a/backend/.sqlx/query-d918e1d3a1ccc36ca8b4b25aeeec1cb55aadaf1e6609a1aad09b0515c93a667a.json b/backend/.sqlx/query-d918e1d3a1ccc36ca8b4b25aeeec1cb55aadaf1e6609a1aad09b0515c93a667a.json deleted file mode 100644 index b577ed2dbd..0000000000 --- a/backend/.sqlx/query-d918e1d3a1ccc36ca8b4b25aeeec1cb55aadaf1e6609a1aad09b0515c93a667a.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT usage.usage + 1 FROM usage \n WHERE is_workspace = false AND\n month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "?column?", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - null - ] - }, - "hash": "d918e1d3a1ccc36ca8b4b25aeeec1cb55aadaf1e6609a1aad09b0515c93a667a" -} diff --git a/backend/.sqlx/query-e38240e6d50bfe60e1c2b649588eb41dcef121ed161db04b2568ac2d990aed7c.json b/backend/.sqlx/query-e38240e6d50bfe60e1c2b649588eb41dcef121ed161db04b2568ac2d990aed7c.json new file mode 100644 index 0000000000..1ff35d7227 --- /dev/null +++ b/backend/.sqlx/query-e38240e6d50bfe60e1c2b649588eb41dcef121ed161db04b2568ac2d990aed7c.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "e38240e6d50bfe60e1c2b649588eb41dcef121ed161db04b2568ac2d990aed7c" +} diff --git a/backend/.sqlx/query-e7418515b88d14d1fd79078342963e11422eed9a60e0472c68971f4e8f8735d9.json b/backend/.sqlx/query-e7418515b88d14d1fd79078342963e11422eed9a60e0472c68971f4e8f8735d9.json deleted file mode 100644 index 43ae3b8d07..0000000000 --- a/backend/.sqlx/query-e7418515b88d14d1fd79078342963e11422eed9a60e0472c68971f4e8f8735d9.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, $2, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 0) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Bool", - "Int4" - ] - }, - "nullable": [] - }, - "hash": "e7418515b88d14d1fd79078342963e11422eed9a60e0472c68971f4e8f8735d9" -} diff --git a/backend/.sqlx/query-e7a1c2b5d79e72f557181782419a9d8d1a502796842f185374d2d0f69043086b.json b/backend/.sqlx/query-e7a1c2b5d79e72f557181782419a9d8d1a502796842f185374d2d0f69043086b.json new file mode 100644 index 0000000000..829d4f6768 --- /dev/null +++ b/backend/.sqlx/query-e7a1c2b5d79e72f557181782419a9d8d1a502796842f185374d2d0f69043086b.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(id) FROM queue WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "e7a1c2b5d79e72f557181782419a9d8d1a502796842f185374d2d0f69043086b" +} diff --git a/backend/.sqlx/query-f7f2598ac824e9b5719b96b1d1873ad728f7f786c7f447df6cac3e65d5a53bf1.json b/backend/.sqlx/query-f7f2598ac824e9b5719b96b1d1873ad728f7f786c7f447df6cac3e65d5a53bf1.json index 05fd1b0da6..33bc8f1c2d 100644 --- a/backend/.sqlx/query-f7f2598ac824e9b5719b96b1d1873ad728f7f786c7f447df6cac3e65d5a53bf1.json +++ b/backend/.sqlx/query-f7f2598ac824e9b5719b96b1d1873ad728f7f786c7f447df6cac3e65d5a53bf1.json @@ -27,11 +27,6 @@ "ordinal": 4, "name": "premium", "type_info": "Bool" - }, - { - "ordinal": 5, - "name": "is_overquota", - "type_info": "Bool" } ], "parameters": { @@ -44,7 +39,6 @@ false, false, false, - false, false ] }, diff --git a/backend/migrations/20240328145425_remove_overquota.down.sql b/backend/migrations/20240328145425_remove_overquota.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20240328145425_remove_overquota.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20240328145425_remove_overquota.up.sql b/backend/migrations/20240328145425_remove_overquota.up.sql new file mode 100644 index 0000000000..87c82ba19f --- /dev/null +++ b/backend/migrations/20240328145425_remove_overquota.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +alter table workspace drop column is_overquota; \ No newline at end of file diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 9d91c4c190..020584e5df 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1955,6 +1955,22 @@ paths: schema: $ref: "#/components/schemas/LargeFileStorage" + /w/{workspace}/workspaces/usage: + get: + summary: get usage + operationId: getWorkspaceUsage + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + responses: + "200": + description: usage + content: + text/plain: + schema: + type: number + /w/{workspace}/users/list: get: summary: list users diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index e6e47c51d0..12a43cfc30 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -112,7 +112,8 @@ pub fn workspaced_service() -> Router { .route("/leave", post(leave_workspace)) .route("/get_workspace_name", get(get_workspace_name)) .route("/change_workspace_name", post(change_workspace_name)) - .route("/change_workspace_id", post(change_workspace_id)); + .route("/change_workspace_id", post(change_workspace_id)) + .route("/usage", get(get_usage)); #[cfg(feature = "stripe")] { @@ -146,7 +147,6 @@ struct Workspace { owner: String, deleted: bool, premium: bool, - is_overquota: bool, } #[derive(FromRow, Serialize, Debug)] @@ -2801,7 +2801,7 @@ async fn change_workspace_id( // duplicate workspace with new id name sqlx::query!( - "INSERT INTO workspace SELECT $1, $2, owner, deleted, premium, is_overquota FROM workspace WHERE id = $3", + "INSERT INTO workspace SELECT $1, $2, owner, deleted, premium FROM workspace WHERE id = $3", &rw.new_id, &rw.new_name, &old_id @@ -3082,3 +3082,21 @@ async fn change_workspace_id( &old_id, &rw.new_id )) } + +async fn get_usage( + Extension(db): Extension, + Path(w_id): Path, +) -> Result { + let usage = sqlx::query_scalar!( + " + SELECT usage.usage FROM usage + WHERE is_workspace = true + AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date) + AND id = $1", + w_id + ) + .fetch_optional(&db) + .await? + .unwrap_or(0); + Ok(usage.to_string()) +} \ No newline at end of file diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 4c897f86a6..886d163158 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -701,14 +701,25 @@ pub async fn add_completed_job< .map_err(|e| Error::InternalErr(format!("fetching if {w_id} is premium: {e}")))?; let _ = sqlx::query!( "INSERT INTO usage (id, is_workspace, month_, usage) - VALUES ($1, $2, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 0) - ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $3", - if premium_workspace { w_id } else { &queued_job.email }, - premium_workspace, + VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) + ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2", + w_id, additional_usage as i32) .execute(db) .await .map_err(|e| Error::InternalErr(format!("updating usage: {e}"))); + + if !premium_workspace { + let _ = sqlx::query!( + "INSERT INTO usage (id, is_workspace, month_, usage) + VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) + ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2", + queued_job.email, + additional_usage as i32) + .execute(db) + .await + .map_err(|e| Error::InternalErr(format!("updating usage: {e}"))); + } } if !skip_downstream_error_handlers @@ -2579,8 +2590,8 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection ) -> Result<(Uuid, QueueTransaction<'c, R>), Error> { #[cfg(feature = "enterprise")] if *CLOUD_HOSTED { - let row = sqlx::query!( - "SELECT premium, is_overquota FROM workspace WHERE id = $1", + let premium_workspace = sqlx::query_scalar!( + "SELECT premium FROM workspace WHERE id = $1", workspace_id ) .fetch_one(_db) @@ -2590,43 +2601,43 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection "fetching if {workspace_id} is premium and overquota: {e}" )) })?; - let premium_workspace = row.premium; - let is_overquota = premium_workspace && row.is_overquota; // we track only non flow steps - let usage = if !matches!( + let (workspace_usage, user_usage) = if !matches!( job_payload, JobPayload::Flow { .. } | JobPayload::RawFlow { .. } ) { - sqlx::query_scalar!( - "INSERT INTO usage (id, is_workspace, month_, usage) - VALUES ($1, $2, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 0) + let workspace_usage = sqlx::query_scalar!( + "INSERT INTO usage (id, is_workspace, month_, usage) + VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1) ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 RETURNING usage.usage", - if premium_workspace { workspace_id } else { email }, - premium_workspace + workspace_id ) .fetch_one(_db) .await - .map_err(|e| Error::InternalErr(format!("updating usage: {e}")))? - } else if !premium_workspace { - sqlx::query_scalar!( - " - SELECT usage.usage + 1 FROM usage - WHERE is_workspace = false AND - month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date) - AND id = $1", - email - ) - .fetch_optional(_db) - .await? - .flatten() - .unwrap_or(0) + .map_err(|e| Error::InternalErr(format!("updating usage: {e}")))?; + + let user_usage = if !premium_workspace { + Some(sqlx::query_scalar!( + "INSERT INTO usage (id, is_workspace, month_, usage) + VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1) + ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 + RETURNING usage.usage", + email + ) + .fetch_one(_db) + .await + .map_err(|e| Error::InternalErr(format!("updating usage: {e}")))?) + } else { + None + }; + (Some(workspace_usage), user_usage) } else { - 0 + (None, None) }; - if is_overquota || !premium_workspace { + if !premium_workspace { let is_super_admin = sqlx::query_scalar!("SELECT super_admin FROM password WHERE email = $1", email) .fetch_optional(_db) @@ -2634,12 +2645,39 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection .unwrap_or(false); if !is_super_admin { - if is_overquota { - return Err(error::Error::BadRequest(format!( - "Workspace {workspace_id} is overquota. Please update your subscription in the customer portal to continue using Windmill." - ))); - } - if usage > MAX_FREE_EXECS + let user_usage = if let Some(user_usage) = user_usage { + user_usage + } else { + sqlx::query_scalar!( + "SELECT usage.usage + 1 FROM usage + WHERE is_workspace IS FALSE AND + month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date) + AND id = $1", + email + ) + .fetch_optional(_db) + .await? + .flatten() + .unwrap_or(1) + }; + + let workspace_usage = if let Some(workspace_usage) = workspace_usage { + workspace_usage + } else { + sqlx::query_scalar!( + "SELECT usage.usage + 1 FROM usage + WHERE is_workspace IS TRUE AND + month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date) + AND id = $1", + workspace_id + ) + .fetch_optional(_db) + .await? + .flatten() + .unwrap_or(1) + }; + + if user_usage > MAX_FREE_EXECS && !matches!(job_payload, JobPayload::Dependencies { .. }) && !matches!(job_payload, JobPayload::FlowDependencies { .. }) && !matches!(job_payload, JobPayload::AppDependencies { .. }) @@ -2648,6 +2686,15 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection "User {email} has exceeded the free usage limit of {MAX_FREE_EXECS} that applies outside of premium workspaces." ))); } + if workspace_usage > MAX_FREE_EXECS + && !matches!(job_payload, JobPayload::Dependencies { .. }) + && !matches!(job_payload, JobPayload::FlowDependencies { .. }) + && !matches!(job_payload, JobPayload::AppDependencies { .. }) + { + return Err(error::Error::BadRequest(format!( + "Workspace {workspace_id} has exceeded the free usage limit of {MAX_FREE_EXECS} that applies outside of premium workspaces." + ))); + } let in_queue = sqlx::query_scalar!("SELECT COUNT(id) FROM queue WHERE email = $1", email) .fetch_one(_db) @@ -2660,6 +2707,18 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection ))); } + let in_queue_workspace = + sqlx::query_scalar!("SELECT COUNT(id) FROM queue WHERE workspace_id = $1", workspace_id) + .fetch_one(_db) + .await? + .unwrap_or(0); + + if in_queue_workspace > MAX_FREE_EXECS.into() { + return Err(error::Error::BadRequest(format!( + "Workspace {workspace_id} has exceeded the jobs in queue limit of {MAX_FREE_EXECS} that applies outside of premium workspaces." + ))); + } + let concurrent_runs = sqlx::query_scalar!( "SELECT COUNT(id) FROM queue WHERE running = true AND email = $1", email @@ -2673,6 +2732,20 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection "User {email} has exceeded the concurrent runs limit of {MAX_FREE_CONCURRENT_RUNS} that applies outside of premium workspaces." ))); } + + let concurrent_runs_workspace = sqlx::query_scalar!( + "SELECT COUNT(id) FROM queue WHERE running = true AND workspace_id = $1", + workspace_id + ) + .fetch_one(_db) + .await? + .unwrap_or(0); + + if concurrent_runs_workspace > MAX_FREE_CONCURRENT_RUNS.into() { + return Err(error::Error::BadRequest(format!( + "Workspace {workspace_id} has exceeded the concurrent runs limit of {MAX_FREE_CONCURRENT_RUNS} that applies outside of premium workspaces." + ))); + } } } } diff --git a/frontend/src/lib/components/sidebar/UserMenu.svelte b/frontend/src/lib/components/sidebar/UserMenu.svelte index 50824dbf86..8c6d98b014 100644 --- a/frontend/src/lib/components/sidebar/UserMenu.svelte +++ b/frontend/src/lib/components/sidebar/UserMenu.svelte @@ -1,7 +1,7 @@