From a77a25675dc64369c436155ab3c144fb3ebd7562 Mon Sep 17 00:00:00 2001
From: Torsten Dittmann
Date: Mon, 11 Dec 2023 18:13:19 +0100
Subject: [PATCH] feat: project usage
---
package-lock.json | 8 +-
package.json | 2 +-
src/lib/components/progressBarBig.svelte | 2 +-
src/lib/helpers/project.ts | 1 +
src/lib/layout/usage.svelte | 27 +-
src/lib/sdk/billing.ts | 16 +-
.../(billing-modal)/postReleaseModal.svelte | 2 +-
.../usage/[[invoice]]/+page.svelte | 36 ++-
.../usage/[[invoice]]/+page.ts | 15 +-
.../usage/[[invoice]]/ProjectBreakdown.svelte | 9 +-
.../project-[project]/overview/+layout.svelte | 6 +-
.../project-[project]/overview/store.ts | 12 +-
.../settings/usage/[[invoice]]/+page.svelte | 274 ++++++++++++++++++
.../settings/usage/[[invoice]]/+page.ts | 35 +++
.../settings/usage/[[period]]/+page.svelte | 195 -------------
.../settings/usage/[[period]]/+page.ts | 24 --
16 files changed, 402 insertions(+), 262 deletions(-)
create mode 100644 src/routes/console/project-[project]/settings/usage/[[invoice]]/+page.svelte
create mode 100644 src/routes/console/project-[project]/settings/usage/[[invoice]]/+page.ts
delete mode 100644 src/routes/console/project-[project]/settings/usage/[[period]]/+page.svelte
delete mode 100644 src/routes/console/project-[project]/settings/usage/[[period]]/+page.ts
diff --git a/package-lock.json b/package-lock.json
index 9f8794aea9..ed28f0e906 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,7 +6,7 @@
"": {
"name": "@appwrite/console",
"dependencies": {
- "@appwrite.io/console": "^0.3.0",
+ "@appwrite.io/console": "^0.4.1",
"@appwrite.io/pink": "0.2.0",
"@appwrite.io/pink-icons": "0.2.0",
"@popperjs/core": "^2.11.8",
@@ -159,9 +159,9 @@
"integrity": "sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg=="
},
"node_modules/@appwrite.io/console": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/@appwrite.io/console/-/console-0.3.0.tgz",
- "integrity": "sha512-XHKQHirzQliXoDpMLakSdXwpq9aPoeTABNhW8Z7MK3p6oBfp2gMplDD43Fbd91JREXoJ+RG8okX0z0nIc6hDiQ==",
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@appwrite.io/console/-/console-0.4.1.tgz",
+ "integrity": "sha512-GxT6bz1ct0dpvvp6xjVSp/VVUCGxaEAG11vi2KTWjJvRlITwHLYNXt5iycx+pe65gc2FNGTsApcCr1d6sRqLsQ==",
"dependencies": {
"cross-fetch": "3.1.5",
"isomorphic-form-data": "2.0.0"
diff --git a/package.json b/package.json
index cdea2dea34..8f85b2d766 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,7 @@
"e2e": "playwright test tests/e2e"
},
"dependencies": {
- "@appwrite.io/console": "^0.3.0",
+ "@appwrite.io/console": "^0.4.1",
"@appwrite.io/pink": "0.2.0",
"@appwrite.io/pink-icons": "0.2.0",
"@popperjs/core": "^2.11.8",
diff --git a/src/lib/components/progressBarBig.svelte b/src/lib/components/progressBarBig.svelte
index 4bd00e6258..62777b97c1 100644
--- a/src/lib/components/progressBarBig.svelte
+++ b/src/lib/components/progressBarBig.svelte
@@ -7,7 +7,7 @@
export let progressMax: number;
export let showBar = true;
- $: progress = Math.round((progressValue / progressMax) * 100);
+ $: progress = Math.min(Math.max(Math.round((progressValue / progressMax) * 100), 0), 100);
diff --git a/src/lib/helpers/project.ts b/src/lib/helpers/project.ts
index a8a88cd3f7..91cfc90ac8 100644
--- a/src/lib/helpers/project.ts
+++ b/src/lib/helpers/project.ts
@@ -1,5 +1,6 @@
export function getProjectId() {
const pathname = window.location.pathname + '/';
const projectMatch = pathname.match(/\/project-(.*?)\//);
+
return projectMatch?.[1] || null;
}
diff --git a/src/lib/layout/usage.svelte b/src/lib/layout/usage.svelte
index ea571a4c60..05f4407f73 100644
--- a/src/lib/layout/usage.svelte
+++ b/src/lib/layout/usage.svelte
@@ -1,13 +1,38 @@
diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts
index 1d785bf989..c073606cde 100644
--- a/src/lib/sdk/billing.ts
+++ b/src/lib/sdk/billing.ts
@@ -512,15 +512,21 @@ export class Billing {
async listUsage(
organizationId: string,
- startDate: string = null,
- endDate: string = null
+ startDate: string = undefined,
+ endDate: string = undefined
): Promise {
const path = `/organizations/${organizationId}/usage`;
const params = {
- organizationId,
- startDate,
- endDate
+ organizationId
};
+
+ if (startDate !== undefined) {
+ params['startDate'] = startDate;
+ }
+ if (endDate !== undefined) {
+ params['endDate'] = endDate;
+ }
+
const uri = new URL(this.client.config.endpoint + path);
return await this.client.call(
'get',
diff --git a/src/routes/console/(billing-modal)/postReleaseModal.svelte b/src/routes/console/(billing-modal)/postReleaseModal.svelte
index 88f0119d18..82abd8e4fc 100644
--- a/src/routes/console/(billing-modal)/postReleaseModal.svelte
+++ b/src/routes/console/(billing-modal)/postReleaseModal.svelte
@@ -85,7 +85,7 @@
fullWidth
class="u-margin-block-start-24"
on:click={() => wizard.start(ChangeOrganizationTierCloud)}
- on:click={() => show = false}
+ on:click={() => (show = false)}
on:click={() =>
trackEvent('click_open_website', {
source: 'billing_release_modal',
diff --git a/src/routes/console/organization-[organization]/usage/[[invoice]]/+page.svelte b/src/routes/console/organization-[organization]/usage/[[invoice]]/+page.svelte
index 14e799c340..560d90abf2 100644
--- a/src/routes/console/organization-[organization]/usage/[[invoice]]/+page.svelte
+++ b/src/routes/console/organization-[organization]/usage/[[invoice]]/+page.svelte
@@ -9,10 +9,11 @@
import { goto } from '$app/navigation';
import { toLocaleDate } from '$lib/helpers/date.js';
import { bytesToSize, humanFileSize } from '$lib/helpers/sizeConvertion';
- import { abbreviateNumber } from '$lib/helpers/numbers';
import { BarChart } from '$lib/charts';
import ChangeOrganizationTierCloud from '$routes/console/changeOrganizationTierCloud.svelte';
import ProjectBreakdown from './ProjectBreakdown.svelte';
+ import { last } from '$lib/helpers/array';
+ import { formatNum } from '$lib/helpers/string';
export let data;
@@ -93,7 +94,7 @@
- {@const current = data.organizationUsage.bandwidth[0]?.value ?? 0}
+ {@const current = last(data.organizationUsage.bandwidth)?.value ?? 0}
{@const currentHumanized = humanFileSize(current)}
{@const max = getServiceLimit('bandwidth', tier)}
+ value
+ ? `${humanFileSize(+value).value} ${humanFileSize(+value).unit}`
+ : '0'
+ }
+ }
+ }}
series={[
{
name: 'Bandwidth',
- data: [...data.organizationUsage.bandwidth.map((e) => [e.date, e.value])]
+ data: [...data.organizationUsage.bandwidth.map((e) => [e.date, e.value])],
+ tooltip: {
+ valueFormatter: (value) =>
+ `${humanFileSize(+value).value} ${humanFileSize(+value).unit}`
+ }
}
]} />
The total number of users across all projects in your organization.
- {@const current = data.organizationUsage.users[0]?.value ?? 0}
+ {@const current = last(data.organizationUsage.users)?.value ?? 0}
{@const max = getServiceLimit('users', tier)}
{
const { invoice } = params;
const parentData = await parent();
const org = parentData.organization as Organization;
- const tomorrow = getTomorrow(new Date());
- let startDate: string;
- let endDate: string;
- let currentInvoice: Invoice | null = null;
+ let startDate: string = undefined;
+ let endDate: string = undefined;
+ let currentInvoice: Invoice = undefined;
if (invoice) {
currentInvoice = await sdk.forConsole.billing.getInvoice(org.$id, invoice);
startDate = currentInvoice.from;
endDate = currentInvoice.to;
- } else {
- startDate = org.billingCurrentInvoiceDate;
- endDate = tomorrow.toISOString();
}
const [invoices, usage] = await Promise.all([
- sdk.forConsole.billing.listInvoices(org.$id),
+ sdk.forConsole.billing.listInvoices(org.$id, [Query.orderDesc('from')]),
sdk.forConsole.billing.listUsage(params.organization, startDate, endDate)
]);
diff --git a/src/routes/console/organization-[organization]/usage/[[invoice]]/ProjectBreakdown.svelte b/src/routes/console/organization-[organization]/usage/[[invoice]]/ProjectBreakdown.svelte
index d553efc80b..edb226d101 100644
--- a/src/routes/console/organization-[organization]/usage/[[invoice]]/ProjectBreakdown.svelte
+++ b/src/routes/console/organization-[organization]/usage/[[invoice]]/ProjectBreakdown.svelte
@@ -54,8 +54,8 @@
- Project breakdown
-
+
Project breakdown
+
Project
@@ -71,8 +71,9 @@
{format(project.usage)}
View project usage
+ href={getProjectUsageLink(project.projectId)}>
+ View project usage
+
{/each}
diff --git a/src/routes/console/project-[project]/overview/+layout.svelte b/src/routes/console/project-[project]/overview/+layout.svelte
index 1568617ddb..fe98b91657 100644
--- a/src/routes/console/project-[project]/overview/+layout.svelte
+++ b/src/routes/console/project-[project]/overview/+layout.svelte
@@ -25,6 +25,7 @@
import { formatNum } from '$lib/helpers/string';
import { total } from '$lib/helpers/array';
import type { Metric } from '$lib/sdk/usage';
+ import { periodToDates } from '$lib/layout/usage.svelte';
$: projectId = $page.params.project;
$: path = `/console/project-${projectId}/overview`;
@@ -34,7 +35,7 @@
afterNavigate(handle);
async function handle() {
- const promise = usage.load(period);
+ const promise = changePeriod(period);
if ($usage) {
await promise;
@@ -43,7 +44,8 @@
function changePeriod(newPeriod: UsagePeriods) {
period = newPeriod;
- usage.load(period);
+ const dates = periodToDates(newPeriod);
+ return usage.load(dates.start, dates.end, dates.period);
}
$: $registerCommands([
diff --git a/src/routes/console/project-[project]/overview/store.ts b/src/routes/console/project-[project]/overview/store.ts
index 9a1734507e..ea055c522f 100644
--- a/src/routes/console/project-[project]/overview/store.ts
+++ b/src/routes/console/project-[project]/overview/store.ts
@@ -1,19 +1,17 @@
import { sdk } from '$lib/stores/sdk';
import { cachedStore } from '$lib/helpers/cache';
-import type { UsageProject } from '$lib/sdk/usage';
import { writable, type Writable } from 'svelte/store';
+import type { Models } from '@appwrite.io/console';
export const usage = cachedStore<
- UsageProject,
+ Models.UsageProject,
{
- load: (range: string) => Promise;
+ load: (start: string, end: string, period: '1h' | '1d') => Promise;
}
>('projectUsage', function ({ set }) {
return {
- load: async (range) => {
- const usages: UsageProject = (await sdk.forProject.project.getUsage(
- range
- )) as unknown as UsageProject;
+ load: async (start, end, period) => {
+ const usages = await sdk.forProject.project.getUsage(start, end, period);
set(usages);
}
};
diff --git a/src/routes/console/project-[project]/settings/usage/[[invoice]]/+page.svelte b/src/routes/console/project-[project]/settings/usage/[[invoice]]/+page.svelte
new file mode 100644
index 0000000000..e0ee9538b2
--- /dev/null
+++ b/src/routes/console/project-[project]/settings/usage/[[invoice]]/+page.svelte
@@ -0,0 +1,274 @@
+
+
+
+
+ Usage
+
+ {#if $organization?.billingPlan === 'tier-0'}
+
+ {/if}
+
+
+ {#if $organization.billingPlan === 'tier-2'}
+
+ On the Scale plan, you'll be charged only for any usage that exceeds the thresholds
+ per resource listed below.
+
+ {:else}
+
+ If you exceed the limits of the {plan} plan, services for your projects may be disrupted.
+ .
+
+ {/if}
+
+
+
+
+ Bandwidth
+ Calculated for all bandwidth used in your project.
+
+ {#if network}
+ {@const humanized = humanFileSize(total(network))}
+
+ {humanized.value}
+ {humanized.unit}
+
+
+ value
+ ? `${humanFileSize(+value).value} ${
+ humanFileSize(+value).unit
+ }`
+ : '0'
+ }
+ }
+ }}
+ series={[
+ {
+ name: 'Bandwidth',
+ data: [...network.map((e) => [e.date, e.value])],
+ tooltip: {
+ valueFormatter: (value) =>
+ `${humanFileSize(+value).value} ${humanFileSize(+value).unit}`
+ }
+ }
+ ]} />
+ {:else}
+
+
+
+ {/if}
+
+
+
+ Users
+
+ Total user in your project.
+
+
+ {@const current = formatNum(usersTotal)}
+
+
+
+ {current}
+ Users
+
+
+
+ [e.date, e.value])]
+ }
+ ]} />
+
+
+
+ Function executions
+
+
+ Calculated for all functions that are executed in all projects in your project.
+
+
+
+ {@const current = formatNum(executions)}
+
+
+
+ {current}
+ Executions
+
+
+
+ {#if data.usage.executionsBreakdown.length > 0}
+
+
+ Project
+ Usage
+
+
+
+ {#each data.usage.executionsBreakdown as func}
+
+
+ {func.name ?? func.resourceId}
+
+
+ {formatNum(func.value)} executions
+
+
+ View function
+
+
+ {/each}
+
+
+ {/if}
+
+
+
+ Storage
+
+
+ Calculated for all your files, deployments, builds and databases. While in beta, only
+ file storage is counted against your plan limits.
+
+
+
+ {@const humanized = humanFileSize(storage)}
+
+
+
+ {humanized.value}
+ {humanized.unit}
+
+
+
+ {#if data.usage.bucketsBreakdown.length > 0}
+
+
+ Bucket
+ Usage
+
+
+
+ {#each data.usage.bucketsBreakdown.sort((a, b) => b.value - a.value) as bucket}
+ {@const humanized = humanFileSize(bucket.value)}
+
+
+ {bucket.name ?? bucket.resourceId}
+
+
+ {humanized.value}{humanized.unit}
+
+
+ View bucket
+
+
+ {/each}
+
+
+ {/if}
+
+
+
+ Metrics are estimates updated every 24 hours and may not accurately reflect your invoice.
+
+
diff --git a/src/routes/console/project-[project]/settings/usage/[[invoice]]/+page.ts b/src/routes/console/project-[project]/settings/usage/[[invoice]]/+page.ts
new file mode 100644
index 0000000000..c22f296989
--- /dev/null
+++ b/src/routes/console/project-[project]/settings/usage/[[invoice]]/+page.ts
@@ -0,0 +1,35 @@
+import { getSdkForProject, sdk } from '$lib/stores/sdk';
+import { Query } from '@appwrite.io/console';
+import type { PageLoad } from './$types';
+import type { Organization } from '$lib/stores/organization';
+import type { Invoice } from '$lib/sdk/billing';
+
+export const load: PageLoad = async ({ params, parent }) => {
+ const { invoice, project } = params;
+ const parentData = await parent();
+ const org = parentData.organization as Organization;
+
+ let startDate: string = org.billingCurrentInvoiceDate;
+ let endDate: string = org.billingNextInvoiceDate;
+ let currentInvoice: Invoice = undefined;
+
+ if (invoice) {
+ currentInvoice = await sdk.forConsole.billing.getInvoice(org.$id, invoice);
+ startDate = currentInvoice.from;
+ endDate = currentInvoice.to;
+ }
+
+ const [invoices, usage] = await Promise.all([
+ sdk.forConsole.billing.listInvoices(org.$id, [Query.orderDesc('from')]),
+ /**
+ * Workaround because project id might not be populated yet.
+ */
+ getSdkForProject(project).project.getUsage(startDate, endDate)
+ ]);
+
+ return {
+ usage,
+ invoices,
+ currentInvoice
+ };
+};
diff --git a/src/routes/console/project-[project]/settings/usage/[[period]]/+page.svelte b/src/routes/console/project-[project]/settings/usage/[[period]]/+page.svelte
deleted file mode 100644
index 8379ad3c70..0000000000
--- a/src/routes/console/project-[project]/settings/usage/[[period]]/+page.svelte
+++ /dev/null
@@ -1,195 +0,0 @@
-
-
-
-
- Usage
-
-
- 24h
-
-
- 30d
-
-
- 90d
-
-
-
- {#if isCloud}
-
- Project resources contribute to your organization's monthly free usage limits on the {tierToPlan(
- $organization.billingPlan
- ).name} plan.
-
-
- {/if}
-
- Bandwidth
- Calculated for all bandwidth used across your project.
-
- {#if data?.requestsTotal && total(data.requestsTotal)}
-
- {total(data.requestsTotal)}
- GB
-
- [e.date, e.value])]
- }
- ]} />
- {:else}
-
-
-
- {/if}
-
-
-
- Users
- The total number of users of your project.
-
- {#if total(users)}
-
- {total(users)}
- Users
-
- [e.date, e.value])]
- }
- ]} />
- {:else}
-
-
-
- {/if}
-
-
-
- Function executions
- Calculated for all functions executed in your project.
-
- {#if total(executions)}
-
- {total(executions)}
- Executions
-
- [e.date, e.value])]
- }
- ]} />
- {:else}
-
-
-
- {/if}
-
-
- {#if data.filesTotal}
-
- Storage
- Calculated for all storage operations in your project.
-
- {@const size = humanFileSize(total(data.filesTotal))}
- {#if size}
-
- {size.value}
- {size.unit}
-
-
- {:else}
-
-
-
- {/if}
-
-
- {/if}
-
- Metrics are estimates updated every 24 hours and may not accurately reflect your invoice.
-
-
diff --git a/src/routes/console/project-[project]/settings/usage/[[period]]/+page.ts b/src/routes/console/project-[project]/settings/usage/[[period]]/+page.ts
deleted file mode 100644
index dee7ea1517..0000000000
--- a/src/routes/console/project-[project]/settings/usage/[[period]]/+page.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { Models } from '@appwrite.io/console';
-import { sdk } from '$lib/stores/sdk';
-import type { PageLoad } from './$types';
-import { error } from '@sveltejs/kit';
-
-export const load: PageLoad = async ({ params }) => {
- try {
- const response = await sdk.forProject.project.getUsage(params.period ?? '30d');
- console.log(response);
- return {
- range: response.range,
- bucketsTotal: response.bucketsTotal as unknown as Models.Metric[],
- filesTotal: response.filesStorage as unknown as Models.Metric[],
- databasesTotal: response.databasesTotal as unknown as Models.Metric[],
- documentsTotal: response.documentsTotal as unknown as Models.Metric[],
- executionsTotal: response.executionsTotal as unknown as Models.Metric[],
- networkTotal: response.network as unknown as Models.Metric[],
- requestsTotal: response.requestsTotal as unknown as Models.Metric[],
- usersTotal: response.usersTotal as unknown as Models.Metric[]
- };
- } catch (e) {
- throw error(e.code, e.message);
- }
-};