From 51475ceef6ed35d0208c42f181e00513a25ebf1f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 23 Oct 2024 16:31:12 +1300 Subject: [PATCH 1/5] Fix payment method setup route type --- src/lib/sdk/billing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts index f46a3f634..bd635b78a 100644 --- a/src/lib/sdk/billing.ts +++ b/src/lib/sdk/billing.ts @@ -951,7 +951,7 @@ export class Billing { }; const uri = new URL(this.client.config.endpoint + path); return await this.client.call( - 'post', + 'patch', uri, { 'content-type': 'application/json' From e6418e7d834623549ec41901f4c5aa13f31175a6 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Wed, 23 Oct 2024 16:22:56 -0700 Subject: [PATCH 2/5] fix: ensure we fetch all projects for usage breakdown Before this, we tried to fetch all projects in a single query. However, this fails if there are more than 100 projects because the maximum number of values that can be included in a query is 100. Furthermore, the default limit for projects returned by `projects.list()` is 25. This PR ensures all projects are fetched by chunking the project IDs and passing the chunk amount as a limit in the query. While it is possible to use `projects.get()` instead of `projects.list()`, the excessive number of network requests ends up being slower and more overwhelming than using `projects.list()`. --- .../usage/[[invoice]]/+page.ts | 35 ++++++++++++------- .../usage/[[invoice]]/ProjectBreakdown.svelte | 6 +--- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.ts b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.ts index 243e5b52c..f469f97a8 100644 --- a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.ts +++ b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.ts @@ -1,5 +1,5 @@ import { sdk } from '$lib/stores/sdk'; -import { Query } from '@appwrite.io/console'; +import { Query, type Models } from '@appwrite.io/console'; import type { PageLoad } from './$types'; import type { Organization } from '$lib/stores/organization'; import type { Invoice } from '$lib/sdk/billing'; @@ -48,22 +48,33 @@ export const load: PageLoad = async ({ params, parent }) => { sdk.forConsole.teams.listMemberships(params.organization) ]); - const queries: string[] = []; - + const projectNames: { [key: string]: Models.Project } = {}; if (usage?.projects?.length > 0) { - queries.push( - Query.equal( - '$id', - usage.projects.map((p) => p.projectId) - ) - ); - } + // in batches of 100 (the max number of values in a query) + const requests = []; + const chunk = 100; + for (let i = 0; i < usage.projects.length; i += chunk) { + const queries = [ + Query.limit(chunk), + Query.equal( + '$id', + usage.projects.slice(i, i + chunk).map((p) => p.projectId) + ) + ]; + requests.push(sdk.forConsole.projects.list(queries)); + } - const projectNames = await sdk.forConsole.projects.list(queries); + const responses = await Promise.all(requests); + for (const response of responses) { + for (const project of response.projects) { + projectNames[project.$id] = project; + } + } + } return { organizationUsage: usage, - projectNames: projectNames.projects, + projectNames, invoices, currentInvoice, organizationMembers diff --git a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/ProjectBreakdown.svelte b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/ProjectBreakdown.svelte index c7fc534de..964f50eee 100644 --- a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/ProjectBreakdown.svelte +++ b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/ProjectBreakdown.svelte @@ -21,10 +21,6 @@ export let projects: OrganizationUsage['projects']; export let metric: Metric; - function getProjectName(projectId: string): string { - return data.projectNames.find((project) => project.$id === projectId)?.name; - } - function getProjectUsageLink(projectId: string): string { return `${base}/project-${projectId}/settings/usage`; } @@ -69,7 +65,7 @@ {#each groupByProject(metric).sort((a, b) => b.usage - a.usage) as project} - {getProjectName(project.projectId)} + {data.projectNames[project.projectId]?.name ?? 'Unknown'} {format(project.usage)} {#if $canSeeProjects} From 5aa9fad088eb1b79cd0e101edbe5ca13ddc5480b Mon Sep 17 00:00:00 2001 From: ernstmul Date: Fri, 25 Oct 2024 12:24:19 +0200 Subject: [PATCH 3/5] Show credit in billing estimates --- .../billing/planSummary.svelte | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index f551afd40..f2c000acd 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -18,6 +18,7 @@ let currentInvoice: Invoice; let extraMembers = 0; let currentPlan; + let availableCredit = 0; const today = new Date(); onMount(async () => { @@ -30,6 +31,11 @@ extraMembers = members.total > 1 ? members.total - 1 : 0; currentPlan = await sdk.forConsole.billing.getPlan($organization?.$id); + + const creditList = await sdk.forConsole.billing.listCredits($organization.$id, [ + Query.offset(0) + ]); + availableCredit = creditList.available; }); $: extraUsage = (currentInvoice?.amount ?? 0) - (currentPlan?.price ?? 0); @@ -139,6 +145,31 @@ {/if} + {#if $organization?.billingPlan !== BillingPlan.FREE && availableCredit > 0} + + + + + Credits to be applied + +
+ -{formatCurrency(Math.min(availableCredit, currentInvoice?.amount))} +
+
+ {/if} + Current total (USD) @@ -153,7 +184,13 @@
{$organization?.billingPlan === BillingPlan.FREE ? formatCurrency(0) - : formatCurrency(currentInvoice?.amount ?? 0)} + : formatCurrency( + Math.max( + (currentInvoice?.amount ?? 0) - + Math.min(availableCredit, currentInvoice?.amount), + 0 + ) + )}
From 11ab071f17c1f6ee9f0c30a4ff2f13ac3b839d54 Mon Sep 17 00:00:00 2001 From: ernstmul Date: Fri, 25 Oct 2024 14:26:55 +0200 Subject: [PATCH 4/5] Fix NaN's --- .../organization-[organization]/billing/planSummary.svelte | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index f2c000acd..b6da73ee8 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -165,7 +165,9 @@
- -{formatCurrency(Math.min(availableCredit, currentInvoice?.amount))} + -{formatCurrency( + Math.min(availableCredit, currentInvoice?.amount ?? 0) + )}
{/if} @@ -187,7 +189,7 @@ : formatCurrency( Math.max( (currentInvoice?.amount ?? 0) - - Math.min(availableCredit, currentInvoice?.amount), + Math.min(availableCredit, currentInvoice?.amount ?? 0), 0 ) )} From 8c0bebf2814323239cc185c8345019a70e4cae5a Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Fri, 25 Oct 2024 22:41:19 -0700 Subject: [PATCH 5/5] fix: update resource for appwrite to appwrite migration The "environment variable" resource was renamed to "environment-variable" in the backend so the front end needed to be updated as well. --- src/lib/stores/migration.ts | 6 +++--- .../(console)/(migration-wizard)/resource-form.svelte | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/stores/migration.ts b/src/lib/stores/migration.ts index eaf142841..dcb4a6088 100644 --- a/src/lib/stores/migration.ts +++ b/src/lib/stores/migration.ts @@ -42,7 +42,7 @@ export const ResourcesFriendly = { file: { singular: 'File', plural: 'Files' }, bucket: { singular: 'Bucket', plural: 'Buckets' }, function: { singular: 'Function', plural: 'Functions' }, - 'environment variable': { singular: 'Environment Variable', plural: 'Environment Variables' }, + 'environment-variable': { singular: 'Environment Variable', plural: 'Environment Variables' }, deployment: { singular: 'Deployment', plural: 'Deployments' }, database: { singular: 'Database', plural: 'Databases' }, collection: { singular: 'Collection', plural: 'Collections' }, @@ -102,7 +102,7 @@ export const migrationFormToResources = ( addResource('function'); } if (formData.functions.env) { - addResource('environment variable'); + addResource('environment-variable'); } if (formData.functions.inactive) { addResource('deployment'); @@ -156,7 +156,7 @@ export const resourcesToMigrationForm = ( if (resources.includes('function') && isVersionAtLeast(version, '1.4.0')) { formData.functions.root = true; } - if (resources.includes('environment variable') && isVersionAtLeast(version, '1.4.0')) { + if (resources.includes('environment-variable') && isVersionAtLeast(version, '1.4.0')) { formData.functions.env = true; } if (resources.includes('deployment') && isVersionAtLeast(version, '1.4.0')) { diff --git a/src/routes/(console)/(migration-wizard)/resource-form.svelte b/src/routes/(console)/(migration-wizard)/resource-form.svelte index cc7e34ca8..5ddae52fa 100644 --- a/src/routes/(console)/(migration-wizard)/resource-form.svelte +++ b/src/routes/(console)/(migration-wizard)/resource-form.svelte @@ -344,7 +344,7 @@
Import all functions and their active deployment
    - {#if resources?.includes('environment variable')} + {#if resources?.includes('environment-variable')}