diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.constants.ts b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.constants.ts index e2e908a62d8..f51beb8974c 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.constants.ts +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.constants.ts @@ -1,4 +1,5 @@ import { PricingMetric } from '@/data/analytics/org-daily-stats-query' +import { OrgSubscription } from '@/data/subscriptions/types' import { DOCS_URL } from '@/lib/constants' export const USAGE_APPROACHING_THRESHOLD = 0.8 @@ -17,181 +18,187 @@ export interface Metric { } } -export const BILLING_BREAKDOWN_METRICS: Metric[] = [ - { - key: PricingMetric.DATABASE_SIZE, - name: 'Database Size', - units: 'bytes', - anchor: 'dbSize', - category: 'Database', - unitName: 'GB', - tip: 'Database size refers to the actual amount of space used by all your database objects, as reported by Postgres. This measurement reflects your current database size at this moment. Free Plan projects are limited to 0.5 GB Database Size each and are monitored across the entire billing period.', - }, - { - key: PricingMetric.EGRESS, - name: 'Egress', - units: 'bytes', - anchor: 'egress', - category: 'Database', - unitName: 'GB', - }, - { - key: PricingMetric.CACHED_EGRESS, - name: 'Cached Egress', - units: 'bytes', - anchor: 'cachedEgress', - category: 'Database', - unitName: 'GB', - }, - { - key: PricingMetric.MONTHLY_ACTIVE_USERS, - name: 'Monthly Active Users', - units: 'absolute', - anchor: 'mau', - category: 'Authentication', - unitName: 'MAU', - }, - { - key: PricingMetric.MONTHLY_ACTIVE_SSO_USERS, - name: 'Monthly Active SSO Users', - units: 'absolute', - anchor: 'mauSso', - category: 'Authentication', - unitName: 'MAU', - }, - { - key: PricingMetric.MONTHLY_ACTIVE_THIRD_PARTY_USERS, - name: 'Monthly Active Third-Party Users', - units: 'absolute', - category: 'Authentication', - unitName: 'MAU', - }, - { - key: PricingMetric.STORAGE_SIZE, - name: 'Storage Size', - units: 'bytes', - anchor: 'storageSize', - category: 'Storage', - unitName: 'GB', - }, - { - key: PricingMetric.STORAGE_IMAGES_TRANSFORMED, - name: 'Storage Image Transformations', - units: 'absolute', - anchor: 'storageImageTransformations', - category: 'Storage', - }, - { - key: PricingMetric.REALTIME_PEAK_CONNECTIONS, - name: 'Realtime Concurrent Peak Connections', - units: 'absolute', - anchor: 'realtimePeakConnections', - category: 'Realtime', - }, - { - key: PricingMetric.REALTIME_MESSAGE_COUNT, - name: 'Realtime Messages', - units: 'absolute', - anchor: 'realtimeMessageCount', - category: 'Realtime', - }, - { - key: PricingMetric.FUNCTION_INVOCATIONS, - name: 'Edge Function Invocations', - units: 'absolute', - anchor: 'funcInvocations', - category: 'Edge Functions', - }, - { - key: PricingMetric.DISK_SIZE_GB_HOURS_GP3, - name: 'Disk Size', - anchor: 'diskSize', - units: 'absolute', - unitName: 'GB-Hrs', - category: 'Database', - tip: 'Each project gets provisioned with 8 GB of GP3 disk for free. When you get close to the disk size limit, we autoscale your disk by 1.5x. Each GB of provisioned disk size beyond 8 GB incurs a GB-Hr every hour. Each extra GB is billed at $0.125/month ($0.000171/GB-Hr), prorated down to the hour.', - docLink: { - title: 'Read more', - url: `${DOCS_URL}/guides/platform/manage-your-usage/disk-size`, +export const BILLING_BREAKDOWN_METRICS = (subscription?: OrgSubscription): Metric[] => { + return [ + { + key: PricingMetric.DATABASE_SIZE, + name: 'Database Size', + units: 'bytes', + anchor: 'dbSize', + category: 'Database', + unitName: 'GB', + tip: 'Database size refers to the actual amount of space used by all your database objects, as reported by Postgres. This measurement reflects your current database size at this moment. Free Plan projects are limited to 0.5 GB Database Size each and are monitored across the entire billing period.', }, - }, - { - key: PricingMetric.DISK_SIZE_GB_HOURS_IO2, - name: 'Disk Size IO2', - anchor: 'diskSize', - units: 'absolute', - unitName: 'GB-Hrs', - category: 'Database', - }, - { - key: PricingMetric.DISK_IOPS_IO2, - name: 'Disk IOPS IO2', - units: 'absolute', - unitName: 'IOPS-Hrs', - category: 'Database', - }, - { - key: PricingMetric.DISK_IOPS_GP3, - name: 'Disk IOPS GP3', - units: 'absolute', - unitName: 'IOPS-Hrs', - category: 'Database', - }, - { - key: PricingMetric.DISK_THROUGHPUT_GP3, - name: 'Disk Throughput', - units: 'absolute', - unitName: 'IOPS-Hrs', - category: 'Database', - }, - { - key: PricingMetric.LOG_DRAIN_EVENTS, - name: 'Log Drain Events', - units: 'absolute', - category: 'Database', - }, - { - key: PricingMetric.LOG_INGESTION, - name: 'Log Ingestion', - units: 'bytes', - category: 'Logs', - anchor: 'logIngestion', - }, - { - key: PricingMetric.LOG_QUERYING, - name: 'Log Query', - units: 'bytes', - category: 'Logs', - anchor: 'logQuery', - }, - { - key: PricingMetric.LOG_STORAGE, - name: 'Log Storage', - units: 'bytes', - category: 'Logs', - anchor: 'logStorage', - }, - { - key: PricingMetric.ACTIVE_COMPUTE_HOURS, - name: 'Active Compute Hours', - units: 'absolute', - category: 'compute', - anchor: 'active-compute', - }, - { - key: PricingMetric.ETL_COPY_BACKFILL_DATA, - name: 'Initial Sync Data Processed', - units: 'bytes', - unitName: 'GB', - category: 'Pipelines', - anchor: 'pipeline-initial-sync-data', - }, - { - key: PricingMetric.ETL_REPLICATED_DATA, - name: 'Ongoing Replication Data Processed', - units: 'bytes', - unitName: 'GB', - category: 'Pipelines', - anchor: 'pipeline-ongoing-replication-data', - }, -] + { + key: PricingMetric.EGRESS, + name: 'Egress', + units: 'bytes', + anchor: 'egress', + category: 'Database', + unitName: 'GB', + }, + { + key: PricingMetric.CACHED_EGRESS, + name: 'Cached Egress', + units: 'bytes', + anchor: 'cachedEgress', + category: 'Database', + unitName: 'GB', + }, + { + key: PricingMetric.MONTHLY_ACTIVE_USERS, + name: 'Monthly Active Users', + units: 'absolute', + anchor: 'mau', + category: 'Authentication', + unitName: 'MAU', + }, + { + key: PricingMetric.MONTHLY_ACTIVE_SSO_USERS, + name: 'Monthly Active SSO Users', + units: 'absolute', + anchor: 'mauSso', + category: 'Authentication', + unitName: 'MAU', + }, + { + key: PricingMetric.MONTHLY_ACTIVE_THIRD_PARTY_USERS, + name: 'Monthly Active Third-Party Users', + units: 'absolute', + category: 'Authentication', + unitName: 'MAU', + }, + { + key: PricingMetric.STORAGE_SIZE, + name: 'Storage Size', + units: 'bytes', + anchor: 'storageSize', + category: 'Storage', + unitName: 'GB', + }, + { + key: PricingMetric.STORAGE_IMAGES_TRANSFORMED, + name: 'Storage Image Transformations', + units: 'absolute', + anchor: 'storageImageTransformations', + category: 'Storage', + }, + { + key: PricingMetric.REALTIME_PEAK_CONNECTIONS, + name: 'Realtime Concurrent Peak Connections', + units: 'absolute', + anchor: 'realtimePeakConnections', + category: 'Realtime', + }, + { + key: PricingMetric.REALTIME_MESSAGE_COUNT, + name: 'Realtime Messages', + units: 'absolute', + anchor: 'realtimeMessageCount', + category: 'Realtime', + }, + { + key: PricingMetric.FUNCTION_INVOCATIONS, + name: 'Edge Function Invocations', + units: 'absolute', + anchor: 'funcInvocations', + category: 'Edge Functions', + }, + { + key: PricingMetric.DISK_SIZE_GB_HOURS_GP3, + name: 'Disk Size', + anchor: 'diskSize', + units: 'absolute', + unitName: 'GB-Hrs', + category: 'Database', + tip: 'Each project gets provisioned with 8 GB of GP3 disk for free. When you get close to the disk size limit, we autoscale your disk by 1.5x. Each GB of provisioned disk size beyond 8 GB incurs a GB-Hr every hour. Each extra GB is billed at $0.125/month ($0.000171/GB-Hr), prorated down to the hour.', + docLink: { + title: 'Read more', + url: `${DOCS_URL}/guides/platform/manage-your-usage/disk-size`, + }, + }, + { + key: PricingMetric.DISK_SIZE_GB_HOURS_IO2, + name: 'Disk Size IO2', + anchor: 'diskSize', + units: 'absolute', + unitName: 'GB-Hrs', + category: 'Database', + }, + { + key: PricingMetric.DISK_IOPS_IO2, + name: 'Disk IOPS IO2', + units: 'absolute', + unitName: 'IOPS-Hrs', + category: 'Database', + }, + { + key: PricingMetric.DISK_IOPS_GP3, + name: 'Disk IOPS GP3', + units: 'absolute', + unitName: 'IOPS-Hrs', + category: 'Database', + }, + { + key: PricingMetric.DISK_THROUGHPUT_GP3, + name: 'Disk Throughput', + units: 'absolute', + unitName: 'IOPS-Hrs', + category: 'Database', + }, + { + key: PricingMetric.LOG_DRAIN_EVENTS, + name: 'Log Drain Events', + units: 'absolute', + category: 'Database', + }, + { + key: PricingMetric.LOG_INGESTION, + name: 'Log Ingestion', + units: 'bytes', + category: 'Logs', + anchor: 'logIngestion', + }, + { + key: PricingMetric.LOG_QUERYING, + name: 'Log Query', + units: 'bytes', + category: 'Logs', + anchor: 'logQuery', + tip: + subscription?.plan.id !== 'platform' + ? "Your organization gets a log query allowance that scales with how much log data you ingest. Once exceeded, your logs will be degraded. Exceeding your allowance doesn't rate limit queries, shorten retention, or cut off access yet — the grace period ends at the end of 2026." + : undefined, + }, + { + key: PricingMetric.LOG_STORAGE, + name: 'Log Storage', + units: 'bytes', + category: 'Logs', + anchor: 'logStorage', + }, + { + key: PricingMetric.ACTIVE_COMPUTE_HOURS, + name: 'Active Compute Hours', + units: 'absolute', + category: 'compute', + anchor: 'active-compute', + }, + { + key: PricingMetric.ETL_COPY_BACKFILL_DATA, + name: 'Initial Sync Data Processed', + units: 'bytes', + unitName: 'GB', + category: 'Pipelines', + anchor: 'pipeline-initial-sync-data', + }, + { + key: PricingMetric.ETL_REPLICATED_DATA, + name: 'Ongoing Replication Data Processed', + units: 'bytes', + unitName: 'GB', + category: 'Pipelines', + anchor: 'pipeline-ongoing-replication-data', + }, + ] +} diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingMetric.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingMetric.tsx index 849182d7c50..2f602d36a4c 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingMetric.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingMetric.tsx @@ -1,7 +1,7 @@ import { ChevronRight } from 'lucide-react' import Link from 'next/link' import { useMemo } from 'react' -import { cn, HoverCard, HoverCardContent, HoverCardTrigger } from 'ui' +import { Badge, cn, HoverCard, HoverCardContent, HoverCardTrigger } from 'ui' import { billingMetricUnit, formatUsage } from '../helpers' import { Metric, USAGE_APPROACHING_THRESHOLD } from './BillingBreakdown.constants' @@ -47,12 +47,16 @@ export const BillingMetric = ({ : usageMeta.usage.toLocaleString() + (metric.unitName ? ` ${metric.unitName}` : '') } else { return metric.units === 'bytes' || metric.units === 'gigabytes' - ? `${usageMeta.usage.toLocaleString() ?? 0} / ${usageMeta.pricing_free_units ?? 0} GB` + ? `${usageMeta.usage.toLocaleString() ?? 0} / ${usageMeta.pricing_free_units?.toLocaleString() ?? 0} GB` : `${usageMeta.usage.toLocaleString()} / ${usageMeta.pricing_free_units?.toLocaleString()}` + (metric.unitName ? ` ${metric.unitName}` : '') } }, [usageMeta, relativeToSubscription, metric]) + const isLogMetricOnNonPlatformPlan = + (metric.key === PricingMetric.LOG_INGESTION || metric.key === PricingMetric.LOG_QUERYING) && + subscription?.plan.id !== 'platform' + const sortedProjectAllocations = useMemo(() => { if (!usageMeta || !usageMeta.project_allocations) return [] @@ -86,8 +90,13 @@ export const BillingMetric = ({ {metric.anchor ? (
-

- {metric.name} +

+ {metric.name} + {isLogMetricOnNonPlatformPlan && ( + + Upcoming + + )}

{usageMeta.available_in_plan && ( @@ -97,7 +106,10 @@ export const BillingMetric = ({
{usageLabel}  {relativeToSubscription && usageMeta.cost && usageMeta.cost > 0 ? ( - + ({formatCurrency(usageMeta.cost)}) ) : usageMeta.available_in_plan && @@ -201,14 +213,23 @@ export const BillingMetric = ({ {subscription.usage_billing_enabled === false && relativeToSubscription && - (isApproachingLimit || isExceededLimit) && ( + (isApproachingLimit || isExceededLimit) && + (isLogMetricOnNonPlatformPlan ? ( +
+

+ Enforcement of restrictions will only start after the grace period ends on Dec + 23, 2026. Reduce your usage, upgrade to a usage-based plan or disable the spend + cap to avoid restrictions. +

+
+ ) : (

Exceeding your plans included usage will lead to restrictions to your project. Upgrade to a usage-based plan or disable the spend cap to avoid restrictions.

- )} + ))} {sortedProjectAllocations && sortedProjectAllocations.length > 0 && ( diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Restriction.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Restriction.tsx index 6139406abda..7f275ae2f0d 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Restriction.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Restriction.tsx @@ -21,6 +21,9 @@ export const Restriction = () => { usage?.usages.find( (metric) => metric.metric !== PricingMetric.DISK_SIZE_GB_HOURS_GP3 && + // Log metrics should not trigger the exceeded limits alert for now (soft rollout) + metric.metric !== PricingMetric.LOG_INGESTION && + metric.metric !== PricingMetric.LOG_QUERYING && !metric.unlimited && metric.capped && metric.usage > (metric?.pricing_free_units ?? 0) diff --git a/apps/studio/components/interfaces/Organization/Usage/OrgLogUsage.tsx b/apps/studio/components/interfaces/Organization/Usage/OrgLogUsage.tsx index 7ef2d7e0162..3b572c2c9b1 100644 --- a/apps/studio/components/interfaces/Organization/Usage/OrgLogUsage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/OrgLogUsage.tsx @@ -42,11 +42,18 @@ const OrgLogUsage = ({ margin: 20, isLoading: isLoadingOrgDailyStats, }, - [PricingMetric.LOG_STORAGE]: { - data: dailyUsageToDataPoints(orgDailyStats, (metric) => metric === PricingMetric.LOG_STORAGE), - margin: 0, - isLoading: isLoadingOrgDailyStats, - }, + ...(subscription?.plan.id === 'platform' + ? { + [PricingMetric.LOG_STORAGE]: { + data: dailyUsageToDataPoints( + orgDailyStats, + (metric) => metric === PricingMetric.LOG_STORAGE + ), + margin: 0, + isLoading: isLoadingOrgDailyStats, + }, + } + : {}), } return ( diff --git a/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx b/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx index 7ac3cf352e7..79081d1f7d7 100644 --- a/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx @@ -34,9 +34,7 @@ const METRICS_TO_HIDE_WITH_NO_USAGE: PricingMetric[] = [ PricingMetric.DISK_SIZE_GB_HOURS_GP3, PricingMetric.DISK_SIZE_GB_HOURS_IO2, PricingMetric.DISK_THROUGHPUT_GP3, - PricingMetric.LOG_INGESTION, PricingMetric.LOG_STORAGE, - PricingMetric.LOG_QUERYING, PricingMetric.ACTIVE_COMPUTE_HOURS, PricingMetric.ETL_PIPELINE, PricingMetric.ETL_REPLICATED_DATA, @@ -82,6 +80,9 @@ export const TotalUsage = ({ (usageItem) => // Filter out compute as compute has no quota and is always being charged for !usageItem.metric.startsWith('COMPUTE_') && + // Log metrics should not trigger the exceeded limits alert for now (soft rollout) + usageItem.metric !== PricingMetric.LOG_INGESTION && + usageItem.metric !== PricingMetric.LOG_QUERYING && !usageItem.unlimited && usageItem.usage > (usageItem?.pricing_free_units ?? 0) ) @@ -90,17 +91,28 @@ export const TotalUsage = ({ const sortedBillingMetrics = useMemo(() => { if (!usage) return [] - const breakdownMetrics = BILLING_BREAKDOWN_METRICS.filter((metric) => - usage.usages.some((usage) => usage.metric === metric.key) - ).filter((metric) => { - if (!METRICS_TO_HIDE_WITH_NO_USAGE.includes(metric.key as PricingMetric)) return true + const breakdownMetrics = BILLING_BREAKDOWN_METRICS(subscription) + .filter((metric) => usage.usages.some((usage) => usage.metric === metric.key)) + .filter((metric) => { + if (!METRICS_TO_HIDE_WITH_NO_USAGE.includes(metric.key as PricingMetric)) return true - const metricUsage = usage.usages.find((it) => it.metric === metric.key) + const metricUsage = usage.usages.find((it) => it.metric === metric.key) - return metricUsage && metricUsage.usage > 0 - }) + return metricUsage && metricUsage.usage > 0 + }) + + const PINNED_METRICS = [PricingMetric.LOG_INGESTION, PricingMetric.LOG_QUERYING] return breakdownMetrics.slice().sort((a, b) => { + const pinnedIndexA = PINNED_METRICS.indexOf(a.key as PricingMetric) + const pinnedIndexB = PINNED_METRICS.indexOf(b.key as PricingMetric) + + if (pinnedIndexA !== -1 || pinnedIndexB !== -1) { + if (pinnedIndexA === -1) return 1 + if (pinnedIndexB === -1) return -1 + return pinnedIndexA - pinnedIndexB + } + const usageMetaA = usage.usages.find((x) => x.metric === a.key) const usageRatioA = typeof usageMetaA !== 'number' @@ -120,7 +132,7 @@ export const TotalUsage = ({ usageRatioB - usageRatioA ) }) - }, [usage]) + }, [usage, subscription]) const computeMetrics = (usage?.usages || []) .filter((it) => it.metric.startsWith('COMPUTE')) @@ -199,6 +211,7 @@ export const TotalUsage = ({ )}

)} +
{sortedBillingMetrics.map((metric, i) => { return ( diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx b/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx index 6fccdd78045..c9eddca09d5 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx @@ -230,6 +230,23 @@ export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[ ], }) + const logStorageAttributes: CategoryAttribute[] = + subscription?.plan.id === 'platform' + ? [ + { + anchor: 'logStorage', + key: PricingMetric.LOG_STORAGE, + attributes: [{ key: PricingMetric.LOG_STORAGE.toLowerCase(), color: 'white' }], + name: 'Log Storage', + unit: 'absolute', + description: + 'Total amount of logs stored on the platform. Log retention depends on your platform agreement.\nBilling is based on the total amount of logs stored and factors in the retention period.', + chartDescription: 'The data refreshes every hour.', + links: [], + }, + ] + : [] + return [ { key: 'egress', @@ -373,34 +390,38 @@ export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[ key: PricingMetric.LOG_INGESTION, attributes: [{ key: PricingMetric.LOG_INGESTION.toLowerCase(), color: 'white' }], name: 'Log Ingestion', - unit: 'absolute', + unit: 'bytes', description: - 'Total amount of logs ingested across all projects.\nBilling is based on the total amount of logs ingested in Gigabyte.', + subscription?.plan.id === 'platform' + ? "Total volume of log data that Supabase ingests across all your project's services (Postgres, API gateway, Auth, Storage, Realtime, Edge Functions, and others) during the billing cycle." + : "Total volume of log data that Supabase ingests across all your project's services (Postgres, API gateway, Auth, Storage, Realtime, Edge Functions, and others) during the billing cycle.\nBilling starts after the grace period ends at the start of 2027.", chartDescription: 'The data refreshes every hour.', - links: [], + links: [ + { + name: 'Log Ingestion', + url: `${DOCS_URL}/guides/platform/manage-your-usage/log-ingest`, + }, + ], }, { anchor: 'logQuery', key: PricingMetric.LOG_QUERYING, attributes: [{ key: PricingMetric.LOG_QUERYING.toLowerCase(), color: 'white' }], name: 'Log Query', - unit: 'absolute', + unit: 'bytes', description: - 'Total amount of logs queried across all projects.\nBilling is based on the total amount of logs queried in Gigabyte.', + subscription?.plan.id === 'platform' + ? 'Total amount of logs queried across all projects.\nBilling is based on the total amount of logs queried in Gigabyte.' + : "Total amount of logs queried across all projects.\nLogs Query usage isn't billed directly. Instead, your organization gets a log query allowance that scales with how much log data you ingest. The allowance covers the volume of log data scanned when you read logs through the Studio UI, the Management API, the CLI, or any other interface.\nEnforcement starts after the grace period ends at the start of 2027.", chartDescription: 'The data refreshes every hour.', - links: [], - }, - { - anchor: 'logStorage', - key: PricingMetric.LOG_STORAGE, - attributes: [{ key: PricingMetric.LOG_STORAGE.toLowerCase(), color: 'white' }], - name: 'Log Storage', - unit: 'absolute', - description: - 'Total amount of logs stored on the platform. Log retention depends on your platform agreement.\nBilling is based on the total amount of logs stored and factors in the retention period.', - chartDescription: 'The data refreshes every hour.', - links: [], + links: [ + { + name: 'Log Query', + url: `${DOCS_URL}/guides/platform/manage-your-usage/log-query`, + }, + ], }, + ...logStorageAttributes, ], }, diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx index afdacaceef1..abb2e55de33 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx @@ -355,18 +355,16 @@ export const Usage = () => { isLoadingOrgDailyStats={isLoadingOrgDailyStats} /> - {subscription?.plan.id === 'platform' && ( - - )} + {attribute.unit === 'bytes' || attribute.unit === 'gigabytes' - ? `${usageMeta.pricing_free_units ?? 0} GB` + ? `${usageMeta.pricing_free_units?.toLocaleString() ?? 0} GB` : (usageMeta.pricing_free_units ?? 0).toLocaleString()}

)} diff --git a/apps/studio/components/interfaces/ProjectHome/PlanUsageCard.tsx b/apps/studio/components/interfaces/ProjectHome/PlanUsageCard.tsx index 902ccffb398..c3d3635c668 100644 --- a/apps/studio/components/interfaces/ProjectHome/PlanUsageCard.tsx +++ b/apps/studio/components/interfaces/ProjectHome/PlanUsageCard.tsx @@ -1,4 +1,3 @@ -import { useFlag } from 'common' import { ChevronRight } from 'lucide-react' import Link from 'next/link' import { cn } from 'ui' @@ -44,6 +43,12 @@ const METRICS: MetricConfig[] = [ unit: 'gigabytes', anchor: 'logIngestion', }, + { + key: PricingMetric.LOG_QUERYING, + label: 'Log Query', + unit: 'gigabytes', + anchor: 'logQuery', + }, ] const formatCount = (value: number) => value.toLocaleString() @@ -175,22 +180,14 @@ export const PlanUsageCard = () => { const { data: organization } = useSelectedOrganizationQuery() const { data: usage, isSuccess, isError } = useOrgUsageQuery({ orgSlug: organization?.slug }) - const logIngestionBillingEnabled = useFlag('logIngestionBillingEnabled') - - const metricsIncludingLogIngestion = logIngestionBillingEnabled - ? METRICS - : METRICS.filter((m) => m.key !== PricingMetric.LOG_INGESTION) - const visibleRows = isSuccess - ? metricsIncludingLogIngestion - .map((config) => { - const usageItem = usage.usages.find((u) => u.metric === config.key) - if (!usageItem) return null - if (!usageItem.available_in_plan) return null - if (!usageItem.pricing_free_units || usageItem.pricing_free_units <= 0) return null - return { config, usageItem } - }) - .filter((row): row is { config: MetricConfig; usageItem: OrgMetricsUsage } => row !== null) + ? METRICS.map((config) => { + const usageItem = usage.usages.find((u) => u.metric === config.key) + if (!usageItem) return null + if (!usageItem.available_in_plan) return null + if (!usageItem.pricing_free_units || usageItem.pricing_free_units <= 0) return null + return { config, usageItem } + }).filter((row): row is { config: MetricConfig; usageItem: OrgMetricsUsage } => row !== null) : [] if (isError) return null @@ -223,9 +220,7 @@ export const PlanUsageCard = () => { orgSlug={organization?.slug ?? '_'} /> )) - : metricsIncludingLogIngestion.map((config) => ( - - ))} + : METRICS.map((config) => )}
diff --git a/apps/studio/components/ui/OveragesBanner/OveragesBanner.utils.ts b/apps/studio/components/ui/OveragesBanner/OveragesBanner.utils.ts index 766064ef1d3..cc95573d93a 100644 --- a/apps/studio/components/ui/OveragesBanner/OveragesBanner.utils.ts +++ b/apps/studio/components/ui/OveragesBanner/OveragesBanner.utils.ts @@ -9,7 +9,10 @@ export const getResourcesExceededLimitsOrg = (usageMetrics: OrgMetricsUsage[]): !usageMetric.capped || !usageMetric.available_in_plan || usageMetric.unlimited || - usageMetric.metric === 'DISK_IOPS_GP3' + usageMetric.metric === 'DISK_IOPS_GP3' || + // Log metrics should not trigger the exceeded limits alert for now (soft rollout) + usageMetric.metric === 'LOG_INGESTION' || + usageMetric.metric === 'LOG_QUERYING' ) { return false } diff --git a/apps/studio/pages/org/[slug]/index.tsx b/apps/studio/pages/org/[slug]/index.tsx index 112511cc9a8..700b45f047e 100644 --- a/apps/studio/pages/org/[slug]/index.tsx +++ b/apps/studio/pages/org/[slug]/index.tsx @@ -42,18 +42,20 @@ const ProjectsPage: NextPageWithLayout = () => { } /> ) : ( -
-
- - +
+
+
+ + +
+ {showOrgProjectsListUsageCard && ( + + )}
- {showOrgProjectsListUsageCard && ( - - )}
)} diff --git a/packages/common/constants/local-storage.ts b/packages/common/constants/local-storage.ts index a8714a1040b..622d0f96671 100644 --- a/packages/common/constants/local-storage.ts +++ b/packages/common/constants/local-storage.ts @@ -4,6 +4,7 @@ export const LOCAL_STORAGE_KEYS = { /** * STUDIO */ + LOG_PRICING_ANNOUNCEMENT_DISMISSED: 'log-pricing-announcement-dismissed', AI_ASSISTANT_STATE: (projectRef: string | undefined) => `supabase-ai-assistant-state-${projectRef}`, SIDEBAR_BEHAVIOR: 'supabase-sidebar-behavior',