mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 02:15:05 +03:00
68 files changed
+6506
-4201
No files matched your search
@@ -231,7 +231,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
- [Norwegian (Bokmål) / Norsk (Bokmål)](/i18n/README.nb-no.md)
|
||||
- [Persian / فارسی](/i18n/README.fa.md)
|
||||
- [Polish / Polski](/i18n/README.pl.md)
|
||||
- [Portuguese / Portuguese](/i18n/README.pt.md)
|
||||
- [Portuguese / Português](/i18n/README.pt.md)
|
||||
- [Portuguese (Brazilian) / Português Brasileiro](/i18n/README.pt-br.md)
|
||||
- [Romanian / Română](/i18n/README.ro.md)
|
||||
- [Russian / Pусский](/i18n/README.ru.md)
|
||||
|
||||
@@ -11,7 +11,9 @@ const Footer = () => {
|
||||
<hr className="border-scale-400 mt-8"></hr>
|
||||
<div className="flex gap-4 items-center mt-6 justify-between">
|
||||
<div className="flex flex-col lg:flex-row gap-3 ">
|
||||
<span className="text-xs text-scale-900">Supabase 2022</span>
|
||||
<Link href="https://supabase.com/">
|
||||
<a className="text-xs text-scale-900">© Supabase Inc</a>
|
||||
</Link>
|
||||
<span className="text-xs text-scale-900">—</span>
|
||||
{footerData.map((item) => (
|
||||
<Link href={item.url} key={item.url}>
|
||||
|
||||
@@ -471,6 +471,10 @@ export const database = {
|
||||
name: 'Managing Indexes',
|
||||
url: '/guides/database/postgres/indexes',
|
||||
},
|
||||
{
|
||||
name: 'Cascade Deletes',
|
||||
url: '/guides/database/postgres/cascade-deletes',
|
||||
},
|
||||
{
|
||||
name: 'Drop All Tables in Schema',
|
||||
url: '/guides/database/postgres/dropping-all-tables-in-schema',
|
||||
|
||||
@@ -138,9 +138,9 @@ create table
|
||||
|
||||
create table
|
||||
members (
|
||||
"id" serial primary key,
|
||||
"user_id" int references users,
|
||||
"team_id" int references teams
|
||||
"team_id" int references teams,
|
||||
primary key(user_id, team_id)
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ It's good practice to create the extension within a separate schema (like `exten
|
||||
|
||||
## API
|
||||
|
||||
- [`sign(payload json, secret text, algorithm text default 'HSA256')`](https://github.com/michelp/pgjwt#usage): Signs a JWT containng _payload_ with _secret_ using _algorithm_.
|
||||
- [`sign(payload json, secret text, algorithm text default 'HSA256')`](https://github.com/michelp/pgjwt#usage): Signs a JWT containing _payload_ with _secret_ using _algorithm_.
|
||||
- [`verify(token text, secret text, algorithm text default 'HSA256')`](https://github.com/michelp/pgjwt#usage): Decodes a JWT _token_ that was signed with _secret_ using _algorithm_.
|
||||
|
||||
Where:
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
title: 'Cascade Deletes',
|
||||
description: 'Understand the types of foreign key constraint deletes',
|
||||
footerHelpType: 'postgres',
|
||||
}
|
||||
|
||||
There are 5 options for foreign key constraint deletes:
|
||||
|
||||
1. **CASCADE:** When a row is deleted from the parent table, all related rows in the child table(s) are deleted as well.
|
||||
2. **RESTRICT:** When a row is deleted from the parent table, the delete operation is aborted if there are any related rows in the child table(s).
|
||||
3. **SET NULL:** When a row is deleted from the parent table, the values of the foreign key columns in the child table(s) are set to NULL.
|
||||
4. **SET DEFAULT:** When a row is deleted from the parent table, the values of the foreign key columns in the child table(s) are set to their default values.
|
||||
5. **NO ACTION:** This option is similar to RESTRICT, but it also has the option to be “deferred” to the end of a transaction. This means that other cascading deletes can run first, and then this delete constraint will only throw an error if there is referenced data remaining _at the end of the transaction_.
|
||||
|
||||
These options can be specified when defining a foreign key constraint using the "ON DELETE" clause. For example, the following SQL statement creates a foreign key constraint with the `CASCADE` option:
|
||||
|
||||
```sql
|
||||
alter table child_table
|
||||
add constraint fk_parent foreign key (parent_id) references parent_table (id)
|
||||
on delete cascade;
|
||||
```
|
||||
|
||||
This means that when a row is deleted from the "parent_table", all related rows in the "child_table" will be deleted as well.
|
||||
|
||||
## `RESTRICT` vs `NO ACTION`
|
||||
|
||||
The difference between `NO ACTION` and `RESTRICT` is subtle and can be a bit confusing.
|
||||
|
||||
Both `NO ACTION` and `RESTRICT` are used to prevent deletion of a row in a parent table if there are related rows in a child table. However, there is a subtle difference in how they behave.
|
||||
|
||||
When a foreign key constraint is defined with the option `RESTRICT`, it means that if a row in the parent table is deleted, the database will immediately raise an error and prevent the deletion of the row in the parent table. The database will not delete, update or set to NULL any rows in the referenced table(s).
|
||||
|
||||
When a foreign key constraint is defined with the option `NO ACTION`, it means that if a row in the parent table is deleted, the database will also raise an error and prevent the deletion of the row in the parent table. However unlike `RESTRICT`, `NO ACTION` has the option defer the check using `INITIALLY DEFERRED`. This will only raise the above error _if_ the referenced rows still exist at the end of the transaction.
|
||||
|
||||
The difference from `RESTRICT` is that a constraint marked as `NO ACTION INITIALLY DEFERRED` is deferred until the end of the transaction, rather than running immediately. If, for example there is another foreign key constraint between the same tables marked as `CASCADE`, the cascade will occur first and delete the referenced rows, and no error will be thrown by the deferred constraint. Otherwise if there are still rows referencing the parent row by the end of the transaction, an error will be raised just like before. Just like `RESTRICT`, the database will not delete, update or set to NULL any rows in the referenced table(s).
|
||||
|
||||
In practice, you can use either `NO ACTION` or `RESTRICT` depending on your needs. `NO ACTION` is the default behavior if you do not specify anything. If you prefer to defer the check until the end of the transaction, use `NO ACTION INITIALLY DEFERRED`.
|
||||
|
||||
## Example
|
||||
|
||||
Let's further illustrate the difference with an example. We'll use the following data:
|
||||
|
||||
`grandparent`
|
||||
|
||||
| id | name |
|
||||
| --- | --------- |
|
||||
| 1 | Elizabeth |
|
||||
|
||||
`parent`
|
||||
|
||||
| id | name | parent_id |
|
||||
| --- | ------- | --------- |
|
||||
| 1 | Charles | 1 |
|
||||
| 2 | Diana | 1 |
|
||||
|
||||
`child`
|
||||
|
||||
| id | name | father | mother |
|
||||
| --- | ------- | ------ | ------ |
|
||||
| 1 | William | 1 | 2 |
|
||||
|
||||
To create these tables and their data, we run:
|
||||
|
||||
```sql
|
||||
create table grandparent (
|
||||
id serial primary key,
|
||||
name text
|
||||
);
|
||||
|
||||
create table parent (
|
||||
id serial primary key,
|
||||
name text,
|
||||
parent_id integer references grandparent (id)
|
||||
on delete cascade
|
||||
);
|
||||
|
||||
create table child (
|
||||
id serial primary key,
|
||||
name text,
|
||||
father integer references parent (id)
|
||||
on delete restrict
|
||||
);
|
||||
|
||||
insert into grandparent
|
||||
(id, name)
|
||||
values
|
||||
(1, 'Elizabeth');
|
||||
|
||||
insert into parent
|
||||
(id, name, parent_id)
|
||||
values
|
||||
(1, 'Charles', 1);
|
||||
|
||||
insert into parent
|
||||
(id, name, parent_id)
|
||||
values
|
||||
(2, 'Diana', 1);
|
||||
|
||||
-- We'll just link the father for now
|
||||
insert into child
|
||||
(id, name, father)
|
||||
values
|
||||
(1, 'William', 1);
|
||||
```
|
||||
|
||||
### `RESTRICT`
|
||||
|
||||
`RESTRICT` will prevent a delete and raise an error:
|
||||
|
||||
```shell
|
||||
postgres=# delete from grandparent;
|
||||
ERROR: update or delete on table "parent" violates foreign key constraint "child_father_fkey" on table "child"
|
||||
DETAIL: Key (id)=(1) is still referenced from table "child".
|
||||
```
|
||||
|
||||
Even though the foreign key constraint between parent and grandparent is `CASCADE`, the constraint between child and father is `RESTRICT`. Therefore an error is raised and no records are deleted.
|
||||
|
||||
### `NO ACTION`
|
||||
|
||||
Let's change the child-father relationship to `NO ACTION`:
|
||||
|
||||
```sql
|
||||
alter table child
|
||||
drop constraint child_father_fkey;
|
||||
|
||||
alter table child
|
||||
add constraint child_father_fkey foreign key (father) references parent (id)
|
||||
on delete no action;
|
||||
```
|
||||
|
||||
We see that `NO ACTION` will also prevent a delete and raise an error:
|
||||
|
||||
```shell
|
||||
postgres=# delete from grandparent;
|
||||
ERROR: update or delete on table "parent" violates foreign key constraint "child_father_fkey" on table "child"
|
||||
DETAIL: Key (id)=(1) is still referenced from table "child".
|
||||
```
|
||||
|
||||
### `NO ACTION INITIALLY DEFERRED`
|
||||
|
||||
We'll change the foreign key constraint between child and father to be `NO ACTION INITIALLY DEFERRED`:
|
||||
|
||||
```sql
|
||||
alter table child
|
||||
drop constraint child_father_fkey;
|
||||
|
||||
alter table child
|
||||
add constraint child_father_fkey foreign key (father) references parent (id)
|
||||
on delete no action initially deferred;
|
||||
```
|
||||
|
||||
Here you will see that `INITIALLY DEFFERED` seems to operate like `NO ACTION` or `RESTRICT`. When we run a delete, it seems to make no difference:
|
||||
|
||||
```shell
|
||||
postgres=# delete from grandparent;
|
||||
ERROR: update or delete on table "parent" violates foreign key constraint "child_father_fkey" on table "child"
|
||||
DETAIL: Key (id)=(1) is still referenced from table "child".
|
||||
```
|
||||
|
||||
But, when we combine it with _other_ constraints, then any other constraints take precedence. For example, let's run the same but add a `mother` column that has a `CASCADE` delete:
|
||||
|
||||
```sql
|
||||
alter table child
|
||||
add column mother integer references parent (id)
|
||||
on delete cascade;
|
||||
|
||||
update child
|
||||
set mother = 2
|
||||
where id = 1;
|
||||
```
|
||||
|
||||
Then let's run a delete on the `grandparent` table:
|
||||
|
||||
```shell
|
||||
postgres=# delete from grandparent;
|
||||
DELETE 1
|
||||
|
||||
postgres=# select * from parent;
|
||||
id | name | parent_id
|
||||
----+------+-----------
|
||||
(0 rows)
|
||||
|
||||
postgres=# select * from child;
|
||||
id | name | father | mother
|
||||
----+------+--------+--------
|
||||
(0 rows)
|
||||
```
|
||||
|
||||
The `mother` deletion took precedence over the `father`, and so William was deleted. After William was deleted, there was no reference to “Charles” and so he was free to be deleted, even though previously he wasn't (without `INITIALLY DEFERRED`).
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -22,7 +22,7 @@ Let's start building the Vue 3 app from scratch.
|
||||
|
||||
### Initialize a Nuxt 3 app
|
||||
|
||||
We can use [`nuxi init`](https://v3.nuxtjs.org/getting-started/quick-start/) to create an app called `nuxt-user-management`:
|
||||
We can use [`nuxi init`](https://nuxt.com/docs/getting-started/installation) to create an app called `nuxt-user-management`:
|
||||
|
||||
```bash
|
||||
npx nuxi init nuxt-user-management
|
||||
|
||||
@@ -207,7 +207,7 @@ You can configure Postgres by executing the following statement, followed by a s
|
||||
alter system set max_connections = '<val-here>';
|
||||
```
|
||||
|
||||
Note that [the default configuration used by the Supabase platform](https://github.com/supabase/supabase-admin-api/blob/master/optimizations/postgres.go) optimizes the database to maximize resource utilization, and as a result, you might also need to configure other options (e.g. `work_mem`, `shared_buffers`, `maintenance_work_mem`) in order to tune them towards your use-case, and to avoid causing instability in your database.
|
||||
Note that the default configuration used by the Supabase platform optimizes the database to maximize resource utilization, and as a result, you might also need to configure other options (e.g. `work_mem`, `shared_buffers`, `maintenance_work_mem`) in order to tune them towards your use-case, and to avoid causing instability in your database.
|
||||
|
||||
Once overridden, the Supabase platform will continue to respect your manually configured value (even if the add-on size is changed), unless the override is removed with the following statement, followed by a server restart:
|
||||
|
||||
|
||||
@@ -153,25 +153,31 @@ export const postgres = [
|
||||
{
|
||||
title: 'Managing Indexes',
|
||||
hasLightIcon: true,
|
||||
href: '/guides/resources/postgres/indexes',
|
||||
href: '/guides/database/postgres/indexes',
|
||||
description: 'Improve query performance using various index types in Postgres.',
|
||||
},
|
||||
{
|
||||
title: 'Cascade Deletes',
|
||||
hasLightIcon: true,
|
||||
href: '/guides/database/postgres/cascade-deletes',
|
||||
description: 'Understand the types of foreign key constraint deletes.',
|
||||
},
|
||||
{
|
||||
title: 'Drop all tables in schema',
|
||||
hasLightIcon: true,
|
||||
href: '/guides/resources/postgres/dropping-all-tables-in-schema',
|
||||
href: '/guides/database/postgres/dropping-all-tables-in-schema',
|
||||
description: 'Delete all tables in a given schema.',
|
||||
},
|
||||
{
|
||||
title: 'Select first row per group',
|
||||
hasLightIcon: true,
|
||||
href: '/guides/resources/postgres/first-row-in-group',
|
||||
href: '/guides/database/postgres/first-row-in-group',
|
||||
description: 'Retrieve the first row in each distinct group.',
|
||||
},
|
||||
{
|
||||
title: 'Print PostgreSQL version',
|
||||
hasLightIcon: true,
|
||||
href: '/guides/resources/postgres/which-version-of-postgres',
|
||||
href: '/guides/database/postgres/which-version-of-postgres',
|
||||
description: 'Find out which version of Postgres you are running.',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -109,6 +109,7 @@ Build a basic Todo List with Supabase and your favorite frontend framework:
|
||||
- Subscriptions with Supabase and Stripe Billing [Blog](https://www.sandromaglione.com/2021/04/29/supabase-auth-create-stripe-customer-subscription-supabase-stripe-billing-part-1/)
|
||||
- Flutter Supabase Authentication [Blog](https://www.sandromaglione.com/2021/04/24/flutter-supabase-authentication/)
|
||||
- Supabase in 6 minutes [Video](https://www.youtube.com/watch?v=c8DNV9yl0mg)
|
||||
- Supabase React Auth UI Tutorial [Video](https://youtu.be/6ch1PtIqCUw)
|
||||
- Let's build SupaAuth - Build an Authentication System using Supabase, Next.js and Typescript [6-part Blog Series](https://aalam.in/blog/supabase-auth-intro-setup-next)
|
||||
- In-depth self-hosting guide using Nginx [Blog](https://dev.to/chronsyn/self-hosting-with-supabase-1aii)
|
||||
- Build an Email and Social Auth for Next JS with Supabase, Tailwind CSS 3.0 and TypeScript [Blog](https://creativedesignsguru.com/next-js-supabase-auth/)
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
title: 'Next steps for Postgres pluggable storage'
|
||||
description: Exploring history of Postgres pluggable storage and the possibility of landing it in the Postgres core.
|
||||
author: paul_copplestone
|
||||
image: pluggable-storage/pluggable-storage-og.jpg
|
||||
thumb: pluggable-storage/pluggable-storage.jpg
|
||||
tags:
|
||||
- postgres
|
||||
- database
|
||||
date: '2023-04-28'
|
||||
toc_depth: 2
|
||||
---
|
||||
|
||||
You might have recently read “[The Part of PostgreSQL We Hate the Most](https://ottertune.com/blog/the-part-of-postgresql-we-hate-the-most/)” which highlights some of the deficiencies with Postgres storage. Fortunately, there is ongoing work in the Postgres ecosystem to solve this using “pluggable storage”.
|
||||
|
||||
This article explores the history of Postgres pluggable storage and the possibility of landing it in the Postgres core.
|
||||
|
||||
## What is pluggable storage?
|
||||
|
||||
Pluggable storage gives developers the ability to use different storage engines for different tables within the same database.
|
||||
|
||||
Developers will be able to choose a storage method that is optimized for their specific needs. Some tables could be configured for high transactional loads, others for analytics workloads, and still others for archiving.
|
||||
|
||||
Something like this is already [available in MySQL](https://en.wikipedia.org/wiki/Comparison_of_MySQL_database_engines), which uses the [InnoDB](https://en.wikipedia.org/wiki/InnoDB) as the default storage engine since MySQL 5.5. This replaced [MyISAM](https://en.wikipedia.org/wiki/MyISAM) which had it’s own [set of problems](https://www.percona.com/blog/using-myisam-in-production/).
|
||||
|
||||
The “pluggable” part refers to the ability for developers to develop their own storage methods, similar to Postgres Extensions. While some databases offer multiple built-in storage methods by default, Postgres follows a core philosophy of flexibility and extensibility, and using a pluggable approach makes a lot of sense.
|
||||
|
||||
## Current progress for pluggable storage
|
||||
|
||||
In version 12, PostgreSQL introduced basic support for pluggable storage with the goal of [adding ZHeap](https://anarazel.de/talks/2019-05-30-pgcon-pluggable-table-storage/pluggable.pdf). Andres Freund and several contributors enabled that feature by refactoring and expanding a variety of table access method (TAM) APIs.
|
||||
|
||||
ZHeap [was released](https://github.com/EnterpriseDB/zheap) with [promising results](https://www.pgcon.org/2018/schedule/attachments/501_zheap-a-new-storage-format-postgresql-5.pdf), providing an undo log and solving some long-term table bloat issues in Postgres.
|
||||
|
||||
While it was a great start, unfortunately it appears that the work on ZHeap and the TAM API [has stalled](https://twitter.com/andy_pavlo/status/1590703943176589312).
|
||||
This leaves a few holes in that need to be fixed before the community can use the TAM API for pluggable storage. In particular:
|
||||
|
||||
- The TAM API doesn’t provide much flexibility for working with indexes. For example, when you update a row an index update is an all-or-nothing procedure.
|
||||
- Row versions need to be identified by the pairing of a block number and offset number. This means you can’t implement some useful functions, like an index-organized table or undo-based versioning.
|
||||
|
||||
Even without a full API for pluggable storage, there are a couple of solutions available on the market today:
|
||||
|
||||
- **Citus’s** [Columnar Storage](https://docs.citusdata.com/en/v11.1/admin_guide/table_management.html#columnar-storage), which powers Microsoft’s [Hyperscale](https://azure.microsoft.com/en-us/updates/azure-database-for-postgresql-hyperscale-citus-is-now-available/) offering. Citus has a few [limitations](https://docs.citusdata.com/en/v11.1/admin_guide/table_management.html#limitations) due to the restrictions of the current API.
|
||||
- EDB’s [Advanced Storage Pack](https://www.infoworld.com/article/3681112/3-key-features-in-edb-postgresql-15.html), which is appears to provide some enhancements to heap storage, although the source is not available for us to confirm.
|
||||
|
||||
## The future of pluggable storage
|
||||
|
||||
Right now, pluggable storage requires a fork of Postgres. But there are several initiatives in the Postgres community that we’re excited about.
|
||||
|
||||
- There have been community updates for [custom resource managers](https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=5c279a6d350205cc98f91fb8e1d3e4442a6b25d1) that expand [write-ahead log (WAL)](https://www.postgresql.org/docs/current/wal-intro.html) support for TAM.
|
||||
- The creators at [OrioleDB](https://github.com/orioledb/orioledb) started with a fork of Postgres and have been quietly upstreaming code that would make pluggable storage a reality. In Postgres 14 their patchset was ~5,000 lines of code, representing the total number of changes required in their fork. In the upcoming Postgres 16 release, it is ~2,000 lines of code. That means that around 60% of the code is already committed to the Postgres core. (Disclosure: Supabase started [sponsoring OrioleDB](https://supabase.com/blog/supabase-series-b#where-were-going) in 2022).
|
||||
|
||||
<div className="bg-gray-300 rounded-lg px-6 py-2 italic">
|
||||
|
||||
Alexander Korotkov, the maintainer of OrioleDB, will be talking at PGCON 23 about some of the [changes](https://www.pgcon.org/events/pgcon_2023/schedule/session/470-future-of-table-access-methods/) they are making. They are solving many ["wicked Postgres problems"](https://www.slideshare.net/AlexanderKorotkov/solving-postgresql-wicked-problems) with [dual pointers](https://www.orioledata.com/blog/buffer-management/) instead of buffer mapping, row-level WAL instead of block-level WAL, undo logs instead of bloat-prone MVCC, and index-organized tables instead of heap.
|
||||
|
||||
</div>
|
||||
|
||||
## Timelines for pluggable storage
|
||||
|
||||
Ambitiously, many of the changes required for pluggable storage could land in Postgres 17. If that happens, new storage engines like OrioleDB would be available as a simple extension for developers to use in any Postgres instance. This paves the way for other Pluggable Storage Engines, just like MySQL’s [support for multiple storage engines](https://dev.mysql.com/doc/refman/5.7/en/storage-engines.html).
|
||||
|
||||
As a community project, Postgres requires support for these initiatives. You can support pluggable storage by getting involved during Postgres [Commitfests](https://supabase.com/blog/postgresql-commitfest). Postgres is already one of the best database engines in the world, and pluggable storage would make it even more attractive and maintainable.
|
||||
|
||||
## More Postgres content
|
||||
|
||||
- [Storing OpenAI embeddings in Postgres with pgvector](https://supabase.com/blog/openai-embeddings-postgres-vector)
|
||||
- [Choosing a Postgres Primary Key](https://supabase.com/blog/choosing-a-postgres-primary-key)
|
||||
- [SQL or NoSQL? Why not use both (with PostgreSQL)?](https://supabase.com/blog/sql-or-nosql-both-with-postgresql)
|
||||
- [pg_jsonschema: JSON Schema support for Postgres](https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation)
|
||||
- [Implementing "seen by" functionality with Postgres](https://supabase.com/blog/seen-by-in-postgresql)
|
||||
@@ -87,6 +87,7 @@ function CodeBlock(props: CodeBlockProps) {
|
||||
</div>
|
||||
)}
|
||||
<div className="relative">
|
||||
{/* @ts-ignore */}
|
||||
<SyntaxHighlighter
|
||||
language={lang}
|
||||
style={monokaiCustomTheme}
|
||||
|
||||
@@ -150,6 +150,7 @@ const CodeExamples = () => {
|
||||
</div>
|
||||
<SyntaxHighlighter
|
||||
language="javascript"
|
||||
// @ts-ignore
|
||||
style={monokaiCustomTheme}
|
||||
className="rounded-b-lg"
|
||||
customStyle={{
|
||||
|
||||
@@ -111,7 +111,7 @@ const CareerPage: NextPage = ({ jobs, contributors }: any) => {
|
||||
Explore remote possibilities and join our team to help us achieve it.
|
||||
</p>
|
||||
<a href="#positions">
|
||||
<Button className="text-white xl:text-sm">Open positions</Button>
|
||||
<Button className="text-white xl:text-sm mt-4">Open positions</Button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -69,7 +69,6 @@ export default function IndexPage() {
|
||||
'8GB database & 100GB file storage',
|
||||
'50GB bandwidth',
|
||||
'5GB file uploads',
|
||||
'Social OAuth providers',
|
||||
'100,000 monthly active users',
|
||||
'2M Edge Function invocations',
|
||||
'Daily backups',
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
Generated
+20
@@ -39040,6 +39040,7 @@
|
||||
"@types/react": "^17.0.37",
|
||||
"@types/react-copy-to-clipboard": "^5.0.4",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-syntax-highlighter": "^15.5.6",
|
||||
"sql-formatter": "^12.2.0",
|
||||
"storybook-dark-mode": "^2.1.1",
|
||||
"typescript": "4.3.4"
|
||||
@@ -39077,6 +39078,15 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"packages/ui/node_modules/@types/react-syntax-highlighter": {
|
||||
"version": "15.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.6.tgz",
|
||||
"integrity": "sha512-i7wFuLbIAFlabTeD2I1cLjEOrG/xdMa/rpx2zwzAoGHuXJDhSqp9BSfDlMHSh9JSuNfxHk9eEmMX6D55GiyjGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"packages/ui/node_modules/clsx": {
|
||||
"version": "1.2.1",
|
||||
"license": "MIT",
|
||||
@@ -68097,6 +68107,7 @@
|
||||
"@types/react": "^17.0.37",
|
||||
"@types/react-copy-to-clipboard": "^5.0.4",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-syntax-highlighter": "*",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"clsx": "^1.2.1",
|
||||
"cmdk-supabase": "^0.2.2",
|
||||
@@ -68150,6 +68161,15 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-syntax-highlighter": {
|
||||
"version": "15.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.6.tgz",
|
||||
"integrity": "sha512-i7wFuLbIAFlabTeD2I1cLjEOrG/xdMa/rpx2zwzAoGHuXJDhSqp9BSfDlMHSh9JSuNfxHk9eEmMX6D55GiyjGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"clsx": {
|
||||
"version": "1.2.1"
|
||||
},
|
||||
|
||||
@@ -5,8 +5,6 @@ export interface UseThemeProps {
|
||||
/**
|
||||
* Toggle between dark mode and light mode.
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* If `darkMode` left `undefined`, toggles between modes.
|
||||
*
|
||||
* If `darkMode` set, forces
|
||||
@@ -31,11 +29,11 @@ export const ThemeProvider = ({ children }: ThemeProviderProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
const key = localStorage.getItem('supabaseDarkMode')
|
||||
const mode = key === 'true'
|
||||
|
||||
// Default to dark mode if no preference config
|
||||
const mode = !key || key === 'true'
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
|
||||
toggleTheme(mode)
|
||||
prefersDark ? toggleTheme(true) : toggleTheme(mode)
|
||||
}, [])
|
||||
|
||||
const toggleTheme: UseThemeProps['toggleTheme'] = (darkMode) => {
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"postcss": "^8.4.5",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-countdown": "^2.3.5",
|
||||
"react-feather": "^2.0.10",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
@@ -42,8 +43,7 @@
|
||||
"sse.js": "^0.6.1",
|
||||
"tailwind-merge": "^1.10.0",
|
||||
"tailwindcss": "^3.0.15",
|
||||
"tailwindcss-radix": "^1.6.0",
|
||||
"react-countdown": "^2.3.5"
|
||||
"tailwindcss-radix": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/react": "^6.5.16",
|
||||
@@ -51,6 +51,7 @@
|
||||
"@types/react": "^17.0.37",
|
||||
"@types/react-copy-to-clipboard": "^5.0.4",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-syntax-highlighter": "^15.5.6",
|
||||
"sql-formatter": "^12.2.0",
|
||||
"storybook-dark-mode": "^2.1.1",
|
||||
"typescript": "4.3.4"
|
||||
|
||||
@@ -89,9 +89,11 @@ export const CodeBlock = ({
|
||||
)}
|
||||
{className ? (
|
||||
<div className="relative max-w-[90vw] md:max-w-none overflow-auto">
|
||||
{/* @ts-ignore */}
|
||||
<SyntaxHighlighter
|
||||
language={lang}
|
||||
wrapLines={true}
|
||||
// @ts-ignore
|
||||
style={monokaiTheme}
|
||||
className={[
|
||||
'code-block border p-4 w-full !my-0 !bg-scale-300',
|
||||
|
||||
@@ -282,7 +282,7 @@ const GenerateSQL = () => {
|
||||
)}
|
||||
<Input
|
||||
inputRef={inputRef}
|
||||
className="bg-scale-100 rounded mx-3 mb-4"
|
||||
className="bg-scale-100 rounded mx-3 mb-4 [&_input]:pr-32 md:[&_input]:pr-40"
|
||||
autoFocus
|
||||
placeholder={
|
||||
isLoading || isResponding
|
||||
|
||||
@@ -609,7 +609,7 @@ const DomainsForm = () => {
|
||||
<div className="px-5">
|
||||
<p className="text-scale-1100 text-sm">
|
||||
This will add a domain to a list of allowed domains that can interact with
|
||||
your Authenticaton services for this project.
|
||||
your Authentication services for this project.
|
||||
</p>
|
||||
</div>
|
||||
<div className="border-overlay-border border-t"></div>
|
||||
|
||||
+1881
-1191
File diff suppressed because it is too large.
Load diff
@@ -3672,7 +3672,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 400,
|
||||
"line": 410,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -3724,7 +3724,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 613,
|
||||
"line": 625,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -3762,7 +3762,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 615,
|
||||
"line": 627,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -3784,7 +3784,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 616,
|
||||
"line": 628,
|
||||
"character": 10
|
||||
}
|
||||
],
|
||||
@@ -3814,7 +3814,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 618,
|
||||
"line": 630,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -3851,7 +3851,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 621,
|
||||
"line": 633,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -3873,7 +3873,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 622,
|
||||
"line": 634,
|
||||
"character": 10
|
||||
}
|
||||
],
|
||||
@@ -3902,7 +3902,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 624,
|
||||
"line": 636,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -3940,7 +3940,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 627,
|
||||
"line": 639,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -3962,7 +3962,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 628,
|
||||
"line": 640,
|
||||
"character": 10
|
||||
}
|
||||
],
|
||||
@@ -3991,7 +3991,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 630,
|
||||
"line": 642,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -4029,7 +4029,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 676,
|
||||
"line": 688,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -4125,7 +4125,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 987,
|
||||
"line": 1000,
|
||||
"character": 2
|
||||
}
|
||||
],
|
||||
@@ -4227,7 +4227,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 988,
|
||||
"line": 1001,
|
||||
"character": 4
|
||||
}
|
||||
],
|
||||
@@ -4249,7 +4249,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 988,
|
||||
"line": 1001,
|
||||
"character": 12
|
||||
}
|
||||
],
|
||||
@@ -4292,7 +4292,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 822,
|
||||
"line": 834,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -4336,7 +4336,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 822,
|
||||
"line": 834,
|
||||
"character": 42
|
||||
}
|
||||
],
|
||||
@@ -4382,7 +4382,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1027,
|
||||
"line": 1040,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -4440,7 +4440,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1031,
|
||||
"line": 1044,
|
||||
"character": 6
|
||||
}
|
||||
],
|
||||
@@ -4463,7 +4463,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1030,
|
||||
"line": 1043,
|
||||
"character": 6
|
||||
}
|
||||
],
|
||||
@@ -4509,7 +4509,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1035,
|
||||
"line": 1048,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -4533,7 +4533,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1036,
|
||||
"line": 1049,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -4570,7 +4570,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1038,
|
||||
"line": 1051,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -4588,7 +4588,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1038,
|
||||
"line": 1051,
|
||||
"character": 20
|
||||
}
|
||||
],
|
||||
@@ -4627,7 +4627,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 758,
|
||||
"line": 770,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -4669,7 +4669,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 759,
|
||||
"line": 771,
|
||||
"character": 4
|
||||
}
|
||||
],
|
||||
@@ -4687,7 +4687,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 760,
|
||||
"line": 772,
|
||||
"character": 4
|
||||
}
|
||||
],
|
||||
@@ -4733,7 +4733,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 430,
|
||||
"line": 440,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -4792,7 +4792,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 386,
|
||||
"line": 396,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -4845,7 +4845,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 478,
|
||||
"line": 488,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -4899,7 +4899,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 336,
|
||||
"line": 346,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -4953,7 +4953,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 584,
|
||||
"line": 596,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -5007,7 +5007,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 962,
|
||||
"line": 974,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -5043,7 +5043,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 962,
|
||||
"line": 974,
|
||||
"character": 29
|
||||
}
|
||||
],
|
||||
@@ -5144,7 +5144,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1325,
|
||||
"line": 1346,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -5157,7 +5157,7 @@
|
||||
"flags": {},
|
||||
"comment": {
|
||||
"shortText": "Starts an auto-refresh process in the background. The session is checked\nevery few seconds. Close to the time of expiration a process is started to\nrefresh the session. If refreshing fails it will be retried for as long as\nnecessary.",
|
||||
"text": "If you set the {@link GoTrueClientOptions#autoRefreshToken} you don't need\nto call this function, it will be called for you.\n\nOn browsers the refresh process works only when the tab/window is in the\nforeground to conserve resources as well as prevent race conditions and\nflooding auth with requests. If you call this method any managed\nvisibility change callback will be removed and you must manage visibility\nchanges on your own.\n\nOn non-browser platforms the refresh process works *continuously* in the\nbackground, which may not be desirable. You should hook into your\nplatform's foreground indication mechanism and call these methods\nappropriately to conserve resources.\n\n{@see #stopAutoRefresh}\n"
|
||||
"text": "If you set the {@link GoTrueClientOptions#autoRefreshToken} you don't need\nto call this function, it will be called for you.\n\nOn browsers the refresh process works only when the tab/window is in the\nforeground to conserve resources as well as prevent race conditions and\nflooding auth with requests. If you call this method any managed\nvisibility change callback will be removed and you must manage visibility\nchanges on your own.\n\nOn non-browser platforms the refresh process works *continuously* in the\nbackground, which may not be desireable. You should hook into your\nplatform's foreground indication mechanism and call these methods\nappropriately to conserve resources.\n\n{@see #stopAutoRefresh}\n"
|
||||
},
|
||||
"type": {
|
||||
"type": "reference",
|
||||
@@ -5183,7 +5183,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1338,
|
||||
"line": 1359,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -5222,7 +5222,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 705,
|
||||
"line": 717,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -5275,7 +5275,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 708,
|
||||
"line": 720,
|
||||
"character": 6
|
||||
}
|
||||
],
|
||||
@@ -5322,7 +5322,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 531,
|
||||
"line": 543,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
|
||||
@@ -8642,7 +8642,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 400,
|
||||
"line": 410,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -8694,7 +8694,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 613,
|
||||
"line": 625,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -8732,7 +8732,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 615,
|
||||
"line": 627,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -8754,7 +8754,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 616,
|
||||
"line": 628,
|
||||
"character": 10
|
||||
}
|
||||
],
|
||||
@@ -8784,7 +8784,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 618,
|
||||
"line": 630,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -8821,7 +8821,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 621,
|
||||
"line": 633,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -8843,7 +8843,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 622,
|
||||
"line": 634,
|
||||
"character": 10
|
||||
}
|
||||
],
|
||||
@@ -8872,7 +8872,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 624,
|
||||
"line": 636,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -8910,7 +8910,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 627,
|
||||
"line": 639,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -8932,7 +8932,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 628,
|
||||
"line": 640,
|
||||
"character": 10
|
||||
}
|
||||
],
|
||||
@@ -8961,7 +8961,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 630,
|
||||
"line": 642,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -8999,7 +8999,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 676,
|
||||
"line": 688,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -9095,7 +9095,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 987,
|
||||
"line": 1000,
|
||||
"character": 2
|
||||
}
|
||||
],
|
||||
@@ -9197,7 +9197,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 988,
|
||||
"line": 1001,
|
||||
"character": 4
|
||||
}
|
||||
],
|
||||
@@ -9219,7 +9219,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 988,
|
||||
"line": 1001,
|
||||
"character": 12
|
||||
}
|
||||
],
|
||||
@@ -9467,7 +9467,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 822,
|
||||
"line": 834,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -9511,7 +9511,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 822,
|
||||
"line": 834,
|
||||
"character": 42
|
||||
}
|
||||
],
|
||||
@@ -9557,7 +9557,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1027,
|
||||
"line": 1040,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -9615,7 +9615,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1031,
|
||||
"line": 1044,
|
||||
"character": 6
|
||||
}
|
||||
],
|
||||
@@ -9638,7 +9638,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1030,
|
||||
"line": 1043,
|
||||
"character": 6
|
||||
}
|
||||
],
|
||||
@@ -9684,7 +9684,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1035,
|
||||
"line": 1048,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -9708,7 +9708,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1036,
|
||||
"line": 1049,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -9745,7 +9745,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1038,
|
||||
"line": 1051,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -9763,7 +9763,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1038,
|
||||
"line": 1051,
|
||||
"character": 20
|
||||
}
|
||||
],
|
||||
@@ -9802,7 +9802,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 758,
|
||||
"line": 770,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -9844,7 +9844,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 759,
|
||||
"line": 771,
|
||||
"character": 4
|
||||
}
|
||||
],
|
||||
@@ -9862,7 +9862,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 760,
|
||||
"line": 772,
|
||||
"character": 4
|
||||
}
|
||||
],
|
||||
@@ -9908,7 +9908,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 430,
|
||||
"line": 440,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -10146,7 +10146,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 386,
|
||||
"line": 396,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -10517,7 +10517,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 478,
|
||||
"line": 488,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -10963,7 +10963,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 336,
|
||||
"line": 346,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -11326,7 +11326,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 584,
|
||||
"line": 596,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -11669,7 +11669,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 962,
|
||||
"line": 974,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -11705,7 +11705,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 962,
|
||||
"line": 974,
|
||||
"character": 29
|
||||
}
|
||||
],
|
||||
@@ -12194,7 +12194,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1325,
|
||||
"line": 1346,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -12207,7 +12207,7 @@
|
||||
"flags": {},
|
||||
"comment": {
|
||||
"shortText": "Starts an auto-refresh process in the background. The session is checked\nevery few seconds. Close to the time of expiration a process is started to\nrefresh the session. If refreshing fails it will be retried for as long as\nnecessary.",
|
||||
"text": "If you set the {@link GoTrueClientOptions#autoRefreshToken} you don't need\nto call this function, it will be called for you.\n\nOn browsers the refresh process works only when the tab/window is in the\nforeground to conserve resources as well as prevent race conditions and\nflooding auth with requests. If you call this method any managed\nvisibility change callback will be removed and you must manage visibility\nchanges on your own.\n\nOn non-browser platforms the refresh process works *continuously* in the\nbackground, which may not be desirable. You should hook into your\nplatform's foreground indication mechanism and call these methods\nappropriately to conserve resources.\n\n{@see #stopAutoRefresh}\n"
|
||||
"text": "If you set the {@link GoTrueClientOptions#autoRefreshToken} you don't need\nto call this function, it will be called for you.\n\nOn browsers the refresh process works only when the tab/window is in the\nforeground to conserve resources as well as prevent race conditions and\nflooding auth with requests. If you call this method any managed\nvisibility change callback will be removed and you must manage visibility\nchanges on your own.\n\nOn non-browser platforms the refresh process works *continuously* in the\nbackground, which may not be desireable. You should hook into your\nplatform's foreground indication mechanism and call these methods\nappropriately to conserve resources.\n\n{@see #stopAutoRefresh}\n"
|
||||
},
|
||||
"type": {
|
||||
"type": "reference",
|
||||
@@ -12233,7 +12233,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 1338,
|
||||
"line": 1359,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -12272,7 +12272,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 705,
|
||||
"line": 717,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
@@ -12441,7 +12441,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 708,
|
||||
"line": 720,
|
||||
"character": 6
|
||||
}
|
||||
],
|
||||
@@ -12488,7 +12488,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "src/GoTrueClient.ts",
|
||||
"line": 531,
|
||||
"line": 543,
|
||||
"character": 8
|
||||
}
|
||||
],
|
||||
|
||||
+1690
-1004
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -5020,7 +5020,7 @@
|
||||
"kindString": "Call signature",
|
||||
"flags": {},
|
||||
"comment": {
|
||||
"shortText": "Creates a signed upload URL.\nSigned upload URLs can be used to upload files to the bucket without further authentication.\nThey are valid for one minute."
|
||||
"shortText": "Creates a signed upload URL.\nSigned upload URLs can be used to upload files to the bucket without further authentication.\nThey are valid for 2 hours."
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
@@ -5257,7 +5257,7 @@
|
||||
"kindString": "Call signature",
|
||||
"flags": {},
|
||||
"comment": {
|
||||
"shortText": "Creates a signed upload URL.\nSigned upload URLs can be used to upload files to the bucket without further authentication.\nThey are valid for one minute."
|
||||
"shortText": "Creates a signed upload URL.\nSigned upload URLs can be used to upload files to the bucket without further authentication.\nThey are valid for 2 hours."
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
@@ -91,8 +91,9 @@
|
||||
},
|
||||
"allowed_mime_types": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"examples": [["image/png", "image/jpg"]]
|
||||
"nullable": true,
|
||||
"examples": [["image/png", "image/jpg"]],
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
|
||||
@@ -43,10 +43,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"query"
|
||||
],
|
||||
"required": ["name", "query"],
|
||||
"title": "Endpoint",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -179,9 +176,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Source",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -200,10 +195,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"name"
|
||||
],
|
||||
"required": ["email", "name"],
|
||||
"title": "TeamUser",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -257,20 +249,12 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"provider",
|
||||
"token",
|
||||
"provider_uid",
|
||||
"api_key"
|
||||
],
|
||||
"required": ["email", "provider", "token", "provider_uid", "api_key"],
|
||||
"title": "User",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Team",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -283,10 +267,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"name"
|
||||
],
|
||||
"required": ["email", "name"],
|
||||
"title": "TeamUser",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -335,13 +316,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"provider",
|
||||
"token",
|
||||
"provider_uid",
|
||||
"api_key"
|
||||
],
|
||||
"required": ["email", "provider", "token", "provider_uid", "api_key"],
|
||||
"title": "User",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -401,10 +376,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"query"
|
||||
],
|
||||
"required": ["name", "query"],
|
||||
"title": "Endpoint",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -416,9 +388,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "List endpoints",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
},
|
||||
"post": {
|
||||
"callbacks": {},
|
||||
@@ -465,10 +435,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"query"
|
||||
],
|
||||
"required": ["name", "query"],
|
||||
"title": "Endpoint",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -519,10 +486,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"query"
|
||||
],
|
||||
"required": ["name", "query"],
|
||||
"title": "Endpoint",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -542,9 +506,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "Create endpoint",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
}
|
||||
},
|
||||
"/api/endpoints/{token}": {
|
||||
@@ -585,10 +547,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "Delete endpoint",
|
||||
"tags": [
|
||||
"management",
|
||||
"management"
|
||||
]
|
||||
"tags": ["management", "management"]
|
||||
},
|
||||
"get": {
|
||||
"callbacks": {},
|
||||
@@ -646,10 +605,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"query"
|
||||
],
|
||||
"required": ["name", "query"],
|
||||
"title": "Endpoint",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -669,9 +625,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "Fetch endpoint",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
},
|
||||
"patch": {
|
||||
"callbacks": {},
|
||||
@@ -728,10 +682,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"query"
|
||||
],
|
||||
"required": ["name", "query"],
|
||||
"title": "Endpoint",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -782,10 +733,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"query"
|
||||
],
|
||||
"required": ["name", "query"],
|
||||
"title": "Endpoint",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -805,9 +753,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "Update endpoint",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
},
|
||||
"put": {
|
||||
"callbacks": {},
|
||||
@@ -864,10 +810,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"query"
|
||||
],
|
||||
"required": ["name", "query"],
|
||||
"title": "Endpoint",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -918,10 +861,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"query"
|
||||
],
|
||||
"required": ["name", "query"],
|
||||
"title": "Endpoint",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -941,9 +881,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "Update endpoint",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
}
|
||||
},
|
||||
"/api/sources": {
|
||||
@@ -1046,9 +984,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Source",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -1060,9 +996,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "List sources",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
},
|
||||
"post": {
|
||||
"callbacks": {},
|
||||
@@ -1161,9 +1095,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Source",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1266,9 +1198,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Source",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1288,9 +1218,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "Create source",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
}
|
||||
},
|
||||
"/api/sources/{token}": {
|
||||
@@ -1331,9 +1259,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "Delete source",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
},
|
||||
"get": {
|
||||
"callbacks": {},
|
||||
@@ -1443,9 +1369,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Source",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1465,9 +1389,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "Fetch source",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
},
|
||||
"patch": {
|
||||
"callbacks": {},
|
||||
@@ -1576,9 +1498,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Source",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1681,9 +1601,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Source",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1703,9 +1621,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "Update source",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
},
|
||||
"put": {
|
||||
"callbacks": {},
|
||||
@@ -1814,9 +1730,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Source",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1919,9 +1833,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Source",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1941,9 +1853,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "Update source",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
}
|
||||
},
|
||||
"/api/teams": {
|
||||
@@ -1971,10 +1881,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"name"
|
||||
],
|
||||
"required": ["email", "name"],
|
||||
"title": "TeamUser",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -2028,20 +1935,12 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"provider",
|
||||
"token",
|
||||
"provider_uid",
|
||||
"api_key"
|
||||
],
|
||||
"required": ["email", "provider", "token", "provider_uid", "api_key"],
|
||||
"title": "User",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Team",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -2053,9 +1952,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "List teams",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
},
|
||||
"post": {
|
||||
"callbacks": {},
|
||||
@@ -2079,10 +1976,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"name"
|
||||
],
|
||||
"required": ["email", "name"],
|
||||
"title": "TeamUser",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -2136,20 +2030,12 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"provider",
|
||||
"token",
|
||||
"provider_uid",
|
||||
"api_key"
|
||||
],
|
||||
"required": ["email", "provider", "token", "provider_uid", "api_key"],
|
||||
"title": "User",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Team",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -2177,10 +2063,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"name"
|
||||
],
|
||||
"required": ["email", "name"],
|
||||
"title": "TeamUser",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -2234,20 +2117,12 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"provider",
|
||||
"token",
|
||||
"provider_uid",
|
||||
"api_key"
|
||||
],
|
||||
"required": ["email", "provider", "token", "provider_uid", "api_key"],
|
||||
"title": "User",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Team",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -2267,9 +2142,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "Create Team",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
}
|
||||
},
|
||||
"/api/teams/{token}": {
|
||||
@@ -2310,9 +2183,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "Delete Team",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
},
|
||||
"get": {
|
||||
"callbacks": {},
|
||||
@@ -2347,10 +2218,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"name"
|
||||
],
|
||||
"required": ["email", "name"],
|
||||
"title": "TeamUser",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -2404,20 +2272,12 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"provider",
|
||||
"token",
|
||||
"provider_uid",
|
||||
"api_key"
|
||||
],
|
||||
"required": ["email", "provider", "token", "provider_uid", "api_key"],
|
||||
"title": "User",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Team",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -2437,9 +2297,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "Fetch team",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
},
|
||||
"patch": {
|
||||
"callbacks": {},
|
||||
@@ -2473,10 +2331,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"name"
|
||||
],
|
||||
"required": ["email", "name"],
|
||||
"title": "TeamUser",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -2530,20 +2385,12 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"provider",
|
||||
"token",
|
||||
"provider_uid",
|
||||
"api_key"
|
||||
],
|
||||
"required": ["email", "provider", "token", "provider_uid", "api_key"],
|
||||
"title": "User",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Team",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -2571,10 +2418,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"name"
|
||||
],
|
||||
"required": ["email", "name"],
|
||||
"title": "TeamUser",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -2628,20 +2472,12 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"provider",
|
||||
"token",
|
||||
"provider_uid",
|
||||
"api_key"
|
||||
],
|
||||
"required": ["email", "provider", "token", "provider_uid", "api_key"],
|
||||
"title": "User",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Team",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -2661,9 +2497,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "Update team",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
},
|
||||
"put": {
|
||||
"callbacks": {},
|
||||
@@ -2697,10 +2531,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"name"
|
||||
],
|
||||
"required": ["email", "name"],
|
||||
"title": "TeamUser",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -2754,20 +2585,12 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"provider",
|
||||
"token",
|
||||
"provider_uid",
|
||||
"api_key"
|
||||
],
|
||||
"required": ["email", "provider", "token", "provider_uid", "api_key"],
|
||||
"title": "User",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Team",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -2795,10 +2618,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"name"
|
||||
],
|
||||
"required": ["email", "name"],
|
||||
"title": "TeamUser",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -2852,20 +2672,12 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"provider",
|
||||
"token",
|
||||
"provider_uid",
|
||||
"api_key"
|
||||
],
|
||||
"required": ["email", "provider", "token", "provider_uid", "api_key"],
|
||||
"title": "User",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"required": ["name"],
|
||||
"title": "Team",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -2885,9 +2697,7 @@
|
||||
}
|
||||
},
|
||||
"summary": "Update team",
|
||||
"tags": [
|
||||
"management"
|
||||
]
|
||||
"tags": ["management"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -147,10 +147,11 @@
|
||||
},
|
||||
"allowed_mime_types": {
|
||||
"type": "array",
|
||||
"nullable": true,
|
||||
"examples": [["image/png", "image/jpg"]],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": [["image/png", "image/jpg"]]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
|
||||
@@ -141,7 +141,7 @@ const RedirectUrls = () => {
|
||||
<div className="px-5">
|
||||
<p className="text-sm text-scale-1100">
|
||||
This will add a URL to a list of allowed URLs that can interact with your
|
||||
Authenticaton services for this project.
|
||||
Authentication services for this project.
|
||||
</p>
|
||||
</div>
|
||||
<div className="border-overlay-border border-t" />
|
||||
|
||||
@@ -73,6 +73,19 @@ export const USAGE_BASED_PRODUCTS = [
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'monthly_active_sso_users',
|
||||
attribute: 'total_auth_billing_period_sso_mau',
|
||||
title: 'Monthly Single Sign-On Users',
|
||||
units: 'absolute',
|
||||
costPerUnit: 0.015,
|
||||
tooltip: (
|
||||
<span>
|
||||
The amount of distinct Single Sign-On users requesting your API throughout the billing period. Resets
|
||||
at the beginning of every billing period.
|
||||
</span>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -272,42 +272,40 @@ const EnterpriseUpdate: FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-[440px]">
|
||||
<PaymentSummaryPanel
|
||||
isSpendCapEnabled={true}
|
||||
isSubmitting={isSubmitting}
|
||||
isRefreshingPreview={isRefreshingPreview}
|
||||
currentSubscription={currentSubscription}
|
||||
subscriptionPreview={subscriptionPreview}
|
||||
// Current subscription configuration based on DB
|
||||
currentPlan={currentSubscription.tier}
|
||||
currentAddons={currentAddons}
|
||||
// Selected subscription configuration based on UI
|
||||
selectedAddons={selectedAddons}
|
||||
paymentMethods={paymentMethods}
|
||||
isLoadingPaymentMethods={isLoadingPaymentMethods}
|
||||
selectedPaymentMethod={selectedPaymentMethodId}
|
||||
onSelectPaymentMethod={setSelectedPaymentMethodId}
|
||||
onSelectAddNewPaymentMethod={() => {
|
||||
setShowAddPaymentMethodModal(true)
|
||||
}}
|
||||
beforeConfirmPayment={beforeConfirmPayment}
|
||||
onConfirmPayment={onConfirmPayment}
|
||||
captcha={
|
||||
<HCaptcha
|
||||
ref={captchaRef}
|
||||
sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY!}
|
||||
size="invisible"
|
||||
onVerify={(token) => {
|
||||
setCaptchaToken(token)
|
||||
}}
|
||||
onExpire={() => {
|
||||
setCaptchaToken(null)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<PaymentSummaryPanel
|
||||
isSpendCapEnabled={true}
|
||||
isSubmitting={isSubmitting}
|
||||
isRefreshingPreview={isRefreshingPreview}
|
||||
currentSubscription={currentSubscription}
|
||||
subscriptionPreview={subscriptionPreview}
|
||||
// Current subscription configuration based on DB
|
||||
currentPlan={currentSubscription.tier}
|
||||
currentAddons={currentAddons}
|
||||
// Selected subscription configuration based on UI
|
||||
selectedAddons={selectedAddons}
|
||||
paymentMethods={paymentMethods}
|
||||
isLoadingPaymentMethods={isLoadingPaymentMethods}
|
||||
selectedPaymentMethod={selectedPaymentMethodId}
|
||||
onSelectPaymentMethod={setSelectedPaymentMethodId}
|
||||
onSelectAddNewPaymentMethod={() => {
|
||||
setShowAddPaymentMethodModal(true)
|
||||
}}
|
||||
beforeConfirmPayment={beforeConfirmPayment}
|
||||
onConfirmPayment={onConfirmPayment}
|
||||
captcha={
|
||||
<HCaptcha
|
||||
ref={captchaRef}
|
||||
sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY!}
|
||||
size="invisible"
|
||||
onVerify={(token) => {
|
||||
setCaptchaToken(token)
|
||||
}}
|
||||
onExpire={() => {
|
||||
setCaptchaToken(null)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Transition>
|
||||
|
||||
<AddNewPaymentMethodModal
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { FC, useReducer, useState, useEffect, useRef } from 'react'
|
||||
import { useReducer, useState, useEffect, useRef } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { includes, without } from 'lodash'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { Button, Form, Input, Modal, IconArrowLeft, Alert } from 'ui'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import { useFlag, useStore } from 'hooks'
|
||||
import { post, patch } from 'lib/common/fetch'
|
||||
import { API_URL, PROJECT_STATUS } from 'lib/constants'
|
||||
import { CANCELLATION_REASONS } from '../Billing.constants'
|
||||
@@ -13,13 +14,13 @@ import { SubscriptionPreview } from '../Billing.types'
|
||||
import { StripeSubscription } from 'components/interfaces/Billing'
|
||||
import HCaptcha from '@hcaptcha/react-hcaptcha'
|
||||
|
||||
interface Props {
|
||||
interface ExitSurveyProps {
|
||||
freeTier: any
|
||||
subscription?: StripeSubscription
|
||||
onSelectBack: () => void
|
||||
}
|
||||
|
||||
const ExitSurvey: FC<Props> = ({ freeTier, subscription, onSelectBack }) => {
|
||||
const ExitSurvey = ({ freeTier, subscription, onSelectBack }: ExitSurveyProps) => {
|
||||
const router = useRouter()
|
||||
const { app, ui } = useStore()
|
||||
const projectId = ui.selectedProject?.id ?? -1
|
||||
@@ -27,6 +28,7 @@ const ExitSurvey: FC<Props> = ({ freeTier, subscription, onSelectBack }) => {
|
||||
|
||||
const captchaRef = useRef<HCaptcha>(null)
|
||||
const [captchaToken, setCaptchaToken] = useState<string | null>(null)
|
||||
const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
|
||||
|
||||
const formRef = useRef<any>()
|
||||
|
||||
@@ -252,26 +254,52 @@ const ExitSurvey: FC<Props> = ({ freeTier, subscription, onSelectBack }) => {
|
||||
name="message"
|
||||
label="Anything else that we can improve on?"
|
||||
/>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
htmlType="button"
|
||||
type="default"
|
||||
onClick={() => router.push(`/project/${projectRef}/settings/billing/update`)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="danger"
|
||||
htmlType="submit"
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
Confirm downgrade
|
||||
</Button>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-xs text-scale-900">
|
||||
The unused amount for the remaining of your billing cycle will be refunded as
|
||||
credits
|
||||
</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
htmlType="button"
|
||||
type="default"
|
||||
onClick={() =>
|
||||
router.push(`/project/${projectRef}/settings/billing/update`)
|
||||
}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger className="w-full">
|
||||
<Button
|
||||
type="danger"
|
||||
htmlType="submit"
|
||||
loading={isSubmitting}
|
||||
disabled={projectUpdateDisabled || isSubmitting}
|
||||
>
|
||||
Confirm downgrade
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{projectUpdateDisabled ? (
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
Subscription changes are currently disabled. Our engineers are
|
||||
working on a fix.
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
) : null}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</div>
|
||||
<div className="self-center">
|
||||
<HCaptcha
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { Listbox, IconLoader, Button, IconPlus, IconAlertCircle, IconCreditCard } from 'ui'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { checkPermissions, useStore } from 'hooks'
|
||||
import { checkPermissions, useFlag, useStore } from 'hooks'
|
||||
import { BASE_PATH, PRICING_TIER_PRODUCT_IDS, STRIPE_PRODUCT_IDS } from 'lib/constants'
|
||||
import { SubscriptionPreview } from '../Billing.types'
|
||||
import { getProductPrice, validateSubscriptionUpdatePayload } from '../Billing.utils'
|
||||
@@ -14,6 +14,7 @@ import { getPITRDays } from './PaymentSummaryPanel.utils'
|
||||
import ConfirmPaymentModal from './ConfirmPaymentModal'
|
||||
import { StripeSubscription } from '../Subscription/Subscription.types'
|
||||
import { useIsProjectActive } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import clsx from 'clsx'
|
||||
|
||||
// [Joshen] PITR stuff can be undefined for now until we officially launch PITR self serve
|
||||
|
||||
@@ -92,6 +93,7 @@ const PaymentSummaryPanel = ({
|
||||
)
|
||||
|
||||
const [showConfirmModal, setShowConfirmModal] = useState(false)
|
||||
const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
|
||||
|
||||
const selectedPlanCost =
|
||||
selectedPlan !== undefined
|
||||
@@ -178,7 +180,11 @@ const PaymentSummaryPanel = ({
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="w-full px-6 py-10 space-y-8 overflow-y-auto border-l bg-panel-body-light dark:bg-panel-body-dark lg:px-12"
|
||||
className={clsx(
|
||||
'bg-panel-body-light dark:bg-panel-body-dark overflow-y-auto border-l',
|
||||
'px-6 lg:px-12 py-10 space-y-8',
|
||||
'min-w-[450px] max-w-[450px] 2xl:min-w-[630px] 2xl:max-w-[630px]'
|
||||
)}
|
||||
style={{ height: 'calc(100vh - 57px)' }}
|
||||
>
|
||||
<p>Payment Summary</p>
|
||||
@@ -447,6 +453,7 @@ const PaymentSummaryPanel = ({
|
||||
loading={isSubmitting}
|
||||
disabled={
|
||||
!isActive ||
|
||||
projectUpdateDisabled ||
|
||||
isSubmitting ||
|
||||
isLoadingPaymentMethods ||
|
||||
!hasChangesToPlan ||
|
||||
@@ -474,6 +481,8 @@ const PaymentSummaryPanel = ({
|
||||
? 'Please select a payment method'
|
||||
: !isActive
|
||||
? 'Unable to update subscription as project is not active'
|
||||
: projectUpdateDisabled
|
||||
? 'Subscription changes are currently disabled. Our engineers are working on a fix.'
|
||||
: ''}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,7 @@ const PlanSelection: FC<Props> = ({ visible, tiers, currentPlan, onSelectPlan })
|
||||
enterTo="transform opacity-100 translate-x-0"
|
||||
>
|
||||
<div>
|
||||
<h4 className="text-lg mb-8">Change your project's subscription</h4>
|
||||
<h4 className="text-lg">Change your project's subscription</h4>
|
||||
<div className={`grid py-8 grid-cols-2 ${gridCols}`}>
|
||||
{formattedTiers.map((plan) => {
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Button, IconLoader } from 'ui'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
|
||||
import { useFlag } from 'hooks'
|
||||
import { STRIPE_PRODUCT_IDS } from 'lib/constants'
|
||||
import { PRICING_TIER_PRODUCT_IDS, STRIPE_PRODUCT_IDS } from 'lib/constants'
|
||||
import { StripeProduct } from 'components/interfaces/Billing'
|
||||
import { useIsProjectActive } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
|
||||
@@ -14,9 +14,11 @@ interface PlanCTAButtonProps {
|
||||
}
|
||||
|
||||
const PlanCTAButton = ({ plan, currentPlan, onSelectPlan }: PlanCTAButtonProps) => {
|
||||
const isActive = useIsProjectActive()
|
||||
const isProjectActive = useIsProjectActive()
|
||||
const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
|
||||
|
||||
const isTeamTier = currentPlan?.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM
|
||||
|
||||
const getButtonType = (plan: any, currentPlan: any) => {
|
||||
if (['Free tier'].includes(plan.name)) {
|
||||
// Free is always default
|
||||
@@ -85,7 +87,8 @@ const PlanCTAButton = ({ plan, currentPlan, onSelectPlan }: PlanCTAButtonProps)
|
||||
const type = getButtonType(plan, currentPlan)
|
||||
const ctaText = getButtonText(plan, currentPlan)
|
||||
const disabled =
|
||||
(!isActive && plan.name !== 'Enterprise') ||
|
||||
(!isProjectActive && plan.name !== 'Enterprise') ||
|
||||
(isTeamTier && plan.name !== 'Enterprise' && plan.name !== 'Team tier') ||
|
||||
(plan.id === STRIPE_PRODUCT_IDS.FREE && currentPlan.prod_id === STRIPE_PRODUCT_IDS.FREE)
|
||||
|
||||
if (plan.name === 'Enterprise') {
|
||||
@@ -113,7 +116,7 @@ const PlanCTAButton = ({ plan, currentPlan, onSelectPlan }: PlanCTAButtonProps)
|
||||
{ctaText}
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{(projectUpdateDisabled || !isActive) && (
|
||||
{((disabled && isTeamTier) || projectUpdateDisabled || !isProjectActive) && (
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
@@ -127,8 +130,10 @@ const PlanCTAButton = ({ plan, currentPlan, onSelectPlan }: PlanCTAButtonProps)
|
||||
<span className="text-xs text-scale-1200 text-center">
|
||||
{projectUpdateDisabled
|
||||
? 'Subscription changes are currently disabled, our engineers are working on a fix'
|
||||
: !isActive
|
||||
: !isProjectActive
|
||||
? 'Unable to update subscription as project is not active'
|
||||
: isTeamTier
|
||||
? "Unable to update subscription from Team tier. Please reach out to us via support if you'd like to change your plan"
|
||||
: ''}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -39,7 +39,6 @@ export const PRICING_META = {
|
||||
'8GB database & 100GB file storage',
|
||||
'50GB bandwidth',
|
||||
'5GB file uploads',
|
||||
'Social OAuth providers',
|
||||
'100,000 monthly active users',
|
||||
'2M Edge Function invocations',
|
||||
'Daily backups',
|
||||
|
||||
@@ -321,43 +321,41 @@ const ProUpgrade: FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-[440px]">
|
||||
<PaymentSummaryPanel
|
||||
isRefreshingPreview={isRefreshingPreview}
|
||||
subscriptionPreview={subscriptionPreview}
|
||||
isSpendCapEnabled={isSpendCapEnabled}
|
||||
// Current subscription configuration based on DB
|
||||
currentPlan={currentSubscription.tier}
|
||||
currentAddons={currentAddons}
|
||||
currentSubscription={currentSubscription}
|
||||
// Selected subscription configuration based on UI
|
||||
selectedPlan={selectedTier}
|
||||
selectedAddons={selectedAddons}
|
||||
paymentMethods={paymentMethods}
|
||||
isLoadingPaymentMethods={isLoadingPaymentMethods}
|
||||
selectedPaymentMethod={selectedPaymentMethodId}
|
||||
onSelectPaymentMethod={setSelectedPaymentMethodId}
|
||||
onSelectAddNewPaymentMethod={() => {
|
||||
setShowAddPaymentMethodModal(true)
|
||||
}}
|
||||
beforeConfirmPayment={beforeConfirmPayment}
|
||||
onConfirmPayment={onConfirmPayment}
|
||||
isSubmitting={isSubmitting}
|
||||
captcha={
|
||||
<HCaptcha
|
||||
ref={captchaRef}
|
||||
sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY!}
|
||||
size="invisible"
|
||||
onVerify={(token) => {
|
||||
setCaptchaToken(token)
|
||||
}}
|
||||
onExpire={() => {
|
||||
setCaptchaToken(null)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<PaymentSummaryPanel
|
||||
isRefreshingPreview={isRefreshingPreview}
|
||||
subscriptionPreview={subscriptionPreview}
|
||||
isSpendCapEnabled={isSpendCapEnabled}
|
||||
// Current subscription configuration based on DB
|
||||
currentPlan={currentSubscription.tier}
|
||||
currentAddons={currentAddons}
|
||||
currentSubscription={currentSubscription}
|
||||
// Selected subscription configuration based on UI
|
||||
selectedPlan={selectedTier}
|
||||
selectedAddons={selectedAddons}
|
||||
paymentMethods={paymentMethods}
|
||||
isLoadingPaymentMethods={isLoadingPaymentMethods}
|
||||
selectedPaymentMethod={selectedPaymentMethodId}
|
||||
onSelectPaymentMethod={setSelectedPaymentMethodId}
|
||||
onSelectAddNewPaymentMethod={() => {
|
||||
setShowAddPaymentMethodModal(true)
|
||||
}}
|
||||
beforeConfirmPayment={beforeConfirmPayment}
|
||||
onConfirmPayment={onConfirmPayment}
|
||||
isSubmitting={isSubmitting}
|
||||
captcha={
|
||||
<HCaptcha
|
||||
ref={captchaRef}
|
||||
sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY!}
|
||||
size="invisible"
|
||||
onVerify={(token) => {
|
||||
setCaptchaToken(token)
|
||||
}}
|
||||
onExpire={() => {
|
||||
setCaptchaToken(null)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Transition>
|
||||
|
||||
<AddNewPaymentMethodModal
|
||||
|
||||
@@ -79,6 +79,42 @@ const SpendCapModal: FC<Props> = ({ visible, onHide }) => {
|
||||
<p className="w-[25%] text-sm">$0.00325/user</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<div className="flex w-[50%] items-center space-x-2">
|
||||
<p className="text-sm">Single Sign-On (SAML 2.0) MAUs</p>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<IconHelpCircle
|
||||
size={16}
|
||||
strokeWidth={1.5}
|
||||
className="transition opacity-50 cursor-pointer hover:opacity-100"
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
'border border-scale-200 ', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
Single Sign-On Monthly Active Users: A user that has made an API
|
||||
request in the last month
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
<p className="w-[25%] text-sm">50</p>
|
||||
<p className="w-[25%] text-sm">$0.015/user</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Storage size</p>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import dayjs from 'dayjs'
|
||||
import Link from 'next/link'
|
||||
import { FC, useEffect } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Button, Loading } from 'ui'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
@@ -23,7 +23,6 @@ interface Props {
|
||||
}
|
||||
|
||||
const Subscription: FC<Props> = ({ showProjectName = false }) => {
|
||||
const router = useRouter()
|
||||
const { ui } = useStore()
|
||||
const { ref: projectRef } = useParams()
|
||||
const isActive = useIsProjectActive()
|
||||
@@ -41,10 +40,12 @@ const Subscription: FC<Props> = ({ showProjectName = false }) => {
|
||||
isLoading: loading,
|
||||
error,
|
||||
} = useProjectSubscriptionQuery({ projectRef: ui.selectedProject?.ref })
|
||||
const { data: usage, isLoading: loadingUsage } = useProjectUsageQuery({ projectRef })
|
||||
const { data: usage } = useProjectUsageQuery({ projectRef })
|
||||
|
||||
const isPayg = subscription?.tier.prod_id === STRIPE_PRODUCT_IDS.PAYG
|
||||
const isTeam = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM
|
||||
const isEnterprise = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE
|
||||
const canOnlyUpdateAddons = isTeam || isEnterprise
|
||||
|
||||
const addOns = subscription?.addons ?? []
|
||||
const paid = subscription && subscription.tier.unit_amount > 0
|
||||
@@ -69,6 +70,12 @@ const Subscription: FC<Props> = ({ showProjectName = false }) => {
|
||||
return basePlanCost + totalAddOnCost + totalUsageCost
|
||||
}
|
||||
|
||||
const subscriptionUpdateURL = isEnterprise
|
||||
? `/project/${projectRef}/settings/billing/update/enterprise`
|
||||
: isTeam
|
||||
? `/project/${projectRef}/settings/billing/update/team`
|
||||
: `/project/${projectRef}/settings/billing/update`
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
ui.setNotification({
|
||||
@@ -92,18 +99,16 @@ const Subscription: FC<Props> = ({ showProjectName = false }) => {
|
||||
<div className="flex flex-col items-end space-y-2">
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<Button
|
||||
disabled={!canUpdateSubscription || projectUpdateDisabled || !isActive}
|
||||
onClick={() => {
|
||||
const url = isEnterprise
|
||||
? `/project/${projectRef}/settings/billing/update/enterprise`
|
||||
: `/project/${projectRef}/settings/billing/update`
|
||||
router.push(url)
|
||||
}}
|
||||
type="primary"
|
||||
>
|
||||
{isEnterprise ? 'Change add-ons' : 'Change subscription'}
|
||||
</Button>
|
||||
<Link href={subscriptionUpdateURL}>
|
||||
<a>
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={!canUpdateSubscription || projectUpdateDisabled || !isActive}
|
||||
>
|
||||
{canOnlyUpdateAddons ? 'Change add-ons' : 'Change subscription'}
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateSubscription || projectUpdateDisabled || !isActive ? (
|
||||
<Tooltip.Portal>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FC, useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
@@ -25,27 +25,24 @@ import {
|
||||
formatCustomDomainOptions,
|
||||
formatPITROptions,
|
||||
} from './AddOns/AddOns.utils'
|
||||
import BackButton from 'components/ui/BackButton'
|
||||
import SupportPlan from './AddOns/SupportPlan'
|
||||
import HCaptcha from '@hcaptcha/react-hcaptcha'
|
||||
|
||||
// Do not allow compute size changes for af-south-1
|
||||
|
||||
interface Props {
|
||||
interface TeamUpgradeProps {
|
||||
products: { tiers: any[]; addons: SubscriptionAddon[] }
|
||||
paymentMethods?: PaymentMethod[]
|
||||
currentSubscription: StripeSubscription
|
||||
isLoadingPaymentMethods: boolean
|
||||
onSelectBack: () => void
|
||||
}
|
||||
|
||||
const TeamUpgrade: FC<Props> = ({
|
||||
const TeamUpgrade = ({
|
||||
products,
|
||||
paymentMethods,
|
||||
currentSubscription,
|
||||
isLoadingPaymentMethods,
|
||||
onSelectBack,
|
||||
}) => {
|
||||
}: TeamUpgradeProps) => {
|
||||
const { app, ui } = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -225,8 +222,7 @@ const TeamUpgrade: FC<Props> = ({
|
||||
>
|
||||
<div className="flex-grow mt-10">
|
||||
<div className="relative space-y-4">
|
||||
<div className="px-32 mx-auto space-y-4 2xl:max-w-5xl">
|
||||
<BackButton onClick={() => onSelectBack()} />
|
||||
<div className="relative px-32 mx-auto space-y-4 2xl:max-w-5xl">
|
||||
<h4 className="text-lg text-scale-900 !mb-8">Change your project's subscription</h4>
|
||||
</div>
|
||||
|
||||
@@ -291,43 +287,41 @@ const TeamUpgrade: FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-[440px]">
|
||||
<PaymentSummaryPanel
|
||||
isRefreshingPreview={isRefreshingPreview}
|
||||
subscriptionPreview={subscriptionPreview}
|
||||
isSpendCapEnabled={false}
|
||||
// Current subscription configuration based on DB
|
||||
currentPlan={currentSubscription.tier}
|
||||
currentAddons={currentAddons}
|
||||
currentSubscription={currentSubscription}
|
||||
// Selected subscription configuration based on UI
|
||||
selectedPlan={selectedTier}
|
||||
selectedAddons={selectedAddons}
|
||||
paymentMethods={paymentMethods}
|
||||
isLoadingPaymentMethods={isLoadingPaymentMethods}
|
||||
selectedPaymentMethod={selectedPaymentMethodId}
|
||||
onSelectPaymentMethod={setSelectedPaymentMethodId}
|
||||
onSelectAddNewPaymentMethod={() => {
|
||||
setShowAddPaymentMethodModal(true)
|
||||
}}
|
||||
beforeConfirmPayment={beforeConfirmPayment}
|
||||
onConfirmPayment={onConfirmPayment}
|
||||
isSubmitting={isSubmitting}
|
||||
captcha={
|
||||
<HCaptcha
|
||||
ref={captchaRef}
|
||||
sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY!}
|
||||
size="invisible"
|
||||
onVerify={(token) => {
|
||||
setCaptchaToken(token)
|
||||
}}
|
||||
onExpire={() => {
|
||||
setCaptchaToken(null)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<PaymentSummaryPanel
|
||||
isRefreshingPreview={isRefreshingPreview}
|
||||
subscriptionPreview={subscriptionPreview}
|
||||
isSpendCapEnabled={false}
|
||||
// Current subscription configuration based on DB
|
||||
currentPlan={currentSubscription.tier}
|
||||
currentAddons={currentAddons}
|
||||
currentSubscription={currentSubscription}
|
||||
// Selected subscription configuration based on UI
|
||||
selectedPlan={selectedTier}
|
||||
selectedAddons={selectedAddons}
|
||||
paymentMethods={paymentMethods}
|
||||
isLoadingPaymentMethods={isLoadingPaymentMethods}
|
||||
selectedPaymentMethod={selectedPaymentMethodId}
|
||||
onSelectPaymentMethod={setSelectedPaymentMethodId}
|
||||
onSelectAddNewPaymentMethod={() => {
|
||||
setShowAddPaymentMethodModal(true)
|
||||
}}
|
||||
beforeConfirmPayment={beforeConfirmPayment}
|
||||
onConfirmPayment={onConfirmPayment}
|
||||
isSubmitting={isSubmitting}
|
||||
captcha={
|
||||
<HCaptcha
|
||||
ref={captchaRef}
|
||||
sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY!}
|
||||
size="invisible"
|
||||
onVerify={(token) => {
|
||||
setCaptchaToken(token)
|
||||
}}
|
||||
onExpire={() => {
|
||||
setCaptchaToken(null)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Transition>
|
||||
|
||||
<AddNewPaymentMethodModal
|
||||
|
||||
@@ -33,7 +33,7 @@ const ProjectUsage: FC<Props> = ({}) => {
|
||||
|
||||
const selectedInterval = CHART_INTERVALS.find((i) => i.key === interval) || CHART_INTERVALS[1]
|
||||
const startDate = dayjs()
|
||||
.subtract(selectedInterval.startValue, selectedInterval.startUnit)
|
||||
.subtract(selectedInterval.startValue, selectedInterval.startUnit as dayjs.ManipulateType)
|
||||
.format(DATE_FORMAT)
|
||||
const endDate = dayjs().format(DATE_FORMAT)
|
||||
const charts = data?.data
|
||||
|
||||
@@ -14,7 +14,8 @@ export interface ReportWidgetProps<T = any> {
|
||||
tooltip?: string
|
||||
className?: string
|
||||
renderer: (props: ReportWidgetRendererProps) => React.ReactNode
|
||||
params: BaseReportParams | LogsEndpointParams
|
||||
// omitting params will hide the "View in logs explorer" button
|
||||
params?: BaseReportParams | LogsEndpointParams
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
@@ -64,38 +65,40 @@ const ReportWidget: React.FC<ReportWidgetProps> = (props) => {
|
||||
</div>
|
||||
<p className="text-sm text-scale-1100">{props.description}</p>
|
||||
</div>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<Button
|
||||
type="default"
|
||||
icon={<IconExternalLink strokeWidth={1.5} />}
|
||||
className="px-1"
|
||||
onClick={() => {
|
||||
router.push({
|
||||
pathname: `/project/${projectRef}/logs/explorer`,
|
||||
query: {
|
||||
q: props.params?.sql,
|
||||
its: props.params.iso_timestamp_start,
|
||||
ite: props.params.iso_timestamp_end,
|
||||
},
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 max-w-xs leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">Open in Logs Explorer</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
{props.params && (
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<Button
|
||||
type="default"
|
||||
icon={<IconExternalLink strokeWidth={1.5} />}
|
||||
className="px-1"
|
||||
onClick={() => {
|
||||
router.push({
|
||||
pathname: `/project/${projectRef}/logs/explorer`,
|
||||
query: {
|
||||
q: props.params?.sql,
|
||||
its: props.params!.iso_timestamp_start,
|
||||
ite: props.params!.iso_timestamp_end,
|
||||
},
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 max-w-xs leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">Open in Logs Explorer</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<LoadingOpacity className="w-full" active={props.isLoading}>
|
||||
|
||||
@@ -68,10 +68,8 @@ const ConnectionPooling: FC<Props> = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const { project } = poolingConfiguration
|
||||
|
||||
// for older projects
|
||||
if (!project.pgbouncer_enabled && project.pool_mode == null)
|
||||
if (!poolingConfiguration.pgbouncer_enabled && poolingConfiguration.pool_mode == null)
|
||||
return (
|
||||
<Panel
|
||||
title={
|
||||
@@ -85,7 +83,7 @@ const ConnectionPooling: FC<Props> = () => {
|
||||
</Panel.Content>
|
||||
</Panel>
|
||||
)
|
||||
const formModel = project
|
||||
const formModel = poolingConfiguration
|
||||
const DB_FIELDS = ['db_host', 'db_name', 'db_port', 'db_user', 'inserted_at']
|
||||
const connectionInfo = pluckObjectFields(formModel, DB_FIELDS)
|
||||
const BOUNCER_FIELDS = [
|
||||
@@ -137,15 +135,22 @@ export const PgbouncerConfig: FC<ConfigProps> = observer(
|
||||
pool_mode: bouncerInfo.pool_mode || 'transaction',
|
||||
default_pool_size: bouncerInfo.default_pool_size || '',
|
||||
ignore_startup_parameters: bouncerInfo.ignore_startup_parameters || '',
|
||||
pgbouncer_enabled: bouncerInfo.pgbouncer_enabled,
|
||||
})
|
||||
|
||||
const updateConfig = async (updatedConfig: any) => {
|
||||
try {
|
||||
const response = await patch(`${API_URL}/props/pooling/${projectRef}/config`, updatedConfig)
|
||||
const response = await patch(`${API_URL}/projects/${projectRef}/config/pgbouncer`, {
|
||||
pgbouncer_enabled: updatedConfig.pgbouncer_enabled,
|
||||
default_pool_size: updatedConfig.default_pool_size,
|
||||
ignore_startup_parameters: updatedConfig.ignore_startup_parameters,
|
||||
pool_mode: updatedConfig.pool_mode,
|
||||
max_client_conn: updatedConfig.max_client_conn,
|
||||
})
|
||||
if (response.error) {
|
||||
throw response.error
|
||||
} else {
|
||||
setUpdates({ ...response.project })
|
||||
setUpdates({ ...response })
|
||||
ui.setNotification({ category: 'success', message: 'Successfully saved settings' })
|
||||
}
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -1,117 +1,29 @@
|
||||
import { BarChart } from 'components/to-be-cleaned/Charts/ChartRenderer'
|
||||
import dayjs from 'dayjs'
|
||||
import BarChart, { BarChartProps } from 'components/ui/Charts/BarChart'
|
||||
import { EventChartData, isUnixMicro, LogData, unixMicroToIsoTimestamp } from '.'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
interface Props {
|
||||
data?: EventChartData[]
|
||||
export interface LogEventChartProps {
|
||||
data: EventChartData[]
|
||||
onBarClick: (isoTimestamp: string) => void
|
||||
}
|
||||
|
||||
const LogEventChart: React.FC<Props> = ({ data, onBarClick }) => {
|
||||
// TODO: remove once endpoint returns iso timestamp directly
|
||||
const transformedData = useMemo(() => {
|
||||
return data?.map((d) => {
|
||||
const iso = isUnixMicro(d.timestamp)
|
||||
? unixMicroToIsoTimestamp(d.timestamp)
|
||||
: dayjs(d.timestamp).toISOString()
|
||||
|
||||
return {
|
||||
...d,
|
||||
timestamp: iso,
|
||||
// needed for bar chart
|
||||
// TODO: remove once bar chart is refactored
|
||||
period_start: iso,
|
||||
}
|
||||
})
|
||||
}, [JSON.stringify(data)])
|
||||
|
||||
if (!data) return null
|
||||
|
||||
return (
|
||||
<BarChart
|
||||
minimalHeader
|
||||
chartSize="tiny"
|
||||
data={transformedData}
|
||||
attribute="count"
|
||||
label="Logs / Time"
|
||||
onBarClick={(v?: { activePayload?: { payload: any }[] }) => {
|
||||
if (!v || !v?.activePayload?.[0]?.payload) return
|
||||
const unixOrIsoTimestamp = v.activePayload[0].payload.timestamp
|
||||
const isoTimestamp = isUnixMicro(unixOrIsoTimestamp)
|
||||
? unixMicroToIsoTimestamp(unixOrIsoTimestamp)
|
||||
: unixOrIsoTimestamp
|
||||
// 60s before
|
||||
onBarClick(isoTimestamp)
|
||||
}}
|
||||
customDateFormat="MMM D, HH:mm:s"
|
||||
noDataMessage={''}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// const useAggregated = (data: LogData[]) => {
|
||||
// const truncateToMinute = (micro: number) => Math.floor(micro / 1000 / 1000 / 60) * 60
|
||||
// const truncateToHour = (micro: number) => Math.floor(micro / 1000 / 1000 / 60 / 60) * 60 * 60
|
||||
// const getDiffMinute = (currentTimestamp: number, olderTimestamp: number) =>
|
||||
// Math.round((currentTimestamp - olderTimestamp) / 1000 / 60)
|
||||
// const getDiffHour = (currentTimestamp: number, olderTimestamp: number) =>
|
||||
// Math.round((currentTimestamp - olderTimestamp) / 1000 / 60 / 60)
|
||||
// const diffMultiplierMinute = (v: number) => v * 60
|
||||
// const diffMultiplierHour = (v: number) => v * 60 * 60
|
||||
// return useMemo(() => {
|
||||
// const oldest = data[data.length - 1]
|
||||
// if (!oldest) return
|
||||
// const latest = data[0]
|
||||
// const oldestDayjs = dayjs(oldest.timestamp / 1000)
|
||||
// const latestDayjs = dayjs(latest.timestamp / 1000)
|
||||
// let truncFunc = truncateToMinute
|
||||
// let getDiff = getDiffMinute
|
||||
// let diffMultiplier = diffMultiplierMinute
|
||||
// if (Math.abs(oldestDayjs.diff(latestDayjs, 'day', true)) > 0.25) {
|
||||
// truncFunc = truncateToHour
|
||||
// getDiff = getDiffHour
|
||||
// diffMultiplier = diffMultiplierHour
|
||||
// }
|
||||
|
||||
// const countMap = data
|
||||
// .map((d) => {
|
||||
// // truncate to per-minute
|
||||
// return { ...d, timestamp: truncateToMinute(d.timestamp) }
|
||||
// })
|
||||
// .reduce((acc, d) => {
|
||||
// if (acc[d.timestamp]) {
|
||||
// acc[d.timestamp] = acc[d.timestamp] + 1
|
||||
// } else {
|
||||
// acc[d.timestamp] = 1
|
||||
// }
|
||||
// return acc
|
||||
// }, {} as TimestampMap)
|
||||
|
||||
// // Add in additional data points for empty minutes
|
||||
// const oldestEvent = data[data.length - 1]
|
||||
// if (!oldestEvent) return []
|
||||
// const currentTimestamp = new Date().getTime()
|
||||
// const oldestTimestampMicro = oldestEvent.timestamp
|
||||
// const latestTimestamp = truncFunc(data[0]['timestamp'])
|
||||
// const diff = getDiff(currentTimestamp, oldestTimestampMicro / 1000)
|
||||
// for (const toAdd of Array.from(Array(diff).keys())) {
|
||||
// const tsToCheck = truncFunc(oldestTimestampMicro) + diffMultiplier(toAdd)
|
||||
// if (!(tsToCheck in countMap) && tsToCheck <= latestTimestamp) {
|
||||
// countMap[tsToCheck] = 0
|
||||
// }
|
||||
// }
|
||||
|
||||
// let aggregated = []
|
||||
// for (const [key, value] of Object.entries(countMap)) {
|
||||
// const v: number = Number(key)
|
||||
// aggregated.push({
|
||||
// timestamp: key,
|
||||
// timestampMicro: v * 1000 * 1000,
|
||||
// count: value,
|
||||
// })
|
||||
// }
|
||||
// return aggregated.sort((a, b) => a.timestampMicro - b.timestampMicro)
|
||||
// }, [JSON.stringify(data)])
|
||||
// }
|
||||
const LogEventChart = ({ data, onBarClick }: LogEventChartProps) => (
|
||||
<BarChart
|
||||
minimalHeader
|
||||
size="tiny"
|
||||
yAxisKey="count"
|
||||
xAxisKey="timestamp"
|
||||
data={data as unknown as BarChartProps['data']}
|
||||
title="Logs / Time"
|
||||
onBarClick={(v?: { activePayload?: { payload: any }[] }) => {
|
||||
if (!v || !v?.activePayload?.[0]?.payload) return
|
||||
const unixOrIsoTimestamp = v.activePayload[0].payload.timestamp
|
||||
const isoTimestamp = isUnixMicro(unixOrIsoTimestamp)
|
||||
? unixMicroToIsoTimestamp(unixOrIsoTimestamp)
|
||||
: unixOrIsoTimestamp
|
||||
// 60s before
|
||||
onBarClick(isoTimestamp)
|
||||
}}
|
||||
customDateFormat="MMM D, HH:mm:s"
|
||||
/>
|
||||
)
|
||||
export default LogEventChart
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Filters, LogData, LogsEndpointParams, LogsTableName, SQL_FILTER_TEMPLATES } from '.'
|
||||
import dayjs, { Dayjs } from 'dayjs'
|
||||
import { get } from 'lodash'
|
||||
import { get, isEqual } from 'lodash'
|
||||
import { StripeSubscription } from 'components/interfaces/Billing'
|
||||
import { useMonaco } from '@monaco-editor/react'
|
||||
import logConstants from 'shared-data/logConstants'
|
||||
import BackwardIterator from 'components/ui/CodeEditor/Providers/BackwardIterator'
|
||||
import { uniqBy } from 'lodash'
|
||||
import uniqBy from 'lodash/uniqBy'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
/**
|
||||
@@ -228,13 +228,22 @@ export const genChartQuery = (
|
||||
) => {
|
||||
const [startOffset, trunc] = calcChartStart(params)
|
||||
const where = _genWhereStatement(table, filters)
|
||||
|
||||
let joins = 'cross join unnest(t.metadata) as metadata'
|
||||
if (table === LogsTableName.EDGE) {
|
||||
joins += ' \n cross join unnest(metadata.request) as request'
|
||||
joins += ' \n cross join unnest(metadata.response) as response'
|
||||
} else if (table === LogsTableName.POSTGRES) {
|
||||
joins += ' \n cross join unnest(metadata.parsed) as parsed'
|
||||
}
|
||||
|
||||
return `
|
||||
SELECT
|
||||
timestamp_trunc(t.timestamp, ${trunc}) as timestamp,
|
||||
count(t.timestamp) as count
|
||||
FROM
|
||||
${table} t
|
||||
cross join unnest(t.metadata) as metadata
|
||||
${joins}
|
||||
${
|
||||
where
|
||||
? where + ` and t.timestamp > '${startOffset.toISOString()}'`
|
||||
@@ -349,3 +358,64 @@ export const useEditorHints = () => {
|
||||
}
|
||||
}, [monaco])
|
||||
}
|
||||
|
||||
/**
|
||||
* Assumes that all timestamps are in ISO-8601 UTC timezone.
|
||||
*
|
||||
* min/max are the datetime strings that extend beyond the given timeseries data.
|
||||
*/
|
||||
export const fillTimeseries = (
|
||||
timeseriesData: any[],
|
||||
timestampKey: string,
|
||||
valueKey: string,
|
||||
defaultValue: number,
|
||||
min?: string,
|
||||
max?: string
|
||||
) => {
|
||||
if (timeseriesData.length <= 1 && !(min || max)) return timeseriesData
|
||||
const dates: unknown[] = timeseriesData.map((datum) => dayjs.utc(datum[timestampKey]))
|
||||
|
||||
const maxDate = max ? dayjs.utc(max) : dayjs.utc(Math.max.apply(null, dates as number[]))
|
||||
const minDate = min ? dayjs.utc(min) : dayjs.utc(Math.min.apply(null, dates as number[]))
|
||||
|
||||
const truncationSample = timeseriesData.length > 0 ? timeseriesData[0][timestampKey] : min || max
|
||||
const truncation = getTimestampTruncation(truncationSample)
|
||||
|
||||
const newData = timeseriesData.map((datum) => {
|
||||
const iso = dayjs.utc(datum[timestampKey]).toISOString()
|
||||
datum[timestampKey] = iso
|
||||
return datum
|
||||
})
|
||||
|
||||
const diff = maxDate.diff(minDate, truncation as dayjs.UnitType)
|
||||
for (let i = 0; i <= diff; i++) {
|
||||
const dateToMaybeAdd = minDate.add(i, truncation as dayjs.ManipulateType)
|
||||
if (!dates.find((d) => isEqual(d, dateToMaybeAdd))) {
|
||||
newData.push({
|
||||
[timestampKey]: dateToMaybeAdd.toISOString(),
|
||||
[valueKey]: defaultValue,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return newData
|
||||
}
|
||||
|
||||
export const getTimestampTruncation = (datetime: string): 'second' | 'minute' | 'hour' | 'day' => {
|
||||
const values = ['second', 'minute', 'hour', 'day'].map((key) =>
|
||||
dayjs(datetime).get(key as dayjs.UnitType)
|
||||
)
|
||||
const zeroCount = values.reduce((acc, value) => {
|
||||
if (value === 0) {
|
||||
acc += 1
|
||||
}
|
||||
return acc
|
||||
}, 0)
|
||||
const truncation = {
|
||||
0: 'second' as const,
|
||||
1: 'minute' as const,
|
||||
2: 'hour' as const,
|
||||
3: 'day' as const,
|
||||
}[zeroCount]!
|
||||
return truncation
|
||||
}
|
||||
@@ -191,9 +191,9 @@ export const LogsPreviewer: React.FC<Props> = ({
|
||||
}
|
||||
>
|
||||
<div className={condensedLayout ? 'px-4' : ''}>
|
||||
{showChart && (
|
||||
{!isLoading && showChart && (
|
||||
<LogEventChart
|
||||
data={!isLoading && eventChartData ? eventChartData : undefined}
|
||||
data={eventChartData}
|
||||
onBarClick={(isoTimestamp) => {
|
||||
handleSearch('event-chart-bar-click', {
|
||||
query: filters.search_query as string,
|
||||
|
||||
+20
-5
@@ -317,11 +317,26 @@ const ForeignKeySelector: FC<Props> = ({ column, visible = false, closePanel, sa
|
||||
id="deletionAction"
|
||||
value={selectedForeignKey.deletionAction}
|
||||
label="Action if referenced row is removed"
|
||||
// @ts-ignore
|
||||
descriptionText={generateDeletionActionDescription(
|
||||
selectedForeignKey.deletionAction,
|
||||
`${selectedForeignKey.schema}.${selectedForeignKey.table}`
|
||||
)}
|
||||
descriptionText={
|
||||
<>
|
||||
<p>
|
||||
{generateDeletionActionDescription(
|
||||
selectedForeignKey.deletionAction,
|
||||
`${selectedForeignKey.schema}.${selectedForeignKey.table}`
|
||||
)}
|
||||
</p>
|
||||
<p className="mt-2">
|
||||
<a
|
||||
href="https://supabase.com/docs/guides/database/postgres/cascade-deletes"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-brand-900 opacity-75"
|
||||
>
|
||||
Learn more about cascade deletes
|
||||
</a>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
error={errors.column}
|
||||
onChange={(value: string) => updateDeletionAction(value)}
|
||||
>
|
||||
|
||||
@@ -35,6 +35,7 @@ import { ForeignRowSelectorProps } from './SidePanelEditor/RowEditor/ForeignRowS
|
||||
import TwoOptionToggle from 'components/ui/TwoOptionToggle'
|
||||
import TableDefinition from './TableDefinition'
|
||||
import APIDocumentationPanel from './APIDocumentationPanel'
|
||||
import { ERROR_PRIMARY_KEY_NOTFOUND } from 'components/grid/constants'
|
||||
|
||||
export interface TableGridEditorProps {
|
||||
/** Theme for the editor */
|
||||
@@ -320,6 +321,12 @@ const TableGridEditor = ({
|
||||
)
|
||||
|
||||
const configuration = { identifiers }
|
||||
if (Object.keys(identifiers).length === 0) {
|
||||
return ui.setNotification({
|
||||
category: 'error',
|
||||
message: ERROR_PRIMARY_KEY_NOTFOUND,
|
||||
})
|
||||
}
|
||||
|
||||
mutateUpdateTableRow({
|
||||
projectRef: project.ref,
|
||||
|
||||
@@ -16,12 +16,9 @@ export function getTableRowDeleteSql({
|
||||
rows,
|
||||
}: Pick<TableRowDeleteVariables, 'table' | 'rows'>) {
|
||||
const { primaryKeys, error } = getPrimaryKeys({ table })
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
if (error) throw error
|
||||
|
||||
let queryChains = new Query().from(table.name, table.schema ?? undefined).delete()
|
||||
|
||||
primaryKeys!.forEach((key) => {
|
||||
const primaryKeyValues = rows.map((x) => x[key])
|
||||
queryChains = queryChains.filter(key, 'in', primaryKeyValues)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { fillTimeseries } from 'components/interfaces/Settings/Logs'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
/**
|
||||
* Convenience hook for memoized filling of timeseries data.
|
||||
*/
|
||||
const useFillTimeseriesSorted = (...args: Parameters<typeof fillTimeseries>) => {
|
||||
return useMemo(() => {
|
||||
const [data, timestampKey] = args
|
||||
if (!data[0]?.[timestampKey]) return data;
|
||||
|
||||
const filled = fillTimeseries(...args)
|
||||
return filled.sort((a, b) => {
|
||||
return (new Date(a[args[1]]) as any) - (new Date(b[args[1]]) as any)
|
||||
})
|
||||
}, [JSON.stringify(args[0]), ...args])
|
||||
}
|
||||
export default useFillTimeseriesSorted
|
||||
@@ -15,10 +15,12 @@ import {
|
||||
LogsTableName,
|
||||
PREVIEWER_DATEPICKER_HELPERS,
|
||||
} from 'components/interfaces/Settings/Logs'
|
||||
import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react'
|
||||
import { Dispatch, SetStateAction, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import dayjs from 'dayjs'
|
||||
import useFillTimeseriesSorted from './useFillTimeseriesSorted'
|
||||
import useTimeseriesUnixToIso from './useTimeseriesUnixToIso'
|
||||
|
||||
interface Data {
|
||||
logData: LogData[]
|
||||
@@ -29,7 +31,7 @@ interface Data {
|
||||
filters: Filters
|
||||
params: LogsEndpointParams
|
||||
oldestTimestamp?: string
|
||||
eventChartData: EventChartData[] | null
|
||||
eventChartData: EventChartData[]
|
||||
}
|
||||
interface Handlers {
|
||||
loadOlder: () => void
|
||||
@@ -176,6 +178,22 @@ function useLogsPreview(
|
||||
setFilters({ ...newFilters, ...filterOverride })
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedEventChartData = useTimeseriesUnixToIso(
|
||||
eventChartResponse?.result || [],
|
||||
'timestamp'
|
||||
)
|
||||
|
||||
const eventChartData = useFillTimeseriesSorted(
|
||||
normalizedEventChartData,
|
||||
'timestamp',
|
||||
'count',
|
||||
0,
|
||||
params.iso_timestamp_start ,
|
||||
// default to current time if not set
|
||||
params.iso_timestamp_end || new Date().toISOString()
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
newCount,
|
||||
@@ -186,7 +204,7 @@ function useLogsPreview(
|
||||
filters,
|
||||
params,
|
||||
oldestTimestamp: oldestTimestamp ? String(oldestTimestamp) : undefined,
|
||||
eventChartData: eventChartResponse?.result || null,
|
||||
eventChartData,
|
||||
},
|
||||
{
|
||||
setFilters: handleSetFilters,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { isUnixMicro, unixMicroToIsoTimestamp } from 'components/interfaces/Settings/Logs'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
/**
|
||||
* Convenience hook for converting timeseries timestamp from unix microsecond to iso
|
||||
*
|
||||
* memoized
|
||||
*/
|
||||
const useTimeseriesUnixToIso = (data: any[], timestampKey: string) => {
|
||||
return useMemo(() => {
|
||||
// check if need to convert or not
|
||||
if (data.length === 0) return data
|
||||
if (!isUnixMicro(data[0][timestampKey])) return data
|
||||
|
||||
return data?.map((d) => {
|
||||
d[timestampKey] = unixMicroToIsoTimestamp(d[timestampKey])
|
||||
return d
|
||||
})
|
||||
}, [JSON.stringify(data)])
|
||||
}
|
||||
export default useTimeseriesUnixToIso
|
||||
@@ -151,6 +151,12 @@ export const METRICS = [
|
||||
provider: 'daily-stats',
|
||||
category: METRIC_CATEGORIES.API_AUTH,
|
||||
},
|
||||
{
|
||||
key: 'total_auth_billing_period_sso_mau',
|
||||
label: 'Auth Monthly Active SSO User',
|
||||
provider: 'daily-stats',
|
||||
category: METRIC_CATEGORIES.API_AUTH,
|
||||
},
|
||||
{
|
||||
key: 'total_auth_ingress',
|
||||
label: 'Auth Ingress',
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import dayjs from 'dayjs'
|
||||
import { useState } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Button } from 'ui'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { checkPermissions } from 'hooks'
|
||||
import { useParams } from 'common/hooks'
|
||||
import { ChartIntervals, NextPageWithLayout } from 'types'
|
||||
import { DATE_FORMAT } from 'lib/constants'
|
||||
import Panel from 'components/ui/Panel'
|
||||
import NoPermission from 'components/ui/NoPermission'
|
||||
import ChartHandler from 'components/to-be-cleaned/Charts/ChartHandler'
|
||||
import FunctionsLayout from 'components/layouts/FunctionsLayout'
|
||||
import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query'
|
||||
import { useFunctionsInvStatsQuery } from 'data/analytics/functions-inv-stats-query'
|
||||
import ReportWidget from 'components/interfaces/Reports/ReportWidget'
|
||||
import BarChart from 'components/ui/Charts/BarChart'
|
||||
import AreaChart from 'components/ui/Charts/AreaChart'
|
||||
import { isUnixMicro, unixMicroToIsoTimestamp } from 'components/interfaces/Settings/Logs'
|
||||
import meanBy from 'lodash/meanBy'
|
||||
import sumBy from 'lodash/sumBy'
|
||||
|
||||
const CHART_INTERVALS: ChartIntervals[] = [
|
||||
{
|
||||
@@ -22,40 +24,18 @@ const CHART_INTERVALS: ChartIntervals[] = [
|
||||
label: '5 min',
|
||||
startValue: 1,
|
||||
startUnit: 'hour',
|
||||
format: 'MMM D, h:mma',
|
||||
},
|
||||
{ key: '15min', label: '15 min', startValue: 24, startUnit: 'hour', format: 'MMM D, ha' },
|
||||
{ key: '1hr', label: '1 hour', startValue: 7, startUnit: 'day', format: 'MMM D' },
|
||||
{ key: '1day', label: '1 day', startValue: 7, startUnit: 'day', format: 'MMM D' },
|
||||
{ key: '7day', label: '7 days', startValue: 7, startUnit: 'day', format: 'MMM D' },
|
||||
{ key: '15min', label: '15 min', startValue: 15, startUnit: 'min' },
|
||||
{ key: '1hr', label: '1 hour', startValue: 1, startUnit: 'hour' },
|
||||
{ key: '1day', label: '1 day', startValue: 1, startUnit: 'day' },
|
||||
{ key: '7day', label: '7 days', startValue: 7, startUnit: 'day' },
|
||||
]
|
||||
|
||||
function calculateHighlightedValue(array: any, attribute: string, options?: { sum: boolean }) {
|
||||
if (!array) return ''
|
||||
|
||||
let total = 0
|
||||
let count = 0
|
||||
|
||||
array.forEach(function (item: any, index: number) {
|
||||
const _item = item[attribute]
|
||||
total += _item
|
||||
count++
|
||||
})
|
||||
|
||||
if (options?.sum) {
|
||||
return total
|
||||
} else {
|
||||
return total / count
|
||||
}
|
||||
}
|
||||
|
||||
const PageLayout: NextPageWithLayout = () => {
|
||||
const router = useRouter()
|
||||
const { ref: projectRef, functionSlug } = useParams()
|
||||
|
||||
const [interval, setInterval] = useState<string>('15min')
|
||||
const selectedInterval = CHART_INTERVALS.find((i) => i.key === interval) || CHART_INTERVALS[1]
|
||||
|
||||
const { data: selectedFunction } = useEdgeFunctionQuery({ projectRef, slug: functionSlug })
|
||||
const id = selectedFunction?.id
|
||||
|
||||
@@ -64,37 +44,22 @@ const PageLayout: NextPageWithLayout = () => {
|
||||
functionId: id,
|
||||
interval: selectedInterval.key,
|
||||
})
|
||||
const isChartLoading = !data?.result && !error ? true : false
|
||||
const chartData = useMemo(() => {
|
||||
return (data?.result || []).map((d: any) => ({
|
||||
...d,
|
||||
timestamp: isUnixMicro(d.timestamp) ? unixMicroToIsoTimestamp(d.timestamp) : d.timestamp,
|
||||
}))
|
||||
}, [data?.result])
|
||||
|
||||
const startDate = dayjs()
|
||||
.subtract(selectedInterval.startValue, selectedInterval.startUnit)
|
||||
.format(DATE_FORMAT)
|
||||
const endDate = dayjs().format(DATE_FORMAT)
|
||||
|
||||
const charts: any = {}
|
||||
charts.data = data?.result
|
||||
|
||||
const datetimeFormat = selectedInterval.format || 'MMM D, ha'
|
||||
|
||||
const handleBarClick = (v: any, search: string) => {
|
||||
if (!v || !v.activePayload?.[0]?.payload) return
|
||||
// returns rechart internal tooltip data type
|
||||
const payload = v.activePayload[0].payload
|
||||
const timestamp = payload.timestamp
|
||||
const timestampDigits = String(timestamp).length
|
||||
if (timestampDigits < 16) {
|
||||
// pad unix timestamp with additional 0 and then forward
|
||||
const paddedTimestamp = String(timestamp) + '0'.repeat(16 - timestampDigits)
|
||||
router.push(`/project/${projectRef}/functions/${id}/logs?te=${paddedTimestamp}`)
|
||||
} else {
|
||||
router.push(`/project/${projectRef}/functions/${id}/logs?te=${timestamp}`)
|
||||
}
|
||||
}
|
||||
|
||||
const startDate = dayjs().subtract(
|
||||
selectedInterval.startValue,
|
||||
selectedInterval.startUnit as dayjs.ManipulateType
|
||||
)
|
||||
const canReadFunction = checkPermissions(PermissionAction.FUNCTIONS_READ, functionSlug as string)
|
||||
if (!canReadFunction) {
|
||||
return <NoPermission isFullPage resourceText="access this edge function" />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6 py-2">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
@@ -128,79 +93,45 @@ const PageLayout: NextPageWithLayout = () => {
|
||||
</span>
|
||||
</div>
|
||||
<div className="">
|
||||
{startDate && endDate && (
|
||||
<>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 md:gap-4 lg:grid-cols-2 lg:gap-8">
|
||||
<Panel key="database-chart">
|
||||
<Panel.Content className="space-y-4">
|
||||
{/*
|
||||
// @ts-ignore */}
|
||||
<ChartHandler
|
||||
label="Execution time"
|
||||
defaultChartStyle="line"
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
attribute={'avg_execution_time'}
|
||||
provider="log-stats"
|
||||
// interval="1d"
|
||||
hideChartType
|
||||
customDateFormat={datetimeFormat}
|
||||
data={charts}
|
||||
format="ms"
|
||||
isLoading={!charts.data && !error ? true : false}
|
||||
highlightedValue={calculateHighlightedValue(data?.result, 'avg_execution_time')}
|
||||
// onBarClick={(v) => handleBarClick(v, '/rest')}
|
||||
/>
|
||||
</Panel.Content>
|
||||
</Panel>
|
||||
<Panel key="invocation-chart">
|
||||
<Panel.Content className="space-y-4">
|
||||
{/*
|
||||
{/*
|
||||
{/*
|
||||
// @ts-ignore */}
|
||||
<ChartHandler
|
||||
label="Invocations"
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
attribute={'count'}
|
||||
provider="log-stats"
|
||||
// interval="1d"
|
||||
hideChartType
|
||||
customDateFormat={datetimeFormat}
|
||||
data={charts}
|
||||
isLoading={!charts.data && !error ? true : false}
|
||||
highlightedValue={calculateHighlightedValue(data?.result, 'count', {
|
||||
sum: true,
|
||||
})}
|
||||
onBarClick={(v) => handleBarClick(v, '/auth')}
|
||||
/>
|
||||
</Panel.Content>
|
||||
</Panel>
|
||||
</div>
|
||||
{/* <Panel key="error-chart">
|
||||
<Panel.Content className="space-y-4">
|
||||
<ChartHandler
|
||||
defaultChartStyle="line"
|
||||
label="Error count"
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
attribute={'min_execution_time'}
|
||||
provider="log-stats"
|
||||
// interval="1d"
|
||||
hideChartType
|
||||
customDateFormat={datetimeFormat}
|
||||
data={charts}
|
||||
isLoading={!charts.data && !error ? true : false}
|
||||
highlightedValue={calculateHighlightedValue(data?.result, 'min_execution_time', {
|
||||
sum: true,
|
||||
})}
|
||||
onBarClick={(v) => handleBarClick(v, '/auth')}
|
||||
/>
|
||||
</Panel.Content>
|
||||
</Panel> */}
|
||||
</>
|
||||
)}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 md:gap-4 lg:grid-cols-2 lg:gap-8">
|
||||
<ReportWidget
|
||||
title="Execution time"
|
||||
tooltip="Average execution time of function invocations"
|
||||
data={chartData}
|
||||
isLoading={isChartLoading}
|
||||
renderer={(props) => (
|
||||
<AreaChart
|
||||
className="w-full"
|
||||
xAxisKey="timestamp"
|
||||
yAxisKey="avg_execution_time"
|
||||
data={props.data}
|
||||
format="ms"
|
||||
highlightedValue={meanBy(props.data, 'avg_execution_time')}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<ReportWidget
|
||||
title="Invocations"
|
||||
data={chartData}
|
||||
isLoading={isChartLoading}
|
||||
renderer={(props) => (
|
||||
<BarChart
|
||||
className="w-full"
|
||||
xAxisKey="timestamp"
|
||||
yAxisKey="count"
|
||||
data={props.data}
|
||||
highlightedValue={sumBy(props.data, 'count')}
|
||||
onBarClick={(v) => {
|
||||
router.push(
|
||||
`/project/${projectRef}/functions/${functionSlug}/invocations?its=${startDate.toISOString()}&ite=${
|
||||
v.timestamp
|
||||
}`
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { useStore, useFlag } from 'hooks'
|
||||
import { useStore } from 'hooks'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { API_URL, PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
|
||||
import { get } from 'lib/common/fetch'
|
||||
@@ -18,7 +18,6 @@ const BillingUpdateEnterprise: NextPageWithLayout = () => {
|
||||
|
||||
const projectRef = ui.selectedProject?.ref
|
||||
const orgSlug = ui.selectedOrganization?.slug
|
||||
const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
|
||||
|
||||
const [isLoadingPaymentMethods, setIsLoadingPaymentMethods] = useState(false)
|
||||
const [subscription, setSubscription] = useState<StripeSubscription>()
|
||||
@@ -29,9 +28,7 @@ const BillingUpdateEnterprise: NextPageWithLayout = () => {
|
||||
subscription && subscription.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE
|
||||
|
||||
useEffect(() => {
|
||||
if (projectUpdateDisabled) {
|
||||
router.push(`/project/${projectRef}/settings/billing/update`)
|
||||
} else if (projectRef) {
|
||||
if (projectRef) {
|
||||
getStripeProducts()
|
||||
getSubscription()
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react-lite'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { useStore, useFlag } from 'hooks'
|
||||
import { useStore } from 'hooks'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_URL, PRICING_TIER_PRODUCT_IDS, STRIPE_PRODUCT_IDS } from 'lib/constants'
|
||||
|
||||
@@ -16,8 +16,6 @@ const BillingUpdateFree: NextPageWithLayout = () => {
|
||||
const router = useRouter()
|
||||
const projectRef = ui.selectedProject?.ref
|
||||
|
||||
const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
|
||||
|
||||
const [products, setProducts] = useState<{ tiers: any[]; addons: any[] }>()
|
||||
const [subscription, setSubscription] = useState<StripeSubscription>()
|
||||
|
||||
@@ -25,9 +23,7 @@ const BillingUpdateFree: NextPageWithLayout = () => {
|
||||
subscription && subscription.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE
|
||||
|
||||
useEffect(() => {
|
||||
if (projectUpdateDisabled) {
|
||||
router.push(`/project/${projectRef}/settings/billing/update`)
|
||||
} else if (projectRef) {
|
||||
if (projectRef) {
|
||||
getStripeProducts()
|
||||
getSubscription()
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@ const BillingUpdate: NextPageWithLayout = () => {
|
||||
const [subscription, setSubscription] = useState<StripeSubscription>()
|
||||
const [selectedPlan, setSelectedPlan] = useState<any>()
|
||||
|
||||
const isEnterprise =
|
||||
subscription && subscription.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE
|
||||
const isEnterprise = subscription?.tier?.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE
|
||||
|
||||
useEffect(() => {
|
||||
if (projectRef) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { useStore, useFlag } from 'hooks'
|
||||
import { useStore } from 'hooks'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_URL, PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
|
||||
|
||||
@@ -21,8 +21,6 @@ const BillingUpdatePro: NextPageWithLayout = () => {
|
||||
const projectRef = ui.selectedProject?.ref
|
||||
const orgSlug = ui.selectedOrganization?.slug
|
||||
|
||||
const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
|
||||
|
||||
const [subscription, setSubscription] = useState<StripeSubscription>()
|
||||
const [products, setProducts] = useState<{ tiers: any[]; addons: SubscriptionAddon[] }>()
|
||||
const [paymentMethods, setPaymentMethods] = useState<PaymentMethod[]>()
|
||||
@@ -39,9 +37,7 @@ const BillingUpdatePro: NextPageWithLayout = () => {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (projectUpdateDisabled) {
|
||||
router.push(`/project/${projectRef}/settings/billing/update`)
|
||||
} else if (projectRef) {
|
||||
if (projectRef) {
|
||||
getStripeProducts()
|
||||
getSubscription()
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { useStore, useFlag } from 'hooks'
|
||||
import { useStore } from 'hooks'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_URL, PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
|
||||
|
||||
@@ -21,8 +21,6 @@ const BillingUpdateTeam: NextPageWithLayout = () => {
|
||||
const projectRef = ui.selectedProject?.ref
|
||||
const orgSlug = ui.selectedOrganization?.slug
|
||||
|
||||
const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
|
||||
|
||||
const [subscription, setSubscription] = useState<StripeSubscription>()
|
||||
const [products, setProducts] = useState<{ tiers: any[]; addons: SubscriptionAddon[] }>()
|
||||
const [paymentMethods, setPaymentMethods] = useState<PaymentMethod[]>()
|
||||
@@ -39,9 +37,7 @@ const BillingUpdateTeam: NextPageWithLayout = () => {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (projectUpdateDisabled) {
|
||||
router.push(`/project/${projectRef}/settings/billing/update`)
|
||||
} else if (projectRef) {
|
||||
if (projectRef) {
|
||||
getStripeProducts()
|
||||
getSubscription()
|
||||
}
|
||||
@@ -122,7 +118,6 @@ const BillingUpdateTeam: NextPageWithLayout = () => {
|
||||
currentSubscription={subscription}
|
||||
isLoadingPaymentMethods={isLoadingPaymentMethods}
|
||||
paymentMethods={paymentMethods || []}
|
||||
onSelectBack={() => router.push(`/project/${projectRef}/settings/billing/update`)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export default class DatabaseStore extends PostgresMetaInterface<any> {
|
||||
async getPoolingConfiguration(projectRef: string) {
|
||||
try {
|
||||
// Need to use project ref -> shouldn;t need to pass it in via argument
|
||||
const url = `${API_URL}/props/pooling/${projectRef}/config`
|
||||
const url = `${API_URL}/projects/${projectRef}/config/pgbouncer`
|
||||
const response = await get(url, { headers: this.headers })
|
||||
if (response.error) throw response.error
|
||||
return response
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import {
|
||||
ensureNoTimestampConflict,
|
||||
fillTimeseries,
|
||||
genChartQuery,
|
||||
genDefaultQuery,
|
||||
LogsTableName,
|
||||
SQL_FILTER_TEMPLATES,
|
||||
} from 'components/interfaces/Settings/Logs'
|
||||
import dayjs from 'dayjs'
|
||||
import { isEqual } from 'lodash'
|
||||
|
||||
describe.each(Object.values(LogsTableName))('%s', (table) => {
|
||||
const templates = SQL_FILTER_TEMPLATES[table]
|
||||
@@ -98,3 +100,57 @@ test.each([
|
||||
expect(result[0]).toEqual(expected[0])
|
||||
expect(result[1]).toEqual(expected[1])
|
||||
})
|
||||
|
||||
// test for log trunc filling
|
||||
test.each([
|
||||
{
|
||||
// truncate timestamp string from bigquery
|
||||
case: 'bq utc timestamp, truncated minutely',
|
||||
data: [
|
||||
{ timestamp: '2023-04-26T17:18:00', count: 123 },
|
||||
{ timestamp: '2023-04-26T17:20:00', count: 123 },
|
||||
],
|
||||
// expected
|
||||
len: 3,
|
||||
includes: [{ timestamp: '2023-04-26T17:19:00.000Z', count: 0 }],
|
||||
},
|
||||
// hourly truncation
|
||||
{
|
||||
case: 'bq timestamp truncated hourly, different options',
|
||||
data: [
|
||||
{ period: '2023-04-26T17:00:00.000Z', value: 123 },
|
||||
{ period: '2023-04-26T20:00:00.000Z', value: 123 },
|
||||
],
|
||||
// expected
|
||||
len: 4,
|
||||
defaultVal: 1,
|
||||
valKey: 'value',
|
||||
tsKey: 'period',
|
||||
includes: [
|
||||
{ period: '2023-04-26T18:00:00.000Z', value: 1 },
|
||||
{ period: '2023-04-26T19:00:00.000Z', value: 1 },
|
||||
],
|
||||
},
|
||||
// fill beyong data min/max
|
||||
{
|
||||
case: 'fill beyond min/max',
|
||||
data: [],
|
||||
len: 3,
|
||||
min: '2023-04-26T18:00:00.000Z',
|
||||
max: '2023-04-26T20:00:00.000Z',
|
||||
includes: [
|
||||
{ timestamp: '2023-04-26T18:00:00.000Z', count: 0 },
|
||||
{ timestamp: '2023-04-26T19:00:00.000Z', count: 0 },
|
||||
{ timestamp: '2023-04-26T20:00:00.000Z', count: 0 },
|
||||
],
|
||||
},
|
||||
])(
|
||||
'fillTimeseries : $case',
|
||||
({ data, len, includes, min, max, tsKey = 'timestamp', valKey = 'count', defaultVal = 0 }) => {
|
||||
const result = fillTimeseries(data, tsKey, valKey, defaultVal, min, max)
|
||||
expect(result.length).toEqual(len)
|
||||
for (const inc of includes) {
|
||||
expect(result.find((d) => isEqual(d, inc))).toBeTruthy()
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -347,7 +347,7 @@ test('log event chart hide', async () => {
|
||||
return { result: [] }
|
||||
})
|
||||
render(<LogsPreviewer projectRef="123" tableName={LogsTableName.EDGE} />)
|
||||
await screen.findByText(/Logs \/ Time/)
|
||||
await screen.findByText(/No data/)
|
||||
const toggle = await screen.findByText(/Chart/)
|
||||
userEvent.click(toggle)
|
||||
await expect(screen.findByText('Events')).rejects.toThrow()
|
||||
|
||||
+3
-3
@@ -18,9 +18,9 @@ interface NotificationMetadata {
|
||||
export interface ChartIntervals {
|
||||
key: 'minutely' | 'hourly' | 'daily' | '5min' | '15min' | '1hr' | '1day' | '7day'
|
||||
label: string
|
||||
startValue: 1 | 24 | 7
|
||||
startUnit: 'hour' | 'day'
|
||||
format: 'MMM D, h:mma' | 'MMM D, ha' | 'MMM D'
|
||||
startValue: number
|
||||
startUnit: 'min' | 'hour' | 'day'
|
||||
format?: 'MMM D, h:mma' | 'MMM D, ha' | 'MMM D'
|
||||
}
|
||||
|
||||
export interface VaultSecret {
|
||||
|
||||
Reference in new issue
Block a user