From 8812985ec43059bd56108e0f3c19b72429f08fc6 Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Wed, 14 Dec 2022 14:13:24 +0000 Subject: [PATCH 001/127] added in example description --- apps/docs/components/reference/RefFunctionSection.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/docs/components/reference/RefFunctionSection.tsx b/apps/docs/components/reference/RefFunctionSection.tsx index a31c52af992..18c753a52ef 100644 --- a/apps/docs/components/reference/RefFunctionSection.tsx +++ b/apps/docs/components/reference/RefFunctionSection.tsx @@ -203,6 +203,16 @@ const RefFunctionSection: React.FC = (props) => { .replace('js', '') .replace('ts', ''))} + + {example.description && ( +
+
Example Notes
+ + {example.description} + +
+ )} + {response && ( Date: Wed, 14 Dec 2022 19:58:30 +0000 Subject: [PATCH 002/127] supabase js v2 yml removed jsx from descriptions --- spec/supabase_js_v2.yml | 3081 +-------------------------------------- 1 file changed, 11 insertions(+), 3070 deletions(-) diff --git a/spec/supabase_js_v2.yml b/spec/supabase_js_v2.yml index 8899420a2ce..0a3a70f5483 100644 --- a/spec/supabase_js_v2.yml +++ b/spec/supabase_js_v2.yml @@ -1049,96 +1049,7 @@ functions: If you're in a situation where your tables are **NOT** directly related, but instead are joined by a _join table_, you can still use the `select()` method to query the related data. The join table needs - to have the foreign keys as part of its composite primary key. - - - - - ```sql - create table - users ( - id int8 primary key, - name text - ); - create table - teams ( - id int8 primary key, - name text - ); - -- join table - create table - users_teams ( - user_id int8 not null references users, - team_id int8 not null references teams, - -- both foreign keys must be part of a composite primary key - primary key (user_id, team_id) - ); - - insert into - users (id, name) - values - (1, 'Kiran'), - (2, 'Evan'); - insert into - teams (id, name) - values - (1, 'Green'), - (2, 'Blue'); - insert into - users_teams (user_id, team_id) - values - (1, 1), - (1, 2), - (2, 2); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .select(` - name, - teams ( - name - ) - `) - ``` - - - - - ```json - { - "data": [ - { - "name": "Kiran", - "teams": [ - { - "name": "Green" - }, - { - "name": "Blue" - } - ] - }, - { - "name": "Evan", - "teams": [ - { - "name": "Blue" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - + to have the foreign keys as part of its composite primary key. hideCodeBlock: true - id: query-the-same-foreign-table-multiple-times name: Query the same foreign table multiple times @@ -1198,69 +1109,6 @@ functions: If you need to query the same foreign table twice, use the name of the joined column to identify which join to use. You can also give each column an alias. - - - - - ```sql - create table - users (id int8 primary key, name text); - - create table - messages ( - sender_id int8 not null references users, - receiver_id int8 not null references users, - content text - ); - - insert into - users (id, name) - values - (1, 'Kiran'), - (2, 'Evan'); - - insert into - messages (sender_id, receiver_id, content) - values - (1, 2, '👋'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('messages') - .select(` - content, - from:sender_id(name), - to:receiver_id(name) - `) - ``` - - - - - ```json - { - "data": [ - { - "content": "👋", - "from": { - "name": "Kiran" - }, - "to": { - "name": "Evan" - } - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: filtering-through-foreign-tables name: Filtering through foreign tables @@ -1314,90 +1162,7 @@ functions: description: | If the filter on a foreign table's column is not satisfied, the foreign table returns `[]` or `null` but the parent table is not filtered out. - - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'Germany'), - (2, 'Indonesia'); - insert into - cities (id, country_id, name) - values - (1, 2, 'Bali'), - (2, 1, 'Munich'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('cities') - .select('name, countries(*)') - .eq('countries.name', 'Estonia') - ``` - - - - - ```json - { - "data": [ - { - "name": "Bali", - "countries": null - }, - { - "name": "Munich", - "countries": null - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - - If you want to filter out the parent table rows, use the `!inner` hint: - - - - - ```ts - const { data, error } = await supabase - .from('cities') - .select('name, countries!inner(*)') - .eq('countries.name', 'Estonia') - ``` - - - - - ```json - { - "data": [], - "status": 200, - "statusText": "OK" - } - ``` - - - + If you want to filter out the parent table rows, use the `!inner` hint hideCodeBlock: true - id: querying-with-count-option name: Querying with count option @@ -1430,46 +1195,7 @@ functions: ``` description: | You can get the number of rows by using the - [count](/docs/reference/javascript/select#parameters) option. For - example, to get the table count without returning all rows: - - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { count, error } = await supabase - .from('countries') - .select('*', { count: 'exact', head: true }) - ``` - - - - - ```json - { - "count": 3, - "status": 200, - "statusText": "OK" - } - ``` - - - + [count](/docs/reference/javascript/select#parameters) option. hideCodeBlock: true - id: querying-json-data name: Querying JSON data @@ -1516,55 +1242,6 @@ functions: [JSON](/docs/guides/database/json) columns. Postgres offers some [operators](/docs/guides/database/json#query-the-jsonb-data) for querying JSON data. - - - - - ```sql - create table - users ( - id int8 primary key, - name text, - address jsonb - ); - - insert into - users (id, name, address) - values - (1, 'Avdotya', '{"city":"Saint Petersburg"}'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .select(` - id, name, - address->city - `) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Avdotya", - "city": "Saint Petersburg" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: insert @@ -1598,63 +1275,6 @@ functions: "statusText": "Created" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - ``` - - - - - ```ts - const { error } = await supabase - .from('countries') - .insert({ id: 1, name: 'Denmark' }) - ``` - - - - - ```json - { - "status": 201, - "statusText": "Created" - } - ``` - - - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Denmark" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: create-a-record-and-return-it @@ -1685,43 +1305,6 @@ functions: "statusText": "Created" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .insert({ id: 1, name: 'Denmark' }) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Denmark" - } - ], - "status": 201, - "statusText": "Created" - } - ``` - - - hideCodeBlock: true - id: bulk-create name: Bulk create @@ -1756,68 +1339,6 @@ functions: description: | A bulk create operation is handled in a single transaction. If any of the inserts fail, none of the rows are inserted. - - - - - ```sql - create table - countries (id int8 primary key, name text); - ``` - - - - - ```ts - const { error } = await supabase - .from('countries') - .insert([ - { id: 1, name: 'Nepal' }, - { id: 1, name: 'Vietnam' }, - ]) - ``` - - - - - ```json - { - "error": { - "code": "23505", - "details": "Key (id)=(1) already exists.", - "hint": null, - "message": "duplicate key value violates unique constraint \"countries_pkey\"" - }, - "status": 409, - "statusText": "Conflict" - } - ``` - - - - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - ``` - - - - - ```json - { - "data": [], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: update @@ -1853,70 +1374,6 @@ functions: "statusText": "No Content" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Taiwan'); - ``` - - - - - ```ts - const { error } = await supabase - .from('countries') - .update({ name: 'Australia' }) - .eq('id', 1) - ``` - - - - - ```json - { - "status": 204, - "statusText": "No Content" - } - ``` - - - - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Australia" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: update-a-record-and-return-it @@ -1953,49 +1410,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Taiwan'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .update({ name: 'Australia' }) - .eq('id', 1) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Australia" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: updating-json-data name: Updating JSON data @@ -2047,65 +1461,7 @@ functions: description: | Postgres offers some [operators](/docs/guides/database/json#query-the-jsonb-data) for - working with JSON data. - - - - - ```sql - create table - users ( - id int8 primary key, - name text, - address jsonb - ); - - insert into - users (id, name, address) - values - (1, 'Michael', '{ "postcode": 90210 }'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .update({ - address: { - street: 'Melrose Place', - postcode: 90210 - } - }) - .eq('address->postcode', 90210) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Michael", - "address": { - "street": "Melrose Place", - "postcode": 90210 - } - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - - Currently, it is only possible to update the entire JSON document. + working with JSON data. Currently, it is only possible to update the entire JSON document. hideCodeBlock: true - id: upsert title: 'Upsert data: upsert()' @@ -2146,48 +1502,6 @@ functions: "statusText": "Created" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .upsert({ id: 1, name: 'Albania' }) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Albania" - } - ], - "status": 201, - "statusText": "Created" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: bulk-upsert-your-data @@ -2230,55 +1544,6 @@ functions: "statusText": "Created" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .upsert([ - { id: 1, name: 'Albania' }, - { id: 2, name: 'Algeria' }, - ]) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Albania" - }, - { - "id": 2, - "name": "Algeria" - } - ], - "status": 201, - "statusText": "Created" - } - ``` - - - hideCodeBlock: true - id: upserting-into-tables-with-constraints name: Upserting into tables with constraints @@ -2322,90 +1587,8 @@ functions: (primary key) column to determine conflicts. If there is no existing row with the same `id`, `upsert()` inserts a new row, which will fail in this case as there is already a row with `handle` `"saoirse"`. - - - - - ```sql - create table - users ( - id int8 generated by default as identity primary key, - handle text not null unique, - display_name text - ); - - insert into - users (id, handle, display_name) - values - (1, 'saoirse', null); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .upsert({ id: 42, handle: 'saoirse', display_name: 'Saoirse' }) - .select() - ``` - - - - - ```json - { - "error": { - "code": "23505", - "details": "Key (handle)=(saoirse) already exists.", - "hint": null, - "message": "duplicate key value violates unique constraint \"users_handle_key\"" - }, - "status": 409, - "statusText": "Conflict" - } - ``` - - - - Using the `onConflict` option, you can instruct `upsert()` to use - another column with a unique constraint to determine conflicts: - - - - - ```ts - await supabase - .from('users') - .upsert( - { id: 42, handle: 'saoirse', display_name: 'Saoirse' }, - { onConflict: 'handle' }, - ) - const { data, error } = await supabase - .from('users') - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 42, - "handle": "saoirse", - "display_name": "Saoirse" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - + another column with a unique constraint to determine conflicts. hideCodeBlock: true - id: delete @@ -2446,65 +1629,6 @@ functions: "statusText": "No Content" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Spain'); - ``` - - - - - ```ts - const { error } = await supabase - .from('countries') - .delete() - .eq('id', 1) - ``` - - - - - ```json - { - "status": 204, - "statusText": "No Content" - } - ``` - - - - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - ``` - - - - - ```json - { - "data": [], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true @@ -2542,36 +1666,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create function hello_world() returns text as $$ - select 'Hello world'; - $$ language sql; - ``` - - - - - ```ts - const { data, error } = await supabase.rpc('hello_world') - ``` - - - - - ```json - { - "data": "Hello world", - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: call-a-postgres-function-with-arguments @@ -2595,36 +1689,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create function echo(say text) returns text as $$ - select say; - $$ language sql; - ``` - - - - - ```ts - const { data, error } = await supabase.rpc('echo', { say: '👋' }) - ``` - - - - - ```json - { - "data": "👋", - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: bulk-processing name: Bulk processing @@ -2652,41 +1716,7 @@ functions: } ``` description: | - You can process large payloads by passing in an array as an argument: - - - - - ```sql - create function add_one_each(arr int[]) returns int[] as $$ - select array_agg(n + 1) from unnest(arr) as n; - $$ language sql; - ``` - - - - - ```ts - const { data, error } = await supabase.rpc('add_one_each', { arr: [1, 2, 3] }) - ``` - - - - - ```json - { - "data": [ - 2, - 3, - 4 - ], - "status": 200, - "statusText": "OK" - } - ``` - - - + You can process large payloads by passing in an array as an argument. hideCodeBlock: true - id: call-a-postgres-function-with-filters name: Call a Postgres function with filters @@ -2728,51 +1758,6 @@ functions: Postgres functions that return tables can also be combined with [Filters](/docs/reference/javascript/using-filters) and [Modifiers](/docs/reference/javascript/using-modifiers). - - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'France'), - (2, 'United Kingdom'); - - create function list_stored_countries() returns setof countries as $$ - select * from countries; - $$ language sql; - ``` - - - - - ```ts - const { data, error } = await supabase - .rpc('list_stored_countries') - .eq('id', 1) - .single() - ``` - - - - - ```json - { - "data": { - "id": 1, - "name": "France" - }, - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: using-filters @@ -2900,56 +1885,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - users ( - id int8 primary key, - name text, - address jsonb - ); - - insert into - users (id, name, address) - values - (1, 'Michael', '{ "postcode": 90210 }'), - (2, 'Jane', null); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .select() - .eq('address->postcode', 90210) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Michael", - "address": { - "postcode": 90210 - } - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - id: filter-foreign-tables name: Filter Foreign Tables code: | @@ -3006,70 +1941,7 @@ functions: ``` description: | You can filter on foreign tables in your `select()` query using dot - notation: - - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'Germany'), - (2, 'Indonesia'); - insert into - cities (id, country_id, name) - values - (1, 2, 'Bali'), - (2, 1, 'Munich'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities!inner ( - name - ) - `) - .eq('cities.name', 'Bali') - ``` - - - - - ```json - { - "data": [ - { - "name": "Indonesia", - "cities": [ - { - "name": "Bali" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - + notation. ######## TODO CHECK FILTER YAML ERRORS ######## - id: eq title: eq() @@ -3110,50 +1982,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .eq('name', 'Albania') - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: neq @@ -3199,54 +2027,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .neq('name', 'Albania') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Afghanistan" - }, - { - "id": 3, - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: gt @@ -3288,50 +2068,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .gt('id', 2) - ``` - - - - - ```json - { - "data": [ - { - "id": 3, - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: gte @@ -3377,54 +2113,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .gte('id', 2) - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": "Albania" - }, - { - "id": 3, - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: lt @@ -3466,50 +2154,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .lt('id', 2) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Afghanistan" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: lte @@ -3555,54 +2199,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .lte('id', 2) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Afghanistan" - }, - { - "id": 2, - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: like @@ -3633,50 +2229,6 @@ functions: ``` response: | - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .like('name', '%Alba%') - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: ilike @@ -3718,50 +2270,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .like('name', '%alba%') - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: is @@ -3803,81 +2311,10 @@ functions: } ``` description: | - Using the `eq()` filter doesn't work when filtering for `null`: + Using the `eq()` filter doesn't work when filtering for `null`. - - + Instead, you need to use `is()`. - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'null'), - (2, null); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .eq('name', null) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "null" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - - Instead, you need to use `is()`: - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .is('name', null) - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": null - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: in @@ -3923,54 +2360,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .in('name', ['Albania', 'Algeria']) - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": "Albania" - }, - { - "id": 3, - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: contains @@ -4014,52 +2403,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - issues ( - id int8 primary key, - title text, - tags text[] - ); - - insert into - issues (id, title, tags) - values - (1, 'Cache invalidation is not working', array['is:open', 'severity:high', 'priority:low']), - (2, 'Use better names', array['is:open', 'severity:low', 'priority:medium']); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('issues') - .select('title') - .contains('tags', ['is:open', 'severity:high']) - ``` - - - - - ```json - { - "data": [ - { - "title": "Cache invalidation is not working" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: on-range-columns @@ -4106,54 +2449,6 @@ functions: types](https://www.postgresql.org/docs/current/rangetypes.html). You can filter on range columns using the string representation of range values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .contains('during', '[2000-01-01 13:00, 2000-01-01 13:30)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: on-jsonb-columns name: On `jsonb` columns @@ -4192,52 +2487,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - users ( - id int8 primary key, - name text, - address jsonb - ); - - insert into - users (id, name, address) - values - (1, 'Michael', '{ "postcode": 90210, "street": "Melrose Place" }'), - (2, 'Jane', '{}'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .select('name') - .contains('address', { postcode: 90210 }) - ``` - - - - - ```json - { - "data": [ - { - "name": "Michael" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: contained-by @@ -4281,52 +2530,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - classes ( - id int8 primary key, - name text, - days text[] - ); - - insert into - classes (id, name, days) - values - (1, 'Chemistry', array['monday', 'friday']), - (2, 'History', array['monday', 'wednesday', 'thursday']); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('classes') - .select('name') - .containedBy('days', ['monday', 'tuesday', 'wednesday', 'friday']) - ``` - - - - - ```json - { - "data": [ - { - "name": "Chemistry" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: on-range-columns @@ -4373,54 +2576,6 @@ functions: types](https://www.postgresql.org/docs/current/rangetypes.html). You can filter on range columns using the string representation of range values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .containedBy('during', '[2000-01-01 00:00, 2000-01-01 23:59)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: on-jsonb-columns name: On `jsonb` columns @@ -4459,52 +2614,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - users ( - id int8 primary key, - name text, - address jsonb - ); - - insert into - users (id, name, address) - values - (1, 'Michael', '{ "postcode": 90210, "street": "Melrose Place" }'), - (2, 'Jane', '{}'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .select('name') - .containedBy('address', {}) - ``` - - - - - ```json - { - "data": [ - { - "name": "Jane" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: range-gt @@ -4555,54 +2664,6 @@ functions: types](https://www.postgresql.org/docs/current/rangetypes.html). You can filter on range columns using the string representation of range values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeGt('during', '[2000-01-02 08:00, 2000-01-02 09:00)') - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "room_name": "Topaz", - "during": "[\"2000-01-02 09:00:00\",\"2000-01-02 10:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true @@ -4654,54 +2715,6 @@ functions: types](https://www.postgresql.org/docs/current/rangetypes.html). You can filter on range columns using the string representation of range values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeGte('during', '[2000-01-02 08:30, 2000-01-02 09:30)') - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "room_name": "Topaz", - "during": "[\"2000-01-02 09:00:00\",\"2000-01-02 10:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true @@ -4753,54 +2766,6 @@ functions: types](https://www.postgresql.org/docs/current/rangetypes.html). You can filter on range columns using the string representation of range values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeLt('during', '[2000-01-01 15:00, 2000-01-01 16:00)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: range-lte @@ -4851,54 +2816,6 @@ functions: types](https://www.postgresql.org/docs/current/rangetypes.html). You can filter on range columns using the string representation of range values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeLte('during', '[2000-01-01 14:00, 2000-01-01 16:00)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true @@ -4950,54 +2867,6 @@ functions: types](https://www.postgresql.org/docs/current/rangetypes.html). You can filter on range columns using the string representation of range values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeAdjacent('during', '[2000-01-01 12:00, 2000-01-01 13:00)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true @@ -5042,52 +2911,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - issues ( - id int8 primary key, - title text, - tags text[] - ); - - insert into - issues (id, title, tags) - values - (1, 'Cache invalidation is not working', array['is:open', 'severity:high', 'priority:low']), - (2, 'Use better names', array['is:open', 'severity:low', 'priority:medium']); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('issues') - .select('title') - .overlaps('tags', ['is:closed', 'severity:high']) - ``` - - - - - ```json - { - "data": [ - { - "title": "Cache invalidation is not working" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: on-range-columns @@ -5134,54 +2957,6 @@ functions: types](https://www.postgresql.org/docs/current/rangetypes.html). You can filter on range columns using the string representation of range values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .overlaps('during', '[2000-01-01 12:45, 2000-01-01 13:15)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true # TODO: schema & result - id: text-search @@ -5288,49 +3063,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .match({ id: 2, name: 'Albania' }) - ``` - - - - - ```json - { - "data": [ - { - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: not @@ -5378,49 +3110,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'null'), - (2, null); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .not('name', 'is', null) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "null" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: or @@ -5471,52 +3160,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .or('id.eq.2,name.eq.Algeria') - ``` - - - - - ```json - { - "data": [ - { - "name": "Albania" - }, - { - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: use-or-with-and @@ -5553,50 +3196,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .or('id.gt.3,and(id.eq.1,name.eq.Afghanistan)') - ``` - - - - - - ```json - { - "data": [ - { - "name": "Afghanistan" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: use-or-on-foreign-tables name: Use `or` on foreign tables @@ -5652,69 +3251,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'Germany'), - (2, 'Indonesia'); - insert into - cities (id, country_id, name) - values - (1, 2, 'Bali'), - (2, 1, 'Munich'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities!inner ( - name - ) - `) - .or('country_id.eq.1,name.eq.Beijing', { foreignTable: 'cities' }) - ``` - - - - - ```json - { - "data": [ - { - "name": "Germany", - "cities": [ - { - "name": "Munich" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: filter @@ -5763,50 +3299,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .filter('name', 'in', '("Algeria","Japan")') - ``` - - - - - ```json - { - "data": [ - { - "id": 3, - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: on-a-foreign-table @@ -5863,69 +3355,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'Germany'), - (2, 'Indonesia'); - insert into - cities (id, country_id, name) - values - (1, 2, 'Bali'), - (2, 1, 'Munich'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities!inner ( - name - ) - `) - .filter('cities.name', 'eq', 'Bali') - ``` - - - - - ```json - { - "data": [ - { - "name": "Indonesia", - "cities": [ - { - "name": "Bali" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: using-modifiers title: Using Modifiers @@ -5976,70 +3405,6 @@ functions: "statusText": "Created" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .upsert({ id: 1, name: 'Albania' }) - ``` - - - - - ```json - { - "status": 201, - "statusText": "Created" - } - ``` - - - - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .upsert({ id: 1, name: 'Algeria' }) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Algeria" - } - ], - "status": 201, - "statusText": "Created" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: order @@ -6089,58 +3454,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .order('name', { ascending: false }) - ``` - - - - - ```json - { - "data": [ - { - "id": 3, - "name": "Algeria" - }, - { - "id": 2, - "name": "Albania" - }, - { - "id": 1, - "name": "Afghanistan" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: on-a-foreign-table @@ -6207,76 +3520,6 @@ functions: description: | Ordering on foreign tables doesn't affect the ordering of the parent table. - - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'United States'), - (2, 'Vanuatu'); - insert into - cities (id, country_id, name) - values - (1, 1, 'Atlanta'), - (2, 1, 'New York City'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities ( - name - ) - `) - .order('name', { foreignTable: 'cities', ascending: false }) - ``` - - - - - ```json - { - "data": [ - { - "name": "United States", - "cities": [ - { - "name": "New York City" - }, - { - "name": "Atlanta" - } - ] - }, - { - "name": "Vanuatu", - "cities": [] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: limit @@ -6317,49 +3560,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .limit(1) - ``` - - - - - ```json - { - "data": [ - { - "name": "Afghanistan" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: on-a-foreign-table @@ -6415,68 +3615,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'United States'); - insert into - cities (id, country_id, name) - values - (1, 1, 'Atlanta'), - (2, 1, 'New York City'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities ( - name - ) - `) - .limit(1, { foreignTable: 'cities' }) - ``` - - - - - ```json - { - "data": [ - { - "name": "United States", - "cities": [ - { - "name": "Atlanta" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: range title: range() @@ -6519,52 +3657,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .range(0, 1) - ``` - - - - - ```json - { - "data": [ - { - "name": "Afghanistan" - }, - { - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: abort-signal @@ -6606,38 +3698,8 @@ functions: } ``` description: | - You can use an [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to abort requests. Note that `status` and `statusText` don't mean anything for aborted requests as the request wasn't fulfilled. - - - - - ```ts - const ac = new AbortController() - ac.abort() - const { data, error } = await supabase - .from('very_big_table') - .select() - .abortSignal(ac.signal) - ``` - - - - - ```json - { - "error": { - "message": "FetchError: The user aborted a request.", - "details": "", - "hint": "", - "code": "" - }, - "status": 400, - "statusText": "Bad Request" - } - ``` - - - + You can use an [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to abort requests. + Note that `status` and `statusText` don't mean anything for aborted requests as the request wasn't fulfilled. hideCodeBlock: true isSpotlight: true - id: single @@ -6677,48 +3739,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .limit(1) - .single() - ``` - - - - - ```json - { - "data": { - "name": "Afghanistan" - }, - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: maybe-single @@ -6755,45 +3775,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .eq('name', 'Singapore') - .maybeSingle() - ``` - - - - - ```json - { - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: csv @@ -6832,45 +3813,6 @@ functions: ``` description: | By default, the data is returned in JSON format, but can also be returned as Comma Separated Values. - - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .csv() - ``` - - - - - ```json - { - "data": "id,name\n1,Afghanistan\n2,Albania\n3,Algeria", - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true @@ -6969,7 +3911,6 @@ functions: examples: - id: basic-invocation name: Basic invocation - description: isSpotlight: true code: | ```js From 36f4b192385396cdf3437066919d4aef92672983 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 21 Dec 2022 16:26:16 -0330 Subject: [PATCH 003/127] Rename component so it matches filename --- apps/docs/layouts/SiteLayout.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx index 9c73280091d..ac46ec931fc 100644 --- a/apps/docs/layouts/SiteLayout.tsx +++ b/apps/docs/layouts/SiteLayout.tsx @@ -170,7 +170,7 @@ const SiteLayout = ({ children }) => {
- +
@@ -183,7 +183,6 @@ const SiteLayout = ({ children }) => { ].join(' ')} >
- {/* */}
@@ -269,7 +268,7 @@ const SiteLayout = ({ children }) => { : 'left-[-40px] h-0 opacity-0 invisible', ].join(' ')} > - +
From 5aa0f828ef0d3cd3b0687f510acbc5c784b47316 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 21 Dec 2022 16:26:54 -0330 Subject: [PATCH 004/127] Pass a menu context to avoid dupe key warnings --- .../NavigationMenu/NavigationMenu.tsx | 26 +++++++++---------- .../NavigationMenuGuideList.tsx | 25 +++++++++--------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index 96bb2416bcf..c579eb68707 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -62,7 +62,7 @@ export type RefKeyOptions = | 'self-hosting-storage' | 'self-hosting-realtime' -const SideNav = () => { +const NavigationMenu = ({ context }) => { const router = useRouter() let version = '' @@ -160,17 +160,17 @@ const SideNav = () => {
{/* // main menu */} - - - - - - - - - - - + + + + + + + + + + + {/* // Client Libs */} { ) } -export default memo(SideNav) +export default memo(NavigationMenu) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx index 6b110bd5687..d5f97f50e6c 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx @@ -3,7 +3,7 @@ import { useTheme } from 'common/Providers' import Image from 'next/image' import Link from 'next/link' import { useRouter } from 'next/router' -import React from 'react' +import React, { Fragment } from 'react' import { IconChevronLeft } from '~/../../packages/ui' import { useMenuLevelId } from '~/hooks/useMenuState' import * as NavItems from './NavigationMenu.constants' @@ -11,8 +11,9 @@ import * as NavItems from './NavigationMenu.constants' interface Props { id: string setMenuLevelId?: any + context: 'side' | 'mobile' } -const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId }) => { +const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId, context }) => { const router = useRouter() const { isDarkMode } = useTheme() @@ -30,7 +31,7 @@ const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId }) => { return ( = ({ id, setMenuLevelId }) => { // console.log('1st type of link?', x.items && x.items.length > 0) // console.log() return ( -
+
{x.items && x.items.length > 0 ? (
{x.items.map((subItem, subItemIndex) => { @@ -114,7 +115,7 @@ const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId }) => { } return ( - <> + {subItemIndex === 0 && ( <>
@@ -123,8 +124,8 @@ const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId }) => { )} - -
  • + +
  • = ({ id, setMenuLevelId }) => { {subItem.items.map((subSubItem, subSubItemIndex) => { return ( -
  • +
  • = ({ id, setMenuLevelId }) => { )} - + ) })}
  • ) : ( - <> +
  • = ({ id, setMenuLevelId }) => {
  • - + )}
    ) @@ -210,7 +211,7 @@ const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId }) => { )} {menu.extras?.map((x) => { return ( -
    +
  • From 82d7eaed40952777d258cb4636aadcf6743f2db1 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 21 Dec 2022 17:08:06 -0330 Subject: [PATCH 005/127] More cleanup and key passing --- .../NavigationMenuGuideList.tsx | 19 ++++- .../NavigationMenu/NavigationMenuRefList.tsx | 79 +++++++------------ .../reference/RefFunctionSection.tsx | 21 ++--- 3 files changed, 55 insertions(+), 64 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx index d5f97f50e6c..32bacbe94b8 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx @@ -191,7 +191,15 @@ const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId, context : 'hover:text-brand-900 text-scale-1000', ].join(' ')} > - {x.icon && } + {x.icon && ( + {x.icon} + )} + {x.name} @@ -215,7 +223,14 @@ const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId, context
  • - {x.icon && } + {x.icon && ( + {x.icon} + )} {x.name} diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefList.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefList.tsx index e2db35f487a..377973897a7 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefList.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefList.tsx @@ -8,11 +8,11 @@ import { find } from 'lodash' import Image from 'next/image' import { useTheme } from 'common/Providers' -// import apiCommonSections from '~/../../spec/common-client-libs-sections.json' - import RevVersionDropdown from '~/components/RefVersionDropdown' import { useMenuActiveRefId, useMenuLevelId } from '~/hooks/useMenuState' import { RefIdOptions, RefKeyOptions } from './NavigationMenu' +import { isFuncNotInLibraryOrVersion } from './NavigationMenu.utils' +import { Fragment } from 'react' const FunctionLink = ({ title, @@ -140,9 +140,6 @@ const NavigationMenuRefList: React.FC = ({ }).items.map((x) => x.id) : [] - // console.log(filterIds) - // console.log(modifierIds) - const level = useMenuLevelId() return ( @@ -192,24 +189,8 @@ const NavigationMenuRefList: React.FC = ({
      {sections.map((fn: any, fnIndex) => { - // - // check if the link is allowed to be displayed - function isFuncNotInLibraryOrVersion(id, type) { - if (id && allowedKeys && !allowedKeys.includes(id) && type !== 'markdown') { - /* - * Remove this menu link from side bar, as it does not exist for either - * this language, or for this lib version - * - * */ - return true - } else { - return false - } - } - - // run allow check - if (isFuncNotInLibraryOrVersion(fn.id, fn.type)) { - return <> + if (isFuncNotInLibraryOrVersion(fn.id, fn.type, allowedKeys)) { + return } const RenderLink = (props) => { @@ -261,7 +242,7 @@ const NavigationMenuRefList: React.FC = ({ props.items .filter((item) => allowedKeys.includes(item.id)) .map((item) => { - return + return })} @@ -269,34 +250,28 @@ const NavigationMenuRefList: React.FC = ({ ) } - // handle subtitles with subitems - if (!fn.id) { - return ( - <> - - - {fn.items && - fn.items - //.filter((item) => item.libs && item.libs.includes(lib)) - .map((item) => { - // run allow check - if (isFuncNotInLibraryOrVersion(item.id, item.type)) return <> - return - })} - - ) - } else { - // handle normal links - return ( - <> - - {fn.items && - fn.items - //.filter((item) => item.libs.includes(lib)) - .map((item) => )} - - ) - } + return fn.id ? ( + + + {fn.items && + fn.items.map((item) => ( + + ))} + + ) : ( + + + + {fn.items && + fn.items.map((item, i) => + isFuncNotInLibraryOrVersion(item.id, item.type, allowedKeys) ? ( + + ) : ( + + ) + )} + + ) })}
  • diff --git a/apps/docs/components/reference/RefFunctionSection.tsx b/apps/docs/components/reference/RefFunctionSection.tsx index a31c52af992..32002c389bc 100644 --- a/apps/docs/components/reference/RefFunctionSection.tsx +++ b/apps/docs/components/reference/RefFunctionSection.tsx @@ -9,6 +9,7 @@ import RefSubLayout from '~/layouts/ref/RefSubLayout' import { extractTsDocNode, generateParameters } from '~/lib/refGenerator/helpers' import RefDetailCollapse from '~/components/reference/RefDetailCollapse' +import { Fragment } from 'react' interface ICommonFunc { id: string @@ -32,11 +33,6 @@ const RefFunctionSection: React.FC = (props) => { // gracefully return nothing if function does not exist if (!item) return <> - if (item && !item['$ref']) { - //console.warn('🚩 issue with $ref in:', item.id) - } - - // console.log(item) const hasTsRef = item['$ref'] || null const tsDefinition = @@ -89,25 +85,29 @@ const RefFunctionSection: React.FC = (props) => { }) const paramItem = overide?.length > 0 ? overide[0] : param - return ( - + {paramItem.subContent && (
    {param.subContent.map((param) => { return ( - <> + {param.subContent && ( {param.subContent.map((param) => { - return + return ( + + ) })} )} - + ) })} @@ -150,6 +150,7 @@ const RefFunctionSection: React.FC = (props) => { return ( From eba225523acfec46be9cafdfdf6d8be8f546a06a Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 21 Dec 2022 17:19:08 -0330 Subject: [PATCH 006/127] More keys --- .../NavigationMenu/NavigationMenu.utils.ts | 6 ++++++ .../components/reference/RefSectionHandler.tsx | 17 +++++++++++++---- apps/docs/layouts/ref/RefSubLayout.tsx | 6 ++---- 3 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 apps/docs/components/Navigation/NavigationMenu/NavigationMenu.utils.ts diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.utils.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.utils.ts new file mode 100644 index 00000000000..ad3d100450f --- /dev/null +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.utils.ts @@ -0,0 +1,6 @@ +// check if the link is allowed to be displayed +export function isFuncNotInLibraryOrVersion(id, type, allowedKeys) { + if (id && allowedKeys && !allowedKeys.includes(id) && type !== 'markdown') { + return true + } +} diff --git a/apps/docs/components/reference/RefSectionHandler.tsx b/apps/docs/components/reference/RefSectionHandler.tsx index 75476287461..d54e5d616bb 100644 --- a/apps/docs/components/reference/RefSectionHandler.tsx +++ b/apps/docs/components/reference/RefSectionHandler.tsx @@ -36,16 +36,17 @@ const RefSectionHandler = (props) => { return ( - {props.sections.map((x) => { + {props.sections.map((x, i) => { switch (x.type) { case 'markdown': const markdownData = props.pageProps.docs.find((doc) => doc.id === x.id) - return + return break case 'function': return ( { /> ) case 'cli-command': - return + return break case 'operation': - return + return ( + + ) default: return ( & RefSubLayoutSubComponents = (props) = } const Section: FC = (props) => { - // console.log({ props }) return (
    = (props) => { } const EducationSection: FC = ({ icon, hideTitle = false, ...props }) => { - // console.log({ props }) return (
    {!hideTitle && } From aacfc1dee63ce07c3665775f380c166629561d97 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 21 Dec 2022 21:12:04 -0330 Subject: [PATCH 007/127] Fix ReactMarkdown expects a string error --- .../reference/RefFunctionSection.tsx | 6 +--- spec/supabase_js_v2.yml | 36 +++++++++---------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/apps/docs/components/reference/RefFunctionSection.tsx b/apps/docs/components/reference/RefFunctionSection.tsx index 32002c389bc..a796a7b4aa9 100644 --- a/apps/docs/components/reference/RefFunctionSection.tsx +++ b/apps/docs/components/reference/RefFunctionSection.tsx @@ -60,11 +60,7 @@ const RefFunctionSection: React.FC = (props) => { {item.description}
    )} - {/* {functionMarkdownContent && ( -
    - -
    - )} */} + {item.notes && (
    {item.notes} diff --git a/spec/supabase_js_v2.yml b/spec/supabase_js_v2.yml index 309fe559544..237b0a3b729 100644 --- a/spec/supabase_js_v2.yml +++ b/spec/supabase_js_v2.yml @@ -401,7 +401,7 @@ functions: isSpotlight: true code: | ```js - const { data, error } = supabase.auth.setSession({ + const { data, error } = supabase.auth.setSession({ access_token, refresh_token ) @@ -433,7 +433,7 @@ functions: $ref: '@supabase/gotrue-js.GoTrueClient.onAuthStateChange' notes: | - Types of auth events: `SIGNED_IN`, `SIGNED_OUT`, `TOKEN_REFRESHED`, `USER_UPDATED`, `PASSWORD_RECOVERY` - - Currently, `onAuthStateChange()` does not work across tabs. + - Currently, `onAuthStateChange()` does not work across tabs. For instance, in the case of a password reset flow, the original tab which requested for the password reset link will not receive the `SIGNED_IN` and `PASSWORD_RECOVERY` event when the user clicks on the link. examples: @@ -453,7 +453,7 @@ functions: supabase.auth.onAuthStateChange((event, session) => { if (event == 'PASSWORD_RECOVERY') { console.log('PASSWORD_RECOVERY', session) - + // show screen to update user's password showPasswordResetScreen(true) } @@ -509,8 +509,8 @@ functions: isSpotlight: true code: | ```js - const { data, error } = await supabase.auth.mfa.enroll({ - factorType: 'totp' + const { data, error } = await supabase.auth.mfa.enroll({ + factorType: 'totp' }) // Use the id to create a challenge. @@ -530,7 +530,7 @@ functions: isSpotlight: true code: | ```js - const { data, error } = await supabase.auth.mfa.challenge({ + const { data, error } = await supabase.auth.mfa.challenge({ factorId: '34e770dd-9ff9-416c-87fa-43b31d7ef225' }) ``` @@ -545,9 +545,9 @@ functions: isSpotlight: true code: | ```js - const { data, error } = await supabase.auth.mfa.verify({ + const { data, error } = await supabase.auth.mfa.verify({ factorId: '34e770dd-9ff9-416c-87fa-43b31d7ef225', - challengeId: '4034ae6f-a8ce-4fb5-8ee5-69a5863a7c15', + challengeId: '4034ae6f-a8ce-4fb5-8ee5-69a5863a7c15', code: '123456' }) ``` @@ -565,7 +565,7 @@ functions: ```js const { data, error } = await supabase.auth.mfa.challengeAndVerify({ factorId: '34e770dd-9ff9-416c-87fa-43b31d7ef225', - challengeId: '4034ae6f-a8ce-4fb5-8ee5-69a5863a7c15', + challengeId: '4034ae6f-a8ce-4fb5-8ee5-69a5863a7c15', code: '123456' }) ``` @@ -729,18 +729,18 @@ functions: title: 'resetPasswordForEmail()' $ref: '@supabase/gotrue-js.GoTrueClient.resetPasswordForEmail' notes: | - - The password reset flow consist of 2 broad steps: (i) Allow the user to login via the password reset link; (ii) Update the user's password. - - The `resetPasswordForEmail()` only sends a password reset link to the user's email. + - The password reset flow consist of 2 broad steps: (i) Allow the user to login via the password reset link; (ii) Update the user's password. + - The `resetPasswordForEmail()` only sends a password reset link to the user's email. To update the user's password, see [`updateUser()`](/docs/reference/javascript/auth-updateuser). - - A `SIGNED_IN` and `PASSWORD_RECOVERY` event will be emitted when the password recovery link is clicked. + - A `SIGNED_IN` and `PASSWORD_RECOVERY` event will be emitted when the password recovery link is clicked. You can use [`onAuthStateChange()`](/docs/reference/javascript/auth-onauthstatechange) to listen and invoke a callback function on these events. - When the user clicks the reset link in the email they are redirected back to your application. - You can configure the URL that the user is redirected to with the `redirectTo` parameter. + You can configure the URL that the user is redirected to with the `redirectTo` parameter. See [redirect URLs and wildcards](/docs/guides/auth/overview#redirect-urls-and-wildcards) to add additional redirect URLs to your project. - After the user has been redirected successfully, prompt them for a new password and call `updateUser()`: ```js - const { data, error } = await supabase.auth.updateUser({ - password: new_password + const { data, error } = await supabase.auth.updateUser({ + password: new_password }) ``` examples: @@ -934,10 +934,10 @@ functions: isSpotlight: true code: | ```js - const { data, error } = await supabase.auth.admin.mfa.deleteFactor({ + const { data, error } = await supabase.auth.admin.mfa.deleteFactor({ id: '34e770dd-9ff9-416c-87fa-43b31d7ef225', userId: 'a89baba7-b1b7-440f-b4bb-91026967f66b', - }) + }) ``` - id: select title: 'Fetch data: select()' @@ -5201,7 +5201,7 @@ functions: - id: text-search title: textSearch() $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.textSearch' - notes: + notes: | - For more information, see [Postgres full text search](/docs/guides/database/full-text-search). examples: - id: text-search From 5a3e10e67e915ebd3cd3deab9520d18427193d58 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 21 Dec 2022 21:31:05 -0330 Subject: [PATCH 008/127] Fix missing image warnings --- .../NavigationMenu.constants.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index b5b21af6420..a7c634f9ba6 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -14,7 +14,7 @@ export const REFERENCES: References = { name: 'supabase-js', library: 'supabase-js', versions: ['v2', 'v1'], - icon: '/docs/img/libraries/javascript-icon.svg', + icon: '/img/libraries/javascript-icon', }, dart: { name: 'Flutter', @@ -688,22 +688,22 @@ export const reference = { url: '/reference/javascript/start', level: 'reference_javascript', items: [], - icon: 'docs/img/icons/javascript.svg', + icon: '/img/icons/menu/reference-javascript', }, { name: 'supabase-dart', url: '/reference/dart/start', level: 'reference_dart', items: [], - icon: 'docs/img/icons/javascript.svg', - }, - { - name: 'supbase-python', - url: '/reference/python/start', - level: 'reference_python', - items: [], - icon: 'docs/img/icons/javascript.svg', + icon: '/img/icons/menu/reference-dart', }, + // { + // name: 'supbase-python', + // url: '/reference/python/start', + // level: 'reference_python', + // items: [], + // icon: '/img/icons/menu/reference-javascript', + // }, ], }, { @@ -713,13 +713,13 @@ export const reference = { name: 'Supabase CLI', url: '/reference/cli/start', items: [], - icon: 'docs/img/icons/cli-icon.svg', + icon: '/img/icons/menu/reference-cli', }, { name: 'Management API', url: '/reference/javascript', items: [], - icon: 'docs/img/icons/javascript-icon.svg', + icon: '/img/icons/menu/reference-api', }, ], }, From c02d0b5074a6981add58fded7d811719ea87f425 Mon Sep 17 00:00:00 2001 From: dng Date: Wed, 21 Dec 2022 17:18:16 -0800 Subject: [PATCH 009/127] Fix typo --- .../Navigation/NavigationMenu/NavigationMenu.constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index a7c634f9ba6..d9837f2ea65 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -698,7 +698,7 @@ export const reference = { icon: '/img/icons/menu/reference-dart', }, // { - // name: 'supbase-python', + // name: 'supabase-python', // url: '/reference/python/start', // level: 'reference_python', // items: [], From df5305eb7c51870b905a185bdcd11c16e481407c Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Wed, 21 Dec 2022 18:07:05 -0800 Subject: [PATCH 010/127] Add missing import to component On line 138 the component is using a `useState` function call which is a react hook, however no react hook is imported causing an error. --- apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx index df4d8171a4d..0a174410282 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx @@ -125,6 +125,7 @@ export default MyApp Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component: ```jsx lines=2,8 title=pages/_app.tsx +import { useState } from 'react' import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' import { SessionContextProvider, Session } from '@supabase/auth-helpers-react' From bc0a03bbf60482010b5d64289a0763971a95c42a Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Thu, 22 Dec 2022 00:33:17 -0500 Subject: [PATCH 011/127] move docs to og generator --- apps/docs/layouts/guides/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/docs/layouts/guides/index.tsx b/apps/docs/layouts/guides/index.tsx index e3b64828a60..14a5d08ba33 100644 --- a/apps/docs/layouts/guides/index.tsx +++ b/apps/docs/layouts/guides/index.tsx @@ -60,6 +60,8 @@ const Layout: FC = (props) => { const hasTableOfContents = tocList.length > 0 + const ogPageType = asPath.split('/')[2] + return ( <> = (props) => { url: `https://supabase.com/docs${asPath}`, images: [ { - url: `https://supabase.com/docs/img/supabase-og-image.png`, + url: `https://obuldanrptloktxcffvn.functions.supabase.co/og-images?site=docs${ogPageType ? `&type=${ogPageType}` : ''}&title=${props.meta?.title}&description=${props.meta?.description}`, }, ], }} From 63fc5826690b304705527109249dd90289fdf362 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Thu, 22 Dec 2022 00:37:10 -0500 Subject: [PATCH 012/127] run prettier --- apps/docs/layouts/guides/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/docs/layouts/guides/index.tsx b/apps/docs/layouts/guides/index.tsx index 14a5d08ba33..991183239d9 100644 --- a/apps/docs/layouts/guides/index.tsx +++ b/apps/docs/layouts/guides/index.tsx @@ -73,7 +73,9 @@ const Layout: FC = (props) => { url: `https://supabase.com/docs${asPath}`, images: [ { - url: `https://obuldanrptloktxcffvn.functions.supabase.co/og-images?site=docs${ogPageType ? `&type=${ogPageType}` : ''}&title=${props.meta?.title}&description=${props.meta?.description}`, + url: `https://obuldanrptloktxcffvn.functions.supabase.co/og-images?site=docs${ + ogPageType ? `&type=${ogPageType}` : '' + }&title=${props.meta?.title}&description=${props.meta?.description}`, }, ], }} From de3287e47f8e9a0edc02901103741072552c6146 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Thu, 22 Dec 2022 01:03:31 -0500 Subject: [PATCH 013/127] encode the title and description --- apps/docs/layouts/guides/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/layouts/guides/index.tsx b/apps/docs/layouts/guides/index.tsx index 991183239d9..0c79c49e05c 100644 --- a/apps/docs/layouts/guides/index.tsx +++ b/apps/docs/layouts/guides/index.tsx @@ -75,7 +75,7 @@ const Layout: FC = (props) => { { url: `https://obuldanrptloktxcffvn.functions.supabase.co/og-images?site=docs${ ogPageType ? `&type=${ogPageType}` : '' - }&title=${props.meta?.title}&description=${props.meta?.description}`, + }&title=${encodeURIComponent(props.meta?.title)}&description=${encodeURIComponent(props.meta?.description)}`, }, ], }} From d24b3ba0c40f486f7d8c9478bcf5830a5652b932 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Thu, 22 Dec 2022 01:04:17 -0500 Subject: [PATCH 014/127] run prettier --- apps/docs/layouts/guides/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/docs/layouts/guides/index.tsx b/apps/docs/layouts/guides/index.tsx index 0c79c49e05c..07ec7929195 100644 --- a/apps/docs/layouts/guides/index.tsx +++ b/apps/docs/layouts/guides/index.tsx @@ -75,7 +75,9 @@ const Layout: FC = (props) => { { url: `https://obuldanrptloktxcffvn.functions.supabase.co/og-images?site=docs${ ogPageType ? `&type=${ogPageType}` : '' - }&title=${encodeURIComponent(props.meta?.title)}&description=${encodeURIComponent(props.meta?.description)}`, + }&title=${encodeURIComponent(props.meta?.title)}&description=${encodeURIComponent( + props.meta?.description + )}`, }, ], }} From c5901320d096895bc7532db09371355d881c7055 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Thu, 22 Dec 2022 16:35:32 +0800 Subject: [PATCH 015/127] Update index.mdx --- apps/docs/pages/index.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/pages/index.mdx b/apps/docs/pages/index.mdx index 9921286f50c..17bc30954ec 100644 --- a/apps/docs/pages/index.mdx +++ b/apps/docs/pages/index.mdx @@ -11,9 +11,9 @@ export const meta = {

    Supabase is an open source Firebase alternative providing all the backend features you need to - build a product. [Learn more](/docs/architecture) about Supabase, start with the - [tutorials](/docs/guides/getting-started#tutorials), or dive straight into the different products - and APIs. + build a product. [Learn more](/docs/guides/getting-started/architecture) about Supabase, start + with the [tutorials](/docs/guides/getting-started#tutorials), or dive straight into the different + products and APIs.

    {/* start container */} From 94e97a455217d87fa8de2223fd0a752fb815c8af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Thu, 22 Dec 2022 13:17:23 +0100 Subject: [PATCH 016/127] chore: do not send captcha token for subscription endpoint (#11200) --- studio/components/interfaces/Billing/Billing.utils.ts | 6 ++---- studio/components/interfaces/Billing/EnterpriseUpdate.tsx | 6 ++---- .../components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx | 5 ++--- studio/components/interfaces/Billing/ProUpgrade.tsx | 6 ++---- 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/studio/components/interfaces/Billing/Billing.utils.ts b/studio/components/interfaces/Billing/Billing.utils.ts index 67f98f93e7b..4bad6ed40a7 100644 --- a/studio/components/interfaces/Billing/Billing.utils.ts +++ b/studio/components/interfaces/Billing/Billing.utils.ts @@ -52,8 +52,7 @@ export const formSubscriptionUpdatePayload = ( }, nonChangeableAddons: SubscriptionAddon[], selectedPaymentMethod: string, - region: string, - hcaptchaToken: string | undefined + region: string ) => { const { computeSize, pitrDuration, customDomains } = selectedAddons @@ -78,8 +77,7 @@ export const formSubscriptionUpdatePayload = ( ...(tierPriceId && { tier: tierPriceId.id }), addons, proration_date, - payment_method: selectedPaymentMethod, - hcaptchaToken + payment_method: selectedPaymentMethod } } diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index 254d83275c7..969b5d58993 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -114,8 +114,7 @@ const EnterpriseUpdate: FC = ({ selectedAddons, nonChangeableAddons, selectedPaymentMethodId, - projectRegion, - undefined + projectRegion ), tier: currentSubscription.tier.price_id, } @@ -178,8 +177,7 @@ const EnterpriseUpdate: FC = ({ selectedAddons, nonChangeableAddons, selectedPaymentMethodId, - projectRegion, - token ?? undefined + projectRegion ), tier: currentSubscription.tier.price_id, } diff --git a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx index 439f5edc6f3..9cda2b5aa30 100644 --- a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx +++ b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx @@ -109,7 +109,7 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { setMessage(values.message) return setShowConfirmModal(true) } else { - downgradeProject(values, token as string) + downgradeProject(values) } } @@ -118,7 +118,7 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { captchaRef.current?.resetCaptcha() } - const downgradeProject = async (values?: any, hcaptchaToken?: string) => { + const downgradeProject = async (values?: any) => { const downgradeMessage = values?.message ?? message try { @@ -132,7 +132,6 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { tier, addons, proration_date, - hcaptchaToken: captchaToken ?? hcaptchaToken, }) resetCaptcha() diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index 2f05f9a8844..b4cf2635051 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -137,8 +137,7 @@ const ProUpgrade: FC = ({ selectedAddons, nonChangeableAddons, selectedPaymentMethodId, - projectRegion, - undefined + projectRegion ) setIsRefreshingPreview(true) @@ -184,8 +183,7 @@ const ProUpgrade: FC = ({ selectedAddons, nonChangeableAddons, selectedPaymentMethodId, - projectRegion, - captchaToken ?? undefined + projectRegion ) const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload) resetCaptcha() From b58c99189fcaff23c657207440a780ae9170f0ed Mon Sep 17 00:00:00 2001 From: Francisco Mazzoni Date: Thu, 22 Dec 2022 09:42:19 -0300 Subject: [PATCH 017/127] after lw6 first pass --- apps/www/pages/launch-week/index.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/www/pages/launch-week/index.tsx b/apps/www/pages/launch-week/index.tsx index 7888948b8c7..3fc5fb1d0ff 100644 --- a/apps/www/pages/launch-week/index.tsx +++ b/apps/www/pages/launch-week/index.tsx @@ -1030,7 +1030,7 @@ export default function launchweek() {
    - Happening all week + Submissions Closed

    Launch Week Hackathon

    @@ -1212,8 +1212,14 @@ export default function launchweek() { })}

    - - Currently Happening + + Shipped

    The Supabase Content Storm

    From 7d27671e4971b7f9ce244acefdf3ec1b8c389840 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Thu, 22 Dec 2022 10:07:59 -0330 Subject: [PATCH 018/127] More console warnings --- .../NavigationMenu/NavigationMenu.tsx | 2 +- .../NavigationMenuGuideListItems.tsx | 16 ++- .../NavigationMenuRefListItems.tsx | 116 ++++++++---------- apps/docs/pages/guides/functions.mdx | 2 +- 4 files changed, 63 insertions(+), 73 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index 8017146442b..cfb4d87b143 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -62,7 +62,7 @@ export type RefKeyOptions = | 'self-hosting-storage' | 'self-hosting-realtime' -const NavigationMenu = ({ context }) => { +const NavigationMenu = () => { const router = useRouter() let version = '' diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideListItems.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideListItems.tsx index f298683984d..e4e73a8e132 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideListItems.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideListItems.tsx @@ -137,7 +137,14 @@ const ContentLink = React.memo(function ContentLink(props: any) { : 'hover:text-brand-900 text-scale-1000', ].join(' ')} > - {props.icon && } + {props.icon && ( + {props.icon} + )} {props.name} @@ -177,14 +184,15 @@ const Content = (props) => { - {menu.items.map((x, index) => { + {menu.items.map((x) => { return ( -

    +
    {x.items && x.items.length > 0 ? (
    {x.items.map((subItem, subItemIndex) => { return ( { })}
    ) : ( - + )}
    ) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx index fd87508d468..87a90b82baa 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx @@ -12,8 +12,9 @@ import RevVersionDropdown from '~/components/RefVersionDropdown' import { useMenuActiveRefId } from '~/hooks/useMenuState' import { RefIdOptions, RefKeyOptions } from './NavigationMenu' -import React from 'react' +import React, { Fragment } from 'react' import { generateAllowedClientLibKeys } from '~/lib/refGenerator/helpers' +import { isFuncNotInLibraryOrVersion } from './NavigationMenu.utils' const HeaderImage = React.memo(function HeaderImage(props: any) { const router = useRouter() @@ -131,6 +132,7 @@ const RenderLink = React.memo(function RenderLink(props: any) { .map((item) => { return ( = ({ id, lib, commonSections, sp
      {sections.map((fn: any, fnIndex) => { - // - // check if the link is allowed to be displayed - function isFuncNotInLibraryOrVersion(id, type) { - if (id && allowedKeys && !allowedKeys.includes(id) && type !== 'markdown') { - /* - * Remove this menu link from side bar, as it does not exist for either - * this language, or for this lib version - * - * */ - return true - } else { - return false - } - } - // run allow check - if (isFuncNotInLibraryOrVersion(fn.id, fn.type)) { - return <> + if (isFuncNotInLibraryOrVersion(fn.id, fn.type, allowedKeys)) { + return } // handle subtitles with subitems - if (!fn.id) { - return ( - <> - - - {fn.items && - fn.items - //.filter((item) => item.libs && item.libs.includes(lib)) - .map((item) => { - // run allow check - if (isFuncNotInLibraryOrVersion(item.id, item.type)) return <> - return ( - - ) - })} - - ) - } else { - // handle normal links - return ( - <> - - {fn.items && - fn.items - //.filter((item) => item.libs.includes(lib)) - .map((item) => ( - - ))} - - ) - } + return fn.id ? ( + + + {fn.items && + fn.items + //.filter((item) => item.libs.includes(lib)) + .map((item) => ( + + ))} + + ) : ( + + + + {fn.items && + fn.items.map((item, i) => { + // run allow check + if (isFuncNotInLibraryOrVersion(item.id, item.type, allowedKeys)) + return + return ( + + ) + })} + + ) })}
    diff --git a/apps/docs/pages/guides/functions.mdx b/apps/docs/pages/guides/functions.mdx index fdceb0ceea9..9c970cb6bfe 100644 --- a/apps/docs/pages/guides/functions.mdx +++ b/apps/docs/pages/guides/functions.mdx @@ -29,7 +29,7 @@ Check out the [Edge Function Examples](https://github.com/supabase/supabase/tree
    {examples.map((x) => ( -
    +
    From 5322a76451157591b61153930b5b44cdc7b57596 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Tue, 22 Nov 2022 16:37:49 +0800 Subject: [PATCH 019/127] feat: refactor preset reports to use hook for data fetching --- .../interfaces/Reports/PresetReport.tsx | 113 ------------------ .../interfaces/Reports/Reports.constants.ts | 10 +- .../interfaces/Reports/Reports.types.ts | 5 + .../interfaces/Reports/Reports.utils.ts | 7 -- .../interfaces/Reports/Reports.utils.tsx | 86 +++++++++++++ .../ReportsLayout/ReportsMenu.utils.ts | 9 +- studio/hooks/analytics/useLogsQuery.tsx | 6 +- .../project/[ref]/reports/api-overview.tsx | 65 ++++++++++ .../pages/project/[ref]/reports/api-usage.tsx | 18 --- 9 files changed, 172 insertions(+), 147 deletions(-) delete mode 100644 studio/components/interfaces/Reports/PresetReport.tsx delete mode 100644 studio/components/interfaces/Reports/Reports.utils.ts create mode 100644 studio/components/interfaces/Reports/Reports.utils.tsx create mode 100644 studio/pages/project/[ref]/reports/api-overview.tsx delete mode 100644 studio/pages/project/[ref]/reports/api-usage.tsx diff --git a/studio/components/interfaces/Reports/PresetReport.tsx b/studio/components/interfaces/Reports/PresetReport.tsx deleted file mode 100644 index bcf7ae5ad0d..00000000000 --- a/studio/components/interfaces/Reports/PresetReport.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { Button, IconRefreshCw } from 'ui' -import useLogsQuery from 'hooks/analytics/useLogsQuery' -import { DatePickerToFrom } from '../Settings/Logs' -import DatePickers from '../Settings/Logs/Logs.DatePickers' -import { - renderErrorRateChart, - renderRequestsPathsTable, - renderStatusCodesChart, -} from './renderers/OverviewRenderers' -import { PRESET_CONFIG, REPORTS_DATEPICKER_HELPERS } from './Reports.constants' -import { Presets } from './Reports.types' -import ReportWidget from './ReportWidget' - -interface Props { - preset: Presets - projectRef: string -} - -const DEFAULT_PARAMS = { - iso_timestamp_start: REPORTS_DATEPICKER_HELPERS[0].calcFrom(), - iso_timestamp_end: REPORTS_DATEPICKER_HELPERS[0].calcTo(), -} - -const PresetReport: React.FC = ({ projectRef, preset }) => { - const config = PRESET_CONFIG[preset] - const [statusCodes, statusCodesHandlers] = useLogsQuery(projectRef, { - sql: config.sql.statusCodes, - ...DEFAULT_PARAMS, - }) - - const [requestPaths, requestPathsHandlers] = useLogsQuery(projectRef, { - sql: config.sql.requestPaths, - ...DEFAULT_PARAMS, - }) - - const [errorRate, errorRateHandlers] = useLogsQuery(projectRef, { - sql: config.sql.errorRate, - ...DEFAULT_PARAMS, - }) - - const handleRefresh = () => { - statusCodesHandlers.runQuery() - requestPathsHandlers.runQuery() - errorRateHandlers.runQuery() - } - const handleDatepickerChange = ({ to, from }: DatePickerToFrom) => { - const newParams = { iso_timestamp_start: from || '', iso_timestamp_end: to || '' } - statusCodesHandlers.setParams((prev) => ({ ...prev, ...newParams })) - requestPathsHandlers.setParams((prev) => ({ ...prev, ...newParams })) - errorRateHandlers.setParams((prev) => ({ ...prev, ...newParams })) - } - const isLoading = statusCodes.isLoading || requestPaths.isLoading - - return ( -
    -

    {config.title}

    -
    - - -
    - -
    - - - -
    -
    - ) -} - -export default PresetReport diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index ac4492a7075..e334c0485b8 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -1,6 +1,6 @@ import dayjs from 'dayjs' import { DatetimeHelper } from '../Settings/Logs' -import { Presets } from './Reports.types' +import { PresetConfig, Presets } from './Reports.types' export const LAYOUT_COLUMN_COUNT = 24 @@ -28,7 +28,13 @@ export const REPORTS_DATEPICKER_HELPERS: DatetimeHelper[] = [ }, ] -export const PRESET_CONFIG = { +export const DEFAULT_QUERY_PARAMS = { + iso_timestamp_start: REPORTS_DATEPICKER_HELPERS[0].calcFrom(), + iso_timestamp_end: REPORTS_DATEPICKER_HELPERS[0].calcTo(), +} + + +export const PRESET_CONFIG : Record ={ [Presets.OVERVIEW]: { title: '', sql: { diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts index 1838192894f..3679feeb5ca 100644 --- a/studio/components/interfaces/Reports/Reports.types.ts +++ b/studio/components/interfaces/Reports/Reports.types.ts @@ -1,6 +1,11 @@ export enum Presets { OVERVIEW = 'overview', } + +export interface PresetConfig { + title: string + sql: Record +} export interface StatusCodesDatum { timestamp: number status_code: number diff --git a/studio/components/interfaces/Reports/Reports.utils.ts b/studio/components/interfaces/Reports/Reports.utils.ts deleted file mode 100644 index 12a7d634797..00000000000 --- a/studio/components/interfaces/Reports/Reports.utils.ts +++ /dev/null @@ -1,7 +0,0 @@ - -/** - * Converts a query params string to an object - */ -export const queryParamsToObject = (params: string) => { - return Object.fromEntries(new URLSearchParams(params)) -} \ No newline at end of file diff --git a/studio/components/interfaces/Reports/Reports.utils.tsx b/studio/components/interfaces/Reports/Reports.utils.tsx new file mode 100644 index 00000000000..659362a0a9c --- /dev/null +++ b/studio/components/interfaces/Reports/Reports.utils.tsx @@ -0,0 +1,86 @@ +import { IconRefreshCw } from '@supabase/ui' +import useLogsQuery, { LogsQueryData, LogsQueryHandlers } from 'hooks/analytics/useLogsQuery' +import React from 'react' +import { Button } from 'ui' +import { DatePickerToFrom } from '../Settings/Logs' +import DatePickers from '../Settings/Logs/Logs.DatePickers' +import { DEFAULT_QUERY_PARAMS, REPORTS_DATEPICKER_HELPERS } from './Reports.constants' +import { PresetConfig } from './Reports.types' + +/** + * Converts a query params string to an object + */ +export const queryParamsToObject = (params: string) => { + return Object.fromEntries(new URLSearchParams(params)) +} + +// generate hooks based on preset config +type PresetHooks = Record [LogsQueryData, LogsQueryHandlers]> +export const hooksFactory = (projectRef: string, config: PresetConfig) => { + const hooks: PresetHooks = Object.entries(config.sql).reduce((acc, [k, sql]) => { + return { + ...acc, + [k]: () => + useLogsQuery(projectRef, { + sql, + ...DEFAULT_QUERY_PARAMS, + }), + } + }, {}) + console.log(hooks) + return hooks +} + +export const usePresetReport = (data: LogsQueryData[], handlers: LogsQueryHandlers[]) => { + const handleRefresh = () => { + handlers.forEach((handler) => { + handler.runQuery() + }) + } + const handleDatepickerChange = ({ to, from }: DatePickerToFrom) => { + const newParams = { iso_timestamp_start: from || '', iso_timestamp_end: to || '' } + + handlers.forEach((handler) => { + handler.setParams((prev) => ({ + ...prev, + ...newParams, + })) + }) + } + const isLoading = data.map((datum) => datum.isLoading).some((v) => v) + const Layout: React.FC<{title: string}> = ({ title, children }) => ( +
    +

    {title}

    +
    + + +
    + + {children} +
    + ) + return { + isLoading, + Layout, + handleRefresh, + handleDatepickerChange, + } +} diff --git a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts index 28c14e91e92..49c75e4afa9 100644 --- a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts +++ b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts @@ -19,10 +19,11 @@ export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { ...(productReports ? [ { - name: 'API usage', - key: 'api-usage', - url: `/project/${ref}/reports/api-usage`, - items: [], + name: 'API', + key: 'api-overview', + url: `/project/${ref}/reports/api-overview`, + items: [] + }, }, ] : []), diff --git a/studio/hooks/analytics/useLogsQuery.tsx b/studio/hooks/analytics/useLogsQuery.tsx index 7b102dbc2d5..48721a081ae 100644 --- a/studio/hooks/analytics/useLogsQuery.tsx +++ b/studio/hooks/analytics/useLogsQuery.tsx @@ -8,13 +8,13 @@ import { LogsEndpointParams, Logs, LogData } from 'components/interfaces/Setting import { API_URL } from 'lib/constants' import useSWR from 'swr' import { get } from 'lib/common/fetch' -interface Data { +export interface LogsQueryData { params: LogsEndpointParams isLoading: boolean logData: LogData[] error: string | Object | null } -interface Handlers { +export interface LogsQueryHandlers { changeQuery: (newQuery?: string) => void runQuery: () => void setParams: Dispatch> @@ -23,7 +23,7 @@ interface Handlers { const useLogsQuery = ( projectRef: string, initialParams: Partial = {} -): [Data, Handlers] => { +): [LogsQueryData, LogsQueryHandlers] => { const defaultHelper = getDefaultHelper(EXPLORER_DATEPICKER_HELPERS) const [params, setParams] = useState({ sql: initialParams?.sql || '', diff --git a/studio/pages/project/[ref]/reports/api-overview.tsx b/studio/pages/project/[ref]/reports/api-overview.tsx new file mode 100644 index 00000000000..40ed529d425 --- /dev/null +++ b/studio/pages/project/[ref]/reports/api-overview.tsx @@ -0,0 +1,65 @@ +import { observer } from 'mobx-react-lite' +import { useRouter } from 'next/router' + +import { NextPageWithLayout } from 'types' +import { Presets } from 'components/interfaces/Reports/Reports.types' +import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout' +import { PRESET_CONFIG } from 'components/interfaces/Reports/Reports.constants' +import ReportWidget from 'components/interfaces/Reports/ReportWidget' +import { hooksFactory, usePresetReport } from 'components/interfaces/Reports/Reports.utils' +import { + renderErrorRateChart, + renderRequestsPathsTable, + renderStatusCodesChart, +} from 'components/interfaces/Reports/renderers/OverviewRenderers' + +export const ApiOverviewPage: NextPageWithLayout = () => { + const router = useRouter() + const { ref } = router.query + const config = PRESET_CONFIG[Presets.OVERVIEW] + const hooks = hooksFactory(ref as string, config) + const [statusCodes, statusCodesHandlers] = hooks.statusCodes() + const [errorRate, errorRateHandlers] = hooks.errorRate() + const [requestPaths, requestPathsHandlers] = hooks.requestPaths() + const { isLoading, Layout } = usePresetReport( + [statusCodes, errorRate, requestPaths], + [statusCodesHandlers, errorRateHandlers, requestPathsHandlers] + ) + return ( + +
    + + + +
    +
    + ) +} + +ApiOverviewPage.getLayout = (page) => {page} + +export default observer(ApiOverviewPage) diff --git a/studio/pages/project/[ref]/reports/api-usage.tsx b/studio/pages/project/[ref]/reports/api-usage.tsx deleted file mode 100644 index 9a027edf251..00000000000 --- a/studio/pages/project/[ref]/reports/api-usage.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { observer } from 'mobx-react-lite' -import { useRouter } from 'next/router' - -import { NextPageWithLayout } from 'types' -import PresetReport from 'components/interfaces/Reports/PresetReport' -import { Presets } from 'components/interfaces/Reports/Reports.types' -import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout' - -export const ApiUsagePage: NextPageWithLayout = () => { - const router = useRouter() - const { ref } = router.query - - return -} - -ApiUsagePage.getLayout = (page) => {page} - -export default observer(ApiUsagePage) From de385d38c91972649ce453e2cd47759764b4c026 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Tue, 22 Nov 2022 17:38:55 +0800 Subject: [PATCH 020/127] feat: add api bots report --- .../interfaces/Reports/Reports.constants.ts | 54 ++++++++++- .../interfaces/Reports/Reports.types.ts | 3 +- .../Reports/renderers/ApiBotsRenderers.tsx | 90 +++++++++++++++++++ .../ReportsLayout/ReportsMenu.utils.ts | 15 +++- .../ui/ProductMenu/ProductMenuGroup.tsx | 3 + .../pages/project/[ref]/reports/api-bots.tsx | 51 +++++++++++ .../project/[ref]/reports/api-overview.tsx | 2 +- 7 files changed, 212 insertions(+), 6 deletions(-) create mode 100644 studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx create mode 100644 studio/components/ui/ProductMenu/ProductMenuGroup.tsx create mode 100644 studio/pages/project/[ref]/reports/api-bots.tsx diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index e334c0485b8..ea6005b890c 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -35,7 +35,8 @@ export const DEFAULT_QUERY_PARAMS = { export const PRESET_CONFIG : Record ={ - [Presets.OVERVIEW]: { + + [Presets.API_OVERVIEW]: { title: '', sql: { statusCodes: ` @@ -95,6 +96,57 @@ ORDER BY `, }, }, + [Presets.API_BOTS]: { + title: "API Bots", + sql: { + userAgents: ` +select + h.user_agent as user_agent, + cf.asOrganization as request_source, + count(f.id) as count +from + edge_logs as f + cross join unnest(f.metadata) as m + cross join unnest(m.request) as r + cross join unnest(r.cf) as cf + cross join unnest(r.headers) as h +group by + user_agent, + request_source +order by + count desc +limit 12 +`, + botScores:` +select + h.cf_connecting_ip as ip, + h.cf_ipcountry as country, + h.user_agent as user_agent, + r.path as path, + b.score as bot_score, + b.verifiedBot as bot_verified, + count(f.id) as count +from + edge_logs as f + cross join unnest(f.metadata) as m + cross join unnest(m.request) as r + cross join unnest(r.cf) as cf + cross join unnest(r.headers) as h + cross join unnest(cf.botManagement) as b +group by + ip, + country, + path, + user_agent, + bot_score, + bot_verified +order by + bot_score desc +limit 20 + + ` + } + } } export const DATETIME_FORMAT = 'MMM D, ha' diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts index 3679feeb5ca..359b9bd7569 100644 --- a/studio/components/interfaces/Reports/Reports.types.ts +++ b/studio/components/interfaces/Reports/Reports.types.ts @@ -1,5 +1,6 @@ export enum Presets { - OVERVIEW = 'overview', + API_OVERVIEW = 'overview', + API_BOTS="api_bots" } export interface PresetConfig { diff --git a/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx b/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx new file mode 100644 index 00000000000..22f47142c32 --- /dev/null +++ b/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx @@ -0,0 +1,90 @@ +import Table from 'components/to-be-cleaned/Table' +import { ReportWidgetProps } from '../ReportWidget' + +export const renderUserAgents = ( + props: ReportWidgetProps<{ + user_agent: string + request_source: string + count: number + }> +) => { + return ( + + User Agents + Request Source + Count + + } + body={ + <> + {props.data.map((row, index) => { + return ( + + + {row.user_agent} + + {row.request_source} + + {row.count} + + + ) + })} + + } + /> + ) +} + +export const renderBotScores = ( + props: ReportWidgetProps<{ + ip: string + country: string + user_agent: string + path: string + bot_score: number + bot_verified: boolean + count: number + }> +) => { + return ( +
    + IP (Country) + Request Path + User Agent + Bot Score + Is Verified Bot + Count + + } + body={ + <> + {props.data.map((row, index) => { + return ( + + + {row.ip} ({row.country}) + + {row.path} + {row.user_agent} + {row.bot_score} + {row.bot_verified ? 'Yes' : 'No'} + + {row.count} + + + ) + })} + + } + /> + ) +} \ No newline at end of file diff --git a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts index 49c75e4afa9..427476af448 100644 --- a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts +++ b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts @@ -8,7 +8,7 @@ export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { return [ { - title: 'Reports', + title: '', items: [ { name: 'Custom reports', @@ -16,14 +16,23 @@ export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { url: `/project/${ref}/reports`, items: [], }, + ], + }, + { + items: [ ...(productReports ? [ { - name: 'API', + name: 'API Overview', key: 'api-overview', url: `/project/${ref}/reports/api-overview`, - items: [] + items: [], }, + { + name: 'API Bots', + key: 'api-bots', + url: `/project/${ref}/reports/api-bots`, + items: [], }, ] : []), diff --git a/studio/components/ui/ProductMenu/ProductMenuGroup.tsx b/studio/components/ui/ProductMenu/ProductMenuGroup.tsx new file mode 100644 index 00000000000..96f4ea20ef0 --- /dev/null +++ b/studio/components/ui/ProductMenu/ProductMenuGroup.tsx @@ -0,0 +1,3 @@ +const ProductMenuGroup = ()=>{ + +} \ No newline at end of file diff --git a/studio/pages/project/[ref]/reports/api-bots.tsx b/studio/pages/project/[ref]/reports/api-bots.tsx new file mode 100644 index 00000000000..40f518ae010 --- /dev/null +++ b/studio/pages/project/[ref]/reports/api-bots.tsx @@ -0,0 +1,51 @@ +import { observer } from 'mobx-react-lite' +import { useRouter } from 'next/router' + +import { NextPageWithLayout } from 'types' +import { Presets } from 'components/interfaces/Reports/Reports.types' +import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout' +import { hooksFactory, usePresetReport } from 'components/interfaces/Reports/Reports.utils' +import { PRESET_CONFIG } from 'components/interfaces/Reports/Reports.constants' +import { renderBotScores, renderUserAgents } from 'components/interfaces/Reports/renderers/ApiBotsRenderers' +import ReportWidget from 'components/interfaces/Reports/ReportWidget' + +export const ApiBotsPage: NextPageWithLayout = () => { + const router = useRouter() + const { ref } = router.query + const config = PRESET_CONFIG[Presets.API_BOTS] + const hooks = hooksFactory(ref as string, config) + const [userAgents, userAgentsHandlers] = hooks.userAgents() + const [botScores, botScoresHandlers] = hooks.botScores() + const { isLoading, Layout } = usePresetReport( + [userAgents, botScores], + [userAgentsHandlers, botScoresHandlers] + ) + return ( + +
    + + +
    +
    + ) +} + +ApiBotsPage.getLayout = (page) => {page} + +export default observer(ApiBotsPage) diff --git a/studio/pages/project/[ref]/reports/api-overview.tsx b/studio/pages/project/[ref]/reports/api-overview.tsx index 40ed529d425..cd211996a34 100644 --- a/studio/pages/project/[ref]/reports/api-overview.tsx +++ b/studio/pages/project/[ref]/reports/api-overview.tsx @@ -16,7 +16,7 @@ import { export const ApiOverviewPage: NextPageWithLayout = () => { const router = useRouter() const { ref } = router.query - const config = PRESET_CONFIG[Presets.OVERVIEW] + const config = PRESET_CONFIG[Presets.API_OVERVIEW] const hooks = hooksFactory(ref as string, config) const [statusCodes, statusCodesHandlers] = hooks.statusCodes() const [errorRate, errorRateHandlers] = hooks.errorRate() From de04637d4968895142f9cf4af085b32cb4e6b482 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Tue, 22 Nov 2022 20:04:48 +0800 Subject: [PATCH 021/127] feat: add auth report --- .../interfaces/Reports/Reports.constants.ts | 47 ++++++++++++---- .../interfaces/Reports/Reports.types.ts | 3 +- .../Reports/renderers/AuthRenderers.tsx | 47 ++++++++++++++++ .../ReportsLayout/ReportsMenu.utils.ts | 6 +++ studio/pages/project/[ref]/reports/auth.tsx | 54 +++++++++++++++++++ 5 files changed, 147 insertions(+), 10 deletions(-) create mode 100644 studio/components/interfaces/Reports/renderers/AuthRenderers.tsx create mode 100644 studio/pages/project/[ref]/reports/auth.tsx diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index ea6005b890c..c2a81041b29 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -33,9 +33,7 @@ export const DEFAULT_QUERY_PARAMS = { iso_timestamp_end: REPORTS_DATEPICKER_HELPERS[0].calcTo(), } - -export const PRESET_CONFIG : Record ={ - +export const PRESET_CONFIG: Record = { [Presets.API_OVERVIEW]: { title: '', sql: { @@ -97,7 +95,7 @@ ORDER BY }, }, [Presets.API_BOTS]: { - title: "API Bots", + title: 'API Bots', sql: { userAgents: ` select @@ -117,7 +115,7 @@ order by count desc limit 12 `, - botScores:` + botScores: ` select h.cf_connecting_ip as ip, h.cf_ipcountry as country, @@ -143,10 +141,41 @@ group by order by bot_score desc limit 20 - - ` - } - } + `, + }, + }, + [Presets.AUTH]: { + title: 'Auth', + sql: { + dailyActiveUsers: ` +select + timestamp_trunc(timestamp, day) as timestamp, + count(distinct json_extract(f.event_message, "$.user_id")) as count +from gotrue_logs f +cross join unnest(f.metadata) as m +where json_extract(f.event_message, "$.user_id") is not null +group by + timestamp +order by + timestamp +`, +newUsers: ` +select + TIMESTAMP_TRUNC(f.timestamp, DAY) as timestamp, + count(id) as count +from +gotrue_logs f + cross join unnest(f.metadata) as m +where f.timestamp > "2022-11-20" +and m.project = "tzljvicuuxegpzwuwife" +and JSON_VALUE(f.event_message, "$.path") = "/signup" +and JSON_VALUE(f.event_message, "$.auth_event.actor_id") is not null +and JSON_VALUE(f.event_message, "$.auth_event.action") != "user_repeated_signup" +group by + timestamp +` + }, + }, } export const DATETIME_FORMAT = 'MMM D, ha' diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts index 359b9bd7569..5ace186ea57 100644 --- a/studio/components/interfaces/Reports/Reports.types.ts +++ b/studio/components/interfaces/Reports/Reports.types.ts @@ -1,6 +1,7 @@ export enum Presets { API_OVERVIEW = 'overview', - API_BOTS="api_bots" + API_BOTS = 'api_bots', + AUTH = 'auth', } export interface PresetConfig { diff --git a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx new file mode 100644 index 00000000000..c6a05709fe9 --- /dev/null +++ b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx @@ -0,0 +1,47 @@ +import { BarChart } from 'components/to-be-cleaned/Charts/ChartRenderer' +import { DATETIME_FORMAT } from '../Reports.constants' +import { ReportWidgetProps } from '../ReportWidget' + +export const renderDailyActiveUsers = ( + props: ReportWidgetProps<{ + count: number + timestamp: string + }> +) => { + return ( + <> +
    + +
    + + ) +} + +export const renderNewUsers = ( + props: ReportWidgetProps<{ + count: number + timestamp: string + }> +) => { + return ( + <> +
    + +
    + + ) +} diff --git a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts index 427476af448..c120e0fbb64 100644 --- a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts +++ b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts @@ -34,6 +34,12 @@ export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { url: `/project/${ref}/reports/api-bots`, items: [], }, + { + name: 'Auth', + key: 'auth', + url: `/project/${ref}/reports/auth`, + items: [], + }, ] : []), { diff --git a/studio/pages/project/[ref]/reports/auth.tsx b/studio/pages/project/[ref]/reports/auth.tsx new file mode 100644 index 00000000000..2a33a8d6837 --- /dev/null +++ b/studio/pages/project/[ref]/reports/auth.tsx @@ -0,0 +1,54 @@ +import { observer } from 'mobx-react-lite' +import { useRouter } from 'next/router' + +import { NextPageWithLayout } from 'types' +import { Presets } from 'components/interfaces/Reports/Reports.types' +import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout' +import { hooksFactory, usePresetReport } from 'components/interfaces/Reports/Reports.utils' +import { PRESET_CONFIG } from 'components/interfaces/Reports/Reports.constants' +import ReportWidget from 'components/interfaces/Reports/ReportWidget' +import { + renderDailyActiveUsers, + renderNewUsers, +} from 'components/interfaces/Reports/renderers/AuthRenderers' + +export const ApiBotsPage: NextPageWithLayout = () => { + const router = useRouter() + const { ref } = router.query + const config = PRESET_CONFIG[Presets.AUTH] + const hooks = hooksFactory(ref as string, config) + const [dailyActiveUsers, dailyActiveUsersHandlers] = hooks.dailyActiveUsers() + const [newUsers, newUsersHandlers] = hooks.newUsers() + const { isLoading, Layout } = usePresetReport( + [dailyActiveUsers, newUsers], + [dailyActiveUsersHandlers, newUsersHandlers] + ) + return ( + +
    + + +
    +
    + ) +} + +ApiBotsPage.getLayout = (page) => {page} + +export default observer(ApiBotsPage) From 31880c2740a4f77a78aa6ad84d769183ef2ed9b5 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Mon, 5 Dec 2022 17:52:55 +0800 Subject: [PATCH 022/127] feat: refactored hook generation logic --- .../interfaces/Reports/Reports.constants.ts | 233 ++++++++++-------- .../interfaces/Reports/Reports.types.ts | 17 +- .../interfaces/Reports/Reports.utils.tsx | 55 +++-- studio/hooks/analytics/useDbQuery.tsx | 37 +++ .../pages/project/[ref]/reports/api-bots.tsx | 13 +- .../project/[ref]/reports/api-overview.tsx | 23 +- studio/pages/project/[ref]/reports/auth.tsx | 13 +- 7 files changed, 237 insertions(+), 154 deletions(-) create mode 100644 studio/hooks/analytics/useDbQuery.tsx diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index c2a81041b29..2f99d265a3d 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -36,118 +36,135 @@ export const DEFAULT_QUERY_PARAMS = { export const PRESET_CONFIG: Record = { [Presets.API_OVERVIEW]: { title: '', - sql: { - statusCodes: ` - select - timestamp_trunc(timestamp, hour) as timestamp, - r.status_code as status_code, - count(status_code) as count - FROM - edge_logs - CROSS JOIN UNNEST(metadata) AS m - CROSS JOIN UNNEST(m.response) AS r - GROUP BY - timestamp, - status_code - ORDER BY - timestamp ASC`, - errorRate: ` -select - timestamp_trunc(timestamp, hour) as timestamp, - count(timestamp) as count -FROM - edge_logs - LEFT JOIN UNNEST(metadata) AS f1 ON TRUE - LEFT JOIN UNNEST(f1.response) AS f3 ON TRUE -WHERE - f3.status_code >= 400 -GROUP BY - timestamp -ORDER BY - timestamp ASC -`, - requestPaths: ` - select - f2.path as path, - f2.search as query_params, - f2.method as method, - f3.status_code as status_code, - avg(f3.origin_time) as avg_origin_time, - sum(f3.origin_time) as sum, - APPROX_QUANTILES(f3.origin_time, 100) as quantiles, - count(timestamp) as count - FROM - edge_logs - LEFT JOIN UNNEST(metadata) AS f1 ON TRUE - LEFT JOIN UNNEST(f1.request) AS f2 ON TRUE - LEFT JOIN UNNEST(f1.response) AS f3 ON TRUE - GROUP BY - path, - query_params, - method, - status_code - ORDER BY - sum DESC, - count desc, - avg_origin_time DESC - LIMIT 50 -`, + queries: { + statusCodes: { + queryType: 'logs', + sql: ` + select + timestamp_trunc(timestamp, hour) as timestamp, + r.status_code as status_code, + count(status_code) as count + FROM + edge_logs + CROSS JOIN UNNEST(metadata) AS m + CROSS JOIN UNNEST(m.response) AS r + GROUP BY + timestamp, + status_code + ORDER BY + timestamp ASC`, + }, + errorRates: { + queryType: 'logs', + sql: ` + select + timestamp_trunc(timestamp, hour) as timestamp, + count(timestamp) as count + FROM + edge_logs + LEFT JOIN UNNEST(metadata) AS f1 ON TRUE + LEFT JOIN UNNEST(f1.response) AS f3 ON TRUE + WHERE + f3.status_code >= 400 + GROUP BY + timestamp + ORDER BY + timestamp ASC + `, + }, + requestPaths: { + queryType: 'logs', + sql: ` + select + f2.path as path, + f2.search as query_params, + f2.method as method, + f3.status_code as status_code, + avg(f3.origin_time) as avg_origin_time, + sum(f3.origin_time) as sum, + APPROX_QUANTILES(f3.origin_time, 100) as quantiles, + count(timestamp) as count + FROM + edge_logs + LEFT JOIN UNNEST(metadata) AS f1 ON TRUE + LEFT JOIN UNNEST(f1.request) AS f2 ON TRUE + LEFT JOIN UNNEST(f1.response) AS f3 ON TRUE + GROUP BY + path, + query_params, + method, + status_code + ORDER BY + sum DESC, + count desc, + avg_origin_time DESC + LIMIT 50 + `, + }, }, }, [Presets.API_BOTS]: { title: 'API Bots', - sql: { - userAgents: ` -select - h.user_agent as user_agent, - cf.asOrganization as request_source, - count(f.id) as count -from - edge_logs as f - cross join unnest(f.metadata) as m - cross join unnest(m.request) as r - cross join unnest(r.cf) as cf - cross join unnest(r.headers) as h -group by - user_agent, - request_source -order by - count desc -limit 12 -`, - botScores: ` -select - h.cf_connecting_ip as ip, - h.cf_ipcountry as country, - h.user_agent as user_agent, - r.path as path, - b.score as bot_score, - b.verifiedBot as bot_verified, - count(f.id) as count -from - edge_logs as f - cross join unnest(f.metadata) as m - cross join unnest(m.request) as r - cross join unnest(r.cf) as cf - cross join unnest(r.headers) as h - cross join unnest(cf.botManagement) as b -group by - ip, - country, - path, - user_agent, - bot_score, - bot_verified -order by - bot_score desc -limit 20 - `, + queries: { + userAgents: { + queryType: 'logs', + sql: ` + select + h.user_agent as user_agent, + cf.asOrganization as request_source, + count(f.id) as count + from + edge_logs as f + cross join unnest(f.metadata) as m + cross join unnest(m.request) as r + cross join unnest(r.cf) as cf + cross join unnest(r.headers) as h + group by + user_agent, + request_source + order by + count desc + limit 12 + `, + }, + botScores: { + queryType: 'logs', + sql: ` + select + h.cf_connecting_ip as ip, + h.cf_ipcountry as country, + h.user_agent as user_agent, + r.path as path, + b.score as bot_score, + b.verifiedBot as bot_verified, + count(f.id) as count + from + edge_logs as f + cross join unnest(f.metadata) as m + cross join unnest(m.request) as r + cross join unnest(r.cf) as cf + cross join unnest(r.headers) as h + cross join unnest(cf.botManagement) as b + group by + ip, + country, + path, + user_agent, + bot_score, + bot_verified + order by + bot_score desc + limit 20 + `, + }, }, }, [Presets.AUTH]: { title: 'Auth', - sql: { - dailyActiveUsers: ` + queries: { + dailyActiveUsers: { + queryType: 'logs', + sql: ` select timestamp_trunc(timestamp, day) as timestamp, count(distinct json_extract(f.event_message, "$.user_id")) as count @@ -159,7 +176,10 @@ group by order by timestamp `, -newUsers: ` + }, + newUsers: { + queryType: 'logs', + sql: ` select TIMESTAMP_TRUNC(f.timestamp, DAY) as timestamp, count(id) as count @@ -173,7 +193,8 @@ and JSON_VALUE(f.event_message, "$.auth_event.actor_id") is not null and JSON_VALUE(f.event_message, "$.auth_event.action") != "user_repeated_signup" group by timestamp -` +`, + }, }, }, } diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts index 5ace186ea57..f86e32ef143 100644 --- a/studio/components/interfaces/Reports/Reports.types.ts +++ b/studio/components/interfaces/Reports/Reports.types.ts @@ -4,9 +4,24 @@ export enum Presets { AUTH = 'auth', } +export interface BaseReportParams { + iso_timestamp_start: string + iso_timestamp_end: string +} + +export type VariableSql = string | ((params: BaseReportParams) => string) + +export type PresetSql = string | VariableSql + export interface PresetConfig { title: string - sql: Record + queries: Record< + string, + { + sql: PresetSql + queryType: 'logs' | 'db' + } + > } export interface StatusCodesDatum { timestamp: number diff --git a/studio/components/interfaces/Reports/Reports.utils.tsx b/studio/components/interfaces/Reports/Reports.utils.tsx index 659362a0a9c..1f624714b2c 100644 --- a/studio/components/interfaces/Reports/Reports.utils.tsx +++ b/studio/components/interfaces/Reports/Reports.utils.tsx @@ -1,4 +1,5 @@ import { IconRefreshCw } from '@supabase/ui' +import useDbQuery, { DbQueryData, DbQueryHandler } from 'hooks/analytics/useDbQuery' import useLogsQuery, { LogsQueryData, LogsQueryHandlers } from 'hooks/analytics/useLogsQuery' import React from 'react' import { Button } from 'ui' @@ -15,23 +16,35 @@ export const queryParamsToObject = (params: string) => { } // generate hooks based on preset config -type PresetHooks = Record [LogsQueryData, LogsQueryHandlers]> +type PresetHookResult = [LogsQueryData, LogsQueryHandlers] +type PresetHooks = Record PresetHookResult> export const hooksFactory = (projectRef: string, config: PresetConfig) => { - const hooks: PresetHooks = Object.entries(config.sql).reduce((acc, [k, sql]) => { - return { - ...acc, - [k]: () => - useLogsQuery(projectRef, { - sql, - ...DEFAULT_QUERY_PARAMS, - }), - } - }, {}) - console.log(hooks) + const hooks: PresetHooks = Object.entries(config.queries).reduce( + (acc, [k, { sql, queryType }]) => { + if (queryType === 'db') { + return { + ...acc, + [k]: () => useDbQuery(sql), + } + } else { + return { + ...acc, + [k]: () => + useLogsQuery(projectRef, { + sql: sql as string, + ...DEFAULT_QUERY_PARAMS, + }), + } + } + }, + {} + ) return hooks } -export const usePresetReport = (data: LogsQueryData[], handlers: LogsQueryHandlers[]) => { +export const usePresetReport = (hooks: PresetHookResult[]) => { + const data = hooks.map((h) => h[0]) + const handlers = hooks.map((h) => h[1]) const handleRefresh = () => { handlers.forEach((handler) => { handler.runQuery() @@ -41,21 +54,23 @@ export const usePresetReport = (data: LogsQueryData[], handlers: LogsQueryHandle const newParams = { iso_timestamp_start: from || '', iso_timestamp_end: to || '' } handlers.forEach((handler) => { - handler.setParams((prev) => ({ - ...prev, - ...newParams, - })) + if (handler.setParams) { + handler.setParams((prev) => ({ + ...prev, + ...newParams, + })) + } }) } const isLoading = data.map((datum) => datum.isLoading).some((v) => v) - const Layout: React.FC<{title: string}> = ({ title, children }) => ( + const Layout: React.FC<{ title: string }> = ({ title, children }) => (

    {title}

    @@ -45,6 +45,6 @@ export const ApiBotsPage: NextPageWithLayout = () => { ) } -ApiBotsPage.getLayout = (page) => {page} +ApiBotsReport.getLayout = (page) => {page} -export default observer(ApiBotsPage) +export default observer(ApiBotsReport) diff --git a/studio/pages/project/[ref]/reports/api-overview.tsx b/studio/pages/project/[ref]/reports/api-overview.tsx index 7f2881877bb..ad9c4d2ecdc 100644 --- a/studio/pages/project/[ref]/reports/api-overview.tsx +++ b/studio/pages/project/[ref]/reports/api-overview.tsx @@ -13,7 +13,7 @@ import { renderStatusCodesChart, } from 'components/interfaces/Reports/renderers/OverviewRenderers' -export const ApiOverviewPage: NextPageWithLayout = () => { +export const ApiOverviewReport: NextPageWithLayout = () => { const router = useRouter() const { ref } = router.query const config = PRESET_CONFIG[Presets.API_OVERVIEW] @@ -31,7 +31,7 @@ export const ApiOverviewPage: NextPageWithLayout = () => { className="col-span-4 col-start-1" title="API Status Codes" description="Distribution of API responses by status codes." - data={statusCodes[0].logData} + data={statusCodes[0].logData || []} renderer={renderStatusCodesChart} /> { className="col-span-4 col-start-1" title="Error Rate" description="Percentage of API 5XX and 4XX error responses." - data={errorRates[0].logData} + data={errorRates[0].logData || []} renderer={renderErrorRateChart} /> { className="col-span-4 col-start-1" title="Slow API Requests" description="Most frequently used requests, sorted by total querying time" - data={requestPaths[0].logData} + data={requestPaths[0].logData || []} renderer={renderRequestsPathsTable} />
    @@ -57,6 +57,6 @@ export const ApiOverviewPage: NextPageWithLayout = () => { ) } -ApiOverviewPage.getLayout = (page) => {page} +ApiOverviewReport.getLayout = (page) => {page} -export default observer(ApiOverviewPage) +export default observer(ApiOverviewReport) diff --git a/studio/pages/project/[ref]/reports/auth.tsx b/studio/pages/project/[ref]/reports/auth.tsx index 1d1557f2444..5e6c6781a79 100644 --- a/studio/pages/project/[ref]/reports/auth.tsx +++ b/studio/pages/project/[ref]/reports/auth.tsx @@ -13,7 +13,7 @@ import { renderCumulativeUsers, } from 'components/interfaces/Reports/renderers/AuthRenderers' -export const ApiBotsPage: NextPageWithLayout = () => { +export const AuthReport: NextPageWithLayout = () => { const router = useRouter() const { ref } = router.query const config = PRESET_CONFIG[Presets.AUTH] @@ -31,33 +31,33 @@ export const ApiBotsPage: NextPageWithLayout = () => { params={cumulativeUsers[0].params} className="col-span-2 col-start-1" title="Users Over Time" - description="Daily count of confirmed users" + description="Cumulative count of confirmed users" data={cumulativeUsers[0].data!} renderer={renderCumulativeUsers} /> + - ) } -ApiBotsPage.getLayout = (page) => {page} +AuthReport.getLayout = (page) => {page} -export default observer(ApiBotsPage) +export default observer(AuthReport) diff --git a/studio/tests/components/Reports/PresetReport.test.js b/studio/tests/components/Reports/PresetReport.test.js deleted file mode 100644 index 7813c376821..00000000000 --- a/studio/tests/components/Reports/PresetReport.test.js +++ /dev/null @@ -1,84 +0,0 @@ -import dayjs from 'dayjs' -import utc from 'dayjs/plugin/utc' - -dayjs.extend(utc) - -// mock the fetch function -jest.mock('lib/common/fetch') -import { get } from 'lib/common/fetch' - -// mock mobx -jest.mock('mobx-react-lite') -import { observer } from 'mobx-react-lite' -observer.mockImplementation((v) => v) - -// mock the router -jest.mock('next/router') -import { useRouter } from 'next/router' -const defaultRouterMock = () => { - const router = jest.fn() - router.query = {} - router.push = jest.fn() - router.pathname = 'logs/path' - return router -} -useRouter.mockReturnValue(defaultRouterMock()) -// mock usage flags -jest.mock('components/ui/Flag/Flag') -import Flag from 'components/ui/Flag/Flag' -Flag.mockImplementation(({ children }) => <>{children}) -jest.mock('hooks') -import { useFlag } from 'hooks' -useFlag.mockReturnValue(true) - -import { SWRConfig } from 'swr' -jest.mock('components/interfaces/Reports/PresetReport') -import PresetReport from 'components/interfaces/Reports/PresetReport' -PresetReport.mockImplementation((props) => { - const Comp = jest.requireActual('components/interfaces/Reports/PresetReport').default - // wrap with SWR to reset the cache each time - return ( - new Map(), - shouldRetryOnError: false, - }} - > - - - ) -}) - -jest.mock('hooks') -import { useProjectSubscription } from 'hooks' -useProjectSubscription = jest.fn((ref) => ({ - subscription: { - tier: { - supabase_prod_id: 'tier_free', - }, - }, -})) - -import { render, fireEvent, waitFor, screen, act } from '@testing-library/react' -import { Presets } from 'components/interfaces/Reports/Reports.types' -beforeEach(() => { - // reset mocks between tests - get.mockReset() - useRouter.mockReset() - useRouter.mockReturnValue(defaultRouterMock()) -}) - -test('static elements', async () => { - render() - await screen.findByText(/Last 7 days/) - await screen.findAllByText(/Refresh/) -}) - -test('changing date range triggers query refresh', async () => { - render() - await waitFor(() => expect(get).toBeCalled()) - get.mockReset() - const refresh = await screen.findByText(/Refresh/) - fireEvent.click(refresh) - await waitFor(() => expect(get).toBeCalled()) -}) diff --git a/studio/tests/pages/projects/reports/product-reports.test.js b/studio/tests/pages/projects/reports/product-reports.test.js new file mode 100644 index 00000000000..a30e33c1aea --- /dev/null +++ b/studio/tests/pages/projects/reports/product-reports.test.js @@ -0,0 +1,55 @@ +import dayjs from 'dayjs' +import utc from 'dayjs/plugin/utc' +dayjs.extend(utc) + +import { get, post } from 'lib/common/fetch' + +import { useRouter } from 'next/router' +import { render } from '../../../helpers' +import { fireEvent, waitFor, screen } from '@testing-library/react' +import { ApiOverviewReport } from 'pages/project/[ref]/reports/api-overview' +import { ApiBotsReport } from 'pages/project/[ref]/reports/api-bots' +import { AuthReport } from 'pages/project/[ref]/reports/auth' + +beforeEach(() => { + // reset mocks between tests + get.mockReset() + useRouter.mockReset() + useRouter.mockReturnValue({ + query: { ref: '123' }, + }) + get.mockImplementation(async (url) => { + return [{ data: [] }] + }) + post.mockResolvedValue([]) +}) + +describe.each([ + { Page: ApiOverviewReport, contains: ['API Overview'] }, + { Page: ApiBotsReport, contains: ['API Bots'] }, + { Page: AuthReport, contains: ['Auth'] }, +])('$Page rendering', ({ Page, contains }) => { + test(`contains ${contains}`, async () => { + render() + + contains.forEach((word) => { + expect(screen.findByText(word)).resolves.toBeTruthy() + }) + }) + test('static elements', async () => { + render() + await screen.findByText(/Last 7 days/) + await screen.findAllByText(/Refresh/) + }) + + test('changing date range triggers query refresh', async () => { + render() + await waitFor(() => expect(get).toBeCalled()) + get.mockReset() + const refresh = await screen.findByText(/Refresh/) + fireEvent.click(refresh) + + const calls = get.mock.calls.concat(post.mock.calls) + expect(calls.length).toBeGreaterThan(0) + }) +}) From 2c7890e4f2bde2b69c9c68179571bfe46942172c Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Mon, 12 Dec 2022 19:02:08 +0800 Subject: [PATCH 025/127] fix: update querying revalidation to reduce mutliple querying --- studio/hooks/analytics/useDbQuery.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/studio/hooks/analytics/useDbQuery.tsx b/studio/hooks/analytics/useDbQuery.tsx index 9dbbf902440..45d9236d4f9 100644 --- a/studio/hooks/analytics/useDbQuery.tsx +++ b/studio/hooks/analytics/useDbQuery.tsx @@ -21,6 +21,9 @@ const useDbQuery: UseDbQuery = (sql, params = DEFAULT_QUERY_PARAMS) => { mutate, } = useSWR(resolvedSql, async () => await meta.query(resolvedSql), { revalidateOnFocus: false, + revalidateIfStale: false, + revalidateOnReconnect: false, + dedupingInterval: 2000, }) const error = swrError || (typeof data === 'object' ? data?.error : '') From 233f023698b93177cd063ecd1e90ad2c034fbcb9 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Mon, 12 Dec 2022 20:54:42 +0800 Subject: [PATCH 026/127] feat: fixed auth daily active users query --- studio/components/interfaces/Reports/ReportWidget.tsx | 2 +- .../interfaces/Reports/Reports.constants.ts | 9 ++++----- .../interfaces/Reports/renderers/AuthRenderers.tsx | 7 +++++-- studio/pages/project/[ref]/reports/api-bots.tsx | 11 ++++++----- studio/pages/project/[ref]/reports/api-overview.tsx | 2 +- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/studio/components/interfaces/Reports/ReportWidget.tsx b/studio/components/interfaces/Reports/ReportWidget.tsx index 84d54267af2..09e03029f59 100644 --- a/studio/components/interfaces/Reports/ReportWidget.tsx +++ b/studio/components/interfaces/Reports/ReportWidget.tsx @@ -19,7 +19,7 @@ const ReportWidget: React.FC = (props) => { const router = useRouter() const { ref } = router.query return ( - +
    diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index 59ef87fd261..f4811451e16 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -167,14 +167,13 @@ export const PRESET_CONFIG: Record = { sql: ` select timestamp_trunc(timestamp, day) as timestamp, - count(distinct json_extract(f.event_message, "$.user_id")) as count -from gotrue_logs f -cross join unnest(f.metadata) as m -where json_extract(f.event_message, "$.user_id") is not null + count(distinct json_value(f.event_message, "$.user_id")) as count +from auth_logs f +where json_value(f.event_message, "$.action") = "login" group by timestamp order by - timestamp + timestamp desc `, }, cumulativeUsers: { diff --git a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx index f85ddabeece..7fe04969540 100644 --- a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx @@ -14,7 +14,7 @@ export const renderCumulativeUsers = ( ) => { + const last = props.data?.[props.data.length - 1]?.count + return (
    diff --git a/studio/pages/project/[ref]/reports/api-bots.tsx b/studio/pages/project/[ref]/reports/api-bots.tsx index 5da674c7a09..3df85b74abf 100644 --- a/studio/pages/project/[ref]/reports/api-bots.tsx +++ b/studio/pages/project/[ref]/reports/api-bots.tsx @@ -6,7 +6,10 @@ import { Presets } from 'components/interfaces/Reports/Reports.types' import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout' import { hooksFactory, usePresetReport } from 'components/interfaces/Reports/Reports.utils' import { PRESET_CONFIG } from 'components/interfaces/Reports/Reports.constants' -import { renderBotScores, renderUserAgents } from 'components/interfaces/Reports/renderers/ApiBotsRenderers' +import { + renderBotScores, + renderUserAgents, +} from 'components/interfaces/Reports/renderers/ApiBotsRenderers' import ReportWidget from 'components/interfaces/Reports/ReportWidget' export const ApiBotsReport: NextPageWithLayout = () => { @@ -16,12 +19,10 @@ export const ApiBotsReport: NextPageWithLayout = () => { const hooks = hooksFactory(ref as string, config) const userAgents = hooks.userAgents() const botScores = hooks.botScores() - const { isLoading, Layout } = usePresetReport( - [userAgents, botScores], - ) + const { isLoading, Layout } = usePresetReport([userAgents, botScores]) return ( -
    +
    { const { isLoading, Layout } = usePresetReport([statusCodes, errorRates, requestPaths]) return ( -
    +
    Date: Mon, 12 Dec 2022 20:56:54 +0800 Subject: [PATCH 027/127] fix: add no user agent text --- .../interfaces/Reports/renderers/ApiBotsRenderers.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx b/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx index 22f47142c32..9c92a616dad 100644 --- a/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx @@ -25,7 +25,11 @@ export const renderUserAgents = ( return ( - {row.user_agent} + {row.user_agent ? ( + row.user_agent + ) : ( + No user agent + )} {row.request_source} @@ -87,4 +91,4 @@ export const renderBotScores = ( } /> ) -} \ No newline at end of file +} From 608bb492240ba95b05a520182591151d410d51e3 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Mon, 12 Dec 2022 21:55:54 +0800 Subject: [PATCH 028/127] feat: add failed migration counts widget for auth report --- .../interfaces/Reports/ReportWidget.tsx | 37 +++++++++++++++++-- .../interfaces/Reports/Reports.constants.ts | 10 +++++ .../interfaces/Reports/Statistic.tsx | 36 ++++++++++++++++++ .../Reports/renderers/AuthRenderers.tsx | 14 ++++++- studio/pages/project/[ref]/reports/auth.tsx | 19 ++++++++-- 5 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 studio/components/interfaces/Reports/Statistic.tsx diff --git a/studio/components/interfaces/Reports/ReportWidget.tsx b/studio/components/interfaces/Reports/ReportWidget.tsx index 09e03029f59..2594c10c646 100644 --- a/studio/components/interfaces/Reports/ReportWidget.tsx +++ b/studio/components/interfaces/Reports/ReportWidget.tsx @@ -4,11 +4,14 @@ import Panel from 'components/ui/Panel' import { useRouter } from 'next/router' import { BaseReportParams } from './Reports.types' import { LogsEndpointParams } from '../Settings/Logs' +import * as Tooltip from '@radix-ui/react-tooltip' +import { IconHelpCircle } from '@supabase/ui' export interface ReportWidgetProps { data: T[] title: string - description: string + description?: string + tooltip?: string className?: string renderer: (props: ReportWidgetProps) => React.ReactNode params: BaseReportParams | LogsEndpointParams @@ -19,11 +22,37 @@ const ReportWidget: React.FC = (props) => { const router = useRouter() const { ref } = router.query return ( - +
    -

    {props.title}

    +
    + +

    {props.title}

    {' '} + {props?.tooltip && ( + + + + + + +
    + {props.tooltip} +
    +
    +
    + )} +

    {props.description}

    = (props) => { title="Actions..." as="span" type="text" - icon={} + icon={} >
    diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index f4811451e16..bf67eb7c019 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -162,6 +162,16 @@ export const PRESET_CONFIG: Record = { [Presets.AUTH]: { title: 'Auth', queries: { + failedMigrations: { + queryType: "logs", + sql: ` +select count(f.id) +from auth_logs +where json_value(f.event_message, "$.level") = "fatal" + and regexp_contains(f.event_message, "error executing migrations") +limit 1 +` + }, dailyActiveUsers: { queryType: 'logs', sql: ` diff --git a/studio/components/interfaces/Reports/Statistic.tsx b/studio/components/interfaces/Reports/Statistic.tsx new file mode 100644 index 00000000000..9f21c6feefc --- /dev/null +++ b/studio/components/interfaces/Reports/Statistic.tsx @@ -0,0 +1,36 @@ +import React from 'react' + +interface Props { + value: string | number + startingValue?: string | number + prefix?: string + suffix?: string + valueFormatter?: (value: Props['value']) => string +} + +const Statistic: React.FC = ({ prefix, suffix, value, startingValue }) => { + const isNumber = typeof value === 'number' && !Number.isNaN(value) + const percentageChange = + startingValue && isNumber ? Number(startingValue) / Number(value) - 1 : null + return ( +
    +
    + {prefix} + {value} + {suffix} +
    + {startingValue && ( +
    + {isNumber && percentageChange !== null && ( + + {Number(percentageChange * 100).toFixed(1)} + {percentageChange > 0 ? 'increase' : 'decrease'} + + )} +
    + )} +
    + ) +} + +export default Statistic diff --git a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx index 7fe04969540..282e4e09bd6 100644 --- a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx @@ -1,7 +1,17 @@ import { BarChart } from 'components/to-be-cleaned/Charts/ChartRenderer' import { DATETIME_FORMAT } from '../Reports.constants' import { ReportWidgetProps } from '../ReportWidget' - +import Statistic from '../Statistic' +export const renderFailedMigrations = ( + props: ReportWidgetProps<{ + timestamp: string + count: number + }> +) => { + console.log(props.data) + const count = props.data.length === 0 ? 0: props?.data?.[0]?.count + return +} export const renderCumulativeUsers = ( props: ReportWidgetProps<{ count: number @@ -15,6 +25,7 @@ export const renderCumulativeUsers = ( data={props.data} attribute="count" label="" + minimalHeader highlightedValue={total} customDateFormat={DATETIME_FORMAT} noDataMessage={'No confirmed users yet'} @@ -36,6 +47,7 @@ export const renderNewUsers = ( data={props.data} attribute="count" label="" + minimalHeader highlightedValue={sum} customDateFormat={DATETIME_FORMAT} noDataMessage={'No new user signups during this time period'} diff --git a/studio/pages/project/[ref]/reports/auth.tsx b/studio/pages/project/[ref]/reports/auth.tsx index 5e6c6781a79..42e22d60ade 100644 --- a/studio/pages/project/[ref]/reports/auth.tsx +++ b/studio/pages/project/[ref]/reports/auth.tsx @@ -11,6 +11,7 @@ import { renderDailyActiveUsers, renderNewUsers, renderCumulativeUsers, + renderFailedMigrations, } from 'components/interfaces/Reports/renderers/AuthRenderers' export const AuthReport: NextPageWithLayout = () => { @@ -21,15 +22,25 @@ export const AuthReport: NextPageWithLayout = () => { const dailyActiveUsers = hooks.dailyActiveUsers() const newUsers = hooks.newUsers() const cumulativeUsers = hooks.cumulativeUsers() + const failedMigrations = hooks.failedMigrations() const { isLoading, Layout } = usePresetReport([dailyActiveUsers, newUsers, cumulativeUsers]) return ( -
    +
    + { { Date: Mon, 12 Dec 2022 21:58:57 +0800 Subject: [PATCH 029/127] chore: remove console.logging --- studio/components/interfaces/Reports/renderers/AuthRenderers.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx index 282e4e09bd6..0444185508a 100644 --- a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx @@ -8,7 +8,6 @@ export const renderFailedMigrations = ( count: number }> ) => { - console.log(props.data) const count = props.data.length === 0 ? 0: props?.data?.[0]?.count return } From b91da2d3bd5af68ceb0baab6bad4dc4d3f8f1f1c Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Wed, 14 Dec 2022 18:52:47 +0800 Subject: [PATCH 030/127] feat: StackedBarChart implementation --- .../components/ui/Charts/Charts.constants.ts | 18 +++ studio/components/ui/Charts/Charts.types.tsx | 16 ++- studio/components/ui/Charts/Charts.utils.ts | 38 ------ studio/components/ui/Charts/Charts.utils.tsx | 118 ++++++++++++++++ .../ui/Charts/StackedBarChart.stories.tsx | 81 +++++++++++ .../components/ui/Charts/StackedBarChart.tsx | 129 ++++++++++++++++++ 6 files changed, 360 insertions(+), 40 deletions(-) delete mode 100644 studio/components/ui/Charts/Charts.utils.ts create mode 100644 studio/components/ui/Charts/Charts.utils.tsx create mode 100644 studio/components/ui/Charts/StackedBarChart.stories.tsx create mode 100644 studio/components/ui/Charts/StackedBarChart.tsx diff --git a/studio/components/ui/Charts/Charts.constants.ts b/studio/components/ui/Charts/Charts.constants.ts index d423e9129e9..f9cff43f861 100644 --- a/studio/components/ui/Charts/Charts.constants.ts +++ b/studio/components/ui/Charts/Charts.constants.ts @@ -9,6 +9,24 @@ export const CHART_COLORS = { GREEN_2: 'var(--colors-brand6)', } +// refer to packages/ui/radix-colors.js for full list of colors +export const STACK_COLORS = [ + ['brand', 8], + ['mint', 8], + ['blue', 8], + ['sky', 7], + ['bronze', 8], + ['violet', 7], + ['indigo', 8], + ['tomato', 7], + ['orange', 8], + ['olive', 8], +].map(([color, n]) => ({ + lighter: `var(--colors-${color}${(n as number) - 1})`, + base: `var(--colors-${color}${n})`, + darker: `var(--colors-${color}${(n as number) + 1})`, +})) + export const USAGE_COLORS = { 200: 'var(--colors-brand9)', 201: 'var(--colors-brand8)', diff --git a/studio/components/ui/Charts/Charts.types.tsx b/studio/components/ui/Charts/Charts.types.tsx index 239d1f29368..3332fc441ae 100644 --- a/studio/components/ui/Charts/Charts.types.tsx +++ b/studio/components/ui/Charts/Charts.types.tsx @@ -1,3 +1,5 @@ +import { AreaProps } from 'recharts' + export interface CommonChartProps extends Pick< HeaderType, @@ -9,8 +11,18 @@ export interface CommonChartProps | 'minimalHeader' | 'displayDateInUtc' > { - title?: string; + title?: string className?: string + isLoading?: boolean + size?: 'small' | 'normal' | 'large' +} + +export interface StackedChartProps extends CommonChartProps { + xAxisKey: string + xAxisFormatAsDate?: boolean + dateFormat?: string + yAxisKey: string + stackKey: string } export type HeaderType = { @@ -18,7 +30,7 @@ export type HeaderType = { focus: number | null format?: string highlightedValue?: number | string - highlightedLabel?: string + highlightedLabel?: string data: D[] customDateFormat?: string label: string diff --git a/studio/components/ui/Charts/Charts.utils.ts b/studio/components/ui/Charts/Charts.utils.ts deleted file mode 100644 index 8683067c1b9..00000000000 --- a/studio/components/ui/Charts/Charts.utils.ts +++ /dev/null @@ -1,38 +0,0 @@ - -/** - * Auto formats a number to a default precision if it is a float - * - * @example - * numberFormatter(123) // "123" - * numberFormatter(123.123) // "123.12" - * numberFormatter(123, 2) // "123.00" - */ -export const numberFormatter = (num: number, precision = 2) => isFloat(num) ? precisionFormatter(num, precision) : String(num); - - -/** - * Tests if a number is a float. - * - * @example - * isFloat(123) // false - * isFloat(123.123) // true - */ -export const isFloat = (num: number) => String(num).includes(".") - - -/** - * Formats a number to a particular precision. - * - * @example - * precisionFormatter(123, 2) // "123.00" - * precisionFormatter(123.123, 2) // "123.12" - */ -export const precisionFormatter = (num: number, precision: number): string => { - if (isFloat(num)) { - const [head, tail] = String(num).split(".") - return head + "." + tail.slice(0, precision) - } else { - // pad int with 0 - return String(num) + "." + ("0".repeat(precision)) - } -} \ No newline at end of file diff --git a/studio/components/ui/Charts/Charts.utils.tsx b/studio/components/ui/Charts/Charts.utils.tsx new file mode 100644 index 00000000000..687603d651e --- /dev/null +++ b/studio/components/ui/Charts/Charts.utils.tsx @@ -0,0 +1,118 @@ +import { DATETIME_FORMAT } from 'components/interfaces/Reports/Reports.constants' +import dayjs from 'dayjs' +import React, { useMemo } from 'react' +import { ResponsiveContainer } from 'recharts' +import { DateTimeFormats } from './Charts.constants' +import { CommonChartProps, StackedChartProps } from './Charts.types' + +/** + * Auto formats a number to a default precision if it is a float + * + * @example + * numberFormatter(123) // "123" + * numberFormatter(123.123) // "123.12" + * numberFormatter(123, 2) // "123.00" + */ +export const numberFormatter = (num: number, precision = 2) => + isFloat(num) ? precisionFormatter(num, precision) : String(num) + +/** + * Tests if a number is a float. + * + * @example + * isFloat(123) // false + * isFloat(123.123) // true + */ +export const isFloat = (num: number) => String(num).includes('.') + +/** + * Formats a number to a particular precision. + * + * @example + * precisionFormatter(123, 2) // "123.00" + * precisionFormatter(123.123, 2) // "123.12" + */ +export const precisionFormatter = (num: number, precision: number): string => { + if (isFloat(num)) { + const [head, tail] = String(num).split('.') + return head + '.' + tail.slice(0, precision) + } else { + // pad int with 0 + return String(num) + '.' + '0'.repeat(precision) + } +} + +export const timestampFormatter = (value: string, format: string= DateTimeFormats.FULL) => dayjs(value).format(format) + +/** + * Hook to create common wrapping components, perform data transformations + * returns a Container component and the minHeight set + */ +export const useChartSize = (size: CommonChartProps['size'] = 'normal') => { + const minHeight = { small: 120, normal: 160, large: 280 }[size] + const Container: React.FC = useMemo( + () => + ({ children }) => + ( + + {children as JSX.Element} + + ), + [size] + ) + return { + Container, + minHeight, + } +} + +/** + * Transforms data points into a stacked data structure that can be consumed by recharts + */ +export const useStacked = ({ + data, + xAxisKey, + yAxisKey, + stackKey, + variant = 'values', +}: Pick, 'xAxisKey' | 'yAxisKey' | 'stackKey'> & { + variant?: 'values' | 'percentages' +} & Pick>, 'data'>) => { + const stackedData = useMemo(() => { + if (!data) return [] + const mapping = data.reduce((acc, datum) => { + const x = datum[xAxisKey] + const y = datum[yAxisKey] + const s = datum[stackKey] + if (!acc[x]) { + acc[x] = {} + } + + acc[x][s] = y + return acc + }, {} as Record>) + + const flattened = Object.entries(mapping).map(([x, sMap]) => { + let map = sMap + if (variant === 'percentages') { + const sum = Object.values(sMap).reduce((acc, value) => acc + value, 0) + map = Object.entries(sMap).reduce((acc, [key, value]) => { + acc[key] = value !== 0 ? value / sum : 0 + return acc + }, {} as any) + } + return { + ...map, + [xAxisKey]: x, + } + }) + return flattened + }, [JSON.stringify(data)]) + const dataKeys = useMemo(() => { + return Object.keys(stackedData[0] || {}) + .filter((k) => k !== xAxisKey && k !== yAxisKey) + .sort() + }, [JSON.stringify(stackedData[0] || {})]) + + return { dataKeys, stackedData } +} diff --git a/studio/components/ui/Charts/StackedBarChart.stories.tsx b/studio/components/ui/Charts/StackedBarChart.stories.tsx new file mode 100644 index 00000000000..3f3feabebb3 --- /dev/null +++ b/studio/components/ui/Charts/StackedBarChart.stories.tsx @@ -0,0 +1,81 @@ +import React from 'react' +import { ComponentStory, ComponentMeta } from '@storybook/react' +import dayjs from 'dayjs' +import Chart from './StackedBarChart' +import { StackedChartProps } from './Charts.types' +import { STACK_COLORS } from './Charts.constants' + +export default { + title: 'Charts/StackedBarChart', +} as ComponentMeta + +const DATA = [290, 430, 649, 422, 321].flatMap((value, index) => [ + { + count: value, + level: 'error', + timestamp: dayjs().subtract(index, 'day').toISOString(), + }, + { + count: value * 2, + level: 'info', + timestamp: dayjs().subtract(index, 'day').toISOString(), + }, + { + count: value * 1.3, + level: 'warn', + timestamp: dayjs().subtract(index, 'day').toISOString(), + }, +]) + +const PROPS = { + title: 'Memory usage', + xAxisKey: 'timestamp', + yAxisKey: 'count', + stackKey: 'level', + data: DATA, +} +export const StackedBarChart = () => ( +
    + Stack Colors +
    + {STACK_COLORS.map((c) => ( +
    + ))} +
    + + Examples +
    + {[ + { title: 'Normal', props: {} }, + { title: 'No title', props: { title: undefined } }, + { title: 'Minimal Header', props: { minimalHeader: true } }, + { title: 'Percentage Bars', props: { variant: 'percentages' as const } }, + { + title: 'Formatter - Auto 2 Precision for floats', + props: { data: DATA.map((d) => ({ ...d, count: d.count + 5.12 })) }, + }, + { title: 'UTC Dates', props: { displayDateInUtc: true } }, + { title: 'Custom date format', props: { customDateFormat: 'HH:mm' } }, + { title: 'Value format (c suffix)', props: { format: 'c' } }, + { title: 'Minimal Value format', props: { minimalHeader: true, format: 'c' } }, + { title: 'No data', props: { data: [] } }, + ].map(({ title, props }) => ( +
    + {title} +
    + +
    +
    + ))} +
    +
    +) + +const ExampleName: React.FC = ({ children }) => ( +

    + {children} +

    +) diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx new file mode 100644 index 00000000000..126e1eb2d0c --- /dev/null +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -0,0 +1,129 @@ +import dayjs from 'dayjs' +import { useState } from 'react' +import { BarChart, Bar, XAxis, Tooltip, Legend, Cell } from 'recharts' +import ChartHeader from './ChartHeader' +import { CHART_COLORS, STACK_COLORS, DateTimeFormats } from './Charts.constants' +import { CommonChartProps, StackedChartProps } from './Charts.types' +import { timestampFormatter, useChartSize, useStacked } from './Charts.utils' +import { precisionFormatter } from './Charts.utils' +import NoDataPlaceholder from './NoDataPlaceholder' + +interface Props extends CommonChartProps { + xAxisKey: string + yAxisKey: string + stackKey: string + onBarClick?: () => void + variant?: 'values' | 'percentages' + xAxisFormatAsDate?: boolean +} +const StackedBarChart: React.FC = ({ + size, + data, + xAxisKey, + stackKey, + yAxisKey, + customDateFormat = DateTimeFormats.FULL, + title, + format, + minimalHeader = false, + onBarClick, + variant, + xAxisFormatAsDate = true, +}) => { + const { Container } = useChartSize(size) + const { dataKeys, stackedData } = useStacked({ data, xAxisKey, stackKey, yAxisKey, variant }) + const [focusDataIndex, setFocusDataIndex] = useState(null) + if (data.length === 0) return + return ( +
    + + + { + console.log(e) + if (e.activeTooltipIndex !== focusDataIndex) { + setFocusDataIndex(e.activeTooltipIndex) + } + }} + onMouseLeave={() => setFocusDataIndex(null)} + > + + + + {/* */} + {/* */} + {dataKeys.map((datum, stackIndex) => ( + + {stackedData?.map((_entry: unknown, index: any) => ( + + ))} + + ))} + timestampFormatter(label) : undefined} + formatter={(value: number) => { + if (variant === 'percentages') { + return precisionFormatter(value * 100, 1) + '%' + } + return value + }} + cursor={false} + labelClassName="text-white" + contentStyle={{ backgroundColor: '#444444', borderColor: '#444444', fontSize: '12px' }} + wrapperClassName="bg-gray-600 rounded" + /> + + + {/* {console.log(data)} */} + {stackedData && stackedData[0] && ( +
    + {dayjs(stackedData[0][xAxisKey]).format(customDateFormat)} + {dayjs(stackedData[data?.length - 1]?.[xAxisKey]).format(customDateFormat)} +
    + )} +
    + ) +} + +export default StackedBarChart From 4c7160d07f6b172eea2589c66a550063607f9b2e Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Thu, 15 Dec 2022 14:24:21 +0800 Subject: [PATCH 031/127] feat: add utc datetime rendering for StackedBarChart --- studio/components/ui/Charts/Charts.utils.tsx | 21 +++++++++++++- .../components/ui/Charts/StackedBarChart.tsx | 29 ++++++++++++++----- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/studio/components/ui/Charts/Charts.utils.tsx b/studio/components/ui/Charts/Charts.utils.tsx index 687603d651e..ea260c5ff57 100644 --- a/studio/components/ui/Charts/Charts.utils.tsx +++ b/studio/components/ui/Charts/Charts.utils.tsx @@ -4,6 +4,8 @@ import React, { useMemo } from 'react' import { ResponsiveContainer } from 'recharts' import { DateTimeFormats } from './Charts.constants' import { CommonChartProps, StackedChartProps } from './Charts.types' +import utc from 'dayjs/plugin/utc' +dayjs.extend(utc) /** * Auto formats a number to a default precision if it is a float @@ -42,7 +44,24 @@ export const precisionFormatter = (num: number, precision: number): string => { } } -export const timestampFormatter = (value: string, format: string= DateTimeFormats.FULL) => dayjs(value).format(format) +/** + * Formats a timestamp. + * Optionally formats the string to UTC + * @param value + * @param format + * @param utc + * @returns + */ +export const timestampFormatter = ( + value: string, + format: string = DateTimeFormats.FULL, + utc: boolean = false +) => { + if (utc) { + return dayjs.utc(value).format(format) + } + return dayjs(value).format(format) +} /** * Hook to create common wrapping components, perform data transformations diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx index 126e1eb2d0c..be4771fa158 100644 --- a/studio/components/ui/Charts/StackedBarChart.tsx +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -1,13 +1,11 @@ -import dayjs from 'dayjs' import { useState } from 'react' import { BarChart, Bar, XAxis, Tooltip, Legend, Cell } from 'recharts' import ChartHeader from './ChartHeader' import { CHART_COLORS, STACK_COLORS, DateTimeFormats } from './Charts.constants' -import { CommonChartProps, StackedChartProps } from './Charts.types' +import { CommonChartProps } from './Charts.types' import { timestampFormatter, useChartSize, useStacked } from './Charts.utils' import { precisionFormatter } from './Charts.utils' import NoDataPlaceholder from './NoDataPlaceholder' - interface Props extends CommonChartProps { xAxisKey: string yAxisKey: string @@ -15,6 +13,7 @@ interface Props extends CommonChartProps { onBarClick?: () => void variant?: 'values' | 'percentages' xAxisFormatAsDate?: boolean + displayDateInUtc?: boolean } const StackedBarChart: React.FC = ({ size, @@ -29,6 +28,7 @@ const StackedBarChart: React.FC = ({ onBarClick, variant, xAxisFormatAsDate = true, + displayDateInUtc, }) => { const { Container } = useChartSize(size) const { dataKeys, stackedData } = useStacked({ data, xAxisKey, stackKey, yAxisKey, variant }) @@ -101,7 +101,11 @@ const StackedBarChart: React.FC = ({ ))} timestampFormatter(label) : undefined} + labelFormatter={ + xAxisFormatAsDate + ? (label) => timestampFormatter(label, customDateFormat, displayDateInUtc) + : undefined + } formatter={(value: number) => { if (variant === 'percentages') { return precisionFormatter(value * 100, 1) + '%' @@ -115,11 +119,22 @@ const StackedBarChart: React.FC = ({ /> - {/* {console.log(data)} */} {stackedData && stackedData[0] && (
    - {dayjs(stackedData[0][xAxisKey]).format(customDateFormat)} - {dayjs(stackedData[data?.length - 1]?.[xAxisKey]).format(customDateFormat)} + + {timestampFormatter( + stackedData[0][xAxisKey] as string, + customDateFormat, + displayDateInUtc + )} + + + {timestampFormatter( + stackedData[stackedData?.length - 1][xAxisKey] as string, + customDateFormat, + displayDateInUtc + )} +
    )}
    From c2116126c8808416a0b016a3c9f12f9deb25a22d Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Thu, 15 Dec 2022 14:26:26 +0800 Subject: [PATCH 032/127] feat: add optoinal legend hiding --- .../ui/Charts/StackedBarChart.stories.tsx | 1 + .../components/ui/Charts/StackedBarChart.tsx | 20 +++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/studio/components/ui/Charts/StackedBarChart.stories.tsx b/studio/components/ui/Charts/StackedBarChart.stories.tsx index 3f3feabebb3..12e12d2c2a6 100644 --- a/studio/components/ui/Charts/StackedBarChart.stories.tsx +++ b/studio/components/ui/Charts/StackedBarChart.stories.tsx @@ -49,6 +49,7 @@ export const StackedBarChart = () => ( { title: 'Normal', props: {} }, { title: 'No title', props: { title: undefined } }, { title: 'Minimal Header', props: { minimalHeader: true } }, + { title: 'Hide Legend', props: { hideLegend: true } }, { title: 'Percentage Bars', props: { variant: 'percentages' as const } }, { title: 'Formatter - Auto 2 Precision for floats', diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx index be4771fa158..0141b85bd2e 100644 --- a/studio/components/ui/Charts/StackedBarChart.tsx +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -14,6 +14,7 @@ interface Props extends CommonChartProps { variant?: 'values' | 'percentages' xAxisFormatAsDate?: boolean displayDateInUtc?: boolean + hideLegend?: boolean } const StackedBarChart: React.FC = ({ size, @@ -29,6 +30,7 @@ const StackedBarChart: React.FC = ({ variant, xAxisFormatAsDate = true, displayDateInUtc, + hideLegend = false, }) => { const { Container } = useChartSize(size) const { dataKeys, stackedData } = useStacked({ data, xAxisKey, stackKey, yAxisKey, variant }) @@ -61,14 +63,14 @@ const StackedBarChart: React.FC = ({ }} onMouseLeave={() => setFocusDataIndex(null)} > - - + {!hideLegend && ( + + )} = ({ axisLine={{ stroke: CHART_COLORS.AXIS }} tickLine={{ stroke: CHART_COLORS.AXIS }} /> - {/* */} - {/* */} {dataKeys.map((datum, stackIndex) => ( Date: Thu, 15 Dec 2022 14:29:27 +0800 Subject: [PATCH 033/127] fix: tweak position of legend --- studio/components/ui/Charts/StackedBarChart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx index 0141b85bd2e..099e96777ae 100644 --- a/studio/components/ui/Charts/StackedBarChart.tsx +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -65,7 +65,7 @@ const StackedBarChart: React.FC = ({ > {!hideLegend && ( Date: Thu, 15 Dec 2022 14:32:31 +0800 Subject: [PATCH 034/127] feat: added tooltip formatting for suffix --- studio/components/ui/Charts/StackedBarChart.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx index 099e96777ae..ed8b0ea51d9 100644 --- a/studio/components/ui/Charts/StackedBarChart.tsx +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -107,10 +107,12 @@ const StackedBarChart: React.FC = ({ : undefined } formatter={(value: number) => { + let val: string = String(value) if (variant === 'percentages') { - return precisionFormatter(value * 100, 1) + '%' + val = precisionFormatter(value * 100, 1) + '%' } - return value + val += (format || '') + return val }} cursor={false} labelClassName="text-white" From 2d5602067fc773aa537ee76f3882dcf51508b476 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 16 Dec 2022 15:16:37 +0800 Subject: [PATCH 035/127] feat: add value rendering in tooltip alongside percentage render, add testing for stacked chart utils --- package-lock.json | 75 ++++++++++++++++++- studio/components/ui/Charts/Charts.utils.tsx | 39 ++++++---- .../ui/Charts/StackedBarChart.stories.tsx | 8 +- .../components/ui/Charts/StackedBarChart.tsx | 31 +++++--- studio/package.json | 5 +- .../components/ui/Charts/Charts.utils.test.ts | 60 ++++++++++++--- 6 files changed, 172 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index d002b6210bb..2f08bafa685 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9827,6 +9827,36 @@ "react-dom": "<18.0.0" } }, + "node_modules/@testing-library/react-hooks": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz", + "integrity": "sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "react-error-boundary": "^3.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0", + "react": "^16.9.0 || ^17.0.0", + "react-dom": "^16.9.0 || ^17.0.0", + "react-test-renderer": "^16.9.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-test-renderer": { + "optional": true + } + } + }, "node_modules/@testing-library/user-event": { "version": "13.5.0", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", @@ -29842,6 +29872,22 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "node_modules/react-error-boundary": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-fast-compare": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", @@ -37621,8 +37667,9 @@ "@storybook/react": "^6.4.19", "@storybook/testing-library": "^0.0.9", "@tailwindcss/typography": "^0.5.2", - "@testing-library/dom": "^8.11.1", - "@testing-library/react": "^12.1.2", + "@testing-library/dom": "^8.19.0", + "@testing-library/react": "^12.1.5", + "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^13.5.0", "@types/blueimp-md5": "^2.18.0", "@types/crypto-js": "^4.0.2", @@ -44568,6 +44615,16 @@ "@types/react-dom": "<18.0.0" } }, + "@testing-library/react-hooks": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz", + "integrity": "sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "react-error-boundary": "^3.1.0" + } + }, "@testing-library/user-event": { "version": "13.5.0", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", @@ -60519,6 +60576,15 @@ } } }, + "react-error-boundary": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5" + } + }, "react-fast-compare": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", @@ -63325,8 +63391,9 @@ "@tailwindcss/typography": "^0.5.2", "@tanstack/react-query": "^4.16.1", "@tanstack/react-query-devtools": "^4.16.1", - "@testing-library/dom": "^8.11.1", - "@testing-library/react": "^12.1.2", + "@testing-library/dom": "^8.19.0", + "@testing-library/react": "^12.1.5", + "@testing-library/react-hooks": "*", "@testing-library/user-event": "^13.5.0", "@types/blueimp-md5": "^2.18.0", "@types/crypto-js": "^4.0.2", diff --git a/studio/components/ui/Charts/Charts.utils.tsx b/studio/components/ui/Charts/Charts.utils.tsx index ea260c5ff57..af6c33d77db 100644 --- a/studio/components/ui/Charts/Charts.utils.tsx +++ b/studio/components/ui/Charts/Charts.utils.tsx @@ -111,20 +111,10 @@ export const useStacked = ({ return acc }, {} as Record>) - const flattened = Object.entries(mapping).map(([x, sMap]) => { - let map = sMap - if (variant === 'percentages') { - const sum = Object.values(sMap).reduce((acc, value) => acc + value, 0) - map = Object.entries(sMap).reduce((acc, [key, value]) => { - acc[key] = value !== 0 ? value / sum : 0 - return acc - }, {} as any) - } - return { - ...map, - [xAxisKey]: x, - } - }) + const flattened = Object.entries(mapping).map(([x, sMap]) => ({ + ...sMap, + [xAxisKey]: Number.isNaN(Number(x)) ? x : Number(x), + })) return flattened }, [JSON.stringify(data)]) const dataKeys = useMemo(() => { @@ -133,5 +123,24 @@ export const useStacked = ({ .sort() }, [JSON.stringify(stackedData[0] || {})]) - return { dataKeys, stackedData } + const percentagesStackedData = useMemo(() => { + if (variant !== 'percentages') return + + return stackedData.map((stack) => { + const entries = Object.entries(stack) as Array<[string, number]> + let map + const sum = entries + .filter(([key, _value]) => dataKeys.includes(key)) + .reduce((acc, [_key, value]) => acc + value, 0) + map = entries.reduce((acc, [key, value]) => { + if (!dataKeys.includes(key)) { + return { ...acc, [key]: value } + } + return { ...acc, [key]: value !== 0 ? value / sum : 0 } + }, {} as any) + return map + }) + }, [JSON.stringify(stackedData)]) + + return { dataKeys, stackedData, percentagesStackedData } } diff --git a/studio/components/ui/Charts/StackedBarChart.stories.tsx b/studio/components/ui/Charts/StackedBarChart.stories.tsx index 12e12d2c2a6..a6703a51727 100644 --- a/studio/components/ui/Charts/StackedBarChart.stories.tsx +++ b/studio/components/ui/Charts/StackedBarChart.stories.tsx @@ -36,10 +36,10 @@ const PROPS = { } export const StackedBarChart = () => (
    - Stack Colors + Default Stacked Chart Colors
    {STACK_COLORS.map((c) => ( -
    +
    ))}
    @@ -51,6 +51,10 @@ export const StackedBarChart = () => ( { title: 'Minimal Header', props: { minimalHeader: true } }, { title: 'Hide Legend', props: { hideLegend: true } }, { title: 'Percentage Bars', props: { variant: 'percentages' as const } }, + { + title: 'Percentage Bars with suffix', + props: { variant: 'percentages' as const, format: 'mem' }, + }, { title: 'Formatter - Auto 2 Precision for floats', props: { data: DATA.map((d) => ({ ...d, count: d.count + 5.12 })) }, diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx index ed8b0ea51d9..8f389778d0b 100644 --- a/studio/components/ui/Charts/StackedBarChart.tsx +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import { BarChart, Bar, XAxis, Tooltip, Legend, Cell } from 'recharts' +import { BarChart, Bar, XAxis, Tooltip, Legend, Cell, TooltipProps } from 'recharts' import ChartHeader from './ChartHeader' import { CHART_COLORS, STACK_COLORS, DateTimeFormats } from './Charts.constants' import { CommonChartProps } from './Charts.types' @@ -33,7 +33,13 @@ const StackedBarChart: React.FC = ({ hideLegend = false, }) => { const { Container } = useChartSize(size) - const { dataKeys, stackedData } = useStacked({ data, xAxisKey, stackKey, yAxisKey, variant }) + const { dataKeys, stackedData, percentagesStackedData } = useStacked({ + data, + xAxisKey, + stackKey, + yAxisKey, + variant, + }) const [focusDataIndex, setFocusDataIndex] = useState(null) if (data.length === 0) return return ( @@ -46,7 +52,7 @@ const StackedBarChart: React.FC = ({ /> = ({ className="cursor-pointer overflow-visible" // mouse hover focusing logic onMouseMove={(e: any) => { - console.log(e) if (e.activeTooltipIndex !== focusDataIndex) { setFocusDataIndex(e.activeTooltipIndex) } @@ -106,18 +111,22 @@ const StackedBarChart: React.FC = ({ ? (label) => timestampFormatter(label, customDateFormat, displayDateInUtc) : undefined } - formatter={(value: number) => { - let val: string = String(value) - if (variant === 'percentages') { - val = precisionFormatter(value * 100, 1) + '%' + formatter={(value: number, name: string, props: any) => { + const suffix = format || '' + if (variant === 'percentages' && percentagesStackedData) { + const index = percentagesStackedData.findIndex( + (pStack) => pStack === props.payload! + ) + const val = stackedData[index][name] + const percentage = precisionFormatter(value * 100, 1) + '%' + return `${percentage} (${val}${suffix})` } - val += (format || '') - return val + return String(value) + suffix }} cursor={false} labelClassName="text-white" contentStyle={{ backgroundColor: '#444444', borderColor: '#444444', fontSize: '12px' }} - wrapperClassName="bg-gray-600 rounded" + wrapperClassName="bg-gray-600 rounded min-w-md" /> diff --git a/studio/package.json b/studio/package.json index 7a39cefd0d6..547db163b08 100644 --- a/studio/package.json +++ b/studio/package.json @@ -103,8 +103,9 @@ "@storybook/react": "^6.4.19", "@storybook/testing-library": "^0.0.9", "@tailwindcss/typography": "^0.5.2", - "@testing-library/dom": "^8.11.1", - "@testing-library/react": "^12.1.2", + "@testing-library/dom": "^8.19.0", + "@testing-library/react": "^12.1.5", + "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^13.5.0", "@types/blueimp-md5": "^2.18.0", "@types/crypto-js": "^4.0.2", diff --git a/studio/tests/components/ui/Charts/Charts.utils.test.ts b/studio/tests/components/ui/Charts/Charts.utils.test.ts index 071f19f02d9..4da9e749668 100644 --- a/studio/tests/components/ui/Charts/Charts.utils.test.ts +++ b/studio/tests/components/ui/Charts/Charts.utils.test.ts @@ -1,16 +1,52 @@ -import { isFloat, numberFormatter, precisionFormatter } from "components/ui/Charts/Charts.utils" +import { + isFloat, + numberFormatter, + precisionFormatter, + useStacked, +} from 'components/ui/Charts/Charts.utils' +import { renderHook } from '@testing-library/react-hooks' - -test("isFloat", () => { - expect(isFloat(123)).toBe(false) - expect(isFloat(123.123)).toBe(true) +test('isFloat', () => { + expect(isFloat(123)).toBe(false) + expect(isFloat(123.123)).toBe(true) }) -test("numberFormatter", () => { - expect(numberFormatter(123)).toBe("123") - expect(numberFormatter(123.123)).toBe("123.12") +test('numberFormatter', () => { + expect(numberFormatter(123)).toBe('123') + expect(numberFormatter(123.123)).toBe('123.12') }) -test("precisionFormatter", () => { - expect(precisionFormatter(123, 1)).toBe("123.0") - expect(precisionFormatter(123.12345, 4)).toBe("123.1234") -}) \ No newline at end of file +test('precisionFormatter', () => { + expect(precisionFormatter(123, 1)).toBe('123.0') + expect(precisionFormatter(123.12345, 4)).toBe('123.1234') +}) + +test('useStacked', () => { + const { result } = renderHook(() => + useStacked({ + data: [ + { label: 'a', x: 1, y: 2 }, + { label: 'b', x: 1, y: 3 }, + ] as any, + xAxisKey: 'x', + yAxisKey: 'y', + stackKey: 'label', + variant: 'percentages', + }) + ) + expect(result.current).toMatchObject({ + stackedData: [ + { + a: 2, + b: 3, + x: 1, + }, + ], + percentagesStackedData: [ + { + a: 2 / 5, + b: 3 / 5, + x: 1, + }, + ], + }) +}) From de105ac4ecf51247a1e90e8e61f5804db95c7fa3 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 16 Dec 2022 15:47:50 +0800 Subject: [PATCH 036/127] feat: add sparkline chart --- studio/components/ui/Charts/Charts.utils.tsx | 11 +++- .../ui/Charts/Sparkline.stories.tsx | 54 +++++++++++++++++++ studio/components/ui/Charts/Sparkline.tsx | 46 ++++++++++++++++ 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 studio/components/ui/Charts/Sparkline.stories.tsx create mode 100644 studio/components/ui/Charts/Sparkline.tsx diff --git a/studio/components/ui/Charts/Charts.utils.tsx b/studio/components/ui/Charts/Charts.utils.tsx index af6c33d77db..ce988a2d86b 100644 --- a/studio/components/ui/Charts/Charts.utils.tsx +++ b/studio/components/ui/Charts/Charts.utils.tsx @@ -67,8 +67,15 @@ export const timestampFormatter = ( * Hook to create common wrapping components, perform data transformations * returns a Container component and the minHeight set */ -export const useChartSize = (size: CommonChartProps['size'] = 'normal') => { - const minHeight = { small: 120, normal: 160, large: 280 }[size] +export const useChartSize = ( + size: CommonChartProps['size'] = 'normal', + sizeMap: { + small: number + normal: number + large: number + } = { small: 120, normal: 160, large: 280 } +) => { + const minHeight = sizeMap[size] const Container: React.FC = useMemo( () => ({ children }) => diff --git a/studio/components/ui/Charts/Sparkline.stories.tsx b/studio/components/ui/Charts/Sparkline.stories.tsx new file mode 100644 index 00000000000..e341ff7b86a --- /dev/null +++ b/studio/components/ui/Charts/Sparkline.stories.tsx @@ -0,0 +1,54 @@ +import React from 'react' +import { ComponentStory, ComponentMeta } from '@storybook/react' +import dayjs from 'dayjs' +import Chart from './Sparkline' +import { StackedChartProps } from './Charts.types' +import { STACK_COLORS } from './Charts.constants' + +export default { + title: 'Charts/Sparkline', +} as ComponentMeta + +const DATA = [290, 430, 649, 422, 321].flatMap((value, index) => [ + { + count: value, + level: 'error', + timestamp: dayjs().subtract(index, 'day').toISOString(), + }, +]) + +const PROPS = { + title: 'Memory usage', + xAxisKey: 'timestamp', + yAxisKey: 'count', + data: DATA, +} +export const Sparkline = () => ( +
    + Examples +
    + {[ + { title: 'Small', props: { size: 'small' as const } }, + { title: 'Normal', props: {} }, + { title: 'Large', props: { size: 'large' as const } }, + { title: 'No data', props: { data: [] } }, + ].map(({ title, props }) => ( +
    + {title} +
    + +
    +
    + ))} +
    +
    +) + +const ExampleName: React.FC = ({ children }) => ( +

    + {children} +

    +) diff --git a/studio/components/ui/Charts/Sparkline.tsx b/studio/components/ui/Charts/Sparkline.tsx new file mode 100644 index 00000000000..855a9f86383 --- /dev/null +++ b/studio/components/ui/Charts/Sparkline.tsx @@ -0,0 +1,46 @@ +import { useState } from 'react' +import { LineChart, Bar, XAxis, Tooltip, Legend, Cell, TooltipProps, Line } from 'recharts' +import ChartHeader from './ChartHeader' +import { CHART_COLORS, STACK_COLORS, DateTimeFormats } from './Charts.constants' +import { CommonChartProps } from './Charts.types' +import { timestampFormatter, useChartSize, useStacked } from './Charts.utils' +import { precisionFormatter } from './Charts.utils' +import NoDataPlaceholder from './NoDataPlaceholder' +interface Props extends CommonChartProps { + xAxisKey: string + yAxisKey: string +} +const Sparkline: React.FC = ({ size, data, xAxisKey, yAxisKey }) => { + const { Container } = useChartSize(size, { + small: 50, + normal: 90, + large: 140, + }) + if (data.length === 0) return null + return ( + + + + + + + ) +} + +export default Sparkline From 7796598897678d7066d91934f88a58b10e9179e0 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 16 Dec 2022 16:36:39 +0800 Subject: [PATCH 037/127] feat: added sign up provider chart, tweaked stacked chart colors --- .../interfaces/Reports/Reports.constants.ts | 13 ++++++ .../Reports/renderers/AuthRenderers.tsx | 22 +++++++++- .../components/ui/Charts/Charts.constants.ts | 17 ++++---- .../ui/Charts/StackedBarChart.stories.tsx | 1 + .../components/ui/Charts/StackedBarChart.tsx | 18 ++++---- studio/pages/project/[ref]/reports/auth.tsx | 41 ++++++++++++++----- 6 files changed, 84 insertions(+), 28 deletions(-) diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index bf67eb7c019..187bb9a7543 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -162,6 +162,19 @@ export const PRESET_CONFIG: Record = { [Presets.AUTH]: { title: 'Auth', queries: { + signUpProviders: { + queryType: "db", + sql: ` +select + sum(count(u.id)) over (order by date_trunc('day', u.created_at)) as count, + date_trunc('day', u.created_at) as timestamp, + i.provider as provider +from auth.users u +join auth.identities as i on u.id = i.user_id +group by timestamp, provider +order by timestamp desc + ` + }, failedMigrations: { queryType: "logs", sql: ` diff --git a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx index 0444185508a..f69dcc88e10 100644 --- a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx @@ -1,14 +1,34 @@ import { BarChart } from 'components/to-be-cleaned/Charts/ChartRenderer' +import StackedBarChart from 'components/ui/Charts/StackedBarChart' import { DATETIME_FORMAT } from '../Reports.constants' import { ReportWidgetProps } from '../ReportWidget' import Statistic from '../Statistic' + +export const renderSignUpProviders = ( + props: ReportWidgetProps<{ + timestamp: string + provider: string + count: number + }> +) => { + return ( + + ) +} export const renderFailedMigrations = ( props: ReportWidgetProps<{ timestamp: string count: number }> ) => { - const count = props.data.length === 0 ? 0: props?.data?.[0]?.count + const count = props.data.length === 0 ? 0 : props?.data?.[0]?.count return } export const renderCumulativeUsers = ( diff --git a/studio/components/ui/Charts/Charts.constants.ts b/studio/components/ui/Charts/Charts.constants.ts index f9cff43f861..7aa3a6de752 100644 --- a/studio/components/ui/Charts/Charts.constants.ts +++ b/studio/components/ui/Charts/Charts.constants.ts @@ -11,16 +11,13 @@ export const CHART_COLORS = { // refer to packages/ui/radix-colors.js for full list of colors export const STACK_COLORS = [ - ['brand', 8], - ['mint', 8], - ['blue', 8], - ['sky', 7], - ['bronze', 8], - ['violet', 7], - ['indigo', 8], - ['tomato', 7], - ['orange', 8], - ['olive', 8], + ['brand', 9], + ['mint', 9], + ['blue', 9], + ['sky', 9], + ['lime', 9], + ['yellow', 9], + ['orange', 9], ].map(([color, n]) => ({ lighter: `var(--colors-${color}${(n as number) - 1})`, base: `var(--colors-${color}${n})`, diff --git a/studio/components/ui/Charts/StackedBarChart.stories.tsx b/studio/components/ui/Charts/StackedBarChart.stories.tsx index a6703a51727..2e0289aa158 100644 --- a/studio/components/ui/Charts/StackedBarChart.stories.tsx +++ b/studio/components/ui/Charts/StackedBarChart.stories.tsx @@ -50,6 +50,7 @@ export const StackedBarChart = () => ( { title: 'No title', props: { title: undefined } }, { title: 'Minimal Header', props: { minimalHeader: true } }, { title: 'Hide Legend', props: { hideLegend: true } }, + { title: 'Hide header', props: { hideHeader: true } }, { title: 'Percentage Bars', props: { variant: 'percentages' as const } }, { title: 'Percentage Bars with suffix', diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx index 8f389778d0b..df9f6484ce3 100644 --- a/studio/components/ui/Charts/StackedBarChart.tsx +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -15,6 +15,7 @@ interface Props extends CommonChartProps { xAxisFormatAsDate?: boolean displayDateInUtc?: boolean hideLegend?: boolean + hideHeader?: boolean } const StackedBarChart: React.FC = ({ size, @@ -31,6 +32,7 @@ const StackedBarChart: React.FC = ({ xAxisFormatAsDate = true, displayDateInUtc, hideLegend = false, + hideHeader = false, }) => { const { Container } = useChartSize(size) const { dataKeys, stackedData, percentagesStackedData } = useStacked({ @@ -43,13 +45,15 @@ const StackedBarChart: React.FC = ({ const [focusDataIndex, setFocusDataIndex] = useState(null) if (data.length === 0) return return ( -
    - +
    + {!hideHeader && ( + + )} { @@ -23,20 +24,20 @@ export const AuthReport: NextPageWithLayout = () => { const newUsers = hooks.newUsers() const cumulativeUsers = hooks.cumulativeUsers() const failedMigrations = hooks.failedMigrations() + const signUpProviders = hooks.signUpProviders() - const { isLoading, Layout } = usePresetReport([dailyActiveUsers, newUsers, cumulativeUsers]) + const { isLoading, Layout } = usePresetReport([ + dailyActiveUsers, + newUsers, + cumulativeUsers, + failedMigrations, + signUpProviders, + ]) return (
    - +

    User Activity

    + { data={dailyActiveUsers[0].logData!} renderer={renderDailyActiveUsers} /> + + +

    Sever Health

    +
    ) From 4ac96293a02b2a03d7f25b2034a4f75d3dafb8ef Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 16 Dec 2022 17:25:15 +0800 Subject: [PATCH 038/127] feat: add banned user widget and unverified users widget --- .../interfaces/Reports/Reports.constants.ts | 23 ++++++++++++++ .../interfaces/Reports/Statistic.tsx | 18 ++++++++++- .../Reports/renderers/AuthRenderers.tsx | 17 ++++++++++ .../components/ui/Charts/StackedBarChart.tsx | 2 +- studio/pages/project/[ref]/reports/auth.tsx | 31 +++++++++++++++++-- 5 files changed, 86 insertions(+), 5 deletions(-) diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index 187bb9a7543..e76f48949ac 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -162,6 +162,28 @@ export const PRESET_CONFIG: Record = { [Presets.AUTH]: { title: 'Auth', queries: { + bannedUsers: { + queryType: "db", + sql: ` +select + count(distinct u.id) as count +from auth.users u +where u.banned_until is not null and u.banned_until > now() +limit 1 + ` + }, + unverifiedUsers: { + queryType: "db", + sql: ` +select + date_trunc('day', u.created_at) as timestamp, + count(distinct u.id) as count +from auth.users u +where u.confirmed_at is null +group by timestamp +order by timestamp desc + ` + }, signUpProviders: { queryType: "db", sql: ` @@ -207,6 +229,7 @@ select date_trunc('day', u.created_at) as timestamp, sum(count(u.id)) over (order by date_trunc('day', u.created_at)) as count from auth.users as u +where u.confirmed_at is not null group by timestamp order by timestamp desc `, diff --git a/studio/components/interfaces/Reports/Statistic.tsx b/studio/components/interfaces/Reports/Statistic.tsx index 9f21c6feefc..61c26979c05 100644 --- a/studio/components/interfaces/Reports/Statistic.tsx +++ b/studio/components/interfaces/Reports/Statistic.tsx @@ -1,3 +1,4 @@ +import Sparkline from 'components/ui/Charts/Sparkline' import React from 'react' interface Props { @@ -6,12 +7,24 @@ interface Props { prefix?: string suffix?: string valueFormatter?: (value: Props['value']) => string + sparklineData?: Record[] + sparklineXAxis?: string + sparklineYAxis?: string } -const Statistic: React.FC = ({ prefix, suffix, value, startingValue }) => { +const Statistic: React.FC = ({ + prefix, + suffix, + value, + startingValue, + sparklineData, + sparklineXAxis, + sparklineYAxis, +}) => { const isNumber = typeof value === 'number' && !Number.isNaN(value) const percentageChange = startingValue && isNumber ? Number(startingValue) / Number(value) - 1 : null + const showSparkline = sparklineData && sparklineXAxis && sparklineYAxis return (
    @@ -29,6 +42,9 @@ const Statistic: React.FC = ({ prefix, suffix, value, startingValue }) => )}
    )} + {showSparkline && ( + + )}
    ) } diff --git a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx index f69dcc88e10..fbd38fb6c5f 100644 --- a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx @@ -4,6 +4,23 @@ import { DATETIME_FORMAT } from '../Reports.constants' import { ReportWidgetProps } from '../ReportWidget' import Statistic from '../Statistic' +export const renderBannedUsers = ( + props: ReportWidgetProps<{ + count: number + }> +) => { + const count = props.data.length === 0 ? 0 : props?.data?.[0]?.count + return +} +export const renderUnverifiedUsers = ( + props: ReportWidgetProps<{ + timestamp: string + count: number + }> +) => { + const count = props.data.length === 0 ? 0 : props?.data?.[0]?.count + return +} export const renderSignUpProviders = ( props: ReportWidgetProps<{ timestamp: string diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx index df9f6484ce3..d378cef19a8 100644 --- a/studio/components/ui/Charts/StackedBarChart.tsx +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -43,7 +43,7 @@ const StackedBarChart: React.FC = ({ variant, }) const [focusDataIndex, setFocusDataIndex] = useState(null) - if (data.length === 0) return + if (!data || data.length === 0) return return (
    {!hideHeader && ( diff --git a/studio/pages/project/[ref]/reports/auth.tsx b/studio/pages/project/[ref]/reports/auth.tsx index 0d84ed91ebe..d57839ebf23 100644 --- a/studio/pages/project/[ref]/reports/auth.tsx +++ b/studio/pages/project/[ref]/reports/auth.tsx @@ -13,6 +13,8 @@ import { renderCumulativeUsers, renderFailedMigrations, renderSignUpProviders, + renderUnverifiedUsers, + renderBannedUsers, } from 'components/interfaces/Reports/renderers/AuthRenderers' export const AuthReport: NextPageWithLayout = () => { @@ -25,6 +27,8 @@ export const AuthReport: NextPageWithLayout = () => { const cumulativeUsers = hooks.cumulativeUsers() const failedMigrations = hooks.failedMigrations() const signUpProviders = hooks.signUpProviders() + const unverifiedUsers = hooks.unverifiedUsers() + const bannedUsers = hooks.bannedUsers() const { isLoading, Layout } = usePresetReport([ dailyActiveUsers, @@ -32,10 +36,11 @@ export const AuthReport: NextPageWithLayout = () => { cumulativeUsers, failedMigrations, signUpProviders, + unverifiedUsers, ]) return ( -
    +

    User Activity

    { -

    Sever Health

    + + + + +

    Sever Health

    Date: Fri, 16 Dec 2022 17:29:25 +0800 Subject: [PATCH 039/127] feat: add conditional rendering of sparkline --- studio/components/interfaces/Reports/Statistic.tsx | 4 ++-- .../interfaces/Reports/renderers/AuthRenderers.tsx | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/studio/components/interfaces/Reports/Statistic.tsx b/studio/components/interfaces/Reports/Statistic.tsx index 61c26979c05..e8f534ae003 100644 --- a/studio/components/interfaces/Reports/Statistic.tsx +++ b/studio/components/interfaces/Reports/Statistic.tsx @@ -7,7 +7,7 @@ interface Props { prefix?: string suffix?: string valueFormatter?: (value: Props['value']) => string - sparklineData?: Record[] + sparklineData?: any sparklineXAxis?: string sparklineYAxis?: string } @@ -43,7 +43,7 @@ const Statistic: React.FC = ({
    )} {showSparkline && ( - + )}
    ) diff --git a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx index fbd38fb6c5f..f61c26a5674 100644 --- a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx @@ -19,7 +19,15 @@ export const renderUnverifiedUsers = ( }> ) => { const count = props.data.length === 0 ? 0 : props?.data?.[0]?.count - return + + return ( + = 3 ? props.data : null} + sparklineXAxis="timestamp" + sparklineYAxis="count" + /> + ) } export const renderSignUpProviders = ( props: ReportWidgetProps<{ From fcbc5e88d12ed4922d1d16cc652d88d1319b1295 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 16 Dec 2022 17:35:56 +0800 Subject: [PATCH 040/127] feat: combine bot subreport into API report --- studio/tests/pages/projects/reports/product-reports.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/tests/pages/projects/reports/product-reports.test.js b/studio/tests/pages/projects/reports/product-reports.test.js index a30e33c1aea..885fcbe145a 100644 --- a/studio/tests/pages/projects/reports/product-reports.test.js +++ b/studio/tests/pages/projects/reports/product-reports.test.js @@ -7,7 +7,7 @@ import { get, post } from 'lib/common/fetch' import { useRouter } from 'next/router' import { render } from '../../../helpers' import { fireEvent, waitFor, screen } from '@testing-library/react' -import { ApiOverviewReport } from 'pages/project/[ref]/reports/api-overview' +import { ApiOverviewReport } from 'pages/project/[ref]/reports/api' import { ApiBotsReport } from 'pages/project/[ref]/reports/api-bots' import { AuthReport } from 'pages/project/[ref]/reports/auth' From f041bb3644a94898014198f48aa6beae6db81cd5 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 16 Dec 2022 17:37:31 +0800 Subject: [PATCH 041/127] feat: combine bot subreport into API report --- .../interfaces/Reports/Reports.constants.ts | 9 +- .../interfaces/Reports/Reports.types.ts | 4 +- .../Reports/renderers/ApiBotsRenderers.tsx | 94 ------------------- ...OverviewRenderers.tsx => ApiRenderers.tsx} | 92 ++++++++++++++++++ .../ReportsLayout/ReportsMenu.utils.ts | 12 +-- .../pages/project/[ref]/reports/api-bots.tsx | 51 ---------- .../reports/{api-overview.tsx => api.tsx} | 47 ++++++++-- .../projects/reports/product-reports.test.js | 6 +- 8 files changed, 139 insertions(+), 176 deletions(-) delete mode 100644 studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx rename studio/components/interfaces/Reports/renderers/{OverviewRenderers.tsx => ApiRenderers.tsx} (70%) delete mode 100644 studio/pages/project/[ref]/reports/api-bots.tsx rename studio/pages/project/[ref]/reports/{api-overview.tsx => api.tsx} (58%) diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index e76f48949ac..456c6c95a8d 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -34,8 +34,8 @@ export const DEFAULT_QUERY_PARAMS = { } export const PRESET_CONFIG: Record = { - [Presets.API_OVERVIEW]: { - title: '', + [Presets.API]: { + title: 'API', queries: { statusCodes: { queryType: 'logs', @@ -101,11 +101,6 @@ export const PRESET_CONFIG: Record = { LIMIT 50 `, }, - }, - }, - [Presets.API_BOTS]: { - title: 'API Bots', - queries: { userAgents: { queryType: 'logs', sql: ` diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts index 419303e66af..b13d36dd3ba 100644 --- a/studio/components/interfaces/Reports/Reports.types.ts +++ b/studio/components/interfaces/Reports/Reports.types.ts @@ -1,10 +1,8 @@ import { ResponseError } from 'types' -import { LogsEndpointParams } from '../Settings/Logs' import { DEFAULT_QUERY_PARAMS } from './Reports.constants' export enum Presets { - API_OVERVIEW = 'overview', - API_BOTS = 'api_bots', + API = 'api', AUTH = 'auth', } diff --git a/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx b/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx deleted file mode 100644 index 9c92a616dad..00000000000 --- a/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import Table from 'components/to-be-cleaned/Table' -import { ReportWidgetProps } from '../ReportWidget' - -export const renderUserAgents = ( - props: ReportWidgetProps<{ - user_agent: string - request_source: string - count: number - }> -) => { - return ( -
    - User Agents - Request Source - Count - - } - body={ - <> - {props.data.map((row, index) => { - return ( - - - {row.user_agent ? ( - row.user_agent - ) : ( - No user agent - )} - - {row.request_source} - - {row.count} - - - ) - })} - - } - /> - ) -} - -export const renderBotScores = ( - props: ReportWidgetProps<{ - ip: string - country: string - user_agent: string - path: string - bot_score: number - bot_verified: boolean - count: number - }> -) => { - return ( -
    - IP (Country) - Request Path - User Agent - Bot Score - Is Verified Bot - Count - - } - body={ - <> - {props.data.map((row, index) => { - return ( - - - {row.ip} ({row.country}) - - {row.path} - {row.user_agent} - {row.bot_score} - {row.bot_verified ? 'Yes' : 'No'} - - {row.count} - - - ) - })} - - } - /> - ) -} diff --git a/studio/components/interfaces/Reports/renderers/OverviewRenderers.tsx b/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx similarity index 70% rename from studio/components/interfaces/Reports/renderers/OverviewRenderers.tsx rename to studio/components/interfaces/Reports/renderers/ApiRenderers.tsx index 2fc2c8eac4e..e6e8156d296 100644 --- a/studio/components/interfaces/Reports/renderers/OverviewRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx @@ -10,6 +10,98 @@ import { PathsDatum, StatusCodesDatum } from '../Reports.types' import { queryParamsToObject } from '../Reports.utils' import { ReportWidgetProps } from '../ReportWidget' +export const renderUserAgents = ( + props: ReportWidgetProps<{ + user_agent: string + request_source: string + count: number + }> +) => { + return ( +
    + User Agents + Request Source + Count + + } + body={ + <> + {props.data.map((row, index) => { + return ( + + + {row.user_agent ? ( + row.user_agent + ) : ( + No user agent + )} + + {row.request_source} + + {row.count} + + + ) + })} + + } + /> + ) +} + +export const renderBotScores = ( + props: ReportWidgetProps<{ + ip: string + country: string + user_agent: string + path: string + bot_score: number + bot_verified: boolean + count: number + }> +) => { + return ( +
    + IP (Country) + Request Path + User Agent + Bot Score + Is Verified Bot + Count + + } + body={ + <> + {props.data.map((row, index) => { + return ( + + + {row.ip} ({row.country}) + + {row.path} + {row.user_agent} + {row.bot_score} + {row.bot_verified ? 'Yes' : 'No'} + + {row.count} + + + ) + })} + + } + /> + ) +} + export const renderStatusCodesChart = (props: ReportWidgetProps) => ( { ...(productReports ? [ { - name: 'API Overview', - key: 'api-overview', - url: `/project/${ref}/reports/api-overview`, - items: [], - }, - { - name: 'API Bots', - key: 'api-bots', - url: `/project/${ref}/reports/api-bots`, + name: 'API', + key: 'api', + url: `/project/${ref}/reports/api`, items: [], }, { diff --git a/studio/pages/project/[ref]/reports/api-bots.tsx b/studio/pages/project/[ref]/reports/api-bots.tsx deleted file mode 100644 index 3df85b74abf..00000000000 --- a/studio/pages/project/[ref]/reports/api-bots.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { observer } from 'mobx-react-lite' -import { useRouter } from 'next/router' - -import { NextPageWithLayout } from 'types' -import { Presets } from 'components/interfaces/Reports/Reports.types' -import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout' -import { hooksFactory, usePresetReport } from 'components/interfaces/Reports/Reports.utils' -import { PRESET_CONFIG } from 'components/interfaces/Reports/Reports.constants' -import { - renderBotScores, - renderUserAgents, -} from 'components/interfaces/Reports/renderers/ApiBotsRenderers' -import ReportWidget from 'components/interfaces/Reports/ReportWidget' - -export const ApiBotsReport: NextPageWithLayout = () => { - const router = useRouter() - const { ref } = router.query - const config = PRESET_CONFIG[Presets.API_BOTS] - const hooks = hooksFactory(ref as string, config) - const userAgents = hooks.userAgents() - const botScores = hooks.botScores() - const { isLoading, Layout } = usePresetReport([userAgents, botScores]) - return ( - -
    - - -
    -
    - ) -} - -ApiBotsReport.getLayout = (page) => {page} - -export default observer(ApiBotsReport) diff --git a/studio/pages/project/[ref]/reports/api-overview.tsx b/studio/pages/project/[ref]/reports/api.tsx similarity index 58% rename from studio/pages/project/[ref]/reports/api-overview.tsx rename to studio/pages/project/[ref]/reports/api.tsx index 2d266dabeaf..1c6257d180b 100644 --- a/studio/pages/project/[ref]/reports/api-overview.tsx +++ b/studio/pages/project/[ref]/reports/api.tsx @@ -11,17 +11,27 @@ import { renderErrorRateChart, renderRequestsPathsTable, renderStatusCodesChart, -} from 'components/interfaces/Reports/renderers/OverviewRenderers' + renderBotScores, + renderUserAgents +} from 'components/interfaces/Reports/renderers/ApiRenderers' -export const ApiOverviewReport: NextPageWithLayout = () => { +export const ApiReport: NextPageWithLayout = () => { const router = useRouter() const { ref } = router.query - const config = PRESET_CONFIG[Presets.API_OVERVIEW] + const config = PRESET_CONFIG[Presets.API] const hooks = hooksFactory(ref as string, config) const statusCodes = hooks.statusCodes() const errorRates = hooks.errorRates() const requestPaths = hooks.requestPaths() - const { isLoading, Layout } = usePresetReport([statusCodes, errorRates, requestPaths]) + const userAgents = hooks.userAgents() + const botScores = hooks.botScores() + const { isLoading, Layout } = usePresetReport([ + statusCodes, + errorRates, + requestPaths, + userAgents, + botScores, + ]) return (
    @@ -31,7 +41,7 @@ export const ApiOverviewReport: NextPageWithLayout = () => { className="col-span-4 col-start-1" title="API Status Codes" description="Distribution of API responses by status codes." - data={statusCodes[0].logData || []} + data={statusCodes[0].logData || []} renderer={renderStatusCodesChart} /> { className="col-span-4 col-start-1" title="Error Rate" description="Percentage of API 5XX and 4XX error responses." - data={errorRates[0].logData || []} + data={errorRates[0].logData || []} renderer={renderErrorRateChart} /> { data={requestPaths[0].logData || []} renderer={renderRequestsPathsTable} /> + +

    Bots

    + + +
    ) } -ApiOverviewReport.getLayout = (page) => {page} +ApiReport.getLayout = (page) => {page} -export default observer(ApiOverviewReport) +export default observer(ApiReport) diff --git a/studio/tests/pages/projects/reports/product-reports.test.js b/studio/tests/pages/projects/reports/product-reports.test.js index 885fcbe145a..a82267a2717 100644 --- a/studio/tests/pages/projects/reports/product-reports.test.js +++ b/studio/tests/pages/projects/reports/product-reports.test.js @@ -7,8 +7,7 @@ import { get, post } from 'lib/common/fetch' import { useRouter } from 'next/router' import { render } from '../../../helpers' import { fireEvent, waitFor, screen } from '@testing-library/react' -import { ApiOverviewReport } from 'pages/project/[ref]/reports/api' -import { ApiBotsReport } from 'pages/project/[ref]/reports/api-bots' +import { ApiReport } from 'pages/project/[ref]/reports/api' import { AuthReport } from 'pages/project/[ref]/reports/auth' beforeEach(() => { @@ -25,8 +24,7 @@ beforeEach(() => { }) describe.each([ - { Page: ApiOverviewReport, contains: ['API Overview'] }, - { Page: ApiBotsReport, contains: ['API Bots'] }, + { Page: ApiReport, contains: ['API'] }, { Page: AuthReport, contains: ['Auth'] }, ])('$Page rendering', ({ Page, contains }) => { test(`contains ${contains}`, async () => { From a66766ece2b204768fd0b4450f20d854d6739d37 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 16 Dec 2022 17:59:32 +0800 Subject: [PATCH 042/127] fix: add db query timestamp filtering --- .../interfaces/Reports/Reports.constants.ts | 17 +++++++------- .../interfaces/Reports/Reports.types.ts | 23 +++++++++---------- .../interfaces/Reports/Statistic.tsx | 7 +++++- studio/components/ui/Charts/Sparkline.tsx | 10 +++----- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index 456c6c95a8d..90010822e2e 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -159,7 +159,7 @@ export const PRESET_CONFIG: Record = { queries: { bannedUsers: { queryType: "db", - sql: ` + sql: (_params)=> ` select count(distinct u.id) as count from auth.users u @@ -169,25 +169,26 @@ limit 1 }, unverifiedUsers: { queryType: "db", - sql: ` + sql: (params)=> ` select date_trunc('day', u.created_at) as timestamp, count(distinct u.id) as count from auth.users u -where u.confirmed_at is null +where u.confirmed_at is null and '${params.iso_timestamp_start}' < u.created_at group by timestamp order by timestamp desc ` }, signUpProviders: { queryType: "db", - sql: ` + sql: (params) => ` select sum(count(u.id)) over (order by date_trunc('day', u.created_at)) as count, date_trunc('day', u.created_at) as timestamp, i.provider as provider from auth.users u join auth.identities as i on u.id = i.user_id +where date_trunc('day', u.created_at) > '${params.iso_timestamp_start}' group by timestamp, provider order by timestamp desc ` @@ -218,25 +219,25 @@ order by }, cumulativeUsers: { queryType: 'db', - sql: ` + sql: params => ` select date_trunc('day', u.created_at) as timestamp, sum(count(u.id)) over (order by date_trunc('day', u.created_at)) as count from auth.users as u -where u.confirmed_at is not null +where u.confirmed_at is not null and date_trunc('day', u.created_at) > '${params.iso_timestamp_start}' group by timestamp order by timestamp desc `, }, newUsers: { queryType: 'db', - sql: ` + sql: params => ` select date_trunc('day', u.created_at) as timestamp, count(u.id) as count from auth.users as u -where u.confirmed_at is not null +where u.confirmed_at is not null and date_trunc('day', u.created_at) > '${params.iso_timestamp_start}' group by timestamp order by diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts index b13d36dd3ba..20c07dd463b 100644 --- a/studio/components/interfaces/Reports/Reports.types.ts +++ b/studio/components/interfaces/Reports/Reports.types.ts @@ -23,20 +23,19 @@ export interface DbQueryHandler { export type MetaQueryResponse = any & { error: ResponseError } export type BaseReportParams = typeof DEFAULT_QUERY_PARAMS & { sql?: string } & unknown - -export type VariableSql = string | ((params: BaseReportParams) => string) - -export type PresetSql = string | VariableSql - export interface PresetConfig { title: string - queries: Record< - string, - { - sql: PresetSql - queryType: 'logs' | 'db' - } - > + queries: Record +} + +interface DbQuery { + queryType: 'db' + sql: (params: BaseReportParams) => string +} + +interface LogsQuery { + queryType: 'logs' + sql: string } export interface StatusCodesDatum { timestamp: number diff --git a/studio/components/interfaces/Reports/Statistic.tsx b/studio/components/interfaces/Reports/Statistic.tsx index e8f534ae003..3cc6cd8bdac 100644 --- a/studio/components/interfaces/Reports/Statistic.tsx +++ b/studio/components/interfaces/Reports/Statistic.tsx @@ -43,7 +43,12 @@ const Statistic: React.FC = ({ )} {showSparkline && ( - + )} ) diff --git a/studio/components/ui/Charts/Sparkline.tsx b/studio/components/ui/Charts/Sparkline.tsx index 855a9f86383..c6c2bf1079b 100644 --- a/studio/components/ui/Charts/Sparkline.tsx +++ b/studio/components/ui/Charts/Sparkline.tsx @@ -1,11 +1,7 @@ -import { useState } from 'react' -import { LineChart, Bar, XAxis, Tooltip, Legend, Cell, TooltipProps, Line } from 'recharts' -import ChartHeader from './ChartHeader' -import { CHART_COLORS, STACK_COLORS, DateTimeFormats } from './Charts.constants' +import { LineChart, XAxis, Line } from 'recharts' +import { CHART_COLORS } from './Charts.constants' import { CommonChartProps } from './Charts.types' -import { timestampFormatter, useChartSize, useStacked } from './Charts.utils' -import { precisionFormatter } from './Charts.utils' -import NoDataPlaceholder from './NoDataPlaceholder' +import { useChartSize } from './Charts.utils' interface Props extends CommonChartProps { xAxisKey: string yAxisKey: string From 179402ef927dedc68f01a2fb3cb6d24d84327a19 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 16 Dec 2022 18:01:54 +0800 Subject: [PATCH 043/127] fix: remove unused file --- studio/components/ui/ProductMenu/ProductMenuGroup.tsx | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 studio/components/ui/ProductMenu/ProductMenuGroup.tsx diff --git a/studio/components/ui/ProductMenu/ProductMenuGroup.tsx b/studio/components/ui/ProductMenu/ProductMenuGroup.tsx deleted file mode 100644 index 96f4ea20ef0..00000000000 --- a/studio/components/ui/ProductMenu/ProductMenuGroup.tsx +++ /dev/null @@ -1,3 +0,0 @@ -const ProductMenuGroup = ()=>{ - -} \ No newline at end of file From d52154f68654673dfbf0c877c64ef438ea65fc81 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Wed, 21 Dec 2022 21:19:41 +0800 Subject: [PATCH 044/127] chore: fix failing typecheck --- studio/components/interfaces/Reports/Reports.types.ts | 6 +++--- studio/hooks/analytics/useDbQuery.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts index 20c07dd463b..0cc81dd470e 100644 --- a/studio/components/interfaces/Reports/Reports.types.ts +++ b/studio/components/interfaces/Reports/Reports.types.ts @@ -28,12 +28,12 @@ export interface PresetConfig { queries: Record } -interface DbQuery { +export interface DbQuery { queryType: 'db' - sql: (params: BaseReportParams) => string + sql: string | ((params: BaseReportParams) => string) } -interface LogsQuery { +export interface LogsQuery { queryType: 'logs' sql: string } diff --git a/studio/hooks/analytics/useDbQuery.tsx b/studio/hooks/analytics/useDbQuery.tsx index 45d9236d4f9..9c229fb7f83 100644 --- a/studio/hooks/analytics/useDbQuery.tsx +++ b/studio/hooks/analytics/useDbQuery.tsx @@ -4,12 +4,12 @@ import { DbQueryData, DbQueryHandler, MetaQueryResponse, - PresetSql, + DbQuery, } from 'components/interfaces/Reports/Reports.types' import { useStore } from 'hooks' import useSWR from 'swr' -type UseDbQuery = (sql: PresetSql, params?: BaseReportParams) => [DbQueryData, DbQueryHandler] +type UseDbQuery = (sql: DbQuery['sql'], params?: BaseReportParams) => [DbQueryData, DbQueryHandler] const useDbQuery: UseDbQuery = (sql, params = DEFAULT_QUERY_PARAMS) => { const { meta } = useStore() From f674a35d3df5f494ca1fdccce4538c5a726cad3a Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Wed, 21 Dec 2022 23:04:21 +0800 Subject: [PATCH 045/127] fix: ui fixes for datepicker --- .../components/interfaces/Reports/Reports.constants.ts | 7 +++---- studio/components/interfaces/Reports/Reports.utils.tsx | 10 ++++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index 90010822e2e..fedef4cdd7e 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -6,13 +6,12 @@ export const LAYOUT_COLUMN_COUNT = 24 export const REPORTS_DATEPICKER_HELPERS: DatetimeHelper[] = [ { - text: 'Last 7 days', - calcFrom: () => dayjs().subtract(7, 'day').startOf('day').toISOString(), + text: 'Last day', calcTo: () => '', - default: true, + calcFrom: () => dayjs().subtract(1, 'day').startOf('day').toISOString(), }, { - text: 'Last 14 days', + text: 'Last 7 days', calcFrom: () => dayjs().subtract(7, 'day').startOf('day').toISOString(), calcTo: () => '', }, diff --git a/studio/components/interfaces/Reports/Reports.utils.tsx b/studio/components/interfaces/Reports/Reports.utils.tsx index 4d07a2686ee..41e0ee4d975 100644 --- a/studio/components/interfaces/Reports/Reports.utils.tsx +++ b/studio/components/interfaces/Reports/Reports.utils.tsx @@ -63,6 +63,12 @@ export const usePresetReport = (hooks: PresetHookResult[]) => { }) } const isLoading = data.map((datum) => datum.isLoading).some((v) => v) + const helpers = REPORTS_DATEPICKER_HELPERS.map((helper, index) => { + if (tier?.key === 'FREE' && index === 0) return { ...helper, default: true } + if (tier?.key === 'PRO' && index === 1) return { ...helper, default: true } + return { ...helper, default: false } + }) + const Layout: React.FC<{ title: string }> = ({ title, children }) => (

    {title}

    @@ -70,8 +76,8 @@ export const usePresetReport = (hooks: PresetHookResult[]) => {
    {day2.steps[0].title}
    - + New
    @@ -454,7 +454,7 @@ export default function launchweek() {
    {day3.steps[0].title} - + Updated
    @@ -516,7 +516,7 @@ export default function launchweek() {
    {day4.steps[0].title} - + New
    @@ -585,7 +585,7 @@ export default function launchweek() { {day5.steps[0].title} - + New @@ -778,7 +778,7 @@ export default function launchweek() { />
    - + Updated @@ -823,7 +823,7 @@ export default function launchweek() { />
    - + New @@ -868,7 +868,7 @@ export default function launchweek() { />
    - + New @@ -913,7 +913,7 @@ export default function launchweek() { />
    - + Experimental @@ -958,7 +958,7 @@ export default function launchweek() { />
    - + Upgrade @@ -1003,7 +1003,7 @@ export default function launchweek() { />
    - + Upgrade @@ -1029,7 +1029,9 @@ export default function launchweek() {
    - + Submissions Closed

    Launch Week Hackathon

    From f3c3d566fc774ce33d9f1ceb1cc2446a32bfab43 Mon Sep 17 00:00:00 2001 From: Francisco Mazzoni Date: Thu, 22 Dec 2022 13:20:51 -0300 Subject: [PATCH 048/127] small badge fix --- apps/www/pages/launch-week/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/www/pages/launch-week/index.tsx b/apps/www/pages/launch-week/index.tsx index c6e3b798330..10cddcf4113 100644 --- a/apps/www/pages/launch-week/index.tsx +++ b/apps/www/pages/launch-week/index.tsx @@ -102,7 +102,7 @@ export default function launchweek() { @@ -1030,7 +1030,7 @@ export default function launchweek() {
    Submissions Closed @@ -1217,7 +1217,7 @@ export default function launchweek() { From 2b27b688e6ca0a2d4028eb602f8da646f4973baa Mon Sep 17 00:00:00 2001 From: Chensokheng <52232579+Chensokheng@users.noreply.github.com> Date: Fri, 23 Dec 2022 00:01:11 +0700 Subject: [PATCH 049/127] fix(docs): change navbar color to dark on dark mode --- apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx | 1 + apps/docs/layouts/SiteLayout.tsx | 2 ++ 2 files changed, 3 insertions(+) diff --git a/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx b/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx index d5a706d6cb2..b1d344b081d 100644 --- a/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx @@ -42,6 +42,7 @@ const TopNavBarRef: FC = () => { const key = localStorage.getItem('supabaseDarkMode') document.documentElement.className = key === 'true' ? 'dark' : '' + document.documentElement.style.colorScheme = key === 'true' ? 'dark' : '' } const onSelectVersion = (version: string) => { diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx index 9aa8c37d9c0..eb017378efd 100644 --- a/apps/docs/layouts/SiteLayout.tsx +++ b/apps/docs/layouts/SiteLayout.tsx @@ -294,8 +294,10 @@ const SiteLayout = ({ children }) => { if (!key) { // Default to dark mode if no preference config document.documentElement.className = 'dark' + document.documentElement.style.colorScheme = 'dark' } else { document.documentElement.className = key === 'true' ? 'dark' : '' + document.documentElement.style.colorScheme = key === 'true' ? 'dark' : '' } }, []) From b2d8e8eb4eebc6878349e9ba2d23e7fe4a49f0ac Mon Sep 17 00:00:00 2001 From: dannykng Date: Thu, 22 Dec 2022 10:27:34 -0800 Subject: [PATCH 050/127] Update more links --- .../resources/supabase-cli/local-development.mdx | 2 +- apps/www/_blog/2021-11-29-community-day-lw3.mdx | 2 +- .../2022-04-20-partner-gallery-works-with-supabase.mdx | 2 +- ...ck-consolidate-slackbot-to-consolidate-messages.mdx | 10 +++------- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/apps/docs/pages/guides/resources/supabase-cli/local-development.mdx b/apps/docs/pages/guides/resources/supabase-cli/local-development.mdx index 3ff1cb1c82a..90490ae4e55 100644 --- a/apps/docs/pages/guides/resources/supabase-cli/local-development.mdx +++ b/apps/docs/pages/guides/resources/supabase-cli/local-development.mdx @@ -54,7 +54,7 @@ git init supabase init ``` -Make sure Docker is running. The [start](/docs/reference/cli/usage#supabase-start) command uses Docker to start the Supabase [services](/docs/architecture). +Make sure Docker is running. The [start](/docs/reference/cli/usage#supabase-start) command uses Docker to start the Supabase [services](/docs/guides/getting-started/architecture). This command may take a while to run if this is the first time using the CLI. ```bash diff --git a/apps/www/_blog/2021-11-29-community-day-lw3.mdx b/apps/www/_blog/2021-11-29-community-day-lw3.mdx index 4ac1ea6ea05..801bfc24217 100644 --- a/apps/www/_blog/2021-11-29-community-day-lw3.mdx +++ b/apps/www/_blog/2021-11-29-community-day-lw3.mdx @@ -22,7 +22,7 @@ So let's get cracking, we've got a lot to cover. 🚀 ## Open Source Spotlight: Kong API Gateway -Supabase provides a whole bunch of features that are made up of a [collection of open-source tools](/docs/architecture). To orchestrate these different services and functionalities we use the [Kong API Gateway](https://github.com/Kong/kong). +Supabase provides a whole bunch of features that are made up of a [collection of open-source tools](/docs/guides/getting-started/architecture). To orchestrate these different services and functionalities we use the [Kong API Gateway](https://github.com/Kong/kong). As a user of our hosted offering you don't really need to know all this, but we love showcasing the amazing open-source tools that we work with, and so we've invited [Vik Gamov](https://twitter.com/gAmUssA) to give you a little intro to [Kong](https://github.com/Kong/kong): diff --git a/apps/www/_blog/2022-04-20-partner-gallery-works-with-supabase.mdx b/apps/www/_blog/2022-04-20-partner-gallery-works-with-supabase.mdx index eaea01bbd94..aaf5e4d21b0 100644 --- a/apps/www/_blog/2022-04-20-partner-gallery-works-with-supabase.mdx +++ b/apps/www/_blog/2022-04-20-partner-gallery-works-with-supabase.mdx @@ -19,7 +19,7 @@ To keep track of all the great tools and people that “Work With Supabase”, w ![partner-gallery-screenshot.png](/images/blog/partner-gallery/partner-gallery-screenshot.png) -As with anything at Supabase, the Partner Gallery is built on top of the [Supabase stack](/docs/architecture) and is, of course, open-source. If you’re looking to build a partner gallery yourself, feel free to use our template as a start: [github.com/supabase-community/partner-gallery-example](https://github.com/supabase-community/partner-gallery-example). It’s packed with goodies like [Postgres Full Text Search](/docs/guides/database/full-text-search), `next-image` with Supabase Storage, and sending emails based on an insert trigger with Supabase Edge Functions. +As with anything at Supabase, the Partner Gallery is built on top of the [Supabase stack](/docs/guides/getting-started/architecture) and is, of course, open-source. If you’re looking to build a partner gallery yourself, feel free to use our template as a start: [github.com/supabase-community/partner-gallery-example](https://github.com/supabase-community/partner-gallery-example). It’s packed with goodies like [Postgres Full Text Search](/docs/guides/database/full-text-search), `next-image` with Supabase Storage, and sending emails based on an insert trigger with Supabase Edge Functions. # Partner showcase - new integrations and expert services diff --git a/apps/www/_blog/2022-08-09-slack-consolidate-slackbot-to-consolidate-messages.mdx b/apps/www/_blog/2022-08-09-slack-consolidate-slackbot-to-consolidate-messages.mdx index 2172b0330a7..7581b57e33a 100644 --- a/apps/www/_blog/2022-08-09-slack-consolidate-slackbot-to-consolidate-messages.mdx +++ b/apps/www/_blog/2022-08-09-slack-consolidate-slackbot-to-consolidate-messages.mdx @@ -13,7 +13,7 @@ date: '2022-08-09' toc_depth: 3 --- -Supabase is a platform-as-a-service built on top of PostgreSQL and [many other amazing open-source tools](https://supabase.com/docs/architecture). It’s a fantastic option to create data-intensive apps and tooling. Taking advantage of the community-made [Python SDK](https://github.com/supabase-community/supabase-py) and [Slack's Python SDK](https://slack.dev/python-slack-sdk/), you can automate tasks and build apps for several use cases. +Supabase is a platform-as-a-service built on top of PostgreSQL and [many other amazing open-source tools](https://supabase.com/docs/guides/getting-started/architecture). It’s a fantastic option to create data-intensive apps and tooling. Taking advantage of the community-made [Python SDK](https://github.com/supabase-community/supabase-py) and [Slack's Python SDK](https://slack.dev/python-slack-sdk/), you can automate tasks and build apps for several use cases. ## Prerequisites @@ -161,9 +161,9 @@ Slack links have the following format: https://ORGANIZATION.slack.com/archives/ channel_id/pmessage_id -
    +
    Organization: subdomain used in Slack -
    +
    Channel ID: It is the string that you'll need to enter in slack channels as the channel id e.g C0000ABC02DE @@ -418,7 +418,3 @@ If you have any questions please reach out via [Twitter](https://twitter.com/sup - [Python data loading with Supabase](https://supabase.com/blog/loading-data-supabase-python) - [Visualizing Supabase Data using Metabase](https://supabase.com/blog/visualizing-supabase-data-using-metabase) - [Supabase-py (Database) on Replit](https://replit.com/@Supabase/Supabase-py-Database?v=1) - - - - From 41d411939b0931d4641122e6ab63988dfd12487b Mon Sep 17 00:00:00 2001 From: Ryan Peach Date: Thu, 22 Dec 2022 18:43:52 -0500 Subject: [PATCH 051/127] Updated plain text descriptions of file locations. I struggled following this guide on the homepage because the site CSS does not show the title of the markdown code snippets in the plain text of the code. As such the path is hidden, and as a new user of the codebase, it's important to be clear about where the code should be located and what it should be called. --- .../guides/getting-started/tutorials/with-sveltekit.mdx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx index d8af92de291..0efd740c84f 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx @@ -46,8 +46,8 @@ PUBLIC_SUPABASE_URL="YOUR_SUPABASE_URL" PUBLIC_SUPABASE_ANON_KEY="YOUR_SUPABASE_KEY" ``` -Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed -on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database. +Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. Call it `src/lib/supabaseClient.ts`. +These variables will be exposed on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database. ```js title=src/lib/supabaseClient.ts import { createClient } from '@supabase/auth-helpers-sveltekit' @@ -136,6 +136,7 @@ import '$lib/supabaseClient' ### Set up a Login component Let's set up a Svelte component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords. +Create a new `Auth.svelte` component in the `src/routes` directory to handle this functionality. ```html title=src/routes/Auth.svelte