From 09e3a50d9f07aecc71d92146ee01555d999c2c8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Fri, 2 Jun 2023 19:34:44 +0200 Subject: [PATCH 1/7] trigger preview --- .../interfaces/BillingV2/Subscription/Subscription.utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/studio/components/interfaces/BillingV2/Subscription/Subscription.utils.ts b/studio/components/interfaces/BillingV2/Subscription/Subscription.utils.ts index e809b7db543..44da0c1b0ae 100644 --- a/studio/components/interfaces/BillingV2/Subscription/Subscription.utils.ts +++ b/studio/components/interfaces/BillingV2/Subscription/Subscription.utils.ts @@ -4,5 +4,6 @@ export const getAddons = (selectedAddons: ProjectSelectedAddon[]) => { const computeInstance = selectedAddons.find((addon) => addon.type === 'compute_instance') const pitr = selectedAddons.find((addon) => addon.type === 'pitr') const customDomain = selectedAddons.find((addon) => addon.type === 'custom_domain') + return { computeInstance, pitr, customDomain } } From d41fb1d4beda6add0b6bcdbc0644e70c3ad8bdaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Fri, 2 Jun 2023 23:35:37 +0200 Subject: [PATCH 2/7] chore: styling/copy improvements for usage page --- .../interfaces/Billing/Billing.constants.tsx | 2 +- .../Subscription/BillingBreakdown.tsx | 1 + .../Subscription/Tier/SubscriptionTier.tsx | 1 + .../interfaces/BillingV2/Usage/Activity.tsx | 3 +- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 3 +- .../BillingV2/Usage/Infrastructure.tsx | 24 +++++++++--- .../BillingV2/Usage/SectionContent.tsx | 2 +- .../BillingV2/Usage/SizeAndCounts.tsx | 3 +- .../BillingV2/Usage/Usage.constants.ts | 38 +++++++++---------- 9 files changed, 48 insertions(+), 29 deletions(-) diff --git a/studio/components/interfaces/Billing/Billing.constants.tsx b/studio/components/interfaces/Billing/Billing.constants.tsx index 6d22f8dc1a7..7661fa41f61 100644 --- a/studio/components/interfaces/Billing/Billing.constants.tsx +++ b/studio/components/interfaces/Billing/Billing.constants.tsx @@ -164,7 +164,7 @@ export const USAGE_BASED_PRODUCTS = [ costPerUnit: 0.000002, tooltip: ( - Every single serverless function invocation independent of response status is counted. + Every serverless function invocation independent of response status is counted. Billing is based on the sum of all invocations throughout your billing period. ), diff --git a/studio/components/interfaces/BillingV2/Subscription/BillingBreakdown.tsx b/studio/components/interfaces/BillingV2/Subscription/BillingBreakdown.tsx index 5d5f97b9457..1ae114d3079 100644 --- a/studio/components/interfaces/BillingV2/Subscription/BillingBreakdown.tsx +++ b/studio/components/interfaces/BillingV2/Subscription/BillingBreakdown.tsx @@ -198,6 +198,7 @@ const BillingBreakdown = ({}: BillingBreakdownProps) => { ? 'bg-amber-900' : 'bg-scale-1100' } + bgClass="bg-gray-300 dark:bg-gray-600" labelBottom={usageLabel} labelBottomClass="!text-scale-1000" labelTop={hasLimit ? percentageLabel : undefined} diff --git a/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionTier.tsx b/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionTier.tsx index e61b6193a97..6955be892c6 100644 --- a/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionTier.tsx +++ b/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionTier.tsx @@ -102,6 +102,7 @@ const SubscriptionTier = ({}: SubscriptionTierProps) => { labelBottom={`Current billing cycle (${billingCycleStart.format( 'MMM DD' )} - ${billingCycleEnd.format('MMM DD')})`} + bgClass="bg-gray-300 dark:bg-gray-600" labelBottomClass="!text-scale-1000 pb-1" labelTop={`${daysToCycleEnd} Days left`} /> diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 987f30d3fff..5437bdfec3e 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -219,6 +219,7 @@ const Activity = ({ projectRef }: ActivityProps) => { ? 'bg-amber-900' : 'bg-scale-1100' )} + bgClass="bg-gray-300 dark:bg-gray-600" value={usageMeta?.usage ?? 0} max={usageMeta?.limit || 1} /> @@ -244,7 +245,7 @@ const Activity = ({ projectRef }: ActivityProps) => { {usageMeta.limit > 0 && (
-

Overage this month

+

Overage in period

{((usageMeta?.limit ?? 0) === -1 || usageExcess < 0 ? 0 diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 4e1c17070e7..7470c72697e 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -140,6 +140,7 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { ? 'bg-amber-900' : 'bg-scale-1100' )} + bgClass="bg-gray-300 dark:bg-gray-600" value={usageMeta?.usage ?? 0} max={usageMeta?.limit || 1} /> @@ -161,7 +162,7 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => {

{usageMeta?.limit > 0 && (
-

Overage this month

+

Overage in period

{usageExcess < 0 ? formatBytes(0) : formatBytes(usageExcess)}

diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 70477e64f4b..058775e7e3e 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -150,12 +150,26 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { ) : null}
-

What is Disk IO Bandwidth?

+

Disk IO Bandwidth

+

- Smaller compute instances can burst up to the maximum disk IO bandwidth for 30 - minutes in a day. Beyond that, the performance reverts to the baseline disk IO - bandwidth. + The disk performance of your workload is determined by the Disk IO bandwidth.

+ + {currentComputeInstanceSpecs.maxBandwidth === + currentComputeInstanceSpecs.baseBandwidth ? ( +

+ Your current compute can has a baseline and maximum disk throughput of + {currentComputeInstanceSpecs.maxBandwidth.toLocaleString()} Mbps. +

+ ) : ( +

+ Your current compute can burst up to{' '} + {currentComputeInstanceSpecs.maxBandwidth.toLocaleString()} Mbps for 30 + minutes a day and reverts to the baseline performance of{' '} + {currentComputeInstanceSpecs.baseBandwidth.toLocaleString()} Mbps. +

+ )}

Overview

@@ -185,7 +199,7 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => {
{attribute.key === 'disk_io_consumption' ? ( -

IO consumed per day

+

IO consumed per {interval === '1d' ? 'day' : 'hour'}

) : (

Max{' '} diff --git a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx index 752aae4e2dc..92a1cd43d0e 100644 --- a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx @@ -37,7 +37,7 @@ const SectionContent = ({

{links && links.length && (
-

More information

+

More information

{links.map((link) => (
diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index d2a0abda6c9..54b14e1bd94 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -156,6 +156,7 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { ? 'bg-amber-900' : 'bg-scale-1100' )} + bgClass="bg-gray-300 dark:bg-gray-600" value={usageMeta?.usage ?? 0} max={usageMeta?.limit || 1} /> @@ -183,7 +184,7 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => {
{usageMeta?.limit > 0 && (
-

Overage this month

+

Overage in period

{usageExcess < 0 ? attribute.unit === 'bytes' diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts index c8b99e2b1d4..05751140509 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts @@ -76,7 +76,7 @@ export const USAGE_CATEGORIES: { }, ], description: - 'SSD Disks are attached to your servers. The disk performance of your workload is determined by the Disk IO bandwidth of this connection.', + 'Smaller compute instances (below 4XL) can burst up to their largest throughput and IOPS for 30 minutes in a day. Beyond that, the performance reverts to the baseline. Your disk budget gets replenished throughout the day.', chartDescription: '', }, ], @@ -84,7 +84,7 @@ export const USAGE_CATEGORIES: { { key: 'bandwidth', name: 'Bandwidth', - description: 'Amount of data transmitted over network connections', + description: 'Amount of data transmitted over all network connections', attributes: [ { anchor: 'dbEgress', @@ -94,7 +94,7 @@ export const USAGE_CATEGORIES: { unit: 'bytes', description: 'Contains any outgoing traffic (egress) from your database.\nBilling is based on the total sum of egress in GB throughout your billing period.', - chartDescription: 'The data shown here refreshes every 24 hours.', + chartDescription: 'The data refreshes every 24 hours.', }, { anchor: 'storageEgress', @@ -103,8 +103,8 @@ export const USAGE_CATEGORIES: { name: 'Storage egress', unit: 'bytes', description: - 'All requests to download/view your storage items go through our CDN. We sum up all outgoing traffic (egress) for storage related requests through our CDN. We do not differentiate between cache and no cache hits.\nBilling is based on the total amount of egress in GB throughout your billing period.', - chartDescription: 'The data shown here refreshes every 24 hours.', + 'All requests to view and download your storage items go through our CDN. We sum up all outgoing traffic (egress) for storage related requests through our CDN. We do not differentiate between cache and no cache hits.\nBilling is based on the total amount of egress in GB throughout your billing period.', + chartDescription: 'The data refreshes every 24 hours.', }, ], }, @@ -128,7 +128,7 @@ export const USAGE_CATEGORIES: { url: 'https://supabase.com/docs/guides/platform/database-size', }, ], - chartDescription: 'The data shown here refreshes every 24 hours.', + chartDescription: 'The data refreshes every 24 hours.', }, { anchor: 'storageSize', @@ -138,8 +138,8 @@ export const USAGE_CATEGORIES: { chartPrefix: 'Max ', unit: 'bytes', description: - 'Sum of all objects in your storage buckets.\nBilling is based on the average size in GB throughout your billing period', - chartDescription: 'The data shown here refreshes every 24 hours.', + 'Sum of all objects in your storage buckets.\nBilling is based on the average size in GB throughout your billing period.', + chartDescription: 'The data refreshes every 24 hours.', }, { anchor: 'funcCount', @@ -149,8 +149,8 @@ export const USAGE_CATEGORIES: { chartPrefix: 'Max ', unit: 'absolute', description: - 'Number of serverless functions in your project.\nBilling is based on the maximum amount of functions at any point in time throughout your billing period', - chartDescription: 'The data shown here refreshes every 24 hours.', + 'Number of serverless functions in your project.\nBilling is based on the maximum amount of functions at any point in time throughout your billing period.', + chartDescription: 'The data refreshes every 24 hours.', }, ], }, @@ -168,7 +168,7 @@ export const USAGE_CATEGORIES: { description: 'Users who log in or refresh their token count towards MAU.\nBilling is based on the sum of distinct users requesting your API throughout the billing period. Resets every billing cycle.', chartDescription: - 'The data shown here is refreshed over a period of 24 hours and resets at the beginning of every billing period.\nThe data points are relative to the beginning of your billing period.', + 'The data is refreshed over a period of 24 hours and resets at the beginning of every billing period.\nThe data points are relative to the beginning of your billing period.', }, { anchor: 'mauSso', @@ -179,7 +179,7 @@ export const USAGE_CATEGORIES: { description: 'SSO users who log in or refresh their token count towards SSO MAU.\nBilling is based on the sum of distinct Single Sign-On users requesting your API throughout the billing period. Resets every billing cycle.', chartDescription: - 'The data shown here is refreshed over a period of 24 hours and resets at the beginning of every billing period.\nThe data points are relative to the beginning of your billing period.', + 'The data refreshes over a period of 24 hours and resets at the beginning of every billing period.\nThe data points are relative to the beginning of your billing period.', }, { anchor: 'storageImageTransformations', @@ -188,9 +188,9 @@ export const USAGE_CATEGORIES: { name: 'Storage image transformations', unit: 'absolute', description: - 'We distinctly count all images that were transformed in the billing period, ignoring any transformations. If you transform one image with different transformations, it only counts as one.\nBilling is based on the unique count of (origin) images that used transformations throughout the billing period. Resets every billing cycle.', + 'We count all images that were transformed in the billing period, ignoring any transformations.\nUsage example: You transform one image with four different size transformations and another image with just a single transformations. It counts as two, as only two images were transformed.\nBilling is based on the count of (origin) images that used transformations throughout the billing period. Resets every billing cycle.', chartDescription: - 'The data shown here refreshes every 24 hours.\nThe data points are relative to the beginning of your billing period.', + 'The data refreshes every 24 hours.\nThe data points are relative to the beginning of your billing period.', }, { anchor: 'functionInvocations', @@ -199,8 +199,8 @@ export const USAGE_CATEGORIES: { name: 'Edge function invocations', unit: 'absolute', description: - 'Every single serverless function invocation independent of response status is counted.\nBilling is based on the sum of all invocations throughout your billing period.', - chartDescription: 'The data shown here refreshes every 24 hours.', + 'Every serverless function invocation independent of response status is counted.\nBilling is based on the sum of all invocations throughout your billing period.', + chartDescription: 'The data refreshes every 24 hours.', }, { anchor: 'realtimeMessageCount', @@ -209,8 +209,8 @@ export const USAGE_CATEGORIES: { name: 'Realtime message count', unit: 'absolute', description: - "Count of messages going through Realtime. If you do a database change and 5 clients listen to that change via Realtime, that's 5 messages. If you broadcast a message and 4 clients listen to that, that's 5 messages (1 message sent, 4 received).\nBilling is based on the total amount of messages throughout your billing period.", - chartDescription: 'The data shown here refreshes every 24 hours.', + "Count of messages going through Realtime.\nUsage example: If you do a database change and 5 clients listen to that change via Realtime, that's 5 messages. If you broadcast a message and 4 clients listen to that, that's 5 messages (1 message sent, 4 received).\nBilling is based on the total amount of messages throughout your billing period.", + chartDescription: 'The data refreshes every 24 hours.', }, { anchor: 'realtimePeakConnection', @@ -221,7 +221,7 @@ export const USAGE_CATEGORIES: { unit: 'absolute', description: 'Total number of successful connections (not connection attempts).\nBilling is based on the maximum amount of concurrent peak connections throughout your billing period.', - chartDescription: 'The data shown here refreshes every 24 hours.', + chartDescription: 'The data refreshes every 24 hours.', }, ], }, From 216f2d4764f6c55ba222b1f3dd016d1afbe0a6b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Fri, 2 Jun 2023 23:50:24 +0200 Subject: [PATCH 3/7] better copy for infra --- .../BillingV2/Usage/Infrastructure.tsx | 12 ++++++++++ .../BillingV2/Usage/Usage.constants.ts | 22 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 058775e7e3e..1d7210d0d94 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -211,6 +211,18 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { )}

+ {attribute.key === 'ram_usage' && ( +

+ Your compute instance has {currentComputeInstanceSpecs.memoryGb} GB memory. +

+ )} + + {attribute.key === 'cpu_usage' && ( +

+ Your compute instance has {currentComputeInstanceSpecs.cpuCores} CPU cores. +

+ )} + {attribute.chartDescription.split('\n').map((paragraph, idx) => (

{paragraph} diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts index 05751140509..754f4a70000 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts @@ -229,45 +229,65 @@ export const USAGE_CATEGORIES: { // [Joshen] Ideally live in common package for www as well export const COMPUTE_INSTANCE_SPECS: { - [key: string]: { maxBandwidth: number; baseBandwidth: number } + [key: string]: { maxBandwidth: number; baseBandwidth: number; memoryGb: number; cpuCores: number } } = { addon_instance_micro: { + memoryGb: 1, + cpuCores: 2, maxBandwidth: 2085, baseBandwidth: 87, }, addon_instance_small: { + memoryGb: 2, + cpuCores: 2, maxBandwidth: 2085, baseBandwidth: 174, }, addon_instance_medium: { + memoryGb: 4, + cpuCores: 2, maxBandwidth: 2085, baseBandwidth: 347, }, addon_instance_large: { + memoryGb: 8, + cpuCores: 2, maxBandwidth: 4750, baseBandwidth: 630, }, addon_instance_xlarge: { + memoryGb: 16, + cpuCores: 4, maxBandwidth: 4750, baseBandwidth: 1188, }, addon_instance_xxlarge: { + memoryGb: 32, + cpuCores: 8, maxBandwidth: 4750, baseBandwidth: 2375, }, addon_instance_4xlarge: { + memoryGb: 64, + cpuCores: 16, maxBandwidth: 4750, baseBandwidth: 4750, }, addon_instance_8xlarge: { + memoryGb: 128, + cpuCores: 32, maxBandwidth: 9500, baseBandwidth: 9500, }, addon_instance_12xlarge: { + memoryGb: 192, + cpuCores: 48, maxBandwidth: 14250, baseBandwidth: 14250, }, addon_instance_16xlarge: { + memoryGb: 256, + cpuCores: 64, maxBandwidth: 19000, baseBandwidth: 19000, }, From 620ee49d4764808ce3cf0db417e83b8d0d0ca5ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Sat, 3 Jun 2023 00:24:26 +0200 Subject: [PATCH 4/7] better no data handling --- .../interfaces/BillingV2/Usage/Activity.tsx | 34 ++++++--- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 38 +++++++--- .../BillingV2/Usage/Infrastructure.tsx | 16 ++--- .../BillingV2/Usage/SizeAndCounts.tsx | 70 +++++++++++-------- .../interfaces/BillingV2/Usage/Usage.tsx | 20 +++--- .../interfaces/BillingV2/Usage/Usage.utils.ts | 18 ++++- 6 files changed, 124 insertions(+), 72 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 5437bdfec3e..68353e5ab38 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -2,7 +2,6 @@ import clsx from 'clsx' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import SparkBar from 'components/ui/SparkBar' import { useDailyStatsQuery } from 'data/analytics/daily-stats-query' -import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' import { ProjectUsageResponse, UsageMetric, @@ -16,10 +15,10 @@ import UsageBarChart from './UsageBarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' -import { ChartYFormatterCompactNumber, getUpgradeUrl } from './Usage.utils' -import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants' +import { ChartYFormatterCompactNumber, getUpgradeUrlFromV2Subscription } from './Usage.utils' import { DataPoint } from 'data/analytics/constants' import Panel from 'components/ui/Panel' +import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' export interface ActivityProps { projectRef: string @@ -27,8 +26,8 @@ export interface ActivityProps { const Activity = ({ projectRef }: ActivityProps) => { const { data: usage } = useProjectUsageQuery({ projectRef }) - const { data: subscription } = useProjectSubscriptionQuery({ projectRef }) - const { current_period_start, current_period_end } = subscription?.billing ?? {} + const { data: subscription } = useProjectSubscriptionV2Query({ projectRef }) + const { current_period_start, current_period_end } = subscription ?? {} const startDate = current_period_start !== undefined ? new Date(current_period_start * 1000).toISOString() @@ -53,11 +52,9 @@ const Activity = ({ projectRef }: ActivityProps) => { const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'activity') - const upgradeUrl = getUpgradeUrl(projectRef, subscription) + const upgradeUrl = getUpgradeUrlFromV2Subscription(projectRef, subscription) - const isFreeTier = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.FREE - const isProTier = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.PRO - const usageBasedBilling = !isFreeTier && !isProTier + const usageBasedBilling = subscription?.usage_billing_enabled const exceededLimitStyle = !usageBasedBilling ? 'text-red-900' : 'text-amber-900' const { data: mauData, isLoading: isLoadingMauData } = useDailyStatsQuery({ @@ -168,6 +165,10 @@ const Activity = ({ projectRef }: ActivityProps) => { const chartData = chartMeta[attribute.key]?.data ?? [] + const notAllValuesZero = chartData.some( + (dataPoint) => Number(dataPoint[attribute.attribute]) !== 0 + ) + return (

@@ -227,7 +228,7 @@ const Activity = ({ projectRef }: ActivityProps) => {

- Included in {subscription?.tier.name.toLowerCase()} + Included in {subscription?.plan?.name.toLowerCase()} plan

{usageMeta?.limit === -1 ? (

None

@@ -270,7 +271,7 @@ const Activity = ({ projectRef }: ActivityProps) => {
- ) : ( + ) : chartData.length > 1 && notAllValuesZero ? ( { yLeftMargin={chartMeta[attribute.key].margin} yFormatter={(value) => ChartYFormatterCompactNumber(value, attribute.unit)} /> + ) : ( + + +
+

No data

+

+ No {notAllValuesZero ? 'data' : 'usage'} in period +

+
+
+
)} ) : ( diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 7470c72697e..088ea920036 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -3,7 +3,6 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import SparkBar from 'components/ui/SparkBar' import { DataPoint } from 'data/analytics/constants' import { useDailyStatsQuery } from 'data/analytics/daily-stats-query' -import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' import { ProjectUsageResponse, UsageMetric, @@ -17,8 +16,14 @@ import { Button, IconAlertTriangle } from 'ui' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' -import { ChartYFormatterCompactNumber, getUpgradeUrl } from './Usage.utils' +import { + ChartYFormatterCompactNumber, + getUpgradeUrl, + getUpgradeUrlFromV2Subscription, +} from './Usage.utils' import UsageBarChart from './UsageBarChart' +import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' +import Panel from 'components/ui/Panel' export interface BandwidthProps { projectRef: string @@ -26,8 +31,8 @@ export interface BandwidthProps { const Bandwidth = ({ projectRef }: BandwidthProps) => { const { data: usage } = useProjectUsageQuery({ projectRef }) - const { data: subscription } = useProjectSubscriptionQuery({ projectRef }) - const { current_period_start, current_period_end } = subscription?.billing ?? {} + const { data: subscription } = useProjectSubscriptionV2Query({ projectRef }) + const { current_period_start, current_period_end } = subscription ?? {} const startDate = current_period_start !== undefined ? new Date(current_period_start * 1000).toISOString() @@ -42,10 +47,8 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'bandwidth') - const upgradeUrl = getUpgradeUrl(projectRef, subscription) - const isFreeTier = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.FREE - const isProTier = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.PRO - const usageBasedBilling = !isFreeTier && !isProTier + const upgradeUrl = getUpgradeUrlFromV2Subscription(projectRef, subscription) + const usageBasedBilling = subscription?.usage_billing_enabled const exceededLimitStyle = !usageBasedBilling ? 'text-red-900' : 'text-amber-900' const { data: dbEgressData, isLoading: isLoadingDbEgressData } = useDailyStatsQuery({ @@ -95,6 +98,10 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { const chartData = chartMeta[attribute.key]?.data ?? [] + const notAllValuesZero = chartData.some( + (dataPoint) => Number(dataPoint[attribute.attribute]) !== 0 + ) + return (
@@ -148,7 +155,7 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => {

- Included in {subscription?.tier.name.toLowerCase()} + Included in {subscription?.plan?.name.toLowerCase()} plan

{usageMeta?.limit === 0 ? 'Unlimited' : formatBytes(usageMeta?.limit ?? 0)} @@ -184,7 +191,7 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => {

- ) : ( + ) : chartData.length > 1 && notAllValuesZero ? ( { yLeftMargin={chartMeta[attribute.key].margin} yFormatter={(value) => ChartYFormatterCompactNumber(value, attribute.unit)} /> + ) : ( + + +
+

No data

+

+ No {notAllValuesZero ? 'data' : 'usage'} in period +

+
+
+
)}
diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 1d7210d0d94..8ef35c0b8b0 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -1,7 +1,6 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { DataPoint } from 'data/analytics/constants' import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' -import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' import dayjs from 'dayjs' import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants' import Link from 'next/link' @@ -9,9 +8,10 @@ import { Alert, Button } from 'ui' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { COMPUTE_INSTANCE_SPECS, USAGE_CATEGORIES } from './Usage.constants' -import { getUpgradeUrl } from './Usage.utils' +import { getUpgradeUrl, getUpgradeUrlFromV2Subscription } from './Usage.utils' import UsageBarChart from './UsageBarChart' import Panel from 'components/ui/Panel' +import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' export interface InfrastructureProps { projectRef: string @@ -20,8 +20,8 @@ export interface InfrastructureProps { // [Joshen] Need to update the IO budget chart to show burst mbps and duration next time const Infrastructure = ({ projectRef }: InfrastructureProps) => { - const { data: subscription } = useProjectSubscriptionQuery({ projectRef }) - const { current_period_start, current_period_end } = subscription?.billing ?? {} + const { data: subscription } = useProjectSubscriptionV2Query({ projectRef }) + const { current_period_start, current_period_end } = subscription ?? {} const startDate = current_period_start !== undefined ? new Date(current_period_start * 1000).toISOString() @@ -37,8 +37,8 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'infra') - const upgradeUrl = getUpgradeUrl(projectRef, subscription) - const isFreeTier = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.FREE + const upgradeUrl = getUpgradeUrlFromV2Subscription(projectRef, subscription) + const isFreeTier = subscription?.plan?.id === 'free' const currentComputeInstance = subscription?.addons.find((addon) => addon.supabase_prod_id.includes('_instance_') ) @@ -249,9 +249,7 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => {

No data

-

- There is no data in the timeframe available -

+

There is no data in period

diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index 54b14e1bd94..dae39168258 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -2,14 +2,12 @@ import clsx from 'clsx' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import SparkBar from 'components/ui/SparkBar' import { useDailyStatsQuery } from 'data/analytics/daily-stats-query' -import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' import { ProjectUsageResponse, UsageMetric, useProjectUsageQuery, } from 'data/usage/project-usage-query' import dayjs from 'dayjs' -import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants' import { formatBytes } from 'lib/helpers' import Link from 'next/link' import { Badge, Button, IconAlertTriangle, IconExternalLink } from 'ui' @@ -18,8 +16,10 @@ import UsageBarChart from './UsageBarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' -import { ChartYFormatterCompactNumber, getUpgradeUrl } from './Usage.utils' +import { ChartYFormatterCompactNumber, getUpgradeUrlFromV2Subscription } from './Usage.utils' import { DataPoint } from 'data/analytics/constants' +import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' +import Panel from 'components/ui/Panel' export interface SizeAndCountsProps { projectRef: string @@ -27,8 +27,8 @@ export interface SizeAndCountsProps { const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { const { data: usage } = useProjectUsageQuery({ projectRef }) - const { data: subscription } = useProjectSubscriptionQuery({ projectRef }) - const { current_period_start, current_period_end } = subscription?.billing ?? {} + const { data: subscription } = useProjectSubscriptionV2Query({ projectRef }) + const { current_period_start, current_period_end } = subscription ?? {} const startDate = current_period_start !== undefined ? new Date(current_period_start * 1000).toISOString() @@ -43,10 +43,8 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'sizeCount') - const upgradeUrl = getUpgradeUrl(projectRef, subscription) - const isFreeTier = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.FREE - const isProTier = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.PRO - const usageBasedBilling = !isFreeTier && !isProTier + const upgradeUrl = getUpgradeUrlFromV2Subscription(projectRef, subscription) + const usageBasedBilling = subscription?.usage_billing_enabled const exceededLimitStyle = !usageBasedBilling ? 'text-red-900' : 'text-amber-900' const { data: dbSizeData, isLoading: isLoadingDbSizeData } = useDailyStatsQuery({ @@ -110,6 +108,10 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { const chartData = chartMeta[attribute.key]?.data ?? [] + const notAllValuesZero = chartData.some( + (dataPoint) => Number(dataPoint[attribute.attribute]) !== 0 + ) + return (
@@ -164,7 +166,7 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => {

- Included in {subscription?.tier.name.toLowerCase()} + Included in {subscription?.plan?.name.toLowerCase()} plan

{attribute.unit === 'bytes' @@ -199,25 +201,24 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => {

- {attribute.key === 'db_size' && - subscription?.tier.supabase_prod_id !== PRICING_TIER_PRODUCT_IDS.FREE && ( -
-
-

Disk size:

-

{usage?.disk_volume_size_gb} GB

- - Auto-scaling - -
- - - - - + {attribute.key === 'db_size' && subscription?.plan?.id !== 'free' && ( +
+
+

Disk size:

+

{usage?.disk_volume_size_gb} GB

+ + Auto-scaling +
- )} + + + + + +
+ )}

@@ -236,7 +237,7 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => {

- ) : ( + ) : chartData.length > 1 && notAllValuesZero ? ( { yLeftMargin={chartMeta[attribute.key].margin} yFormatter={(value) => ChartYFormatterCompactNumber(value, attribute.unit)} /> + ) : ( + + +
+

No data

+

+ No {notAllValuesZero ? 'data' : 'usage'} in period +

+
+
+
)}
diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index d96a78e9292..45de5f1f433 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -1,11 +1,10 @@ import clsx from 'clsx' import { useParams } from 'common' -import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' import { useProjectUsageQuery } from 'data/usage/project-usage-query' import dayjs from 'dayjs' import Link from 'next/link' import { useRef, useState } from 'react' -import { Button, IconAlertCircle, IconCheckCircle, IconLoader, Listbox } from 'ui' +import { Button, IconAlertCircle, IconLoader, Listbox } from 'ui' import Activity from './Activity' import Bandwidth from './Bandwidth' import Infrastructure from './Infrastructure' @@ -13,7 +12,7 @@ import SizeAndCounts from './SizeAndCounts' import { USAGE_CATEGORIES, USAGE_STATUS } from './Usage.constants' import { getUsageStatus } from './Usage.utils' import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' -import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants' +import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' const Usage = () => { const { ref } = useParams() @@ -28,11 +27,11 @@ const Usage = () => { const activityRef = useRef(null) const { data: usage } = useProjectUsageQuery({ projectRef: ref }) - const { data: subscription, isLoading: isLoadingSubscription } = useProjectSubscriptionQuery({ + const { data: subscription, isLoading: isLoadingSubscription } = useProjectSubscriptionV2Query({ projectRef: selectedProjectRef, }) - const { current_period_start, current_period_end } = subscription?.billing ?? {} + const { current_period_start, current_period_end } = subscription ?? {} const startDate = current_period_start !== undefined ? new Date(current_period_start * 1000).toISOString() @@ -52,13 +51,10 @@ const Usage = () => { ] ?? 100 ) - const billingCycleStart = dayjs.unix(subscription?.billing?.current_period_start ?? 0).utc() - const billingCycleEnd = dayjs.unix(subscription?.billing?.current_period_end ?? 0).utc() + const billingCycleStart = dayjs.unix(subscription?.current_period_start ?? 0).utc() + const billingCycleEnd = dayjs.unix(subscription?.current_period_end ?? 0).utc() - const subscriptionTierId = subscription?.tier?.supabase_prod_id - const usageBillingEnabled = - subscriptionTierId !== PRICING_TIER_PRODUCT_IDS.FREE && - subscriptionTierId !== PRICING_TIER_PRODUCT_IDS.PRO + const usageBillingEnabled = subscription?.usage_billing_enabled const scrollTo = (id: 'infra' | 'bandwidth' | 'sizeCount' | 'activity') => { switch (id) { @@ -107,7 +103,7 @@ const Usage = () => { ) : subscription !== undefined ? (

- Project is on {subscription.tier.name} + Project is on {subscription.plan.name} plan

{billingCycleStart.format('DD MMM YYYY')} -{' '} diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts b/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts index 99f6092748f..11e35b9331d 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts @@ -5,6 +5,7 @@ import { CategoryAttribute, USAGE_STATUS } from './Usage.constants' import { StripeSubscription } from 'components/interfaces/Billing' import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants' import { formatBytes } from 'lib/helpers' +import { ProjectSubscriptionResponse } from 'data/subscriptions/project-subscription-v2-query' // [Joshen] This is just for development to generate some test data for chart rendering export const generateUsageData = (attribute: string, days: number): DataPoint[] => { @@ -48,6 +49,21 @@ export const getUpgradeUrl = (projectRef: string, subscription?: StripeSubscript : `/project/${projectRef}/settings/billing/update/pro` } +export const getUpgradeUrlFromV2Subscription = ( + projectRef: string, + subscription?: ProjectSubscriptionResponse +) => { + if (!subscription) return `/project/${projectRef}/settings/billing/update` + + return subscription?.plan.id === 'enterprise' + ? `/project/${projectRef}/settings/billing/update/enterprise` + : subscription?.plan.id === 'team' + ? `/project/${projectRef}/settings/billing/update/team` + : subscription?.plan.id === 'free' + ? `/project/${projectRef}/settings/billing/update` + : `/project/${projectRef}/settings/billing/update/pro` +} + const compactNumberFormatter = new Intl.NumberFormat('en-US', { notation: 'compact', compactDisplay: 'short', @@ -58,7 +74,7 @@ export const ChartYFormatterCompactNumber = (number: number | string, unit: stri if (unit === 'bytes') { const formattedBytes = formatBytes(number, 0).replace(/\s/g, '') - + return formattedBytes === '0bytes' ? '0' : formattedBytes } else { return compactNumberFormatter.format(number) From 186b1a662ad2acace57788b454c2fbb47b5bddbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Sat, 3 Jun 2023 01:04:56 +0200 Subject: [PATCH 5/7] fix requests being send 3x times or more --- .../interfaces/BillingV2/Usage/Activity.tsx | 40 +++++++++-------- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 44 ++++++++++++------- .../BillingV2/Usage/Infrastructure.tsx | 28 ++++++------ .../BillingV2/Usage/SizeAndCounts.tsx | 36 ++++++++++----- 4 files changed, 89 insertions(+), 59 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 68353e5ab38..12f7e3777ee 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -19,6 +19,7 @@ import { ChartYFormatterCompactNumber, getUpgradeUrlFromV2Subscription } from '. import { DataPoint } from 'data/analytics/constants' import Panel from 'components/ui/Panel' import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' +import { useMemo } from 'react' export interface ActivityProps { projectRef: string @@ -28,27 +29,30 @@ const Activity = ({ projectRef }: ActivityProps) => { const { data: usage } = useProjectUsageQuery({ projectRef }) const { data: subscription } = useProjectSubscriptionV2Query({ projectRef }) const { current_period_start, current_period_end } = subscription ?? {} - const startDate = - current_period_start !== undefined - ? new Date(current_period_start * 1000).toISOString() - : undefined - let endDate = - current_period_end !== undefined ? new Date(current_period_end * 1000).toISOString() : undefined - // If end date is in future, set end date to yesterday/now - if (endDate && dayjs(endDate).isAfter(dayjs())) { - const yesterday = dayjs(new Date()).subtract(1, 'day') + const startDate = useMemo(() => { + return current_period_start ? new Date(current_period_start * 1000).toISOString() : undefined + }, [current_period_start]) - /** - * Currently, daily-stats data is only available a day later, so we'll use yesterday as end date, as otherwise the current day would just show up with "0" values - * - * We are actively working on removing this restriction on the data-eng/LF side and can remove this workaround once that's done - */ - let newEndDate = yesterday.isAfter(dayjs(startDate)) ? yesterday : new Date() + const endDate = useMemo(() => { + const periodEndDate = current_period_end ? new Date(current_period_end * 1000) : undefined + // If end date is in future, set end date to now + if (periodEndDate && dayjs(periodEndDate).isAfter(dayjs())) { + const yesterday = dayjs(new Date()).subtract(1, 'day') - // LF seems to have an issue with the milliseconds, causes infinite loading sometimes - endDate = newEndDate.toISOString().slice(0, -5) + 'Z' - } + /** + * Currently, daily-stats data is only available a day later, so we'll use yesterday as end date, as otherwise the current day would just show up with "0" values + * + * We are actively working on removing this restriction on the data-eng/LF side and can remove this workaround once that's done + */ + const newEndDate = yesterday.isAfter(dayjs(startDate)) ? yesterday : new Date() + + // LF seems to have an issue with the milliseconds, causes infinite loading sometimes + return newEndDate.toISOString().slice(0, -5) + 'Z' + } else if (periodEndDate) { + return periodEndDate.toISOString() + } + }, [current_period_end, startDate]) const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'activity') diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 088ea920036..96a620d1b0e 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -9,21 +9,18 @@ import { useProjectUsageQuery, } from 'data/usage/project-usage-query' import dayjs from 'dayjs' -import { PRICING_TIER_PRODUCT_IDS, USAGE_APPROACHING_THRESHOLD } from 'lib/constants' +import { USAGE_APPROACHING_THRESHOLD } from 'lib/constants' import { formatBytes } from 'lib/helpers' import Link from 'next/link' import { Button, IconAlertTriangle } from 'ui' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' -import { - ChartYFormatterCompactNumber, - getUpgradeUrl, - getUpgradeUrlFromV2Subscription, -} from './Usage.utils' +import { ChartYFormatterCompactNumber, getUpgradeUrlFromV2Subscription } from './Usage.utils' import UsageBarChart from './UsageBarChart' import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' import Panel from 'components/ui/Panel' +import { useMemo } from 'react' export interface BandwidthProps { projectRef: string @@ -33,17 +30,30 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { const { data: usage } = useProjectUsageQuery({ projectRef }) const { data: subscription } = useProjectSubscriptionV2Query({ projectRef }) const { current_period_start, current_period_end } = subscription ?? {} - const startDate = - current_period_start !== undefined - ? new Date(current_period_start * 1000).toISOString() - : undefined - let endDate = - current_period_end !== undefined ? new Date(current_period_end * 1000).toISOString() : undefined - // If end date is in future, set end date to now - if (endDate && dayjs(endDate).isAfter(dayjs())) { - // LF seems to have an issue with the milliseconds, causes infinite loading sometimes - endDate = new Date().toISOString().slice(0, -5) + 'Z' - } + + const startDate = useMemo(() => { + return current_period_start ? new Date(current_period_start * 1000).toISOString() : undefined + }, [current_period_start]) + + const endDate = useMemo(() => { + const periodEndDate = current_period_end ? new Date(current_period_end * 1000) : undefined + // If end date is in future, set end date to now + if (periodEndDate && dayjs(periodEndDate).isAfter(dayjs())) { + const yesterday = dayjs(new Date()).subtract(1, 'day') + + /** + * Currently, daily-stats data is only available a day later, so we'll use yesterday as end date, as otherwise the current day would just show up with "0" values + * + * We are actively working on removing this restriction on the data-eng/LF side and can remove this workaround once that's done + */ + const newEndDate = yesterday.isAfter(dayjs(startDate)) ? yesterday : new Date() + + // LF seems to have an issue with the milliseconds, causes infinite loading sometimes + return newEndDate.toISOString().slice(0, -5) + 'Z' + } else if (periodEndDate) { + return periodEndDate.toISOString() + } + }, [current_period_end, startDate]) const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'bandwidth') diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 8ef35c0b8b0..9be2b2ffb20 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -2,16 +2,16 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { DataPoint } from 'data/analytics/constants' import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' import dayjs from 'dayjs' -import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants' import Link from 'next/link' import { Alert, Button } from 'ui' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { COMPUTE_INSTANCE_SPECS, USAGE_CATEGORIES } from './Usage.constants' -import { getUpgradeUrl, getUpgradeUrlFromV2Subscription } from './Usage.utils' +import { getUpgradeUrlFromV2Subscription } from './Usage.utils' import UsageBarChart from './UsageBarChart' import Panel from 'components/ui/Panel' import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' +import { useMemo } from 'react' export interface InfrastructureProps { projectRef: string @@ -22,18 +22,20 @@ export interface InfrastructureProps { const Infrastructure = ({ projectRef }: InfrastructureProps) => { const { data: subscription } = useProjectSubscriptionV2Query({ projectRef }) const { current_period_start, current_period_end } = subscription ?? {} - const startDate = - current_period_start !== undefined - ? new Date(current_period_start * 1000).toISOString() - : undefined - let endDate = - current_period_end !== undefined ? new Date(current_period_end * 1000).toISOString() : undefined + const startDate = useMemo(() => { + return current_period_start ? new Date(current_period_start * 1000).toISOString() : undefined + }, [current_period_start]) - // If end date is in future, set end date to now - if (endDate && dayjs(endDate).isAfter(dayjs())) { - // LF seems to have an issue with the milliseconds, causes infinite loading sometimes - endDate = new Date().toISOString().slice(0, -5) + 'Z' - } + const endDate = useMemo(() => { + const periodEndDate = current_period_end ? new Date(current_period_end * 1000) : undefined + // If end date is in future, set end date to now + if (periodEndDate && dayjs(periodEndDate).isAfter(dayjs())) { + // LF seems to have an issue with the milliseconds, causes infinite loading sometimes + return new Date().toISOString().slice(0, -5) + 'Z' + } else if (periodEndDate) { + return periodEndDate.toISOString() + } + }, [current_period_end]) const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'infra') diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index dae39168258..d790a670d7a 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -20,6 +20,7 @@ import { ChartYFormatterCompactNumber, getUpgradeUrlFromV2Subscription } from '. import { DataPoint } from 'data/analytics/constants' import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' import Panel from 'components/ui/Panel' +import { useMemo } from 'react' export interface SizeAndCountsProps { projectRef: string @@ -29,17 +30,30 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { const { data: usage } = useProjectUsageQuery({ projectRef }) const { data: subscription } = useProjectSubscriptionV2Query({ projectRef }) const { current_period_start, current_period_end } = subscription ?? {} - const startDate = - current_period_start !== undefined - ? new Date(current_period_start * 1000).toISOString() - : undefined - let endDate = - current_period_end !== undefined ? new Date(current_period_end * 1000).toISOString() : undefined - // If end date is in future, set end date to now - if (endDate && dayjs(endDate).isAfter(dayjs())) { - // LF seems to have an issue with the milliseconds, causes infinite loading sometimes - endDate = new Date().toISOString().slice(0, -5) + 'Z' - } + + const startDate = useMemo(() => { + return current_period_start ? new Date(current_period_start * 1000).toISOString() : undefined + }, [current_period_start]) + + const endDate = useMemo(() => { + const periodEndDate = current_period_end ? new Date(current_period_end * 1000) : undefined + // If end date is in future, set end date to now + if (periodEndDate && dayjs(periodEndDate).isAfter(dayjs())) { + const yesterday = dayjs(new Date()).subtract(1, 'day') + + /** + * Currently, daily-stats data is only available a day later, so we'll use yesterday as end date, as otherwise the current day would just show up with "0" values + * + * We are actively working on removing this restriction on the data-eng/LF side and can remove this workaround once that's done + */ + const newEndDate = yesterday.isAfter(dayjs(startDate)) ? yesterday : new Date() + + // LF seems to have an issue with the milliseconds, causes infinite loading sometimes + return newEndDate.toISOString().slice(0, -5) + 'Z' + } else if (periodEndDate) { + return periodEndDate.toISOString() + } + }, [current_period_end, startDate]) const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'sizeCount') From 210546be0528561e235452d434a53e23b9907608 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Sun, 4 Jun 2023 15:45:59 +0200 Subject: [PATCH 6/7] chore: reduce duplication --- .../interfaces/BillingV2/Usage/Activity.tsx | 230 +---------------- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 195 +-------------- .../BillingV2/Usage/SectionContent.tsx | 1 - .../BillingV2/Usage/SizeAndCounts.tsx | 231 +----------------- ...Usage.constants.ts => Usage.constants.tsx} | 34 ++- .../interfaces/BillingV2/Usage/Usage.tsx | 52 +++- .../interfaces/BillingV2/Usage/UsageFoo.tsx | 191 +++++++++++++++ 7 files changed, 303 insertions(+), 631 deletions(-) rename studio/components/interfaces/BillingV2/Usage/{Usage.constants.ts => Usage.constants.tsx} (88%) create mode 100644 studio/components/interfaces/BillingV2/Usage/UsageFoo.tsx diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 12f7e3777ee..2038079fbc7 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -1,66 +1,19 @@ -import clsx from 'clsx' -import ShimmeringLoader from 'components/ui/ShimmeringLoader' -import SparkBar from 'components/ui/SparkBar' import { useDailyStatsQuery } from 'data/analytics/daily-stats-query' -import { - ProjectUsageResponse, - UsageMetric, - useProjectUsageQuery, -} from 'data/usage/project-usage-query' -import dayjs from 'dayjs' -import Link from 'next/link' -import { Button, IconAlertTriangle } from 'ui' -import { USAGE_APPROACHING_THRESHOLD } from '../Billing.constants' -import UsageBarChart from './UsageBarChart' -import SectionContent from './SectionContent' -import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' -import { ChartYFormatterCompactNumber, getUpgradeUrlFromV2Subscription } from './Usage.utils' import { DataPoint } from 'data/analytics/constants' -import Panel from 'components/ui/Panel' -import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' -import { useMemo } from 'react' +import { ProjectSubscriptionResponse } from 'data/subscriptions/project-subscription-v2-query' +import UsageFoo from './UsageFoo' export interface ActivityProps { projectRef: string + startDate: string | undefined + endDate: string | undefined + subscription: ProjectSubscriptionResponse | undefined } -const Activity = ({ projectRef }: ActivityProps) => { - const { data: usage } = useProjectUsageQuery({ projectRef }) - const { data: subscription } = useProjectSubscriptionV2Query({ projectRef }) - const { current_period_start, current_period_end } = subscription ?? {} - - const startDate = useMemo(() => { - return current_period_start ? new Date(current_period_start * 1000).toISOString() : undefined - }, [current_period_start]) - - const endDate = useMemo(() => { - const periodEndDate = current_period_end ? new Date(current_period_end * 1000) : undefined - // If end date is in future, set end date to now - if (periodEndDate && dayjs(periodEndDate).isAfter(dayjs())) { - const yesterday = dayjs(new Date()).subtract(1, 'day') - - /** - * Currently, daily-stats data is only available a day later, so we'll use yesterday as end date, as otherwise the current day would just show up with "0" values - * - * We are actively working on removing this restriction on the data-eng/LF side and can remove this workaround once that's done - */ - const newEndDate = yesterday.isAfter(dayjs(startDate)) ? yesterday : new Date() - - // LF seems to have an issue with the milliseconds, causes infinite loading sometimes - return newEndDate.toISOString().slice(0, -5) + 'Z' - } else if (periodEndDate) { - return periodEndDate.toISOString() - } - }, [current_period_end, startDate]) - +const Activity = ({ projectRef, subscription, startDate, endDate }: ActivityProps) => { const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'activity') - const upgradeUrl = getUpgradeUrlFromV2Subscription(projectRef, subscription) - - const usageBasedBilling = subscription?.usage_billing_enabled - const exceededLimitStyle = !usageBasedBilling ? 'text-red-900' : 'text-amber-900' - const { data: mauData, isLoading: isLoadingMauData } = useDailyStatsQuery({ projectRef, attribute: 'total_auth_billing_period_mau', @@ -158,171 +111,12 @@ const Activity = ({ projectRef }: ActivityProps) => { if (categoryMeta === undefined) return null return ( - <> - - - {categoryMeta.attributes.map((attribute) => { - const usageMeta = usage?.[attribute.key as keyof ProjectUsageResponse] as UsageMetric - const usageRatio = - typeof usageMeta !== 'number' ? (usageMeta?.usage ?? 0) / (usageMeta?.limit ?? 0) : 0 - const usageExcess = (usageMeta?.usage ?? 0) - (usageMeta?.limit ?? 0) - - const chartData = chartMeta[attribute.key]?.data ?? [] - - const notAllValuesZero = chartData.some( - (dataPoint) => Number(dataPoint[attribute.attribute]) !== 0 - ) - - return ( -

- - {usageMeta?.available_in_plan ? ( - <> -
-
-
-

{attribute.name} usage

- {!usageBasedBilling && usageRatio >= 1 ? ( -
- -

Exceeded limit

-
- ) : !usageBasedBilling && usageRatio >= USAGE_APPROACHING_THRESHOLD ? ( -
- -

Approaching limit

-
- ) : null} -
- {!usageBasedBilling && usageRatio >= USAGE_APPROACHING_THRESHOLD && ( - - - - - - )} -
- {usageMeta.limit > 0 && ( - = 1 - ? usageBasedBilling - ? 'bg-amber-900' - : 'bg-red-900' - : usageRatio >= USAGE_APPROACHING_THRESHOLD - ? 'bg-amber-900' - : 'bg-scale-1100' - )} - bgClass="bg-gray-300 dark:bg-gray-600" - value={usageMeta?.usage ?? 0} - max={usageMeta?.limit || 1} - /> - )} -
-
-

- Included in {subscription?.plan?.name.toLowerCase()} plan -

- {usageMeta?.limit === -1 ? ( -

None

- ) : usageMeta?.limit === 0 ? ( -

Unlimited

- ) : ( -

{(usageMeta?.limit ?? 0).toLocaleString()}

- )} -
-
-

- {attribute.chartPrefix || 'Used '}in period -

-

{(usageMeta?.usage ?? 0).toLocaleString()}

-
- {usageMeta.limit > 0 && ( -
-

Overage in period

-

- {((usageMeta?.limit ?? 0) === -1 || usageExcess < 0 - ? 0 - : usageExcess - ).toLocaleString()} -

-
- )} -
-
-
-

{attribute.name} per day

- {attribute.chartDescription.split('\n').map((paragraph, idx) => ( -

- {paragraph} -

- ))} -
- {chartMeta[attribute.key].isLoading ? ( -
- - - -
- ) : chartData.length > 1 && notAllValuesZero ? ( - ChartYFormatterCompactNumber(value, attribute.unit)} - /> - ) : ( - - -
-

No data

-

- No {notAllValuesZero ? 'data' : 'usage'} in period -

-
-
-
- )} - - ) : ( - - -
-
-

Not included in plan

-
-

- You need to be on a higher plan in order to use this feature. -

-
-
- - - - - -
-
-
- )} -
-
- ) - })} - + ) } diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 96a620d1b0e..9c76f21ed2e 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -1,66 +1,19 @@ -import clsx from 'clsx' -import ShimmeringLoader from 'components/ui/ShimmeringLoader' -import SparkBar from 'components/ui/SparkBar' import { DataPoint } from 'data/analytics/constants' import { useDailyStatsQuery } from 'data/analytics/daily-stats-query' -import { - ProjectUsageResponse, - UsageMetric, - useProjectUsageQuery, -} from 'data/usage/project-usage-query' -import dayjs from 'dayjs' -import { USAGE_APPROACHING_THRESHOLD } from 'lib/constants' -import { formatBytes } from 'lib/helpers' -import Link from 'next/link' -import { Button, IconAlertTriangle } from 'ui' -import SectionContent from './SectionContent' -import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' -import { ChartYFormatterCompactNumber, getUpgradeUrlFromV2Subscription } from './Usage.utils' -import UsageBarChart from './UsageBarChart' -import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' -import Panel from 'components/ui/Panel' -import { useMemo } from 'react' +import { ProjectSubscriptionResponse } from 'data/subscriptions/project-subscription-v2-query' +import UsageFoo from './UsageFoo' export interface BandwidthProps { projectRef: string + startDate: string | undefined + endDate: string | undefined + subscription: ProjectSubscriptionResponse | undefined } -const Bandwidth = ({ projectRef }: BandwidthProps) => { - const { data: usage } = useProjectUsageQuery({ projectRef }) - const { data: subscription } = useProjectSubscriptionV2Query({ projectRef }) - const { current_period_start, current_period_end } = subscription ?? {} - - const startDate = useMemo(() => { - return current_period_start ? new Date(current_period_start * 1000).toISOString() : undefined - }, [current_period_start]) - - const endDate = useMemo(() => { - const periodEndDate = current_period_end ? new Date(current_period_end * 1000) : undefined - // If end date is in future, set end date to now - if (periodEndDate && dayjs(periodEndDate).isAfter(dayjs())) { - const yesterday = dayjs(new Date()).subtract(1, 'day') - - /** - * Currently, daily-stats data is only available a day later, so we'll use yesterday as end date, as otherwise the current day would just show up with "0" values - * - * We are actively working on removing this restriction on the data-eng/LF side and can remove this workaround once that's done - */ - const newEndDate = yesterday.isAfter(dayjs(startDate)) ? yesterday : new Date() - - // LF seems to have an issue with the milliseconds, causes infinite loading sometimes - return newEndDate.toISOString().slice(0, -5) + 'Z' - } else if (periodEndDate) { - return periodEndDate.toISOString() - } - }, [current_period_end, startDate]) - +const Bandwidth = ({ projectRef, subscription, startDate, endDate }: BandwidthProps) => { const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'bandwidth') - const upgradeUrl = getUpgradeUrlFromV2Subscription(projectRef, subscription) - const usageBasedBilling = subscription?.usage_billing_enabled - const exceededLimitStyle = !usageBasedBilling ? 'text-red-900' : 'text-amber-900' - const { data: dbEgressData, isLoading: isLoadingDbEgressData } = useDailyStatsQuery({ projectRef, attribute: 'total_egress_modified', @@ -97,136 +50,12 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { if (categoryMeta === undefined) return null return ( - <> - - - {categoryMeta.attributes.map((attribute) => { - const usageMeta = usage?.[attribute.key as keyof ProjectUsageResponse] as UsageMetric - const usageRatio = - typeof usageMeta !== 'number' ? (usageMeta?.usage ?? 0) / (usageMeta?.limit ?? 0) : 0 - const usageExcess = (usageMeta?.usage ?? 0) - (usageMeta?.limit ?? 0) - - const chartData = chartMeta[attribute.key]?.data ?? [] - - const notAllValuesZero = chartData.some( - (dataPoint) => Number(dataPoint[attribute.attribute]) !== 0 - ) - - return ( -
- -
-
-
-

{attribute.name} usage

- {!usageBasedBilling && usageRatio >= 1 ? ( -
- -

Exceeded limit

-
- ) : !usageBasedBilling && usageRatio >= USAGE_APPROACHING_THRESHOLD ? ( -
- -

Approaching limit

-
- ) : null} -
- {!usageBasedBilling && usageRatio >= USAGE_APPROACHING_THRESHOLD && ( - - - - - - )} -
- {usageMeta?.limit > 0 && ( - = 1 - ? usageBasedBilling - ? 'bg-amber-900' - : 'bg-red-900' - : usageRatio >= USAGE_APPROACHING_THRESHOLD - ? 'bg-amber-900' - : 'bg-scale-1100' - )} - bgClass="bg-gray-300 dark:bg-gray-600" - value={usageMeta?.usage ?? 0} - max={usageMeta?.limit || 1} - /> - )} -
-
-

- Included in {subscription?.plan?.name.toLowerCase()} plan -

-

- {usageMeta?.limit === 0 ? 'Unlimited' : formatBytes(usageMeta?.limit ?? 0)} -

-
-
-

- {attribute.chartPrefix || 'Used '}in period -

-

{formatBytes(usageMeta?.usage ?? 0)}

-
- {usageMeta?.limit > 0 && ( -
-

Overage in period

-

- {usageExcess < 0 ? formatBytes(0) : formatBytes(usageExcess)} -

-
- )} -
-
-
-

{attribute.name} per day

- {attribute.chartDescription.split('\n').map((paragraph, idx) => ( -

- {paragraph} -

- ))} -
- {chartMeta[attribute.key]?.isLoading ? ( -
- - - -
- ) : chartData.length > 1 && notAllValuesZero ? ( - ChartYFormatterCompactNumber(value, attribute.unit)} - /> - ) : ( - - -
-

No data

-

- No {notAllValuesZero ? 'data' : 'usage'} in period -

-
-
-
- )} -
-
- ) - })} - + ) } diff --git a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx index 92a1cd43d0e..b52b0983efd 100644 --- a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx @@ -2,7 +2,6 @@ import { PropsWithChildren } from 'react' import { Badge, IconExternalLink } from 'ui' import { CategoryAttribute } from './Usage.constants' import Link from 'next/link' -import { ReactMarkdown } from 'react-markdown/lib/react-markdown' export interface SectionContent { section: CategoryAttribute diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index d790a670d7a..f5716aa0f4c 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -1,66 +1,19 @@ -import clsx from 'clsx' -import ShimmeringLoader from 'components/ui/ShimmeringLoader' -import SparkBar from 'components/ui/SparkBar' import { useDailyStatsQuery } from 'data/analytics/daily-stats-query' -import { - ProjectUsageResponse, - UsageMetric, - useProjectUsageQuery, -} from 'data/usage/project-usage-query' -import dayjs from 'dayjs' -import { formatBytes } from 'lib/helpers' -import Link from 'next/link' -import { Badge, Button, IconAlertTriangle, IconExternalLink } from 'ui' -import { USAGE_APPROACHING_THRESHOLD } from '../Billing.constants' -import UsageBarChart from './UsageBarChart' -import SectionContent from './SectionContent' -import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' -import { ChartYFormatterCompactNumber, getUpgradeUrlFromV2Subscription } from './Usage.utils' import { DataPoint } from 'data/analytics/constants' -import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' -import Panel from 'components/ui/Panel' -import { useMemo } from 'react' +import { ProjectSubscriptionResponse } from 'data/subscriptions/project-subscription-v2-query' +import UsageFoo from './UsageFoo' export interface SizeAndCountsProps { projectRef: string + startDate: string | undefined + endDate: string | undefined + subscription: ProjectSubscriptionResponse | undefined } -const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { - const { data: usage } = useProjectUsageQuery({ projectRef }) - const { data: subscription } = useProjectSubscriptionV2Query({ projectRef }) - const { current_period_start, current_period_end } = subscription ?? {} - - const startDate = useMemo(() => { - return current_period_start ? new Date(current_period_start * 1000).toISOString() : undefined - }, [current_period_start]) - - const endDate = useMemo(() => { - const periodEndDate = current_period_end ? new Date(current_period_end * 1000) : undefined - // If end date is in future, set end date to now - if (periodEndDate && dayjs(periodEndDate).isAfter(dayjs())) { - const yesterday = dayjs(new Date()).subtract(1, 'day') - - /** - * Currently, daily-stats data is only available a day later, so we'll use yesterday as end date, as otherwise the current day would just show up with "0" values - * - * We are actively working on removing this restriction on the data-eng/LF side and can remove this workaround once that's done - */ - const newEndDate = yesterday.isAfter(dayjs(startDate)) ? yesterday : new Date() - - // LF seems to have an issue with the milliseconds, causes infinite loading sometimes - return newEndDate.toISOString().slice(0, -5) + 'Z' - } else if (periodEndDate) { - return periodEndDate.toISOString() - } - }, [current_period_end, startDate]) - +const SizeAndCounts = ({ projectRef, startDate, endDate, subscription }: SizeAndCountsProps) => { const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'sizeCount') - const upgradeUrl = getUpgradeUrlFromV2Subscription(projectRef, subscription) - const usageBasedBilling = subscription?.usage_billing_enabled - const exceededLimitStyle = !usageBasedBilling ? 'text-red-900' : 'text-amber-900' - const { data: dbSizeData, isLoading: isLoadingDbSizeData } = useDailyStatsQuery({ projectRef, attribute: 'total_db_size_bytes', @@ -111,172 +64,12 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { if (categoryMeta === undefined) return null return ( - <> - - - {categoryMeta.attributes.map((attribute) => { - const usageMeta = usage?.[attribute.key as keyof ProjectUsageResponse] as UsageMetric - const usageRatio = - typeof usageMeta !== 'number' ? (usageMeta?.usage ?? 0) / (usageMeta?.limit ?? 0) : 0 - const usageExcess = (usageMeta?.usage ?? 0) - (usageMeta?.limit ?? 0) - - const chartData = chartMeta[attribute.key]?.data ?? [] - - const notAllValuesZero = chartData.some( - (dataPoint) => Number(dataPoint[attribute.attribute]) !== 0 - ) - - return ( -
- -
-
-
-

{attribute.name} usage

- {!usageBasedBilling && usageRatio >= 1 ? ( -
- -

Exceeded limit

-
- ) : !usageBasedBilling && usageRatio >= USAGE_APPROACHING_THRESHOLD ? ( -
- -

Approaching limit

-
- ) : null} -
- - {!usageBasedBilling && usageRatio >= USAGE_APPROACHING_THRESHOLD && ( - - - - - - )} -
- {usageMeta?.limit > 0 && ( - = 1 - ? usageBasedBilling - ? 'bg-amber-900' - : 'bg-red-900' - : usageRatio >= USAGE_APPROACHING_THRESHOLD - ? 'bg-amber-900' - : 'bg-scale-1100' - )} - bgClass="bg-gray-300 dark:bg-gray-600" - value={usageMeta?.usage ?? 0} - max={usageMeta?.limit || 1} - /> - )} -
-
-

- Included in {subscription?.plan?.name.toLowerCase()} plan -

-

- {attribute.unit === 'bytes' - ? formatBytes(usageMeta?.limit ?? 0) - : (usageMeta?.limit ?? 0).toLocaleString()} -

-
-
-

- {attribute.chartPrefix || 'Used '}in period -

-

- {attribute.unit === 'bytes' - ? formatBytes(usageMeta?.usage ?? 0) - : (usageMeta?.usage ?? 0).toLocaleString()} -

-
- {usageMeta?.limit > 0 && ( -
-

Overage in period

-

- {usageExcess < 0 - ? attribute.unit === 'bytes' - ? formatBytes(0) - : 0 - : attribute.unit === 'bytes' - ? formatBytes(usageExcess) - : usageExcess.toLocaleString()} -

-
- )} -
-
- - {attribute.key === 'db_size' && subscription?.plan?.id !== 'free' && ( -
-
-

Disk size:

-

{usage?.disk_volume_size_gb} GB

- - Auto-scaling - -
- - - - - -
- )} - -
-

- {attribute.chartPrefix || ''} - {attribute.name} per day -

- {attribute.chartDescription.split('\n').map((paragraph, idx) => ( -

- {paragraph} -

- ))} -
- {chartMeta[attribute.key].isLoading ? ( -
- - - -
- ) : chartData.length > 1 && notAllValuesZero ? ( - ChartYFormatterCompactNumber(value, attribute.unit)} - /> - ) : ( - - -
-

No data

-

- No {notAllValuesZero ? 'data' : 'usage'} in period -

-
-
-
- )} -
-
- ) - })} - + ) } diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts b/studio/components/interfaces/BillingV2/Usage/Usage.constants.tsx similarity index 88% rename from studio/components/interfaces/BillingV2/Usage/Usage.constants.ts rename to studio/components/interfaces/BillingV2/Usage/Usage.constants.tsx index 754f4a70000..4f18fead725 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.constants.tsx @@ -1,3 +1,8 @@ +import { ProjectSubscriptionResponse } from 'data/subscriptions/project-subscription-v2-query' +import { ProjectUsageData } from 'data/usage/project-usage-query' +import Link from 'next/link' +import { Badge, Button, IconExternalLink } from 'ui' + export const Y_DOMAIN_CEILING_MULTIPLIER = 4 / 3 export const USAGE_STATUS = { @@ -19,14 +24,20 @@ export interface CategoryAttribute { }[] description: string chartDescription: string + additionalInfo?: ( + subscription?: ProjectSubscriptionResponse, + usage?: ProjectUsageData + ) => JSX.Element | null } -export const USAGE_CATEGORIES: { +export interface CategoryMeta { key: 'infra' | 'bandwidth' | 'sizeCount' | 'activity' name: string description: string attributes: CategoryAttribute[] -}[] = [ +} + +export const USAGE_CATEGORIES: CategoryMeta[] = [ { key: 'infra', name: 'Infrastructure', @@ -129,6 +140,25 @@ export const USAGE_CATEGORIES: { }, ], chartDescription: 'The data refreshes every 24 hours.', + additionalInfo: (subscription?: ProjectSubscriptionResponse, usage?: ProjectUsageData) => + subscription?.plan?.id !== 'free' ? ( +
+
+

Disk size:

+

{usage?.disk_volume_size_gb} GB

+ + Auto-scaling + +
+ + + + + +
+ ) : null, }, { anchor: 'storageSize', diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index 45de5f1f433..01d1fc8f79a 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -3,7 +3,7 @@ import { useParams } from 'common' import { useProjectUsageQuery } from 'data/usage/project-usage-query' import dayjs from 'dayjs' import Link from 'next/link' -import { useRef, useState } from 'react' +import { useMemo, useRef, useState } from 'react' import { Button, IconAlertCircle, IconLoader, Listbox } from 'ui' import Activity from './Activity' import Bandwidth from './Bandwidth' @@ -32,12 +32,33 @@ const Usage = () => { }) const { current_period_start, current_period_end } = subscription ?? {} - const startDate = - current_period_start !== undefined - ? new Date(current_period_start * 1000).toISOString() - : undefined + + const startDate = useMemo(() => { + return current_period_start ? new Date(current_period_start * 1000).toISOString() : undefined + }, [current_period_start]) + const endDate = current_period_end !== undefined ? new Date(current_period_end * 1000).toISOString() : undefined + + const dailyStatsEndDate = useMemo(() => { + // If end date is in future, set end date to now + if (endDate && dayjs(endDate).isAfter(dayjs())) { + const yesterday = dayjs(new Date()).subtract(1, 'day') + + /** + * Currently, daily-stats data is only available a day later, so we'll use yesterday as end date, as otherwise the current day would just show up with "0" values + * + * We are actively working on removing this restriction on the data-eng/LF side and can remove this workaround once that's done + */ + const newEndDate = yesterday.isAfter(dayjs(startDate)) ? yesterday : new Date() + + // LF seems to have an issue with the milliseconds, causes infinite loading sometimes + return newEndDate.toISOString().slice(0, -5) + 'Z' + } else if (endDate) { + return endDate + } + }, [endDate, startDate]) + const { data: ioBudgetData } = useInfraMonitoringQuery({ projectRef: selectedProjectRef, attribute: 'disk_io_budget', @@ -166,13 +187,28 @@ const Usage = () => {
- +
- +
- +
) diff --git a/studio/components/interfaces/BillingV2/Usage/UsageFoo.tsx b/studio/components/interfaces/BillingV2/Usage/UsageFoo.tsx new file mode 100644 index 00000000000..6b3e4374484 --- /dev/null +++ b/studio/components/interfaces/BillingV2/Usage/UsageFoo.tsx @@ -0,0 +1,191 @@ +import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import SectionHeader from './SectionHeader' +import { CategoryMeta } from './Usage.constants' +import { + ProjectUsageResponse, + UsageMetric, + useProjectUsageQuery, +} from 'data/usage/project-usage-query' +import { DataPoint } from 'data/analytics/constants' +import SectionContent from './SectionContent' +import { Button, IconAlertTriangle } from 'ui' +import { USAGE_APPROACHING_THRESHOLD } from '../Billing.constants' +import Link from 'next/link' +import SparkBar from 'components/ui/SparkBar' +import clsx from 'clsx' +import { ProjectSubscriptionResponse } from 'data/subscriptions/project-subscription-v2-query' +import { ChartYFormatterCompactNumber, getUpgradeUrlFromV2Subscription } from './Usage.utils' +import { formatBytes } from 'lib/helpers' +import UsageBarChart from './UsageBarChart' +import Panel from 'components/ui/Panel' + +interface UsageFooProps { + projectRef: string + categoryMeta: CategoryMeta + subscription: ProjectSubscriptionResponse | undefined + chartMeta: { + [key: string]: { data: DataPoint[]; margin: number; isLoading: boolean; hasNoData: boolean } + } +} + +const UsageFoo = ({ projectRef, categoryMeta, chartMeta, subscription }: UsageFooProps) => { + const { data: usage } = useProjectUsageQuery({ projectRef }) + const usageBasedBilling = subscription?.usage_billing_enabled + const exceededLimitStyle = !usageBasedBilling ? 'text-red-900' : 'text-amber-900' + + const upgradeUrl = getUpgradeUrlFromV2Subscription(projectRef, subscription) + + return ( + <> + + + {categoryMeta.attributes.map((attribute) => { + const usageMeta = usage?.[attribute.key as keyof ProjectUsageResponse] as UsageMetric + const usageRatio = + typeof usageMeta !== 'number' ? (usageMeta?.usage ?? 0) / (usageMeta?.limit ?? 0) : 0 + const usageExcess = (usageMeta?.usage ?? 0) - (usageMeta?.limit ?? 0) + + const chartData = chartMeta[attribute.key]?.data ?? [] + + const notAllValuesZero = chartData.some( + (dataPoint) => Number(dataPoint[attribute.attribute]) !== 0 + ) + + return ( +
+ +
+
+
+

{attribute.name} usage

+ {!usageBasedBilling && usageRatio >= 1 ? ( +
+ +

Exceeded limit

+
+ ) : !usageBasedBilling && usageRatio >= USAGE_APPROACHING_THRESHOLD ? ( +
+ +

Approaching limit

+
+ ) : null} +
+ + {!usageBasedBilling && usageRatio >= USAGE_APPROACHING_THRESHOLD && ( + + + + + + )} +
+ {usageMeta?.limit > 0 && ( + = 1 + ? usageBasedBilling + ? 'bg-amber-900' + : 'bg-red-900' + : usageRatio >= USAGE_APPROACHING_THRESHOLD + ? 'bg-amber-900' + : 'bg-scale-1100' + )} + bgClass="bg-gray-300 dark:bg-gray-600" + value={usageMeta?.usage ?? 0} + max={usageMeta?.limit || 1} + /> + )} +
+
+

+ Included in {subscription?.plan?.name.toLowerCase()} plan +

+

+ {attribute.unit === 'bytes' + ? formatBytes(usageMeta?.limit ?? 0) + : (usageMeta?.limit ?? 0).toLocaleString()} +

+
+
+

+ {attribute.chartPrefix || 'Used '}in period +

+

+ {attribute.unit === 'bytes' + ? formatBytes(usageMeta?.usage ?? 0) + : (usageMeta?.usage ?? 0).toLocaleString()} +

+
+ {usageMeta?.limit > 0 && ( +
+

Overage in period

+

+ {usageExcess < 0 + ? attribute.unit === 'bytes' + ? formatBytes(0) + : 0 + : attribute.unit === 'bytes' + ? formatBytes(usageExcess) + : usageExcess.toLocaleString()} +

+
+ )} +
+
+ + {attribute.additionalInfo?.(subscription, usage)} + +
+

+ {attribute.chartPrefix || ''} + {attribute.name} per day +

+ {attribute.chartDescription.split('\n').map((paragraph, idx) => ( +

+ {paragraph} +

+ ))} +
+ {chartMeta[attribute.key].isLoading ? ( +
+ + + +
+ ) : chartData.length > 1 && notAllValuesZero ? ( + ChartYFormatterCompactNumber(value, attribute.unit)} + /> + ) : ( + + +
+

No data

+

+ No {notAllValuesZero ? 'data' : 'usage'} in period +

+
+
+
+ )} +
+
+ ) + })} + + ) +} + +export default UsageFoo From 23e5ad76d5fcc57b7e0d68656550c2a5c724ecc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Sun, 4 Jun 2023 15:54:50 +0200 Subject: [PATCH 7/7] cleanup --- .../interfaces/BillingV2/Usage/Activity.tsx | 11 +++-------- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 11 +++-------- .../interfaces/BillingV2/Usage/SizeAndCounts.tsx | 11 +++-------- .../interfaces/BillingV2/Usage/Usage.constants.tsx | 2 ++ .../Usage/{UsageFoo.tsx => UsageSection.tsx} | 14 +++++++++----- 5 files changed, 20 insertions(+), 29 deletions(-) rename studio/components/interfaces/BillingV2/Usage/{UsageFoo.tsx => UsageSection.tsx} (95%) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 2038079fbc7..b6bb88ef7c1 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -1,8 +1,7 @@ import { useDailyStatsQuery } from 'data/analytics/daily-stats-query' -import { USAGE_CATEGORIES } from './Usage.constants' import { DataPoint } from 'data/analytics/constants' import { ProjectSubscriptionResponse } from 'data/subscriptions/project-subscription-v2-query' -import UsageFoo from './UsageFoo' +import UsageSection from './UsageSection' export interface ActivityProps { projectRef: string @@ -12,8 +11,6 @@ export interface ActivityProps { } const Activity = ({ projectRef, subscription, startDate, endDate }: ActivityProps) => { - const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'activity') - const { data: mauData, isLoading: isLoadingMauData } = useDailyStatsQuery({ projectRef, attribute: 'total_auth_billing_period_mau', @@ -108,12 +105,10 @@ const Activity = ({ projectRef, subscription, startDate, endDate }: ActivityProp }, } - if (categoryMeta === undefined) return null - return ( - diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 9c76f21ed2e..c013dc314b0 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -1,8 +1,7 @@ import { DataPoint } from 'data/analytics/constants' import { useDailyStatsQuery } from 'data/analytics/daily-stats-query' -import { USAGE_CATEGORIES } from './Usage.constants' import { ProjectSubscriptionResponse } from 'data/subscriptions/project-subscription-v2-query' -import UsageFoo from './UsageFoo' +import UsageSection from './UsageSection' export interface BandwidthProps { projectRef: string @@ -12,8 +11,6 @@ export interface BandwidthProps { } const Bandwidth = ({ projectRef, subscription, startDate, endDate }: BandwidthProps) => { - const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'bandwidth') - const { data: dbEgressData, isLoading: isLoadingDbEgressData } = useDailyStatsQuery({ projectRef, attribute: 'total_egress_modified', @@ -47,12 +44,10 @@ const Bandwidth = ({ projectRef, subscription, startDate, endDate }: BandwidthPr }, } - if (categoryMeta === undefined) return null - return ( - diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index f5716aa0f4c..2f66c88e095 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -1,8 +1,7 @@ import { useDailyStatsQuery } from 'data/analytics/daily-stats-query' -import { USAGE_CATEGORIES } from './Usage.constants' import { DataPoint } from 'data/analytics/constants' import { ProjectSubscriptionResponse } from 'data/subscriptions/project-subscription-v2-query' -import UsageFoo from './UsageFoo' +import UsageSection from './UsageSection' export interface SizeAndCountsProps { projectRef: string @@ -12,8 +11,6 @@ export interface SizeAndCountsProps { } const SizeAndCounts = ({ projectRef, startDate, endDate, subscription }: SizeAndCountsProps) => { - const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'sizeCount') - const { data: dbSizeData, isLoading: isLoadingDbSizeData } = useDailyStatsQuery({ projectRef, attribute: 'total_db_size_bytes', @@ -61,12 +58,10 @@ const SizeAndCounts = ({ projectRef, startDate, endDate, subscription }: SizeAnd }, } - if (categoryMeta === undefined) return null - return ( - diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.constants.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.constants.tsx index 4f18fead725..c5433c5abbe 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.constants.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.constants.tsx @@ -30,6 +30,8 @@ export interface CategoryAttribute { ) => JSX.Element | null } +export type CategoryMetaKey = 'infra' | 'bandwidth' | 'sizeCount' | 'activity' + export interface CategoryMeta { key: 'infra' | 'bandwidth' | 'sizeCount' | 'activity' name: string diff --git a/studio/components/interfaces/BillingV2/Usage/UsageFoo.tsx b/studio/components/interfaces/BillingV2/Usage/UsageSection.tsx similarity index 95% rename from studio/components/interfaces/BillingV2/Usage/UsageFoo.tsx rename to studio/components/interfaces/BillingV2/Usage/UsageSection.tsx index 6b3e4374484..db512cd7ad2 100644 --- a/studio/components/interfaces/BillingV2/Usage/UsageFoo.tsx +++ b/studio/components/interfaces/BillingV2/Usage/UsageSection.tsx @@ -1,6 +1,6 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import SectionHeader from './SectionHeader' -import { CategoryMeta } from './Usage.constants' +import { CategoryMetaKey, USAGE_CATEGORIES } from './Usage.constants' import { ProjectUsageResponse, UsageMetric, @@ -19,17 +19,21 @@ import { formatBytes } from 'lib/helpers' import UsageBarChart from './UsageBarChart' import Panel from 'components/ui/Panel' -interface UsageFooProps { +interface UsageSectionProps { projectRef: string - categoryMeta: CategoryMeta + categoryKey: CategoryMetaKey subscription: ProjectSubscriptionResponse | undefined chartMeta: { [key: string]: { data: DataPoint[]; margin: number; isLoading: boolean; hasNoData: boolean } } } -const UsageFoo = ({ projectRef, categoryMeta, chartMeta, subscription }: UsageFooProps) => { +const UsageSection = ({ projectRef, categoryKey, chartMeta, subscription }: UsageSectionProps) => { const { data: usage } = useProjectUsageQuery({ projectRef }) + const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === categoryKey) + + if (!categoryMeta) return null + const usageBasedBilling = subscription?.usage_billing_enabled const exceededLimitStyle = !usageBasedBilling ? 'text-red-900' : 'text-amber-900' @@ -188,4 +192,4 @@ const UsageFoo = ({ projectRef, categoryMeta, chartMeta, subscription }: UsageFo ) } -export default UsageFoo +export default UsageSection