From 7abc50e4d64702213f434bb9a54472f7906b00cb Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 28 Apr 2023 14:33:48 +0800 Subject: [PATCH 01/45] Scaffold --- .../project/[ref]/settings/billing/usage.tsx | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/studio/pages/project/[ref]/settings/billing/usage.tsx b/studio/pages/project/[ref]/settings/billing/usage.tsx index 676a784d041..0d025264da9 100644 --- a/studio/pages/project/[ref]/settings/billing/usage.tsx +++ b/studio/pages/project/[ref]/settings/billing/usage.tsx @@ -1,10 +1,10 @@ import dayjs from 'dayjs' import { observer } from 'mobx-react-lite' -import { FC, useEffect, useState } from 'react' +import { useEffect, useState } from 'react' import { IconArrowRight, Loading } from 'ui' -import { Project, NextPageWithLayout } from 'types' -import { useStore } from 'hooks' +import { NextPageWithLayout } from 'types' +import { useStore, useFlag } from 'hooks' import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' import { STRIPE_PRODUCT_IDS, TIME_PERIODS_REPORTS, TIME_PERIODS_BILLING } from 'lib/constants' import { SettingsLayout } from 'components/layouts' @@ -14,13 +14,12 @@ import { PAYGUsage } from 'components/interfaces/Billing' import ProjectUsageBars from 'components/interfaces/Settings/ProjectUsageBars/ProjectUsageBars' const ProjectBillingUsage: NextPageWithLayout = () => { - const { ui } = useStore() - const project = ui.selectedProject + const showUsageV2 = useFlag('usageV2') return (
- +
) @@ -32,12 +31,9 @@ ProjectBillingUsage.getLayout = (page) => ( export default observer(ProjectBillingUsage) -interface SettingsProps { - project?: Project -} - -const Settings: FC = ({ project }) => { +const Settings = () => { const { ui } = useStore() + const project = ui.selectedProject const { data: subscription, From 648bda3a2c02cddf427d04325de82c91f0efd45f Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 28 Apr 2023 17:52:19 +0800 Subject: [PATCH 02/45] Add chart component for usage --- .../interfaces/BillingV2/Usage/BarChart.tsx | 62 ++++++++++ .../BillingV2/Usage/Infrastructure.tsx | 71 ++++++++++++ .../interfaces/BillingV2/Usage/Usage.tsx | 95 ++++++++++++++++ .../interfaces/BillingV2/Usage/Usage.utils.ts | 13 +++ studio/data/analytics/constants.ts | 15 +++ studio/data/analytics/daily-stats-query.ts | 107 ++++++++++++++++++ .../data/analytics/infra-monitoring-query.ts | 73 ++++++++++++ studio/data/analytics/keys.ts | 25 ++++ studio/data/projects/keys.ts | 4 + studio/data/projects/project-detail-query.ts | 43 +++++++ studio/data/projects/projects-query.ts | 37 ++++++ .../project/[ref]/settings/billing/usage.tsx | 5 +- studio/styles/ui.scss | 14 +++ 13 files changed, 563 insertions(+), 1 deletion(-) create mode 100644 studio/components/interfaces/BillingV2/Usage/BarChart.tsx create mode 100644 studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx create mode 100644 studio/components/interfaces/BillingV2/Usage/Usage.tsx create mode 100644 studio/components/interfaces/BillingV2/Usage/Usage.utils.ts create mode 100644 studio/data/analytics/constants.ts create mode 100644 studio/data/analytics/daily-stats-query.ts create mode 100644 studio/data/analytics/infra-monitoring-query.ts create mode 100644 studio/data/projects/keys.ts create mode 100644 studio/data/projects/project-detail-query.ts create mode 100644 studio/data/projects/projects-query.ts diff --git a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx new file mode 100644 index 00000000000..ee0f627ff85 --- /dev/null +++ b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx @@ -0,0 +1,62 @@ +import { DataPoint } from 'data/analytics/constants' +import { + ResponsiveContainer, + ComposedChart, + CartesianGrid, + YAxis, + XAxis, + Bar, + ReferenceLine, +} from 'recharts' + +// [Joshen] I don't think I should be replacing ChartHandler just yet as the styling of the usage charts +// are slightly different from that on the other pages, just in case. + +interface Reference { + value: number + label: string + width: number + height: number + x: number + y: number +} + +export interface BarChartProps { + data: DataPoint[] + attribute: string + reference?: Reference +} + +const BarChart = ({ data, attribute, reference }: BarChartProps) => { + return ( +
+ + + + + + + {reference && ( + +
+ {reference.label} +
+ + } + /> + )} +
+
+
+ ) +} + +export default BarChart diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx new file mode 100644 index 00000000000..7ecea76d216 --- /dev/null +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -0,0 +1,71 @@ +import { useParams } from 'common' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' +import dayjs from 'dayjs' +import { DATE_FORMAT } from 'lib/constants' +import BarChart from './BarChart' +import { generateUsageData } from './Usage.utils' + +const Infrastructure = () => { + const { ref } = useParams() + + const currentDate = 1682669326710 / 1000 + const startDate = dayjs.unix(currentDate).subtract(24, 'hour').format(DATE_FORMAT) + const endDate = dayjs.unix(currentDate).format(DATE_FORMAT) + + const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({ + projectRef: ref, + attribute: 'disk_io_budget', + startDate, + endDate, + interval: '1h', + }) + + return ( + <> +
+
+
+
+
+

IO Budget

+

Some description here

+
+
+
+
+

IO budget per day

+

+ Just FYI for now its a percentage for IO budget, which is different from what the + figma designs are (in minutes). We will need o11y or infra to help update this +

+
+ {isLoadingIoBudgetData ? ( +
+ + + +
+ ) : ( + + )} +
+
+
+
+ + ) +} + +export default Infrastructure diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx new file mode 100644 index 00000000000..45fd184500a --- /dev/null +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -0,0 +1,95 @@ +import { useParams } from 'common' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import { useProjectsQuery } from 'data/projects/projects-query' +import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' +import { useEffect, useState } from 'react' +import { Button, IconCheckCircle, Listbox } from 'ui' +import Infrastructure from './Infrastructure' + +const Usage = () => { + const { ref } = useParams() + const [selectedProjectRef, setSelectedProjectRef] = useState(ref as string) + + const { data: projects, isLoading, isSuccess } = useProjectsQuery() + const { data: subscription } = useProjectSubscriptionQuery({ projectRef: ref }) + + useEffect(() => {}, [isSuccess]) + + return ( + <> +
+
+

Usage

+ +
+
+ {/* */} + {isLoading ? ( + + ) : ( + {}} className="w-[200px]"> + {(projects ?? []).map((project) => ( + + {project.name} + + ))} + + )} + {subscription !== undefined && ( +

Project is on {subscription.tier.name}

+ )} +
+
+ + +
+
+ +
+
+ +

Infrastructure

+
+
+ +

Bandwidth

+
+
+ +

Size & Counts

+
+
+ +

Activity

+
+
+
+
+ + + +
+
+ Segment 2 +
+
+
+
+ Segment 3 +
+
+
+
+ Segment 4 +
+
+ + ) +} + +export default Usage diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts b/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts new file mode 100644 index 00000000000..9406c4fbcce --- /dev/null +++ b/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts @@ -0,0 +1,13 @@ +import { DataPoint } from 'data/analytics/constants' + +// [Joshen] This is just for development +export const generateUsageData = (attribute: string, days: number): DataPoint[] => { + const tempArray = new Array(days).fill(0) + return tempArray.map((x, idx) => { + return { + loopId: (idx + 1).toString(), + period_start: '', + [attribute]: Math.floor(Math.random() * 100).toString(), + } + }) +} diff --git a/studio/data/analytics/constants.ts b/studio/data/analytics/constants.ts new file mode 100644 index 00000000000..ef173509814 --- /dev/null +++ b/studio/data/analytics/constants.ts @@ -0,0 +1,15 @@ +export type DataPoint = { + loopId: number | string + period_start: string +} & { + // Attribute name will be the key + [key: string]: string +} + +export interface AnalyticsData { + data: DataPoint[] + format: string + total: number + totalAverage: number + yAxisLimit: number +} diff --git a/studio/data/analytics/daily-stats-query.ts b/studio/data/analytics/daily-stats-query.ts new file mode 100644 index 00000000000..70bac3c155b --- /dev/null +++ b/studio/data/analytics/daily-stats-query.ts @@ -0,0 +1,107 @@ +import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' +import { get } from 'lib/common/fetch' +import { API_URL } from 'lib/constants' +import { useCallback } from 'react' +import { analyticsKeys } from './keys' +import { AnalyticsData } from './constants' + +export type DailyStatsVariables = { + projectRef?: string + attribute?: + | 'total_realtime_egress' + | 'total_realtime_requests' + | 'total_realtime_ingress' + | 'total_rest_ingress' + | 'total_rest_egress' + | 'total_rest_requests' + | 'total_rest_get_requests' + | 'total_rest_post_requests' + | 'total_rest_patch_requests' + | 'total_rest_delete_requests' + | 'total_rest_options_requests' + | 'total_auth_billing_period_mau' + | 'total_auth_billing_period_sso_mau' + | 'total_auth_ingress' + | 'total_auth_egress' + | 'total_auth_requests' + | 'total_auth_get_requests' + | 'total_auth_post_requests' + | 'total_auth_patch_requests' + | 'total_auth_delete_requests' + | 'total_auth_options_requests' + | 'total_storage_ingress' + | 'total_storage_egress' + | 'total_storage_image_render_count' + | 'total_storage_requests' + | 'total_storage_get_requests' + | 'total_storage_post_requests' + | 'total_storage_patch_requests' + | 'total_storage_delete_requests' + | 'total_storage_options_requests' + | 'total_ingress' + | 'total_egress' + | 'total_requests' + | 'total_get_requests' + | 'total_post_requests' + | 'total_patch_requests' + | 'total_delete_requests' + | 'total_options_requests' + startDate?: string + endDate?: string + interval?: string +} + +export async function getDailyStats( + { projectRef, attribute, startDate, endDate, interval = '1d' }: DailyStatsVariables, + signal?: AbortSignal +) { + if (!projectRef) throw new Error('Project ref is required') + if (!attribute) throw new Error('Attribute is required') + if (!startDate) throw new Error('Start date is required') + if (!endDate) throw new Error('Start date is required') + + const data = await get( + `${API_URL}/projects/${projectRef}/daily-stats?attribute=${attribute}&startDate=${encodeURIComponent( + startDate + )}&endDate=${encodeURIComponent(endDate)}&interval=${interval}`, + { signal } + ) + if (data.error) throw data.error + return data as AnalyticsData +} + +export type DailyStatsData = Awaited> +export type DailyStatsError = unknown + +export const useDailyStatsQuery = ( + { projectRef, attribute, startDate, endDate, interval = '1d' }: DailyStatsVariables, + { enabled = true, ...options }: UseQueryOptions = {} +) => + useQuery( + analyticsKeys.dailyStats(projectRef, { attribute, startDate, endDate, interval }), + ({ signal }) => getDailyStats({ projectRef, attribute, startDate, endDate, interval }, signal), + { + enabled: enabled && typeof projectRef !== 'undefined', + ...options, + } + ) + +export const useDailyStatsPrefetch = ({ + projectRef, + attribute, + startDate, + endDate, + interval = '1d', +}: DailyStatsVariables) => { + const client = useQueryClient() + + return useCallback(() => { + if (projectRef && attribute && startDate && endDate && interval) { + client.prefetchQuery( + analyticsKeys.dailyStats(projectRef, { attribute, startDate, endDate, interval }), + ({ signal }) => + getDailyStats({ projectRef, attribute, startDate, endDate, interval }, signal) + ) + } + }, [projectRef]) +} diff --git a/studio/data/analytics/infra-monitoring-query.ts b/studio/data/analytics/infra-monitoring-query.ts new file mode 100644 index 00000000000..c9bdf9a42fb --- /dev/null +++ b/studio/data/analytics/infra-monitoring-query.ts @@ -0,0 +1,73 @@ +import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' +import { get } from 'lib/common/fetch' +import { API_URL } from 'lib/constants' +import { useCallback } from 'react' +import { analyticsKeys } from './keys' +import { AnalyticsData } from './constants' + +export type InfraMonitoringVariables = { + projectRef?: string + attribute?: 'cpu_usage' | 'disk_io_budget' | 'ram_usage' + startDate?: string + endDate?: string + interval?: string +} + +export async function getInfraMonitoring( + { projectRef, attribute, startDate, endDate, interval = '1d' }: InfraMonitoringVariables, + signal?: AbortSignal +) { + if (!projectRef) throw new Error('Project ref is required') + if (!attribute) throw new Error('Attribute is required') + if (!startDate) throw new Error('Start date is required') + if (!endDate) throw new Error('Start date is required') + + const data = await get( + `${API_URL}/projects/${projectRef}/infra-monitoring?attribute=${attribute}&startDate=${encodeURIComponent( + startDate + )}&endDate=${encodeURIComponent(endDate)}&interval=${interval}`, + { signal } + ) + if (data.error) throw data.error + return data as AnalyticsData +} + +export type InfraMonitoringData = Awaited> +export type InfraMonitoringError = unknown + +export const useInfraMonitoringQuery = ( + { projectRef, attribute, startDate, endDate, interval = '1d' }: InfraMonitoringVariables, + { + enabled = true, + ...options + }: UseQueryOptions = {} +) => + useQuery( + analyticsKeys.dailyStats(projectRef, { attribute, startDate, endDate, interval }), + ({ signal }) => + getInfraMonitoring({ projectRef, attribute, startDate, endDate, interval }, signal), + { + enabled: enabled && typeof projectRef !== 'undefined', + ...options, + } + ) + +export const useInfraMonitoringPrefetch = ({ + projectRef, + attribute, + startDate, + endDate, + interval = '1d', +}: InfraMonitoringVariables) => { + const client = useQueryClient() + + return useCallback(() => { + if (projectRef && attribute && startDate && endDate && interval) { + client.prefetchQuery( + analyticsKeys.dailyStats(projectRef, { attribute, startDate, endDate, interval }), + ({ signal }) => + getInfraMonitoring({ projectRef, attribute, startDate, endDate, interval }, signal) + ) + } + }, [projectRef]) +} diff --git a/studio/data/analytics/keys.ts b/studio/data/analytics/keys.ts index b360a8f7132..049d7d0a0a0 100644 --- a/studio/data/analytics/keys.ts +++ b/studio/data/analytics/keys.ts @@ -5,4 +5,29 @@ export const analyticsKeys = { projectRef: string | undefined, { interval, functionId }: { functionId: string | undefined; interval: string | undefined } ) => ['projects', projectRef, 'functions-inv-stats', { interval, functionId }] as const, + dailyStats: ( + projectRef: string | undefined, + { + attribute, + startDate, + endDate, + interval, + }: { attribute?: string; startDate?: string; endDate?: string; interval?: string } + ) => + ['projects', projectRef, 'daily-stats', { attribute, startDate, endDate, interval }] as const, + infraMonitoring: ( + projectRef: string | undefined, + { + attribute, + startDate, + endDate, + interval, + }: { attribute?: string; startDate?: string; endDate?: string; interval?: string } + ) => + [ + 'projects', + projectRef, + 'infra-monitoring', + { attribute, startDate, endDate, interval }, + ] as const, } diff --git a/studio/data/projects/keys.ts b/studio/data/projects/keys.ts new file mode 100644 index 00000000000..7a3d357e9c6 --- /dev/null +++ b/studio/data/projects/keys.ts @@ -0,0 +1,4 @@ +export const projectKeys = { + list: () => ['projects'] as const, + detail: (projectRef: string | undefined) => ['projects', projectRef, 'detail'] as const, +} diff --git a/studio/data/projects/project-detail-query.ts b/studio/data/projects/project-detail-query.ts new file mode 100644 index 00000000000..552ede409d3 --- /dev/null +++ b/studio/data/projects/project-detail-query.ts @@ -0,0 +1,43 @@ +import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' +import { get } from 'lib/common/fetch' +import { API_URL } from 'lib/constants' +import { useCallback } from 'react' +import { Project } from 'types' +import { projectKeys } from './keys' + +export type ProjectDetailVariables = { ref?: string } + +export async function getProjectDetail({ ref }: ProjectDetailVariables, signal?: AbortSignal) { + if (!ref) throw new Error('Project ref is required') + const data = await get(`${API_URL}/projects/${ref}`, { signal }) + if (data.error) throw data.error + return data as Project +} + +export type ProjectDetailData = Awaited> +export type ProjectDetailError = unknown + +export const useProjectDetailQuery = ( + { ref }: ProjectDetailVariables, + { enabled = true, ...options }: UseQueryOptions = {} +) => + useQuery( + projectKeys.detail(ref), + ({ signal }) => getProjectDetail({ ref }, signal), + { + enabled: enabled && typeof ref !== 'undefined', + ...options, + } + ) + +export const useProjectDetailPrefetch = ({ ref }: ProjectDetailVariables) => { + const client = useQueryClient() + + return useCallback(() => { + if (ref) { + client.prefetchQuery(projectKeys.detail(ref), ({ signal }) => + getProjectDetail({ ref }, signal) + ) + } + }, [ref]) +} diff --git a/studio/data/projects/projects-query.ts b/studio/data/projects/projects-query.ts new file mode 100644 index 00000000000..a60dc03b378 --- /dev/null +++ b/studio/data/projects/projects-query.ts @@ -0,0 +1,37 @@ +import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' +import { get } from 'lib/common/fetch' +import { API_URL } from 'lib/constants' +import { useCallback } from 'react' +import { Project } from 'types' +import { projectKeys } from './keys' + +export type ProjectsVariables = { + ref?: string +} + +export async function getProjects(signal?: AbortSignal) { + const data = await get(`${API_URL}/projects`, { signal }) + if (data.error) throw data.error + return data as Project[] +} + +export type OrganizationRolesData = Awaited> +export type OrganizationRolesError = unknown + +export const useProjectsQuery = ({ + enabled = true, + ...options +}: UseQueryOptions = {}) => + useQuery( + projectKeys.list(), + ({ signal }) => getProjects(signal), + { enabled: enabled, ...options } + ) + +export const useProjectsPrefetch = () => { + const client = useQueryClient() + + return useCallback(() => { + client.prefetchQuery(projectKeys.list(), ({ signal }) => getProjects(signal)) + }, []) +} diff --git a/studio/pages/project/[ref]/settings/billing/usage.tsx b/studio/pages/project/[ref]/settings/billing/usage.tsx index 0d025264da9..94d6c7d5db0 100644 --- a/studio/pages/project/[ref]/settings/billing/usage.tsx +++ b/studio/pages/project/[ref]/settings/billing/usage.tsx @@ -12,9 +12,12 @@ import LoadingUI from 'components/ui/Loading' import DateRangePicker from 'components/to-be-cleaned/DateRangePicker' import { PAYGUsage } from 'components/interfaces/Billing' import ProjectUsageBars from 'components/interfaces/Settings/ProjectUsageBars/ProjectUsageBars' +import Usage from 'components/interfaces/BillingV2/Usage/Usage' const ProjectBillingUsage: NextPageWithLayout = () => { - const showUsageV2 = useFlag('usageV2') + const showNewUsageUI = useFlag('usagev2') + + if (showNewUsageUI) return return (
diff --git a/studio/styles/ui.scss b/studio/styles/ui.scss index 12e09fa2017..54b9fc584d4 100644 --- a/studio/styles/ui.scss +++ b/studio/styles/ui.scss @@ -438,3 +438,17 @@ @apply stroke-green-500; } } + +.recharts-cartesian-axis-tick { + text { + @apply text-xs; + } +} +.recharts-reference-line { + line { + @apply stroke-amber-900; + } + text { + @apply text-xs fill-amber-900 bg-amber-600; + } +} \ No newline at end of file From b792c5afcb723567aad08594f01daab708a34be3 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 2 May 2023 15:51:23 +0800 Subject: [PATCH 03/45] Add charts for CPU and memory usage --- .../interfaces/BillingV2/Usage/Activity.tsx | 79 ++++++++++++++++ .../interfaces/BillingV2/Usage/Bandwidth.tsx | 79 ++++++++++++++++ .../interfaces/BillingV2/Usage/BarChart.tsx | 35 +++++-- .../BillingV2/Usage/Infrastructure.tsx | 94 ++++++++++++++----- .../BillingV2/Usage/SizeAndCounts.tsx | 79 ++++++++++++++++ .../interfaces/BillingV2/Usage/Usage.tsx | 22 ++--- .../interfaces/BillingV2/Usage/Usage.utils.ts | 2 +- studio/data/analytics/daily-stats-query.ts | 10 +- .../data/analytics/infra-monitoring-query.ts | 38 +++++++- studio/styles/ui.scss | 5 +- 10 files changed, 385 insertions(+), 58 deletions(-) create mode 100644 studio/components/interfaces/BillingV2/Usage/Activity.tsx create mode 100644 studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx create mode 100644 studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx new file mode 100644 index 00000000000..fddd1725ad4 --- /dev/null +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -0,0 +1,79 @@ +import { useParams } from 'common' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' +import dayjs from 'dayjs' +import { DATE_FORMAT } from 'lib/constants' +import BarChart from './BarChart' +import { generateUsageData } from './Usage.utils' + +const Activity = () => { + const { ref } = useParams() + + const currentDate = 1682669326710 / 1000 + const startDate = dayjs.unix(currentDate).subtract(24, 'hour').format(DATE_FORMAT) + const endDate = dayjs.unix(currentDate).format(DATE_FORMAT) + + const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({ + projectRef: ref, + attribute: 'disk_io_budget', + startDate, + endDate, + interval: '1h', + }) + + return ( + <> +
+
+
+

Activity

+

Some description here

+
+
+
+
+
+
+
+
+

IO Budget

+

Some description here

+
+
+
+
+

IO budget per day

+

+ Just FYI for now its a percentage for IO budget, which is different from what the + figma designs are (in minutes). We will need o11y or infra to help update this +

+
+ {isLoadingIoBudgetData ? ( +
+ + + +
+ ) : ( + + )} +
+
+
+
+ + ) +} + +export default Activity diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx new file mode 100644 index 00000000000..835dcd19e03 --- /dev/null +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -0,0 +1,79 @@ +import { useParams } from 'common' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' +import dayjs from 'dayjs' +import { DATE_FORMAT } from 'lib/constants' +import BarChart from './BarChart' +import { generateUsageData } from './Usage.utils' + +const Bandwidth = () => { + const { ref } = useParams() + + const currentDate = 1682669326710 / 1000 + const startDate = dayjs.unix(currentDate).subtract(24, 'hour').format(DATE_FORMAT) + const endDate = dayjs.unix(currentDate).format(DATE_FORMAT) + + const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({ + projectRef: ref, + attribute: 'disk_io_budget', + startDate, + endDate, + interval: '1h', + }) + + return ( + <> +
+
+
+

Bandwidth

+

Some description here

+
+
+
+
+
+
+
+
+

IO Budget

+

Some description here

+
+
+
+
+

IO budget per day

+

+ Just FYI for now its a percentage for IO budget, which is different from what the + figma designs are (in minutes). We will need o11y or infra to help update this +

