mirror of
https://github.com/supabase/supabase.git
synced 2026-10-15 14:19:11 +03:00
Merge branch 'master' into feat/column-order-refactored
This commit is contained in:
131 files changed
+2109
-1628
No files matched your search
@@ -0,0 +1,6 @@
|
||||
---
|
||||
description:
|
||||
globs: apps/studio/**/*.test.ts,apps/studio/**/*.test.tsx
|
||||
alwaysApply: false
|
||||
---
|
||||
Make sure to follow the guidelines in this file to write tests: [README.md](mdc:apps/studio/tests/README.md)
|
||||
@@ -5,8 +5,94 @@ exports[`/api/graphql schema snapshot > should match snapshot 1`] = `
|
||||
query: RootQueryType
|
||||
}
|
||||
|
||||
"""
|
||||
A document containing content from the Supabase docs. This is a guide, which might describe a concept, or explain the steps for using or implementing a feature.
|
||||
"""
|
||||
type Guide implements SearchResult {
|
||||
"""The title of the document"""
|
||||
title: String
|
||||
|
||||
"""The URL of the document"""
|
||||
href: String
|
||||
|
||||
"""
|
||||
The full content of the document, including all subsections (both those matching and not matching any query string) and possibly more content
|
||||
"""
|
||||
content: String
|
||||
|
||||
"""
|
||||
The subsections of the document. If the document is returned from a search match, only matching content chunks are returned. For the full content of the original document, use the content field in the parent Guide.
|
||||
"""
|
||||
subsections: SubsectionCollection
|
||||
}
|
||||
|
||||
"""Document that matches a search query"""
|
||||
interface SearchResult {
|
||||
"""The title of the matching result"""
|
||||
title: String
|
||||
|
||||
"""The URL of the matching result"""
|
||||
href: String
|
||||
|
||||
"""The full content of the matching result"""
|
||||
content: String
|
||||
}
|
||||
|
||||
"""
|
||||
A collection of content chunks from a larger document in the Supabase docs.
|
||||
"""
|
||||
type SubsectionCollection {
|
||||
"""A list of edges containing nodes in this collection"""
|
||||
edges: [SubsectionEdge!]!
|
||||
|
||||
"""The nodes in this collection, directly accessible"""
|
||||
nodes: [Subsection!]!
|
||||
|
||||
"""The total count of items available in this collection"""
|
||||
totalCount: Int!
|
||||
}
|
||||
|
||||
"""An edge in a collection of Subsections"""
|
||||
type SubsectionEdge {
|
||||
"""The Subsection at the end of the edge"""
|
||||
node: Subsection!
|
||||
}
|
||||
|
||||
"""A content chunk taken from a larger document in the Supabase docs"""
|
||||
type Subsection {
|
||||
"""The title of the subsection"""
|
||||
title: String
|
||||
|
||||
"""The URL of the subsection"""
|
||||
href: String
|
||||
|
||||
"""The content of the subsection"""
|
||||
content: String
|
||||
}
|
||||
|
||||
type RootQueryType {
|
||||
"""Get the GraphQL schema for this endpoint"""
|
||||
schema: String!
|
||||
|
||||
"""Search the Supabase docs for content matching a query string"""
|
||||
searchDocs(query: String!, limit: Int): SearchResultCollection
|
||||
}
|
||||
|
||||
"""A collection of search results containing content from Supabase docs"""
|
||||
type SearchResultCollection {
|
||||
"""A list of edges containing nodes in this collection"""
|
||||
edges: [SearchResultEdge!]!
|
||||
|
||||
"""The nodes in this collection, directly accessible"""
|
||||
nodes: [SearchResult!]!
|
||||
|
||||
"""The total count of items available in this collection"""
|
||||
totalCount: Int!
|
||||
}
|
||||
|
||||
"""An edge in a collection of SearchResults"""
|
||||
type SearchResultEdge {
|
||||
"""The SearchResult at the end of the edge"""
|
||||
node: SearchResult!
|
||||
}"
|
||||
`;
|
||||
@@ -1,7 +1,19 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { POST } from './route'
|
||||
|
||||
describe('/api/graphql basic error statuses', () => {
|
||||
beforeAll(() => {
|
||||
vi.mock('server-only', () => {
|
||||
return {}
|
||||
})
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
vi.restoreAllMocks()
|
||||
vi.doUnmock('server-only')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { Result } from '~/features/helpers.fn'
|
||||
import { type OpenAIClientInterface } from '~/lib/openAi'
|
||||
import { ApiError } from '../../utils'
|
||||
import { POST } from '../route'
|
||||
|
||||
const contentEmbeddingMock = vi.fn().mockImplementation(async () => Result.ok([0.1, 0.2, 0.3]))
|
||||
const openAIMock: OpenAIClientInterface = {
|
||||
createContentEmbedding: contentEmbeddingMock,
|
||||
}
|
||||
vi.mock(import('~/lib/openAi'), () => ({
|
||||
openAI: () => openAIMock,
|
||||
}))
|
||||
|
||||
const rpcSpy = vi.fn().mockImplementation((funcName, params) => {
|
||||
if (funcName === 'search_content') {
|
||||
const limit = params?.max_result || 2
|
||||
const mockResults = [
|
||||
{
|
||||
type: 'markdown',
|
||||
page_title: 'Test Guide',
|
||||
href: '/guides/test',
|
||||
content: params?.include_full_content ? 'Test content' : null,
|
||||
subsections: [
|
||||
{ title: 'Introduction', content: 'Introduction content' },
|
||||
{ title: 'Details', content: 'Details content' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'markdown',
|
||||
page_title: 'Another Guide',
|
||||
href: '/guides/another',
|
||||
content: params?.include_full_content ? 'Another content' : null,
|
||||
subsections: [{ title: 'Getting Started', content: 'Getting Started content' }],
|
||||
},
|
||||
]
|
||||
return Promise.resolve({ data: mockResults.slice(0, limit), error: null })
|
||||
}
|
||||
return Promise.resolve({ data: [], error: null })
|
||||
})
|
||||
vi.mock('~/lib/supabase', () => ({
|
||||
supabase: () => ({
|
||||
rpc: rpcSpy,
|
||||
}),
|
||||
}))
|
||||
|
||||
describe('/api/graphql searchDocs', () => {
|
||||
it('should return search results when given a valid query', async () => {
|
||||
const searchQuery = `
|
||||
query {
|
||||
searchDocs(query: "authentication") {
|
||||
nodes {
|
||||
title
|
||||
href
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const request = new Request('http://localhost/api/graphql', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ query: searchQuery }),
|
||||
})
|
||||
|
||||
const response = await POST(request)
|
||||
const json = await response.json()
|
||||
|
||||
expect(json.errors).toBeUndefined()
|
||||
expect(json.data).toBeDefined()
|
||||
expect(json.data.searchDocs).toBeDefined()
|
||||
expect(json.data.searchDocs.nodes).toBeInstanceOf(Array)
|
||||
expect(json.data.searchDocs.nodes).toHaveLength(2)
|
||||
expect(json.data.searchDocs.nodes[0]).toMatchObject({
|
||||
title: 'Test Guide',
|
||||
href: '/guides/test',
|
||||
})
|
||||
})
|
||||
|
||||
it('should respect the limit parameter', async () => {
|
||||
const searchQuery = `
|
||||
query {
|
||||
searchDocs(query: "database", limit: 1) {
|
||||
nodes {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const request = new Request('http://localhost/api/graphql', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ query: searchQuery }),
|
||||
})
|
||||
|
||||
const response = await POST(request)
|
||||
const json = await response.json()
|
||||
|
||||
expect(json.errors).toBeUndefined()
|
||||
expect(json.data.searchDocs.nodes).toHaveLength(1)
|
||||
expect(json.data.searchDocs.nodes[0].title).toBe('Test Guide')
|
||||
expect(rpcSpy).toHaveBeenCalledWith(
|
||||
'search_content',
|
||||
expect.objectContaining({
|
||||
max_result: 1,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should include content field when requested', async () => {
|
||||
const searchQuery = `
|
||||
query {
|
||||
searchDocs(query: "api") {
|
||||
nodes {
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const request = new Request('http://localhost/api/graphql', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ query: searchQuery }),
|
||||
})
|
||||
|
||||
const response = await POST(request)
|
||||
const json = await response.json()
|
||||
|
||||
expect(json.errors).toBeUndefined()
|
||||
expect(json.data.searchDocs.nodes[0].content).toBe('Test content')
|
||||
expect(rpcSpy).toHaveBeenCalledWith(
|
||||
'search_content',
|
||||
expect.objectContaining({
|
||||
include_full_content: true,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should handle errors from embedding creation', async () => {
|
||||
contentEmbeddingMock.mockImplementationOnce(() => {
|
||||
return Result.error(new ApiError('Embedding generation failed'))
|
||||
})
|
||||
|
||||
const searchQuery = `
|
||||
query {
|
||||
searchDocs(query: "failed query") {
|
||||
nodes {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const request = new Request('http://localhost/api/graphql', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ query: searchQuery }),
|
||||
})
|
||||
|
||||
const response = await POST(request)
|
||||
const json = await response.json()
|
||||
|
||||
expect(json.errors).toBeDefined()
|
||||
expect(json.errors[0].message).toBe('Internal Server Error')
|
||||
})
|
||||
|
||||
it('should require a query parameter', async () => {
|
||||
const searchQuery = `
|
||||
query {
|
||||
searchDocs {
|
||||
nodes {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const request = new Request('http://localhost/api/graphql', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ query: searchQuery }),
|
||||
})
|
||||
|
||||
const response = await POST(request)
|
||||
const json = await response.json()
|
||||
|
||||
expect(json.errors).toBeDefined()
|
||||
expect(json.errors[0].message).toContain('required')
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,16 @@
|
||||
import { z, type ZodError } from 'zod'
|
||||
import { type PostgrestError } from '@supabase/supabase-js'
|
||||
import { type ZodError } from 'zod'
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(message: string) {
|
||||
type ObjectOrNever = object | never
|
||||
|
||||
export type ApiErrorGeneric = ApiError<ObjectOrNever>
|
||||
|
||||
export class ApiError<Details extends ObjectOrNever = never> extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public source?: unknown,
|
||||
public details?: Details
|
||||
) {
|
||||
super(message)
|
||||
}
|
||||
|
||||
@@ -14,9 +23,9 @@ export class ApiError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidRequestError extends ApiError {
|
||||
constructor(message: string, source?: unknown) {
|
||||
super(`Invalid request: ${message}`)
|
||||
export class InvalidRequestError<Details extends ObjectOrNever = never> extends ApiError<Details> {
|
||||
constructor(message: string, source?: unknown, details?: Details) {
|
||||
super(`Invalid request: ${message}`, source, details)
|
||||
}
|
||||
|
||||
isPrivate() {
|
||||
@@ -28,6 +37,15 @@ export class InvalidRequestError extends ApiError {
|
||||
}
|
||||
}
|
||||
|
||||
export function convertUnknownToApiError(error: unknown): ApiError {
|
||||
return new ApiError('Unknown error', error)
|
||||
}
|
||||
|
||||
export function convertPostgrestToApiError(error: PostgrestError): ApiError {
|
||||
const message = `${error.code}: ${error.hint}`
|
||||
return new ApiError(message, error)
|
||||
}
|
||||
|
||||
export function convertZodToInvalidRequestError(
|
||||
error: ZodError,
|
||||
prelude?: string
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
interface PriceProps {
|
||||
price: string
|
||||
}
|
||||
|
||||
export const Price: React.FC<PriceProps> = ({ price }) => {
|
||||
return <span translate="no">${price}</span>
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
There is no fixed fee for a Preview branch. You only pay for the usage it incurs. With Compute costs of $0.01344 per hour, a branch running on Micro Compute size starts at $0.32 per day.
|
||||
There is no fixed fee for a Preview branch. You only pay for the usage it incurs. With Compute costs of <Price price="0.01344" /> per hour, a branch running on Micro Compute size starts at <Price price="0.32" /> per day.
|
||||
@@ -1,8 +1,9 @@
|
||||
$2 per 1 million invocations. You are only charged for usage exceeding your subscription plan's quota.
|
||||
<Price price="2" /> per 1 million invocations. You are only charged for usage exceeding your subscription
|
||||
plan's quota.
|
||||
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | --------- | ---------------------------- |
|
||||
| Free | 500,000 | - |
|
||||
| Pro | 2 million | $2 per 1 million invocations |
|
||||
| Team | 2 million | $2 per 1 million invocations |
|
||||
| Enterprise | Custom | Custom |
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | --------- | --------------------------------------------- |
|
||||
| Free | 500,000 | - |
|
||||
| Pro | 2 million | <Price price="2" /> per 1 million invocations |
|
||||
| Team | 2 million | <Price price="2" /> per 1 million invocations |
|
||||
| Enterprise | Custom | Custom |
|
||||
@@ -1 +1,2 @@
|
||||
$0.00325 per MAU. You are only charged for usage exceeding your subscription plan's quota.
|
||||
<Price price="0.00325" /> per MAU. You are only charged for usage exceeding your subscription plan's
|
||||
quota.
|
||||
@@ -1 +1,2 @@
|
||||
$0.015 per SSO MAU. You are only charged for usage exceeding your subscription plan's quota.
|
||||
<Price price="0.015" /> per SSO MAU. You are only charged for usage exceeding your subscription plan's
|
||||
quota.
|
||||
@@ -1 +1,2 @@
|
||||
$0.00325 per Third-Party MAU. You are only charged for usage exceeding your subscription plan's quota.
|
||||
<Price price="0.00325" /> per Third-Party MAU. You are only charged for usage exceeding your subscription
|
||||
plan's quota.
|
||||
@@ -1,7 +1,8 @@
|
||||
$0.1027 per hour ($75 per month) for the first project. $0.0137 per hour ($10 per month) for every additional project.
|
||||
<Price price="0.1027" /> per hour (<Price price="75" /> per month) for the first project. <Price price="0.0137" /> per
|
||||
hour (<Price price="10" /> per month) for every additional project.
|
||||
|
||||
| Plan | Project 1 per month | Project 2 per month | Project 3 per month |
|
||||
| ---------- | ------------------- | ------------------- | ------------------- |
|
||||
| Pro | $75 | $10 | $10 |
|
||||
| Team | $75 | $10 | $10 |
|
||||
| Enterprise | Custom | Custom | Custom |
|
||||
| Plan | Project 1 per month | Project 2 per month | Project 3 per month |
|
||||
| ---------- | -------------------- | -------------------- | -------------------- |
|
||||
| Pro | <Price price="75" /> | <Price price="10" /> | <Price price="10" /> |
|
||||
| Team | <Price price="75" /> | <Price price="10" /> | <Price price="10" /> |
|
||||
| Enterprise | Custom | Custom | Custom |
|
||||
@@ -1,7 +1,7 @@
|
||||
Pricing depends on the recovery retention period, which determines how many days back you can restore data to any chosen point of up to seconds in granularity.
|
||||
|
||||
| Recovery Retention Period in Days | Hourly Price USD | Monthly Price USD |
|
||||
| --------------------------------- | ---------------- | ----------------- |
|
||||
| 7 | $0.137 | $100 |
|
||||
| 14 | $0.274 | $200 |
|
||||
| 28 | $0.55 | $400 |
|
||||
| Recovery Retention Period in Days | Hourly Price USD | Monthly Price USD |
|
||||
| --------------------------------- | ----------------------- | --------------------- |
|
||||
| 7 | <Price price="0.137" /> | <Price price="100" /> |
|
||||
| 14 | <Price price="0.274" /> | <Price price="200" /> |
|
||||
| 28 | <Price price="0.55" /> | <Price price="400" /> |
|
||||
@@ -1,8 +1,9 @@
|
||||
$10 per 1,000 peak connections. You are only charged for usage exceeding your subscription plan's quota.
|
||||
<Price price="10" /> per 1,000 peak connections. You are only charged for usage exceeding your subscription
|
||||
plan's quota.
|
||||
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | ------ | ------------------------------ |
|
||||
| Free | 200 | - |
|
||||
| Pro | 500 | $10 per 1,000 peak connections |
|
||||
| Team | 500 | $10 per 1,000 peak connections |
|
||||
| Enterprise | Custom | Custom |
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | ------ | ----------------------------------------------- |
|
||||
| Free | 200 | - |
|
||||
| Pro | 500 | <Price price="10" /> per 1,000 peak connections |
|
||||
| Team | 500 | <Price price="10" /> per 1,000 peak connections |
|
||||
| Enterprise | Custom | Custom |
|
||||
@@ -1,8 +1,9 @@
|
||||
$2.50 per 1 million messages. You are only charged for usage exceeding your subscription plan's quota.
|
||||
<Price price="2.50" /> per 1 million messages. You are only charged for usage exceeding your subscription
|
||||
plan's quota.
|
||||
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | --------- | ---------------------------- |
|
||||
| Free | 2 million | - |
|
||||
| Pro | 5 million | $2.50 per 1 million messages |
|
||||
| Team | 5 million | $2.50 per 1 million messages |
|
||||
| Enterprise | Custom | Custom |
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | --------- | --------------------------------------------- |
|
||||
| Free | 2 million | - |
|
||||
| Pro | 5 million | <Price price="2.50" /> per 1 million messages |
|
||||
| Team | 5 million | <Price price="2.50" /> per 1 million messages |
|
||||
| Enterprise | Custom | Custom |
|
||||
+7
-6
@@ -1,4 +1,5 @@
|
||||
$5 per 1,000 origin images. You are only charged for usage exceeding your subscription plan's quota.
|
||||
<Price price="5" /> per 1,000 origin images. You are only charged for usage exceeding your subscription
|
||||
plan's quota.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
@@ -6,8 +7,8 @@ The count resets at the start of each billing cycle.
|
||||
|
||||
</Admonition>
|
||||
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | ------ | -------------------------- |
|
||||
| Pro | 100 | $5 per 1,000 origin images |
|
||||
| Team | 100 | $5 per 1,000 origin images |
|
||||
| Enterprise | Custom | Custom |
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | ------ | ------------------------------------------- |
|
||||
| Pro | 100 | <Price price="5" /> per 1,000 origin images |
|
||||
| Team | 100 | <Price price="5" /> per 1,000 origin images |
|
||||
| Enterprise | Custom | Custom |
|
||||
@@ -1,8 +1,9 @@
|
||||
$0.00002919 per GB-Hr ($0.021 per GB per month). You are only charged for usage exceeding your subscription plan's quota.
|
||||
<Price price="0.00002919" /> per GB-Hr (<Price price="0.021" /> per GB per month). You are only
|
||||
charged for usage exceeding your subscription plan's quota.
|
||||
|
||||
| Plan | Quota in GB | Over-Usage per GB | Quota in GB-Hrs | Over-Usage per GB-Hr |
|
||||
| ---------- | ----------- | ----------------- | --------------- | -------------------- |
|
||||
| Free | 1 | - | 744 | - |
|
||||
| Pro | 100 | $0.021 | 74,400 | $0.00002919 |
|
||||
| Team | 100 | $0.021 | 74,400 | $0.00002919 |
|
||||
| Enterprise | Custom | Custom | Custom | Custom |
|
||||
| Plan | Quota in GB | Over-Usage per GB | Quota in GB-Hrs | Over-Usage per GB-Hr |
|
||||
| ---------- | ----------- | ----------------------- | --------------- | ---------------------------- |
|
||||
| Free | 1 | - | 744 | - |
|
||||
| Pro | 100 | <Price price="0.021" /> | 74,400 | <Price price="0.00002919" /> |
|
||||
| Team | 100 | <Price price="0.021" /> | 74,400 | <Price price="0.00002919" /> |
|
||||
| Enterprise | Custom | Custom | Custom | Custom |
|
||||
@@ -624,7 +624,7 @@ You can make a similar setup with a distinct project for each environment. Or ju
|
||||
|
||||
Branching is available on the Pro Plan and above. The price is:
|
||||
|
||||
- Each Preview branch costs $0.32 per day
|
||||
- Each Preview branch costs <Price price="0.32" /> per day
|
||||
- Each Preview branch is billed until it is removed
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -26,7 +26,7 @@ values
|
||||
('Mexico', 'MX');
|
||||
```
|
||||
|
||||
If you want to manage multiple seed files or organize them across different folders, you can configure additional paths or glob patterns in your `config.toml` (see the [next section](#splitting-up-your-seed-data) for details).
|
||||
If you want to manage multiple seed files or organize them across different folders, you can configure additional paths or glob patterns in your `config.toml` (see the [next section](#splitting-up-your-seed-file) for details).
|
||||
|
||||
### Splitting up your seed file
|
||||
|
||||
|
||||
@@ -42,14 +42,13 @@ No, we do not charge for paused projects. Compute hours are only counted for act
|
||||
|
||||
#### How are multiple projects billed under a paid organization?
|
||||
|
||||
We provide a dedicated server for every Supabase project. Each paid organization comes with $10 in Compute Credits to cover one project on the default compute size. Additional projects start at ~$10 a month (billed hourly).
|
||||
We provide a dedicated server for every Supabase project. Each paid organization comes with <Price price="10" /> in Compute Credits to cover one project on the default compute size. Additional projects start at ~<Price price="10" /> a month (billed hourly).
|
||||
|
||||
Running 3 projects in a Pro Plan organization on the default Micro instance:
|
||||
|
||||
- $25 Pro Plan
|
||||
- $30 for 3 projects on the default compute size
|
||||
- $10 Compute credits
|
||||
⇒ $45 / month
|
||||
- <Price price="25" /> Pro Plan
|
||||
- <Price price="30" /> for 3 projects on the default compute size
|
||||
- <Price price="10" /> Compute credits ⇒ <Price price="45" /> / month
|
||||
|
||||
Refer to our [Compute](/docs/guides/platform/manage-your-usage/compute#billing-examples) docs for more examples and insights.
|
||||
|
||||
|
||||
@@ -61,18 +61,18 @@ For usage items without a quota, such as [Compute](/docs/guides/platform/manage-
|
||||
|
||||
The quota is applied to your entire organization, independent of how many projects you launch within that organization. For billing purposes, we sum the usage across all projects in a monthly invoice.
|
||||
|
||||
| Usage Item | Free | Pro/Team | Enterprise |
|
||||
| -------------------------------- | ------------------------ | -------------------------------------------------- | ---------- |
|
||||
| Egress | 5 GB | 250 GB included, then $0.09 per GB | Custom |
|
||||
| Database Size | 500 MB | 8 GB disk per project included, then $0.125 per GB | Custom |
|
||||
| Monthly Active Users | 50,000 MAU | 100,000 MAU included, then $0.00325 per MAU | Custom |
|
||||
| Monthly Active Third-Party Users | 50 MAU | 50 MAU included, then $0.00325 per MAU | Custom |
|
||||
| Monthly Active SSO Users | Unavailable on Free Plan | 50 MAU included, then $0.015 per MAU | Custom |
|
||||
| Storage Size | 1 GB | 100 GB included, then $0.021 per GB | Custom |
|
||||
| Storage Images Transformed | Unavailable on Free Plan | 100 included, then $5 per 1000 | Custom |
|
||||
| Edge Function Invocations | 500,000 | 2 million included, then $2 per million | Custom |
|
||||
| Realtime Message Count | 2 million | 5 million included, then $2.5 per million | Custom |
|
||||
| Realtime Peak Connections | 200 | 500 included, then $10 per 1000 | Custom |
|
||||
| Usage Item | Free | Pro/Team | Enterprise |
|
||||
| -------------------------------- | ------------------------ | ------------------------------------------------------------------- | ---------- |
|
||||
| Egress | 5 GB | 250 GB included, then <Price price="0.09" /> per GB | Custom |
|
||||
| Database Size | 500 MB | 8 GB disk per project included, then <Price price="0.125" /> per GB | Custom |
|
||||
| Monthly Active Users | 50,000 MAU | 100,000 MAU included, then <Price price="0.00325" /> per MAU | Custom |
|
||||
| Monthly Active Third-Party Users | 50 MAU | 50 MAU included, then <Price price="0.00325" /> per MAU | Custom |
|
||||
| Monthly Active SSO Users | Unavailable on Free Plan | 50 MAU included, then <Price price="0.015" /> per MAU | Custom |
|
||||
| Storage Size | 1 GB | 100 GB included, then <Price price="0.021" /> per GB | Custom |
|
||||
| Storage Images Transformed | Unavailable on Free Plan | 100 included, then <Price price="5" /> per 1000 | Custom |
|
||||
| Edge Function Invocations | 500,000 | 2 million included, then <Price price="2" /> per million | Custom |
|
||||
| Realtime Message Count | 2 million | 5 million included, then <Price price="2.5" /> per million | Custom |
|
||||
| Realtime Peak Connections | 200 | 500 included, then <Price price="10" /> per 1000 | Custom |
|
||||
|
||||
You can find a detailed breakdown of all usage items and how they are billed on the [Manage your usage](/docs/guides/platform/manage-your-usage) page.
|
||||
|
||||
|
||||
@@ -16,20 +16,20 @@ In paid organizations, Nano Compute are billed at the same price as Micro Comput
|
||||
|
||||
</Admonition>
|
||||
|
||||
| Compute Size | Hourly Price USD | Monthly Price USD | CPU | Memory | Max DB Size (Recommended)[^2] |
|
||||
| ------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------- | ------------ | ----------------------------- |
|
||||
| Nano[^3] | $0 | $0 | Shared | Up to 0.5 GB | 500 MB |
|
||||
| Micro | $0.01344 | ~$10 | 2-core ARM (shared) | 1 GB | 10 GB |
|
||||
| Small | $0.0206 | ~$15 | 2-core ARM (shared) | 2 GB | 50 GB |
|
||||
| Medium | $0.0822 | ~$60 | 2-core ARM (shared) | 4 GB | 100 GB |
|
||||
| Large | $0.1517 | ~$110 | 2-core ARM (dedicated) | 8 GB | 200 GB |
|
||||
| XL | $0.2877 | ~$210 | 4-core ARM (dedicated) | 16 GB | 500 GB |
|
||||
| 2XL | $0.562 | ~$410 | 8-core ARM (dedicated) | 32 GB | 1 TB |
|
||||
| 4XL | $1.32 | ~$960 | 16-core ARM (dedicated) | 64 GB | 2 TB |
|
||||
| 8XL | $2.562 | ~$1,870 | 32-core ARM (dedicated) | 128 GB | 4 TB |
|
||||
| 12XL | $3.836 | ~$2,800 | 48-core ARM (dedicated) | 192 GB | 6 TB |
|
||||
| 16XL | $5.12 | ~$3,730 | 64-core ARM (dedicated) | 256 GB | 10 TB |
|
||||
| >16XL | - | [Contact Us](https://supabase.com/dashboard/support/new?category=sales&subject=Enquiry%20about%20larger%20instance%20sizes) | Custom | Custom | Custom |
|
||||
| Compute Size | Hourly Price USD | Monthly Price USD | CPU | Memory | Max DB Size (Recommended)[^2] |
|
||||
| ------------ | ------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------- | ------------ | ----------------------------- |
|
||||
| Nano[^3] | <Price price="0" /> | <Price price="0" /> | Shared | Up to 0.5 GB | 500 MB |
|
||||
| Micro | <Price price="0.01344" /> | ~<Price price="10" /> | 2-core ARM (shared) | 1 GB | 10 GB |
|
||||
| Small | <Price price="0.0206" /> | ~<Price price="15" /> | 2-core ARM (shared) | 2 GB | 50 GB |
|
||||
| Medium | <Price price="0.0822" /> | ~<Price price="60" /> | 2-core ARM (shared) | 4 GB | 100 GB |
|
||||
| Large | <Price price="0.1517" /> | ~<Price price="110" /> | 2-core ARM (dedicated) | 8 GB | 200 GB |
|
||||
| XL | <Price price="0.2877" /> | ~<Price price="210" /> | 4-core ARM (dedicated) | 16 GB | 500 GB |
|
||||
| 2XL | <Price price="0.562" /> | ~<Price price="410" /> | 8-core ARM (dedicated) | 32 GB | 1 TB |
|
||||
| 4XL | <Price price="1.32" /> | ~<Price price="960" /> | 16-core ARM (dedicated) | 64 GB | 2 TB |
|
||||
| 8XL | <Price price="2.562" /> | ~<Price price="1" />,870 | 32-core ARM (dedicated) | 128 GB | 4 TB |
|
||||
| 12XL | <Price price="3.836" /> | ~<Price price="2" />,800 | 48-core ARM (dedicated) | 192 GB | 6 TB |
|
||||
| 16XL | <Price price="5.12" /> | ~<Price price="3" />,730 | 64-core ARM (dedicated) | 256 GB | 10 TB |
|
||||
| >16XL | - | [Contact Us](https://supabase.com/dashboard/support/new?category=sales&subject=Enquiry%20about%20larger%20instance%20sizes) | Custom | Custom | Custom |
|
||||
|
||||
[^1]: Database max connections are recommended values and can be customized depending on your use case.
|
||||
[^2]: Database size for each compute instance is the default recommendation but the actual performance of your database has many contributing factors, including resources available to it and the size of the data contained within it. See the [shared responsibility model](https://supabase.com/docs/guides/platform/shared-responsibility-model) for more information.
|
||||
@@ -121,14 +121,14 @@ Be aware that increasing IOPS or throughput incurs additional charges.
|
||||
|
||||
When selecting your disk, it's essential to focus on the performance needs of your workload. Here's a comparison of our available disk types:
|
||||
|
||||
| | General Purpose SSD (gp3) | High Performance SSD (io2) |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| **Use Case** | General workloads, development environments, small to medium databases | High-performance needs, large-scale databases, mission-critical applications |
|
||||
| **Max Disk Size** | 16 TB | 60 TB |
|
||||
| **Max IOPS** | 16,000 IOPS (at 32 GB disk size) | 80,000 IOPS (at 80 GB disk size) |
|
||||
| **Throughput** | 125 MB/s (default) to 1,000 MB/s (maximum) | Automatically scales with IOPS |
|
||||
| **Best For** | Great value for most use cases | Low latency and very high IOPS requirements |
|
||||
| **Pricing** | Disk: 8 GB included, then $0.125 per GB<br/>IOPS: 3,000 included, then $0.024 per IOPS<br/>Throughput: 125 MB/s included, then $0.95 per MB/s | Disk: $0.195 per GB<br/>IOPS: $0.119 per IOPS<br/>Throughput: Scales with IOPS at no additional cost |
|
||||
| | General Purpose SSD (gp3) | High Performance SSD (io2) |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Use Case** | General workloads, development environments, small to medium databases | High-performance needs, large-scale databases, mission-critical applications |
|
||||
| **Max Disk Size** | 16 TB | 60 TB |
|
||||
| **Max IOPS** | 16,000 IOPS (at 32 GB disk size) | 80,000 IOPS (at 80 GB disk size) |
|
||||
| **Throughput** | 125 MB/s (default) to 1,000 MB/s (maximum) | Automatically scales with IOPS |
|
||||
| **Best For** | Great value for most use cases | Low latency and very high IOPS requirements |
|
||||
| **Pricing** | Disk: 8 GB included, then <Price price="0.125" /> per GB<br/>IOPS: 3,000 included, then <Price price="0.024" /> per IOPS<br/>Throughput: 125 MB/s included, then <Price price="0.95" /> per MB/s | Disk: <Price price="0.195" /> per GB<br/>IOPS: <Price price="0.119" /> per IOPS<br/>Throughput: Scales with IOPS at no additional cost |
|
||||
|
||||
For general, day-to-day operations, gp3 should be more than enough. If you need high throughput and IOPS for critical systems, io2 will provide the performance required.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ title: 'Credits'
|
||||
|
||||
## Credit balance
|
||||
|
||||
Each organization has a credit balance. Credits are applied to future invoices to reduce the amount due. As long as the credit balance is greater than $0, credits will be used before charging your payment method on file.
|
||||
Each organization has a credit balance. Credits are applied to future invoices to reduce the amount due. As long as the credit balance is greater than <Price price="0" />, credits will be used before charging your payment method on file.
|
||||
|
||||
<Image
|
||||
alt="Subscription upgrade modal"
|
||||
@@ -27,13 +27,13 @@ As an example, if you start a Pro Plan subscription on January 1 and downgrade t
|
||||
|
||||
## Credit top-ups
|
||||
|
||||
You can top up credits at any time, with a maximum of $999 per top-up. These credits do not expire and are non-refundable.
|
||||
You can top up credits at any time, with a maximum of <Price price="999" /> per top-up. These credits do not expire and are non-refundable.
|
||||
|
||||
You may want to consider this option to avoid issues with recurring payments, gain more control over how often your credit card is charged, and potentially make things easier for your accounting department.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
If you are interested in larger (> $1,000) credit packages, [reach out](https://supabase.com/dashboard/support/new?subject=I%20would%20like%20to%20inquire%20about%20larger%20credit%20packages&category=Sales).
|
||||
If you are interested in larger (> <Price price="1" />,000) credit packages, [reach out](https://supabase.com/dashboard/support/new?subject=I%20would%20like%20to%20inquire%20about%20larger%20credit%20packages&category=Sales).
|
||||
|
||||
</Admonition>
|
||||
|
||||
|
||||
@@ -42,32 +42,32 @@ Usage is shown as "Auth MFA Phone Hours" on your invoice.
|
||||
|
||||
The project has MFA Phone activated throughout the entire billing cycle.
|
||||
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | -------- |
|
||||
| Pro Plan | - | $25 |
|
||||
| Compute Hours Micro Project 1 | 744 | $10 |
|
||||
| MFA Phone Hours | 744 | $75 |
|
||||
| **Subtotal** | | **$110** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$100** |
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------------------------- |
|
||||
| Pro Plan | - | <Price price="25" /> |
|
||||
| Compute Hours Micro Project 1 | 744 | <Price price="10" /> |
|
||||
| MFA Phone Hours | 744 | <Price price="75" /> |
|
||||
| **Subtotal** | | **<Price price="110" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="100" />** |
|
||||
|
||||
### Multiple projects
|
||||
|
||||
All projects have MFA Phone activated throughout the entire billing cycle.
|
||||
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | -------- |
|
||||
| Pro Plan | - | $25 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 1 | 744 | $10 |
|
||||
| MFA Phone Hours Project 1 | 744 | $75 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 2 | 744 | $10 |
|
||||
| MFA Phone Hours Project 2 | 744 | $10 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 3 | 744 | $10 |
|
||||
| MFA Phone Hours Project 3 | 744 | $10 |
|
||||
| | | |
|
||||
| **Subtotal** | | **$150** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$140** |
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------------------------- |
|
||||
| Pro Plan | - | <Price price="25" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 1 | 744 | <Price price="10" /> |
|
||||
| MFA Phone Hours Project 1 | 744 | <Price price="75" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 2 | 744 | <Price price="10" /> |
|
||||
| MFA Phone Hours Project 2 | 744 | <Price price="10" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 3 | 744 | <Price price="10" /> |
|
||||
| MFA Phone Hours Project 3 | 744 | <Price price="10" /> |
|
||||
| | | |
|
||||
| **Subtotal** | | **<Price price="150" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="140" />** |
|
||||
@@ -25,21 +25,21 @@ Compute incurred by Preview branches is shown as "Branching Compute Hours" on yo
|
||||
|
||||
The project has a Preview branch "XYZ", that runs for 30 hours, incurring Compute and Egress costs. Disk Size usage remains within the 8 GB included in the subscription plan, so no additional charges apply.
|
||||
|
||||
| Line Item | Costs |
|
||||
| ------------------------------ | --------- |
|
||||
| Pro Plan | $25 |
|
||||
| | |
|
||||
| Compute Hours Small Project 1 | $15 |
|
||||
| Egress Project 1 | $7 |
|
||||
| Disk Size Project 1 | $3 |
|
||||
| | |
|
||||
| Compute Hours Micro Branch XYZ | $0.4 |
|
||||
| Egress Branch XYZ | $1 |
|
||||
| Disk Size Branch XYZ | $0 |
|
||||
| | |
|
||||
| **Subtotal** | **$51.4** |
|
||||
| Compute Credits | -$10 |
|
||||
| **Total** | **$41.4** |
|
||||
| Line Item | Costs |
|
||||
| ------------------------------ | -------------------------- |
|
||||
| Pro Plan | <Price price="25" /> |
|
||||
| | |
|
||||
| Compute Hours Small Project 1 | <Price price="15" /> |
|
||||
| Egress Project 1 | <Price price="7" /> |
|
||||
| Disk Size Project 1 | <Price price="3" /> |
|
||||
| | |
|
||||
| Compute Hours Micro Branch XYZ | <Price price="0.4" /> |
|
||||
| Egress Branch XYZ | <Price price="1" /> |
|
||||
| Disk Size Branch XYZ | <Price price="0" /> |
|
||||
| | |
|
||||
| **Subtotal** | **<Price price="51.4" />** |
|
||||
| Compute Credits | -<Price price="10" /> |
|
||||
| **Total** | **<Price price="41.4" />** |
|
||||
|
||||
## View usage
|
||||
|
||||
|
||||
@@ -36,24 +36,24 @@ Usage is shown as "Compute Hours" on your invoice.
|
||||
|
||||
## Compute Credits
|
||||
|
||||
Paid plans include $10 in Compute Credits, which cover one project running on the Micro/Nano Compute size or portions of other Compute sizes. Compute Credits are applied to your Compute costs and are provided to an organization each month. They reset monthly and do not accumulate.
|
||||
Paid plans include <Price price="10" /> in Compute Credits, which cover one project running on the Micro/Nano Compute size or portions of other Compute sizes. Compute Credits are applied to your Compute costs and are provided to an organization each month. They reset monthly and do not accumulate.
|
||||
|
||||
## Pricing
|
||||
|
||||
| Compute Size | Hourly Price USD | Monthly Price USD |
|
||||
| ------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Nano[^1] | $0 | $0 |
|
||||
| Micro | $0.01344 | ~$10 |
|
||||
| Small | $0.0206 | ~$15 |
|
||||
| Medium | $0.0822 | ~$60 |
|
||||
| Large | $0.1517 | ~$110 |
|
||||
| XL | $0.2877 | ~$210 |
|
||||
| 2XL | $0.562 | ~$410 |
|
||||
| 4XL | $1.32 | ~$960 |
|
||||
| 8XL | $2.562 | ~$1,870 |
|
||||
| 12XL | $3.836 | ~$2,800 |
|
||||
| 16XL | $5.12 | ~$3,730 |
|
||||
| >16XL | - | [Contact Us](https://supabase.com/dashboard/support/new?category=sales&subject=Enquiry%20about%20larger%20instance%20sizes) |
|
||||
| Compute Size | Hourly Price USD | Monthly Price USD |
|
||||
| ------------ | ------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Nano[^1] | <Price price="0" /> | <Price price="0" /> |
|
||||
| Micro | <Price price="0.01344" /> | ~<Price price="10" /> |
|
||||
| Small | <Price price="0.0206" /> | ~<Price price="15" /> |
|
||||
| Medium | <Price price="0.0822" /> | ~<Price price="60" /> |
|
||||
| Large | <Price price="0.1517" /> | ~<Price price="110" /> |
|
||||
| XL | <Price price="0.2877" /> | ~<Price price="210" /> |
|
||||
| 2XL | <Price price="0.562" /> | ~<Price price="410" /> |
|
||||
| 4XL | <Price price="1.32" /> | ~<Price price="960" /> |
|
||||
| 8XL | <Price price="2.562" /> | ~<Price price="1,870" /> |
|
||||
| 12XL | <Price price="3.836" /> | ~<Price price="2,800"/> |
|
||||
| 16XL | <Price price="5.12" /> | ~<Price price="3,730"/> |
|
||||
| >16XL | - | [Contact Us](https://supabase.com/dashboard/support/new?category=sales&subject=Enquiry%20about%20larger%20instance%20sizes) |
|
||||
|
||||
[^1]: Compute resources on the Free Plan are subject to change.
|
||||
|
||||
@@ -69,40 +69,40 @@ In paid organizations, Nano Compute are billed at the same price as Micro Comput
|
||||
|
||||
The project runs on the same Compute size throughout the entire billing cycle.
|
||||
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------- |
|
||||
| Pro Plan | - | $25 |
|
||||
| Compute Hours Micro Project 1 | 744 | $10 |
|
||||
| **Subtotal** | | **$35** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$25** |
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------------------------ |
|
||||
| Pro Plan | - | <Price price="25"/> |
|
||||
| Compute Hours Micro Project 1 | 744 | <Price price="10" /> |
|
||||
| **Subtotal** | | **<Price price="35" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="25" />** |
|
||||
|
||||
### Multiple projects
|
||||
|
||||
All projects run on the same Compute size throughout the entire billing cycle.
|
||||
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------- |
|
||||
| Pro Plan | - | $25 |
|
||||
| Compute Hours Micro Project 1 | 744 | $10 |
|
||||
| Compute Hours Micro Project 2 | 744 | $10 |
|
||||
| Compute Hours Micro Project 3 | 744 | $10 |
|
||||
| **Subtotal** | | **$55** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$45** |
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------------------------ |
|
||||
| Pro Plan | - | <Price price="25" /> |
|
||||
| Compute Hours Micro Project 1 | 744 | <Price price="10" /> |
|
||||
| Compute Hours Micro Project 2 | 744 | <Price price="10" /> |
|
||||
| Compute Hours Micro Project 3 | 744 | <Price price="10" /> |
|
||||
| **Subtotal** | | **<Price price="55" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="45" />** |
|
||||
|
||||
### One project on different Compute sizes
|
||||
|
||||
The project's Compute size changes throughout the billing cycle.
|
||||
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------- |
|
||||
| Pro Plan | - | $25 |
|
||||
| Compute Hours Micro Project 1 | 233 | $3 |
|
||||
| Compute Hours Small Project 1 | 511 | $11 |
|
||||
| **Subtotal** | | **$39** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$29** |
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------------------------ |
|
||||
| Pro Plan | - | <Price price="25" /> |
|
||||
| Compute Hours Micro Project 1 | 233 | <Price price="3" /> |
|
||||
| Compute Hours Small Project 1 | 511 | <Price price="11" /> |
|
||||
| **Subtotal** | | **<Price price="39" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="29" />** |
|
||||
|
||||
## View usage
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ Usage is shown as "Custom Domain Hours" on your invoice.
|
||||
|
||||
## Pricing
|
||||
|
||||
$0.0137 per hour ($10 per month).
|
||||
<Price price="0.0137" /> per hour (<Price price="10" /> per month).
|
||||
|
||||
## Billing examples
|
||||
|
||||
@@ -36,32 +36,32 @@ $0.0137 per hour ($10 per month).
|
||||
|
||||
The project has a custom domain activated throughout the entire billing cycle.
|
||||
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------- |
|
||||
| Pro Plan | - | $25 |
|
||||
| Compute Hours Micro Project 1 | 744 | $10 |
|
||||
| Custom Domain Hours | 744 | $10 |
|
||||
| **Subtotal** | | **$45** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$35** |
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------------------------ |
|
||||
| Pro Plan | - | <Price price="25" /> |
|
||||
| Compute Hours Micro Project 1 | 744 | <Price price="10" /> |
|
||||
| Custom Domain Hours | 744 | <Price price="10" /> |
|
||||
| **Subtotal** | | **<Price price="45" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="35" />** |
|
||||
|
||||
### Multiple projects
|
||||
|
||||
All projects have a custom domain activated throughout the entire billing cycle.
|
||||
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------- |
|
||||
| Pro Plan | - | $25 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 1 | 744 | $10 |
|
||||
| Custom Domain Hours Project 1 | 744 | $10 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 2 | 744 | $10 |
|
||||
| Custom Domain Hours Project 2 | 744 | $10 |
|
||||
| | | |
|
||||
| **Subtotal** | | **$65** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$55** |
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------------------------ |
|
||||
| Pro Plan | - | <Price price="25" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 1 | 744 | <Price price="10" /> |
|
||||
| Custom Domain Hours Project 1 | 744 | <Price price="10" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 2 | 744 | <Price price="10" /> |
|
||||
| Custom Domain Hours Project 2 | 744 | <Price price="10" /> |
|
||||
| | | |
|
||||
| **Subtotal** | | **<Price price="65" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="55" />** |
|
||||
|
||||
## Optimize usage
|
||||
|
||||
|
||||
@@ -29,24 +29,26 @@ Pricing depends on the [disk type](/docs/guides/platform/compute-and-disk#disk-t
|
||||
|
||||
### General purpose disks (gp3)
|
||||
|
||||
$0.00003288 per IOPS-Hr ($0.024 per IOPS per month). gp3 disks come with a default IOPS of 3,000. You are only charged for provisioned IOPS exceeding these 3,000 IOPS.
|
||||
<Price price="0.00003288" /> per IOPS-Hr (<Price price="0.024" /> per IOPS per month). gp3 disks
|
||||
come with a default IOPS of 3,000. You are only charged for provisioned IOPS exceeding these 3,000
|
||||
IOPS.
|
||||
|
||||
| Plan | Included Disk IOPS | Over-Usage per IOPS per month | Over-Usage per IOPS-Hr |
|
||||
| ---------- | ------------------ | ----------------------------- | ---------------------- |
|
||||
| Pro | 3,000 | $0.024 | $0.00003288 |
|
||||
| Team | 3,000 | $0.024 | $0.00003288 |
|
||||
| Enterprise | Custom | Custom | Custom |
|
||||
| Plan | Included Disk IOPS | Over-Usage per IOPS per month | Over-Usage per IOPS-Hr |
|
||||
| ---------- | ------------------ | ----------------------------- | ---------------------------- |
|
||||
| Pro | 3,000 | <Price price="0.024" /> | <Price price="0.00003288" /> |
|
||||
| Team | 3,000 | <Price price="0.024" /> | <Price price="0.00003288" /> |
|
||||
| Enterprise | Custom | Custom | Custom |
|
||||
|
||||
### High performance disks (io2)
|
||||
|
||||
$0.000163 per IOPS-Hr ($0.119 per IOPS per month).
|
||||
Unlike general purpose disks, high performance disks are billed from the first provisioned IOPS.
|
||||
<Price price="0.000163" /> per IOPS-Hr (<Price price="0.119" /> per IOPS per month). Unlike general
|
||||
purpose disks, high performance disks are billed from the first provisioned IOPS.
|
||||
|
||||
| Plan | Included Disk IOPS | Usage per IOPS per month | Usage per IOPS-Hr |
|
||||
| ---------- | ------------------ | ------------------------ | ----------------- |
|
||||
| Pro | 0 | $0.119 | $0.000163 |
|
||||
| Team | 0 | $0.119 | $0.000163 |
|
||||
| Enterprise | Custom | Custom | Custom |
|
||||
| Plan | Included Disk IOPS | Usage per IOPS per month | Usage per IOPS-Hr |
|
||||
| ---------- | ------------------ | ------------------------ | -------------------------- |
|
||||
| Pro | 0 | <Price price="0.119" /> | <Price price="0.000163" /> |
|
||||
| Team | 0 | <Price price="0.119" /> | <Price price="0.000163" /> |
|
||||
| Enterprise | Custom | Custom | Custom |
|
||||
|
||||
## Billing examples
|
||||
|
||||
@@ -54,29 +56,29 @@ Unlike general purpose disks, high performance disks are billed from the first p
|
||||
|
||||
Project 1 doesn't exceed the included IOPS, so no charges for IOPS apply. Project 2 exceeds the included IOPS by 600, incurring charges for this additional usage.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | ---------- | ----------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 1 | 744 hours | $10 |
|
||||
| Disk IOPS Project 1 | 3,000 IOPS | $0 |
|
||||
| | | |
|
||||
| Compute Hours Large Project 2 | 744 hours | $110 |
|
||||
| Disk IOPS Project 2 | 3,600 IOPS | $14.40 |
|
||||
| | | |
|
||||
| **Subtotal** | | **$159.40** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$149.40** |
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | ---------- | ---------------------------- |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 1 | 744 hours | <Price price="10" /> |
|
||||
| Disk IOPS Project 1 | 3,000 IOPS | <Price price="0" /> |
|
||||
| | | |
|
||||
| Compute Hours Large Project 2 | 744 hours | <Price price="110" /> |
|
||||
| Disk IOPS Project 2 | 3,600 IOPS | <Price price="14.40" /> |
|
||||
| | | |
|
||||
| **Subtotal** | | **<Price price="159.40" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="149.40" />** |
|
||||
|
||||
### Io2
|
||||
|
||||
This disk type is billed from the first IOPS provisioned, meaning for 8000 IOPS.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | ---------- | ---------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Large Project 1 | 744 hours | $110 |
|
||||
| Disk IOPS Project 1 | 8,000 IOPS | $952 |
|
||||
| **Subtotal** | | **$1,087** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$1,077** |
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | ---------- | --------------------------- |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Large Project 1 | 744 hours | <Price price="110" /> |
|
||||
| Disk IOPS Project 1 | 8,000 IOPS | <Price price="952" /> |
|
||||
| **Subtotal** | | **<Price price="1" />,087** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="1" />,077** |
|
||||
@@ -28,13 +28,15 @@ Pricing depends on the [disk type](/docs/guides/platform/compute-and-disk#disk-t
|
||||
|
||||
### General purpose disks (gp3)
|
||||
|
||||
$0.000171 per GB-Hr ($0.125 per GB per month). The primary database of your project gets provisioned with an 8 GB disk. You are only charged for provisioned disk size exceeding these 8 GB.
|
||||
<Price price="0.000171" /> per GB-Hr (<Price price="0.125" /> per GB per month). The primary
|
||||
database of your project gets provisioned with an 8 GB disk. You are only charged for provisioned
|
||||
disk size exceeding these 8 GB.
|
||||
|
||||
| Plan | Included Disk Size | Over-Usage per GB per month | Over-Usage per GB-Hr |
|
||||
| ---------- | ------------------ | --------------------------- | -------------------- |
|
||||
| Pro | 8 GB | $0.125 | $0.000171 |
|
||||
| Team | 8 GB | $0.125 | $0.000171 |
|
||||
| Enterprise | Custom | Custom | Custom |
|
||||
| Plan | Included Disk Size | Over-Usage per GB per month | Over-Usage per GB-Hr |
|
||||
| ---------- | ------------------ | --------------------------- | -------------------------- |
|
||||
| Pro | 8 GB | <Price price="0.125" /> | <Price price="0.000171" /> |
|
||||
| Team | 8 GB | <Price price="0.125" /> | <Price price="0.000171" /> |
|
||||
| Enterprise | Custom | Custom | Custom |
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
@@ -44,14 +46,14 @@ Launching a Read Replica creates an additional database with its own dedicated d
|
||||
|
||||
### High performance disks (io2)
|
||||
|
||||
$0.000267 per GB-Hr ($0.195 per GB per month).
|
||||
Unlike general purpose disks, high performance disks are billed from the first byte of provisioned disk.
|
||||
<Price price="0.000267" /> per GB-Hr (<Price price="0.195" /> per GB per month). Unlike general
|
||||
purpose disks, high performance disks are billed from the first byte of provisioned disk.
|
||||
|
||||
| Plan | Included Disk size | Usage per GB per month | Usage per GB-Hr |
|
||||
| ---------- | ------------------ | ---------------------- | --------------- |
|
||||
| Pro | 0 GB | $0.195 | $0.000267 |
|
||||
| Team | 0 GB | $0.195 | $0.000267 |
|
||||
| Enterprise | Custom | Custom | Custom |
|
||||
| Plan | Included Disk size | Usage per GB per month | Usage per GB-Hr |
|
||||
| ---------- | ------------------ | ----------------------- | -------------------------- |
|
||||
| Pro | 0 GB | <Price price="0.195" /> | <Price price="0.000267" /> |
|
||||
| Team | 0 GB | <Price price="0.195" /> | <Price price="0.000267" /> |
|
||||
| Enterprise | Custom | Custom | Custom |
|
||||
|
||||
## Billing examples
|
||||
|
||||
@@ -59,43 +61,43 @@ Unlike general purpose disks, high performance disks are billed from the first b
|
||||
|
||||
Project 1 and 2 don't exceed the included disk size, so no charges for Disk size apply. Project 3 exceeds the included disk size by 42 GB, incurring charges for this additional usage.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | --------- | ---------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 1 | 744 hours | $10 |
|
||||
| Disk Size Project 1 | 8 GB | $0 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 2 | 744 hours | $10 |
|
||||
| Disk Size Project 2 | 8 GB | $0 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 3 | 744 hours | $10 |
|
||||
| Disk Size Project 3 | 50 GB | $5.25 |
|
||||
| | | |
|
||||
| **Subtotal** | | **$50.25** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$40.25** |
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | --------- | --------------------------- |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 1 | 744 hours | <Price price="10" /> |
|
||||
| Disk Size Project 1 | 8 GB | <Price price="0" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 2 | 744 hours | <Price price="10" /> |
|
||||
| Disk Size Project 2 | 8 GB | <Price price="0" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 3 | 744 hours | <Price price="10" /> |
|
||||
| Disk Size Project 3 | 50 GB | <Price price="5.25" /> |
|
||||
| | | |
|
||||
| **Subtotal** | | **<Price price="50.25" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="40.25" />** |
|
||||
|
||||
### Io2
|
||||
|
||||
This disk type is billed from the first byte of provisioned disk, meaning for 66 GB across all projects.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | --------- | ---------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 1 | 744 hours | $10 |
|
||||
| Disk Size Project 1 | 8 GB | $1.56 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 2 | 744 hours | $10 |
|
||||
| Disk Size Project 2 | 8 GB | $1.56 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 3 | 744 hours | $10 |
|
||||
| Disk Size Project 3 | 50 GB | $9.75 |
|
||||
| | | |
|
||||
| **Subtotal** | | **$67.87** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$57.87** |
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | --------- | --------------------------- |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 1 | 744 hours | <Price price="10" /> |
|
||||
| Disk Size Project 1 | 8 GB | <Price price="1.56" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 2 | 744 hours | <Price price="10" /> |
|
||||
| Disk Size Project 2 | 8 GB | <Price price="1.56" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 3 | 744 hours | <Price price="10" /> |
|
||||
| Disk Size Project 3 | 50 GB | <Price price="9.75" /> |
|
||||
| | | |
|
||||
| **Subtotal** | | **<Price price="67.87" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="57.87" />** |
|
||||
|
||||
## View usage
|
||||
|
||||
|
||||
@@ -29,13 +29,15 @@ Pricing depends on the [disk type](/docs/guides/platform/compute-and-disk#disk-t
|
||||
|
||||
### General purpose disks (gp3)
|
||||
|
||||
$0.00013 per MB/s-Hr ($0.095 per MB/s per month). gp3 disks come with a baseline throughput of 125 MB/s. You are only charged for provisioned throughput exceeding these 125 MB/s.
|
||||
<Price price="0.00013" /> per MB/s-Hr (<Price price="0.095" /> per MB/s per month). gp3 disks come
|
||||
with a baseline throughput of 125 MB/s. You are only charged for provisioned throughput exceeding
|
||||
these 125 MB/s.
|
||||
|
||||
| Plan | Included Disk Throughput | Over-Usage per MB/s per month | Over-Usage per MB/s-Hr |
|
||||
| ---------- | ------------------------ | ----------------------------- | ---------------------- |
|
||||
| Pro | 125 MB/s | $0.095 | $0.00013 |
|
||||
| Team | 125 MB/s | $0.095 | $0.00013 |
|
||||
| Enterprise | Custom | Custom | Custom |
|
||||
| Plan | Included Disk Throughput | Over-Usage per MB/s per month | Over-Usage per MB/s-Hr |
|
||||
| ---------- | ------------------------ | ----------------------------- | ------------------------- |
|
||||
| Pro | 125 MB/s | <Price price="0.095" /> | <Price price="0.00013" /> |
|
||||
| Team | 125 MB/s | <Price price="0.095" /> | <Price price="0.00013" /> |
|
||||
| Enterprise | Custom | Custom | Custom |
|
||||
|
||||
### High performance disks (io2)
|
||||
|
||||
@@ -45,42 +47,42 @@ There are no charges. Throughput scales with IOPS at no additional cost.
|
||||
|
||||
### No additional throughput configured
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | --------- | ------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 1 | 744 hours | $10 |
|
||||
| Disk Throughput Project 1 | 125 MB/s | $0 |
|
||||
| | | |
|
||||
| **Subtotal** | | **$35** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$25** |
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | --------- | ------------------------ |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 1 | 744 hours | <Price price="10" /> |
|
||||
| Disk Throughput Project 1 | 125 MB/s | <Price price="0" /> |
|
||||
| | | |
|
||||
| **Subtotal** | | **<Price price="35" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="25" />** |
|
||||
|
||||
### Additional throughput configured
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | --------- | ----------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| | | |
|
||||
| Compute Hours Large Project 1 | 744 hours | $110 |
|
||||
| Disk Throughput Project 1 | 200 MB/s | $7.13 |
|
||||
| | | |
|
||||
| **Subtotal** | | **$142.13** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$132.13** |
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | --------- | ---------------------------- |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| | | |
|
||||
| Compute Hours Large Project 1 | 744 hours | <Price price="110" /> |
|
||||
| Disk Throughput Project 1 | 200 MB/s | <Price price="7.13" /> |
|
||||
| | | |
|
||||
| **Subtotal** | | **<Price price="142.13" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="132.13" />** |
|
||||
|
||||
### Additional throughput configured with Read Replica
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | --------- | ----------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| | | |
|
||||
| Compute Hours Large Project 1 | 744 hours | $110 |
|
||||
| Disk Throughput Project 1 | 200 MB/s | $7.13 |
|
||||
| | | |
|
||||
| Compute Hours Large Replica | 744 hours | $110 |
|
||||
| Disk Throughput Replica | 200 MB/s | $7.13 |
|
||||
| | | |
|
||||
| **Subtotal** | | **$259.26** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$249.26** |
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | --------- | ---------------------------- |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| | | |
|
||||
| Compute Hours Large Project 1 | 744 hours | <Price price="110" /> |
|
||||
| Disk Throughput Project 1 | 200 MB/s | <Price price="7.13" /> |
|
||||
| | | |
|
||||
| Compute Hours Large Replica | 744 hours | <Price price="110" /> |
|
||||
| Disk Throughput Replica | 200 MB/s | <Price price="7.13" /> |
|
||||
| | | |
|
||||
| **Subtotal** | | **<Price price="259.26" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="249.26" />** |
|
||||
@@ -13,14 +13,14 @@ Edge Function Invocations are billed using Package pricing, with each package re
|
||||
|
||||
### Example
|
||||
|
||||
For simplicity, let's assume a package size of 1 million and a charge of $2 per package without a free quota.
|
||||
For simplicity, let's assume a package size of 1 million and a charge of <Price price="2" /> per package without a free quota.
|
||||
|
||||
| Invocations | Packages Billed | Costs |
|
||||
| ----------- | --------------- | ----- |
|
||||
| 999,999 | 1 | $2 |
|
||||
| 1,000,000 | 1 | $2 |
|
||||
| 1,000,001 | 2 | $4 |
|
||||
| 1,500,000 | 2 | $4 |
|
||||
| Invocations | Packages Billed | Costs |
|
||||
| ----------- | --------------- | ------------------- |
|
||||
| 999,999 | 1 | <Price price="2" /> |
|
||||
| 1,000,000 | 1 | <Price price="2" /> |
|
||||
| 1,000,001 | 2 | <Price price="4" /> |
|
||||
| 1,500,000 | 2 | <Price price="4" /> |
|
||||
|
||||
### Usage on your invoice
|
||||
|
||||
@@ -36,27 +36,27 @@ Usage is shown as "Function Invocations" on your invoice.
|
||||
|
||||
The organization's function invocations are within the quota, so no charges apply.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| -------------------- | --------------------- | ------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Function Invocations | 1,800,000 invocations | $0 |
|
||||
| **Subtotal** | | **$35** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$25** |
|
||||
| Line Item | Units | Costs |
|
||||
| -------------------- | --------------------- | ------------------------ |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Function Invocations | 1,800,000 invocations | <Price price="0" /> |
|
||||
| **Subtotal** | | **<Price price="35" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="25" />** |
|
||||
|
||||
### Exceeding quota
|
||||
|
||||
The organization's function invocations exceed the quota by 1.4 million, incurring charges for this additional usage.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| -------------------- | --------------------- | ------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Function Invocations | 3,400,000 invocations | $4 |
|
||||
| **Subtotal** | | **$39** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$29** |
|
||||
| Line Item | Units | Costs |
|
||||
| -------------------- | --------------------- | ------------------------ |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Function Invocations | 3,400,000 invocations | <Price price="4" /> |
|
||||
| **Subtotal** | | **<Price price="39" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="29" />** |
|
||||
|
||||
## View usage
|
||||
|
||||
|
||||
@@ -59,14 +59,15 @@ Usage is shown as "Egress GB" on your invoice.
|
||||
|
||||
## Pricing
|
||||
|
||||
$0.09 per GB per month. You are only charged for usage exceeding your subscription plan's quota.
|
||||
<Price price="0.09" /> per GB per month. You are only charged for usage exceeding your subscription plan's
|
||||
quota.
|
||||
|
||||
| Plan | Unified Egress Quota | Over-Usage per month |
|
||||
| ---------- | -------------------- | -------------------- |
|
||||
| Free | 5 GB | - |
|
||||
| Pro | 250 GB | $0.09 per GB |
|
||||
| Team | 250 GB | $0.09 per GB |
|
||||
| Enterprise | Custom | Custom |
|
||||
| Plan | Unified Egress Quota | Over-Usage per month |
|
||||
| ---------- | -------------------- | ----------------------------- |
|
||||
| Free | 5 GB | - |
|
||||
| Pro | 250 GB | <Price price="0.09" /> per GB |
|
||||
| Team | 250 GB | <Price price="0.09" /> per GB |
|
||||
| Enterprise | Custom | Custom |
|
||||
|
||||
## Billing examples
|
||||
|
||||
@@ -74,27 +75,27 @@ $0.09 per GB per month. You are only charged for usage exceeding your subscripti
|
||||
|
||||
The organization's Egress usage is within the quota, so no charges for Egress apply.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------- | --------- | ------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Egress | 200 GB | $0 |
|
||||
| **Subtotal** | | **$35** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$25** |
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------- | --------- | ------------------------ |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Egress | 200 GB | <Price price="0" /> |
|
||||
| **Subtotal** | | **<Price price="35" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="25" />** |
|
||||
|
||||
### Exceeding quota
|
||||
|
||||
The organization's Egress usage exceeds the quota by 50 GB, incurring charges for this additional usage.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------- | --------- | --------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Egress | 300 GB | $4.5 |
|
||||
| **Subtotal** | | **$39.5** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$29.5** |
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------- | --------- | -------------------------- |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Egress | 300 GB | <Price price="4.5" /> |
|
||||
| **Subtotal** | | **<Price price="39.5" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="29.5" />** |
|
||||
|
||||
## View usage
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Usage is shown as "IPv4 Hours" on your invoice.
|
||||
|
||||
## Pricing
|
||||
|
||||
$0.0055 per hour ($4 per month).
|
||||
<Price price="0.0055" /> per hour (<Price price="4" /> per month).
|
||||
|
||||
## Billing examples
|
||||
|
||||
@@ -42,56 +42,56 @@ $0.0055 per hour ($4 per month).
|
||||
|
||||
The project has the IPv4 add-on enabled throughout the entire billing cycle.
|
||||
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------- |
|
||||
| Pro Plan | - | $25 |
|
||||
| Compute Hours Micro Project 1 | 744 | $10 |
|
||||
| IPv4 Hours | 744 | $4 |
|
||||
| **Subtotal** | | **$39** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$29** |
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------------------------ |
|
||||
| Pro Plan | - | <Price price="25" /> |
|
||||
| Compute Hours Micro Project 1 | 744 | <Price price="10" /> |
|
||||
| IPv4 Hours | 744 | <Price price="4" /> |
|
||||
| **Subtotal** | | **<Price price="39" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="29" />** |
|
||||
|
||||
### Multiple projects
|
||||
|
||||
All projects have the IPv4 add-on enabled throughout the entire billing cycle.
|
||||
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------- |
|
||||
| Pro Plan | - | $25 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 1 | 744 | $10 |
|
||||
| IPv4 Hours Project 1 | 744 | $4 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 2 | 744 | $10 |
|
||||
| IPv4 Hours Project 2 | 744 | $4 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 3 | 744 | $10 |
|
||||
| IPv4 Hours Project 3 | 744 | $4 |
|
||||
| | | |
|
||||
| **Subtotal** | | **$67** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$57** |
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------------------------ |
|
||||
| Pro Plan | - | <Price price="25" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 1 | 744 | <Price price="10" /> |
|
||||
| IPv4 Hours Project 1 | 744 | <Price price="4" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 2 | 744 | <Price price="10" /> |
|
||||
| IPv4 Hours Project 2 | 744 | <Price price="4" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 3 | 744 | <Price price="10" /> |
|
||||
| IPv4 Hours Project 3 | 744 | <Price price="4" /> |
|
||||
| | | |
|
||||
| **Subtotal** | | **<Price price="67" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="57" />** |
|
||||
|
||||
### One project with Read Replicas
|
||||
|
||||
The project has two Read Replicas and the IPv4 add-on enabled throughout the entire billing cycle.
|
||||
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------- |
|
||||
| Pro Plan | - | $25 |
|
||||
| | | |
|
||||
| Compute Hours Small Project 1 | 744 | $15 |
|
||||
| IPv4 Hours Project 1 | 744 | $4 |
|
||||
| | | |
|
||||
| Compute Hours Small Replica 1 | 744 | $15 |
|
||||
| IPv4 Hours Replica 1 | 744 | $4 |
|
||||
| | | |
|
||||
| Compute Hours Small Replica 2 | 744 | $15 |
|
||||
| IPv4 Hours Replica 2 | 744 | $4 |
|
||||
| | | |
|
||||
| **Subtotal** | | **$82** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$72** |
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------------------------ |
|
||||
| Pro Plan | - | <Price price="25" /> |
|
||||
| | | |
|
||||
| Compute Hours Small Project 1 | 744 | <Price price="15" /> |
|
||||
| IPv4 Hours Project 1 | 744 | <Price price="4" /> |
|
||||
| | | |
|
||||
| Compute Hours Small Replica 1 | 744 | <Price price="15" /> |
|
||||
| IPv4 Hours Replica 1 | 744 | <Price price="4" /> |
|
||||
| | | |
|
||||
| Compute Hours Small Replica 2 | 744 | <Price price="15" /> |
|
||||
| IPv4 Hours Replica 2 | 744 | <Price price="4" /> |
|
||||
| | | |
|
||||
| **Subtotal** | | **<Price price="82" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="72" />** |
|
||||
|
||||
## Optimize usage
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ Usage is shown as "Log Drain Hours" on your invoice.
|
||||
|
||||
### Pricing
|
||||
|
||||
Log Drains are available as a project Add-On for all Team and Enterprise users. Each Log Drain costs $0.0822 per hour ($60 per month).
|
||||
Log Drains are available as a project Add-On for all Team and Enterprise users. Each Log Drain costs <Price price="0.0822" /> per hour (<Price price="60" /> per month).
|
||||
|
||||
## Log Drain Events
|
||||
|
||||
@@ -40,12 +40,12 @@ Log Drain Events are billed using Package pricing, with each package representin
|
||||
|
||||
#### Example
|
||||
|
||||
| Events | Packages Billed | Costs |
|
||||
| --------- | --------------- | ----- |
|
||||
| 999,999 | 1 | $0.2 |
|
||||
| 1,000,000 | 1 | $0.2 |
|
||||
| 1,000,001 | 2 | $0.4 |
|
||||
| 1,500,000 | 2 | $0.4 |
|
||||
| Events | Packages Billed | Costs |
|
||||
| --------- | --------------- | --------------------- |
|
||||
| 999,999 | 1 | <Price price="0.2" /> |
|
||||
| 1,000,000 | 1 | <Price price="0.2" /> |
|
||||
| 1,000,001 | 2 | <Price price="0.4" /> |
|
||||
| 1,500,000 | 2 | <Price price="0.4" /> |
|
||||
|
||||
#### Usage on your invoice
|
||||
|
||||
@@ -53,29 +53,29 @@ Usage is shown as "Log Drain Events" on your invoice.
|
||||
|
||||
### Pricing
|
||||
|
||||
$0.2 per 1 million events.
|
||||
<Price price="0.2" /> per 1 million events.
|
||||
|
||||
## Billing example
|
||||
|
||||
The project has two log drains configured throughout the entire billing cycle with 800,000 and 1.6 million events each. In this example we assume that the organization is exceeding its Unified Egress Quota, so charges for Egress apply.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | ------------------ | ----------- |
|
||||
| Team Plan | 1 | $599 |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 1 | 744 hours | $10 |
|
||||
| | | |
|
||||
| Log Drain Hours Drain 1 | 744 hours | $60 |
|
||||
| Log Drain Events Drain 1 | 800,000 events | $0.2 |
|
||||
| Egress Drain 1 | 2 GB | $0.18 |
|
||||
| | | |
|
||||
| Log Drain Hours Drain 2 | 744 hours | $60 |
|
||||
| Log Drain Events Drain 2 | 1.6 million events | $0.4 |
|
||||
| Egress Drain 2 | 4 GB | $0.36 |
|
||||
| | | |
|
||||
| **Subtotal** | | **$730.14** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$720.14** |
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | ------------------ | ---------------------------- |
|
||||
| Team Plan | 1 | <Price price="599" /> |
|
||||
| | | |
|
||||
| Compute Hours Micro Project 1 | 744 hours | <Price price="10" /> |
|
||||
| | | |
|
||||
| Log Drain Hours Drain 1 | 744 hours | <Price price="60" /> |
|
||||
| Log Drain Events Drain 1 | 800,000 events | <Price price="0.2" /> |
|
||||
| Egress Drain 1 | 2 GB | <Price price="0.18" /> |
|
||||
| | | |
|
||||
| Log Drain Hours Drain 2 | 744 hours | <Price price="60" /> |
|
||||
| Log Drain Events Drain 2 | 1.6 million events | <Price price="0.4" /> |
|
||||
| Egress Drain 2 | 4 GB | <Price price="0.36" /> |
|
||||
| | | |
|
||||
| **Subtotal** | | **<Price price="730.14" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="720.14" />** |
|
||||
|
||||
## View usage
|
||||
|
||||
|
||||
@@ -75,11 +75,11 @@ The count resets at the start of each billing cycle.
|
||||
|
||||
</Admonition>
|
||||
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | ------ | ------------------ |
|
||||
| Pro | 50 | $0.015 per SSO MAU |
|
||||
| Team | 50 | $0.015 per SSO MAU |
|
||||
| Enterprise | Custom | Custom |
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | ------ | ----------------------------------- |
|
||||
| Pro | 50 | <Price price="0.015" /> per SSO MAU |
|
||||
| Team | 50 | <Price price="0.015" /> per SSO MAU |
|
||||
| Enterprise | Custom | Custom |
|
||||
|
||||
## Billing examples
|
||||
|
||||
@@ -87,27 +87,27 @@ The count resets at the start of each billing cycle.
|
||||
|
||||
The organization's SSO MAU usage for the billing cycle is within the quota, so no charges apply.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------------ | ---------- | ------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Monthly Active SSO Users | 37 SSO MAU | $0 |
|
||||
| **Subtotal** | | **$35** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$25** |
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------------ | ---------- | ------------------------ |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Monthly Active SSO Users | 37 SSO MAU | <Price price="0" /> |
|
||||
| **Subtotal** | | **<Price price="35" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="25" />** |
|
||||
|
||||
### Exceeding quota
|
||||
|
||||
The organization's SSO MAU usage for the billing cycle exceeds the quota by 10, incurring charges for this additional usage.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------------ | ---------- | ---------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Monthly Active SSO Users | 60 SSO MAU | $0.15 |
|
||||
| **Subtotal** | | **$35.15** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$25.15** |
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------------ | ---------- | --------------------------- |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Monthly Active SSO Users | 60 SSO MAU | <Price price="0.15" /> |
|
||||
| **Subtotal** | | **<Price price="35.15" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="25.15" />** |
|
||||
|
||||
## View usage
|
||||
|
||||
|
||||
+22
-22
@@ -72,12 +72,12 @@ The count resets at the start of each billing cycle.
|
||||
|
||||
</Admonition>
|
||||
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | ------- | ---------------------------- |
|
||||
| Free | 50,000 | - |
|
||||
| Pro | 100,000 | $0.00325 per Third-Party MAU |
|
||||
| Team | 100,000 | $0.00325 per Third-Party MAU |
|
||||
| Enterprise | Custom | Custom |
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | ------- | --------------------------------------------- |
|
||||
| Free | 50,000 | - |
|
||||
| Pro | 100,000 | <Price price="0.00325" /> per Third-Party MAU |
|
||||
| Team | 100,000 | <Price price="0.00325" /> per Third-Party MAU |
|
||||
| Enterprise | Custom | Custom |
|
||||
|
||||
## Billing examples
|
||||
|
||||
@@ -85,27 +85,27 @@ The count resets at the start of each billing cycle.
|
||||
|
||||
The organization's Third-Party MAU usage for the billing cycle is within the quota, so no charges apply.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| -------------------------------- | ---------------------- | ------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Monthly Active Third-Party Users | 37,000 Third-Party MAU | $0 |
|
||||
| **Subtotal** | | **$35** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$25** |
|
||||
| Line Item | Units | Costs |
|
||||
| -------------------------------- | ---------------------- | ------------------------ |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Monthly Active Third-Party Users | 37,000 Third-Party MAU | <Price price="0" /> |
|
||||
| **Subtotal** | | **<Price price="35" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="25" />** |
|
||||
|
||||
### Exceeding quota
|
||||
|
||||
The organization's Third-Party MAU usage for the billing cycle exceeds the quota by 4950, incurring charges for this additional usage.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| -------------------------------- | ----------------------- | ----------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Monthly Active Third-Party Users | 130,000 Third-Party MAU | $97.50 |
|
||||
| **Subtotal** | | **$132.50** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$122.50** |
|
||||
| Line Item | Units | Costs |
|
||||
| -------------------------------- | ----------------------- | ---------------------------- |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Monthly Active Third-Party Users | 130,000 Third-Party MAU | <Price price="97.50" /> |
|
||||
| **Subtotal** | | **<Price price="132.50" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="122.50" />** |
|
||||
|
||||
## View usage
|
||||
|
||||
|
||||
@@ -67,12 +67,12 @@ The count resets at the start of each billing cycle.
|
||||
|
||||
</Admonition>
|
||||
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | ------- | ---------------- |
|
||||
| Free | 50,000 | - |
|
||||
| Pro | 100,000 | $0.00325 per MAU |
|
||||
| Team | 100,000 | $0.00325 per MAU |
|
||||
| Enterprise | Custom | Custom |
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | ------- | --------------------------------- |
|
||||
| Free | 50,000 | - |
|
||||
| Pro | 100,000 | <Price price="0.00325" /> per MAU |
|
||||
| Team | 100,000 | <Price price="0.00325" /> per MAU |
|
||||
| Enterprise | Custom | Custom |
|
||||
|
||||
## Billing examples
|
||||
|
||||
@@ -80,27 +80,27 @@ The count resets at the start of each billing cycle.
|
||||
|
||||
The organization's MAU usage for the billing cycle is within the quota, so no charges apply.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| -------------------- | ---------- | ------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Monthly Active Users | 23,000 MAU | $0 |
|
||||
| **Subtotal** | | **$35** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$25** |
|
||||
| Line Item | Units | Costs |
|
||||
| -------------------- | ---------- | ------------------------ |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Monthly Active Users | 23,000 MAU | <Price price="0" /> |
|
||||
| **Subtotal** | | **<Price price="35" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="25" />** |
|
||||
|
||||
### Exceeding quota
|
||||
|
||||
The organization's MAU usage for the billing cycle exceeds the quota by 60,000, incurring charges for this additional usage.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| -------------------- | ----------- | -------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Monthly Active Users | 160,000 MAU | $195 |
|
||||
| **Subtotal** | | **$230** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$220** |
|
||||
| Line Item | Units | Costs |
|
||||
| -------------------- | ----------- | ------------------------- |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Monthly Active Users | 160,000 MAU | <Price price="195" /> |
|
||||
| **Subtotal** | | **<Price price="230" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="220" />** |
|
||||
|
||||
## View usage
|
||||
|
||||
|
||||
@@ -36,32 +36,32 @@ Usage is shown as "Point-in-time recovery Hours" on your invoice.
|
||||
|
||||
The project has PITR with a recovery retention period of 7 days activated throughout the entire billing cycle.
|
||||
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | -------- |
|
||||
| Pro Plan | - | $25 |
|
||||
| Compute Hours Small Project 1 | 744 | $15 |
|
||||
| PITR Hours | 744 | $100 |
|
||||
| **Subtotal** | | **$140** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$130** |
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------------------------- |
|
||||
| Pro Plan | - | <Price price="25" /> |
|
||||
| Compute Hours Small Project 1 | 744 | <Price price="15" /> |
|
||||
| PITR Hours | 744 | <Price price="100" /> |
|
||||
| **Subtotal** | | **<Price price="140" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="130" />** |
|
||||
|
||||
### Multiple projects
|
||||
|
||||
All projects have PITR with a recovery retention period of 14 days activated throughout the entire billing cycle.
|
||||
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | -------- |
|
||||
| Pro Plan | - | $25 |
|
||||
| | | |
|
||||
| Compute Hours Small Project 1 | 744 | $15 |
|
||||
| PITR Hours Project 1 | 744 | $200 |
|
||||
| | | |
|
||||
| Compute Hours Small Project 2 | 744 | $15 |
|
||||
| PITR Hours Project 2 | 744 | $200 |
|
||||
| | | |
|
||||
| **Subtotal** | | **$455** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$445** |
|
||||
| Line Item | Hours | Costs |
|
||||
| ----------------------------- | ----- | ------------------------- |
|
||||
| Pro Plan | - | <Price price="25" /> |
|
||||
| | | |
|
||||
| Compute Hours Small Project 1 | 744 | <Price price="15" /> |
|
||||
| PITR Hours Project 1 | 744 | <Price price="200" /> |
|
||||
| | | |
|
||||
| Compute Hours Small Project 2 | 744 | <Price price="15" /> |
|
||||
| PITR Hours Project 2 | 744 | <Price price="200" /> |
|
||||
| | | |
|
||||
| **Subtotal** | | **<Price price="455" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="445" />** |
|
||||
|
||||
## Optimize usage
|
||||
|
||||
|
||||
@@ -38,49 +38,49 @@ Compute incurred by Read Replicas is shown as "Replica Compute Hours" on your in
|
||||
|
||||
The project has one Read Replica and no IPv4 and no additional Disk IOPS and Disk Throughput configured.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | --------- | ---------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| | | |
|
||||
| Compute Hours Small Project 1 | 744 hours | $15 |
|
||||
| Disk Size Project 1 | 8 GB | $0 |
|
||||
| | | |
|
||||
| Compute Hours Small Replica | 744 hours | $15 |
|
||||
| Disk Size Replica | 10 GB | $1.25 |
|
||||
| | | |
|
||||
| **Subtotal** | | **$56.25** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$46.25** |
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | --------- | --------------------------- |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| | | |
|
||||
| Compute Hours Small Project 1 | 744 hours | <Price price="15" /> |
|
||||
| Disk Size Project 1 | 8 GB | <Price price="0" /> |
|
||||
| | | |
|
||||
| Compute Hours Small Replica | 744 hours | <Price price="15" /> |
|
||||
| Disk Size Replica | 10 GB | <Price price="1.25" /> |
|
||||
| | | |
|
||||
| **Subtotal** | | **<Price price="56.25" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="46.25" />** |
|
||||
|
||||
### Additional resources configured
|
||||
|
||||
The project has two Read Replicas and IPv4 and additional Disk IOPS and Disk Throughput configured.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | --------- | ----------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| | | |
|
||||
| Compute Hours Large Project 1 | 744 hours | $110 |
|
||||
| Disk Size Project 1 | 8 GB | $0 |
|
||||
| Disk IOPS Project 1 | 3600 | $14.40 |
|
||||
| Disk Throughput Project 1 | 200 MB/s | $7.13 |
|
||||
| IPv4 Hours Project 1 | 744 hours | $4 |
|
||||
| | | |
|
||||
| Compute Hours Large Replica 1 | 744 hours | $110 |
|
||||
| Disk Size Replica 1 | 10 GB | $1.25 |
|
||||
| Disk IOPS Replica 1 | 3600 | $14.40 |
|
||||
| Disk Throughput Replica 1 | 200 MB/s | $7.13 |
|
||||
| IPv4 Hours Replica 1 | 744 hours | $4 |
|
||||
| | | |
|
||||
| Compute Hours Large Replica 2 | 744 hours | $110 |
|
||||
| Disk Size Replica 2 | 10 GB | $1.25 |
|
||||
| Disk IOPS Replica 2 | 3600 | $14.40 |
|
||||
| Disk Throughput Replica 2 | 200 MB/s | $7.13 |
|
||||
| IPv4 Hours Replica 2 | 744 hours | $4 |
|
||||
| | | |
|
||||
| **Subtotal** | | **$434.09** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$424.09** |
|
||||
| Line Item | Units | Costs |
|
||||
| ----------------------------- | --------- | ---------------------------- |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| | | |
|
||||
| Compute Hours Large Project 1 | 744 hours | <Price price="110" /> |
|
||||
| Disk Size Project 1 | 8 GB | <Price price="0" /> |
|
||||
| Disk IOPS Project 1 | 3600 | <Price price="14.40" /> |
|
||||
| Disk Throughput Project 1 | 200 MB/s | <Price price="7.13" /> |
|
||||
| IPv4 Hours Project 1 | 744 hours | <Price price="4" /> |
|
||||
| | | |
|
||||
| Compute Hours Large Replica 1 | 744 hours | <Price price="110" /> |
|
||||
| Disk Size Replica 1 | 10 GB | <Price price="1.25" /> |
|
||||
| Disk IOPS Replica 1 | 3600 | <Price price="14.40" /> |
|
||||
| Disk Throughput Replica 1 | 200 MB/s | <Price price="7.13" /> |
|
||||
| IPv4 Hours Replica 1 | 744 hours | <Price price="4" /> |
|
||||
| | | |
|
||||
| Compute Hours Large Replica 2 | 744 hours | <Price price="110" /> |
|
||||
| Disk Size Replica 2 | 10 GB | <Price price="1.25" /> |
|
||||
| Disk IOPS Replica 2 | 3600 | <Price price="14.40" /> |
|
||||
| Disk Throughput Replica 2 | 200 MB/s | <Price price="7.13" /> |
|
||||
| IPv4 Hours Replica 2 | 744 hours | <Price price="4" /> |
|
||||
| | | |
|
||||
| **Subtotal** | | **<Price price="434.09" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="424.09" />** |
|
||||
|
||||
## FAQ
|
||||
|
||||
|
||||
@@ -19,14 +19,14 @@ Realtime Messages are billed using Package pricing, with each package representi
|
||||
|
||||
### Example
|
||||
|
||||
For simplicity, let's assume a package size of 1,000,000 and a charge of $2.50 per package without quota.
|
||||
For simplicity, let's assume a package size of 1,000,000 and a charge of <Price price="2.50" /> per package without quota.
|
||||
|
||||
| Messages | Packages Billed | Costs |
|
||||
| --------- | --------------- | ----- |
|
||||
| 999,999 | 1 | $2.50 |
|
||||
| 1,000,000 | 1 | $2.50 |
|
||||
| 1,000,001 | 2 | $5.00 |
|
||||
| 1,500,000 | 2 | $5.00 |
|
||||
| Messages | Packages Billed | Costs |
|
||||
| --------- | --------------- | ---------------------- |
|
||||
| 999,999 | 1 | <Price price="2.50" /> |
|
||||
| 1,000,000 | 1 | <Price price="2.50" /> |
|
||||
| 1,000,001 | 2 | <Price price="5.00" /> |
|
||||
| 1,500,000 | 2 | <Price price="5.00" /> |
|
||||
|
||||
### Usage on your invoice
|
||||
|
||||
@@ -42,27 +42,27 @@ Usage is shown as "Realtime Messages" on your invoice.
|
||||
|
||||
The organization's Realtime messages are within the quota, so no charges apply.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------- | -------------------- | ------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Realtime Messages | 1.8 million messages | $0 |
|
||||
| **Subtotal** | | **$35** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$25** |
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------- | -------------------- | ------------------------ |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Realtime Messages | 1.8 million messages | <Price price="0" /> |
|
||||
| **Subtotal** | | **<Price price="35" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="25" />** |
|
||||
|
||||
### Exceeding quota
|
||||
|
||||
The organization's Realtime messages exceed the quota by 3.5 million, incurring charges for this additional usage.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------- | -------------------- | ------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Realtime Messages | 8.5 million messages | $10 |
|
||||
| **Subtotal** | | **$45** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$35** |
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------- | -------------------- | ------------------------ |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Realtime Messages | 8.5 million messages | <Price price="10" /> |
|
||||
| **Subtotal** | | **<Price price="45" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="35" />** |
|
||||
|
||||
## View usage
|
||||
|
||||
|
||||
@@ -24,14 +24,14 @@ Realtime Peak Connections are billed using Package pricing, with each package re
|
||||
|
||||
### Example
|
||||
|
||||
For simplicity, let's assume a package size of 1,000 and a charge of $10 per package with no quota.
|
||||
For simplicity, let's assume a package size of 1,000 and a charge of <Price price="10" /> per package with no quota.
|
||||
|
||||
| Peak Connections | Packages Billed | Costs |
|
||||
| ---------------- | --------------- | ----- |
|
||||
| 999 | 1 | $10 |
|
||||
| 1,000 | 1 | $10 |
|
||||
| 1,001 | 2 | $20 |
|
||||
| 1,500 | 2 | $20 |
|
||||
| Peak Connections | Packages Billed | Costs |
|
||||
| ---------------- | --------------- | -------------------- |
|
||||
| 999 | 1 | <Price price="10" /> |
|
||||
| 1,000 | 1 | <Price price="10" /> |
|
||||
| 1,001 | 2 | <Price price="20" /> |
|
||||
| 1,500 | 2 | <Price price="20" /> |
|
||||
|
||||
### Usage on your invoice
|
||||
|
||||
@@ -47,27 +47,27 @@ Usage is shown as "Realtime Peak Connections" on your invoice.
|
||||
|
||||
The organization's connections are within the quota, so no charges apply.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------------- | --------------- | ------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Realtime Peak Connections | 350 connections | $0 |
|
||||
| **Subtotal** | | **$35** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$25** |
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------------- | --------------- | ------------------------ |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Realtime Peak Connections | 350 connections | <Price price="0" /> |
|
||||
| **Subtotal** | | **<Price price="35" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="25" />** |
|
||||
|
||||
### Exceeding quota
|
||||
|
||||
The organization's connections exceed the quota by 1,200, incurring charges for this additional usage.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------------- | ----------------- | ------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Realtime Peak Connections | 1,700 connections | $20 |
|
||||
| **Subtotal** | | **$45** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$35** |
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------------- | ----------------- | ------------------------ |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Realtime Peak Connections | 1,700 connections | <Price price="20" /> |
|
||||
| **Subtotal** | | **<Price price="45" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="35" />** |
|
||||
|
||||
## View usage
|
||||
|
||||
|
||||
+23
-23
@@ -53,14 +53,14 @@ Storage Image Transformations are billed using Package pricing, with each packag
|
||||
|
||||
### Example
|
||||
|
||||
For simplicity, let's assume a package size of 1,000 and a charge of $5 per package with no quota.
|
||||
For simplicity, let's assume a package size of 1,000 and a charge of <Price price="5" /> per package with no quota.
|
||||
|
||||
| Origin Images | Packages Billed | Costs |
|
||||
| ------------- | --------------- | ----- |
|
||||
| 999 | 1 | $5 |
|
||||
| 1,000 | 1 | $5 |
|
||||
| 1,001 | 2 | $10 |
|
||||
| 1,500 | 2 | $10 |
|
||||
| Origin Images | Packages Billed | Costs |
|
||||
| ------------- | --------------- | -------------------- |
|
||||
| 999 | 1 | <Price price="5" /> |
|
||||
| 1,000 | 1 | <Price price="5" /> |
|
||||
| 1,001 | 2 | <Price price="10" /> |
|
||||
| 1,500 | 2 | <Price price="10" /> |
|
||||
|
||||
### Usage on your invoice
|
||||
|
||||
@@ -76,27 +76,27 @@ Usage is shown as "Storage Image Transformations" on your invoice.
|
||||
|
||||
The organization's number of origin images for the billing cycle is within the quota, so no charges apply.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| --------------------- | ---------------- | ------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Image Transformations | 74 origin images | $0 |
|
||||
| **Subtotal** | | **$35** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$25** |
|
||||
| Line Item | Units | Costs |
|
||||
| --------------------- | ---------------- | ------------------------ |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Image Transformations | 74 origin images | <Price price="0" /> |
|
||||
| **Subtotal** | | **<Price price="35" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="25" />** |
|
||||
|
||||
### Exceeding quota
|
||||
|
||||
The organization's number of origin images for the billing cycle exceeds the quota by 750, incurring charges for this additional usage.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| --------------------- | ----------------- | ------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Image Transformations | 850 origin images | $5 |
|
||||
| **Subtotal** | | **$40** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$30** |
|
||||
| Line Item | Units | Costs |
|
||||
| --------------------- | ----------------- | ------------------------ |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Image Transformations | 850 origin images | <Price price="5" /> |
|
||||
| **Subtotal** | | **<Price price="40" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="30" />** |
|
||||
|
||||
## View usage
|
||||
|
||||
|
||||
@@ -26,27 +26,27 @@ Usage is shown as "Storage Size GB-Hrs" on your invoice.
|
||||
|
||||
The organization's Storage size usage is within the quota, so no charges for Storage size apply.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------- | --------- | ------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Storage Size | 85 GB | $0 |
|
||||
| **Subtotal** | | **$35** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$25** |
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------- | --------- | ------------------------ |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Storage Size | 85 GB | <Price price="0" /> |
|
||||
| **Subtotal** | | **<Price price="35" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="25" />** |
|
||||
|
||||
### Exceeding quota
|
||||
|
||||
The organization's Storage size usage exceeds the quota by 257 GB, incurring charges for this additional usage.
|
||||
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------- | --------- | --------- |
|
||||
| Pro Plan | 1 | $25 |
|
||||
| Compute Hours Micro | 744 hours | $10 |
|
||||
| Storage Size | 357 GB | $5.4 |
|
||||
| **Subtotal** | | **$40.4** |
|
||||
| Compute Credits | | -$10 |
|
||||
| **Total** | | **$30.4** |
|
||||
| Line Item | Units | Costs |
|
||||
| ------------------- | --------- | -------------------------- |
|
||||
| Pro Plan | 1 | <Price price="25" /> |
|
||||
| Compute Hours Micro | 744 hours | <Price price="10" /> |
|
||||
| Storage Size | 357 GB | <Price price="5.4" /> |
|
||||
| **Subtotal** | | **<Price price="40.4" />** |
|
||||
| Compute Credits | | -<Price price="10" /> |
|
||||
| **Total** | | **<Price price="30.4" />** |
|
||||
|
||||
## View usage
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Paid plans come with a usage quota for certain line items. You only pay for usag
|
||||
|
||||
#### Compute Credits
|
||||
|
||||
Paid plans come with $10 in Compute Credits per month. This suffices for a single project using a Nano or Micro compute instance. Every additional project adds compute fees to your monthly invoice though.
|
||||
Paid plans come with <Price price="10" /> in Compute Credits per month. This suffices for a single project using a Nano or Micro compute instance. Every additional project adds compute fees to your monthly invoice though.
|
||||
|
||||
### Example invoice
|
||||
|
||||
@@ -42,18 +42,18 @@ The following invoice was issued on January 6, 2025 with the previous billing cy
|
||||
|
||||
1. The final amount due
|
||||
2. Fixed subscription fee for the current billing cycle
|
||||
3. Usage based fee for Compute for the previous billing cycle. There were two projects (`wsmmedyqtlrvbcesxdew`, `wwxdpovgtfcmcnxwsaad`) running 744 hours (24 hours \* 31 days). These projects incurred $10 in Compute fees each. With $10 in Compute Credits deducted, the final Compute fees are $10.
|
||||
4. Usage based fee for Custom Domain for the previous billing cycle. There is no free usage quota for Custom Domain. You get charged for the 744 hours (24 hours \* 31 days) a Custom Domain was active. The final Custom Domain fees are $10.19.
|
||||
5. Usage based fee for Egress for the previous billing cycle. There is a free usage quota of 250 GB for Egress. You get charged for usage beyond 250 GB only, meaning for 2,119.47 GB. The final Egress fees are $190.75.
|
||||
3. Usage based fee for Compute for the previous billing cycle. There were two projects (`wsmmedyqtlrvbcesxdew`, `wwxdpovgtfcmcnxwsaad`) running 744 hours (24 hours \* 31 days). These projects incurred <Price price="10" /> in Compute fees each. With <Price price="10" /> in Compute Credits deducted, the final Compute fees are <Price price="10." />
|
||||
4. Usage based fee for Custom Domain for the previous billing cycle. There is no free usage quota for Custom Domain. You get charged for the 744 hours (24 hours \* 31 days) a Custom Domain was active. The final Custom Domain fees are <Price price="10.19" />.
|
||||
5. Usage based fee for Egress for the previous billing cycle. There is a free usage quota of 250 GB for Egress. You get charged for usage beyond 250 GB only, meaning for 2,119.47 GB. The final Egress fees are <Price price="190.75" />.
|
||||
6. Usage based fee for Monthly Active Users for the previous billing cycle. There is a free usage quota of 100,000 users. With 141 users there is no charge for this line item.
|
||||
|
||||
{/* supa-mdx-lint-disable-next-line Rule004ExcludeWords */}
|
||||
|
||||
### Why is my invoice more than $25?
|
||||
### Why is my invoice more than <Price price="25" />?
|
||||
|
||||
The amount due of your invoice being higher than the $25 subscription fee for the Pro Plan can have several reasons.
|
||||
The amount due of your invoice being higher than the <Price price="25" /> subscription fee for the Pro Plan can have several reasons.
|
||||
|
||||
- **Running several projects:** You had more than one project running in the previous billing cycle. Supabase provides a dedicated server and database for every project. That means that every project you launch incurs compute costs. While the $10 Compute Credits cover a single project using a Nano or Micro compute instance, every additional project adds at least $10 compute costs to your invoice.
|
||||
- **Running several projects:** You had more than one project running in the previous billing cycle. Supabase provides a dedicated server and database for every project. That means that every project you launch incurs compute costs. While the <Price price="10" /> Compute Credits cover a single project using a Nano or Micro compute instance, every additional project adds at least <Price price="10" /> compute costs to your invoice.
|
||||
- **Usage beyond quota:** You exceeded the included usage quota for one or more line items in the previous billing cycle while having the Spend Cap disabled.
|
||||
- **Usage that is not covered by the Spend Cap:** You had usage in the previous billing cycle that is not covered by the [Spend Cap](/docs/guides/platform/cost-control#spend-cap). For example using an IPv4 address or a custom domain.
|
||||
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ Here are your options if your server platform doesn't support IPv6:
|
||||
- Use the [Supabase Client libraries](https://supabase.com/docs/guides/api/rest/client-libs), which are IPv4 compatible.
|
||||
- Enable the [dedicated IPv4 Add-On](https://supabase.com/dashboard/project/_/settings/addons) (available to Pro and above organizations)
|
||||
|
||||
> Note: the IPv4 Add-On costs $0.0055 an hour, which equates to ~$4.00 if left on for a full month (~720 hours)
|
||||
> Note: the IPv4 Add-On costs <Price price="0.0055" /> an hour, which equates to ~<Price price="4.00" /> if left on for a full month (~720 hours)
|
||||
|
||||
### Checking IPv6 support:
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import { CodeBlock } from '~/features/ui/CodeBlock/CodeBlock'
|
||||
import InfoTooltip from '~/features/ui/InfoTooltip'
|
||||
import { TabPanel, Tabs } from '~/features/ui/Tabs'
|
||||
import { ShowUntil } from '~/features/ui/ShowUntil'
|
||||
import { Price } from '~/components/Price'
|
||||
|
||||
const components = {
|
||||
Accordion,
|
||||
@@ -80,6 +81,7 @@ const components = {
|
||||
</Heading>
|
||||
),
|
||||
pre: CodeBlock,
|
||||
Price,
|
||||
}
|
||||
|
||||
export { components }
|
||||
@@ -33,3 +33,66 @@ export async function pluckPromise<T, K extends keyof T>(promise: Promise<T>, ke
|
||||
const data = await promise
|
||||
return data[key]
|
||||
}
|
||||
|
||||
export class Result<Ok, Error> {
|
||||
constructor(private internal: { data: Ok; error: null } | { data: null; error: Error }) {}
|
||||
|
||||
static ok<Ok, Error>(data: Ok): Result<Ok, Error> {
|
||||
return new Result({ data, error: null })
|
||||
}
|
||||
|
||||
static error<Ok, Error>(error: Error): Result<Ok, Error> {
|
||||
return new Result({ data: null, error })
|
||||
}
|
||||
|
||||
static async tryCatchFlat<
|
||||
Ok,
|
||||
Args extends Array<unknown> = [],
|
||||
OuterError = unknown,
|
||||
InnerError = unknown,
|
||||
>(
|
||||
fn: (...args: Args) => Promise<Result<Ok, InnerError>>,
|
||||
onError: (error: unknown) => OuterError | InnerError,
|
||||
...args: Args
|
||||
): Promise<Result<Ok, OuterError | InnerError>> {
|
||||
try {
|
||||
return await fn(...args)
|
||||
} catch (error: unknown) {
|
||||
return Result.error(onError(error))
|
||||
}
|
||||
}
|
||||
|
||||
isOk(): this is Result<Ok, never> {
|
||||
return this.internal.error == null
|
||||
}
|
||||
|
||||
map<Mapped>(fn: (data: Ok) => Mapped): Result<Mapped, Error> {
|
||||
if (this.isOk()) return Result.ok(fn(this.internal.data))
|
||||
return this as unknown as Result<Mapped, Error>
|
||||
}
|
||||
|
||||
mapError<MappedError>(fn: (error: Error) => MappedError): Result<Ok, MappedError> {
|
||||
if (this.isOk()) return this as unknown as Result<Ok, MappedError>
|
||||
return Result.error(fn(this.internal.error))
|
||||
}
|
||||
|
||||
flatMap<Mapped>(fn: (data: Ok) => Result<Mapped, Error>): Result<Mapped, Error> {
|
||||
if (this.isOk()) return fn(this.internal.data)
|
||||
return this as unknown as Result<Mapped, Error>
|
||||
}
|
||||
|
||||
flatMapAsync<Mapped>(
|
||||
fn: (data: Ok) => Promise<Result<Mapped, Error>>
|
||||
): Promise<Result<Mapped, Error>> {
|
||||
if (this.isOk()) return fn(this.internal.data)
|
||||
return Promise.resolve(this as unknown as Result<Mapped, Error>)
|
||||
}
|
||||
|
||||
match<Mapped, MappedError>(
|
||||
onOk: (data: Ok) => Mapped,
|
||||
onError: (error: Error) => MappedError
|
||||
): Mapped | MappedError {
|
||||
if (this.isOk()) return onOk(this.internal.data)
|
||||
return onError(this.internal.error)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import OpenAI from 'openai'
|
||||
import 'server-only'
|
||||
import {
|
||||
convertUnknownToApiError,
|
||||
InvalidRequestError,
|
||||
type ApiError,
|
||||
type ApiErrorGeneric,
|
||||
} from '~/app/api/utils'
|
||||
import { Result } from '~/features/helpers.fn'
|
||||
|
||||
type Embedding = Array<number>
|
||||
|
||||
interface ModerationFlaggedDetails {
|
||||
flagged: boolean
|
||||
categories: OpenAI.Moderations.Moderation.Categories
|
||||
}
|
||||
|
||||
export interface OpenAIClientInterface {
|
||||
createContentEmbedding(text: string): Promise<Result<Embedding, ApiErrorGeneric>>
|
||||
}
|
||||
|
||||
let openAIClient: OpenAIClientInterface | null
|
||||
|
||||
class OpenAIClient implements OpenAIClientInterface {
|
||||
static CONTENT_EMBEDDING_MODEL = 'text-embedding-ada-002'
|
||||
|
||||
constructor(private client: OpenAI) {}
|
||||
|
||||
async createContentEmbedding(text: string): Promise<Result<Embedding, ApiErrorGeneric>> {
|
||||
return await Result.tryCatchFlat(
|
||||
this.createContentEmbeddingImpl.bind(this),
|
||||
convertUnknownToApiError,
|
||||
text
|
||||
)
|
||||
}
|
||||
|
||||
private async createContentEmbeddingImpl(
|
||||
text: string
|
||||
): Promise<Result<Embedding, ApiError<ModerationFlaggedDetails>>> {
|
||||
const query = text.trim()
|
||||
|
||||
const moderationResponse = await this.client.moderations.create({ input: query })
|
||||
const [result] = moderationResponse.results
|
||||
if (result.flagged) {
|
||||
return Result.error(
|
||||
new InvalidRequestError('Content flagged as inappropriate', undefined, {
|
||||
flagged: true,
|
||||
categories: result.categories,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const embeddingsResponse = await this.client.embeddings.create({
|
||||
model: OpenAIClient.CONTENT_EMBEDDING_MODEL,
|
||||
input: query,
|
||||
})
|
||||
const [{ embedding: queryEmbedding }] = embeddingsResponse.data
|
||||
return Result.ok(queryEmbedding)
|
||||
}
|
||||
}
|
||||
|
||||
export function openAI(): OpenAIClientInterface {
|
||||
if (!openAIClient) {
|
||||
openAIClient = new OpenAIClient(new OpenAI())
|
||||
}
|
||||
return openAIClient
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { createClient, type SupabaseClient } from '@supabase/supabase-js'
|
||||
import { type Database as DatabaseGenerated } from 'common'
|
||||
|
||||
type Database = {
|
||||
graphql_public: DatabaseGenerated['graphql_public']
|
||||
public: {
|
||||
Tables: DatabaseGenerated['public']['Tables']
|
||||
Views: DatabaseGenerated['public']['Views']
|
||||
Functions: Omit<DatabaseGenerated['public']['Functions'], 'search_content'> & {
|
||||
search_content: {
|
||||
Args: Omit<
|
||||
DatabaseGenerated['public']['Functions']['search_content']['Args'],
|
||||
'embedding'
|
||||
> & { embedding: Array<number> }
|
||||
Returns: Array<
|
||||
Omit<
|
||||
DatabaseGenerated['public']['Functions']['search_content']['Returns'][number],
|
||||
'subsections'
|
||||
> & { subsections: Array<{ title?: string; href?: string; content?: string }> }
|
||||
>
|
||||
}
|
||||
}
|
||||
Enums: DatabaseGenerated['public']['Enums']
|
||||
CompositeTypes: DatabaseGenerated['public']['CompositeTypes']
|
||||
}
|
||||
storage: DatabaseGenerated['storage']
|
||||
}
|
||||
|
||||
let _supabase: SupabaseClient<Database>
|
||||
|
||||
export function supabase() {
|
||||
if (!_supabase) {
|
||||
_supabase = createClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
||||
)
|
||||
}
|
||||
|
||||
return _supabase
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
"last-changed:reset": "pnpm run last-changed -- --reset",
|
||||
"codegen:examples": "cp -r ../../examples ./examples",
|
||||
"codemod:frontmatter": "node ./scripts/codemod/mdx-meta.mjs && prettier --write \"content/**/*.mdx\"",
|
||||
"codegen:graphql": "tsx ./scripts/graphqlSchema.ts && graphql-codegen --config codegen.ts",
|
||||
"codegen:graphql": "tsx --conditions=react-server ./scripts/graphqlSchema.ts && graphql-codegen --config codegen.ts",
|
||||
"codegen:references": "tsx features/docs/Reference.generated.script.ts",
|
||||
"clean": "rimraf node_modules features/docs/generated examples __generated__"
|
||||
},
|
||||
@@ -114,7 +114,6 @@
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aws-sdk/client-secrets-manager": "^3.410.0",
|
||||
"@graphiql/toolkit": "^0.9.1",
|
||||
"@graphql-codegen/cli": "5.0.5",
|
||||
"@graphql-codegen/typescript": "4.1.6",
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { type RootQueryTypeSearchDocsArgs } from '~/__generated__/graphql'
|
||||
import { convertPostgrestToApiError, type ApiErrorGeneric } from '~/app/api/utils'
|
||||
import { Result } from '~/features/helpers.fn'
|
||||
import { openAI } from '~/lib/openAi'
|
||||
import { supabase } from '~/lib/supabase'
|
||||
import { GuideModel } from '../guide/guideModel'
|
||||
|
||||
export abstract class SearchResultModel {
|
||||
static async search(
|
||||
args: RootQueryTypeSearchDocsArgs,
|
||||
requestedFields: Array<string>
|
||||
): Promise<Result<SearchResultModel[], ApiErrorGeneric>> {
|
||||
const query = args.query.trim()
|
||||
const includeFullContent = requestedFields.includes('content')
|
||||
const embeddingResult = await openAI().createContentEmbedding(query)
|
||||
|
||||
return embeddingResult.flatMapAsync(async (embedding) => {
|
||||
const matchResult = new Result(
|
||||
await supabase().rpc('search_content', {
|
||||
embedding,
|
||||
include_full_content: includeFullContent,
|
||||
max_result: args.limit,
|
||||
})
|
||||
)
|
||||
.map((matches) =>
|
||||
matches
|
||||
.map(({ type, page_title, href, content, subsections }) => {
|
||||
switch (type) {
|
||||
case 'markdown':
|
||||
return new GuideModel({
|
||||
title: page_title,
|
||||
href,
|
||||
content,
|
||||
subsections,
|
||||
})
|
||||
default:
|
||||
return null
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
)
|
||||
.mapError(convertPostgrestToApiError)
|
||||
|
||||
return matchResult
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
GraphQLError,
|
||||
GraphQLInt,
|
||||
GraphQLNonNull,
|
||||
GraphQLString,
|
||||
type GraphQLResolveInfo,
|
||||
} from 'graphql'
|
||||
import {
|
||||
type RootQueryTypeSearchDocsArgs,
|
||||
type SearchResultCollection,
|
||||
} from '~/__generated__/graphql'
|
||||
import { convertUnknownToApiError, type ApiErrorGeneric } from '~/app/api/utils'
|
||||
import { Result } from '~/features/helpers.fn'
|
||||
import { createCollectionType, GraphQLCollectionBuilder } from '../utils/connections'
|
||||
import { graphQLFields } from '../utils/fields'
|
||||
import { SearchResultModel } from './globalSearchModel'
|
||||
import { GRAPHQL_FIELD_SEARCH_GLOBAL, GraphQLInterfaceTypeSearchResult } from './globalSearchSchema'
|
||||
|
||||
async function resolveSearch(
|
||||
parent: unknown,
|
||||
args: RootQueryTypeSearchDocsArgs,
|
||||
context: unknown,
|
||||
info: GraphQLResolveInfo
|
||||
): Promise<SearchResultCollection | GraphQLError> {
|
||||
return (
|
||||
await Result.tryCatchFlat(
|
||||
resolveSearchImpl,
|
||||
convertUnknownToApiError,
|
||||
parent,
|
||||
args,
|
||||
context,
|
||||
info
|
||||
)
|
||||
).match(
|
||||
(data) => GraphQLCollectionBuilder.create({ items: data }),
|
||||
(error) => {
|
||||
console.error(`Error resolving ${GRAPHQL_FIELD_SEARCH_GLOBAL}:`, error)
|
||||
return new GraphQLError(error.isPrivate() ? 'Internal Server Error' : error.message)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async function resolveSearchImpl(
|
||||
_parent: unknown,
|
||||
args: RootQueryTypeSearchDocsArgs,
|
||||
_context: unknown,
|
||||
info: GraphQLResolveInfo
|
||||
): Promise<Result<Array<SearchResultModel>, ApiErrorGeneric>> {
|
||||
const requestedFields = Object.keys(graphQLFields(info).nodes)
|
||||
return await SearchResultModel.search(args, requestedFields)
|
||||
}
|
||||
|
||||
export const searchRoot = {
|
||||
[GRAPHQL_FIELD_SEARCH_GLOBAL]: {
|
||||
description: 'Search the Supabase docs for content matching a query string',
|
||||
args: {
|
||||
query: {
|
||||
type: new GraphQLNonNull(GraphQLString),
|
||||
},
|
||||
limit: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
},
|
||||
type: createCollectionType(GraphQLInterfaceTypeSearchResult, {
|
||||
skipPageInfo: true,
|
||||
description: 'A collection of search results containing content from Supabase docs',
|
||||
}),
|
||||
resolve: resolveSearch,
|
||||
},
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { GraphQLInterfaceType, GraphQLString } from 'graphql'
|
||||
|
||||
export const GRAPHQL_FIELD_SEARCH_GLOBAL = 'searchDocs'
|
||||
export const GRAPHQL_FIELD_SEARCH_GLOBAL = 'searchDocs' as const
|
||||
|
||||
export const GraphQLInterfaceTypeSearchResult = new GraphQLInterfaceType({
|
||||
name: 'SearchResult',
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
printSchema,
|
||||
} from 'graphql'
|
||||
import { RootQueryTypeResolvers } from '~/__generated__/graphql'
|
||||
import { searchRoot } from './globalSearch/globalSearchResolver'
|
||||
import { GraphQLObjectTypeGuide } from './guide/guideSchema'
|
||||
|
||||
const GRAPHQL_FIELD_INTROSPECT = 'schema' as const
|
||||
|
||||
@@ -29,6 +31,8 @@ export const rootGraphQLSchema = new GraphQLSchema({
|
||||
name: 'RootQueryType',
|
||||
fields: {
|
||||
...introspectRoot,
|
||||
...searchRoot,
|
||||
},
|
||||
}),
|
||||
types: [GraphQLObjectTypeGuide],
|
||||
})
|
||||
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Taken from the [graphql-fields](https://github.com/robrichard/graphql-fields)
|
||||
* package, available under the MIT license.
|
||||
*
|
||||
* See [original license](https://github.com/robrichard/graphql-fields/blob/3446ddf36df03fb8f906596c039988cc2faf94a8/LICENSE).
|
||||
*
|
||||
* Reproduced here to avoid taking on another dependency for a relatively short
|
||||
* piece of code.
|
||||
*/
|
||||
|
||||
import {
|
||||
type DirectiveNode,
|
||||
type FieldNode,
|
||||
type FragmentDefinitionNode,
|
||||
type FragmentSpreadNode,
|
||||
type GraphQLResolveInfo,
|
||||
type InlineFragmentNode,
|
||||
type SelectionNode,
|
||||
type ValueNode,
|
||||
} from 'graphql'
|
||||
|
||||
let options = { processArguments: false, excludedFields: [] }
|
||||
|
||||
function getSelections(ast: FieldNode | FragmentDefinitionNode | InlineFragmentNode) {
|
||||
if (
|
||||
ast &&
|
||||
ast.selectionSet &&
|
||||
ast.selectionSet.selections &&
|
||||
ast.selectionSet.selections.length
|
||||
) {
|
||||
return ast.selectionSet.selections
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
function isFragment(ast: SelectionNode): ast is FragmentSpreadNode | InlineFragmentNode {
|
||||
return ast.kind === 'InlineFragment' || ast.kind === 'FragmentSpread'
|
||||
}
|
||||
|
||||
function getAST(ast: FragmentSpreadNode | InlineFragmentNode, info: GraphQLResolveInfo) {
|
||||
if (ast.kind === 'FragmentSpread') {
|
||||
const fragmentName = ast.name.value
|
||||
return info.fragments[fragmentName]
|
||||
}
|
||||
return ast
|
||||
}
|
||||
|
||||
function getArguments(ast: FieldNode, info: GraphQLResolveInfo) {
|
||||
return ast.arguments.map((argument) => {
|
||||
const argumentValue = getArgumentValue(argument.value, info)
|
||||
|
||||
return {
|
||||
[argument.name.value]: {
|
||||
kind: argument.value.kind,
|
||||
value: argumentValue,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getArgumentValue(arg: ValueNode, info: GraphQLResolveInfo) {
|
||||
switch (arg.kind) {
|
||||
case 'FloatValue':
|
||||
return parseFloat(arg.value)
|
||||
case 'IntValue':
|
||||
return parseInt(arg.value, 10)
|
||||
case 'Variable':
|
||||
return info.variableValues[arg.name.value]
|
||||
case 'ListValue':
|
||||
return arg.values.map((argument) => getArgumentValue(argument, info))
|
||||
case 'ObjectValue':
|
||||
return arg.fields.reduce((argValue, objectField) => {
|
||||
argValue[objectField.name.value] = getArgumentValue(objectField.value, info)
|
||||
return argValue
|
||||
}, {})
|
||||
default:
|
||||
// @ts-ignore
|
||||
return arg.value
|
||||
}
|
||||
}
|
||||
|
||||
function getDirectiveValue(directive: DirectiveNode, info: GraphQLResolveInfo) {
|
||||
const arg = directive.arguments[0]
|
||||
if (arg.value.kind !== 'Variable') {
|
||||
return arg.value.kind === 'BooleanValue' ? arg.value.value : undefined
|
||||
}
|
||||
return info.variableValues[arg.value.name.value]
|
||||
}
|
||||
|
||||
function getDirectiveResults(ast: SelectionNode, info: GraphQLResolveInfo) {
|
||||
const directiveResult = {
|
||||
shouldInclude: true,
|
||||
shouldSkip: false,
|
||||
}
|
||||
return ast.directives.reduce((result, directive) => {
|
||||
switch (directive.name.value) {
|
||||
case 'include':
|
||||
const directiveValue = getDirectiveValue(directive, info)
|
||||
if (directiveValue != undefined) {
|
||||
return { ...result, shouldInclude: directiveValue }
|
||||
}
|
||||
case 'skip':
|
||||
const directiveValule = getDirectiveValue(directive, info)
|
||||
if (directiveValue != undefined) {
|
||||
return { ...result, shouldSkip: directiveValue }
|
||||
}
|
||||
default:
|
||||
return result
|
||||
}
|
||||
}, directiveResult)
|
||||
}
|
||||
|
||||
function flattenAST(
|
||||
ast: FieldNode | FragmentDefinitionNode | InlineFragmentNode,
|
||||
info: GraphQLResolveInfo,
|
||||
obj?: Record<string, any>
|
||||
) {
|
||||
obj = obj || {}
|
||||
return getSelections(ast).reduce((flattened, a) => {
|
||||
if (a.directives && a.directives.length) {
|
||||
const { shouldInclude, shouldSkip } = getDirectiveResults(a, info)
|
||||
if (shouldSkip || !shouldInclude) {
|
||||
return flattened
|
||||
}
|
||||
}
|
||||
if (isFragment(a)) {
|
||||
flattened = flattenAST(getAST(a, info), info, flattened)
|
||||
} else {
|
||||
const name = a.name.value
|
||||
if (options.excludedFields.indexOf(name) !== -1) {
|
||||
return flattened
|
||||
}
|
||||
if (flattened[name] && flattened[name] !== '__arguments') {
|
||||
Object.assign(flattened[name], flattenAST(a, info, flattened[name]))
|
||||
} else {
|
||||
flattened[name] = flattenAST(a, info)
|
||||
}
|
||||
if (options.processArguments) {
|
||||
// check if the current field has arguments
|
||||
if (a.arguments && a.arguments.length) {
|
||||
Object.assign(flattened[name], { __arguments: getArguments(a, info) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return flattened
|
||||
}, obj)
|
||||
}
|
||||
|
||||
export function graphQLFields(
|
||||
info: GraphQLResolveInfo,
|
||||
obj = {},
|
||||
opts = { processArguments: false }
|
||||
): Record<string, any> {
|
||||
const fields = info.fieldNodes
|
||||
options.processArguments = opts.processArguments
|
||||
return fields.reduce((o, ast) => {
|
||||
return flattenAST(ast, info, o)
|
||||
}, obj)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
clispec: '001'
|
||||
info:
|
||||
id: cli
|
||||
version: 2.22.6
|
||||
version: 2.22.12
|
||||
title: Supabase CLI
|
||||
language: sh
|
||||
source: https://github.com/supabase/cli
|
||||
@@ -904,17 +904,6 @@ commands:
|
||||
- id: supabase-secrets
|
||||
title: supabase secrets
|
||||
summary: Manage Supabase secrets
|
||||
description: |
|
||||
Provides tools for managing environment variables and secrets for your Supabase project.
|
||||
|
||||
This command group allows you to set, unset, and list secrets that are securely stored and
|
||||
made available to Edge Functions as environment variables.
|
||||
|
||||
Secrets management through the CLI is useful for:
|
||||
- Setting environment-specific configuration
|
||||
- Managing sensitive credentials securely
|
||||
|
||||
Secrets can be set individually or loaded from .env files for convenience.
|
||||
tags:
|
||||
- management-api
|
||||
links: []
|
||||
@@ -969,15 +958,6 @@ commands:
|
||||
- id: supabase-projects
|
||||
title: supabase projects
|
||||
summary: Manage Supabase projects
|
||||
description: |
|
||||
Provides tools for creating and managing your Supabase projects.
|
||||
|
||||
This command group allows you to list all projects in your organizations, create new projects,
|
||||
delete existing projects, and retrieve API keys. These operations help you manage your Supabase
|
||||
infrastructure programmatically without using the dashboard.
|
||||
|
||||
Project management via CLI is especially useful for automation scripts and when you need to
|
||||
provision environments in a repeatable way.
|
||||
tags:
|
||||
- management-api
|
||||
links: []
|
||||
@@ -2512,17 +2492,6 @@ commands:
|
||||
- id: supabase-gen-types
|
||||
title: supabase gen types
|
||||
summary: Generate types from Postgres schema
|
||||
description: |
|
||||
Automatically generates type definitions based on your Postgres database schema.
|
||||
|
||||
This command connects to your database (local or remote) and generates typed definitions
|
||||
that match your database tables, views, and stored procedures. By default, it generates TypeScript
|
||||
definitions, but also supports Go and Swift.
|
||||
|
||||
Generated types give you type safety and autocompletion when working with your database in code,
|
||||
helping prevent runtime errors and improving developer experience.
|
||||
|
||||
The types respect relationships, constraints, and custom types defined in your database schema.
|
||||
tags: []
|
||||
links: []
|
||||
usage: supabase gen types [flags]
|
||||
@@ -2602,14 +2571,6 @@ commands:
|
||||
- id: supabase-functions
|
||||
title: supabase functions
|
||||
summary: Manage Supabase Edge functions
|
||||
description: |
|
||||
Supabase Edge Functions are server-less functions that run close to your users.
|
||||
|
||||
Edge Functions allow you to execute custom server-side code without deploying or scaling a traditional server.
|
||||
They're ideal for handling webhooks, custom API endpoints, data validation, and serving personalized content.
|
||||
|
||||
Edge Functions are written in TypeScript and run on Deno compatible edge runtime, which is a secure runtime
|
||||
with no package management needed, fast cold starts, and built-in security.
|
||||
tags:
|
||||
- management-api
|
||||
links: []
|
||||
@@ -2695,15 +2656,6 @@ commands:
|
||||
- id: supabase-functions-new
|
||||
title: supabase functions new
|
||||
summary: Create a new Function locally
|
||||
description: |
|
||||
Creates a new Edge Function with boilerplate code in the `supabase/functions` directory.
|
||||
|
||||
This command generates a starter TypeScript file with the necessary Deno imports and a
|
||||
basic function structure. The function is created as a new directory with the name you specify,
|
||||
containing an `index.ts` file with the function code.
|
||||
|
||||
After creating the function, you can edit it locally and then use `supabase functions serve`
|
||||
to test it before deploying with `supabase functions deploy`.
|
||||
tags: []
|
||||
links: []
|
||||
usage: supabase functions new <Function name>
|
||||
@@ -3432,10 +3384,6 @@ commands:
|
||||
- id: supabase-config-push
|
||||
title: supabase config push
|
||||
summary: Pushes local config.toml to the linked project
|
||||
description: |
|
||||
Updates the configurations of a linked Supabase project with the local `supabase/config.toml` file.
|
||||
|
||||
This command allows you to manage project configuration as code by defining settings locally and then pushing them to your remote project.
|
||||
tags: []
|
||||
links: []
|
||||
usage: supabase config push
|
||||
|
||||
@@ -419,9 +419,10 @@ const MfaAuthSettingsForm = () => {
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>
|
||||
Enabling advanced MFA with phone will result in an additional charge of $75
|
||||
per month for the first project in the organization and an additional $10 per
|
||||
month for additional projects.
|
||||
Enabling advanced MFA with phone will result in an additional charge of{' '}
|
||||
<span translate="no">$75</span>
|
||||
per month for the first project in the organization and an additional{' '}
|
||||
<span translate="no">$10</span> per month for additional projects.
|
||||
</AlertTitle_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
</CardContent>
|
||||
|
||||
@@ -48,61 +48,61 @@ const SpendCapModal = ({ visible, onHide }: SpendCapModalProps) => {
|
||||
<TableRow>
|
||||
<TableCell>Disk Size</TableCell>
|
||||
<TableCell>8 GB per project</TableCell>
|
||||
<TableCell>$0.125 per GB</TableCell>
|
||||
<TableCell translate="no">$0.125 per GB</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Egress</TableCell>
|
||||
<TableCell>250 GB</TableCell>
|
||||
<TableCell>$0.09 per GB</TableCell>
|
||||
<TableCell translate="no">$0.09 per GB</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Auth MAUs</TableCell>
|
||||
<TableCell>100,000</TableCell>
|
||||
<TableCell>$0.00325 per user</TableCell>
|
||||
<TableCell translate="no">$0.00325 per user</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Auth Third-Party MAUs</TableCell>
|
||||
<TableCell>100,000</TableCell>
|
||||
<TableCell>$0.00325 per user</TableCell>
|
||||
<TableCell translate="no">$0.00325 per user</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Auth Single Sign-On MAUs</TableCell>
|
||||
<TableCell>50</TableCell>
|
||||
<TableCell>$0.015 per user</TableCell>
|
||||
<TableCell translate="no">$0.015 per user</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Storage Size</TableCell>
|
||||
<TableCell>100 GB</TableCell>
|
||||
<TableCell>$0.021 per GB</TableCell>
|
||||
<TableCell translate="no">$0.021 per GB</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Storage Image Transformations</TableCell>
|
||||
<TableCell>100 origin images</TableCell>
|
||||
<TableCell>$5 per 1000 images</TableCell>
|
||||
<TableCell translate="no">$5 per 1000 images</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Realtime Concurrent Peak Connections</TableCell>
|
||||
<TableCell>500</TableCell>
|
||||
<TableCell>$10 per 1000</TableCell>
|
||||
<TableCell translate="no">$10 per 1000</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Realtime Messages</TableCell>
|
||||
<TableCell>5 Million</TableCell>
|
||||
<TableCell>$2.50 per Million</TableCell>
|
||||
<TableCell translate="no">$2.50 per Million</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Function Invocations</TableCell>
|
||||
<TableCell>2 Million</TableCell>
|
||||
<TableCell>$2 per Million</TableCell>
|
||||
<TableCell translate="no">$2 per Million</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
+3
-1
@@ -116,7 +116,9 @@ const TableDataRow = ({
|
||||
free={upgradeIncluded}
|
||||
/>
|
||||
) : (
|
||||
<span className="text-xs font-mono">{formatCurrency(beforePrice)}</span>
|
||||
<span className="text-xs font-mono" translate="no">
|
||||
{formatCurrency(beforePrice)}
|
||||
</span>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -219,7 +219,10 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<span className="text-foreground text-sm font-semibold">
|
||||
<span
|
||||
className="text-foreground text-sm font-semibold"
|
||||
translate="no"
|
||||
>
|
||||
${price}
|
||||
</span>
|
||||
<span className="text-foreground-light translate-y-[1px]">
|
||||
|
||||
@@ -41,7 +41,7 @@ const BillingChangeBadge = ({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-xs font-mono text-foreground-muted">
|
||||
<span className="text-xs font-mono text-foreground-muted" translate="no">
|
||||
{formatCurrency(beforePrice)}
|
||||
</span>
|
||||
<ChevronRight size={12} strokeWidth={2} className="text-foreground-muted" />
|
||||
@@ -53,6 +53,7 @@ const BillingChangeBadge = ({
|
||||
)}
|
||||
animate={{ scale: [1, 1.1, 1] }}
|
||||
transition={{ duration: 0.12 }}
|
||||
translate="no"
|
||||
>
|
||||
{`${formatCurrency(afterPrice)}/month`}
|
||||
</motion.span>
|
||||
|
||||
@@ -539,9 +539,11 @@ export function LogDrainDestinationSheetForm({
|
||||
}
|
||||
>
|
||||
<ul className="text-right text-foreground-light">
|
||||
<li className="text-brand-link text-base">$60 per drain per month</li>
|
||||
<li>$0.20 per million events</li>
|
||||
<li>$0.09 per GB egress</li>
|
||||
<li className="text-brand-link text-base" translate="no">
|
||||
$60 per drain per month
|
||||
</li>
|
||||
<li translate="no">$0.20 per million events</li>
|
||||
<li translate="no">$0.09 per GB egress</li>
|
||||
</ul>
|
||||
</FormItemLayout>
|
||||
</SheetSection>
|
||||
|
||||
+6
-2
@@ -97,7 +97,9 @@ const BillingMetric = ({
|
||||
)}
|
||||
<span className="text-sm">{usageLabel}</span>
|
||||
{relativeToSubscription && usageMeta.cost && usageMeta.cost > 0 ? (
|
||||
<span className="text-sm">({formatCurrency(usageMeta.cost)})</span>
|
||||
<span className="text-sm" translate="no">
|
||||
({formatCurrency(usageMeta.cost)})
|
||||
</span>
|
||||
) : usageMeta.available_in_plan && !usageMeta.unlimited && relativeToSubscription ? (
|
||||
<span className="text-sm">{percentageLabel}</span>
|
||||
) : null}
|
||||
@@ -156,7 +158,9 @@ const BillingMetric = ({
|
||||
{usageMeta.available_in_plan && (
|
||||
<HoverCardContent side="bottom" align="center" className="w-[500px]" animate="slide-in">
|
||||
<div className="text-sm">
|
||||
<p className="font-medium">{usageMeta.unit_price_desc}</p>
|
||||
<p className="font-medium" translate="no">
|
||||
{usageMeta.unit_price_desc}
|
||||
</p>
|
||||
|
||||
{metric.tip && (
|
||||
<div className="my-2">
|
||||
|
||||
+6
-2
@@ -49,13 +49,17 @@ const ComputeMetric = ({ slug, metric, usage, relativeToSubscription }: ComputeM
|
||||
</Link>
|
||||
<span className="text-sm">{usageLabel}</span>
|
||||
{relativeToSubscription && usageMeta?.cost && usageMeta.cost > 0 ? (
|
||||
<span className="text-sm">({formatCurrency(usageMeta?.cost)})</span>
|
||||
<span className="text-sm" translate="no">
|
||||
({formatCurrency(usageMeta?.cost)})
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent side="bottom" align="center" className="w-[500px]" animate="slide-in">
|
||||
<div className="text-sm text-foreground space-y-2">
|
||||
<p className="font-medium">{usageMeta?.unit_price_desc}</p>
|
||||
<p className="font-medium" translate="no">
|
||||
{usageMeta?.unit_price_desc}
|
||||
</p>
|
||||
|
||||
<div className="my-2">
|
||||
<p className="text-sm">
|
||||
|
||||
+12
-9
@@ -130,7 +130,8 @@ const UpcomingInvoice = ({ slug }: UpcomingInvoiceProps) => {
|
||||
tooltip={
|
||||
<p className="prose text-xs">
|
||||
The first project is covered by Compute Credits. Additional projects incur
|
||||
compute costs starting at $10/month, independent of activity. See{' '}
|
||||
compute costs starting at <span translate="no">$10</span>/month, independent
|
||||
of activity. See{' '}
|
||||
<Link
|
||||
href={'/docs/guides/platform/manage-your-usage/compute'}
|
||||
target="_blank"
|
||||
@@ -217,7 +218,9 @@ const UpcomingInvoice = ({ slug }: UpcomingInvoiceProps) => {
|
||||
item.usage_metric != null) && (
|
||||
<InfoTooltip className="max-w-sm">
|
||||
{item.unit_price_desc && (
|
||||
<p className="mb-2">Pricing: {item.unit_price_desc}</p>
|
||||
<p className="mb-2" translate="no">
|
||||
Pricing: {item.unit_price_desc}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{item.breakdown && item.breakdown.length > 0 && (
|
||||
@@ -287,7 +290,7 @@ const UpcomingInvoice = ({ slug }: UpcomingInvoiceProps) => {
|
||||
Costs accumulated from the beginning of the billing cycle up to now.
|
||||
</InfoTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-medium py-2 px-0">
|
||||
<TableCell className="text-right font-medium py-2 px-0" translate="no">
|
||||
{formatCurrency(upcomingInvoice?.amount_total) ?? '-'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -301,7 +304,7 @@ const UpcomingInvoice = ({ slug }: UpcomingInvoiceProps) => {
|
||||
vary depending on your usage.
|
||||
</InfoTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-medium py-2 px-0">
|
||||
<TableCell className="text-right font-medium py-2 px-0" translate="no">
|
||||
{formatCurrency(upcomingInvoice?.amount_projected) ?? '-'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -324,14 +327,14 @@ function InvoiceLineItemAmount({
|
||||
if (amountBeforeDiscount && amount < amountBeforeDiscount) {
|
||||
return (
|
||||
<div>
|
||||
<span className="text-foreground-light line-through mr-2">
|
||||
<span className="text-foreground-light line-through mr-2" translate="no">
|
||||
{formatCurrency(amountBeforeDiscount)}
|
||||
</span>
|
||||
<span>{formatCurrency(amount)}</span>
|
||||
<span translate="no">{formatCurrency(amount)}</span>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return formatCurrency(amount)
|
||||
return <span translate="no">formatCurrency(amount)</span>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,7 +392,7 @@ function ComputeLineItem({
|
||||
{project.project_name} ({project.computeType} - {project.usage} Hours)
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="!py-2 px-0 text-right">
|
||||
<TableCell className="!py-2 px-0 text-right" translate="no">
|
||||
{formatCurrency(project.computeCosts)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -398,7 +401,7 @@ function ComputeLineItem({
|
||||
{computeCredits && (
|
||||
<TableRow className="text-foreground-light text-xs">
|
||||
<TableCell className="!py-2 px-0 pl-6">Compute Credits</TableCell>
|
||||
<TableCell className="!py-2 px-0 text-right">
|
||||
<TableCell className="!py-2 px-0 text-right" translate="no">
|
||||
{formatCurrency(computeCredits.amount)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
+3
-1
@@ -194,7 +194,9 @@ const PlanUpdateSidePanel = () => {
|
||||
<ShimmeringLoader className="w-[30px] h-[24px]" />
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-foreground text-lg">{formatCurrency(price)}</p>
|
||||
<p className="text-foreground text-lg" translate="no">
|
||||
{formatCurrency(price)}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-foreground-light text-sm">{plan.costUnit}</p>
|
||||
</div>
|
||||
|
||||
+33
-12
@@ -213,7 +213,9 @@ export const SubscriptionPlanUpdateDialog = ({
|
||||
New
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="py-2 pr-0 text-right">{formatCurrency(newPlanCost)}</div>
|
||||
<div className="py-2 pr-0 text-right" translate="no">
|
||||
{formatCurrency(newPlanCost)}
|
||||
</div>
|
||||
</div>
|
||||
{subscription?.plan?.id !== 'free' && (
|
||||
<div className="flex items-center justify-between gap-2 border-b border-muted">
|
||||
@@ -226,7 +228,7 @@ export const SubscriptionPlanUpdateDialog = ({
|
||||
use.
|
||||
</InfoTooltip>
|
||||
</div>
|
||||
<div className="py-2 pr-0 text-right">
|
||||
<div className="py-2 pr-0 text-right" translate="no">
|
||||
-{formatCurrency(proratedCredit)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -241,14 +243,14 @@ export const SubscriptionPlanUpdateDialog = ({
|
||||
Credits will be used first before charging your card.
|
||||
</InfoTooltip>
|
||||
</div>
|
||||
<div className="py-2 pr-0 text-right">
|
||||
<div className="py-2 pr-0 text-right" translate="no">
|
||||
{formatCurrency(customerBalance)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-between gap-2 border-b border-muted text-foreground">
|
||||
<div className="py-2 pl-0">Charge today</div>
|
||||
<div className="py-2 pr-0 text-right">
|
||||
<div className="py-2 pr-0 text-right" translate="no">
|
||||
{formatCurrency(totalCharge)}
|
||||
{subscription?.plan?.id !== 'free' && (
|
||||
<>
|
||||
@@ -272,7 +274,8 @@ export const SubscriptionPlanUpdateDialog = ({
|
||||
<h3 className="text-base font-medium mb-2">Your new monthly invoice</h3>
|
||||
<p className="prose text-xs mb-2">
|
||||
Each paid project runs on a dedicated 24/7 server. First project uses
|
||||
Compute Credits; additional ones cost $10+/month regardless of usage.{' '}
|
||||
Compute Credits; additional ones cost <span translate="no">$10+</span>
|
||||
/month regardless of usage.{' '}
|
||||
<Link
|
||||
href={'/docs/guides/platform/manage-your-usage/compute'}
|
||||
target="_blank"
|
||||
@@ -347,7 +350,10 @@ export const SubscriptionPlanUpdateDialog = ({
|
||||
<TableCell className="!py-2 px-0">
|
||||
{planItem?.description}
|
||||
</TableCell>
|
||||
<TableCell className="text-right py-2 px-0">
|
||||
<TableCell
|
||||
className="text-right py-2 px-0"
|
||||
translate="no"
|
||||
>
|
||||
{formatCurrency(planItem?.total_price)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -356,7 +362,10 @@ export const SubscriptionPlanUpdateDialog = ({
|
||||
<TableCell className="!py-2 px-0 flex items-center gap-1">
|
||||
<span>Compute</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-right py-2 px-0">
|
||||
<TableCell
|
||||
className="text-right py-2 px-0"
|
||||
translate="no"
|
||||
>
|
||||
{formatCurrency(
|
||||
computeItems.reduce(
|
||||
(sum: number, item) => sum + item.total_price,
|
||||
@@ -371,7 +380,10 @@ export const SubscriptionPlanUpdateDialog = ({
|
||||
key={project.project_ref}
|
||||
className="text-foreground-light"
|
||||
>
|
||||
<TableCell className="!py-2 px-0 pl-6">
|
||||
<TableCell
|
||||
className="!py-2 px-0 pl-6"
|
||||
translate="no"
|
||||
>
|
||||
{project.project_name} ({project.computeType}) |{' '}
|
||||
{formatCurrency(project.computeCosts)}
|
||||
</TableCell>
|
||||
@@ -379,7 +391,10 @@ export const SubscriptionPlanUpdateDialog = ({
|
||||
))}
|
||||
{computeCreditsItem && (
|
||||
<TableRow className="text-foreground-light">
|
||||
<TableCell className="!py-2 px-0 pl-6">
|
||||
<TableCell
|
||||
className="!py-2 px-0 pl-6"
|
||||
translate="no"
|
||||
>
|
||||
Compute Credits |{' '}
|
||||
{formatCurrency(computeCreditsItem.total_price)}
|
||||
</TableCell>
|
||||
@@ -413,7 +428,10 @@ export const SubscriptionPlanUpdateDialog = ({
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-right text-xs py-2 px-0">
|
||||
<TableCell
|
||||
className="text-right text-xs py-2 px-0"
|
||||
translate="no"
|
||||
>
|
||||
{formatCurrency(item.total_price)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -427,7 +445,10 @@ export const SubscriptionPlanUpdateDialog = ({
|
||||
<TableCell className="font-medium py-2 px-0">
|
||||
Total per month (excluding other usage)
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-medium py-2 px-0">
|
||||
<TableCell
|
||||
className="text-right font-medium py-2 px-0"
|
||||
translate="no"
|
||||
>
|
||||
{formatCurrency(
|
||||
Math.round(
|
||||
subscriptionPreview?.breakdown?.reduce(
|
||||
@@ -445,7 +466,7 @@ export const SubscriptionPlanUpdateDialog = ({
|
||||
</div>
|
||||
</InfoTooltip>
|
||||
</div>
|
||||
<div className="py-2 pr-0 text-right">
|
||||
<div className="py-2 pr-0 text-right" translate="no">
|
||||
{formatCurrency(
|
||||
Math.round(
|
||||
subscriptionPreview?.breakdown.reduce(
|
||||
|
||||
+1
-1
@@ -125,7 +125,7 @@ const InvoicesSettings = () => {
|
||||
<Table.td>
|
||||
<p>{dayjs(x.period_end * 1000).format('MMM DD, YYYY')}</p>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<Table.td translate="no">
|
||||
<p>{formatCurrency(x.amount_due / 100)}</p>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
|
||||
@@ -413,7 +413,7 @@ const NewOrgForm = ({ onPaymentMethodReset }: NewOrgFormProps) => {
|
||||
|
||||
<SelectContent_Shadcn_>
|
||||
{Object.entries(PRICING_TIER_LABELS_ORG).map(([k, v]) => (
|
||||
<SelectItem_Shadcn_ key={k} value={k}>
|
||||
<SelectItem_Shadcn_ key={k} value={k} translate="no">
|
||||
{v}
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
useContentUpsertMutation,
|
||||
} from 'data/content/content-upsert-mutation'
|
||||
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
|
||||
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import uuidv4 from 'lib/uuid'
|
||||
import { Dashboards } from 'types'
|
||||
@@ -54,6 +55,7 @@ export const GridResize = ({
|
||||
const { ref } = useParams()
|
||||
const { profile } = useProfile()
|
||||
const { project } = useProjectContext()
|
||||
const selectedOrg = useSelectedOrganization()
|
||||
|
||||
const { mutate: sendEvent } = useSendEventMutation()
|
||||
const { mutate: upsertContent } = useContentUpsertMutation()
|
||||
@@ -134,7 +136,10 @@ export const GridResize = ({
|
||||
},
|
||||
}
|
||||
)
|
||||
sendEvent({ action: 'custom_report_assistant_sql_block_added' })
|
||||
sendEvent({
|
||||
action: 'custom_report_assistant_sql_block_added',
|
||||
groups: { project: ref ?? 'Unknown', organization: selectedOrg?.slug ?? 'Unknown' },
|
||||
})
|
||||
}
|
||||
|
||||
if (!editableReport) return null
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useParams } from 'common'
|
||||
import { useContentQuery } from 'data/content/content-query'
|
||||
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
|
||||
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
|
||||
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
|
||||
import { useFlag } from 'hooks/ui/useFlag'
|
||||
import { Metric, METRIC_CATEGORIES, METRICS } from 'lib/constants/metrics'
|
||||
import { Dashboards } from 'types'
|
||||
@@ -39,6 +40,7 @@ interface MetricOptionsProps {
|
||||
|
||||
export const MetricOptions = ({ config, handleChartSelection }: MetricOptionsProps) => {
|
||||
const { ref: projectRef } = useParams()
|
||||
const selectedOrganization = useSelectedOrganization()
|
||||
const supportSQLBlocks = useFlag('reportsV2')
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
@@ -142,6 +144,10 @@ export const MetricOptions = ({ config, handleChartSelection }: MetricOptionsPro
|
||||
})
|
||||
sendEvent({
|
||||
action: 'custom_report_add_sql_block_clicked',
|
||||
groups: {
|
||||
project: projectRef ?? 'Unknown',
|
||||
organization: selectedOrganization?.slug ?? 'Unknown',
|
||||
},
|
||||
})
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from 'data/content/content-upsert-mutation'
|
||||
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
|
||||
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
|
||||
import { Metric, TIME_PERIODS_REPORTS } from 'lib/constants/metrics'
|
||||
import { uuidv4 } from 'lib/helpers'
|
||||
import { useProfile } from 'lib/profile'
|
||||
@@ -42,6 +43,7 @@ const Reports = () => {
|
||||
const { id, ref } = useParams()
|
||||
const { profile } = useProfile()
|
||||
const { project } = useProjectContext()
|
||||
const selectedOrg = useSelectedOrganization()
|
||||
const queryClient = useQueryClient()
|
||||
const state = useDatabaseSelectorStateSnapshot()
|
||||
|
||||
@@ -336,7 +338,10 @@ const Reports = () => {
|
||||
},
|
||||
}
|
||||
)
|
||||
sendEvent({ action: 'custom_report_assistant_sql_block_added' })
|
||||
sendEvent({
|
||||
action: 'custom_report_assistant_sql_block_added',
|
||||
groups: { project: ref ?? 'Unknown', organization: selectedOrg?.slug ?? 'Unknown' },
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -178,7 +178,9 @@ const CustomDomainSidePanel = () => {
|
||||
Use the default supabase domain for your API
|
||||
</p>
|
||||
<div className="flex items-center space-x-1 mt-2">
|
||||
<p className="text-foreground text-sm">$0</p>
|
||||
<p className="text-foreground text-sm" translate="no">
|
||||
$0
|
||||
</p>
|
||||
<p className="text-foreground-light translate-y-[1px]"> / month</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -203,7 +205,9 @@ const CustomDomainSidePanel = () => {
|
||||
Present a branded experience to your users
|
||||
</p>
|
||||
<div className="flex items-center space-x-1 mt-2">
|
||||
<p className="text-foreground text-sm">{formatCurrency(option.price)}</p>
|
||||
<p className="text-foreground text-sm" translate="no">
|
||||
{formatCurrency(option.price)}
|
||||
</p>
|
||||
<p className="text-foreground-light translate-y-[1px]"> / month</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -182,7 +182,9 @@ const IPv4SidePanel = () => {
|
||||
Allow direct database connections via IPv4 address
|
||||
</p>
|
||||
<div className="flex items-center space-x-1 mt-2">
|
||||
<p className="text-foreground text-sm">{formatCurrency(option.price)}</p>
|
||||
<p className="text-foreground text-sm" translate="no">
|
||||
{formatCurrency(option.price)}
|
||||
</p>
|
||||
<p className="text-foreground-light translate-y-[1px]">
|
||||
/ month / database
|
||||
</p>
|
||||
|
||||
@@ -354,7 +354,9 @@ const PITRSidePanel = () => {
|
||||
{option.identifier.split('_')[1]} days ago
|
||||
</p>
|
||||
<div className="flex items-center space-x-1 mt-2">
|
||||
<p className="text-foreground text-sm">{formatCurrency(option.price)}</p>
|
||||
<p className="text-foreground text-sm" translate="no">
|
||||
{formatCurrency(option.price)}
|
||||
</p>
|
||||
<p className="text-foreground-light translate-y-[1px]"> / month</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,29 +1,41 @@
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { ExternalLink, Globe } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Globe } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
|
||||
import { DocsButton } from 'components/ui/DocsButton'
|
||||
import { FormHeader } from 'components/ui/Forms/FormHeader'
|
||||
import { FormPanel } from 'components/ui/Forms/FormPanel'
|
||||
import { useBannedIPsDeleteMutation } from 'data/banned-ips/banned-ips-delete-mutations'
|
||||
import { useBannedIPsQuery } from 'data/banned-ips/banned-ips-query'
|
||||
import { useUserIPAddressQuery } from 'data/misc/user-ip-address-query'
|
||||
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { BASE_PATH } from 'lib/constants'
|
||||
import { Badge, Button } from 'ui'
|
||||
import { Badge, Skeleton } from 'ui'
|
||||
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
|
||||
import { DocsButton } from 'components/ui/DocsButton'
|
||||
|
||||
const BannedIPs = () => {
|
||||
const { ref } = useParams()
|
||||
const { project } = useProjectContext()
|
||||
|
||||
const [selectedIPToUnban, setSelectedIPToUnban] = useState<string | null>(null) // Track the selected IP for unban
|
||||
const { data: ipList } = useBannedIPsQuery({
|
||||
|
||||
const {
|
||||
isLoading: isLoadingIPList,
|
||||
isFetching: isFetchingIPList,
|
||||
data: ipList,
|
||||
error: ipListError,
|
||||
} = useBannedIPsQuery({
|
||||
projectRef: ref,
|
||||
})
|
||||
|
||||
const { data: userIPAddress } = useUserIPAddressQuery()
|
||||
|
||||
const ipListLoading = isLoadingIPList || isFetchingIPList
|
||||
|
||||
const [showUnban, setShowUnban] = useState(false)
|
||||
const [confirmingIP, setConfirmingIP] = useState<string | null>(null) // Track the IP being confirmed for unban
|
||||
|
||||
@@ -58,16 +70,6 @@ const BannedIPs = () => {
|
||||
setShowUnban(true)
|
||||
}
|
||||
|
||||
const [userIPAddress, setUserIPAddress] = useState<string | null>(null)
|
||||
|
||||
// [TODO] Convert this to a react query
|
||||
useEffect(() => {
|
||||
// Fetch user's IP address
|
||||
fetch(`${BASE_PATH}/api/get-ip-address`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => setUserIPAddress(data.ipAddress))
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div id="banned-ips">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
@@ -79,7 +81,18 @@ const BannedIPs = () => {
|
||||
<DocsButton href="https://supabase.com/docs/reference/cli/supabase-network-bans" />
|
||||
</div>
|
||||
<FormPanel>
|
||||
{ipList && ipList.banned_ipv4_addresses.length > 0 ? (
|
||||
{ipListLoading ? (
|
||||
<div className="px-8 py-4 space-y-4">
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className="h-4 w-full" />
|
||||
</div>
|
||||
) : ipListError ? (
|
||||
<AlertError
|
||||
className="border-0 rounded-none"
|
||||
error={ipListError}
|
||||
subject="Failed to retrieve banned IP addresses"
|
||||
/>
|
||||
) : ipList && ipList.banned_ipv4_addresses.length > 0 ? (
|
||||
ipList.banned_ipv4_addresses.map((ip) => (
|
||||
<div key={ip} className="px-8 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center space-x-5">
|
||||
|
||||
+16
-11
@@ -444,13 +444,15 @@ const DeployNewReplicaPanel = ({
|
||||
<CollapsibleTrigger_Shadcn_ className="w-full flex items-center justify-between [&[data-state=open]>svg]:!-rotate-180">
|
||||
<p className="text-sm text-left">
|
||||
New replica will cost an additional{' '}
|
||||
{formatCurrency(
|
||||
estComputeMonthlyCost +
|
||||
additionalCostDiskSize +
|
||||
Number(additionalCostIOPS) +
|
||||
Number(additionalCostThroughput)
|
||||
)}
|
||||
/month
|
||||
<span translate="no">
|
||||
{formatCurrency(
|
||||
estComputeMonthlyCost +
|
||||
additionalCostDiskSize +
|
||||
Number(additionalCostIOPS) +
|
||||
Number(additionalCostThroughput)
|
||||
)}
|
||||
/month
|
||||
</span>
|
||||
</p>
|
||||
<ChevronDown size={14} className="transition" />
|
||||
</CollapsibleTrigger_Shadcn_>
|
||||
@@ -474,7 +476,7 @@ const DeployNewReplicaPanel = ({
|
||||
<TableRow>
|
||||
<TableCell className="pl-0">Compute size</TableCell>
|
||||
<TableCell>{selectedComputeMeta?.name}</TableCell>
|
||||
<TableCell className="text-right font-mono pr-0">
|
||||
<TableCell className="text-right font-mono pr-0" translate="no">
|
||||
{formatCurrency(estComputeMonthlyCost)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -483,14 +485,14 @@ const DeployNewReplicaPanel = ({
|
||||
<TableCell>
|
||||
{((size_gb ?? 0) * 1.25).toLocaleString()} GB ({type})
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-mono pr-0">
|
||||
<TableCell className="text-right font-mono pr-0" translate="no">
|
||||
{formatCurrency(additionalCostDiskSize)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="pl-0">IOPS</TableCell>
|
||||
<TableCell>{iops?.toLocaleString()} IOPS</TableCell>
|
||||
<TableCell className="text-right font-mono pr-0">
|
||||
<TableCell className="text-right font-mono pr-0" translate="no">
|
||||
{formatCurrency(+additionalCostIOPS)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -514,7 +516,10 @@ const DeployNewReplicaPanel = ({
|
||||
read replica on the{' '}
|
||||
<span className="text-foreground">{selectedComputeMeta?.name}</span> size incurs
|
||||
additional{' '}
|
||||
<span className="text-foreground">{selectedComputeMeta?.price_description}</span>.
|
||||
<span className="text-foreground" translate="no">
|
||||
{selectedComputeMeta?.price_description}
|
||||
</span>
|
||||
.
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
+1
-1
@@ -197,7 +197,7 @@ const EnableBranchingModal = () => {
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<p className="text-sm text-foreground">
|
||||
Preview branches are billed $0.32 per day
|
||||
Preview branches are billed <span translate="no">$0.32</span> per day
|
||||
</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
This cost will continue for as long as the branch has not been removed.
|
||||
|
||||
@@ -19,7 +19,7 @@ export const generateAuthMenu = (ref: string): ProductMenuGroup[] => {
|
||||
...(IS_PLATFORM
|
||||
? [
|
||||
{
|
||||
name: 'Sign In / Up',
|
||||
name: 'Sign In / Providers',
|
||||
key: 'sign-in-up',
|
||||
pages: ['providers', 'third-party'],
|
||||
url: `/project/${ref}/auth/providers`,
|
||||
|
||||
@@ -21,7 +21,7 @@ export const AuthProvidersLayout = ({ children }: PropsWithChildren<{}>) => {
|
||||
return (
|
||||
<AuthLayout>
|
||||
<PageLayout
|
||||
title="Sign In / Up"
|
||||
title="Sign In / Providers"
|
||||
subtitle="Configure authentication providers and login methods for your users"
|
||||
navigationItems={navItems}
|
||||
>
|
||||
|
||||
@@ -97,13 +97,13 @@ export function useProjectSettingsGotoCommands(options?: CommandOptions) {
|
||||
{
|
||||
id: 'nav-project-settings-network-restrictions',
|
||||
name: 'Network restrictions',
|
||||
route: `/project/${ref}/settings/general#network-restrictions`,
|
||||
route: `/project/${ref}/settings/database#network-restrictions`,
|
||||
defaultHidden: true,
|
||||
},
|
||||
{
|
||||
id: 'nav-project-settings-banned-ips',
|
||||
name: 'Banned IPs',
|
||||
route: `/project/${ref}/settings/general#banned-ips`,
|
||||
route: `/project/${ref}/settings/database#banned-ips`,
|
||||
defaultHidden: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -26,6 +26,8 @@ import { AssistantSnippetProps } from './AIAssistant.types'
|
||||
import { identifyQueryType } from './AIAssistant.utils'
|
||||
import CollapsibleCodeBlock from './CollapsibleCodeBlock'
|
||||
import { MessageContext } from './Message'
|
||||
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
|
||||
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
|
||||
|
||||
export const OrderedList = memo(({ children }: { children: ReactNode }) => (
|
||||
<ol className="flex flex-col gap-y-4">{children}</ol>
|
||||
@@ -167,6 +169,8 @@ export const MarkdownPre = ({
|
||||
const { mutate: sendEvent } = useSendEventMutation()
|
||||
const snap = useAiAssistantStateSnapshot()
|
||||
const supportSQLBlocks = useFlag('reportsV2')
|
||||
const project = useSelectedProject()
|
||||
const org = useSelectedOrganization()
|
||||
|
||||
const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', {
|
||||
resource: { type: 'sql', owner_id: profile?.id },
|
||||
@@ -234,6 +238,10 @@ export const MarkdownPre = ({
|
||||
? { category: identifyQueryType(cleanContent) ?? 'unknown' }
|
||||
: {}),
|
||||
},
|
||||
groups: {
|
||||
project: project?.ref ?? 'Unknown',
|
||||
organization: org?.slug ?? 'Unknown',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -333,7 +333,7 @@ export const EditorPanel = ({ onChange }: EditorPanelProps) => {
|
||||
)}
|
||||
|
||||
{results !== undefined && results.length > 0 && (
|
||||
<div className={`max-h-72 shrink-0 flex flex-col`}>
|
||||
<div className={`max-h-72 shrink-0 flex flex-col ${showResults && 'h-full'}`}>
|
||||
{showResults && (
|
||||
<div className="border-t flex-1 overflow-auto">
|
||||
<Results rows={results} />
|
||||
|
||||
@@ -2,4 +2,5 @@ export const miscKeys = {
|
||||
cliReleaseVersion: () => ['cliReleaseVersion'] as const,
|
||||
defaultRegion: (cloudProvider: string | undefined, useRestrictedPool: boolean) =>
|
||||
['defaultRegion', [cloudProvider, useRestrictedPool]] as const,
|
||||
ipAddress: () => ['ip-address'] as const,
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
|
||||
import { BASE_PATH, IS_PLATFORM } from 'lib/constants'
|
||||
import type { ResponseError } from 'types'
|
||||
import { miscKeys } from './keys'
|
||||
|
||||
export async function getUserIPAddress() {
|
||||
try {
|
||||
const data = await fetch(`${BASE_PATH}/api/get-ip-address`).then((res) => res.json())
|
||||
return data.ipAddress
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export type UserIPAddressData = Awaited<ReturnType<typeof getUserIPAddress>>
|
||||
export type UserIPAddressError = ResponseError
|
||||
|
||||
export const useUserIPAddressQuery = <TData = UserIPAddressData>({
|
||||
enabled = true,
|
||||
...options
|
||||
}: UseQueryOptions<UserIPAddressData, UserIPAddressError, TData> = {}) =>
|
||||
useQuery<UserIPAddressData, UserIPAddressError, TData>(
|
||||
miscKeys.ipAddress(),
|
||||
() => getUserIPAddress(),
|
||||
{ enabled: enabled && !IS_PLATFORM, ...options }
|
||||
)
|
||||
@@ -5,6 +5,7 @@ export * from './infrastructure'
|
||||
export const IS_PLATFORM = process.env.NEXT_PUBLIC_IS_PLATFORM === 'true'
|
||||
|
||||
export const API_URL = (() => {
|
||||
if (process.env.NODE_ENV === 'test') return 'http://localhost:3000/api'
|
||||
// If running in platform, use API_URL from the env var
|
||||
if (IS_PLATFORM) return process.env.NEXT_PUBLIC_API_URL!
|
||||
// If running in browser, let it add the host
|
||||
|
||||
@@ -40,8 +40,19 @@ export const ProfileProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
sendEvent({ action: 'sign_up', properties: { category: 'conversion' } })
|
||||
},
|
||||
onError: (error) => {
|
||||
Sentry.captureMessage('Failed to create users profile: ' + error.message)
|
||||
toast.error('Failed to create your profile. Please refresh to try again.')
|
||||
if (error.code === 409) {
|
||||
// [Joshen] There's currently an assumption that createProfile is getting triggered
|
||||
// multiple times unnecessarily, although the tracing the code i can't see why this might
|
||||
// be happening unless GET profile is somehow returning `User's profile not found` incorrectly
|
||||
// Adding a Sentry capture + toast in hopes to catch this while developing on local / staging
|
||||
Sentry.captureMessage('Profile already exists: ' + error.message)
|
||||
if (process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod') {
|
||||
toast.error('[DEV] createProfile called despite profile already exists: ' + error.message)
|
||||
}
|
||||
} else {
|
||||
Sentry.captureMessage('Failed to create users profile: ' + error.message)
|
||||
toast.error('Failed to create your profile. Please refresh to try again.')
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -134,7 +134,6 @@
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aws-sdk/client-secrets-manager": "^3.410.0",
|
||||
"@radix-ui/react-use-escape-keydown": "^1.0.3",
|
||||
"@supabase/postgres-meta": "^0.64.4",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
|
||||
@@ -188,6 +188,10 @@ const Wizard: NextPageWithLayout = () => {
|
||||
properties: {
|
||||
instanceSize: form.getValues('instanceSize'),
|
||||
},
|
||||
groups: {
|
||||
project: res.ref,
|
||||
organization: res.organization_slug,
|
||||
},
|
||||
})
|
||||
router.push(`/project/${res.ref}/building`)
|
||||
},
|
||||
@@ -321,6 +325,9 @@ const Wizard: NextPageWithLayout = () => {
|
||||
properties: {
|
||||
instanceSize: values.instanceSize,
|
||||
},
|
||||
groups: {
|
||||
organization: currentOrg?.slug ?? 'Unknown',
|
||||
},
|
||||
})
|
||||
setIsComputeCostsConfirmationModalVisible(true)
|
||||
} else {
|
||||
@@ -754,7 +761,10 @@ const Wizard: NextPageWithLayout = () => {
|
||||
)}{' '}
|
||||
CPU
|
||||
</span>
|
||||
<p className="text-xs text-muted instance-details">
|
||||
<p
|
||||
className="text-xs text-muted instance-details"
|
||||
translate="no"
|
||||
>
|
||||
${instanceSizeSpecs[option].priceHourly}/hour (~$
|
||||
{instanceSizeSpecs[option].priceMonthly}/month)
|
||||
</p>
|
||||
|
||||
@@ -1,6 +1,75 @@
|
||||
# UI Testing Notes
|
||||
|
||||
### `<Popover>` vs `<Dropdown>`
|
||||
## Rules
|
||||
|
||||
- All tests should be run consistently (avoid situations whereby tests fails "sometimes")
|
||||
|
||||
- Group tests in folders based on the feature they are testing. Avoid file/folder based folder names since those can change and we will forget to update the tests.
|
||||
|
||||
Examples: /logs /reports /projects /database-settings /auth
|
||||
|
||||
## Custom Render and Custom Render Hook
|
||||
|
||||
`customRender` and `customRenderHook` are wrappers around `render` and `renderHook` that add some necessary providers like `QueryClientProvider`, `TooltipProvider` and `NuqsTestingAdapter`.
|
||||
|
||||
Generally use those instead of the default `render` and `renderHook` functions.
|
||||
|
||||
```ts
|
||||
import { customRender, customRenderHook } from 'tests/lib/custom-render'
|
||||
|
||||
customRender(<MyComponent />)
|
||||
customRenderHook(() => useMyHook())
|
||||
```
|
||||
|
||||
## Mocking API Requests
|
||||
|
||||
To mock API requests, we use the `msw` library.
|
||||
|
||||
Global mocks can be found in `tests/lib/msw-global-api-mocks.ts`.
|
||||
|
||||
To mock an endpoint you can use the `addAPIMock` function. Make sure to add the mock in the `beforeEach` hook. It won't work with `beforeAll` if you have many tests.
|
||||
|
||||
```ts
|
||||
beforeEach(() => {
|
||||
addAPIMock({
|
||||
method: 'get',
|
||||
path: '/api/my-endpoint',
|
||||
response: {
|
||||
data: { foo: 'bar' },
|
||||
},
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### API Mocking Tips:
|
||||
|
||||
- Keep mocks in the same folder as the tests that use them
|
||||
- Add a test to verify the mock is working
|
||||
|
||||
This will make debugging and updating the mocks easier.
|
||||
|
||||
```ts
|
||||
test('mock is working', async () => {
|
||||
const response = await fetch('/api/my-endpoint')
|
||||
expect(response.json()).resolves.toEqual({ data: { foo: 'bar' } })
|
||||
})
|
||||
```
|
||||
|
||||
## Mocking Nuqs URL Parameters
|
||||
|
||||
To render a component that uses Nuqs with some predefined query parameters, you can use `customRender` with the `nuqs` prop.
|
||||
|
||||
```ts
|
||||
customRender(<MyComponent />, {
|
||||
nuqs: {
|
||||
searchParams: {
|
||||
search: 'hello world',
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## `<Popover>` vs `<Dropdown>`
|
||||
|
||||
When simulating clicks on these components, do the following:
|
||||
|
||||
@@ -13,7 +82,3 @@ await userEvent.click('Hello world')
|
||||
import clickDropdown from 'tests/helpers'
|
||||
clickDropdown('Hello world')
|
||||
```
|
||||
|
||||
### Rules
|
||||
|
||||
- All tests should be run consistently (avoid situations whereby tests fails "sometimes")
|
||||
@@ -9,8 +9,10 @@ test('MSW works as expected', async () => {
|
||||
expect(json).toEqual({ message: 'Hello from MSW!' })
|
||||
})
|
||||
|
||||
test('MSW fails on missing endpoints', async () => {
|
||||
test('MSW errors on missing endpoints', async () => {
|
||||
expect(async () => {
|
||||
await fetch(`${API_URL}/endpoint-that-doesnt-exist`)
|
||||
}).rejects.toThrowError()
|
||||
const res = await fetch(`${API_URL}/endpoint-that-doesnt-exist`)
|
||||
const json = await res.json()
|
||||
expect(json).toEqual({ message: '🚫 MSW missed' })
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import { routerMock } from 'tests/mocks/router'
|
||||
import { routerMock } from '../lib/route-mock'
|
||||
import { expect, suite, test } from 'vitest'
|
||||
import { RouterComponent } from './router'
|
||||
|
||||
|
||||
+17
-24
@@ -1,4 +1,4 @@
|
||||
import { prettyDOM, screen, waitFor } from '@testing-library/react'
|
||||
import { screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import LogTable from 'components/interfaces/Settings/Logs/LogTable'
|
||||
import dayjs from 'dayjs'
|
||||
@@ -26,36 +26,29 @@ beforeAll(() => {
|
||||
|
||||
const fakeMicroTimestamp = dayjs().unix() * 1000
|
||||
|
||||
test.skip('can display log data', async () => {
|
||||
const LOG_DATA = {
|
||||
id: 'some-uuid',
|
||||
timestamp: 1621323232312,
|
||||
event_message: 'event message',
|
||||
metadata: {
|
||||
my_key: 'something_value',
|
||||
},
|
||||
}
|
||||
|
||||
test('can display log data', async () => {
|
||||
render(
|
||||
<>
|
||||
<LogTable
|
||||
projectRef="projectRef"
|
||||
data={[
|
||||
{
|
||||
id: 'some-uuid',
|
||||
timestamp: 1621323232312,
|
||||
event_message: 'event message',
|
||||
metadata: {
|
||||
my_key: 'something_value',
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<LogTable projectRef="default" data={[LOG_DATA]} />
|
||||
</>
|
||||
)
|
||||
|
||||
await screen.findByText(/event message/)
|
||||
await screen.findAllByText(LOG_DATA.timestamp)
|
||||
})
|
||||
|
||||
prettyDOM(screen.getByText(/event message/))
|
||||
test('Shows total results', async () => {
|
||||
render(<LogTable projectRef="default" data={[LOG_DATA]} />)
|
||||
|
||||
const row = await screen.findByText(/event message/)
|
||||
|
||||
await userEvent.click(row)
|
||||
|
||||
// [Joshen] commenting out for now - seems like we need to mock more stuff
|
||||
await screen.findByText(/my_key/)
|
||||
await screen.findByText(/something_value/)
|
||||
await screen.getByText(/results \(1\)/i)
|
||||
})
|
||||
|
||||
test('can run if no queryType provided', async () => {
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,128 @@
|
||||
import { screen, waitFor } from '@testing-library/react'
|
||||
import dayjs from 'dayjs'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
import { beforeEach, expect, test, vi } from 'vitest'
|
||||
import { LogsTableName } from 'components/interfaces/Settings/Logs/Logs.constants'
|
||||
import LogsPreviewer from 'components/interfaces/Settings/Logs/LogsPreviewer'
|
||||
import { customRender, customRenderHook } from 'tests/lib/custom-render'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
|
||||
import useLogsPreview from 'hooks/analytics/useLogsPreview'
|
||||
import { LOGS_API_MOCKS } from './logs.mocks'
|
||||
import { addAPIMock } from 'tests/lib/msw'
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
vi.mock('common', async (importOriginal) => {
|
||||
const actual = await importOriginal()
|
||||
return {
|
||||
useParams: vi.fn().mockReturnValue({}),
|
||||
useIsLoggedIn: vi.fn(),
|
||||
isBrowser: false,
|
||||
LOCAL_STORAGE_KEYS: (actual as any).LOCAL_STORAGE_KEYS,
|
||||
...(actual as any),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('lib/gotrue', async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
auth: { onAuthStateChange: vi.fn() },
|
||||
}))
|
||||
|
||||
beforeEach(() => {
|
||||
addAPIMock({
|
||||
method: 'get',
|
||||
path: '/platform/projects/default/analytics/endpoints/logs.all',
|
||||
response: LOGS_API_MOCKS,
|
||||
})
|
||||
})
|
||||
|
||||
test('search loads with whatever is on the URL', async () => {
|
||||
customRender(
|
||||
<LogsPreviewer queryType="api" projectRef="default" tableName={LogsTableName.EDGE} />,
|
||||
{
|
||||
nuqs: {
|
||||
searchParams: {
|
||||
s: 'test-search-box-value',
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('textbox')).toHaveValue('test-search-box-value')
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('textbox')).not.toHaveValue('WRONGVALUE!🪿')
|
||||
})
|
||||
})
|
||||
|
||||
test('useLogsPreview returns data from MSW', async () => {
|
||||
const { result } = customRenderHook(() =>
|
||||
useLogsPreview({
|
||||
projectRef: 'default',
|
||||
table: LogsTableName.EDGE,
|
||||
})
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.logData.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
expect(result.current.logData).toEqual(LOGS_API_MOCKS.result)
|
||||
})
|
||||
|
||||
test('LogsPreviewer renders the expected data from the API', async () => {
|
||||
customRender(
|
||||
<LogsPreviewer queryType="api" projectRef="default" tableName={LogsTableName.EDGE} />
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('table')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const firstLogEventMessage = LOGS_API_MOCKS.result[0].event_message
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText(firstLogEventMessage)[0]).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
test('can toggle log event chart', async () => {
|
||||
customRender(
|
||||
<LogsPreviewer queryType="api" projectRef="default" tableName={LogsTableName.EDGE} />
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button', { name: /Chart/i })).toBeInTheDocument()
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('logs-bar-chart')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /Chart/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('logs-bar-chart')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
test('can click load older', async () => {
|
||||
customRender(
|
||||
<LogsPreviewer queryType="api" projectRef="default" tableName={LogsTableName.EDGE} />
|
||||
)
|
||||
|
||||
const loadOlder = await waitFor(
|
||||
async () => await screen.findByRole('button', { name: /Load older/i })
|
||||
)
|
||||
|
||||
loadOlder.onclick = vi.fn()
|
||||
|
||||
await userEvent.click(loadOlder)
|
||||
|
||||
expect(loadOlder.onclick).toHaveBeenCalled()
|
||||
})
|
||||
File renamed without changes.
+1
-1
@@ -4,7 +4,7 @@ import dayjs from 'dayjs'
|
||||
import { LogsExplorerPage } from 'pages/project/[ref]/logs/explorer/index'
|
||||
import { clickDropdown } from 'tests/helpers'
|
||||
import { customRender as render } from 'tests/lib/custom-render'
|
||||
import { routerMock } from 'tests/mocks/router'
|
||||
import { routerMock } from 'tests/lib/route-mock'
|
||||
import { beforeAll, describe, expect, test, vi } from 'vitest'
|
||||
|
||||
const router = routerMock
|
||||
@@ -0,0 +1,22 @@
|
||||
export const LOGS_API_MOCKS = {
|
||||
result: [
|
||||
{
|
||||
id: 'uuid',
|
||||
event_message: 'foobar',
|
||||
timestamp: 1298085933,
|
||||
error_count: 1,
|
||||
warning_count: 2,
|
||||
ok_count: 3,
|
||||
count: 2,
|
||||
},
|
||||
{
|
||||
id: 'uuid2',
|
||||
event_message: 'foobar',
|
||||
timestamp: 1298085933,
|
||||
error_count: 1,
|
||||
warning_count: 2,
|
||||
ok_count: 3,
|
||||
count: 2,
|
||||
},
|
||||
],
|
||||
}
|
||||
Loaded 100 of 131 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user