Merge pull request #1693 from appwrite/add-image-transformation-stats

feat: added image transformation usage to org & buckets
This commit is contained in:
Darshan
2025-03-05 11:52:16 +05:30
committed by GitHub
13 changed files with 253 additions and 107 deletions
+1 -1
View File
@@ -19,7 +19,7 @@
"e2e:ui": "playwright test tests/e2e --ui"
},
"dependencies": {
"@appwrite.io/console": "1.5.1",
"@appwrite.io/console": "1.5.2",
"@appwrite.io/pink": "0.25.0",
"@appwrite.io/pink-icons": "0.25.0",
"@popperjs/core": "^2.11.8",
+5 -5
View File
@@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@appwrite.io/console':
specifier: 1.5.1
version: 1.5.1
specifier: 1.5.2
version: 1.5.2
'@appwrite.io/pink':
specifier: 0.25.0
version: 0.25.0
@@ -199,8 +199,8 @@ packages:
'@analytics/type-utils@0.6.2':
resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==}
'@appwrite.io/console@1.5.1':
resolution: {integrity: sha512-H0fkBprsxXjOhbrE+MqXt1e4Gx4QeRdHuvMs7UxqGr2fAVEqh4ez2yk40A0ZSQvvN+rqLduoItMHQFl+sCPbtQ==}
'@appwrite.io/console@1.5.2':
resolution: {integrity: sha512-9nNZzxoQcpm5C5pKlGJLWxsEJ4sr5mdt01Hg6M8QpfMtsRe0gjaPa7WGrGBepRnZVKPDNpozh3Hua7nqgx4aEA==}
'@appwrite.io/pink-icons@0.25.0':
resolution: {integrity: sha512-0O3i2oEuh5mWvjO80i+X6rbzrWLJ1m5wmv2/M3a1p2PyBJsFxN8xQMTEmTn3Wl/D26SsM7SpzbdW6gmfgoVU9Q==}
@@ -3837,7 +3837,7 @@ snapshots:
'@analytics/type-utils@0.6.2': {}
'@appwrite.io/console@1.5.1': {}
'@appwrite.io/console@1.5.2': {}
'@appwrite.io/pink-icons@0.25.0': {}
+11 -7
View File
@@ -6,7 +6,7 @@
export let maxValue: string | undefined = undefined;
export let maxUnit: string | undefined = undefined;
export let progressValue: number;
export let progressMax: number;
export let progressMax: number | undefined = undefined;
export let showBar = true;
export let progressBarData: Array<ProgressbarData> = [];
@@ -14,20 +14,24 @@
</script>
<section class="progress-bar">
{#if currentValue !== undefined && currentUnit !== undefined && progress !== undefined && maxValue !== undefined}
{#if currentValue !== undefined && currentUnit !== undefined && progress !== undefined}
<div class="u-flex u-flex-vertical">
<div class="u-flex u-main-space-between">
<p>
<span class="heading-level-4">{currentValue}</span>
<span class="body-text-1 u-bold">{currentUnit}</span>
</p>
<p class="heading-level-4">{progress}%</p>
{#if progressMax !== undefined}
<p class="heading-level-4">{progress}%</p>
{/if}
</div>
<p class="body-text-2">
{maxValue}
{maxUnit ? maxUnit : ''}
</p>
{#if maxValue !== undefined}
<p class="body-text-2">
{maxValue}
{maxUnit ? maxUnit : ''}
</p>
{/if}
</div>
{/if}
{#if showBar && progressBarData.length > 0}
+21 -18
View File
@@ -68,12 +68,12 @@
</script>
<script lang="ts">
import { Container } from '$lib/layout';
import { BarChart } from '$lib/charts';
import { formatNumberWithCommas } from '$lib/helpers/numbers';
import { Card, SecondaryTabs, SecondaryTabsItem, Heading } from '$lib/components';
import { ProjectUsageRange, type Models } from '@appwrite.io/console';
import { page } from '$app/stores';
import { BarChart } from '$lib/charts';
import { Card, Heading, SecondaryTabs, SecondaryTabsItem } from '$lib/components';
import { formatNumberWithCommas } from '$lib/helpers/numbers';
import { Container } from '$lib/layout';
import { ProjectUsageRange, type Models } from '@appwrite.io/console';
type MetricMetadata = {
title: string;
@@ -85,24 +85,27 @@
export let count: Models.Metric[];
export let countMetadata: MetricMetadata;
export let path: string = null;
export let hideSelectPeriod: boolean = false;
</script>
<Container>
<div class="u-flex u-main-space-between common-section">
<Heading tag="h2" size="5">{title}</Heading>
<SecondaryTabs>
<SecondaryTabsItem href={`${path}/24h`} disabled={$page.params.period === '24h'}>
24h
</SecondaryTabsItem>
<SecondaryTabsItem
href={`${path}/30d`}
disabled={!$page.params.period || $page.params.period === '30d'}>
30d
</SecondaryTabsItem>
<SecondaryTabsItem href={`${path}/90d`} disabled={$page.params.period === '90d'}>
90d
</SecondaryTabsItem>
</SecondaryTabs>
{#if !hideSelectPeriod}
<SecondaryTabs>
<SecondaryTabsItem href={`${path}/24h`} disabled={$page.params.period === '24h'}>
24h
</SecondaryTabsItem>
<SecondaryTabsItem
href={`${path}/30d`}
disabled={!$page.params.period || $page.params.period === '30d'}>
30d
</SecondaryTabsItem>
<SecondaryTabsItem href={`${path}/90d`} disabled={$page.params.period === '90d'}>
90d
</SecondaryTabsItem>
</SecondaryTabs>
{/if}
</div>
<Card>
{#if count}
+7 -3
View File
@@ -1,8 +1,8 @@
import type { Client, Models } from '@appwrite.io/console';
import type { OrganizationError, Organization, OrganizationList } from '../stores/organization';
import type { PaymentMethod } from '@stripe/stripe-js';
import type { Tier } from '$lib/stores/billing';
import type { Campaign } from '$lib/stores/campaigns';
import type { Client, Models } from '@appwrite.io/console';
import type { PaymentMethod } from '@stripe/stripe-js';
import type { Organization, OrganizationError, OrganizationList } from '../stores/organization';
export type PaymentMethodData = {
$id: string;
@@ -213,11 +213,13 @@ export type OrganizationUsage = {
executions: Array<Models.Metric>;
databasesReads: Array<Models.Metric>;
databasesWrites: Array<Models.Metric>;
imageTransformations: Array<Models.Metric>;
executionsTotal: number;
filesStorageTotal: number;
buildsStorageTotal: number;
databasesReadsTotal: number;
databasesWritesTotal: number;
imageTransformationsTotal: number;
deploymentsStorageTotal: number;
executionsMBSecondsTotal: number;
buildsMBSecondsTotal: number;
@@ -235,6 +237,7 @@ export type OrganizationUsage = {
users: number;
authPhoneTotal: number;
authPhoneEstimate: number;
imageTransformations: number;
}>;
authPhoneTotal: number;
authPhoneEstimate: number;
@@ -313,6 +316,7 @@ export type Plan = {
order: number;
bandwidth: number;
storage: number;
imageTransformations: number;
webhooks: number;
users: number;
teams: number;
+19 -1
View File
@@ -185,6 +185,14 @@ export type UsageBuckets = {
* Aggregated statistics of bucket storage files per period.
*/
storage: Metric[];
/**
* Aggregated statistics of bucket image transformations per period.
*/
imageTransformations: Metric[];
/**
* Total aggregated number of bucket image transformations.
*/
imageTransformationsTotal: number;
};
/**
* UsageFunctions
@@ -311,7 +319,17 @@ export type UsageProject = {
authPhoneEstimate: number;
/**
* Aggregated statistics of total number SMS by country
* Aggregated statistics of total number SMS by country.
*/
authPhoneCountriesBreakdown: Models.MetricBreakdown[];
/**
* Array of image transformations per period.
*/
imageTransformations: Metric[];
/**
* Aggregated statistics of total number of image transformations.
*/
imageTransformationsTotal: number;
};
+30 -29
View File
@@ -1,37 +1,37 @@
import { page } from '$app/stores';
import { derived, get, writable } from 'svelte/store';
import { sdk } from './sdk';
import { organization, type Organization, type OrganizationError } from './organization';
import type {
InvoiceList,
AddressesList,
Invoice,
PaymentList,
PlansMap,
PaymentMethodData,
Plan,
Aggregation
} from '$lib/sdk/billing';
import { isCloud } from '$lib/system';
import { cachedStore } from '$lib/helpers/cache';
import { Query } from '@appwrite.io/console';
import { headerAlert } from './headerAlert';
import PaymentAuthRequired from '$lib/components/billing/alerts/paymentAuthRequired.svelte';
import { addNotification, notifications } from './notifications';
import { browser } from '$app/environment';
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { activeHeaderAlert, orgMissingPaymentMethod } from '$routes/(console)/store';
import MarkedForDeletion from '$lib/components/billing/alerts/markedForDeletion.svelte';
import { BillingPlan, NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants';
import PaymentMandate from '$lib/components/billing/alerts/paymentMandate.svelte';
import MissingPaymentMethod from '$lib/components/billing/alerts/missingPaymentMethod.svelte';
import LimitReached from '$lib/components/billing/alerts/limitReached.svelte';
import { page } from '$app/stores';
import { trackEvent } from '$lib/actions/analytics';
import LimitReached from '$lib/components/billing/alerts/limitReached.svelte';
import MarkedForDeletion from '$lib/components/billing/alerts/markedForDeletion.svelte';
import MissingPaymentMethod from '$lib/components/billing/alerts/missingPaymentMethod.svelte';
import newDevUpgradePro from '$lib/components/billing/alerts/newDevUpgradePro.svelte';
import PaymentAuthRequired from '$lib/components/billing/alerts/paymentAuthRequired.svelte';
import PaymentMandate from '$lib/components/billing/alerts/paymentMandate.svelte';
import { BillingPlan, NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants';
import { cachedStore } from '$lib/helpers/cache';
import { sizeToBytes, type Size } from '$lib/helpers/sizeConvertion';
import { user } from './user';
import { browser } from '$app/environment';
import type {
AddressesList,
Aggregation,
Invoice,
InvoiceList,
PaymentList,
PaymentMethodData,
Plan,
PlansMap
} from '$lib/sdk/billing';
import { isCloud } from '$lib/system';
import { activeHeaderAlert, orgMissingPaymentMethod } from '$routes/(console)/store';
import { Query } from '@appwrite.io/console';
import { derived, get, writable } from 'svelte/store';
import { headerAlert } from './headerAlert';
import { addNotification, notifications } from './notifications';
import { organization, type Organization, type OrganizationError } from './organization';
import { canSeeBilling } from './roles';
import { sdk } from './sdk';
import { user } from './user';
export type Tier = 'tier-0' | 'tier-1' | 'tier-2' | 'auto-1' | 'cont-1' | 'ent-1';
@@ -130,7 +130,8 @@ export type PlanServices =
| 'users'
| 'usersAddon'
| 'webhooks'
| 'authPhone';
| 'authPhone'
| 'imageTransformations';
export function getServiceLimit(serviceId: PlanServices, tier: Tier = null, plan?: Plan): number {
if (!isCloud) return 0;
@@ -1,6 +1,16 @@
<script lang="ts">
import { Container } from '$lib/layout';
import { trackEvent } from '$lib/actions/analytics';
import { tooltip } from '$lib/actions/tooltip';
import { BarChart, Legend } from '$lib/charts';
import { Card, CardGrid, Heading, ProgressBarBig } from '$lib/components';
import { BillingPlan } from '$lib/constants';
import { Button } from '$lib/elements/forms';
import { formatCurrency, formatNumberWithCommas } from '$lib/helpers/numbers';
import { bytesToSize, humanFileSize, mbSecondsToGBHours } from '$lib/helpers/sizeConvertion';
import { formatNum } from '$lib/helpers/string';
import { Container } from '$lib/layout';
import { accumulateFromEndingTotal, total } from '$lib/layout/usage.svelte';
import type { OrganizationUsage } from '$lib/sdk/billing';
import {
getServiceLimit,
showUsageRatesModal,
@@ -8,18 +18,8 @@
upgradeURL
} from '$lib/stores/billing';
import { organization } from '$lib/stores/organization';
import { Button } from '$lib/elements/forms';
import { bytesToSize, humanFileSize, mbSecondsToGBHours } from '$lib/helpers/sizeConvertion';
import { BarChart, Legend } from '$lib/charts';
import ProjectBreakdown from './ProjectBreakdown.svelte';
import { formatNum } from '$lib/helpers/string';
import { accumulateFromEndingTotal, total } from '$lib/layout/usage.svelte';
import type { OrganizationUsage } from '$lib/sdk/billing';
import { BillingPlan } from '$lib/constants';
import { trackEvent } from '$lib/actions/analytics';
import TotalMembers from './totalMembers.svelte';
import { tooltip } from '$lib/actions/tooltip';
import { formatCurrency, formatNumberWithCommas } from '$lib/helpers/numbers';
export let data;
@@ -260,6 +260,60 @@
</svelte:fragment>
</CardGrid>
<CardGrid>
<Heading tag="h6" size="7">Image transformations</Heading>
<p class="text">
The total number of unique image transformations across all projects in your
organization. <a
href="https://appwrite.io/docs/advanced/platform/image-transformations"
class="link">Learn more</a
>.
</p>
<svelte:fragment slot="aside">
{#if data.organizationUsage.imageTransformationsTotal}
{@const current = data.organizationUsage.imageTransformationsTotal}
<ProgressBarBig
currentUnit="Transformations"
currentValue={formatNum(current)}
progressValue={current}
showBar={false} />
<BarChart
options={{
yAxis: {
axisLabel: {
formatter: formatNum
}
}
}}
series={[
{
name: 'Image transformations',
data: [
...(data.organizationUsage.imageTransformations ?? []).map((e) => [
e.date,
e.value
])
]
}
]} />
{#if projects?.length > 0}
<ProjectBreakdown {projects} metric="imageTransformations" {data} />
{/if}
{:else}
<Card isDashed>
<div class="u-flex u-cross-center u-flex-vertical u-main-center u-flex">
<span
class="icon-chart-square-bar text-large"
aria-hidden="true"
style="font-size: 32px;" />
<p class="u-bold">No data to show</p>
</div>
</Card>
{/if}
</svelte:fragment>
</CardGrid>
<CardGrid>
<Heading tag="h6" size="7">Executions</Heading>
@@ -1,8 +1,8 @@
import type { Invoice } from '$lib/sdk/billing';
import { type Organization } from '$lib/stores/organization';
import { sdk } from '$lib/stores/sdk';
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';
export const load: PageLoad = async ({ params, parent }) => {
const { invoice } = params;
@@ -33,7 +33,9 @@ export const load: PageLoad = async ({ params, parent }) => {
databasesReads: null,
databasesWrites: null,
databasesReadsTotal: null,
databasesWritesTotal: null
databasesWritesTotal: null,
imageTransformations: null,
imageTransformationsTotal: null
}
};
}
@@ -1,5 +1,5 @@
<script lang="ts">
import type { PageData } from './$types';
import { base } from '$app/paths';
import { Collapsible, CollapsibleItem } from '$lib/components';
import {
TableBody,
@@ -13,9 +13,9 @@
import { abbreviateNumber, formatCurrency, formatNumberWithCommas } from '$lib/helpers/numbers';
import { humanFileSize } from '$lib/helpers/sizeConvertion';
import type { OrganizationUsage } from '$lib/sdk/billing';
import { base } from '$app/paths';
import { canSeeProjects } from '$lib/stores/roles';
import { onMount } from 'svelte';
import type { PageData } from './$types';
type Metric =
| 'users'
@@ -24,7 +24,8 @@
| 'executions'
| 'authPhoneTotal'
| 'databasesReads'
| 'databasesWrites';
| 'databasesWrites'
| 'imageTransformations';
type Estimate = 'authPhoneEstimate';
@@ -93,6 +94,7 @@
}
switch (metric) {
case 'imageTransformations':
case 'authPhoneTotal':
return formatNumberWithCommas(value);
case 'executions':
@@ -1,28 +1,28 @@
<script lang="ts">
import { Container } from '$lib/layout';
import { CardGrid, Heading, Card, ProgressBarBig } from '$lib/components';
import { base } from '$app/paths';
import { BarChart, Legend } from '$lib/charts';
import { Card, CardGrid, Heading, ProgressBarBig } from '$lib/components';
import Collapsible from '$lib/components/collapsible.svelte';
import CollapsibleItem from '$lib/components/collapsibleItem.svelte';
import { BillingPlan } from '$lib/constants.js';
import { Button } from '$lib/elements/forms';
import {
TableRow,
Table,
TableBody,
TableCell,
TableCellHead,
TableHeader,
Table,
TableRow,
TableRowLink
} from '$lib/elements/table';
import { getCountryName } from '$lib/helpers/diallingCodes.js';
import { formatCurrency, formatNumberWithCommas } from '$lib/helpers/numbers';
import { bytesToSize, humanFileSize, mbSecondsToGBHours } from '$lib/helpers/sizeConvertion';
import { formatNum } from '$lib/helpers/string';
import { Container } from '$lib/layout';
import { total } from '$lib/layout/usage.svelte';
import { showUsageRatesModal, tierToPlan, upgradeURL } from '$lib/stores/billing';
import { organization } from '$lib/stores/organization';
import { Button } from '$lib/elements/forms';
import { bytesToSize, humanFileSize, mbSecondsToGBHours } from '$lib/helpers/sizeConvertion';
import { BarChart, Legend } from '$lib/charts';
import { formatNum } from '$lib/helpers/string';
import { total } from '$lib/layout/usage.svelte';
import { BillingPlan } from '$lib/constants.js';
import { base } from '$app/paths';
import { formatCurrency, formatNumberWithCommas } from '$lib/helpers/numbers';
import Collapsible from '$lib/components/collapsible.svelte';
import CollapsibleItem from '$lib/components/collapsibleItem.svelte';
import { getCountryName } from '$lib/helpers/diallingCodes.js';
export let data;
@@ -36,7 +36,8 @@
data.usage.filesStorageTotal +
data.usage.deploymentsStorageTotal +
data.usage.buildsStorageTotal;
$: imageTransformations = data.usage.imageTransformations;
$: imageTransformationsTotal = data.usage.imageTransformationsTotal;
$: dbReads = data.usage.databasesReads;
$: dbWrites = data.usage.databasesWrites;
@@ -246,6 +247,49 @@
{/if}
</svelte:fragment>
</CardGrid>
<CardGrid>
<Heading tag="h6" size="7">Image transformations</Heading>
<p class="text">Total unique image transformations in your project.</p>
<svelte:fragment slot="aside">
{#if imageTransformations}
{@const current = formatNum(imageTransformationsTotal)}
<div class="u-flex u-flex-vertical">
<div class="u-flex u-main-space-between">
<p>
<span class="heading-level-4">{current}</span>
<span class="body-text-1 u-bold">Transformations</span>
</p>
</div>
</div>
<BarChart
options={{
yAxis: {
axisLabel: {
formatter: formatNum
}
}
}}
series={[
{
name: 'Image transformations',
data: [...imageTransformations.map((e) => [e.date, e.value])]
}
]} />
{:else}
<Card isDashed>
<div class="u-flex u-cross-center u-flex-vertical u-main-center u-flex">
<span
class="icon-chart-square-bar text-large"
aria-hidden="true"
style="font-size: 32px;" />
<p class="u-bold">No data to show</p>
</div>
</Card>
{/if}
</svelte:fragment>
</CardGrid>
<CardGrid>
<Heading tag="h6" size="7">Executions</Heading>
@@ -5,16 +5,29 @@
import type { PageData } from './$types';
export let data: PageData;
$: total = data.filesTotal;
$: count = data.files;
$: filesTotal = data.filesTotal;
$: files = data.files;
$: transformationsTotal = data.imageTransformationsTotal;
$: transformations = data.imageTransformations;
</script>
<Usage
title="Files"
path={`${base}/project-${$page.params.project}/storage/bucket-${$page.params.bucket}/usage`}
{total}
{count}
total={filesTotal}
count={files}
countMetadata={{
legend: 'Files',
title: 'Total files'
}} />
<Usage
title="Image transformations"
path={`${base}/project-${$page.params.project}/storage/bucket-${$page.params.bucket}/usage`}
total={transformationsTotal}
count={transformations}
countMetadata={{
legend: 'Image transformations',
title: 'Total transformations'
}}
hideSelectPeriod />
@@ -1,8 +1,7 @@
import { sdk } from '$lib/stores/sdk';
import type { Metric, UsageBuckets } from '$lib/sdk/usage';
import type { PageLoad } from './$types';
import { StorageUsageRange } from '@appwrite.io/console';
import { isValueOfStringEnum } from '$lib/helpers/types';
import { sdk } from '$lib/stores/sdk';
import { StorageUsageRange, type Models } from '@appwrite.io/console';
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ params }) => {
const period = isValueOfStringEnum(StorageUsageRange, params.period)
@@ -11,10 +10,12 @@ export const load: PageLoad = async ({ params }) => {
const response = (await sdk.forProject.storage.getBucketUsage(
params.bucket,
period
)) as unknown as UsageBuckets;
)) as unknown as Models.UsageBuckets;
return {
filesTotal: response.filesTotal,
files: response.files as Metric[]
files: response.files as Models.Metric[],
imageTransformationsTotal: response.imageTransformationsTotal,
imageTransformations: response.imageTransformations as Models.Metric[]
};
};