+
+ {isLoadingIoBudgetData ? ( +
+ + + +
+ ) : ( + + )} +
+
+
+
+ + ) +} + +export default Bandwidth diff --git a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx index ee0f627ff85..c332818ce47 100644 --- a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx +++ b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx @@ -7,6 +7,7 @@ import { XAxis, Bar, ReferenceLine, + Cell, } from 'recharts' // [Joshen] I don't think I should be replacing ChartHandler just yet as the styling of the usage charts @@ -25,17 +26,37 @@ export interface BarChartProps { data: DataPoint[] attribute: string reference?: Reference + unit?: string + yDomain?: number[] } -const BarChart = ({ data, attribute, reference }: BarChartProps) => { +const BarChart = ({ data, attribute, reference, unit, yDomain }: BarChartProps) => { return (
- - - - - + + + + + + {data.map((entry) => { + return ( + = reference.value + ? 'fill-amber-900' + : 'fill-scale-1200' + } + /> + ) + })} + {reference && ( { width={reference.width} height={reference.height} > -
+
{reference.label}
diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 7ecea76d216..f5f0f6b5a0c 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -1,46 +1,62 @@ import { useParams } from 'common' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' +import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' import dayjs from 'dayjs' import { DATE_FORMAT } from 'lib/constants' import BarChart from './BarChart' -import { generateUsageData } from './Usage.utils' const Infrastructure = () => { const { ref } = useParams() + const { data: subscription } = useProjectSubscriptionQuery({ projectRef: ref }) + const { current_period_start, current_period_end } = subscription?.billing ?? {} + const startDate = new Date((current_period_start ?? 0) * 1000).toISOString() + const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() - const currentDate = 1682669326710 / 1000 - const startDate = dayjs.unix(currentDate).subtract(24, 'hour').format(DATE_FORMAT) - const endDate = dayjs.unix(currentDate).format(DATE_FORMAT) - - const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({ + const { data: cpuUsageData, isLoading: isLoadingCpuUsageData } = useInfraMonitoringQuery({ projectRef: ref, - attribute: 'disk_io_budget', + attribute: 'cpu_usage', startDate, endDate, - interval: '1h', + interval: '1d', + modifier: (x: number) => x * 100, + }) + + const { data: memoryUsageData, isLoading: isLoadingMemoryUsageData } = useInfraMonitoringQuery({ + projectRef: ref, + attribute: 'ram_usage', + startDate, + endDate, + interval: '1d', }) return ( <> +
+
+
+

Infrastructure

+

Some description here

+
+
+
+ + {/* CPU USAGE */}
-

IO Budget

+

CPU

Some description here

-

IO budget per day

-

- Just FYI for now its a percentage for IO budget, which is different from what the - figma designs are (in minutes). We will need o11y or infra to help update this -

+

Max CPU usage each day

+

Some description here

- {isLoadingIoBudgetData ? ( + {isLoadingCpuUsageData ? (
@@ -48,16 +64,44 @@ const Infrastructure = () => {
) : ( + )} +
+
+
+
+ + {/* MEMORY USAGE */} +
+
+
+
+
+

Memory

+

Some description here

+
+
+
+
+

Max memory usage each day

+

Some description here

+
+ {isLoadingMemoryUsageData ? ( +
+ + + +
+ ) : ( + )}
diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx new file mode 100644 index 00000000000..00834ccb337 --- /dev/null +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -0,0 +1,79 @@ +import { useParams } from 'common' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' +import dayjs from 'dayjs' +import { DATE_FORMAT } from 'lib/constants' +import BarChart from './BarChart' +import { generateUsageData } from './Usage.utils' + +const SizeAndCounts = () => { + const { ref } = useParams() + + const currentDate = 1682669326710 / 1000 + const startDate = dayjs.unix(currentDate).subtract(24, 'hour').format(DATE_FORMAT) + const endDate = dayjs.unix(currentDate).format(DATE_FORMAT) + + const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({ + projectRef: ref, + attribute: 'disk_io_budget', + startDate, + endDate, + interval: '1h', + }) + + return ( + <> +
+
+
+

Size & Counts

+

Some description here

+
+
+
+
+
+
+
+
+

IO Budget

+

Some description here

+
+
+
+
+

IO budget per day

+

+ Just FYI for now its a percentage for IO budget, which is different from what the + figma designs are (in minutes). We will need o11y or infra to help update this +

+
+ {isLoadingIoBudgetData ? ( +
+ + + +
+ ) : ( + + )} +
+
+
+
+ + ) +} + +export default SizeAndCounts diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index 45fd184500a..956b48669ff 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -5,6 +5,9 @@ import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscrip import { useEffect, useState } from 'react' import { Button, IconCheckCircle, Listbox } from 'ui' import Infrastructure from './Infrastructure' +import Bandwidth from './Bandwidth' +import SizeAndCounts from './SizeAndCounts' +import Activity from './Activity' const Usage = () => { const { ref } = useParams() @@ -72,22 +75,9 @@ const Usage = () => {
- -
-
- Segment 2 -
-
-
-
- Segment 3 -
-
-
-
- Segment 4 -
-
+ {/* + + */} ) } diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts b/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts index 9406c4fbcce..b795682583b 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts @@ -6,7 +6,7 @@ export const generateUsageData = (attribute: string, days: number): DataPoint[] return tempArray.map((x, idx) => { return { loopId: (idx + 1).toString(), - period_start: '', + period_start: `${idx + 1}`, [attribute]: Math.floor(Math.random() * 100).toString(), } }) diff --git a/studio/data/analytics/daily-stats-query.ts b/studio/data/analytics/daily-stats-query.ts index 70bac3c155b..2655fecdec8 100644 --- a/studio/data/analytics/daily-stats-query.ts +++ b/studio/data/analytics/daily-stats-query.ts @@ -49,10 +49,18 @@ export type DailyStatsVariables = { startDate?: string endDate?: string interval?: string + dateFormat?: string } export async function getDailyStats( - { projectRef, attribute, startDate, endDate, interval = '1d' }: DailyStatsVariables, + { + projectRef, + attribute, + startDate, + endDate, + interval = '1d', + dateFormat = 'DD MMM', + }: DailyStatsVariables, signal?: AbortSignal ) { if (!projectRef) throw new Error('Project ref is required') diff --git a/studio/data/analytics/infra-monitoring-query.ts b/studio/data/analytics/infra-monitoring-query.ts index c9bdf9a42fb..cf0323ebfba 100644 --- a/studio/data/analytics/infra-monitoring-query.ts +++ b/studio/data/analytics/infra-monitoring-query.ts @@ -4,13 +4,16 @@ import { API_URL } from 'lib/constants' import { useCallback } from 'react' import { analyticsKeys } from './keys' import { AnalyticsData } from './constants' +import dayjs from 'dayjs' export type InfraMonitoringVariables = { projectRef?: string - attribute?: 'cpu_usage' | 'disk_io_budget' | 'ram_usage' + attribute: 'cpu_usage' | 'disk_io_budget' | 'ram_usage' startDate?: string endDate?: string interval?: string + dateFormat?: string + modifier?: (x: number) => number } export async function getInfraMonitoring( @@ -20,7 +23,7 @@ export async function getInfraMonitoring( if (!projectRef) throw new Error('Project ref is required') if (!attribute) throw new Error('Attribute is required') if (!startDate) throw new Error('Start date is required') - if (!endDate) throw new Error('Start date is required') + if (!endDate) throw new Error('End date is required') const data = await get( `${API_URL}/projects/${projectRef}/infra-monitoring?attribute=${attribute}&startDate=${encodeURIComponent( @@ -36,7 +39,15 @@ export type InfraMonitoringData = Awaited> export type InfraMonitoringError = unknown export const useInfraMonitoringQuery = ( - { projectRef, attribute, startDate, endDate, interval = '1d' }: InfraMonitoringVariables, + { + projectRef, + attribute, + startDate, + endDate, + interval = '1d', + dateFormat = 'DD MMM', + modifier, + }: InfraMonitoringVariables, { enabled = true, ...options @@ -47,7 +58,26 @@ export const useInfraMonitoringQuery = ( ({ signal }) => getInfraMonitoring({ projectRef, attribute, startDate, endDate, interval }, signal), { - enabled: enabled && typeof projectRef !== 'undefined', + enabled: + enabled && + typeof projectRef !== 'undefined' && + typeof attribute !== 'undefined' && + typeof startDate !== 'undefined' && + typeof endDate !== 'undefined', + // @ts-ignore + select(data) { + return { + ...data, + data: data.data.map((x) => { + return { + ...x, + [attribute]: + modifier !== undefined ? modifier(Number(x[attribute])) : Number(x[attribute]), + period_start: dayjs(x.period_start).format(dateFormat), + } + }), + } + }, ...options, } ) diff --git a/studio/styles/ui.scss b/studio/styles/ui.scss index 54b9fc584d4..e080e04ecde 100644 --- a/studio/styles/ui.scss +++ b/studio/styles/ui.scss @@ -446,9 +446,6 @@ } .recharts-reference-line { line { - @apply stroke-amber-900; - } - text { - @apply text-xs fill-amber-900 bg-amber-600; + @apply stroke-scale-1000; } } \ No newline at end of file From 940dfc8a4485b13b81f0faed253b17fcee885bc7 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 2 May 2023 16:36:52 +0800 Subject: [PATCH 04/45] Add charts for db and storage egress --- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 168 +++++++++++++++--- .../BillingV2/Usage/Infrastructure.tsx | 46 ++++- .../interfaces/BillingV2/Usage/Usage.tsx | 6 +- studio/data/analytics/daily-stats-query.ts | 47 +++-- 4 files changed, 221 insertions(+), 46 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 835dcd19e03..e14131babe0 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -1,24 +1,39 @@ import { useParams } from 'common' import ShimmeringLoader from 'components/ui/ShimmeringLoader' -import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' -import dayjs from 'dayjs' -import { DATE_FORMAT } from 'lib/constants' +import { useDailyStatsQuery } from 'data/analytics/daily-stats-query' +import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' +import { useProjectUsageQuery } from 'data/usage/project-usage-query' import BarChart from './BarChart' -import { generateUsageData } from './Usage.utils' +import { Button } from 'ui' +import SparkBar from 'components/ui/SparkBar' +import { formatBytes } from 'lib/helpers' const Bandwidth = () => { const { ref } = useParams() + const { data: usage } = useProjectUsageQuery({ projectRef: ref }) + const { data: subscription } = useProjectSubscriptionQuery({ projectRef: ref }) + const { current_period_start, current_period_end } = subscription?.billing ?? {} + const startDate = new Date((current_period_start ?? 0) * 1000).toISOString() + const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() - const currentDate = 1682669326710 / 1000 - const startDate = dayjs.unix(currentDate).subtract(24, 'hour').format(DATE_FORMAT) - const endDate = dayjs.unix(currentDate).format(DATE_FORMAT) - - const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({ + const { db_egress } = usage ?? {} + const dbEgressExcess = (db_egress?.usage ?? 0) - (db_egress?.limit ?? 0) + const { data: dbEgressData, isLoading: isLoadingDbEgressData } = useDailyStatsQuery({ projectRef: ref, - attribute: 'disk_io_budget', + attribute: 'total_rest_egress', + interval: '1d', + startDate, + endDate, + }) + + const { storage_egress } = usage ?? {} + const storageEgressExcess = (storage_egress?.usage ?? 0) - (storage_egress?.limit ?? 0) + const { data: storageEgressData, isLoading: isLoadingStorageEgressData } = useDailyStatsQuery({ + projectRef: ref, + attribute: 'total_storage_egress', + interval: '1d', startDate, endDate, - interval: '1h', }) return ( @@ -31,24 +46,57 @@ const Bandwidth = () => {
+ + {/* DATABASE EGRESS - need to fix if no value yet (API will return period_start as 0 in first data point) */}
-

IO Budget

+

Database Egress

Some description here

-
-

IO budget per day

-

- Just FYI for now its a percentage for IO budget, which is different from what the - figma designs are (in minutes). We will need o11y or infra to help update this -

+
+
+

+ Egress {subscription?.tier.key.toLowerCase()} quota usage +

+ +
+ +
+
+

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

+

{formatBytes(db_egress?.limit ?? 0)}

+
+
+

Used

+

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

+
+
+

Extra volume used this month

+

+ {dbEgressExcess < 0 ? formatBytes(0) : formatBytes(dbEgressExcess)} +

+
+
- {isLoadingIoBudgetData ? ( +
+

Database egress over time

+

Some description here

+
+ {isLoadingDbEgressData ? (
@@ -56,16 +104,78 @@ const Bandwidth = () => {
) : ( + )} +
+
+
+
+ + {/* STORAGE EGRESS - need to fix if no value yet (API will return period_start as 0 in first data point) */} +
+
+
+
+
+

Storage Egress

+

Some description here

+
+
+
+
+
+

+ Storage egress {subscription?.tier.key.toLowerCase()} quota usage +

+ +
+ +
+
+

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

+

{formatBytes(storage_egress?.limit ?? 0)}

+
+
+

Used

+

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

+
+
+

Extra volume used this month

+

+ {storageEgressExcess < 0 ? formatBytes(0) : formatBytes(storageEgressExcess)} +

+
+
+
+
+

Storage egress over time

+

Some description here

+
+ {isLoadingStorageEgressData ? ( +
+ + + +
+ ) : ( + )}
diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index f5f0f6b5a0c..6614c10dc09 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -2,8 +2,6 @@ import { useParams } from 'common' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' -import dayjs from 'dayjs' -import { DATE_FORMAT } from 'lib/constants' import BarChart from './BarChart' const Infrastructure = () => { @@ -16,18 +14,26 @@ const Infrastructure = () => { const { data: cpuUsageData, isLoading: isLoadingCpuUsageData } = useInfraMonitoringQuery({ projectRef: ref, attribute: 'cpu_usage', + interval: '1d', startDate, endDate, - interval: '1d', modifier: (x: number) => x * 100, }) const { data: memoryUsageData, isLoading: isLoadingMemoryUsageData } = useInfraMonitoringQuery({ projectRef: ref, attribute: 'ram_usage', + interval: '1d', startDate, endDate, + }) + + const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({ + projectRef: ref, + attribute: 'disk_io_budget', interval: '1d', + startDate, + endDate, }) return ( @@ -108,6 +114,40 @@ const Infrastructure = () => {
+ + {/* DISK IO BUDGET */} +
+
+
+
+
+

IO Budget

+

Some description here

+
+
+
+
+

Disk IO budget per day

+

Some description here

+
+ {isLoadingIoBudgetData ? ( +
+ + + +
+ ) : ( + + )} +
+
+
+
) } diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index 956b48669ff..6cbfa0815ba 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -75,9 +75,9 @@ const Usage = () => {
- {/* - - */} + + {/* */} + {/* */} ) } diff --git a/studio/data/analytics/daily-stats-query.ts b/studio/data/analytics/daily-stats-query.ts index 2655fecdec8..c8032574f88 100644 --- a/studio/data/analytics/daily-stats-query.ts +++ b/studio/data/analytics/daily-stats-query.ts @@ -4,10 +4,11 @@ import { API_URL } from 'lib/constants' import { useCallback } from 'react' import { analyticsKeys } from './keys' import { AnalyticsData } from './constants' +import dayjs from 'dayjs' export type DailyStatsVariables = { projectRef?: string - attribute?: + attribute: | 'total_realtime_egress' | 'total_realtime_requests' | 'total_realtime_ingress' @@ -50,17 +51,11 @@ export type DailyStatsVariables = { endDate?: string interval?: string dateFormat?: string + modifier?: (x: number) => number } export async function getDailyStats( - { - projectRef, - attribute, - startDate, - endDate, - interval = '1d', - dateFormat = 'DD MMM', - }: DailyStatsVariables, + { projectRef, attribute, startDate, endDate, interval = '1d' }: DailyStatsVariables, signal?: AbortSignal ) { if (!projectRef) throw new Error('Project ref is required') @@ -82,14 +77,44 @@ export type DailyStatsData = Awaited> export type DailyStatsError = unknown export const useDailyStatsQuery = ( - { projectRef, attribute, startDate, endDate, interval = '1d' }: DailyStatsVariables, + { + projectRef, + attribute, + startDate, + endDate, + interval = '1d', + dateFormat, + modifier, + }: DailyStatsVariables, { enabled = true, ...options }: UseQueryOptions = {} ) => useQuery( analyticsKeys.dailyStats(projectRef, { attribute, startDate, endDate, interval }), ({ signal }) => getDailyStats({ projectRef, attribute, startDate, endDate, interval }, signal), { - enabled: enabled && typeof projectRef !== 'undefined', + enabled: + enabled && + typeof projectRef !== 'undefined' && + typeof attribute !== 'undefined' && + typeof startDate !== 'undefined' && + typeof endDate !== 'undefined', + // @ts-ignore + select(data) { + return { + ...data, + data: data.data.map((x) => { + return { + ...x, + [attribute]: + modifier !== undefined ? modifier(Number(x[attribute])) : Number(x[attribute]), + period_start: + Number(x.period_start) > 0 + ? dayjs(x.period_start).format(dateFormat) + : x.period_start, + } + }), + } + }, ...options, } ) From 72cbbc11a68e6584083b753289adcaf58310bf65 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 2 May 2023 16:47:29 +0800 Subject: [PATCH 05/45] Add scaffolding for db and storage size charts --- .../BillingV2/Usage/SizeAndCounts.tsx | 168 +++++++++++++++--- .../interfaces/BillingV2/Usage/Usage.tsx | 2 +- 2 files changed, 140 insertions(+), 30 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index 00834ccb337..d5759b8660b 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -1,24 +1,41 @@ import { useParams } from 'common' import ShimmeringLoader from 'components/ui/ShimmeringLoader' -import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' -import dayjs from 'dayjs' -import { DATE_FORMAT } from 'lib/constants' import BarChart from './BarChart' -import { generateUsageData } from './Usage.utils' +import { useDailyStatsQuery } from 'data/analytics/daily-stats-query' +import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' +import { useProjectUsageQuery } from 'data/usage/project-usage-query' +import SparkBar from 'components/ui/SparkBar' +import { formatBytes } from 'lib/helpers' +import { Button } from 'ui' const SizeAndCounts = () => { const { ref } = useParams() + const { data: usage } = useProjectUsageQuery({ projectRef: ref }) + const { data: subscription } = useProjectSubscriptionQuery({ projectRef: ref }) + const { current_period_start, current_period_end } = subscription?.billing ?? {} + const startDate = new Date((current_period_start ?? 0) * 1000).toISOString() + const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() - const currentDate = 1682669326710 / 1000 - const startDate = dayjs.unix(currentDate).subtract(24, 'hour').format(DATE_FORMAT) - const endDate = dayjs.unix(currentDate).format(DATE_FORMAT) - - const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({ + // [JOSHEN TODO] Attribute needs to change after confirming with team o11y if this is implemented + const { db_size } = usage ?? {} + const dbSizeExcess = (db_size?.usage ?? 0) - (db_size?.limit ?? 0) + const { data: dbSizeData, isLoading: isLoadingDbSizeData } = useDailyStatsQuery({ projectRef: ref, - attribute: 'disk_io_budget', + attribute: 'total_rest_egress', + interval: '1d', + startDate, + endDate, + }) + + // [JOSHEN TODO] Attribute needs to change after confirming with team o11y if this is implemented + const { storage_size } = usage ?? {} + const storageSizeExcess = (storage_size?.usage ?? 0) - (storage_size?.limit ?? 0) + const { data: storageSizeData, isLoading: isLoadingStorageSizeData } = useDailyStatsQuery({ + projectRef: ref, + attribute: 'total_storage_egress', + interval: '1d', startDate, endDate, - interval: '1h', }) return ( @@ -31,24 +48,55 @@ const SizeAndCounts = () => {
+ + {/* DATABASE SIZE - need to fix if no value yet (API will return period_start as 0 in first data point) */}
-

IO Budget

+

Database size

Some description here

-
-

IO budget per day

-

- Just FYI for now its a percentage for IO budget, which is different from what the - figma designs are (in minutes). We will need o11y or infra to help update this -

+
+
+

Database size per day

+ +
+ +
+
+

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

+

{formatBytes(db_size?.limit ?? 0)}

+
+
+

Used

+

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

+
+
+

Extra volume used this month

+

+ {dbSizeExcess < 0 ? formatBytes(0) : formatBytes(dbSizeExcess)} +

+
+
- {isLoadingIoBudgetData ? ( +
+

Database egress over time

+

Some description here

+
+ {isLoadingDbSizeData ? (
@@ -56,16 +104,78 @@ const SizeAndCounts = () => {
) : ( + )} +
+
+
+
+ + {/* STORAGE SIZE - need to fix if no value yet (API will return period_start as 0 in first data point) */} +
+
+
+
+
+

Storage size

+

Some description here

+
+
+
+
+
+

+ Storage {subscription?.tier.key.toLowerCase()} quota usage +

+ +
+ +
+
+

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

+

{formatBytes(storage_size?.limit ?? 0)}

+
+
+

Used

+

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

+
+
+

Extra volume used this month

+

+ {storageSizeExcess < 0 ? formatBytes(0) : formatBytes(storageSizeExcess)} +

+
+
+
+
+

Storage size over time

+

Some description here

+
+ {isLoadingStorageSizeData ? ( +
+ + + +
+ ) : ( + )}
diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index 6cbfa0815ba..4f37e49062c 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -76,7 +76,7 @@ const Usage = () => { - {/* */} + {/* */} ) From e31d65a14721212deb723298f4308be450c0e5f5 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 2 May 2023 17:10:28 +0800 Subject: [PATCH 06/45] Add scaffolding for mau, asset transformations and edge function invocations charts --- .../interfaces/BillingV2/Usage/Activity.tsx | 234 ++++++++++++++++-- .../BillingV2/Usage/Infrastructure.tsx | 1 + .../interfaces/BillingV2/Usage/Usage.tsx | 2 +- studio/data/usage/project-usage-query.ts | 48 +--- 4 files changed, 230 insertions(+), 55 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index fddd1725ad4..3393afd42d1 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -1,26 +1,40 @@ import { useParams } from 'common' import ShimmeringLoader from 'components/ui/ShimmeringLoader' -import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' -import dayjs from 'dayjs' -import { DATE_FORMAT } from 'lib/constants' +import { useDailyStatsQuery } from 'data/analytics/daily-stats-query' +import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' +import { useProjectUsageQuery } from 'data/usage/project-usage-query' import BarChart from './BarChart' +import SparkBar from 'components/ui/SparkBar' +import { formatBytes } from 'lib/helpers' +import { Button } from 'ui' import { generateUsageData } from './Usage.utils' const Activity = () => { const { ref } = useParams() + const { data: usage } = useProjectUsageQuery({ projectRef: ref }) + const { data: subscription } = useProjectSubscriptionQuery({ projectRef: ref }) + const { current_period_start, current_period_end } = subscription?.billing ?? {} + const startDate = new Date((current_period_start ?? 0) * 1000).toISOString() + const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() - const currentDate = 1682669326710 / 1000 - const startDate = dayjs.unix(currentDate).subtract(24, 'hour').format(DATE_FORMAT) - const endDate = dayjs.unix(currentDate).format(DATE_FORMAT) - - const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({ + // [JOSHEN TODO] Attribute needs to change after confirming with team o11y if this is implemented + const { monthly_active_users } = usage ?? {} + const mauExcess = (monthly_active_users?.usage ?? 0) - (monthly_active_users?.limit ?? 0) + const { data: mauData, isLoading: isLoadingMauData } = useDailyStatsQuery({ projectRef: ref, - attribute: 'disk_io_budget', + attribute: 'total_auth_billing_period_mau', + interval: '1d', startDate, endDate, - interval: '1h', }) + const { storage_image_render_count } = usage ?? {} + const assetTransformationExcess = + (storage_image_render_count?.usage ?? 0) - (storage_image_render_count?.limit ?? 0) + + const { func_invocations } = usage ?? {} + const funcInvocationsExcess = (func_invocations?.usage ?? 0) - (func_invocations?.limit ?? 0) + return ( <>
@@ -31,24 +45,130 @@ const Activity = () => {
+ + {/* MONTHLY ACTIVE USERS - need to fix if no value yet (API will return period_start as 0 in first data point) */}
-

IO Budget

+

Monthly Active Users (MAU)

Some description here

-
-

IO budget per day

-

- Just FYI for now its a percentage for IO budget, which is different from what the - figma designs are (in minutes). We will need o11y or infra to help update this -

+
+
+

+ Monthly active users {subscription?.tier.key.toLowerCase()} quota usage +

+ +
+ +
+
+

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

+

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

+
+
+

Used

+

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

+
+
+

Extra volume used this month

+

{(mauExcess < 0 ? 0 : mauExcess).toLocaleString()}

+
+
- {isLoadingIoBudgetData ? ( +
+

Monthly active users over time

+

Some description here

+
+ {isLoadingMauData ? ( +
+ + + +
+ ) : ( + + )} +
+
+
+
+ + {/* ASSET TRANSFORMATIONS - need to show actual data */} +
+
+
+
+
+

Asset Transformations

+

Some description here

+
+
+
+
+
+

+ Asset transformations {subscription?.tier.key.toLowerCase()} quota usage +

+ +
+ +
+
+

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

+

+ {(storage_image_render_count?.limit ?? 0).toLocaleString()} +

+
+
+

Used

+

+ {(storage_image_render_count?.usage ?? 0).toLocaleString()} +

+
+
+

Extra volume used this month

+

+ {(assetTransformationExcess < 0 + ? 0 + : assetTransformationExcess + ).toLocaleString()} +

+
+
+
+
+

Asset transformations over time

+

Some description here

+
+ {isLoadingMauData ? (
@@ -58,6 +178,84 @@ const Activity = () => { + )} +
+
+
+
+ + {/* EDGE FUNCTIONS INVOCATIONS - need to show actual data */} +
+
+
+
+
+

Edge Function Invocations

+

Some description here

+
+
+
+
+
+

+ Edge function invocations {subscription?.tier.key.toLowerCase()} quota usage +

+ +
+ +
+
+

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

+

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

+
+
+

Used

+

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

+
+
+

Extra volume used this month

+

+ {(funcInvocationsExcess < 0 ? 0 : funcInvocationsExcess).toLocaleString()} +

+
+
+
+
+

Edge function invocations over time

+

Some description here

+
+ {isLoadingMauData ? ( +
+ + + +
+ ) : ( + { const { ref } = useParams() diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index 4f37e49062c..7fd770efb23 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -77,7 +77,7 @@ const Usage = () => { - {/* */} + ) } diff --git a/studio/data/usage/project-usage-query.ts b/studio/data/usage/project-usage-query.ts index dca03ce536f..e42fc0ab3e9 100644 --- a/studio/data/usage/project-usage-query.ts +++ b/studio/data/usage/project-usage-query.ts @@ -8,37 +8,7 @@ export type ProjectUsageVariables = { projectRef?: string } -export interface DbSize { - usage: number | null - limit: number - cost: number - current: number - available_in_plan: boolean -} - -export interface DbEgress { - usage: number - limit: number - cost: number - available_in_plan: boolean -} - -export interface StorageSize { - usage: number | null - limit: number - cost: number - current: number - available_in_plan: boolean -} - -export interface StorageEgress { - usage: number - limit: number - cost: number - available_in_plan: boolean -} - -export interface MonthlyActiveUsers { +export interface UsageMetric { usage: number limit: number cost: number @@ -46,11 +16,17 @@ export interface MonthlyActiveUsers { } export type ProjectUsageResponse = { - db_size: DbSize - db_egress: DbEgress - storage_size: StorageSize - storage_egress: StorageEgress - monthly_active_users: MonthlyActiveUsers + db_size: UsageMetric + db_egress: UsageMetric + storage_size: UsageMetric + storage_egress: UsageMetric + storage_image_render_count: UsageMetric + monthly_active_users: UsageMetric + monthly_active_sso_users: UsageMetric + realtime_message_count: UsageMetric + realtime_peak_connection: UsageMetric + func_count: UsageMetric + func_invocations: UsageMetric disk_volume_size_gb: number } From 65f8bf73b7a30ca581427467bf657483c25a0673 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 2 May 2023 17:48:21 +0800 Subject: [PATCH 07/45] Update all charts to use the right attribute --- .../interfaces/BillingV2/Usage/Activity.tsx | 163 +++++++++++++----- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 17 +- .../BillingV2/Usage/Infrastructure.tsx | 16 +- .../BillingV2/Usage/SizeAndCounts.tsx | 12 +- studio/data/analytics/daily-stats-query.ts | 35 +++- 5 files changed, 175 insertions(+), 68 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 3393afd42d1..8b037c90c09 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -17,23 +17,42 @@ const Activity = () => { const startDate = new Date((current_period_start ?? 0) * 1000).toISOString() const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() - // [JOSHEN TODO] Attribute needs to change after confirming with team o11y if this is implemented + const MAU_KEY = 'total_auth_billing_period_mau' const { monthly_active_users } = usage ?? {} const mauExcess = (monthly_active_users?.usage ?? 0) - (monthly_active_users?.limit ?? 0) const { data: mauData, isLoading: isLoadingMauData } = useDailyStatsQuery({ projectRef: ref, - attribute: 'total_auth_billing_period_mau', + attribute: MAU_KEY, interval: '1d', startDate, endDate, }) + const ASSET_TRANSFORMATIONS_KEY = 'total_storage_image_render_count' const { storage_image_render_count } = usage ?? {} const assetTransformationExcess = (storage_image_render_count?.usage ?? 0) - (storage_image_render_count?.limit ?? 0) + const { data: assetTransformationsData, isLoading: isLoadingAssetTransformationsData } = + useDailyStatsQuery({ + projectRef: ref, + attribute: ASSET_TRANSFORMATIONS_KEY, + interval: '1d', + startDate, + endDate, + }) + const FUNC_INVOCATIONS_KEY = 'total_func_invocations' const { func_invocations } = usage ?? {} const funcInvocationsExcess = (func_invocations?.usage ?? 0) - (func_invocations?.limit ?? 0) + const { data: funcInvocationsData, isLoading: isLoadingFuncInvocationsData } = useDailyStatsQuery( + { + projectRef: ref, + attribute: FUNC_INVOCATIONS_KEY, + interval: '1d', + startDate, + endDate, + } + ) return ( <> @@ -46,7 +65,7 @@ const Activity = () => {
- {/* MONTHLY ACTIVE USERS - need to fix if no value yet (API will return period_start as 0 in first data point) */} + {/* MONTHLY ACTIVE USERS */}
@@ -101,7 +120,7 @@ const Activity = () => {
) : ( {
- {/* ASSET TRANSFORMATIONS - need to show actual data */} + {/* ASSET TRANSFORMATIONS */}
@@ -143,9 +162,13 @@ const Activity = () => {

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

-

- {(storage_image_render_count?.limit ?? 0).toLocaleString()} -

+ {storage_image_render_count?.limit === -1 ? ( +

None

+ ) : ( +

+ {(storage_image_render_count?.limit ?? 0).toLocaleString()} +

+ )}

Used

@@ -156,7 +179,8 @@ const Activity = () => {

Extra volume used this month

- {(assetTransformationExcess < 0 + {((storage_image_render_count?.limit ?? 0) === -1 || + assetTransformationExcess < 0 ? 0 : assetTransformationExcess ).toLocaleString()} @@ -164,38 +188,34 @@ const Activity = () => {

-
-

Asset transformations over time

-

Some description here

-
- {isLoadingMauData ? ( -
- - - -
- ) : ( - + {storage_image_render_count?.available_in_plan && ( + <> +
+

Asset transformations over time

+

Some description here

+
+ {isLoadingAssetTransformationsData ? ( +
+ + + +
+ ) : ( + + )} + )}
- {/* EDGE FUNCTIONS INVOCATIONS - need to show actual data */} + {/* EDGE FUNCTIONS INVOCATIONS */}
@@ -244,7 +264,7 @@ const Activity = () => {

Edge function invocations over time

Some description here

- {isLoadingMauData ? ( + {isLoadingFuncInvocationsData ? (
@@ -252,24 +272,75 @@ const Activity = () => {
) : ( )}
+ + {/* Sample Chart */} +
+
+
+
+
+

Sample Chart

+

Some description here

+
+
+
+
+
+

Sample quota usage

+ +
+ +
+
+

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

+

{(10000).toLocaleString()}

+
+
+

Used

+

0

+
+
+

Extra volume used this month

+

0

+
+
+
+
+

Sample usage over time

+

Some description here

+
+ +
+
+
+
) } diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index e14131babe0..472b8019653 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -1,12 +1,12 @@ import { useParams } from 'common' 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 { useProjectUsageQuery } from 'data/usage/project-usage-query' -import BarChart from './BarChart' -import { Button } from 'ui' -import SparkBar from 'components/ui/SparkBar' import { formatBytes } from 'lib/helpers' +import { Button } from 'ui' +import BarChart from './BarChart' const Bandwidth = () => { const { ref } = useParams() @@ -16,21 +16,24 @@ const Bandwidth = () => { const startDate = new Date((current_period_start ?? 0) * 1000).toISOString() const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() + // [JOSHEN TODO] Double check if this is the right attribute + const DB_EGRESS_KEY = 'total_db_egress_bytes' const { db_egress } = usage ?? {} const dbEgressExcess = (db_egress?.usage ?? 0) - (db_egress?.limit ?? 0) const { data: dbEgressData, isLoading: isLoadingDbEgressData } = useDailyStatsQuery({ projectRef: ref, - attribute: 'total_rest_egress', + attribute: DB_EGRESS_KEY, interval: '1d', startDate, endDate, }) + const STORAGE_EGRESS_KEY = 'total_storage_egress' const { storage_egress } = usage ?? {} const storageEgressExcess = (storage_egress?.usage ?? 0) - (storage_egress?.limit ?? 0) const { data: storageEgressData, isLoading: isLoadingStorageEgressData } = useDailyStatsQuery({ projectRef: ref, - attribute: 'total_storage_egress', + attribute: STORAGE_EGRESS_KEY, interval: '1d', startDate, endDate, @@ -104,7 +107,7 @@ const Bandwidth = () => {
) : ( { ) : ( { const { ref } = useParams() @@ -12,26 +11,29 @@ const Infrastructure = () => { const startDate = new Date((current_period_start ?? 0) * 1000).toISOString() const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() + const CPU_USAGE_KEY = 'cpu_usage' const { data: cpuUsageData, isLoading: isLoadingCpuUsageData } = useInfraMonitoringQuery({ projectRef: ref, - attribute: 'cpu_usage', + attribute: CPU_USAGE_KEY, interval: '1d', startDate, endDate, modifier: (x: number) => x * 100, }) + const MEMORY_USAGE_KEY = 'ram_usage' const { data: memoryUsageData, isLoading: isLoadingMemoryUsageData } = useInfraMonitoringQuery({ projectRef: ref, - attribute: 'ram_usage', + attribute: MEMORY_USAGE_KEY, interval: '1d', startDate, endDate, }) + const DISK_IO_BUDGET_KEY = 'disk_io_budget' const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({ projectRef: ref, - attribute: 'disk_io_budget', + attribute: DISK_IO_BUDGET_KEY, interval: '1d', startDate, endDate, @@ -71,7 +73,7 @@ const Infrastructure = () => { ) : ( { ) : ( { ) : ( { const startDate = new Date((current_period_start ?? 0) * 1000).toISOString() const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() - // [JOSHEN TODO] Attribute needs to change after confirming with team o11y if this is implemented + const TOTAL_DB_SIZE_KEY = 'total_db_size_bytes' const { db_size } = usage ?? {} const dbSizeExcess = (db_size?.usage ?? 0) - (db_size?.limit ?? 0) const { data: dbSizeData, isLoading: isLoadingDbSizeData } = useDailyStatsQuery({ projectRef: ref, - attribute: 'total_rest_egress', + attribute: TOTAL_DB_SIZE_KEY, interval: '1d', startDate, endDate, }) - // [JOSHEN TODO] Attribute needs to change after confirming with team o11y if this is implemented + const TOTAL_STORAGE_SIZE_KEY = 'total_storage_size_bytes' const { storage_size } = usage ?? {} const storageSizeExcess = (storage_size?.usage ?? 0) - (storage_size?.limit ?? 0) const { data: storageSizeData, isLoading: isLoadingStorageSizeData } = useDailyStatsQuery({ projectRef: ref, - attribute: 'total_storage_egress', + attribute: TOTAL_STORAGE_SIZE_KEY, interval: '1d', startDate, endDate, @@ -104,7 +104,7 @@ const SizeAndCounts = () => { ) : ( { ) : ( ( typeof endDate !== 'undefined', // @ts-ignore select(data) { + // [JOSHEN TODO] if no data, pad with empty data return { ...data, data: data.data.map((x) => { From 966eb1ae35634159c47e2077fd253f0c7311cfa5 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 2 May 2023 18:02:08 +0800 Subject: [PATCH 08/45] Scroll to segment by clicking on tabs --- .../interfaces/BillingV2/Usage/Usage.tsx | 62 ++++++++++++++++--- 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index 7fd770efb23..c62a2f4eff1 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -2,7 +2,7 @@ import { useParams } from 'common' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useProjectsQuery } from 'data/projects/projects-query' import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { Button, IconCheckCircle, Listbox } from 'ui' import Infrastructure from './Infrastructure' import Bandwidth from './Bandwidth' @@ -13,11 +13,35 @@ const Usage = () => { const { ref } = useParams() const [selectedProjectRef, setSelectedProjectRef] = useState(ref as string) + const infrastructureRef = useRef(null) + const bandwidthRef = useRef(null) + const sizeAndCountsRef = useRef(null) + const activityRef = useRef(null) + const { data: projects, isLoading, isSuccess } = useProjectsQuery() const { data: subscription } = useProjectSubscriptionQuery({ projectRef: ref }) useEffect(() => {}, [isSuccess]) + const scrollTo = (id: 'infra' | 'bandwidth' | 'sizeCount' | 'activity') => { + switch (id) { + case 'infra': + if (infrastructureRef.current) + infrastructureRef.current.scrollIntoView({ behavior: 'smooth' }) + break + case 'bandwidth': + if (bandwidthRef.current) bandwidthRef.current.scrollIntoView({ behavior: 'smooth' }) + break + case 'sizeCount': + if (sizeAndCountsRef.current) + sizeAndCountsRef.current.scrollIntoView({ behavior: 'smooth' }) + break + case 'activity': + if (activityRef.current) activityRef.current.scrollIntoView({ behavior: 'smooth' }) + break + } + } + return ( <>
@@ -54,19 +78,31 @@ const Usage = () => {
-
+
scrollTo('infra')} + className="flex items-center opacity-50 space-x-2 py-3 hover:opacity-100 transition cursor-pointer" + >

Infrastructure

-
+
scrollTo('bandwidth')} + className="flex items-center opacity-50 space-x-2 py-3 hover:opacity-100 transition cursor-pointer" + >

Bandwidth

-
+
scrollTo('sizeCount')} + className="flex items-center opacity-50 space-x-2 py-3 hover:opacity-100 transition cursor-pointer" + >

Size & Counts

-
+
scrollTo('activity')} + className="flex items-center opacity-50 space-x-2 py-3 hover:opacity-100 transition cursor-pointer" + >

Activity

@@ -74,10 +110,18 @@ const Usage = () => {
- - - - +
+ +
+
+ +
+
+ +
+
+ +
) } From 9ee841c5c613d6c2e733493ccf0cdcba20ddab07 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 2 May 2023 18:18:34 +0800 Subject: [PATCH 09/45] Scaffold billing cycle and project selection listboxes --- .../interfaces/BillingV2/Usage/Usage.tsx | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index c62a2f4eff1..e5f85728f99 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -3,11 +3,12 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useProjectsQuery } from 'data/projects/projects-query' import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' import { useEffect, useRef, useState } from 'react' -import { Button, IconCheckCircle, Listbox } from 'ui' +import { Button, IconCheckCircle, IconLoader, Listbox } from 'ui' import Infrastructure from './Infrastructure' import Bandwidth from './Bandwidth' import SizeAndCounts from './SizeAndCounts' import Activity from './Activity' +import clsx from 'clsx' const Usage = () => { const { ref } = useParams() @@ -19,7 +20,9 @@ const Usage = () => { const activityRef = useRef(null) const { data: projects, isLoading, isSuccess } = useProjectsQuery() - const { data: subscription } = useProjectSubscriptionQuery({ projectRef: ref }) + const { data: subscription, isLoading: isLoadingSubscription } = useProjectSubscriptionQuery({ + projectRef: selectedProjectRef, + }) useEffect(() => {}, [isSuccess]) @@ -50,11 +53,32 @@ const Usage = () => {
- {/* */} + {}} + > + + Current billing cycle + + + Previous billing cycle + + {isLoading ? ( ) : ( - {}} className="w-[200px]"> + {(projects ?? []).map((project) => ( { ))} )} - {subscription !== undefined && ( -

Project is on {subscription.tier.name}

- )} + {isLoadingSubscription ? ( + + ) : subscription !== undefined ? ( +

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

+ ) : null}
From 0c6fc7a23eb765bb576771c9aee277174917e8d3 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 3 May 2023 11:21:00 +0800 Subject: [PATCH 10/45] Make reusable components for SectionHeader and SectionContent --- .../interfaces/BillingV2/Usage/Activity.tsx | 378 ++++++++---------- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 240 +++++------ .../interfaces/BillingV2/Usage/BarChart.tsx | 4 +- .../BillingV2/Usage/Infrastructure.tsx | 164 +++----- .../BillingV2/Usage/SectionContent.tsx | 26 ++ .../BillingV2/Usage/SectionHeader.tsx | 19 + .../BillingV2/Usage/SizeAndCounts.tsx | 235 +++++------ 7 files changed, 480 insertions(+), 586 deletions(-) create mode 100644 studio/components/interfaces/BillingV2/Usage/SectionContent.tsx create mode 100644 studio/components/interfaces/BillingV2/Usage/SectionHeader.tsx diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 8b037c90c09..6de27c53c4e 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -1,12 +1,13 @@ import { useParams } from 'common' 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 { useProjectUsageQuery } from 'data/usage/project-usage-query' -import BarChart from './BarChart' -import SparkBar from 'components/ui/SparkBar' -import { formatBytes } from 'lib/helpers' import { Button } from 'ui' +import BarChart from './BarChart' +import SectionContent from './SectionContent' +import SectionHeader from './SectionHeader' import { generateUsageData } from './Usage.utils' const Activity = () => { @@ -56,232 +57,183 @@ const Activity = () => { return ( <> -
-
-
-

Activity

-

Some description here

+ + + +
+
+

+ Monthly active users {subscription?.tier.key.toLowerCase()} quota usage +

+ +
+ +
+
+

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

+

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

+
+
+

Used

+

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

+
+
+

Extra volume used this month

+

{(mauExcess < 0 ? 0 : mauExcess).toLocaleString()}

+
-
+
+

Monthly active users over time

+

Some description here

+
+ {isLoadingMauData ? ( +
+ + + +
+ ) : ( + + )} + - {/* MONTHLY ACTIVE USERS */} -
-
-
-
-
-

Monthly Active Users (MAU)

-

Some description here

-
-
-
-
-
-

- Monthly active users {subscription?.tier.key.toLowerCase()} quota usage -

- -
- -
-
-

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

-

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

-
-
-

Used

-

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

-
-
-

Extra volume used this month

-

{(mauExcess < 0 ? 0 : mauExcess).toLocaleString()}

-
-
-
-
-

Monthly active users over time

-

Some description here

-
- {isLoadingMauData ? ( -
- - - -
+ +
+
+

+ Asset transformations {subscription?.tier.key.toLowerCase()} quota usage +

+ +
+ +
+
+

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

+ {storage_image_render_count?.limit === -1 ? ( +

None

) : ( - +

+ {(storage_image_render_count?.limit ?? 0).toLocaleString()} +

)}
+
+

Used

+

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

+
+
+

Extra volume used this month

+

+ {((storage_image_render_count?.limit ?? 0) === -1 || assetTransformationExcess < 0 + ? 0 + : assetTransformationExcess + ).toLocaleString()} +

+
-
- - {/* ASSET TRANSFORMATIONS */} -
-
-
-
-
-

Asset Transformations

-

Some description here

-
+ {storage_image_render_count?.available_in_plan && ( + <> +
+

Asset transformations over time

+

Some description here

-
+ {isLoadingAssetTransformationsData ? (
-
-

- Asset transformations {subscription?.tier.key.toLowerCase()} quota usage -

- -
- -
-
-

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

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

None

- ) : ( -

- {(storage_image_render_count?.limit ?? 0).toLocaleString()} -

- )} -
-
-

Used

-

- {(storage_image_render_count?.usage ?? 0).toLocaleString()} -

-
-
-

Extra volume used this month

-

- {((storage_image_render_count?.limit ?? 0) === -1 || - assetTransformationExcess < 0 - ? 0 - : assetTransformationExcess - ).toLocaleString()} -

-
-
+ + +
- {storage_image_render_count?.available_in_plan && ( - <> -
-

Asset transformations over time

-

Some description here

-
- {isLoadingAssetTransformationsData ? ( -
- - - -
- ) : ( - - )} - - )} -
-
-
-
+ ) : ( + + )} + + )} + - {/* EDGE FUNCTIONS INVOCATIONS */} -
-
-
-
-
-

Edge Function Invocations

-

Some description here

-
+ +
+
+

+ Edge function invocations {subscription?.tier.key.toLowerCase()} quota usage +

+ +
+ +
+
+

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

+

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

-
-
-
-

- Edge function invocations {subscription?.tier.key.toLowerCase()} quota usage -

- -
- -
-
-

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

-

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

-
-
-

Used

-

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

-
-
-

Extra volume used this month

-

- {(funcInvocationsExcess < 0 ? 0 : funcInvocationsExcess).toLocaleString()} -

-
-
-
-
-

Edge function invocations over time

-

Some description here

-
- {isLoadingFuncInvocationsData ? ( -
- - - -
- ) : ( - - )} +
+

Used

+

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

+
+
+

Extra volume used this month

+

+ {(funcInvocationsExcess < 0 ? 0 : funcInvocationsExcess).toLocaleString()} +

-
+
+

Edge function invocations over time

+

Some description here

+
+ {isLoadingFuncInvocationsData ? ( +
+ + + +
+ ) : ( + + )} +
{/* Sample Chart */}
diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 472b8019653..7f3296df7e0 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -7,6 +7,8 @@ import { useProjectUsageQuery } from 'data/usage/project-usage-query' import { formatBytes } from 'lib/helpers' import { Button } from 'ui' import BarChart from './BarChart' +import SectionContent from './SectionContent' +import SectionHeader from './SectionHeader' const Bandwidth = () => { const { ref } = useParams() @@ -41,150 +43,120 @@ const Bandwidth = () => { return ( <> -
-
-
-

Bandwidth

-

Some description here

-
-
-
+ - {/* DATABASE EGRESS - need to fix if no value yet (API will return period_start as 0 in first data point) */} -
-
-
-
-
-

Database Egress

-

Some description here

-
+ +
+
+

Egress {subscription?.tier.key.toLowerCase()} quota usage

+ +
+ +
+
+

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

+

{formatBytes(db_egress?.limit ?? 0)}

-
-
-
-

- Egress {subscription?.tier.key.toLowerCase()} quota usage -

- -
- -
-
-

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

-

{formatBytes(db_egress?.limit ?? 0)}

-
-
-

Used

-

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

-
-
-

Extra volume used this month

-

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

-
-
-
-
-

Database egress over time

-

Some description here

-
- {isLoadingDbEgressData ? ( -
- - - -
- ) : ( - - )} +
+

Used

+

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

+
+
+

Extra volume used this month

+

+ {dbEgressExcess < 0 ? formatBytes(0) : formatBytes(dbEgressExcess)} +

-
+
+

Database egress over time

+

Some description here

+
+ {isLoadingDbEgressData ? ( +
+ + + +
+ ) : ( + // [Joshen] TODO + // - Provide a way to format y axis (bytes) + // - Fix the y axis cartesian grid intervals if possible (atm it looks random) + // - the max Y domain could be dynamic (slightly above limit, but take the usage + some threshold if above limit) + formatBytes(value)} + /> + )} +
- {/* STORAGE EGRESS - need to fix if no value yet (API will return period_start as 0 in first data point) */} -
-
-
-
-
-

Storage Egress

-

Some description here

-
+ +
+
+

+ Storage egress {subscription?.tier.key.toLowerCase()} quota usage +

+ +
+ +
+
+

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

+

{formatBytes(storage_egress?.limit ?? 0)}

-
-
-
-

- Storage egress {subscription?.tier.key.toLowerCase()} quota usage -

- -
- -
-
-

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

-

{formatBytes(storage_egress?.limit ?? 0)}

-
-
-

Used

-

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

-
-
-

Extra volume used this month

-

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

-
-
-
-
-

Storage egress over time

-

Some description here

-
- {isLoadingStorageEgressData ? ( -
- - - -
- ) : ( - - )} +
+

Used

+

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

+
+
+

Extra volume used this month

+

+ {storageEgressExcess < 0 ? formatBytes(0) : formatBytes(storageEgressExcess)} +

-
+
+

Storage egress over time

+

Some description here

+
+ {isLoadingStorageEgressData ? ( +
+ + + +
+ ) : ( + + )} +
) } diff --git a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx index c332818ce47..86dc7df0c40 100644 --- a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx +++ b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx @@ -28,9 +28,10 @@ export interface BarChartProps { reference?: Reference unit?: string yDomain?: number[] + yFormatter?: (value: number) => string } -const BarChart = ({ data, attribute, reference, unit, yDomain }: BarChartProps) => { +const BarChart = ({ data, attribute, reference, unit, yDomain, yFormatter }: BarChartProps) => { return (
@@ -43,6 +44,7 @@ const BarChart = ({ data, attribute, reference, unit, yDomain }: BarChartProps) tickLine={{ stroke: 'none' }} domain={yDomain} unit={unit} + tickFormatter={yFormatter} /> {data.map((entry) => { diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index b431d586843..64dd23457e0 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -3,6 +3,8 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' import BarChart from './BarChart' +import SectionContent from './SectionContent' +import SectionHeader from './SectionHeader' const Infrastructure = () => { const { ref } = useParams() @@ -41,116 +43,70 @@ const Infrastructure = () => { return ( <> -
-
-
-

Infrastructure

-

Some description here

-
-
-
+ - {/* CPU USAGE */} -
-
-
-
-
-

CPU

-

Some description here

-
-
-
-
-

Max CPU usage each day

-

Some description here

-
- {isLoadingCpuUsageData ? ( -
- - - -
- ) : ( - - )} -
-
+ +
+

Max CPU usage each day

+

Some description here

-
+ {isLoadingCpuUsageData ? ( +
+ + + +
+ ) : ( + + )} + - {/* MEMORY USAGE */} -
-
-
-
-
-

Memory

-

Some description here

-
-
-
-
-

Max memory usage each day

-

Some description here

-
- {isLoadingMemoryUsageData ? ( -
- - - -
- ) : ( - - )} -
-
+ +
+

Max memory usage each day

+

Some description here

-
+ {isLoadingMemoryUsageData ? ( +
+ + + +
+ ) : ( + + )} + - {/* DISK IO BUDGET */} -
-
-
-
-
-

IO Budget

-

Some description here

-
-
-
-
-

Disk IO budget per day

-

Some description here

-
- {isLoadingIoBudgetData ? ( -
- - - -
- ) : ( - - )} -
-
+ +
+

Disk IO budget per day

+

Some description here

-
+ {isLoadingIoBudgetData ? ( +
+ + + +
+ ) : ( + + )} + ) } diff --git a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx new file mode 100644 index 00000000000..1f3dc1d0deb --- /dev/null +++ b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx @@ -0,0 +1,26 @@ +import { PropsWithChildren } from 'react' + +export interface SectionContent { + title: string + description: string +} + +const SectionContent = ({ title, description, children }: PropsWithChildren) => { + return ( +
+
+
+
+
+

{title}

+

{description}

+
+
+
{children}
+
+
+
+ ) +} + +export default SectionContent diff --git a/studio/components/interfaces/BillingV2/Usage/SectionHeader.tsx b/studio/components/interfaces/BillingV2/Usage/SectionHeader.tsx new file mode 100644 index 00000000000..45cc9cd30ce --- /dev/null +++ b/studio/components/interfaces/BillingV2/Usage/SectionHeader.tsx @@ -0,0 +1,19 @@ +export interface SectionHeaderProps { + title: string + description: string +} + +const SectionHeader = ({ title, description }: SectionHeaderProps) => { + return ( +
+
+
+

{title}

+

{description}

+
+
+
+ ) +} + +export default SectionHeader diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index 3a822b4dc19..c00076ffab8 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -1,12 +1,14 @@ import { useParams } from 'common' import ShimmeringLoader from 'components/ui/ShimmeringLoader' -import BarChart from './BarChart' +import SparkBar from 'components/ui/SparkBar' import { useDailyStatsQuery } from 'data/analytics/daily-stats-query' import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' import { useProjectUsageQuery } from 'data/usage/project-usage-query' -import SparkBar from 'components/ui/SparkBar' import { formatBytes } from 'lib/helpers' import { Button } from 'ui' +import BarChart from './BarChart' +import SectionContent from './SectionContent' +import SectionHeader from './SectionHeader' const SizeAndCounts = () => { const { ref } = useParams() @@ -40,148 +42,113 @@ const SizeAndCounts = () => { return ( <> -
-
-
-

Size & Counts

-

Some description here

-
-
-
+ - {/* DATABASE SIZE - need to fix if no value yet (API will return period_start as 0 in first data point) */} -
-
-
-
-
-

Database size

-

Some description here

-
+ +
+
+

Database size per day

+ +
+ +
+
+

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

+

{formatBytes(db_size?.limit ?? 0)}

-
-
-
-

Database size per day

- -
- -
-
-

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

-

{formatBytes(db_size?.limit ?? 0)}

-
-
-

Used

-

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

-
-
-

Extra volume used this month

-

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

-
-
-
-
-

Database egress over time

-

Some description here

-
- {isLoadingDbSizeData ? ( -
- - - -
- ) : ( - - )} +
+

Used

+

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

+
+
+

Extra volume used this month

+

+ {dbSizeExcess < 0 ? formatBytes(0) : formatBytes(dbSizeExcess)} +

-
+
+

Database egress over time

+

Some description here

+
+ {isLoadingDbSizeData ? ( +
+ + + +
+ ) : ( + + )} +
- {/* STORAGE SIZE - need to fix if no value yet (API will return period_start as 0 in first data point) */} -
-
-
-
-
-

Storage size

-

Some description here

-
+ +
+
+

Storage {subscription?.tier.key.toLowerCase()} quota usage

+ +
+ +
+
+

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

+

{formatBytes(storage_size?.limit ?? 0)}

-
-
-
-

- Storage {subscription?.tier.key.toLowerCase()} quota usage -

- -
- -
-
-

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

-

{formatBytes(storage_size?.limit ?? 0)}

-
-
-

Used

-

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

-
-
-

Extra volume used this month

-

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

-
-
-
-
-

Storage size over time

-

Some description here

-
- {isLoadingStorageSizeData ? ( -
- - - -
- ) : ( - - )} +
+

Used

+

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

+
+
+

Extra volume used this month

+

+ {storageSizeExcess < 0 ? formatBytes(0) : formatBytes(storageSizeExcess)} +

-
+
+

Storage size over time

+

Some description here

+
+ {isLoadingStorageSizeData ? ( +
+ + + +
+ ) : ( + + )} +
) } From 70f97dbe2bc9eabda14cf2b9cf34953d63066bfc Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 3 May 2023 11:48:40 +0800 Subject: [PATCH 11/45] Apply appropriate units to yaxis for each barchar --- .../interfaces/BillingV2/Usage/Activity.tsx | 10 +++++--- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 9 +++---- .../interfaces/BillingV2/Usage/BarChart.tsx | 24 +++++++++++++++++-- .../BillingV2/Usage/Infrastructure.tsx | 1 - .../BillingV2/Usage/SizeAndCounts.tsx | 8 +++++-- studio/lib/helpers.ts | 12 ++++++---- 6 files changed, 47 insertions(+), 17 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 6de27c53c4e..344cd5afcdd 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -107,7 +107,9 @@ const Activity = () => { attribute={MAU_KEY} data={mauData?.data ?? []} unit={undefined} - yDomain={[0, 100]} + yDomain={[0, monthly_active_users?.limit ?? 0]} + yLeftMargin={18} + yFormatter={(value) => value.toLocaleString()} /> )} @@ -173,7 +175,7 @@ const Activity = () => { attribute={MAU_KEY} data={assetTransformationsData?.data ?? []} unit={undefined} - yDomain={[0, 100]} + yDomain={[0, storage_image_render_count?.limit ?? 0]} /> )} @@ -230,7 +232,9 @@ const Activity = () => { attribute={FUNC_INVOCATIONS_KEY} data={funcInvocationsData?.data ?? []} unit={undefined} - yDomain={[0, 100]} + yDomain={[0, func_invocations?.limit ?? 0]} + yLeftMargin={26} + yFormatter={(value) => value.toLocaleString()} /> )} diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 7f3296df7e0..7e918caf964 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -90,15 +90,14 @@ const Bandwidth = () => {
) : ( // [Joshen] TODO - // - Provide a way to format y axis (bytes) - // - Fix the y axis cartesian grid intervals if possible (atm it looks random) // - the max Y domain could be dynamic (slightly above limit, but take the usage + some threshold if above limit) formatBytes(value)} + yLeftMargin={14} + yFormatter={(value) => formatBytes(value, 1, 'GB').replace(/\s/g, '')} /> )} @@ -153,7 +152,9 @@ const Bandwidth = () => { attribute={STORAGE_EGRESS_KEY} data={storageEgressData?.data ?? []} unit={undefined} - yDomain={[0, 100]} + yDomain={[0, storage_egress?.limit ?? 0]} + yLeftMargin={14} + yFormatter={(value) => formatBytes(value, 1, 'GB').replace(/\s/g, '')} /> )} diff --git a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx index 86dc7df0c40..eea0934e8dc 100644 --- a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx +++ b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx @@ -28,14 +28,33 @@ export interface BarChartProps { reference?: Reference unit?: string yDomain?: number[] + yLeftMargin?: number yFormatter?: (value: number) => string } -const BarChart = ({ data, attribute, reference, unit, yDomain, yFormatter }: BarChartProps) => { +const BarChart = ({ + data, + attribute, + reference, + unit, + yDomain, + yLeftMargin = 10, + yFormatter, +}: BarChartProps) => { + const ticks = + yDomain !== undefined + ? [ + yDomain[0], + (yDomain[1] - yDomain[0]) * 0.25, + (yDomain[1] - yDomain[0]) * 0.5, + (yDomain[1] - yDomain[0]) * 0.75, + yDomain[1], + ] + : undefined return (
- + diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 64dd23457e0..c063ccde089 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -20,7 +20,6 @@ const Infrastructure = () => { interval: '1d', startDate, endDate, - modifier: (x: number) => x * 100, }) const MEMORY_USAGE_KEY = 'ram_usage' diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index c00076ffab8..05d53409f69 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -92,7 +92,9 @@ const SizeAndCounts = () => { attribute={TOTAL_DB_SIZE_KEY} data={dbSizeData?.data ?? []} unit={undefined} - yDomain={[0, 100]} + yDomain={[0, db_size?.limit ?? 0]} + yLeftMargin={14} + yFormatter={(value) => formatBytes(value, 1, 'GB').replace(/\s/g, '')} /> )} @@ -145,7 +147,9 @@ const SizeAndCounts = () => { attribute={TOTAL_STORAGE_SIZE_KEY} data={storageSizeData?.data ?? []} unit={undefined} - yDomain={[0, 100]} + yDomain={[0, storage_size?.limit ?? 0]} + yLeftMargin={14} + yFormatter={(value) => formatBytes(value, 1, 'GB').replace(/\s/g, '')} /> )} diff --git a/studio/lib/helpers.ts b/studio/lib/helpers.ts index d11dd083a6e..759643225f0 100644 --- a/studio/lib/helpers.ts +++ b/studio/lib/helpers.ts @@ -136,15 +136,17 @@ export const propsAreEqual = (prevProps: any, nextProps: any) => { } } -export const formatBytes = (bytes: any, decimals = 2) => { - if (bytes === 0 || bytes === undefined) return '0 bytes' - +export const formatBytes = ( + bytes: any, + decimals = 2, + size?: 'bytes' | 'KB' | 'MB' | 'GB' | 'TB' | 'PB' | 'EB' | 'ZB' | 'YB' +) => { const k = 1024 const dm = decimals < 0 ? 0 : decimals const sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - + if (bytes === 0 || bytes === undefined) return size !== undefined ? `0 ${size}` : '0 bytes' + const i = size !== undefined ? sizes.indexOf(size) : Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] } From 02803aa60be39a2e567c0b7a0ef863fa1d1a7dbc Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 3 May 2023 14:05:06 +0800 Subject: [PATCH 12/45] Fix reference line dynamic position --- .../interfaces/BillingV2/Usage/Activity.tsx | 29 ++++----- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 9 +-- .../interfaces/BillingV2/Usage/BarChart.tsx | 60 +++++++++---------- .../BillingV2/Usage/Infrastructure.tsx | 6 +- .../BillingV2/Usage/SizeAndCounts.tsx | 6 +- .../BillingV2/Usage/Usage.constants.ts | 1 + 6 files changed, 52 insertions(+), 59 deletions(-) create mode 100644 studio/components/interfaces/BillingV2/Usage/Usage.constants.ts diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 344cd5afcdd..401b72c5440 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -104,10 +104,10 @@ const Activity = () => {
) : ( value.toLocaleString()} /> @@ -172,10 +172,10 @@ const Activity = () => {
) : ( )} @@ -229,10 +229,11 @@ const Activity = () => {
) : ( value.toLocaleString()} /> @@ -277,21 +278,15 @@ const Activity = () => {

Sample usage over time

-

Some description here

+

+ Some description here to explain what this metric is about +

diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 7e918caf964..7bb725e9347 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -9,6 +9,7 @@ import { Button } from 'ui' import BarChart from './BarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' +import { Y_DOMAIN_CEILING_MULTIPLIER } from './Usage.constants' const Bandwidth = () => { const { ref } = useParams() @@ -89,13 +90,12 @@ const Bandwidth = () => {
) : ( - // [Joshen] TODO - // - the max Y domain could be dynamic (slightly above limit, but take the usage + some threshold if above limit) formatBytes(value, 1, 'GB').replace(/\s/g, '')} /> @@ -149,10 +149,11 @@ const Bandwidth = () => {
) : ( formatBytes(value, 1, 'GB').replace(/\s/g, '')} /> diff --git a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx index eea0934e8dc..9d8bfb4b526 100644 --- a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx +++ b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx @@ -8,26 +8,19 @@ import { Bar, ReferenceLine, Cell, + Label, } from 'recharts' +import { Y_DOMAIN_CEILING_MULTIPLIER } from './Usage.constants' -// [Joshen] I don't think I should be replacing ChartHandler just yet as the styling of the usage charts -// are slightly different from that on the other pages, just in case. - -interface Reference { - value: number - label: string - width: number - height: number - x: number - y: number -} +// [Joshen] This BarChart is specifically for usage, hence not a reusable component, and not +// replacing the existing BarChart in ui/Charts export interface BarChartProps { data: DataPoint[] attribute: string - reference?: Reference unit?: string - yDomain?: number[] + hasQuota?: boolean + yLimit?: number yLeftMargin?: number yFormatter?: (value: number) => string } @@ -35,22 +28,23 @@ export interface BarChartProps { const BarChart = ({ data, attribute, - reference, unit, - yDomain, + hasQuota = false, + yLimit, yLeftMargin = 10, yFormatter, }: BarChartProps) => { + const yMin = 0 // We can consider passing this as a prop if there's a use case in the future + const yMax = (yLimit ?? 0) * Y_DOMAIN_CEILING_MULTIPLIER + + const yDomain = [yMin, yLimit ?? 0] const ticks = - yDomain !== undefined - ? [ - yDomain[0], - (yDomain[1] - yDomain[0]) * 0.25, - (yDomain[1] - yDomain[0]) * 0.5, - (yDomain[1] - yDomain[0]) * 0.75, - yDomain[1], - ] + yLimit !== undefined && hasQuota + ? [yMin, (yMax - yMin) * 0.25, (yMax - yMin) * 0.5, (yMax - yMin) * 0.75, yMax].map((x) => + Math.ceil(x) + ) : undefined + return (
@@ -71,7 +65,7 @@ const BarChart = ({ return ( = reference.value + yLimit !== undefined && Number(entry[attribute]) >= yLimit ? 'fill-amber-900' : 'fill-scale-1200' } @@ -79,21 +73,21 @@ const BarChart = ({ ) })} - {reference && ( + {hasQuota && yLimit && ( (
- {reference.label} + PLAN QUOTA
- } + )} /> )} diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index c063ccde089..1cf17141e17 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -60,7 +60,7 @@ const Infrastructure = () => { attribute={CPU_USAGE_KEY} data={cpuUsageData?.data ?? []} unit={cpuUsageData?.format} - yDomain={[0, 100]} + yLimit={100} /> )} @@ -81,7 +81,7 @@ const Infrastructure = () => { attribute={MEMORY_USAGE_KEY} data={memoryUsageData?.data ?? []} unit={memoryUsageData?.format} - yDomain={[0, 100]} + yLimit={100} /> )} @@ -102,7 +102,7 @@ const Infrastructure = () => { attribute={DISK_IO_BUDGET_KEY} data={ioBudgetData?.data ?? []} unit={ioBudgetData?.format} - yDomain={[0, 100]} + yLimit={100} /> )} diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index 05d53409f69..48751ea6fcc 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -89,10 +89,11 @@ const SizeAndCounts = () => {
) : ( formatBytes(value, 1, 'GB').replace(/\s/g, '')} /> @@ -144,10 +145,11 @@ const SizeAndCounts = () => {
) : ( formatBytes(value, 1, 'GB').replace(/\s/g, '')} /> diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts new file mode 100644 index 00000000000..d3b789be076 --- /dev/null +++ b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts @@ -0,0 +1 @@ +export const Y_DOMAIN_CEILING_MULTIPLIER = 4 / 3 From 00c6952fb97bba72bd339a1c7a404065a9e5efda Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 3 May 2023 14:11:32 +0800 Subject: [PATCH 13/45] Have charts dynamic to selected project ref --- .../interfaces/BillingV2/Usage/Activity.tsx | 17 ++++++++++------- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 16 +++++++++------- .../BillingV2/Usage/Infrastructure.tsx | 16 +++++++++------- .../BillingV2/Usage/SizeAndCounts.tsx | 15 +++++++++------ .../interfaces/BillingV2/Usage/Usage.tsx | 9 +++++---- 5 files changed, 42 insertions(+), 31 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 401b72c5440..b5c7b444af3 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -10,10 +10,13 @@ import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { generateUsageData } from './Usage.utils' -const Activity = () => { - const { ref } = useParams() - const { data: usage } = useProjectUsageQuery({ projectRef: ref }) - const { data: subscription } = useProjectSubscriptionQuery({ projectRef: ref }) +export interface ActivityProps { + projectRef: string +} + +const Activity = ({ projectRef }: ActivityProps) => { + const { data: usage } = useProjectUsageQuery({ projectRef }) + const { data: subscription } = useProjectSubscriptionQuery({ projectRef }) const { current_period_start, current_period_end } = subscription?.billing ?? {} const startDate = new Date((current_period_start ?? 0) * 1000).toISOString() const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() @@ -22,7 +25,7 @@ const Activity = () => { const { monthly_active_users } = usage ?? {} const mauExcess = (monthly_active_users?.usage ?? 0) - (monthly_active_users?.limit ?? 0) const { data: mauData, isLoading: isLoadingMauData } = useDailyStatsQuery({ - projectRef: ref, + projectRef, attribute: MAU_KEY, interval: '1d', startDate, @@ -35,7 +38,7 @@ const Activity = () => { (storage_image_render_count?.usage ?? 0) - (storage_image_render_count?.limit ?? 0) const { data: assetTransformationsData, isLoading: isLoadingAssetTransformationsData } = useDailyStatsQuery({ - projectRef: ref, + projectRef, attribute: ASSET_TRANSFORMATIONS_KEY, interval: '1d', startDate, @@ -47,7 +50,7 @@ const Activity = () => { const funcInvocationsExcess = (func_invocations?.usage ?? 0) - (func_invocations?.limit ?? 0) const { data: funcInvocationsData, isLoading: isLoadingFuncInvocationsData } = useDailyStatsQuery( { - projectRef: ref, + projectRef, attribute: FUNC_INVOCATIONS_KEY, interval: '1d', startDate, diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 7bb725e9347..edbfe152ad8 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -9,12 +9,14 @@ import { Button } from 'ui' import BarChart from './BarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' -import { Y_DOMAIN_CEILING_MULTIPLIER } from './Usage.constants' -const Bandwidth = () => { - const { ref } = useParams() - const { data: usage } = useProjectUsageQuery({ projectRef: ref }) - const { data: subscription } = useProjectSubscriptionQuery({ projectRef: ref }) +export interface BandwidthProps { + projectRef: string +} + +const Bandwidth = ({ projectRef }: BandwidthProps) => { + const { data: usage } = useProjectUsageQuery({ projectRef }) + const { data: subscription } = useProjectSubscriptionQuery({ projectRef }) const { current_period_start, current_period_end } = subscription?.billing ?? {} const startDate = new Date((current_period_start ?? 0) * 1000).toISOString() const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() @@ -24,7 +26,7 @@ const Bandwidth = () => { const { db_egress } = usage ?? {} const dbEgressExcess = (db_egress?.usage ?? 0) - (db_egress?.limit ?? 0) const { data: dbEgressData, isLoading: isLoadingDbEgressData } = useDailyStatsQuery({ - projectRef: ref, + projectRef, attribute: DB_EGRESS_KEY, interval: '1d', startDate, @@ -35,7 +37,7 @@ const Bandwidth = () => { const { storage_egress } = usage ?? {} const storageEgressExcess = (storage_egress?.usage ?? 0) - (storage_egress?.limit ?? 0) const { data: storageEgressData, isLoading: isLoadingStorageEgressData } = useDailyStatsQuery({ - projectRef: ref, + projectRef, attribute: STORAGE_EGRESS_KEY, interval: '1d', startDate, diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 1cf17141e17..4b80ab93871 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -1,4 +1,3 @@ -import { useParams } from 'common' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' @@ -6,16 +5,19 @@ import BarChart from './BarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' -const Infrastructure = () => { - const { ref } = useParams() - const { data: subscription } = useProjectSubscriptionQuery({ projectRef: ref }) +export interface InfrastructureProps { + projectRef: string +} + +const Infrastructure = ({ projectRef }: InfrastructureProps) => { + const { data: subscription } = useProjectSubscriptionQuery({ projectRef }) const { current_period_start, current_period_end } = subscription?.billing ?? {} const startDate = new Date((current_period_start ?? 0) * 1000).toISOString() const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() const CPU_USAGE_KEY = 'cpu_usage' const { data: cpuUsageData, isLoading: isLoadingCpuUsageData } = useInfraMonitoringQuery({ - projectRef: ref, + projectRef, attribute: CPU_USAGE_KEY, interval: '1d', startDate, @@ -24,7 +26,7 @@ const Infrastructure = () => { const MEMORY_USAGE_KEY = 'ram_usage' const { data: memoryUsageData, isLoading: isLoadingMemoryUsageData } = useInfraMonitoringQuery({ - projectRef: ref, + projectRef, attribute: MEMORY_USAGE_KEY, interval: '1d', startDate, @@ -33,7 +35,7 @@ const Infrastructure = () => { const DISK_IO_BUDGET_KEY = 'disk_io_budget' const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({ - projectRef: ref, + projectRef, attribute: DISK_IO_BUDGET_KEY, interval: '1d', startDate, diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index 48751ea6fcc..4625e223aa5 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -10,10 +10,13 @@ import BarChart from './BarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' -const SizeAndCounts = () => { - const { ref } = useParams() - const { data: usage } = useProjectUsageQuery({ projectRef: ref }) - const { data: subscription } = useProjectSubscriptionQuery({ projectRef: ref }) +export interface SizeAndCountsProps { + projectRef: string +} + +const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { + const { data: usage } = useProjectUsageQuery({ projectRef }) + const { data: subscription } = useProjectSubscriptionQuery({ projectRef }) const { current_period_start, current_period_end } = subscription?.billing ?? {} const startDate = new Date((current_period_start ?? 0) * 1000).toISOString() const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() @@ -22,7 +25,7 @@ const SizeAndCounts = () => { const { db_size } = usage ?? {} const dbSizeExcess = (db_size?.usage ?? 0) - (db_size?.limit ?? 0) const { data: dbSizeData, isLoading: isLoadingDbSizeData } = useDailyStatsQuery({ - projectRef: ref, + projectRef, attribute: TOTAL_DB_SIZE_KEY, interval: '1d', startDate, @@ -33,7 +36,7 @@ const SizeAndCounts = () => { const { storage_size } = usage ?? {} const storageSizeExcess = (storage_size?.usage ?? 0) - (storage_size?.limit ?? 0) const { data: storageSizeData, isLoading: isLoadingStorageSizeData } = useDailyStatsQuery({ - projectRef: ref, + projectRef, attribute: TOTAL_STORAGE_SIZE_KEY, interval: '1d', startDate, diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index e5f85728f99..9d0ae2cf3e7 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -54,6 +54,7 @@ const Usage = () => {
{
- +
- +
- +
- +
) From 89ee0cc15d744c5983cfb632a6cd81bb408494d6 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 3 May 2023 14:36:07 +0800 Subject: [PATCH 14/45] Pad data with 0 values if no values from daily stats --- studio/data/analytics/daily-stats-query.ts | 40 +++++++++++++++------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/studio/data/analytics/daily-stats-query.ts b/studio/data/analytics/daily-stats-query.ts index 178e76319bc..4d38ff6eb4a 100644 --- a/studio/data/analytics/daily-stats-query.ts +++ b/studio/data/analytics/daily-stats-query.ts @@ -113,7 +113,7 @@ export const useDailyStatsQuery = ( startDate, endDate, interval = '1d', - dateFormat, + dateFormat = 'DD MMM', modifier, }: DailyStatsVariables, { enabled = true, ...options }: UseQueryOptions = {} @@ -130,20 +130,34 @@ export const useDailyStatsQuery = ( typeof endDate !== 'undefined', // @ts-ignore select(data) { - // [JOSHEN TODO] if no data, pad with empty data - return { - ...data, - data: data.data.map((x) => { + const noDataYet = Number(data.data[0].period_start) === 0 + + if (noDataYet) { + const days = dayjs(endDate).diff(dayjs(startDate), 'days') + const tempArray = new Array(days).fill(0) + const mockData = tempArray.map((x, idx) => { return { - ...x, - [attribute]: - modifier !== undefined ? modifier(Number(x[attribute])) : Number(x[attribute]), - period_start: - Number(x.period_start) > 0 - ? dayjs(x.period_start).format(dateFormat) - : x.period_start, + loopId: idx, + period_start: dayjs(startDate).add(idx, 'day').format(dateFormat), + [attribute]: 0, } - }), + }) + return { ...data, data: mockData } + } else { + return { + ...data, + data: data.data.map((x) => { + return { + ...x, + [attribute]: + modifier !== undefined ? modifier(Number(x[attribute])) : Number(x[attribute]), + period_start: + Number(x.period_start) > 0 + ? dayjs(x.period_start).format(dateFormat) + : x.period_start, + } + }), + } } }, ...options, From 26687e82419393e1ddcf6fa3645ae3401d146aa7 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 3 May 2023 15:09:39 +0800 Subject: [PATCH 15/45] Dynamically render icon status in usage category tabs --- .../interfaces/BillingV2/Billing.constants.ts | 1 + .../BillingV2/Usage/SizeAndCounts.tsx | 3 + .../BillingV2/Usage/Usage.constants.ts | 25 ++++++ .../interfaces/BillingV2/Usage/Usage.tsx | 80 ++++++++++--------- .../interfaces/BillingV2/Usage/Usage.utils.ts | 21 +++++ studio/data/analytics/daily-stats-query.ts | 1 + studio/data/usage/project-usage-query.ts | 16 ++++ 7 files changed, 108 insertions(+), 39 deletions(-) create mode 100644 studio/components/interfaces/BillingV2/Billing.constants.ts diff --git a/studio/components/interfaces/BillingV2/Billing.constants.ts b/studio/components/interfaces/BillingV2/Billing.constants.ts new file mode 100644 index 00000000000..0b7203cbf01 --- /dev/null +++ b/studio/components/interfaces/BillingV2/Billing.constants.ts @@ -0,0 +1 @@ +export const USAGE_APPROACHING_THRESHOLD = 0.8 diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index 4625e223aa5..1dd99bce697 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -14,6 +14,9 @@ export interface SizeAndCountsProps { projectRef: string } +// [Joshen TODO] If it's a paid tier, need to add information about disk size +// Reference to ProjectUsageBars or reports/database.tsx + const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { const { data: usage } = useProjectUsageQuery({ projectRef }) const { data: subscription } = useProjectSubscriptionQuery({ projectRef }) diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts index d3b789be076..154148b8314 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts @@ -1 +1,26 @@ export const Y_DOMAIN_CEILING_MULTIPLIER = 4 / 3 + +export const USAGE_STATUS = { + NORMAL: 'NORMAL', + APPROACHING: 'APPROACHING', + EXCEEDED: 'EXCEEDED', +} + +export const USAGE_CATEGORIES: { + key: 'infra' | 'bandwidth' | 'sizeCount' | 'activity' + name: string + attributes: string[] +}[] = [ + { + key: 'infra', + name: 'Infrastructure', + attributes: ['cpu_usage', 'ram_usage', 'disk_io_budget'], + }, + { key: 'bandwidth', name: 'Bandwidth', attributes: ['db_egress', 'storage_egress'] }, + { key: 'sizeCount', name: 'Size & Counts', attributes: ['db_size', 'storage_size'] }, + { + key: 'activity', + name: 'Activity', + attributes: ['monthly_active_users', 'storage_image_render_count', 'func_invocations'], + }, +] diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index 9d0ae2cf3e7..d98d8b20996 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -1,14 +1,18 @@ +import clsx from 'clsx' import { useParams } from 'common' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useProjectsQuery } from 'data/projects/projects-query' import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' -import { useEffect, useRef, useState } from 'react' -import { Button, IconCheckCircle, IconLoader, Listbox } from 'ui' -import Infrastructure from './Infrastructure' -import Bandwidth from './Bandwidth' -import SizeAndCounts from './SizeAndCounts' +import Link from 'next/link' +import { useRef, useState } from 'react' +import { Button, IconAlertCircle, IconCheckCircle, IconLoader, Listbox } from 'ui' import Activity from './Activity' -import clsx from 'clsx' +import Bandwidth from './Bandwidth' +import Infrastructure from './Infrastructure' +import SizeAndCounts from './SizeAndCounts' +import { useProjectUsageQuery } from 'data/usage/project-usage-query' +import { getUsageStatus } from './Usage.utils' +import { USAGE_CATEGORIES, USAGE_STATUS } from './Usage.constants' const Usage = () => { const { ref } = useParams() @@ -19,13 +23,12 @@ const Usage = () => { const sizeAndCountsRef = useRef(null) const activityRef = useRef(null) - const { data: projects, isLoading, isSuccess } = useProjectsQuery() + const { data: projects, isLoading } = useProjectsQuery() + const { data: usage } = useProjectUsageQuery({ projectRef: ref }) const { data: subscription, isLoading: isLoadingSubscription } = useProjectSubscriptionQuery({ projectRef: selectedProjectRef, }) - useEffect(() => {}, [isSuccess]) - const scrollTo = (id: 'infra' | 'bandwidth' | 'sizeCount' | 'activity') => { switch (id) { case 'infra': @@ -101,40 +104,39 @@ const Usage = () => { ) : null}
- - + + + + + + + + + +
-
scrollTo('infra')} - className="flex items-center opacity-50 space-x-2 py-3 hover:opacity-100 transition cursor-pointer" - > - -

Infrastructure

-
-
scrollTo('bandwidth')} - className="flex items-center opacity-50 space-x-2 py-3 hover:opacity-100 transition cursor-pointer" - > - -

Bandwidth

-
-
scrollTo('sizeCount')} - className="flex items-center opacity-50 space-x-2 py-3 hover:opacity-100 transition cursor-pointer" - > - -

Size & Counts

-
-
scrollTo('activity')} - className="flex items-center opacity-50 space-x-2 py-3 hover:opacity-100 transition cursor-pointer" - > - -

Activity

-
+ {USAGE_CATEGORIES.map((category) => { + const status = getUsageStatus(category.attributes, usage) + return ( +
scrollTo(category.key)} + className="flex items-center opacity-50 space-x-2 py-3 hover:opacity-100 transition cursor-pointer" + > + {status === USAGE_STATUS.NORMAL ? ( + + ) : status === USAGE_STATUS.APPROACHING ? ( + + ) : status === USAGE_STATUS.EXCEEDED ? ( + + ) : null} +

{category.name}

+
+ ) + })}
diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts b/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts index b795682583b..1fe841d96d8 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts @@ -1,4 +1,7 @@ import { DataPoint } from 'data/analytics/constants' +import { ProjectUsageResponse } from 'data/usage/project-usage-query' +import { USAGE_APPROACHING_THRESHOLD } from '../Billing.constants' +import { USAGE_STATUS } from './Usage.constants' // [Joshen] This is just for development export const generateUsageData = (attribute: string, days: number): DataPoint[] => { @@ -11,3 +14,21 @@ export const generateUsageData = (attribute: string, days: number): DataPoint[] } }) } + +export const getUsageStatus = (attributes: string[], usage?: ProjectUsageResponse) => { + if (!usage) return USAGE_STATUS.NORMAL + + const attributeStatuses = attributes.map((attribute) => { + const usageMeta = usage?.[attribute as keyof ProjectUsageResponse] + const usageRatio = + typeof usageMeta !== 'number' ? (usageMeta?.usage ?? 0) / (usageMeta?.limit ?? 0) : 0 + if (usageRatio >= 1) return USAGE_STATUS.EXCEEDED + else if (usageRatio >= USAGE_APPROACHING_THRESHOLD) return USAGE_STATUS.APPROACHING + else return USAGE_STATUS.NORMAL + }) + + if (attributeStatuses.find((x) => x === USAGE_STATUS.EXCEEDED)) return USAGE_STATUS.EXCEEDED + else if (attributeStatuses.find((x) => x === USAGE_STATUS.APPROACHING)) + return USAGE_STATUS.APPROACHING + else return USAGE_STATUS.NORMAL +} diff --git a/studio/data/analytics/daily-stats-query.ts b/studio/data/analytics/daily-stats-query.ts index 4d38ff6eb4a..d9f63c2e5db 100644 --- a/studio/data/analytics/daily-stats-query.ts +++ b/studio/data/analytics/daily-stats-query.ts @@ -132,6 +132,7 @@ export const useDailyStatsQuery = ( select(data) { const noDataYet = Number(data.data[0].period_start) === 0 + // [Joshen] Ideally handled by API, like infra-monitoring if (noDataYet) { const days = dayjs(endDate).diff(dayjs(startDate), 'days') const tempArray = new Array(days).fill(0) diff --git a/studio/data/usage/project-usage-query.ts b/studio/data/usage/project-usage-query.ts index e42fc0ab3e9..fafdb717786 100644 --- a/studio/data/usage/project-usage-query.ts +++ b/studio/data/usage/project-usage-query.ts @@ -59,6 +59,22 @@ export const useProjectUsageQuery = ( ({ signal }) => getProjectUsage({ projectRef }, signal), { enabled: enabled && typeof projectRef !== 'undefined', + // @ts-ignore, ensure that all usage values are numbers + select(data) { + const formattedData: any = {} + Object.keys(data).forEach((attribute: any) => { + const originalAttributeData = (data as any)[attribute] + const attributeData = + typeof originalAttributeData === 'object' + ? ({ + ...originalAttributeData, + usage: Number((data as any)[attribute].usage), + } as UsageMetric) + : originalAttributeData + formattedData[attribute] = attributeData + }) + return formattedData as ProjectUsageResponse + }, ...options, } ) From 62b0d72a8c71242b291ebc3ceecbd35278fd9573 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 3 May 2023 16:07:27 +0800 Subject: [PATCH 16/45] Refactor each billing section to render via a map rather than manually declaring each attribute --- .../interfaces/BillingV2/Usage/Activity.tsx | 372 +++++++----------- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 218 +++++----- .../BillingV2/Usage/SectionContent.tsx | 2 +- .../BillingV2/Usage/SizeAndCounts.tsx | 218 +++++----- .../BillingV2/Usage/Usage.constants.ts | 88 ++++- .../interfaces/BillingV2/Usage/Usage.tsx | 7 + .../interfaces/BillingV2/Usage/Usage.utils.ts | 8 +- 7 files changed, 431 insertions(+), 482 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index b5c7b444af3..d5cc12fffc7 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -1,14 +1,19 @@ -import { useParams } from 'common' +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 { useProjectUsageQuery } from 'data/usage/project-usage-query' -import { Button } from 'ui' +import { + ProjectUsageResponse, + UsageMetric, + useProjectUsageQuery, +} from 'data/usage/project-usage-query' +import { Button, IconAlertTriangle } from 'ui' +import { USAGE_APPROACHING_THRESHOLD } from '../Billing.constants' import BarChart from './BarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' -import { generateUsageData } from './Usage.utils' +import { USAGE_CATEGORIES } from './Usage.constants' export interface ActivityProps { projectRef: string @@ -20,10 +25,9 @@ const Activity = ({ projectRef }: ActivityProps) => { const { current_period_start, current_period_end } = subscription?.billing ?? {} const startDate = new Date((current_period_start ?? 0) * 1000).toISOString() const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() + const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'activity') const MAU_KEY = 'total_auth_billing_period_mau' - const { monthly_active_users } = usage ?? {} - const mauExcess = (monthly_active_users?.usage ?? 0) - (monthly_active_users?.limit ?? 0) const { data: mauData, isLoading: isLoadingMauData } = useDailyStatsQuery({ projectRef, attribute: MAU_KEY, @@ -33,9 +37,6 @@ const Activity = ({ projectRef }: ActivityProps) => { }) const ASSET_TRANSFORMATIONS_KEY = 'total_storage_image_render_count' - const { storage_image_render_count } = usage ?? {} - const assetTransformationExcess = - (storage_image_render_count?.usage ?? 0) - (storage_image_render_count?.limit ?? 0) const { data: assetTransformationsData, isLoading: isLoadingAssetTransformationsData } = useDailyStatsQuery({ projectRef, @@ -46,8 +47,6 @@ const Activity = ({ projectRef }: ActivityProps) => { }) const FUNC_INVOCATIONS_KEY = 'total_func_invocations' - const { func_invocations } = usage ?? {} - const funcInvocationsExcess = (func_invocations?.usage ?? 0) - (func_invocations?.limit ?? 0) const { data: funcInvocationsData, isLoading: isLoadingFuncInvocationsData } = useDailyStatsQuery( { projectRef, @@ -58,243 +57,136 @@ const Activity = ({ projectRef }: ActivityProps) => { } ) + if (categoryMeta === undefined) return null + return ( <> - -
-
-

- Monthly active users {subscription?.tier.key.toLowerCase()} quota usage -

- -
- -
-
-

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

-

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

-
-
-

Used

-

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

-
-
-

Extra volume used this month

-

{(mauExcess < 0 ? 0 : mauExcess).toLocaleString()}

-
-
-
-
-

Monthly active users over time

-

Some description here

-
- {isLoadingMauData ? ( -
- - - -
- ) : ( - value.toLocaleString()} - /> - )} -
+ {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) - -
-
-

- Asset transformations {subscription?.tier.key.toLowerCase()} quota usage -

- -
- -
-
-

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

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

None

- ) : ( -

- {(storage_image_render_count?.limit ?? 0).toLocaleString()} -

- )} -
-
-

Used

-

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

-
-
-

Extra volume used this month

-

- {((storage_image_render_count?.limit ?? 0) === -1 || assetTransformationExcess < 0 - ? 0 - : assetTransformationExcess - ).toLocaleString()} -

-
-
-
- {storage_image_render_count?.available_in_plan && ( - <> -
-

Asset transformations over time

-

Some description here

-
- {isLoadingAssetTransformationsData ? ( -
- - - + const isLoadingData = + attribute.key === 'monthly_active_users' + ? isLoadingMauData + : attribute.key === 'storage_image_render_count' + ? isLoadingAssetTransformationsData + : isLoadingFuncInvocationsData + const usageData = + attribute.key === 'monthly_active_users' + ? mauData + : attribute.key === 'storage_image_render_count' + ? assetTransformationsData + : funcInvocationsData + + return ( + +
+
+
+

+ {attribute.name} {subscription?.tier.key.toLowerCase()} quota usage +

+ {usageRatio >= 1 ? ( +
+ +

Exceeded limit

+
+ ) : usageRatio >= USAGE_APPROACHING_THRESHOLD ? ( +
+ +

Reaching limit

+
+ ) : null} +
+
- ) : ( - = 1 + ? 'bg-red-900' + : usageRatio >= USAGE_APPROACHING_THRESHOLD + ? 'bg-amber-900' + : 'bg-scale-1100' + )} + value={usageMeta?.usage ?? 0} + max={usageMeta?.limit ?? 0} /> +
+
+

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

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

None

+ ) : ( +

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

+ )} +
+
+

Used

+

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

+
+
+

Extra volume used this month

+

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

+
+
+
+ {usageMeta.available_in_plan && ( + <> +
+

{attribute.name} over time

+

{attribute.chartDescription}

+
+ {isLoadingData ? ( +
+ + + +
+ ) : ( + value.toLocaleString()} + /> + )} + )} - - )} -
- - -
-
-

- Edge function invocations {subscription?.tier.key.toLowerCase()} quota usage -

- -
- -
-
-

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

-

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

-
-
-

Used

-

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

-
-
-

Extra volume used this month

-

- {(funcInvocationsExcess < 0 ? 0 : funcInvocationsExcess).toLocaleString()} -

-
-
-
-
-

Edge function invocations over time

-

Some description here

-
- {isLoadingFuncInvocationsData ? ( -
- - - -
- ) : ( - value.toLocaleString()} - /> - )} -
- - {/* Sample Chart */} -
-
-
-
-
-

Sample Chart

-

Some description here

-
-
-
-
-
-

Sample quota usage

- -
- -
-
-

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

-

{(10000).toLocaleString()}

-
-
-

Used

-

0

-
-
-

Extra volume used this month

-

0

-
-
-
-
-

Sample usage over time

-

- Some description here to explain what this metric is about -

-
- -
-
-
-
+ + ) + })} ) } diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index edbfe152ad8..f7a660c8d70 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -1,14 +1,20 @@ -import { useParams } from 'common' +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 { useProjectUsageQuery } from 'data/usage/project-usage-query' +import { + ProjectUsageResponse, + UsageMetric, + useProjectUsageQuery, +} from 'data/usage/project-usage-query' +import { USAGE_APPROACHING_THRESHOLD } from 'lib/constants' import { formatBytes } from 'lib/helpers' -import { Button } from 'ui' +import { Button, IconAlertTriangle } from 'ui' import BarChart from './BarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' +import { USAGE_CATEGORIES } from './Usage.constants' export interface BandwidthProps { projectRef: string @@ -20,11 +26,10 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { const { current_period_start, current_period_end } = subscription?.billing ?? {} const startDate = new Date((current_period_start ?? 0) * 1000).toISOString() const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() + const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'bandwidth') // [JOSHEN TODO] Double check if this is the right attribute const DB_EGRESS_KEY = 'total_db_egress_bytes' - const { db_egress } = usage ?? {} - const dbEgressExcess = (db_egress?.usage ?? 0) - (db_egress?.limit ?? 0) const { data: dbEgressData, isLoading: isLoadingDbEgressData } = useDailyStatsQuery({ projectRef, attribute: DB_EGRESS_KEY, @@ -34,8 +39,6 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { }) const STORAGE_EGRESS_KEY = 'total_storage_egress' - const { storage_egress } = usage ?? {} - const storageEgressExcess = (storage_egress?.usage ?? 0) - (storage_egress?.limit ?? 0) const { data: storageEgressData, isLoading: isLoadingStorageEgressData } = useDailyStatsQuery({ projectRef, attribute: STORAGE_EGRESS_KEY, @@ -44,123 +47,104 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { endDate, }) + if (categoryMeta === undefined) return null + return ( <> - -
-
-

Egress {subscription?.tier.key.toLowerCase()} quota usage

- -
- -
-
-

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

-

{formatBytes(db_egress?.limit ?? 0)}

-
-
-

Used

-

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

-
-
-

Extra volume used this month

-

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

-
-
-
-
-

Database egress over time

-

Some description here

-
- {isLoadingDbEgressData ? ( -
- - - -
- ) : ( - formatBytes(value, 1, 'GB').replace(/\s/g, '')} - /> - )} -
+ {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) - -
-
-

- Storage egress {subscription?.tier.key.toLowerCase()} quota usage -

- -
- -
-
-

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

-

{formatBytes(storage_egress?.limit ?? 0)}

+ const isLoadingData = + attribute.key === 'db_egress' ? isLoadingDbEgressData : isLoadingStorageEgressData + const usageData = attribute.key === 'db_egress' ? dbEgressData : storageEgressData + + return ( + +
+
+
+

+ {attribute.name} {subscription?.tier.key.toLowerCase()} quota usage +

+ {usageRatio >= 1 ? ( +
+ +

Exceeded limit

+
+ ) : usageRatio >= USAGE_APPROACHING_THRESHOLD ? ( +
+ +

Reaching limit

+
+ ) : null} +
+ +
+ = 1 + ? 'bg-red-900' + : usageRatio >= USAGE_APPROACHING_THRESHOLD + ? 'bg-amber-900' + : 'bg-scale-1100' + )} + value={usageMeta?.usage ?? 0} + max={usageMeta?.limit ?? 0} + /> +
+
+

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

+

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

+
+
+

Used

+

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

+
+
+

Extra volume used this month

+

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

+
+
-
-

Used

-

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

+
+

{attribute.name} over time

+

{attribute.chartDescription}

-
-

Extra volume used this month

-

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

-
-
-
-
-

Storage egress over time

-

Some description here

-
- {isLoadingStorageEgressData ? ( -
- - - -
- ) : ( - formatBytes(value, 1, 'GB').replace(/\s/g, '')} - /> - )} - + {isLoadingData ? ( +
+ + + +
+ ) : ( + formatBytes(value, 1, 'GB').replace(/\s/g, '')} + /> + )} + + ) + })} ) } diff --git a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx index 1f3dc1d0deb..a0b6a45dba1 100644 --- a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx @@ -12,7 +12,7 @@ const SectionContent = ({ title, description, children }: PropsWithChildren
-

{title}

+

{title}

{description}

diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index 1dd99bce697..495551464f6 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -1,14 +1,20 @@ -import { useParams } from 'common' +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 { useProjectUsageQuery } from 'data/usage/project-usage-query' +import { + ProjectUsageResponse, + UsageMetric, + useProjectUsageQuery, +} from 'data/usage/project-usage-query' import { formatBytes } from 'lib/helpers' -import { Button } from 'ui' +import { Button, IconAlertTriangle } from 'ui' +import { USAGE_APPROACHING_THRESHOLD } from '../Billing.constants' import BarChart from './BarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' +import { USAGE_CATEGORIES } from './Usage.constants' export interface SizeAndCountsProps { projectRef: string @@ -23,10 +29,9 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { const { current_period_start, current_period_end } = subscription?.billing ?? {} const startDate = new Date((current_period_start ?? 0) * 1000).toISOString() const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() + const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'sizeCount') const TOTAL_DB_SIZE_KEY = 'total_db_size_bytes' - const { db_size } = usage ?? {} - const dbSizeExcess = (db_size?.usage ?? 0) - (db_size?.limit ?? 0) const { data: dbSizeData, isLoading: isLoadingDbSizeData } = useDailyStatsQuery({ projectRef, attribute: TOTAL_DB_SIZE_KEY, @@ -36,8 +41,6 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { }) const TOTAL_STORAGE_SIZE_KEY = 'total_storage_size_bytes' - const { storage_size } = usage ?? {} - const storageSizeExcess = (storage_size?.usage ?? 0) - (storage_size?.limit ?? 0) const { data: storageSizeData, isLoading: isLoadingStorageSizeData } = useDailyStatsQuery({ projectRef, attribute: TOTAL_STORAGE_SIZE_KEY, @@ -46,121 +49,106 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { endDate, }) + if (categoryMeta === undefined) return null + return ( <> - -
-
-

Database size per day

- -
- -
-
-

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

-

{formatBytes(db_size?.limit ?? 0)}

-
-
-

Used

-

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

-
-
-

Extra volume used this month

-

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

-
-
-
-
-

Database egress over time

-

Some description here

-
- {isLoadingDbSizeData ? ( -
- - - -
- ) : ( - formatBytes(value, 1, 'GB').replace(/\s/g, '')} - /> - )} -
+ {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) - -
-
-

Storage {subscription?.tier.key.toLowerCase()} quota usage

- -
- -
-
-

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

-

{formatBytes(storage_size?.limit ?? 0)}

+ const isLoadingData = + attribute.key === 'db_size' ? isLoadingDbSizeData : isLoadingStorageSizeData + const usageData = attribute.key === 'db_size' ? dbSizeData : storageSizeData + + return ( + +
+
+
+

+ {attribute.name} {subscription?.tier.key.toLowerCase()} quota usage +

+ {usageRatio >= 1 ? ( +
+ +

Exceeded limit

+
+ ) : usageRatio >= USAGE_APPROACHING_THRESHOLD ? ( +
+ +

Reaching limit

+
+ ) : null} +
+ +
+ = 1 + ? 'bg-red-900' + : usageRatio >= USAGE_APPROACHING_THRESHOLD + ? 'bg-amber-900' + : 'bg-scale-1100' + )} + value={usageMeta?.usage ?? 0} + max={usageMeta?.limit ?? 0} + /> +
+
+

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

+

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

+
+
+

Used

+

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

+
+
+

Extra volume used this month

+

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

+
+
-
-

Used

-

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

+
+

{attribute.name} over time

+

{attribute.chartDescription}

-
-

Extra volume used this month

-

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

-
-
-
-
-

Storage size over time

-

Some description here

-
- {isLoadingStorageSizeData ? ( -
- - - -
- ) : ( - formatBytes(value, 1, 'GB').replace(/\s/g, '')} - /> - )} - + {isLoadingData ? ( +
+ + + +
+ ) : ( + formatBytes(value, 1, 'GB').replace(/\s/g, '')} + /> + )} + + ) + })} ) } diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts index 154148b8314..68ad0b6806e 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts @@ -6,21 +6,99 @@ export const USAGE_STATUS = { EXCEEDED: 'EXCEEDED', } +export interface CategoryAttribute { + key: string + name: string + description: string + chartDescription: string +} export const USAGE_CATEGORIES: { key: 'infra' | 'bandwidth' | 'sizeCount' | 'activity' name: string - attributes: string[] + attributes: CategoryAttribute[] }[] = [ { key: 'infra', name: 'Infrastructure', - attributes: ['cpu_usage', 'ram_usage', 'disk_io_budget'], + attributes: [ + { + key: 'cpu_usage', + name: 'CPU usage', + description: 'Some description here', + chartDescription: 'Some description here', + }, + { + key: 'ram_usage', + name: 'Memory usage', + description: 'Some description here', + chartDescription: 'Some description here', + }, + { + key: 'disk_io_budget', + name: 'IO budget', + description: 'Some description here', + chartDescription: 'Some description here', + }, + ], + }, + { + key: 'bandwidth', + name: 'Bandwidth', + attributes: [ + { + key: 'db_egress', + name: 'Database egress', + description: 'Some description here', + chartDescription: 'Some description here', + }, + { + key: 'storage_egress', + name: 'Storage egress', + description: 'Some description here', + chartDescription: 'Some description here', + }, + ], + }, + { + key: 'sizeCount', + name: 'Size & Counts', + attributes: [ + { + key: 'db_size', + name: 'Database size', + description: 'Some description here', + chartDescription: 'Some description here', + }, + { + key: 'storage_size', + name: 'Storage size', + description: 'Some description here', + chartDescription: 'Some description here', + }, + ], }, - { key: 'bandwidth', name: 'Bandwidth', attributes: ['db_egress', 'storage_egress'] }, - { key: 'sizeCount', name: 'Size & Counts', attributes: ['db_size', 'storage_size'] }, { key: 'activity', name: 'Activity', - attributes: ['monthly_active_users', 'storage_image_render_count', 'func_invocations'], + attributes: [ + { + key: 'monthly_active_users', + name: 'Monthly active users (MAU)', + description: 'Some description here', + chartDescription: 'Some description here', + }, + { + key: 'storage_image_render_count', + name: 'Storage image transformations', + description: 'Some description here', + chartDescription: 'Some description here', + }, + { + key: 'func_invocations', + name: 'Edge function invocations', + description: 'Some description here', + chartDescription: 'Some description here', + }, + ], }, ] diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index d98d8b20996..8b4ec1fe140 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -120,6 +120,7 @@ const Usage = () => {
{USAGE_CATEGORIES.map((category) => { const status = getUsageStatus(category.attributes, usage) + return (
{
+ {/* + [Joshen] Could potentially run a map here based on USAGE_CATEGORIES, rather than defining each section + but thinking it's gonna "cover up" too much details and make it harder to add attribute specific components + e.g for database size, we also need to show disk volume size. Not to mention that are little nuances across + each attribute RE formatting (bytes vs locale string) + */}
diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts b/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts index 1fe841d96d8..410d3978d89 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts @@ -1,9 +1,9 @@ import { DataPoint } from 'data/analytics/constants' import { ProjectUsageResponse } from 'data/usage/project-usage-query' import { USAGE_APPROACHING_THRESHOLD } from '../Billing.constants' -import { USAGE_STATUS } from './Usage.constants' +import { CategoryAttribute, USAGE_STATUS } from './Usage.constants' -// [Joshen] This is just for development +// [Joshen] This is just for development to generate some test data for chart rendering export const generateUsageData = (attribute: string, days: number): DataPoint[] => { const tempArray = new Array(days).fill(0) return tempArray.map((x, idx) => { @@ -15,11 +15,11 @@ export const generateUsageData = (attribute: string, days: number): DataPoint[] }) } -export const getUsageStatus = (attributes: string[], usage?: ProjectUsageResponse) => { +export const getUsageStatus = (attributes: CategoryAttribute[], usage?: ProjectUsageResponse) => { if (!usage) return USAGE_STATUS.NORMAL const attributeStatuses = attributes.map((attribute) => { - const usageMeta = usage?.[attribute as keyof ProjectUsageResponse] + const usageMeta = usage?.[attribute.key as keyof ProjectUsageResponse] const usageRatio = typeof usageMeta !== 'number' ? (usageMeta?.usage ?? 0) / (usageMeta?.limit ?? 0) : 0 if (usageRatio >= 1) return USAGE_STATUS.EXCEEDED From 1ffa2b869667165b02e272e1e14aa7d950627d89 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 3 May 2023 16:25:32 +0800 Subject: [PATCH 17/45] Add disk size under db size segment --- .../BillingV2/Usage/SizeAndCounts.tsx | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index 495551464f6..37317e1d0a7 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -9,12 +9,13 @@ import { useProjectUsageQuery, } from 'data/usage/project-usage-query' import { formatBytes } from 'lib/helpers' -import { Button, IconAlertTriangle } from 'ui' +import { Badge, Button, IconAlertTriangle, IconExternalLink } from 'ui' import { USAGE_APPROACHING_THRESHOLD } from '../Billing.constants' import BarChart from './BarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' +import Link from 'next/link' export interface SizeAndCountsProps { projectRef: string @@ -51,6 +52,8 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { if (categoryMeta === undefined) return null + console.log({ usage }) + return ( <> @@ -124,6 +127,26 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => {
+ + {attribute.key === 'db_size' && ( +
+
+

Disk size:

+

{usage?.disk_volume_size_gb} GB

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

{attribute.name} over time

{attribute.chartDescription}

From 03f5f07deaf88c846768c14c66a4cfbe9c0919a6 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 3 May 2023 17:32:07 +0800 Subject: [PATCH 18/45] Refactor + include missing charts in usage page --- .../interfaces/BillingV2/Usage/Activity.tsx | 101 ++++++++++++------ .../interfaces/BillingV2/Usage/Bandwidth.tsx | 22 ++-- .../BillingV2/Usage/SectionContent.tsx | 10 +- .../BillingV2/Usage/SizeAndCounts.tsx | 101 +++++++++++------- .../BillingV2/Usage/Usage.constants.ts | 99 ++++++++++++++--- studio/data/analytics/daily-stats-query.ts | 1 + 6 files changed, 229 insertions(+), 105 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index d5cc12fffc7..150cad37dcf 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -27,36 +27,92 @@ const Activity = ({ projectRef }: ActivityProps) => { const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'activity') - const MAU_KEY = 'total_auth_billing_period_mau' const { data: mauData, isLoading: isLoadingMauData } = useDailyStatsQuery({ projectRef, - attribute: MAU_KEY, + attribute: 'total_auth_billing_period_mau', + interval: '1d', + startDate, + endDate, + }) + + const { data: mauSSOData, isLoading: isLoadingMauSSOData } = useDailyStatsQuery({ + projectRef, + attribute: 'total_auth_billing_period_sso_mau', interval: '1d', startDate, endDate, }) - const ASSET_TRANSFORMATIONS_KEY = 'total_storage_image_render_count' const { data: assetTransformationsData, isLoading: isLoadingAssetTransformationsData } = useDailyStatsQuery({ projectRef, - attribute: ASSET_TRANSFORMATIONS_KEY, + attribute: 'total_storage_image_render_count', interval: '1d', startDate, endDate, }) - const FUNC_INVOCATIONS_KEY = 'total_func_invocations' const { data: funcInvocationsData, isLoading: isLoadingFuncInvocationsData } = useDailyStatsQuery( { projectRef, - attribute: FUNC_INVOCATIONS_KEY, + attribute: 'total_func_invocations', interval: '1d', startDate, endDate, } ) + const { data: realtimeMessagesData, isLoading: isLoadingRealtimeMessagesData } = + useDailyStatsQuery({ + projectRef, + attribute: 'total_realtime_message_count', + interval: '1d', + startDate, + endDate, + }) + + const { data: realtimeConnectionsData, isLoading: isLoadingRealtimeConnectionsData } = + useDailyStatsQuery({ + projectRef, + attribute: 'total_realtime_peak_connection', + interval: '1d', + startDate, + endDate, + }) + + const chartMeta: any = { + monthly_active_users: { + isLoading: isLoadingMauData, + data: mauData?.data ?? [], + margin: 18, + }, + monthly_active_sso_users: { + isLoading: isLoadingMauSSOData, + data: mauSSOData?.data ?? [], + margin: 20, + }, + storage_image_render_count: { + isLoading: isLoadingAssetTransformationsData, + data: assetTransformationsData?.data ?? [], + margin: 0, + }, + func_invocations: { + isLoading: isLoadingFuncInvocationsData, + data: funcInvocationsData?.data ?? [], + margin: 26, + }, + realtime_message_count: { + isLoading: isLoadingRealtimeMessagesData, + data: realtimeMessagesData?.data ?? [], + margin: 38, + }, + realtime_peak_connection: { + isLoading: isLoadingRealtimeConnectionsData, + data: realtimeConnectionsData?.data ?? [], + margin: 0, + }, + } + if (categoryMeta === undefined) return null return ( @@ -69,19 +125,6 @@ const Activity = ({ projectRef }: ActivityProps) => { typeof usageMeta !== 'number' ? (usageMeta?.usage ?? 0) / (usageMeta?.limit ?? 0) : 0 const usageExcess = (usageMeta?.usage ?? 0) - (usageMeta?.limit ?? 0) - const isLoadingData = - attribute.key === 'monthly_active_users' - ? isLoadingMauData - : attribute.key === 'storage_image_render_count' - ? isLoadingAssetTransformationsData - : isLoadingFuncInvocationsData - const usageData = - attribute.key === 'monthly_active_users' - ? mauData - : attribute.key === 'storage_image_render_count' - ? assetTransformationsData - : funcInvocationsData - return ( {

{attribute.name} over time

{attribute.chartDescription}

- {isLoadingData ? ( + {chartMeta[attribute.key].isLoading ? (
@@ -163,22 +206,10 @@ const Activity = ({ projectRef }: ActivityProps) => { ) : ( value.toLocaleString()} /> )} diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index f7a660c8d70..4296da6b307 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -28,25 +28,27 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'bandwidth') - // [JOSHEN TODO] Double check if this is the right attribute - const DB_EGRESS_KEY = 'total_db_egress_bytes' const { data: dbEgressData, isLoading: isLoadingDbEgressData } = useDailyStatsQuery({ projectRef, - attribute: DB_EGRESS_KEY, + attribute: 'total_egress_modified', interval: '1d', startDate, endDate, }) - const STORAGE_EGRESS_KEY = 'total_storage_egress' const { data: storageEgressData, isLoading: isLoadingStorageEgressData } = useDailyStatsQuery({ projectRef, - attribute: STORAGE_EGRESS_KEY, + attribute: 'total_storage_egress', interval: '1d', startDate, endDate, }) + const chartMeta: any = { + db_egress: { isLoading: isLoadingDbEgressData, data: dbEgressData?.data ?? [] }, + storage_egress: { isLoading: isLoadingStorageEgressData, data: storageEgressData?.data ?? [] }, + } + if (categoryMeta === undefined) return null return ( @@ -59,10 +61,6 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { typeof usageMeta !== 'number' ? (usageMeta?.usage ?? 0) / (usageMeta?.limit ?? 0) : 0 const usageExcess = (usageMeta?.usage ?? 0) - (usageMeta?.limit ?? 0) - const isLoadingData = - attribute.key === 'db_egress' ? isLoadingDbEgressData : isLoadingStorageEgressData - const usageData = attribute.key === 'db_egress' ? dbEgressData : storageEgressData - return ( {

{attribute.name} over time

{attribute.chartDescription}

- {isLoadingData ? ( + {chartMeta[attribute.key]?.isLoading ? (
@@ -135,8 +133,8 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { ) : ( formatBytes(value, 1, 'GB').replace(/\s/g, '')} diff --git a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx index a0b6a45dba1..7168b0b63cc 100644 --- a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx @@ -9,11 +9,15 @@ const SectionContent = ({ title, description, children }: PropsWithChildren
-
+
-
+

{title}

-

{description}

+ {description.split('\n').map((value, idx) => ( +

+ {value} +

+ ))}
{children}
diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index 37317e1d0a7..728bd1e070d 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -8,22 +8,20 @@ import { UsageMetric, useProjectUsageQuery, } from 'data/usage/project-usage-query' +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' import { USAGE_APPROACHING_THRESHOLD } from '../Billing.constants' import BarChart from './BarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' -import Link from 'next/link' export interface SizeAndCountsProps { projectRef: string } -// [Joshen TODO] If it's a paid tier, need to add information about disk size -// Reference to ProjectUsageBars or reports/database.tsx - const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { const { data: usage } = useProjectUsageQuery({ projectRef }) const { data: subscription } = useProjectSubscriptionQuery({ projectRef }) @@ -32,27 +30,37 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'sizeCount') - const TOTAL_DB_SIZE_KEY = 'total_db_size_bytes' const { data: dbSizeData, isLoading: isLoadingDbSizeData } = useDailyStatsQuery({ projectRef, - attribute: TOTAL_DB_SIZE_KEY, + attribute: 'total_db_size_bytes', interval: '1d', startDate, endDate, }) - const TOTAL_STORAGE_SIZE_KEY = 'total_storage_size_bytes' const { data: storageSizeData, isLoading: isLoadingStorageSizeData } = useDailyStatsQuery({ projectRef, - attribute: TOTAL_STORAGE_SIZE_KEY, + attribute: 'total_storage_size_bytes', interval: '1d', startDate, endDate, }) - if (categoryMeta === undefined) return null + const { data: functionCountData, isLoading: isLoadingFunctionCountData } = useDailyStatsQuery({ + projectRef, + attribute: 'total_func_count', + interval: '1d', + startDate, + endDate, + }) - console.log({ usage }) + const chartMeta: any = { + db_size: { isLoading: isLoadingDbSizeData, data: dbSizeData?.data ?? [] }, + storage_size: { isLoading: isLoadingStorageSizeData, data: storageSizeData?.data ?? [] }, + func_count: { isLoading: isLoadingFunctionCountData, data: functionCountData?.data ?? [] }, + } + + if (categoryMeta === undefined) return null return ( <> @@ -64,10 +72,6 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { typeof usageMeta !== 'number' ? (usageMeta?.usage ?? 0) / (usageMeta?.limit ?? 0) : 0 const usageExcess = (usageMeta?.usage ?? 0) - (usageMeta?.limit ?? 0) - const isLoadingData = - attribute.key === 'db_size' ? isLoadingDbSizeData : isLoadingStorageSizeData - const usageData = attribute.key === 'db_size' ? dbSizeData : storageSizeData - return ( {

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

-

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

+

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

Used

-

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

+

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

Extra volume used this month

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

- {attribute.key === 'db_size' && ( -
-
-

Disk size:

-

{usage?.disk_volume_size_gb} GB

- - Auto-scaling - + {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.name} over time

{attribute.chartDescription}

- {isLoadingData ? ( + {chartMeta[attribute.key].isLoading ? (
@@ -160,13 +179,15 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { ) : ( formatBytes(value, 1, 'GB').replace(/\s/g, '')} + yFormatter={(value) => + attribute.unit === 'bytes' + ? formatBytes(value, 1, 'GB').replace(/\s/g, '') + : value.toLocaleString() + } /> )} diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts index 68ad0b6806e..b24866490ea 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts @@ -7,8 +7,11 @@ export const USAGE_STATUS = { } export interface CategoryAttribute { - key: string + key: string // Property from project usage + attribute: string // For querying against stats-daily / infra-monitoring name: string + unit: 'bytes' | 'absolute' + docsUrl?: string description: string chartDescription: string } @@ -23,19 +26,25 @@ export const USAGE_CATEGORIES: { attributes: [ { key: 'cpu_usage', + attribute: 'cpu_usage', name: 'CPU usage', + unit: 'absolute', description: 'Some description here', chartDescription: 'Some description here', }, { key: 'ram_usage', + attribute: 'ram_usage', name: 'Memory usage', + unit: 'absolute', description: 'Some description here', chartDescription: 'Some description here', }, { key: 'disk_io_budget', + attribute: 'disk_io_budget', name: 'IO budget', + unit: 'absolute', description: 'Some description here', chartDescription: 'Some description here', }, @@ -47,15 +56,22 @@ export const USAGE_CATEGORIES: { attributes: [ { key: 'db_egress', + attribute: 'total_egress_modified', name: 'Database egress', - description: 'Some description here', - chartDescription: 'Some description here', + unit: 'bytes', + description: 'Contains any outgoing traffic (egress) from your database', + chartDescription: + 'Billing is based on the total sum of egress in GB throughout your billing period. The data shown here is refreshed over a period of 24 hours.', }, { key: 'storage_egress', + attribute: 'total_storage_egress', name: 'Storage egress', - description: 'Some description here', - chartDescription: 'Some description here', + unit: 'bytes', + description: + 'Contains any outgoing traffic (egress) from your storage buckets (only download operations are counted). We currently do not differentiate between no-cache and cache hits.', + chartDescription: + 'Billing is based on the total amount of egress in GB throughout your billing period. The data shown here is refreshed over a period of 24 hours.', }, ], }, @@ -65,15 +81,31 @@ export const USAGE_CATEGORIES: { attributes: [ { key: 'db_size', + attribute: 'total_db_size_bytes', name: 'Database size', - description: 'Some description here', - chartDescription: 'Some description here', + unit: 'bytes', + description: "Size of your project's database", + docsUrl: 'https://supabase.com/docs/guides/platform/database-usage', + chartDescription: + 'Billing is based on the average daily database size in GB throughout the billing period. The data shown here is refreshed over a period of 24 hours.', }, { key: 'storage_size', + attribute: 'total_storage_size_bytes', name: 'Storage size', - description: 'Some description here', - chartDescription: 'Some description here', + unit: 'bytes', + description: 'Sum of all objects in your storage buckets', + chartDescription: + 'Billing is based on the average size in GB throughout your billing period. The data shown here is refreshed over a period of 24 hours.', + }, + { + key: 'func_count', + attribute: 'total_func_count', + name: 'Edge function count', + unit: 'absolute', + description: 'Number of serverless functions in your project', + chartDescription: + 'Billing is based on the maximum amount of functions at any point in time throughout your billing period. The data shown here is refreshed over a period of 24 hours.', }, ], }, @@ -83,21 +115,58 @@ export const USAGE_CATEGORIES: { attributes: [ { key: 'monthly_active_users', + attribute: 'total_auth_billing_period_mau', name: 'Monthly active users (MAU)', - description: 'Some description here', - chartDescription: 'Some description here', + unit: 'absolute', + description: + 'The amount of distinct users requesting your API throughout the billing period. Resets at the beginning of every billing period.', + chartDescription: 'The data shown here is refreshed over a period of 24 hours.', + }, + { + key: 'monthly_active_sso_users', + attribute: 'total_auth_billing_period_sso_mau', + name: 'Monthly active single sign-on (SSO) users', + unit: 'absolute', + description: + 'The amount of distinct Single Sign-On users requesting your API throughout the billing period. Resets at the beginning of every billing period.', + chartDescription: 'The data shown here is refreshed over a period of 24 hours.', }, { key: 'storage_image_render_count', + attribute: 'total_storage_image_render_count', name: 'Storage image transformations', - description: 'Some description here', - chartDescription: 'Some description here', + unit: 'absolute', + description: + 'We distinctly count all images that were transformed in the billing period, ignoring any transformations.\nIf you transform one image with different transformations, it only counts as one. We only count the unique (origin) images being transformed.', + chartDescription: 'The data shown here is refreshed over a period of 24 hours.', }, { key: 'func_invocations', + attribute: 'total_func_invocations', name: 'Edge function invocations', - description: 'Some description here', - chartDescription: 'Some description here', + unit: 'absolute', + description: + 'Every single serverless function invocation independent of response status is counted. ', + chartDescription: + 'Billing is based on the sum of all invocations throughout your billing period. The data shown here is refreshed over a period of 24 hours.', + }, + { + key: 'realtime_message_count', + attribute: 'total_realtime_message_count', + name: 'Realtime message count', + unit: 'absolute', + description: 'Total number of realtime messages sent', + chartDescription: + 'Billing is based on the total amount of messages throughout your billing period. The data shown here is refreshed over a period of 24 hours.', + }, + { + key: 'realtime_peak_connection', + attribute: 'total_realtime_peak_connection', + name: 'Realtime peak connections', + unit: 'absolute', + description: 'Total number of successful connections (not connection attempts)', + chartDescription: + 'Billing is based on the maximum amount of concurrent peak connections throughout your billing period.', }, ], }, diff --git a/studio/data/analytics/daily-stats-query.ts b/studio/data/analytics/daily-stats-query.ts index d9f63c2e5db..b7283e76b8a 100644 --- a/studio/data/analytics/daily-stats-query.ts +++ b/studio/data/analytics/daily-stats-query.ts @@ -11,6 +11,7 @@ export type DailyStatsVariables = { attribute: | 'total_db_egress_bytes' | 'total_db_size_bytes' + | 'total_egress_modified' // Realtime | 'total_realtime_ingress' From a20b803fc2909a58222e1eaaa5989dad281e068c Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 3 May 2023 17:38:48 +0800 Subject: [PATCH 19/45] Add badge to sections which are not included in plan --- .../interfaces/BillingV2/Usage/Activity.tsx | 1 + .../BillingV2/Usage/SectionContent.tsx | 20 +++++++++++++++++-- .../BillingV2/Usage/Usage.constants.ts | 4 ++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 150cad37dcf..bcdf36faca2 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -130,6 +130,7 @@ const Activity = ({ projectRef }: ActivityProps) => { key={attribute.key} title={attribute.name} description={attribute.description} + includedInPlan={usageMeta.available_in_plan} >
diff --git a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx index 7168b0b63cc..1f8407d2025 100644 --- a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx @@ -1,23 +1,39 @@ import { PropsWithChildren } from 'react' +import { Badge } from 'ui' export interface SectionContent { title: string description: string + includedInPlan?: boolean } -const SectionContent = ({ title, description, children }: PropsWithChildren) => { +const SectionContent = ({ + title, + description, + includedInPlan, + children, +}: PropsWithChildren) => { return (
-

{title}

+
+

{title}

+ {includedInPlan === false && Not included} +
{description.split('\n').map((value, idx) => (

{value}

))} + {/* [Joshen] Feels a little too wordy if include below CTA */} + {/* {includedInPlan === false && ( +

+ Upgrade your project to have access to {title} +

+ )} */}
{children}
diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts index b24866490ea..b987c8837be 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts @@ -116,7 +116,7 @@ export const USAGE_CATEGORIES: { { key: 'monthly_active_users', attribute: 'total_auth_billing_period_mau', - name: 'Monthly active users (MAU)', + name: 'Monthly active users', unit: 'absolute', description: 'The amount of distinct users requesting your API throughout the billing period. Resets at the beginning of every billing period.', @@ -125,7 +125,7 @@ export const USAGE_CATEGORIES: { { key: 'monthly_active_sso_users', attribute: 'total_auth_billing_period_sso_mau', - name: 'Monthly active single sign-on (SSO) users', + name: 'Monthly active single sign-on users', unit: 'absolute', description: 'The amount of distinct Single Sign-On users requesting your API throughout the billing period. Resets at the beginning of every billing period.', From 3b0f0766ce1fb185fbc2f580deee592247719a5e Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 3 May 2023 18:11:22 +0800 Subject: [PATCH 20/45] Add link to docs for usage page --- .../interfaces/BillingV2/Usage/Activity.tsx | 3 +- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 6 +- .../BillingV2/Usage/Infrastructure.tsx | 122 ++++++++---------- .../BillingV2/Usage/SectionContent.tsx | 51 +++++--- .../BillingV2/Usage/SizeAndCounts.tsx | 6 +- .../BillingV2/Usage/Usage.constants.ts | 17 ++- 6 files changed, 97 insertions(+), 108 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index bcdf36faca2..d6b874ca7ed 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -128,8 +128,7 @@ const Activity = ({ projectRef }: ActivityProps) => { return (
diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 4296da6b307..7947033f423 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -62,11 +62,7 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { const usageExcess = (usageMeta?.usage ?? 0) - (usageMeta?.limit ?? 0) return ( - +
diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 4b80ab93871..520063dc3ff 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -4,110 +4,94 @@ import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscrip import BarChart from './BarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' +import { USAGE_CATEGORIES } from './Usage.constants' export interface InfrastructureProps { projectRef: string } +// [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 startDate = new Date((current_period_start ?? 0) * 1000).toISOString() const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() + const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'infra') - const CPU_USAGE_KEY = 'cpu_usage' const { data: cpuUsageData, isLoading: isLoadingCpuUsageData } = useInfraMonitoringQuery({ projectRef, - attribute: CPU_USAGE_KEY, + attribute: 'cpu_usage', interval: '1d', startDate, endDate, }) - const MEMORY_USAGE_KEY = 'ram_usage' const { data: memoryUsageData, isLoading: isLoadingMemoryUsageData } = useInfraMonitoringQuery({ projectRef, - attribute: MEMORY_USAGE_KEY, + attribute: 'ram_usage', interval: '1d', startDate, endDate, }) - const DISK_IO_BUDGET_KEY = 'disk_io_budget' const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({ projectRef, - attribute: DISK_IO_BUDGET_KEY, + attribute: 'disk_io_budget', interval: '1d', startDate, endDate, }) + const chartMeta: any = { + cpu_usage: { isLoading: isLoadingCpuUsageData, data: cpuUsageData?.data ?? [] }, + ram_usage: { isLoading: isLoadingMemoryUsageData, data: memoryUsageData?.data ?? [] }, + disk_io_budget: { isLoading: isLoadingIoBudgetData, data: ioBudgetData?.data ?? [] }, + } + + if (categoryMeta === undefined) return null + return ( <> - - -
-

Max CPU usage each day

-

Some description here

-
- {isLoadingCpuUsageData ? ( -
- - - -
- ) : ( - - )} -
- - -
-

Max memory usage each day

-

Some description here

-
- {isLoadingMemoryUsageData ? ( -
- - - -
- ) : ( - - )} -
- - -
-

Disk IO budget per day

-

Some description here

-
- {isLoadingIoBudgetData ? ( -
- - - -
- ) : ( - - )} -
+ {categoryMeta.attributes.map((attribute) => { + return ( + +
+ {attribute.key === 'disk_io_budget' ? ( +

{attribute.name} remaining per day

+ ) : ( +

+ Max{' '} + + {attribute.name} + {' '} + usage each day +

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

+ {paragraph} +

+ ))} +
+ {chartMeta[attribute.key].isLoading ? ( +
+ + + +
+ ) : ( + `${value}%`} + yLimit={100} + /> + )} +
+ ) + })} ) } diff --git a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx index 1f8407d2025..440bf5616fb 100644 --- a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx @@ -1,39 +1,50 @@ import { PropsWithChildren } from 'react' -import { Badge } from 'ui' +import { Badge, Button, IconExternalLink } from 'ui' +import { CategoryAttribute } from './Usage.constants' +import Link from 'next/link' export interface SectionContent { - title: string - description: string + section: CategoryAttribute includedInPlan?: boolean } const SectionContent = ({ - title, - description, + section, includedInPlan, children, }: PropsWithChildren) => { + const { name, description, docsUrl } = section + return (
-
-
-

{title}

- {includedInPlan === false && Not included} +
+
+
+

{name}

+ {includedInPlan === false && Not included} +
+ {description.split('\n').map((value, idx) => ( +

+ {value} +

+ ))}
- {description.split('\n').map((value, idx) => ( -

- {value} -

- ))} - {/* [Joshen] Feels a little too wordy if include below CTA */} - {/* {includedInPlan === false && ( -

- Upgrade your project to have access to {title} -

- )} */} + {docsUrl !== undefined && ( +
+

More information

+ + +
+

Documentation

+ +
+
+ +
+ )}
{children}
diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index 728bd1e070d..7e713d6ab57 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -73,11 +73,7 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { const usageExcess = (usageMeta?.usage ?? 0) - (usageMeta?.limit ?? 0) return ( - +
diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts index b987c8837be..2c5dc8637b2 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts @@ -27,26 +27,29 @@ export const USAGE_CATEGORIES: { { key: 'cpu_usage', attribute: 'cpu_usage', - name: 'CPU usage', + name: 'CPU', unit: 'absolute', description: 'Some description here', - chartDescription: 'Some description here', + chartDescription: 'The data shown here is refreshed over a period of 24 hours.', }, { key: 'ram_usage', attribute: 'ram_usage', - name: 'Memory usage', + name: 'Memory', unit: 'absolute', description: 'Some description here', - chartDescription: 'Some description here', + chartDescription: 'The data shown here is refreshed over a period of 24 hours.', }, { key: 'disk_io_budget', attribute: 'disk_io_budget', - name: 'IO budget', + name: 'Disk IO bandwidth', unit: 'absolute', - description: 'Some description here', - chartDescription: 'Some description here', + docsUrl: 'https://supabase.com/docs/guides/platform/compute-add-ons#disk-io-bandwidth', + description: + 'SSD Disks are attached to your servers and the disk performance of your workload is determined by the Disk IO bandwidth of this connection.', + chartDescription: + 'If the remaining bandwidth is less than 100%, it indicates that your workload has burst beyond the baseline IO throughput during the day.\nIf this metric drops to zero, the workload has used up all the burst IO throughput minutes during the day and is running at the baseline performance.\nThe amount of remaining bandwidth resets at the beginning of each day, and the data shown here is refreshed over a period of 24 hours.', }, ], }, From 010903fd263963577bbba406dab3fa0ff2d9d0b0 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 3 May 2023 18:50:37 +0800 Subject: [PATCH 21/45] Show text on when last data point was added for chart --- .../interfaces/BillingV2/Usage/Activity.tsx | 33 +++++++++++++- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 37 ++++++++++++++-- .../interfaces/BillingV2/Usage/BarChart.tsx | 15 +++---- .../BillingV2/Usage/Infrastructure.tsx | 34 +++++++++++++-- .../BillingV2/Usage/SizeAndCounts.tsx | 43 ++++++++++++++++--- .../BillingV2/Usage/Usage.constants.ts | 2 +- .../interfaces/BillingV2/Usage/Usage.tsx | 4 +- studio/data/analytics/constants.ts | 2 + studio/data/analytics/daily-stats-query.ts | 10 +++-- .../data/analytics/infra-monitoring-query.ts | 6 +-- 10 files changed, 153 insertions(+), 33 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index d6b874ca7ed..e8d87d74041 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -8,6 +8,7 @@ import { UsageMetric, useProjectUsageQuery, } from 'data/usage/project-usage-query' +import dayjs from 'dayjs' import { Button, IconAlertTriangle } from 'ui' import { USAGE_APPROACHING_THRESHOLD } from '../Billing.constants' import BarChart from './BarChart' @@ -84,31 +85,37 @@ const Activity = ({ projectRef }: ActivityProps) => { monthly_active_users: { isLoading: isLoadingMauData, data: mauData?.data ?? [], + showLastUpdated: mauData?.hasNoData === false, margin: 18, }, monthly_active_sso_users: { isLoading: isLoadingMauSSOData, data: mauSSOData?.data ?? [], + showLastUpdated: mauSSOData?.hasNoData === false, margin: 20, }, storage_image_render_count: { isLoading: isLoadingAssetTransformationsData, data: assetTransformationsData?.data ?? [], + showLastUpdated: assetTransformationsData?.hasNoData === false, margin: 0, }, func_invocations: { isLoading: isLoadingFuncInvocationsData, data: funcInvocationsData?.data ?? [], + showLastUpdated: funcInvocationsData?.hasNoData === false, margin: 26, }, realtime_message_count: { isLoading: isLoadingRealtimeMessagesData, data: realtimeMessagesData?.data ?? [], + showLastUpdated: realtimeMessagesData?.hasNoData === false, margin: 38, }, realtime_peak_connection: { isLoading: isLoadingRealtimeConnectionsData, data: realtimeConnectionsData?.data ?? [], + showLastUpdated: realtimeConnectionsData?.hasNoData === false, margin: 0, }, } @@ -125,6 +132,19 @@ const Activity = ({ projectRef }: ActivityProps) => { typeof usageMeta !== 'number' ? (usageMeta?.usage ?? 0) / (usageMeta?.limit ?? 0) : 0 const usageExcess = (usageMeta?.usage ?? 0) - (usageMeta?.limit ?? 0) + const chartData = chartMeta[attribute.key]?.data ?? [] + + // [Joshen] Ideally this should come from the API imo, foresee some discrepancies + const lastZeroValue = chartData.find( + (x: any) => x.loopId > 0 && x[attribute.attribute] === 0 + ) + const lastKnownValue = + lastZeroValue !== undefined + ? dayjs(lastZeroValue.period_start) + .subtract(1, 'day') + .format('DD MMM YYYY, HH:mma (ZZ)') + : undefined + return ( { <>

{attribute.name} over time

-

{attribute.chartDescription}

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

+ {paragraph} +

+ ))} + {lastKnownValue !== undefined && chartMeta[attribute.key].showLastUpdated && ( + + Last updated at: {lastKnownValue} + + )}
{chartMeta[attribute.key].isLoading ? (
@@ -207,7 +236,7 @@ const Activity = ({ projectRef }: ActivityProps) => { value.toLocaleString()} diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 7947033f423..bdf99d0de5e 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -8,6 +8,7 @@ import { 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 { Button, IconAlertTriangle } from 'ui' @@ -45,8 +46,16 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { }) const chartMeta: any = { - db_egress: { isLoading: isLoadingDbEgressData, data: dbEgressData?.data ?? [] }, - storage_egress: { isLoading: isLoadingStorageEgressData, data: storageEgressData?.data ?? [] }, + db_egress: { + isLoading: isLoadingDbEgressData, + data: dbEgressData?.data ?? [], + showLastUpdated: dbEgressData?.hasNoData === false, + }, + storage_egress: { + isLoading: isLoadingStorageEgressData, + data: storageEgressData?.data ?? [], + showLastUpdated: storageEgressData?.hasNoData === false, + }, } if (categoryMeta === undefined) return null @@ -61,6 +70,19 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { typeof usageMeta !== 'number' ? (usageMeta?.usage ?? 0) / (usageMeta?.limit ?? 0) : 0 const usageExcess = (usageMeta?.usage ?? 0) - (usageMeta?.limit ?? 0) + const chartData = chartMeta[attribute.key]?.data ?? [] + + // [Joshen] Ideally this should come from the API imo, foresee some discrepancies + const lastZeroValue = chartData.find( + (x: any) => x.loopId > 0 && x[attribute.attribute] === 0 + ) + const lastKnownValue = + lastZeroValue !== undefined + ? dayjs(lastZeroValue.period_start) + .subtract(1, 'day') + .format('DD MMM YYYY, HH:mma (ZZ)') + : undefined + return (
@@ -118,7 +140,14 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => {

{attribute.name} over time

-

{attribute.chartDescription}

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

+ {paragraph} +

+ ))} + {lastKnownValue !== undefined && chartMeta[attribute.key].showLastUpdated && ( + Last updated at: {lastKnownValue} + )}
{chartMeta[attribute.key]?.isLoading ? (
@@ -130,7 +159,7 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { formatBytes(value, 1, 'GB').replace(/\s/g, '')} diff --git a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx index 9d8bfb4b526..ff32750c910 100644 --- a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx +++ b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx @@ -1,14 +1,13 @@ import { DataPoint } from 'data/analytics/constants' import { - ResponsiveContainer, - ComposedChart, - CartesianGrid, - YAxis, - XAxis, Bar, - ReferenceLine, + CartesianGrid, Cell, - Label, + ComposedChart, + ReferenceLine, + ResponsiveContainer, + XAxis, + YAxis, } from 'recharts' import { Y_DOMAIN_CEILING_MULTIPLIER } from './Usage.constants' @@ -50,7 +49,7 @@ const BarChart = ({ - + { }) const chartMeta: any = { - cpu_usage: { isLoading: isLoadingCpuUsageData, data: cpuUsageData?.data ?? [] }, - ram_usage: { isLoading: isLoadingMemoryUsageData, data: memoryUsageData?.data ?? [] }, - disk_io_budget: { isLoading: isLoadingIoBudgetData, data: ioBudgetData?.data ?? [] }, + cpu_usage: { + isLoading: isLoadingCpuUsageData, + data: cpuUsageData?.data ?? [], + }, + ram_usage: { + isLoading: isLoadingMemoryUsageData, + data: memoryUsageData?.data ?? [], + }, + disk_io_budget: { + isLoading: isLoadingIoBudgetData, + data: ioBudgetData?.data ?? [], + }, } if (categoryMeta === undefined) return null @@ -55,6 +65,19 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { <> {categoryMeta.attributes.map((attribute) => { + const chartData = chartMeta[attribute.key]?.data ?? [] + + // [Joshen] Ideally this should come from the API imo, foresee some discrepancies + const lastZeroValue = chartData.find( + (x: any) => x.loopId > 0 && x[attribute.attribute] === 0 + ) + const lastKnownValue = + lastZeroValue !== undefined + ? dayjs(lastZeroValue.period_start) + .subtract(1, 'day') + .format('DD MMM YYYY, HH:mma (ZZ)') + : undefined + return (
@@ -74,6 +97,9 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { {paragraph}

))} + {lastKnownValue !== undefined && ( + Last updated at: {lastKnownValue} + )}
{chartMeta[attribute.key].isLoading ? (
@@ -84,7 +110,7 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { ) : ( `${value}%`} yLimit={100} /> diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index 7e713d6ab57..981e1778627 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -8,6 +8,7 @@ import { 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' @@ -55,9 +56,21 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { }) const chartMeta: any = { - db_size: { isLoading: isLoadingDbSizeData, data: dbSizeData?.data ?? [] }, - storage_size: { isLoading: isLoadingStorageSizeData, data: storageSizeData?.data ?? [] }, - func_count: { isLoading: isLoadingFunctionCountData, data: functionCountData?.data ?? [] }, + db_size: { + isLoading: isLoadingDbSizeData, + data: dbSizeData?.data ?? [], + showLastUpdated: dbSizeData?.hasNoData === false, + }, + storage_size: { + isLoading: isLoadingStorageSizeData, + data: storageSizeData?.data ?? [], + showLastUpdated: storageSizeData?.hasNoData === false, + }, + func_count: { + isLoading: isLoadingFunctionCountData, + data: functionCountData?.data ?? [], + showLastUpdated: functionCountData?.hasNoData === false, + }, } if (categoryMeta === undefined) return null @@ -72,6 +85,19 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { typeof usageMeta !== 'number' ? (usageMeta?.usage ?? 0) / (usageMeta?.limit ?? 0) : 0 const usageExcess = (usageMeta?.usage ?? 0) - (usageMeta?.limit ?? 0) + const chartData = chartMeta[attribute.key]?.data ?? [] + + // [Joshen] Ideally this should come from the API imo, foresee some discrepancies + const lastZeroValue = chartData.find( + (x: any) => x.loopId > 0 && x[attribute.attribute] === 0 + ) + const lastKnownValue = + lastZeroValue !== undefined + ? dayjs(lastZeroValue.period_start) + .subtract(1, 'day') + .format('DD MMM YYYY, HH:mma (ZZ)') + : undefined + return (
@@ -164,7 +190,14 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => {

{attribute.name} over time

-

{attribute.chartDescription}

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

+ {paragraph} +

+ ))} + {lastKnownValue !== undefined && chartMeta[attribute.key].showLastUpdated && ( + Last updated at: {lastKnownValue} + )}
{chartMeta[attribute.key].isLoading ? (
@@ -176,7 +209,7 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts index 2c5dc8637b2..618d6a89ba7 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts @@ -49,7 +49,7 @@ export const USAGE_CATEGORIES: { description: 'SSD Disks are attached to your servers and the disk performance of your workload is determined by the Disk IO bandwidth of this connection.', chartDescription: - 'If the remaining bandwidth is less than 100%, it indicates that your workload has burst beyond the baseline IO throughput during the day.\nIf this metric drops to zero, the workload has used up all the burst IO throughput minutes during the day and is running at the baseline performance.\nThe amount of remaining bandwidth resets at the beginning of each day, and the data shown here is refreshed over a period of 24 hours.', + 'The amount of remaining bandwidth resets at the beginning of each day, and the data shown here is refreshed over a period of 24 hours.', }, ], }, diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index 8b4ec1fe140..69b744f0898 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -3,6 +3,7 @@ import { useParams } from 'common' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useProjectsQuery } from 'data/projects/projects-query' import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' +import { useProjectUsageQuery } from 'data/usage/project-usage-query' import Link from 'next/link' import { useRef, useState } from 'react' import { Button, IconAlertCircle, IconCheckCircle, IconLoader, Listbox } from 'ui' @@ -10,9 +11,8 @@ import Activity from './Activity' import Bandwidth from './Bandwidth' import Infrastructure from './Infrastructure' import SizeAndCounts from './SizeAndCounts' -import { useProjectUsageQuery } from 'data/usage/project-usage-query' -import { getUsageStatus } from './Usage.utils' import { USAGE_CATEGORIES, USAGE_STATUS } from './Usage.constants' +import { getUsageStatus } from './Usage.utils' const Usage = () => { const { ref } = useParams() diff --git a/studio/data/analytics/constants.ts b/studio/data/analytics/constants.ts index ef173509814..d12ba4a1c19 100644 --- a/studio/data/analytics/constants.ts +++ b/studio/data/analytics/constants.ts @@ -1,6 +1,7 @@ export type DataPoint = { loopId: number | string period_start: string + periodStartFormatted?: string } & { // Attribute name will be the key [key: string]: string @@ -12,4 +13,5 @@ export interface AnalyticsData { total: number totalAverage: number yAxisLimit: number + hasNoData?: boolean } diff --git a/studio/data/analytics/daily-stats-query.ts b/studio/data/analytics/daily-stats-query.ts index b7283e76b8a..e0695189203 100644 --- a/studio/data/analytics/daily-stats-query.ts +++ b/studio/data/analytics/daily-stats-query.ts @@ -1,10 +1,10 @@ import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' +import dayjs from 'dayjs' import { get } from 'lib/common/fetch' import { API_URL } from 'lib/constants' import { useCallback } from 'react' -import { analyticsKeys } from './keys' import { AnalyticsData } from './constants' -import dayjs from 'dayjs' +import { analyticsKeys } from './keys' export type DailyStatsVariables = { projectRef?: string @@ -141,19 +141,21 @@ export const useDailyStatsQuery = ( return { loopId: idx, period_start: dayjs(startDate).add(idx, 'day').format(dateFormat), + periodStartFormatted: dayjs(startDate).add(idx, 'day').format(dateFormat), [attribute]: 0, } }) - return { ...data, data: mockData } + return { ...data, data: mockData, hasNoData: true } } else { return { ...data, + hasNoData: false, data: data.data.map((x) => { return { ...x, [attribute]: modifier !== undefined ? modifier(Number(x[attribute])) : Number(x[attribute]), - period_start: + periodStartFormatted: Number(x.period_start) > 0 ? dayjs(x.period_start).format(dateFormat) : x.period_start, diff --git a/studio/data/analytics/infra-monitoring-query.ts b/studio/data/analytics/infra-monitoring-query.ts index cf0323ebfba..4c5aef011d6 100644 --- a/studio/data/analytics/infra-monitoring-query.ts +++ b/studio/data/analytics/infra-monitoring-query.ts @@ -1,10 +1,10 @@ import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' +import dayjs from 'dayjs' import { get } from 'lib/common/fetch' import { API_URL } from 'lib/constants' import { useCallback } from 'react' -import { analyticsKeys } from './keys' import { AnalyticsData } from './constants' -import dayjs from 'dayjs' +import { analyticsKeys } from './keys' export type InfraMonitoringVariables = { projectRef?: string @@ -73,7 +73,7 @@ export const useInfraMonitoringQuery = ( ...x, [attribute]: modifier !== undefined ? modifier(Number(x[attribute])) : Number(x[attribute]), - period_start: dayjs(x.period_start).format(dateFormat), + periodStartFormatted: dayjs(x.period_start).format(dateFormat), } }), } From 2523b80a959f58c236279d2a27aa74c4125724c6 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 4 May 2023 12:29:03 +0800 Subject: [PATCH 22/45] Style tooltip when hovering over bar --- .../interfaces/BillingV2/Usage/Activity.tsx | 2 + .../interfaces/BillingV2/Usage/Bandwidth.tsx | 2 + .../interfaces/BillingV2/Usage/BarChart.tsx | 37 +++++++++++++++++-- .../BillingV2/Usage/Infrastructure.tsx | 2 + .../BillingV2/Usage/SizeAndCounts.tsx | 2 + .../BillingV2/Usage/Usage.constants.ts | 8 ++-- .../layouts/ProjectLayout/ProjectLayout.tsx | 2 + studio/data/analytics/daily-stats-query.ts | 2 +- studio/data/projects/projects-query.ts | 10 ++--- 9 files changed, 54 insertions(+), 13 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index e8d87d74041..d901ba22603 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -235,6 +235,8 @@ const Activity = ({ projectRef }: ActivityProps) => { ) : ( { ) : ( string + yFormatter?: (value: number | string) => string } const BarChart = ({ data, + name, attribute, unit, hasQuota = false, @@ -55,10 +59,37 @@ const BarChart = ({ axisLine={false} tickLine={{ stroke: 'none' }} domain={yDomain} - unit={unit} ticks={ticks} tickFormatter={yFormatter} /> + { + const { active, payload } = props + if (active && payload && payload.length) { + const dataPeriod = dayjs(payload[0].payload.period_start) + if (dataPeriod.isAfter(dayjs())) return null + + const value = + unit === 'percentage' + ? Number(payload[0].value).toFixed(2) + : Number(payload[0].value) + + return ( +
+

+ {attribute === 'disk_io_budget' ? `Remaining IO budget:` : `${name}:`} +

+

+ {yFormatter !== undefined ? yFormatter(value) : value} +

+

+ {dataPeriod.format('DD MMM YYYY')} +

+
+ ) + } else return null + }} + /> {data.map((entry) => { return ( diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 5cbbabc7828..a51e5953a93 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -109,6 +109,8 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => {
) : ( `${value}%`} diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index 981e1778627..f87a06a8edf 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -208,6 +208,8 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { ) : ( ( const mockData = tempArray.map((x, idx) => { return { loopId: idx, - period_start: dayjs(startDate).add(idx, 'day').format(dateFormat), + period_start: dayjs(startDate).add(idx, 'day').format('DD MMM YYYY'), periodStartFormatted: dayjs(startDate).add(idx, 'day').format(dateFormat), [attribute]: 0, } diff --git a/studio/data/projects/projects-query.ts b/studio/data/projects/projects-query.ts index a60dc03b378..7429a1fd7c5 100644 --- a/studio/data/projects/projects-query.ts +++ b/studio/data/projects/projects-query.ts @@ -15,14 +15,14 @@ export async function getProjects(signal?: AbortSignal) { return data as Project[] } -export type OrganizationRolesData = Awaited> -export type OrganizationRolesError = unknown +export type ProjectsData = Awaited> +export type ProjectsError = unknown -export const useProjectsQuery = ({ +export const useProjectsQuery = ({ enabled = true, ...options -}: UseQueryOptions = {}) => - useQuery( +}: UseQueryOptions = {}) => + useQuery( projectKeys.list(), ({ signal }) => getProjects(signal), { enabled: enabled, ...options } From a2b4e56476480a0f2926a7f319b1484022aee811 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 4 May 2023 13:05:08 +0800 Subject: [PATCH 23/45] Add toggle to switch between old and new interfaces --- .../interfaces/BillingV2/Usage/Activity.tsx | 4 +-- .../interfaces/BillingV2/Usage/Usage.tsx | 3 +- .../project/[ref]/settings/billing/usage.tsx | 31 ++++++++++++++----- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index d901ba22603..77422776165 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -149,7 +149,7 @@ const Activity = ({ projectRef }: ActivityProps) => {
@@ -211,7 +211,7 @@ const Activity = ({ projectRef }: ActivityProps) => {
- {usageMeta.available_in_plan && ( + {usageMeta?.available_in_plan && ( <>

{attribute.name} over time

diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index 69b744f0898..dc8e193dc62 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -6,7 +6,7 @@ import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscrip import { useProjectUsageQuery } from 'data/usage/project-usage-query' import Link from 'next/link' import { useRef, useState } from 'react' -import { Button, IconAlertCircle, IconCheckCircle, IconLoader, Listbox } from 'ui' +import { Button, IconAlertCircle, IconCheckCircle, IconLoader, Listbox, Toggle } from 'ui' import Activity from './Activity' import Bandwidth from './Bandwidth' import Infrastructure from './Infrastructure' @@ -53,7 +53,6 @@ const Usage = () => {

Usage

-
{ - const showNewUsageUI = useFlag('usagev2') - - if (showNewUsageUI) return + const enableUsageV2 = useFlag('usagev2') + const [showNewUsageUI, setShowNewUsageUI] = useState(enableUsageV2) return ( -
-
- -
+
+ {enableUsageV2 && ( +
+ setShowNewUsageUI(!showNewUsageUI)} + /> +

Preview new interface

+
+ )} + {enableUsageV2 && showNewUsageUI ? ( + + ) : ( +
+
+ +
+
+ )}
) } From 1e7e7d17c686ac1abf97fa42454e0b816a8f9d8c Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 4 May 2023 13:09:53 +0800 Subject: [PATCH 24/45] Fix missing key prop in barChart --- studio/components/interfaces/BillingV2/Usage/BarChart.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx index 9bfa7923c5b..eb0ac793aed 100644 --- a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx +++ b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx @@ -91,9 +91,10 @@ const BarChart = ({ }} /> - {data.map((entry) => { + {data.map((entry, idx) => { return ( = yLimit ? 'fill-amber-900' From a03340dfaee46baa6f7296818967e99dfb8823c0 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 4 May 2023 13:34:20 +0800 Subject: [PATCH 25/45] Update tooltip --- .../interfaces/BillingV2/Usage/BarChart.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx index eb0ac793aed..e4c03395438 100644 --- a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx +++ b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx @@ -67,8 +67,6 @@ const BarChart = ({ const { active, payload } = props if (active && payload && payload.length) { const dataPeriod = dayjs(payload[0].payload.period_start) - if (dataPeriod.isAfter(dayjs())) return null - const value = unit === 'percentage' ? Number(payload[0].value).toFixed(2) @@ -79,9 +77,13 @@ const BarChart = ({

{attribute === 'disk_io_budget' ? `Remaining IO budget:` : `${name}:`}

-

- {yFormatter !== undefined ? yFormatter(value) : value} -

+ {dataPeriod.isAfter(dayjs()) ? ( +

No data yet

+ ) : ( +

+ {yFormatter !== undefined ? yFormatter(value) : value} +

+ )}

{dataPeriod.format('DD MMM YYYY')}

From cd897631d17ce5c7bfa121614ca48b43507b82ef Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 4 May 2023 13:55:09 +0800 Subject: [PATCH 26/45] Update missing descriptions --- .../components/interfaces/BillingV2/Usage/Activity.tsx | 2 +- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 2 +- .../components/interfaces/BillingV2/Usage/BarChart.tsx | 4 +++- .../interfaces/BillingV2/Usage/Infrastructure.tsx | 2 +- .../interfaces/BillingV2/Usage/SizeAndCounts.tsx | 2 +- .../interfaces/BillingV2/Usage/Usage.constants.ts | 10 ++++++++-- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 77422776165..e334b561cae 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -124,7 +124,7 @@ const Activity = ({ projectRef }: ActivityProps) => { return ( <> - + {categoryMeta.attributes.map((attribute) => { const usageMeta = usage?.[attribute.key as keyof ProjectUsageResponse] as UsageMetric diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 3d7cf122458..185a6a25f83 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -62,7 +62,7 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { return ( <> - + {categoryMeta.attributes.map((attribute) => { const usageMeta = usage?.[attribute.key as keyof ProjectUsageResponse] as UsageMetric diff --git a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx index e4c03395438..d3d86c2b816 100644 --- a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx +++ b/studio/components/interfaces/BillingV2/Usage/BarChart.tsx @@ -77,7 +77,9 @@ const BarChart = ({

{attribute === 'disk_io_budget' ? `Remaining IO budget:` : `${name}:`}

- {dataPeriod.isAfter(dayjs()) ? ( + {dayjs(dataPeriod.format('DD MMM YYYY')).isAfter( + dayjs(dayjs().format('DD MMM YYYY')) + ) ? (

No data yet

) : (

diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index a51e5953a93..fbd2b232409 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -63,7 +63,7 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { return ( <> - + {categoryMeta.attributes.map((attribute) => { const chartData = chartMeta[attribute.key]?.data ?? [] diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index f87a06a8edf..ad93eedb81d 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -77,7 +77,7 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { return ( <> - + {categoryMeta.attributes.map((attribute) => { const usageMeta = usage?.[attribute.key as keyof ProjectUsageResponse] as UsageMetric diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts index 91d8c40ec69..c86567bdf92 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts @@ -15,21 +15,24 @@ export interface CategoryAttribute { description: string chartDescription: string } + export const USAGE_CATEGORIES: { key: 'infra' | 'bandwidth' | 'sizeCount' | 'activity' name: string + description: string attributes: CategoryAttribute[] }[] = [ { key: 'infra', name: 'Infrastructure', + description: 'Usage statistics related to your Postgres server', attributes: [ { key: 'cpu_usage', attribute: 'cpu_usage', name: 'CPU', unit: 'percentage', - description: 'Some description here', + description: 'CPU usage of your server', chartDescription: 'The data shown here is refreshed over a period of 24 hours.', }, { @@ -37,7 +40,7 @@ export const USAGE_CATEGORIES: { attribute: 'ram_usage', name: 'Memory', unit: 'percentage', - description: 'Some description here', + description: 'Memory usage of your server', chartDescription: 'The data shown here is refreshed over a period of 24 hours.', }, { @@ -56,6 +59,7 @@ export const USAGE_CATEGORIES: { { key: 'bandwidth', name: 'Bandwidth', + description: 'Amount of data transmitted over network connections', attributes: [ { key: 'db_egress', @@ -81,6 +85,7 @@ export const USAGE_CATEGORIES: { { key: 'sizeCount', name: 'Size & Counts', + description: 'Amount of resources your project is consuming', attributes: [ { key: 'db_size', @@ -115,6 +120,7 @@ export const USAGE_CATEGORIES: { { key: 'activity', name: 'Activity', + description: 'Usage statistics that reflect the activity of your project', attributes: [ { key: 'monthly_active_users', From 451bcd8ae90896c36c3be169769647e4bf0e1e13 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 4 May 2023 14:24:57 +0800 Subject: [PATCH 27/45] Small fix --- studio/components/interfaces/BillingV2/Usage/Usage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index dc8e193dc62..77fbf4f1d34 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -103,12 +103,12 @@ const Usage = () => { ) : null}

- + - + From b1473b01d588cb3f5b8d49cb68646c60a5ef83ed Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 4 May 2023 14:36:26 +0800 Subject: [PATCH 28/45] Update URLS for upgrade buttons --- .../interfaces/BillingV2/Usage/Activity.tsx | 19 +++++++++++++++--- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 20 +++++++++++++++---- .../BillingV2/Usage/SizeAndCounts.tsx | 17 +++++++++++++--- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index e334b561cae..d74652e765c 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -9,6 +9,8 @@ import { useProjectUsageQuery, } from 'data/usage/project-usage-query' import dayjs from 'dayjs' +import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants' +import Link from 'next/link' import { Button, IconAlertTriangle } from 'ui' import { USAGE_APPROACHING_THRESHOLD } from '../Billing.constants' import BarChart from './BarChart' @@ -28,6 +30,13 @@ const Activity = ({ projectRef }: ActivityProps) => { const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'activity') + const upgradeUrl = + subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE + ? `/project/${projectRef}/settings/billing/enterprise` + : subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM + ? `/project/${projectRef}/settings/billing/team` + : `/project/${projectRef}/settings/billing/update` + const { data: mauData, isLoading: isLoadingMauData } = useDailyStatsQuery({ projectRef, attribute: 'total_auth_billing_period_mau', @@ -169,9 +178,13 @@ const Activity = ({ projectRef }: ActivityProps) => {
) : null}
- + + + + +
{ const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'bandwidth') + const upgradeUrl = + subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE + ? `/project/${projectRef}/settings/billing/enterprise` + : subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM + ? `/project/${projectRef}/settings/billing/team` + : `/project/${projectRef}/settings/billing/update` + const { data: dbEgressData, isLoading: isLoadingDbEgressData } = useDailyStatsQuery({ projectRef, attribute: 'total_egress_modified', @@ -103,9 +111,13 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => {
) : null}
- + + + + +
{ const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'sizeCount') + const upgradeUrl = + subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE + ? `/project/${projectRef}/settings/billing/enterprise` + : subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM + ? `/project/${projectRef}/settings/billing/team` + : `/project/${projectRef}/settings/billing/update` + const { data: dbSizeData, isLoading: isLoadingDbSizeData } = useDailyStatsQuery({ projectRef, attribute: 'total_db_size_bytes', @@ -118,9 +125,13 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => {
) : null}
- + + + + +
Date: Thu, 4 May 2023 16:52:40 +1000 Subject: [PATCH 29/45] remove rq select ts-ignores --- studio/data/analytics/daily-stats-query.ts | 6 ++-- .../data/analytics/infra-monitoring-query.ts | 3 +- studio/data/usage/project-usage-query.ts | 28 +++++++++---------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/studio/data/analytics/daily-stats-query.ts b/studio/data/analytics/daily-stats-query.ts index 2bd0ab67320..c25f78d85ad 100644 --- a/studio/data/analytics/daily-stats-query.ts +++ b/studio/data/analytics/daily-stats-query.ts @@ -129,7 +129,7 @@ export const useDailyStatsQuery = ( typeof attribute !== 'undefined' && typeof startDate !== 'undefined' && typeof endDate !== 'undefined', - // @ts-ignore + select(data) { const noDataYet = Number(data.data[0].period_start) === 0 @@ -145,7 +145,7 @@ export const useDailyStatsQuery = ( [attribute]: 0, } }) - return { ...data, data: mockData, hasNoData: true } + return { ...data, data: mockData, hasNoData: true } as TData } else { return { ...data, @@ -161,7 +161,7 @@ export const useDailyStatsQuery = ( : x.period_start, } }), - } + } as TData } }, ...options, diff --git a/studio/data/analytics/infra-monitoring-query.ts b/studio/data/analytics/infra-monitoring-query.ts index 4c5aef011d6..028bd927c15 100644 --- a/studio/data/analytics/infra-monitoring-query.ts +++ b/studio/data/analytics/infra-monitoring-query.ts @@ -64,7 +64,6 @@ export const useInfraMonitoringQuery = ( typeof attribute !== 'undefined' && typeof startDate !== 'undefined' && typeof endDate !== 'undefined', - // @ts-ignore select(data) { return { ...data, @@ -76,7 +75,7 @@ export const useInfraMonitoringQuery = ( periodStartFormatted: dayjs(x.period_start).format(dateFormat), } }), - } + } as TData }, ...options, } diff --git a/studio/data/usage/project-usage-query.ts b/studio/data/usage/project-usage-query.ts index fafdb717786..1292eba7e26 100644 --- a/studio/data/usage/project-usage-query.ts +++ b/studio/data/usage/project-usage-query.ts @@ -59,21 +59,21 @@ export const useProjectUsageQuery = ( ({ signal }) => getProjectUsage({ projectRef }, signal), { enabled: enabled && typeof projectRef !== 'undefined', - // @ts-ignore, ensure that all usage values are numbers select(data) { - const formattedData: any = {} - Object.keys(data).forEach((attribute: any) => { - const originalAttributeData = (data as any)[attribute] - const attributeData = - typeof originalAttributeData === 'object' - ? ({ - ...originalAttributeData, - usage: Number((data as any)[attribute].usage), - } as UsageMetric) - : originalAttributeData - formattedData[attribute] = attributeData - }) - return formattedData as ProjectUsageResponse + return Object.fromEntries( + Object.entries(data).map(([key, value]) => { + if (typeof value === 'object') { + const formattedValue = { + ...value, + usage: Number(value.usage), + } + + return [key, formattedValue] + } else { + return [key, value] + } + }) + ) }, ...options, } From c7cc70b481f177a21c03ade5f088aadf1c904c63 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Thu, 4 May 2023 18:58:55 +0800 Subject: [PATCH 30/45] Update studio/components/interfaces/BillingV2/Usage/Usage.constants.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kevin Grüneberg --- studio/components/interfaces/BillingV2/Usage/Usage.constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts index c86567bdf92..b739b75344a 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts @@ -155,7 +155,7 @@ export const USAGE_CATEGORIES: { name: 'Edge function invocations', unit: 'absolute', description: - 'Every single serverless function invocation independent of response status is counted. ', + 'Every single serverless function invocation independent of response status is counted.', chartDescription: 'Billing is based on the sum of all invocations throughout your billing period. The data shown here is refreshed over a period of 24 hours.', }, From a273311c2d20c7ee6ca4fe0800c20590c4638abb Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 4 May 2023 21:47:40 +0800 Subject: [PATCH 31/45] Fix incorrect upgrade url --- .../interfaces/BillingV2/Usage/Activity.tsx | 6 +-- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 6 +-- .../BillingV2/Usage/Infrastructure.tsx | 40 ++++++++++++++++++- .../BillingV2/Usage/SizeAndCounts.tsx | 11 +++-- .../interfaces/BillingV2/Usage/Usage.tsx | 18 +++++++-- 5 files changed, 66 insertions(+), 15 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index d74652e765c..b24cda8644a 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -32,10 +32,10 @@ const Activity = ({ projectRef }: ActivityProps) => { const upgradeUrl = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE - ? `/project/${projectRef}/settings/billing/enterprise` + ? `/project/${projectRef}/settings/billing/update/enterprise` : subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM - ? `/project/${projectRef}/settings/billing/team` - : `/project/${projectRef}/settings/billing/update` + ? `/project/${projectRef}/settings/billing/update/team` + : `/project/${projectRef}/settings/billing/update/pro` const { data: mauData, isLoading: isLoadingMauData } = useDailyStatsQuery({ projectRef, diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index ec3973b58e8..0f9e79ea5a5 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -32,10 +32,10 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { const upgradeUrl = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE - ? `/project/${projectRef}/settings/billing/enterprise` + ? `/project/${projectRef}/settings/billing/update/enterprise` : subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM - ? `/project/${projectRef}/settings/billing/team` - : `/project/${projectRef}/settings/billing/update` + ? `/project/${projectRef}/settings/billing/update/team` + : `/project/${projectRef}/settings/billing/update/pro` const { data: dbEgressData, isLoading: isLoadingDbEgressData } = useDailyStatsQuery({ projectRef, diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index fbd2b232409..7325ccd2032 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -80,9 +80,47 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { return ( + {attribute.key === 'disk_io_budget' && ( + // [Joshen] Eventually should show the overview of the io bandwidth in mbps for burst, baseline and duration + <> +
+

What is 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. +

+
+ {/*
+
+

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

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

None

+ ) : ( +

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

+ )} +
+
+

Used

+

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

+
+
+

Extra volume used this month

+

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

+
+
*/} + + )}
{attribute.key === 'disk_io_budget' ? ( -

{attribute.name} remaining per day

+

IO Budget remaining each day

) : (

Max{' '} diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index dcdfdcd1197..e21c2573c4c 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -33,10 +33,10 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { const upgradeUrl = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE - ? `/project/${projectRef}/settings/billing/enterprise` + ? `/project/${projectRef}/settings/billing/update/enterprise` : subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM - ? `/project/${projectRef}/settings/billing/team` - : `/project/${projectRef}/settings/billing/update` + ? `/project/${projectRef}/settings/billing/update/team` + : `/project/${projectRef}/settings/billing/update/pro` const { data: dbSizeData, isLoading: isLoadingDbSizeData } = useDailyStatsQuery({ projectRef, @@ -65,16 +65,19 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { const chartMeta: any = { db_size: { isLoading: isLoadingDbSizeData, + margin: 14, data: dbSizeData?.data ?? [], showLastUpdated: dbSizeData?.hasNoData === false, }, storage_size: { isLoading: isLoadingStorageSizeData, + margin: 14, data: storageSizeData?.data ?? [], showLastUpdated: storageSizeData?.hasNoData === false, }, func_count: { isLoading: isLoadingFunctionCountData, + margin: 0, data: functionCountData?.data ?? [], showLastUpdated: functionCountData?.hasNoData === false, }, @@ -224,7 +227,7 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { attribute={attribute.attribute} data={chartData} yLimit={usageMeta?.limit ?? 0} - yLeftMargin={14} + yLeftMargin={chartMeta[attribute.key].margin} yFormatter={(value) => attribute.unit === 'bytes' ? formatBytes(value, 1, 'GB').replace(/\s/g, '') diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index 77fbf4f1d34..184ee892bc1 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -13,6 +13,7 @@ import Infrastructure from './Infrastructure' import SizeAndCounts from './SizeAndCounts' import { USAGE_CATEGORIES, USAGE_STATUS } from './Usage.constants' import { getUsageStatus } from './Usage.utils' +import dayjs from 'dayjs' const Usage = () => { const { ref } = useParams() @@ -29,6 +30,9 @@ const Usage = () => { projectRef: selectedProjectRef, }) + const billingCycleStart = dayjs.unix(subscription?.billing?.current_period_start ?? 0).utc() + const billingCycleEnd = dayjs.unix(subscription?.billing?.current_period_end ?? 0).utc() + const scrollTo = (id: 'infra' | 'bandwidth' | 'sizeCount' | 'activity') => { switch (id) { case 'infra': @@ -75,6 +79,7 @@ const Usage = () => { ) : ( { {isLoadingSubscription ? ( ) : subscription !== undefined ? ( -

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

+
+

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

+

+ {billingCycleStart.format('DD MMM YY')} - {billingCycleEnd.format('DD MMM YY')} +

+
) : null}
@@ -127,7 +137,7 @@ const Usage = () => { className="flex items-center opacity-50 space-x-2 py-3 hover:opacity-100 transition cursor-pointer" > {status === USAGE_STATUS.NORMAL ? ( - + ) : status === USAGE_STATUS.APPROACHING ? ( ) : status === USAGE_STATUS.EXCEEDED ? ( From 463f0a57d7a3cb151715e0b4bf50fc4a0d806702 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 4 May 2023 21:54:02 +0800 Subject: [PATCH 32/45] Address feedback --- .../interfaces/BillingV2/Usage/Activity.tsx | 9 ++------- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 10 +++------- .../interfaces/BillingV2/Usage/SizeAndCounts.tsx | 8 ++------ .../components/interfaces/BillingV2/Usage/Usage.tsx | 8 ++++---- .../interfaces/BillingV2/Usage/Usage.utils.ts | 12 ++++++++++++ 5 files changed, 23 insertions(+), 24 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index b24cda8644a..ddc4ded287c 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -9,7 +9,6 @@ import { useProjectUsageQuery, } from 'data/usage/project-usage-query' import dayjs from 'dayjs' -import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants' import Link from 'next/link' import { Button, IconAlertTriangle } from 'ui' import { USAGE_APPROACHING_THRESHOLD } from '../Billing.constants' @@ -17,6 +16,7 @@ import BarChart from './BarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' +import { getUpgradeUrl } from './Usage.utils' export interface ActivityProps { projectRef: string @@ -30,12 +30,7 @@ const Activity = ({ projectRef }: ActivityProps) => { const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'activity') - const upgradeUrl = - subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE - ? `/project/${projectRef}/settings/billing/update/enterprise` - : subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM - ? `/project/${projectRef}/settings/billing/update/team` - : `/project/${projectRef}/settings/billing/update/pro` + const upgradeUrl = getUpgradeUrl(projectRef, subscription) const { data: mauData, isLoading: isLoadingMauData } = useDailyStatsQuery({ projectRef, diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 0f9e79ea5a5..2f020a7a3f8 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -9,7 +9,7 @@ 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' @@ -17,6 +17,7 @@ import BarChart from './BarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' +import { getUpgradeUrl } from './Usage.utils' export interface BandwidthProps { projectRef: string @@ -30,12 +31,7 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'bandwidth') - const upgradeUrl = - subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE - ? `/project/${projectRef}/settings/billing/update/enterprise` - : subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM - ? `/project/${projectRef}/settings/billing/update/team` - : `/project/${projectRef}/settings/billing/update/pro` + const upgradeUrl = getUpgradeUrl(projectRef, subscription) const { data: dbEgressData, isLoading: isLoadingDbEgressData } = useDailyStatsQuery({ projectRef, diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index e21c2573c4c..fe3239fb059 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -18,6 +18,7 @@ import BarChart from './BarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' +import { getUpgradeUrl } from './Usage.utils' export interface SizeAndCountsProps { projectRef: string @@ -31,12 +32,7 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'sizeCount') - const upgradeUrl = - subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE - ? `/project/${projectRef}/settings/billing/update/enterprise` - : subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM - ? `/project/${projectRef}/settings/billing/update/team` - : `/project/${projectRef}/settings/billing/update/pro` + const upgradeUrl = getUpgradeUrl(projectRef, subscription) const { data: dbSizeData, isLoading: isLoadingDbSizeData } = useDailyStatsQuery({ projectRef, diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index 184ee892bc1..2ce4742b162 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -157,16 +157,16 @@ const Usage = () => { e.g for database size, we also need to show disk volume size. Not to mention that are little nuances across each attribute RE formatting (bytes vs locale string) */} -
+
-
+
-
+
-
+
diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts b/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts index 410d3978d89..c49951945c7 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts @@ -2,6 +2,8 @@ import { DataPoint } from 'data/analytics/constants' import { ProjectUsageResponse } from 'data/usage/project-usage-query' import { USAGE_APPROACHING_THRESHOLD } from '../Billing.constants' import { CategoryAttribute, USAGE_STATUS } from './Usage.constants' +import { StripeSubscription } from 'components/interfaces/Billing' +import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants' // [Joshen] This is just for development to generate some test data for chart rendering export const generateUsageData = (attribute: string, days: number): DataPoint[] => { @@ -32,3 +34,13 @@ export const getUsageStatus = (attributes: CategoryAttribute[], usage?: ProjectU return USAGE_STATUS.APPROACHING else return USAGE_STATUS.NORMAL } + +export const getUpgradeUrl = (projectRef: string, subscription?: StripeSubscription) => { + if (!subscription) return `/project/${projectRef}/settings/billing/update` + + return subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE + ? `/project/${projectRef}/settings/billing/update/enterprise` + : subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM + ? `/project/${projectRef}/settings/billing/update/team` + : `/project/${projectRef}/settings/billing/update/pro` +} From 7306d69029eded4cbc166791cacf4fd508eacbee Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 4 May 2023 22:05:35 +0800 Subject: [PATCH 33/45] Remove project dropdown in usage page --- .../interfaces/BillingV2/Usage/Usage.tsx | 30 +++---------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index 2ce4742b162..68db6068849 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -17,6 +17,9 @@ import dayjs from 'dayjs' const Usage = () => { const { ref } = useParams() + + // [Joshen] Using a state here for now as in the future, we'd have this usage page support showing + // stats for different projects. So we'll pass the selected ref as a prop into the individual components const [selectedProjectRef, setSelectedProjectRef] = useState(ref as string) const infrastructureRef = useRef(null) @@ -24,7 +27,6 @@ const Usage = () => { const sizeAndCountsRef = useRef(null) const activityRef = useRef(null) - const { data: projects, isLoading } = useProjectsQuery() const { data: usage } = useProjectUsageQuery({ projectRef: ref }) const { data: subscription, isLoading: isLoadingSubscription } = useProjectSubscriptionQuery({ projectRef: selectedProjectRef, @@ -75,30 +77,6 @@ const Usage = () => { Previous billing cycle - {isLoading ? ( - - ) : ( - - {(projects ?? []).map((project) => ( - - {project.name} - - ))} - - )} {isLoadingSubscription ? ( ) : subscription !== undefined ? ( @@ -106,7 +84,7 @@ const Usage = () => {

Project is on {subscription.tier.name}

-

+

{billingCycleStart.format('DD MMM YY')} - {billingCycleEnd.format('DD MMM YY')}

From aef1ef1e7f621934e039a1bd13c9821206da3403 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 4 May 2023 22:09:13 +0800 Subject: [PATCH 34/45] Only show upgrade button if project is on free tier --- .../interfaces/BillingV2/Usage/Activity.tsx | 18 +++++++++++------- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 19 +++++++++++-------- .../BillingV2/Usage/SizeAndCounts.tsx | 17 ++++++++++------- .../interfaces/BillingV2/Usage/Usage.tsx | 6 ++---- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index ddc4ded287c..973d9b64350 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -17,6 +17,7 @@ import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' import { getUpgradeUrl } from './Usage.utils' +import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants' export interface ActivityProps { projectRef: string @@ -31,6 +32,7 @@ const Activity = ({ projectRef }: ActivityProps) => { const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'activity') const upgradeUrl = getUpgradeUrl(projectRef, subscription) + const isFreeTier = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.FREE const { data: mauData, isLoading: isLoadingMauData } = useDailyStatsQuery({ projectRef, @@ -173,13 +175,15 @@ const Activity = ({ projectRef }: ActivityProps) => {
) : null}
- - - - - + {isFreeTier && ( + + + + + + )}
{ 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 { data: dbEgressData, isLoading: isLoadingDbEgressData } = useDailyStatsQuery({ projectRef, @@ -107,13 +108,15 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => {
) : null}
- - - - - + {isFreeTier && ( + + + + + + )}
{ 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 { data: dbSizeData, isLoading: isLoadingDbSizeData } = useDailyStatsQuery({ projectRef, @@ -124,13 +125,15 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => {
) : null}
- - - - - + {isFreeTier && ( + + + + + + )}
{ const { ref } = useParams() From c7689f17a7b318e9ec1cc9f3ce91c02b2338e143 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 4 May 2023 22:20:36 +0800 Subject: [PATCH 35/45] Small responsiveness improvement in SectionContent --- .../interfaces/BillingV2/Usage/SectionContent.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx index 440bf5616fb..a610d2570b4 100644 --- a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx @@ -18,8 +18,8 @@ const SectionContent = ({ return (
-
-
+
+
@@ -47,7 +47,7 @@ const SectionContent = ({ )}
-
{children}
+
{children}
From 40c94e41d4fe9b1ade620773451d95dc0f526a69 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 4 May 2023 22:21:33 +0800 Subject: [PATCH 36/45] Fix margin for db egress --- studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 53b986e8694..e0e508466d1 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -54,11 +54,13 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { db_egress: { isLoading: isLoadingDbEgressData, data: dbEgressData?.data ?? [], + margin: 16, showLastUpdated: dbEgressData?.hasNoData === false, }, storage_egress: { isLoading: isLoadingStorageEgressData, data: storageEgressData?.data ?? [], + margin: 14, showLastUpdated: storageEgressData?.hasNoData === false, }, } @@ -174,7 +176,7 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { attribute={attribute.attribute} data={chartData} yLimit={usageMeta?.limit ?? 0} - yLeftMargin={14} + yLeftMargin={chartMeta[attribute.key].margin} yFormatter={(value) => formatBytes(value, 1, 'GB').replace(/\s/g, '')} /> )} From 33fdd434d4c60fbfa9593bac8bb14b9d3071ab82 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 4 May 2023 22:22:25 +0800 Subject: [PATCH 37/45] Reword reaching to approaching --- studio/components/interfaces/BillingV2/Usage/Activity.tsx | 2 +- studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx | 2 +- studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 973d9b64350..4896ed06dd2 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -171,7 +171,7 @@ const Activity = ({ projectRef }: ActivityProps) => { ) : usageRatio >= USAGE_APPROACHING_THRESHOLD ? (
-

Reaching limit

+

Approaching limit

) : null}
diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index e0e508466d1..446ad4ac6b8 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -106,7 +106,7 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { ) : usageRatio >= USAGE_APPROACHING_THRESHOLD ? (
-

Reaching limit

+

Approaching limit

) : null}
diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index 789a96cf027..69364c9dfc8 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -121,7 +121,7 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { ) : usageRatio >= USAGE_APPROACHING_THRESHOLD ? (
-

Reaching limit

+

Approaching limit

) : null}
From bfcdeb6a4580f74209b7d4057f63adda40c93a22 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 5 May 2023 11:05:22 +0800 Subject: [PATCH 38/45] Update chart colors for plans with and without spend cap --- .../interfaces/BillingV2/Usage/Activity.tsx | 13 ++++++---- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 13 ++++++---- .../BillingV2/Usage/Infrastructure.tsx | 4 +-- .../BillingV2/Usage/SizeAndCounts.tsx | 25 ++++++++++++++----- .../BillingV2/Usage/Usage.constants.ts | 10 +++++--- .../Usage/{BarChart.tsx => UsageBarChart.tsx} | 13 ++++++---- 6 files changed, 51 insertions(+), 27 deletions(-) rename studio/components/interfaces/BillingV2/Usage/{BarChart.tsx => UsageBarChart.tsx} (92%) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 4896ed06dd2..5c9004705d2 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -12,7 +12,7 @@ import dayjs from 'dayjs' import Link from 'next/link' import { Button, IconAlertTriangle } from 'ui' import { USAGE_APPROACHING_THRESHOLD } from '../Billing.constants' -import BarChart from './BarChart' +import UsageBarChart from './UsageBarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' @@ -33,6 +33,8 @@ const Activity = ({ projectRef }: ActivityProps) => { 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 exceededLimitStyle = isFreeTier || isProTier ? 'text-red-900' : 'text-amber-900' const { data: mauData, isLoading: isLoadingMauData } = useDailyStatsQuery({ projectRef, @@ -165,13 +167,13 @@ const Activity = ({ projectRef }: ActivityProps) => {

{usageRatio >= 1 ? (
- -

Exceeded limit

+ +

Exceeded limit

) : usageRatio >= USAGE_APPROACHING_THRESHOLD ? (
-

Approaching limit

+

Approaching limit

) : null}
@@ -245,7 +247,7 @@ const Activity = ({ projectRef }: ActivityProps) => {
) : ( - { yLimit={usageMeta?.limit ?? 0} yLeftMargin={chartMeta[attribute.key].margin} yFormatter={(value) => value.toLocaleString()} + quotaWarningType={isFreeTier || isProTier ? 'danger' : 'warning'} /> )} diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 446ad4ac6b8..1ff38573db1 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -13,7 +13,7 @@ import { PRICING_TIER_PRODUCT_IDS, USAGE_APPROACHING_THRESHOLD } from 'lib/const import { formatBytes } from 'lib/helpers' import Link from 'next/link' import { Button, IconAlertTriangle } from 'ui' -import BarChart from './BarChart' +import UsageBarChart from './UsageBarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' @@ -33,6 +33,8 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { 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 exceededLimitStyle = isFreeTier || isProTier ? 'text-red-900' : 'text-amber-900' const { data: dbEgressData, isLoading: isLoadingDbEgressData } = useDailyStatsQuery({ projectRef, @@ -100,13 +102,13 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => {

{usageRatio >= 1 ? (
- -

Exceeded limit

+ +

Exceeded limit

) : usageRatio >= USAGE_APPROACHING_THRESHOLD ? (
-

Approaching limit

+

Approaching limit

) : null}
@@ -169,7 +171,7 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => {
) : ( - { yLimit={usageMeta?.limit ?? 0} yLeftMargin={chartMeta[attribute.key].margin} yFormatter={(value) => formatBytes(value, 1, 'GB').replace(/\s/g, '')} + quotaWarningType={isFreeTier || isProTier ? 'danger' : 'warning'} /> )}
diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 7325ccd2032..9e0b4ebf08e 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -2,7 +2,7 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' import dayjs from 'dayjs' -import BarChart from './BarChart' +import UsageBarChart from './UsageBarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' @@ -146,7 +146,7 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => {
) : ( - { 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 exceededLimitStyle = isFreeTier || isProTier ? 'text-red-900' : 'text-amber-900' const { data: dbSizeData, isLoading: isLoadingDbSizeData } = useDailyStatsQuery({ projectRef, @@ -115,13 +117,13 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => {

{usageRatio >= 1 ? (
- -

Exceeded limit

+ +

Exceeded limit

) : usageRatio >= USAGE_APPROACHING_THRESHOLD ? (
-

Approaching limit

+

Approaching limit

) : null}
@@ -219,7 +221,7 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => {
) : ( - { ? formatBytes(value, 1, 'GB').replace(/\s/g, '') : value.toLocaleString() } + quotaWarningType={isFreeTier || isProTier ? 'danger' : 'warning'} /> )}
) })} + ) } diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts index b739b75344a..d94ba87c7f7 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts @@ -128,8 +128,9 @@ export const USAGE_CATEGORIES: { name: 'Monthly active users', unit: 'absolute', description: - 'The amount of distinct users requesting your API throughout the billing period. Resets at the beginning of every billing period.', - chartDescription: 'The data shown here is refreshed over a period of 24 hours.', + 'The amount of distinct users requesting your API throughout the billing period.', + chartDescription: + 'The data shown here is refreshed over a period of 24 hours and resets at the beginning of every billing period.', }, { key: 'monthly_active_sso_users', @@ -137,8 +138,9 @@ export const USAGE_CATEGORIES: { name: 'Monthly active single sign-on users', unit: 'absolute', description: - 'The amount of distinct Single Sign-On users requesting your API throughout the billing period. Resets at the beginning of every billing period.', - chartDescription: 'The data shown here is refreshed over a period of 24 hours.', + 'The amount of distinct Single Sign-On users requesting your API throughout the billing period.', + chartDescription: + 'The data shown here is refreshed over a period of 24 hours and resets at the beginning of every billing period.', }, { key: 'storage_image_render_count', diff --git a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx b/studio/components/interfaces/BillingV2/Usage/UsageBarChart.tsx similarity index 92% rename from studio/components/interfaces/BillingV2/Usage/BarChart.tsx rename to studio/components/interfaces/BillingV2/Usage/UsageBarChart.tsx index d3d86c2b816..77fecdb1abb 100644 --- a/studio/components/interfaces/BillingV2/Usage/BarChart.tsx +++ b/studio/components/interfaces/BillingV2/Usage/UsageBarChart.tsx @@ -16,7 +16,7 @@ import dayjs from 'dayjs' // [Joshen] This BarChart is specifically for usage, hence not a reusable component, and not // replacing the existing BarChart in ui/Charts -export interface BarChartProps { +export interface UsageBarChartProps { data: DataPoint[] name: string // Used within the tooltip attribute: string @@ -25,9 +25,10 @@ export interface BarChartProps { yLimit?: number yLeftMargin?: number yFormatter?: (value: number | string) => string + quotaWarningType?: 'warning' | 'danger' } -const BarChart = ({ +const UsageBarChart = ({ data, name, attribute, @@ -36,9 +37,11 @@ const BarChart = ({ yLimit, yLeftMargin = 10, yFormatter, -}: BarChartProps) => { + quotaWarningType = 'warning', +}: UsageBarChartProps) => { const yMin = 0 // We can consider passing this as a prop if there's a use case in the future const yMax = (yLimit ?? 0) * Y_DOMAIN_CEILING_MULTIPLIER + const quotaWarningClass = quotaWarningType === 'warning' ? 'fill-amber-900' : 'fill-red-900' const yDomain = [yMin, yLimit ?? 0] const ticks = @@ -101,7 +104,7 @@ const BarChart = ({ key={`cell-${attribute}-${idx}`} className={ yLimit !== undefined && Number(entry[attribute]) >= yLimit - ? 'fill-amber-900' + ? quotaWarningClass : 'fill-scale-1200' } /> @@ -131,4 +134,4 @@ const BarChart = ({ ) } -export default BarChart +export default UsageBarChart From 36bc464ed75b1800cfaf4d0a5bef37158034df72 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 5 May 2023 11:12:03 +0800 Subject: [PATCH 39/45] Hide tables and charts for attributes that are not avialable in the plan --- .../interfaces/BillingV2/Usage/Activity.tsx | 136 +++++++++--------- .../BillingV2/Usage/Infrastructure.tsx | 27 +--- .../BillingV2/Usage/SectionContent.tsx | 5 + 3 files changed, 76 insertions(+), 92 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 5c9004705d2..9cad0bb469d 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -159,74 +159,78 @@ const Activity = ({ projectRef }: ActivityProps) => { section={attribute} includedInPlan={usageMeta?.available_in_plan} > -
-
-
-

- {attribute.name} {subscription?.tier.key.toLowerCase()} quota usage -

- {usageRatio >= 1 ? ( -
- -

Exceeded limit

-
- ) : usageRatio >= USAGE_APPROACHING_THRESHOLD ? ( -
- -

Approaching limit

-
- ) : null} -
- {isFreeTier && ( - - - - - - )} -
- = 1 - ? 'bg-red-900' - : usageRatio >= USAGE_APPROACHING_THRESHOLD - ? 'bg-amber-900' - : 'bg-scale-1100' - )} - value={usageMeta?.usage ?? 0} - max={usageMeta?.limit ?? 0} - /> -
-
-

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

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

None

- ) : ( -

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

- )} -
-
-

Used

-

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

-
-
-

Extra volume used this month

-

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

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

+ {attribute.name} {subscription?.tier.key.toLowerCase()} quota usage +

+ {usageRatio >= 1 ? ( +
+ +

Exceeded limit

+
+ ) : usageRatio >= USAGE_APPROACHING_THRESHOLD ? ( +
+ +

Approaching limit

+
+ ) : null} +
+ {isFreeTier && ( + + + + + + )} +
+ = 1 + ? 'bg-red-900' + : usageRatio >= USAGE_APPROACHING_THRESHOLD + ? 'bg-amber-900' + : 'bg-scale-1100' + )} + value={usageMeta?.usage ?? 0} + max={usageMeta?.limit ?? 0} + /> +
+
+

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

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

None

+ ) : ( +

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

+ )} +
+
+

Used

+

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

+
+
+

Extra volume used this month

+

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

+
+
+

{attribute.name} over time

{attribute.chartDescription.split('\n').map((paragraph, idx) => ( diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 9e0b4ebf08e..72e4c55b783 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -79,7 +79,7 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { : undefined return ( - + {attribute.key === 'disk_io_budget' && ( // [Joshen] Eventually should show the overview of the io bandwidth in mbps for burst, baseline and duration <> @@ -91,31 +91,6 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { bandwidth.

- {/*
-
-

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

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

None

- ) : ( -

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

- )} -
-
-

Used

-

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

-
-
-

Extra volume used this month

-

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

-
-
*/} )}
diff --git a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx index a610d2570b4..ee8b97fea12 100644 --- a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx @@ -6,11 +6,13 @@ import Link from 'next/link' export interface SectionContent { section: CategoryAttribute includedInPlan?: boolean + lastKnownValue?: string } const SectionContent = ({ section, includedInPlan, + lastKnownValue, children, }: PropsWithChildren) => { const { name, description, docsUrl } = section @@ -45,6 +47,9 @@ const SectionContent = ({
)} + {lastKnownValue !== undefined && ( +

Last updated at: {lastKnownValue}

+ )}
{children}
From 8ee17b22a63d2a010541dd23c2fcfaa19f4efb57 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 5 May 2023 11:26:32 +0800 Subject: [PATCH 40/45] Address feedback --- studio/components/interfaces/BillingV2/Usage/Activity.tsx | 4 +--- .../components/interfaces/BillingV2/Usage/Bandwidth.tsx | 4 +--- .../interfaces/BillingV2/Usage/SizeAndCounts.tsx | 4 +--- .../interfaces/BillingV2/Usage/UsageBarChart.tsx | 8 +++----- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 9cad0bb469d..375ba50c6cf 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -164,9 +164,7 @@ const Activity = ({ projectRef }: ActivityProps) => {
-

- {attribute.name} {subscription?.tier.key.toLowerCase()} quota usage -

+

{attribute.name} quota usage

{usageRatio >= 1 ? (
{
-

- {attribute.name} {subscription?.tier.key.toLowerCase()} quota usage -

+

{attribute.name} quota usage

{usageRatio >= 1 ? (
diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index d5efec7aea7..b355f05b3f6 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -112,9 +112,7 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => {
-

- {attribute.name} {subscription?.tier.key.toLowerCase()} quota usage -

+

{attribute.name} quota usage

{usageRatio >= 1 ? (
diff --git a/studio/components/interfaces/BillingV2/Usage/UsageBarChart.tsx b/studio/components/interfaces/BillingV2/Usage/UsageBarChart.tsx index 77fecdb1abb..191c7e583ef 100644 --- a/studio/components/interfaces/BillingV2/Usage/UsageBarChart.tsx +++ b/studio/components/interfaces/BillingV2/Usage/UsageBarChart.tsx @@ -80,9 +80,7 @@ const UsageBarChart = ({

{attribute === 'disk_io_budget' ? `Remaining IO budget:` : `${name}:`}

- {dayjs(dataPeriod.format('DD MMM YYYY')).isAfter( - dayjs(dayjs().format('DD MMM YYYY')) - ) ? ( + {dataPeriod.startOf('day').isAfter(dayjs().startOf('day')) ? (

No data yet

) : (

@@ -98,10 +96,10 @@ const UsageBarChart = ({ }} /> - {data.map((entry, idx) => { + {data.map((entry) => { return ( = yLimit ? quotaWarningClass From 3ae477b52e1887c4cf6f5e589aeb9cb35d56d215 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 5 May 2023 12:26:19 +0800 Subject: [PATCH 41/45] Beef up io budget section --- .../BillingV2/Usage/Infrastructure.tsx | 79 ++++++++++++++++++- .../BillingV2/Usage/Usage.constants.ts | 46 +++++++++++ .../interfaces/BillingV2/Usage/Usage.utils.ts | 2 + 3 files changed, 124 insertions(+), 3 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 72e4c55b783..24ca3600f6d 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -2,10 +2,14 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' import dayjs from 'dayjs' -import UsageBarChart from './UsageBarChart' +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 { USAGE_CATEGORIES } from './Usage.constants' +import { COMPUTE_INSTANCE_SPECS, USAGE_CATEGORIES } from './Usage.constants' +import { getUpgradeUrl } from './Usage.utils' +import UsageBarChart from './UsageBarChart' export interface InfrastructureProps { projectRef: string @@ -20,6 +24,14 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() 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 currentComputeInstance = subscription?.addons.find((addon) => + addon.supabase_prod_id.includes('_instance_') + ) + const currentComputeInstanceSpecs = + COMPUTE_INSTANCE_SPECS[currentComputeInstance?.supabase_prod_id ?? 'addon_instance_micro'] + const { data: cpuUsageData, isLoading: isLoadingCpuUsageData } = useInfraMonitoringQuery({ projectRef, attribute: 'cpu_usage', @@ -44,6 +56,12 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { endDate, }) + const currentDayIoBudget = Number( + ioBudgetData?.data.find((x) => x.periodStartFormatted === dayjs().format('DD MMM'))?.[ + 'disk_io_budget' + ] ?? 0 + ) + const chartMeta: any = { cpu_usage: { isLoading: isLoadingCpuUsageData, @@ -81,8 +99,40 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { return ( {attribute.key === 'disk_io_budget' && ( - // [Joshen] Eventually should show the overview of the io bandwidth in mbps for burst, baseline and duration <> + {/* The condition should be that if the disk io budget for TODAY has crossed some threshold */} + {currentDayIoBudget <= 0 ? ( + +

+ Your workload has used up all the burst IO throughput minutes during the day + and is running at the baseline performance. If you need consistent disk + performance, consider upgrading to a larger compute add-on. +

+ + + + + + + ) : currentDayIoBudget <= 20 ? ( + +

+ Your workload is about to use up all the burst IO throughput minutes during + the day. Once this is completely used up, your workload will run at the + baseline performance. If you need consistent disk performance, consider + upgrading to a larger compute add-on. +

+ + + + + +
+ ) : null}

What is Disk IO Bandwidth?

@@ -91,6 +141,29 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { bandwidth.

+
+

Overview

+
+

Current compute instance

+

{currentComputeInstance?.name ?? 'Micro'}

+
+
+

Maximum IO Bandwidth (burst limit)

+

+ {currentComputeInstanceSpecs.maxBandwidth.toLocaleString()} Mbps +

+
+
+

Baseline IO Bandwidth

+

+ {currentComputeInstanceSpecs.baseBandwidth.toLocaleString()} Mbps +

+
+
+

Daily burst time limit

+

30 mins

+
+
)}
diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts index d94ba87c7f7..7fadb438b6a 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.constants.ts @@ -182,3 +182,49 @@ 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 } +} = { + addon_instance_micro: { + maxBandwidth: 2606, + baseBandwidth: 87, + }, + addon_instance_small: { + maxBandwidth: 2606, + baseBandwidth: 174, + }, + addon_instance_medium: { + maxBandwidth: 2606, + baseBandwidth: 347, + }, + addon_instance_large: { + maxBandwidth: 4750, + baseBandwidth: 630, + }, + addon_instance_xlarge: { + maxBandwidth: 4750, + baseBandwidth: 1188, + }, + addon_instance_xxlarge: { + maxBandwidth: 4750, + baseBandwidth: 2375, + }, + addon_instance_4xlarge: { + maxBandwidth: 4750, + baseBandwidth: 4750, + }, + addon_instance_8xlarge: { + maxBandwidth: 9500, + baseBandwidth: 9500, + }, + addon_instance_12xlarge: { + maxBandwidth: 14250, + baseBandwidth: 14250, + }, + addon_instance_16xlarge: { + maxBandwidth: 19000, + baseBandwidth: 19000, + }, +} diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts b/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts index c49951945c7..d4286819f02 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts @@ -42,5 +42,7 @@ export const getUpgradeUrl = (projectRef: string, subscription?: StripeSubscript ? `/project/${projectRef}/settings/billing/update/enterprise` : subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM ? `/project/${projectRef}/settings/billing/update/team` + : subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.FREE + ? `/project/${projectRef}/settings/billing/update` : `/project/${projectRef}/settings/billing/update/pro` } From e3b0acd1f51c8122907a352554f781ab947e7c84 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 5 May 2023 12:32:49 +0800 Subject: [PATCH 42/45] Highlight infra tab if io budget exceeds or approaching --- .../BillingV2/Usage/Infrastructure.tsx | 3 +-- .../interfaces/BillingV2/Usage/Usage.tsx | 26 ++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 24ca3600f6d..87cbedee0ec 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -59,7 +59,7 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { const currentDayIoBudget = Number( ioBudgetData?.data.find((x) => x.periodStartFormatted === dayjs().format('DD MMM'))?.[ 'disk_io_budget' - ] ?? 0 + ] ?? 100 ) const chartMeta: any = { @@ -100,7 +100,6 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { {attribute.key === 'disk_io_budget' && ( <> - {/* The condition should be that if the disk io budget for TODAY has crossed some threshold */} {currentDayIoBudget <= 0 ? (

diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index c58343844a8..d682a025f3e 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -12,6 +12,7 @@ import Infrastructure from './Infrastructure' 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' const Usage = () => { const { ref } = useParams() @@ -30,6 +31,22 @@ const Usage = () => { projectRef: selectedProjectRef, }) + const { current_period_start, current_period_end } = subscription?.billing ?? {} + const startDate = new Date((current_period_start ?? 0) * 1000).toISOString() + const endDate = new Date((current_period_end ?? 0) * 1000).toISOString() + const { data: ioBudgetData } = useInfraMonitoringQuery({ + projectRef: selectedProjectRef, + attribute: 'disk_io_budget', + interval: '1d', + startDate, + endDate, + }) + const currentDayIoBudget = Number( + ioBudgetData?.data.find((x) => x.periodStartFormatted === dayjs().format('DD MMM'))?.[ + 'disk_io_budget' + ] ?? 100 + ) + const billingCycleStart = dayjs.unix(subscription?.billing?.current_period_start ?? 0).utc() const billingCycleEnd = dayjs.unix(subscription?.billing?.current_period_end ?? 0).utc() @@ -104,7 +121,14 @@ const Usage = () => {

{USAGE_CATEGORIES.map((category) => { - const status = getUsageStatus(category.attributes, usage) + const infraStatus = + currentDayIoBudget <= 0 + ? USAGE_STATUS.EXCEEDED + : currentDayIoBudget <= 20 + ? USAGE_STATUS.APPROACHING + : USAGE_STATUS.NORMAL + const status = + category.key === 'infra' ? infraStatus : getUsageStatus(category.attributes, usage) return (
Date: Fri, 5 May 2023 14:29:14 +0800 Subject: [PATCH 43/45] Remove SparkBar and excess limits if limits is 0 --- .../interfaces/BillingV2/Usage/Activity.tsx | 48 +++++++------- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 48 ++++++++------ .../BillingV2/Usage/SizeAndCounts.tsx | 64 +++++++++---------- .../BillingV2/Usage/UsageBarChart.tsx | 2 +- 4 files changed, 83 insertions(+), 79 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 375ba50c6cf..06a3958462e 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -191,18 +191,20 @@ const Activity = ({ projectRef }: ActivityProps) => { )}
- = 1 - ? 'bg-red-900' - : usageRatio >= USAGE_APPROACHING_THRESHOLD - ? 'bg-amber-900' - : 'bg-scale-1100' - )} - value={usageMeta?.usage ?? 0} - max={usageMeta?.limit ?? 0} - /> + {usageMeta.limit > 0 && ( + = 1 + ? 'bg-red-900' + : usageRatio >= USAGE_APPROACHING_THRESHOLD + ? 'bg-amber-900' + : 'bg-scale-1100' + )} + value={usageMeta?.usage ?? 0} + max={usageMeta?.limit ?? 0} + /> + )}

@@ -214,19 +216,21 @@ const Activity = ({ projectRef }: ActivityProps) => {

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

)}
-
+

Used

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

-
-

Extra volume used this month

-

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

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

Extra volume used this month

+

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

+
+ )}
diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index e53fbfa7d59..c00f263b898 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -120,35 +120,41 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { )}
- = 1 - ? 'bg-red-900' - : usageRatio >= USAGE_APPROACHING_THRESHOLD - ? 'bg-amber-900' - : 'bg-scale-1100' - )} - value={usageMeta?.usage ?? 0} - max={usageMeta?.limit ?? 0} - /> + {usageMeta?.limit > 0 && ( + = 1 + ? 'bg-red-900' + : usageRatio >= USAGE_APPROACHING_THRESHOLD + ? 'bg-amber-900' + : 'bg-scale-1100' + )} + value={usageMeta?.usage ?? 0} + max={usageMeta?.limit ?? 0} + /> + )}

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

-

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

+

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

-
+

Used

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

-
-

Extra volume used this month

-

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

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

Extra volume used this month

+

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

+
+ )}
@@ -170,7 +176,7 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => {
) : ( 0} name={attribute.name} unit={attribute.unit} attribute={attribute.attribute} diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index b355f05b3f6..70f6b975475 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -135,18 +135,20 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { )}
- = 1 - ? 'bg-red-900' - : usageRatio >= USAGE_APPROACHING_THRESHOLD - ? 'bg-amber-900' - : 'bg-scale-1100' - )} - value={usageMeta?.usage ?? 0} - max={usageMeta?.limit ?? 0} - /> + {usageMeta.limit > 0 && ( + = 1 + ? 'bg-red-900' + : usageRatio >= USAGE_APPROACHING_THRESHOLD + ? 'bg-amber-900' + : 'bg-scale-1100' + )} + value={usageMeta?.usage ?? 0} + max={usageMeta?.limit ?? 0} + /> + )}

@@ -158,7 +160,7 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { : (usageMeta?.limit ?? 0).toLocaleString()}

-
+

Used

{attribute.unit === 'bytes' @@ -166,18 +168,20 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { : (usageMeta?.usage ?? 0).toLocaleString()}

-
-

Extra volume used this month

-

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

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

Extra volume used this month

+

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

+
+ )}
@@ -238,16 +242,6 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => {
) })} - ) } diff --git a/studio/components/interfaces/BillingV2/Usage/UsageBarChart.tsx b/studio/components/interfaces/BillingV2/Usage/UsageBarChart.tsx index 191c7e583ef..2e2dbddae43 100644 --- a/studio/components/interfaces/BillingV2/Usage/UsageBarChart.tsx +++ b/studio/components/interfaces/BillingV2/Usage/UsageBarChart.tsx @@ -101,7 +101,7 @@ const UsageBarChart = ({ = yLimit + yLimit !== undefined && Number(entry[attribute]) >= yLimit && hasQuota ? quotaWarningClass : 'fill-scale-1200' } From 3d6d0c34fad5e78137a58c205bf265276654fb11 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 5 May 2023 14:36:45 +0800 Subject: [PATCH 44/45] small fix --- .../interfaces/BillingV2/Usage/Activity.tsx | 24 +++++++++---------- .../interfaces/BillingV2/Usage/Bandwidth.tsx | 22 ++++++++--------- .../BillingV2/Usage/Infrastructure.tsx | 6 ++--- .../BillingV2/Usage/SizeAndCounts.tsx | 18 +++++++------- 4 files changed, 33 insertions(+), 37 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index 06a3958462e..e75f52d15b2 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -18,6 +18,7 @@ import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' import { getUpgradeUrl } from './Usage.utils' import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants' +import { DataPoint } from 'data/analytics/constants' export interface ActivityProps { projectRef: string @@ -89,41 +90,43 @@ const Activity = ({ projectRef }: ActivityProps) => { endDate, }) - const chartMeta: any = { + const chartMeta: { + [key: string]: { data: DataPoint[]; margin: number; isLoading: boolean; hasNoData: boolean } + } = { monthly_active_users: { isLoading: isLoadingMauData, data: mauData?.data ?? [], - showLastUpdated: mauData?.hasNoData === false, + hasNoData: mauData?.hasNoData ?? false, margin: 18, }, monthly_active_sso_users: { isLoading: isLoadingMauSSOData, data: mauSSOData?.data ?? [], - showLastUpdated: mauSSOData?.hasNoData === false, + hasNoData: mauSSOData?.hasNoData ?? false, margin: 20, }, storage_image_render_count: { isLoading: isLoadingAssetTransformationsData, data: assetTransformationsData?.data ?? [], - showLastUpdated: assetTransformationsData?.hasNoData === false, + hasNoData: assetTransformationsData?.hasNoData ?? false, margin: 0, }, func_invocations: { isLoading: isLoadingFuncInvocationsData, data: funcInvocationsData?.data ?? [], - showLastUpdated: funcInvocationsData?.hasNoData === false, + hasNoData: funcInvocationsData?.hasNoData ?? false, margin: 26, }, realtime_message_count: { isLoading: isLoadingRealtimeMessagesData, data: realtimeMessagesData?.data ?? [], - showLastUpdated: realtimeMessagesData?.hasNoData === false, + hasNoData: realtimeMessagesData?.hasNoData ?? false, margin: 38, }, realtime_peak_connection: { isLoading: isLoadingRealtimeConnectionsData, data: realtimeConnectionsData?.data ?? [], - showLastUpdated: realtimeConnectionsData?.hasNoData === false, + hasNoData: realtimeConnectionsData?.hasNoData ?? false, margin: 0, }, } @@ -147,7 +150,7 @@ const Activity = ({ projectRef }: ActivityProps) => { (x: any) => x.loopId > 0 && x[attribute.attribute] === 0 ) const lastKnownValue = - lastZeroValue !== undefined + lastZeroValue !== undefined && !chartMeta[attribute.key]?.hasNoData ? dayjs(lastZeroValue.period_start) .subtract(1, 'day') .format('DD MMM YYYY, HH:mma (ZZ)') @@ -240,11 +243,6 @@ const Activity = ({ projectRef }: ActivityProps) => { {paragraph}

))} - {lastKnownValue !== undefined && chartMeta[attribute.key].showLastUpdated && ( - - Last updated at: {lastKnownValue} - - )}
{chartMeta[attribute.key].isLoading ? (
diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index c00f263b898..b38341e249c 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -1,6 +1,7 @@ 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 { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' import { @@ -13,11 +14,11 @@ import { PRICING_TIER_PRODUCT_IDS, USAGE_APPROACHING_THRESHOLD } from 'lib/const import { formatBytes } from 'lib/helpers' import Link from 'next/link' import { Button, IconAlertTriangle } from 'ui' -import UsageBarChart from './UsageBarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' import { getUpgradeUrl } from './Usage.utils' +import UsageBarChart from './UsageBarChart' export interface BandwidthProps { projectRef: string @@ -52,18 +53,20 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { endDate, }) - const chartMeta: any = { + const chartMeta: { + [key: string]: { data: DataPoint[]; margin: number; isLoading: boolean; hasNoData: boolean } + } = { db_egress: { - isLoading: isLoadingDbEgressData, data: dbEgressData?.data ?? [], margin: 16, - showLastUpdated: dbEgressData?.hasNoData === false, + isLoading: isLoadingDbEgressData, + hasNoData: dbEgressData?.hasNoData ?? false, }, storage_egress: { - isLoading: isLoadingStorageEgressData, data: storageEgressData?.data ?? [], margin: 14, - showLastUpdated: storageEgressData?.hasNoData === false, + isLoading: isLoadingStorageEgressData, + hasNoData: storageEgressData?.hasNoData ?? false, }, } @@ -86,14 +89,14 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { (x: any) => x.loopId > 0 && x[attribute.attribute] === 0 ) const lastKnownValue = - lastZeroValue !== undefined + lastZeroValue !== undefined && !chartMeta[attribute.key]?.hasNoData ? dayjs(lastZeroValue.period_start) .subtract(1, 'day') .format('DD MMM YYYY, HH:mma (ZZ)') : undefined return ( - +
@@ -164,9 +167,6 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { {paragraph}

))} - {lastKnownValue !== undefined && chartMeta[attribute.key].showLastUpdated && ( - Last updated at: {lastKnownValue} - )}
{chartMeta[attribute.key]?.isLoading ? (
diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 87cbedee0ec..f002a22b118 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -1,4 +1,5 @@ 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' @@ -62,7 +63,7 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { ] ?? 100 ) - const chartMeta: any = { + const chartMeta: { [key: string]: { data: DataPoint[]; isLoading: boolean } } = { cpu_usage: { isLoading: isLoadingCpuUsageData, data: cpuUsageData?.data ?? [], @@ -182,9 +183,6 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => { {paragraph}

))} - {lastKnownValue !== undefined && ( - Last updated at: {lastKnownValue} - )}
{chartMeta[attribute.key].isLoading ? (
diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index 70f6b975475..d062bf905e2 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -19,6 +19,7 @@ import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' import { generateUsageData, getUpgradeUrl } from './Usage.utils' +import { DataPoint } from 'data/analytics/constants' export interface SizeAndCountsProps { projectRef: string @@ -61,24 +62,26 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { endDate, }) - const chartMeta: any = { + const chartMeta: { + [key: string]: { data: DataPoint[]; margin: number; isLoading: boolean; hasNoData: boolean } + } = { db_size: { isLoading: isLoadingDbSizeData, margin: 14, data: dbSizeData?.data ?? [], - showLastUpdated: dbSizeData?.hasNoData === false, + hasNoData: dbSizeData?.hasNoData ?? false, }, storage_size: { isLoading: isLoadingStorageSizeData, margin: 14, data: storageSizeData?.data ?? [], - showLastUpdated: storageSizeData?.hasNoData === false, + hasNoData: storageSizeData?.hasNoData ?? false, }, func_count: { isLoading: isLoadingFunctionCountData, margin: 0, data: functionCountData?.data ?? [], - showLastUpdated: functionCountData?.hasNoData === false, + hasNoData: functionCountData?.hasNoData ?? false, }, } @@ -101,14 +104,14 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { (x: any) => x.loopId > 0 && x[attribute.attribute] === 0 ) const lastKnownValue = - lastZeroValue !== undefined + lastZeroValue !== undefined && !chartMeta[attribute.key]?.hasNoData ? dayjs(lastZeroValue.period_start) .subtract(1, 'day') .format('DD MMM YYYY, HH:mma (ZZ)') : undefined return ( - +
@@ -212,9 +215,6 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { {paragraph}

))} - {lastKnownValue !== undefined && chartMeta[attribute.key].showLastUpdated && ( - Last updated at: {lastKnownValue} - )}
{chartMeta[attribute.key].isLoading ? (
From 4ead2b0d717703ec5c0178634aaf705eb7e0e92b Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 5 May 2023 14:50:30 +0800 Subject: [PATCH 45/45] Small lint --- .../interfaces/BillingV2/Usage/Activity.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index e75f52d15b2..59efc8dfff0 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -94,40 +94,40 @@ const Activity = ({ projectRef }: ActivityProps) => { [key: string]: { data: DataPoint[]; margin: number; isLoading: boolean; hasNoData: boolean } } = { monthly_active_users: { - isLoading: isLoadingMauData, data: mauData?.data ?? [], - hasNoData: mauData?.hasNoData ?? false, margin: 18, + isLoading: isLoadingMauData, + hasNoData: mauData?.hasNoData ?? false, }, monthly_active_sso_users: { - isLoading: isLoadingMauSSOData, data: mauSSOData?.data ?? [], - hasNoData: mauSSOData?.hasNoData ?? false, margin: 20, + isLoading: isLoadingMauSSOData, + hasNoData: mauSSOData?.hasNoData ?? false, }, storage_image_render_count: { - isLoading: isLoadingAssetTransformationsData, data: assetTransformationsData?.data ?? [], - hasNoData: assetTransformationsData?.hasNoData ?? false, margin: 0, + isLoading: isLoadingMauSSOData, + hasNoData: assetTransformationsData?.hasNoData ?? false, }, func_invocations: { - isLoading: isLoadingFuncInvocationsData, data: funcInvocationsData?.data ?? [], - hasNoData: funcInvocationsData?.hasNoData ?? false, margin: 26, + isLoading: isLoadingFuncInvocationsData, + hasNoData: funcInvocationsData?.hasNoData ?? false, }, realtime_message_count: { - isLoading: isLoadingRealtimeMessagesData, data: realtimeMessagesData?.data ?? [], - hasNoData: realtimeMessagesData?.hasNoData ?? false, margin: 38, + isLoading: isLoadingRealtimeMessagesData, + hasNoData: realtimeMessagesData?.hasNoData ?? false, }, realtime_peak_connection: { - isLoading: isLoadingRealtimeConnectionsData, data: realtimeConnectionsData?.data ?? [], - hasNoData: realtimeConnectionsData?.hasNoData ?? false, margin: 0, + isLoading: isLoadingRealtimeConnectionsData, + hasNoData: realtimeConnectionsData?.hasNoData ?? false, }, }