Usage.constants.tsx407 lines · main
1import { ReactNode } from 'react'
2import { Admonition } from 'ui-patterns'
3
4import { USAGE_APPROACHING_THRESHOLD } from '@/components/interfaces/Billing/Billing.constants'
5import { EgressType, PricingMetric } from '@/data/analytics/org-daily-stats-query'
6import type { OrgSubscription } from '@/data/subscriptions/types'
7import type { OrgUsageResponse } from '@/data/usage/org-usage-query'
8import { DOCS_URL } from '@/lib/constants'
9
10export const COLOR_MAP = {
11 white: { bar: 'fill-foreground', marker: 'bg-foreground' },
12 green: { bar: 'fill-green-800', marker: 'bg-green-800' },
13 'dark-green': { bar: 'fill-green-1000', marker: 'bg-green-1000' },
14 blue: { bar: 'fill-blue-900', marker: 'bg-blue-900' },
15 yellow: { bar: 'fill-yellow-800', marker: 'bg-yellow-800' },
16 'dark-yellow': { bar: 'fill-yellow-1000', marker: 'bg-yellow-1000' },
17 orange: { bar: 'fill-orange-800', marker: 'bg-orange-800' },
18 'dark-orange': { bar: 'fill-orange-1000', marker: 'bg-orange-1100' },
19 teal: { bar: 'fill-teal-600', marker: 'bg-teal-700' },
20 red: { bar: 'fill-red-800', marker: 'bg-red-800' },
21 'dark-red': { bar: 'fill-red-1000', marker: 'bg-red-1000' },
22 purple: { bar: 'fill-purple-900', marker: 'bg-purple-900' },
23}
24
25export const USAGE_STATUS = {
26 NORMAL: 'NORMAL',
27 APPROACHING: 'APPROACHING',
28 EXCEEDED: 'EXCEEDED',
29}
30
31export type AttributeColor =
32 | 'white'
33 | 'blue'
34 | 'green'
35 | 'yellow'
36 | 'orange'
37 | 'purple'
38 | 'red'
39 | 'dark-red'
40 | 'dark-orange'
41 | 'dark-yellow'
42 | 'dark-green'
43 | 'teal'
44
45export interface Attribute {
46 key: string
47 name?: string
48 color: AttributeColor
49}
50export interface CategoryAttribute {
51 anchor: string
52 key: string // Property from organization usage
53 attributes: Attribute[] // For querying against stats-daily / infra-monitoring
54 name: string
55 unit: 'bytes' | 'absolute' | 'percentage' | 'hours' | 'gigabytes'
56 links?: {
57 name: string
58 url: string
59 }[]
60 description: string
61 chartPrefix?: 'Max' | 'Average' | 'Cumulative'
62 chartSuffix?: string
63 chartDescription: string
64 additionalInfo?: (usage?: OrgUsageResponse) => ReactNode | null
65}
66
67export type CategoryMetaKey = 'egress' | 'sizeCount' | 'activity' | 'compute' | 'logs'
68
69export interface CategoryMeta {
70 key: CategoryMetaKey
71 name: string
72 description: string
73 attributes: CategoryAttribute[]
74}
75
76export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[] = (
77 subscription
78) => {
79 const egressAttributes: CategoryAttribute[] = [
80 {
81 anchor: 'egress',
82 key: PricingMetric.EGRESS,
83 attributes: [
84 { key: EgressType.AUTH, name: 'Auth Egress', color: 'yellow' },
85 { key: EgressType.REST, name: 'PostgREST Egress', color: 'green' },
86 { key: EgressType.STORAGE, name: 'Storage Egress', color: 'blue' },
87 { key: EgressType.REALTIME, name: 'Realtime Egress', color: 'orange' },
88 { key: EgressType.FUNCTIONS, name: 'Functions Egress', color: 'purple' },
89 { key: EgressType.SUPAVISOR, name: 'Shared Pooler Egress', color: 'red' },
90 { key: EgressType.LOGDRAIN, name: 'Logdrain Egress', color: 'teal' },
91 ],
92 name: 'Egress',
93 unit: 'bytes',
94 description:
95 'Contains any outgoing traffic including Database, Storage, Realtime, Auth, API, Edge Functions, Pooler and Log Drains.\nBilling is based on the total sum of uncached egress in GB throughout your billing period.\nEgress via cache hits is billed separately.',
96 chartDescription:
97 'The breakdown of different egress types is inclusive of cached egress, even though it is billed separately. The data refreshes every hour.',
98 links: [
99 {
100 name: 'Documentation',
101 url: `${DOCS_URL}/guides/platform/manage-your-usage/egress`,
102 },
103 ],
104 },
105 {
106 anchor: 'cachedEgress',
107 key: PricingMetric.CACHED_EGRESS,
108 attributes: [{ key: PricingMetric.CACHED_EGRESS.toLowerCase(), color: 'white' }],
109 name: 'Cached Egress',
110 unit: 'bytes',
111 description:
112 'Contains any outgoing traffic that is served from a cache hit. Includes API, Storage and Edge Functions.\nBilling is based on the total sum of cached egress in GB throughout your billing period.',
113 chartDescription: 'The data refreshes every hour.',
114 links: [
115 {
116 name: 'Documentation',
117 url: `${DOCS_URL}/guides/platform/manage-your-usage/egress`,
118 },
119 ],
120 },
121 ]
122
123 const databaseAndStorageSizeAttributes: CategoryAttribute[] = []
124 if (subscription?.plan.id === 'free') {
125 databaseAndStorageSizeAttributes.push({
126 anchor: 'dbSize',
127 key: PricingMetric.DATABASE_SIZE,
128 attributes: [{ key: PricingMetric.DATABASE_SIZE.toLowerCase(), color: 'white' }],
129 name: 'Database size',
130 chartPrefix: 'Average',
131 unit: 'bytes',
132 description:
133 'Database size refers to the actual amount of space used by all your database objects, as reported by Postgres.',
134 links: [
135 {
136 name: 'Documentation',
137 url: `${DOCS_URL}/guides/platform/database-size`,
138 },
139 ],
140 chartDescription: 'The data refreshes every hour.',
141 additionalInfo: (usage?: OrgUsageResponse) => {
142 const usageMeta = usage?.usages.find((x) => x.metric === PricingMetric.DATABASE_SIZE)
143 const usageRatio =
144 typeof usageMeta !== 'number'
145 ? (usageMeta?.usage ?? 0) / (usageMeta?.pricing_free_units ?? 0)
146 : 0
147 const hasLimit = usageMeta && (usageMeta?.pricing_free_units ?? 0) > 0
148
149 const isApproachingLimit = hasLimit && usageRatio >= USAGE_APPROACHING_THRESHOLD
150 const isExceededLimit = hasLimit && usageRatio >= 1
151 const isCapped = usageMeta?.capped
152
153 const onFreePlan = subscription?.plan?.name === 'Free'
154
155 return (
156 <div>
157 {(isApproachingLimit || isExceededLimit) && isCapped && (
158 <Admonition
159 type={isExceededLimit ? 'danger' : 'warning'}
160 title={
161 isExceededLimit ? 'Exceeding database size limit' : 'Nearing database size limit'
162 }
163 >
164 <div className="flex w-full items-center flex-col justify-center space-y-2 md:flex-row md:justify-between">
165 <div>
166 When you reach your database size limit, your project can go into read-only
167 mode.{' '}
168 {onFreePlan
169 ? 'Please upgrade your Plan.'
170 : "Disable your spend cap to scale seamlessly, and pay for over-usage beyond your Plan's quota."}
171 </div>
172 </div>
173 </Admonition>
174 )}
175 </div>
176 )
177 },
178 })
179 } else if (subscription?.plan.id !== 'platform') {
180 databaseAndStorageSizeAttributes.push({
181 anchor: 'diskSize',
182 key: 'diskSize',
183 attributes: [],
184 name: 'Disk size',
185 chartPrefix: 'Average',
186 unit: 'bytes',
187 description:
188 "Each Briven project comes with a dedicated disk. Each project gets 8 GB of disk for free. Billing is based on the provisioned disk size. Disk automatically scales up when you get close to it's size.\nEach hour your project is using more than 8 GB of GP3 disk, it incurs the overages in GB-Hrs, i.e. a 16 GB disk incurs 8 GB-Hrs every hour. Extra disk size costs $0.125/GB/month ($0.000171/GB-Hr).",
189 links: [
190 {
191 name: 'Documentation',
192 url: `${DOCS_URL}/guides/platform/manage-your-usage/disk-size`,
193 },
194 {
195 name: 'Disk Management',
196 url: `${DOCS_URL}/guides/platform/database-size#disk-management`,
197 },
198 ],
199 chartDescription: '',
200 })
201 } else if (subscription?.plan.id === 'platform') {
202 databaseAndStorageSizeAttributes.push({
203 anchor: 'databaseSize',
204 key: PricingMetric.DATABASE_SIZE,
205 attributes: [{ key: PricingMetric.DATABASE_SIZE.toLowerCase(), color: 'white' }],
206 name: 'Database Size',
207 chartPrefix: 'Cumulative',
208 unit: 'bytes',
209 description:
210 'Database size refers to the actual amount of space used by all your database objects, as reported by Postgres.\nBilling is prorated down to the hour and will be displayed GB-Hrs.',
211 chartDescription: 'The data refreshes every hour.',
212 })
213 }
214
215 databaseAndStorageSizeAttributes.push({
216 anchor: 'storageSize',
217 key: PricingMetric.STORAGE_SIZE,
218 attributes: [{ key: PricingMetric.STORAGE_SIZE.toLowerCase(), color: 'white' }],
219 name: 'Storage Size',
220 chartPrefix: 'Average',
221 unit: 'bytes',
222 description:
223 'Sum of all objects in your storage buckets.\nBilling is prorated down to the hour and will be displayed GB-Hrs.',
224 chartDescription: 'The data refreshes every hour.',
225 links: [
226 {
227 name: 'Storage',
228 url: `${DOCS_URL}/guides/storage`,
229 },
230 ],
231 })
232
233 return [
234 {
235 key: 'egress',
236 name: 'Egress',
237 description: 'Amount of data transmitted over all network connections',
238 attributes: egressAttributes,
239 },
240 {
241 key: 'sizeCount',
242 name: 'Database & Storage Size',
243 description: 'Amount of resources your project is consuming',
244 attributes: databaseAndStorageSizeAttributes,
245 },
246 {
247 key: 'activity',
248 name: 'Activity',
249 description: 'Usage statistics that reflect the activity of your project',
250 attributes: [
251 {
252 anchor: 'mau',
253 key: PricingMetric.MONTHLY_ACTIVE_USERS,
254 attributes: [{ key: PricingMetric.MONTHLY_ACTIVE_USERS.toLowerCase(), color: 'white' }],
255 name: 'Monthly Active Users',
256 chartPrefix: 'Cumulative',
257 chartSuffix: 'in billing period',
258 unit: 'absolute',
259 description:
260 'Users who log in or refresh their token count towards MAU.\nBilling is based on the sum of distinct users requesting your API throughout the billing period. Resets every billing cycle.',
261 chartDescription:
262 'The data is refreshed over a period of 24 hours and resets at the beginning of every billing period.\nThe data points are relative to the beginning of your billing period and will reset with your billing period.',
263 links: [
264 {
265 name: 'Auth',
266 url: `${DOCS_URL}/guides/auth`,
267 },
268 ],
269 },
270 {
271 anchor: 'mauSso',
272 key: PricingMetric.MONTHLY_ACTIVE_SSO_USERS,
273 attributes: [
274 { key: PricingMetric.MONTHLY_ACTIVE_SSO_USERS.toLowerCase(), color: 'white' },
275 ],
276 name: 'Monthly Active SSO Users',
277 chartPrefix: 'Cumulative',
278 chartSuffix: 'in billing period',
279 unit: 'absolute',
280 description:
281 'SSO users who log in or refresh their token count towards SSO MAU.\nBilling is based on the sum of distinct Single Sign-On users requesting your API throughout the billing period. Resets every billing cycle.',
282 chartDescription:
283 'The data refreshes over a period of 24 hours and resets at the beginning of every billing period.\nThe data points are relative to the beginning of your billing period and will reset with your billing period.',
284 links: [
285 {
286 name: 'SSO with SAML 2.0',
287 url: `${DOCS_URL}/guides/auth/sso/auth-sso-saml`,
288 },
289 ],
290 },
291 {
292 anchor: 'storageImageTransformations',
293 key: PricingMetric.STORAGE_IMAGES_TRANSFORMED,
294 attributes: [
295 { key: PricingMetric.STORAGE_IMAGES_TRANSFORMED.toLowerCase(), color: 'white' },
296 ],
297 name: 'Storage Image Transformations',
298 chartPrefix: 'Cumulative',
299 chartSuffix: 'in billing period',
300 unit: 'absolute',
301 description:
302 'We count all images that were transformed in the billing period, ignoring any transformations.\nUsage example: You transform one image with four different size transformations and another image with just a single transformation. It counts as two, as only two images were transformed.\nBilling is based on the count of (origin) images that used transformations throughout the billing period. Resets every billing cycle.',
303 chartDescription:
304 'The data refreshes every 24 hours.\nThe data points are relative to the beginning of your billing period and will reset with your billing period.',
305 links: [
306 {
307 name: 'Documentation',
308 url: `${DOCS_URL}/guides/storage/image-transformations`,
309 },
310 ],
311 },
312 {
313 anchor: 'funcInvocations',
314 key: PricingMetric.FUNCTION_INVOCATIONS,
315 attributes: [{ key: PricingMetric.FUNCTION_INVOCATIONS.toLowerCase(), color: 'white' }],
316 name: 'Edge Function Invocations',
317 unit: 'absolute',
318 description:
319 'Every serverless function invocation independent of response status is counted.\nBilling is based on the sum of all invocations throughout your billing period.',
320 chartDescription: 'The data refreshes every hour.',
321 links: [
322 {
323 name: 'Edge Functions',
324 url: `${DOCS_URL}/guides/functions`,
325 },
326 ],
327 },
328 {
329 anchor: 'realtimeMessageCount',
330 key: PricingMetric.REALTIME_MESSAGE_COUNT,
331 attributes: [{ key: PricingMetric.REALTIME_MESSAGE_COUNT.toLowerCase(), color: 'white' }],
332 name: 'Realtime Messages',
333 unit: 'absolute',
334 description:
335 "Count of messages going through Realtime. Includes database changes, broadcast and presence. \nUsage example: If you do a database change and 5 clients listen to that change via Realtime, that's 5 messages. If you broadcast a message and 4 clients listen to that, that's 5 messages (1 message sent, 4 received).\nBilling is based on the total amount of messages throughout your billing period.",
336 chartDescription: 'The data refreshes every hour.',
337 links: [
338 {
339 name: 'Realtime Limits',
340 url: `${DOCS_URL}/guides/realtime/limits`,
341 },
342 ],
343 },
344 {
345 anchor: 'realtimePeakConnections',
346 key: PricingMetric.REALTIME_PEAK_CONNECTIONS,
347 attributes: [
348 { key: PricingMetric.REALTIME_PEAK_CONNECTIONS.toLowerCase(), color: 'white' },
349 ],
350 name: 'Realtime Concurrent Peak Connections',
351 chartPrefix: 'Max',
352 unit: 'absolute',
353 description:
354 'Total number of successful connections. Connections attempts are not counted towards usage.\nBilling is based on the maximum amount of concurrent peak connections throughout your billing period.',
355 chartDescription: 'The data refreshes every hour.',
356 links: [
357 {
358 name: 'Realtime Limits',
359 url: `${DOCS_URL}/guides/realtime/limits`,
360 },
361 ],
362 },
363 ],
364 },
365
366 {
367 key: 'logs',
368 name: 'Logs',
369 description: 'Usage statistics related to your logs',
370 attributes: [
371 {
372 anchor: 'logIngestion',
373 key: PricingMetric.LOG_INGESTION,
374 attributes: [{ key: PricingMetric.LOG_INGESTION.toLowerCase(), color: 'white' }],
375 name: 'Log Ingestion',
376 unit: 'absolute',
377 description:
378 'Total amount of logs ingested across all projects.\nBilling is based on the total amount of logs ingested in Gigabyte.',
379 chartDescription: 'The data refreshes every hour.',
380 links: [],
381 },
382 {
383 anchor: 'logQuery',
384 key: PricingMetric.LOG_QUERYING,
385 attributes: [{ key: PricingMetric.LOG_QUERYING.toLowerCase(), color: 'white' }],
386 name: 'Log Query',
387 unit: 'absolute',
388 description:
389 'Total amount of logs queried across all projects.\nBilling is based on the total amount of logs queried in Gigabyte.',
390 chartDescription: 'The data refreshes every hour.',
391 links: [],
392 },
393 {
394 anchor: 'logStorage',
395 key: PricingMetric.LOG_STORAGE,
396 attributes: [{ key: PricingMetric.LOG_STORAGE.toLowerCase(), color: 'white' }],
397 name: 'Log Storage',
398 unit: 'absolute',
399 description:
400 'Total amount of logs stored on the platform. Log retention depends on your platform agreement.\nBilling is based on the total amount of logs stored and factors in the retention period.',
401 chartDescription: 'The data refreshes every hour.',
402 links: [],
403 },
404 ],
405 },
406 ]
407}