From 1e3dea91aa04f9b95adb9085ff941f2e795f7834 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 25 Apr 2023 17:01:26 +0800 Subject: [PATCH 01/39] Init API authorization page and scaffold RQ for API auth requests --- .../layouts/APIAuthorizationLayout.tsx | 49 ++++++++++ .../api-authorization-approve-mutation.ts | 43 +++++++++ .../api-authorization-decline-mutation.ts | 40 ++++++++ .../api-authorization-query.ts | 52 ++++++++++ studio/data/api-authorization/keys.ts | 3 + studio/data/organizations/keys.ts | 1 + .../data/organizations/organizations-query.ts | 37 +++++++ studio/pages/authorize.tsx | 96 +++++++++++++++++++ 8 files changed, 321 insertions(+) create mode 100644 studio/components/layouts/APIAuthorizationLayout.tsx create mode 100644 studio/data/api-authorization/api-authorization-approve-mutation.ts create mode 100644 studio/data/api-authorization/api-authorization-decline-mutation.ts create mode 100644 studio/data/api-authorization/api-authorization-query.ts create mode 100644 studio/data/api-authorization/keys.ts create mode 100644 studio/data/organizations/organizations-query.ts create mode 100644 studio/pages/authorize.tsx diff --git a/studio/components/layouts/APIAuthorizationLayout.tsx b/studio/components/layouts/APIAuthorizationLayout.tsx new file mode 100644 index 00000000000..f43656a4303 --- /dev/null +++ b/studio/components/layouts/APIAuthorizationLayout.tsx @@ -0,0 +1,49 @@ +import Head from 'next/head' +import Image from 'next/image' +import { useTheme } from 'common' +import { PropsWithChildren } from 'react' +import { BASE_PATH } from 'lib/constants' +import Divider from 'components/ui/Divider' + +export interface APIAuthorizationLayoutProps {} + +const APIAuthorizationLayout = ({ children }: PropsWithChildren) => { + const { isDarkMode } = useTheme() + return ( + <> + + Authorize API access | Supabase + +
+
+
+
+
+
+ Supabase + Supabase Logo +
+
+
+
+
+ +
+

Authorize API Access

+ {children} +
+
+ + ) +} + +export default APIAuthorizationLayout diff --git a/studio/data/api-authorization/api-authorization-approve-mutation.ts b/studio/data/api-authorization/api-authorization-approve-mutation.ts new file mode 100644 index 00000000000..5654041d540 --- /dev/null +++ b/studio/data/api-authorization/api-authorization-approve-mutation.ts @@ -0,0 +1,43 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { post } from 'lib/common/fetch' +import { API_ADMIN_URL } from 'lib/constants' +// import { resourceKeys } from './keys' + +export type ApiAuthorizationApproveVariables = { + id: string +} + +export async function approveApiAuthorization({ id }: ApiAuthorizationApproveVariables) { + if (!id) throw new Error('Authorization ID is required') + + const response = await post( + `${API_ADMIN_URL}/oauth/authorization/${id}?skip_browser_redirect=true`, + {} + ) + if (response.error) throw response.error + return response +} + +type ApiAuthorizationApproveData = Awaited> + +export const useApiAuthorizationApproveMutation = ({ + onSuccess, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (vars) => approveApiAuthorization(vars), + { + async onSuccess(data, variables, context) { + // const { id } = variables + // await queryClient.invalidateQueries(networkRestrictionKeys.list(projectRef)) + // await onSuccess?.(data, variables, context) + }, + ...options, + } + ) +} diff --git a/studio/data/api-authorization/api-authorization-decline-mutation.ts b/studio/data/api-authorization/api-authorization-decline-mutation.ts new file mode 100644 index 00000000000..f623f7d2905 --- /dev/null +++ b/studio/data/api-authorization/api-authorization-decline-mutation.ts @@ -0,0 +1,40 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { delete_ } from 'lib/common/fetch' +import { API_ADMIN_URL } from 'lib/constants' +// import { resourceKeys } from './keys' + +export type ApiAuthorizationDeclineVariables = { + id: string +} + +export async function declineApiAuthorization({ id }: ApiAuthorizationDeclineVariables) { + if (!id) throw new Error('Authorization ID is required') + + const response = await delete_(`${API_ADMIN_URL}/oauth/authorization/${id}`, {}) + if (response.error) throw response.error + return response +} + +type ApiAuthorizationDeclineData = Awaited> + +export const useApiAuthorizationDeclineMutation = ({ + onSuccess, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (vars) => declineApiAuthorization(vars), + { + async onSuccess(data, variables, context) { + // const { id } = variables + // await queryClient.invalidateQueries(networkRestrictionKeys.list(projectRef)) + // await onSuccess?.(data, variables, context) + }, + ...options, + } + ) +} diff --git a/studio/data/api-authorization/api-authorization-query.ts b/studio/data/api-authorization/api-authorization-query.ts new file mode 100644 index 00000000000..f687e49c403 --- /dev/null +++ b/studio/data/api-authorization/api-authorization-query.ts @@ -0,0 +1,52 @@ +import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' +import { get } from 'lib/common/fetch' +import { API_ADMIN_URL } from 'lib/constants' +import { useCallback } from 'react' +import { resourceKeys } from './keys' + +export type ApiAuthorizationVariables = { + id?: string +} + +export type ApiAuthorizationResponse = { + id: string +} + +export async function getApiAuthorizationDetails( + { id }: ApiAuthorizationVariables, + signal?: AbortSignal +) { + if (!id) throw new Error('Authorization ID is required') + + const response = await get(`${API_ADMIN_URL}/oauth/authorization/${id}`, { signal }) + if (response.error) throw response.error + return response as ApiAuthorizationResponse +} + +export type ResourceData = Awaited> +export type ResourceError = unknown + +export const useApiAuthorizationQuery = ( + { id }: ApiAuthorizationVariables, + { enabled = true, ...options }: UseQueryOptions = {} +) => + useQuery( + resourceKeys.resource(id), + ({ signal }) => getApiAuthorizationDetails({ id }, signal), + { + enabled: enabled && typeof id !== 'undefined', + ...options, + } + ) + +export const useApiAuthorizationPrefetch = ({ id }: ApiAuthorizationVariables) => { + const client = useQueryClient() + + return useCallback(() => { + if (id) { + client.prefetchQuery(resourceKeys.resource(id), ({ signal }) => + getApiAuthorizationDetails({ id }, signal) + ) + } + }, [id]) +} diff --git a/studio/data/api-authorization/keys.ts b/studio/data/api-authorization/keys.ts new file mode 100644 index 00000000000..69660c24f42 --- /dev/null +++ b/studio/data/api-authorization/keys.ts @@ -0,0 +1,3 @@ +export const resourceKeys = { + resource: (id: string | undefined) => ['api-authorization', id] as const, +} diff --git a/studio/data/organizations/keys.ts b/studio/data/organizations/keys.ts index 5b7250c028d..4aabe753ab3 100644 --- a/studio/data/organizations/keys.ts +++ b/studio/data/organizations/keys.ts @@ -1,4 +1,5 @@ export const organizationKeys = { + list: () => ['organizations'] as const, detail: (slug: string | undefined) => ['organizations', slug, 'detail'] as const, roles: (slug: string | undefined) => ['organizations', slug, 'roles'] as const, freeProjectLimitCheck: (slug: string | undefined) => diff --git a/studio/data/organizations/organizations-query.ts b/studio/data/organizations/organizations-query.ts new file mode 100644 index 00000000000..598c8b17df4 --- /dev/null +++ b/studio/data/organizations/organizations-query.ts @@ -0,0 +1,37 @@ +import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' +import { get } from 'lib/common/fetch' +import { API_URL } from 'lib/constants' +import { useCallback } from 'react' +import { Organization } from 'types' +import { organizationKeys } from './keys' + +export type OrganizationsResponse = Organization[] + +export async function getOrganizations(signal?: AbortSignal) { + const data = await get(`${API_URL}/organizations`, { signal }) + if (data.error) throw data.error + + return data as OrganizationsResponse +} + +export type OrganizationsData = Awaited> +export type OrganizationsError = unknown + +export const useOrganizationsQuery = ({ + enabled = true, + ...options +}: UseQueryOptions = {}) => + useQuery( + organizationKeys.list(), + ({ signal }) => getOrganizations(signal), + { enabled: enabled, ...options } + ) + +export const useOrganizationsPrefetch = () => { + const client = useQueryClient() + + return useCallback( + () => client.prefetchQuery(organizationKeys.list(), ({ signal }) => getOrganizations(signal)), + [] + ) +} diff --git a/studio/pages/authorize.tsx b/studio/pages/authorize.tsx new file mode 100644 index 00000000000..9d4e3aed96f --- /dev/null +++ b/studio/pages/authorize.tsx @@ -0,0 +1,96 @@ +import { useParams } from 'common' + +import { withAuth } from 'hooks' +import { NextPageWithLayout } from 'types' +import { FormPanel } from 'components/ui/Forms' +import APIAuthorizationLayout from 'components/layouts/APIAuthorizationLayout' +import { Alert, Button, Listbox } from 'ui' +import { useOrganizationsQuery } from 'data/organizations/organizations-query' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import { useApiAuthorizationQuery } from 'data/api-authorization/api-authorization-query' + +const APIAuthorizationPage: NextPageWithLayout = () => { + const { auth_id } = useParams() + const { data: organizations, isLoading: isLoadingOrganizations } = useOrganizationsQuery() + // const { data: apiAuthDetails, isLoading, isSuccess, isError } = useApiAuthorizationQuery({ id: auth_id }) + + // [Joshen] To be replaced with actual API call above + const apiAuthDetails = { + icon: 'https://cdn-icons-png.flaticon.com/512/5969/5969044.png', + host: 'cloudflare.com', + name: 'Cloudflare', + } + + const isExpired = false + + return ( + +
+ + +
+ + } + > +
+ {/* API Authorization requester details */} +
+

Authorize {apiAuthDetails.name}

+
+
+
+
+
+
+

+ {apiAuthDetails.name} is requesting API access to an organization. The application + will be able to{' '} + + read and write the organization's settings and projects + +

+
+
+ + {/* Expiry warning */} + {isExpired && ( + + Please retry your authorization request from the requesting app + + )} + + {/* Organization selection */} + {isLoadingOrganizations ? ( +
+ + +
+ ) : ( + + {(organizations ?? []).map((organization) => ( + + {organization.name} + + ))} + + )} +
+
+ ) +} + +APIAuthorizationPage.getLayout = (page) => {page} +export default withAuth(APIAuthorizationPage) From f65eb032c452f7b007ad0d6c6d5d98314e74d118 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 25 Apr 2023 17:06:58 +0800 Subject: [PATCH 02/39] Small UI change --- .../layouts/APIAuthorizationLayout.tsx | 1 - studio/pages/authorize.tsx | 33 +++++++++---------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/studio/components/layouts/APIAuthorizationLayout.tsx b/studio/components/layouts/APIAuthorizationLayout.tsx index f43656a4303..143dd4a6526 100644 --- a/studio/components/layouts/APIAuthorizationLayout.tsx +++ b/studio/components/layouts/APIAuthorizationLayout.tsx @@ -38,7 +38,6 @@ const APIAuthorizationLayout = ({ children }: PropsWithChildren
-

Authorize API Access

{children}
diff --git a/studio/pages/authorize.tsx b/studio/pages/authorize.tsx index 9d4e3aed96f..c41f134f31f 100644 --- a/studio/pages/authorize.tsx +++ b/studio/pages/authorize.tsx @@ -25,6 +25,7 @@ const APIAuthorizationPage: NextPageWithLayout = () => { return ( Authorize API access for {apiAuthDetails.name}

} footer={
@@ -40,25 +41,23 @@ const APIAuthorizationPage: NextPageWithLayout = () => { >
{/* API Authorization requester details */} -
-

Authorize {apiAuthDetails.name}

-
-
-
-
-
+ +
+
+
+
-

- {apiAuthDetails.name} is requesting API access to an organization. The application - will be able to{' '} - - read and write the organization's settings and projects - -

+

+ {apiAuthDetails.name} is requesting API access to an organization. The application will + be able to{' '} + + read and write the organization's settings and projects + +

{/* Expiry warning */} From 63267c1fa9fe41d5c30aeec2ccccc9b624d9cdbc Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 25 Apr 2023 17:26:41 +0800 Subject: [PATCH 03/39] Scaffold error states to be handled --- studio/pages/authorize.tsx | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/studio/pages/authorize.tsx b/studio/pages/authorize.tsx index c41f134f31f..6161eca20ec 100644 --- a/studio/pages/authorize.tsx +++ b/studio/pages/authorize.tsx @@ -21,8 +21,38 @@ const APIAuthorizationPage: NextPageWithLayout = () => { name: 'Cloudflare', } + // [Joshen] To be replaced with actual API call above + const isApiAuthDetailsError = false const isExpired = false + if (auth_id === undefined) { + return ( + Authorization for API access

}> +
+ + Please provide a valid authorization ID in the URL + +
+
+ ) + } + + if (isApiAuthDetailsError) { + return ( + Authorization for API access

}> +
+ + Please retry your authorization request from the requesting app + +
+
+ ) + } + return ( Authorize API access for {apiAuthDetails.name}

} From f13c7299238e8a3102ebf49d45a41e7f93e1bf72 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Wed, 3 May 2023 15:42:56 +0200 Subject: [PATCH 04/39] chore: add analytics event to hp herop --- apps/www/components/Hero.tsx | 23 +++++++++++-- apps/www/hooks/useGoogleAnalyticsProps.tsx | 13 ++++++++ apps/www/lib/constants.ts | 2 ++ apps/www/lib/fetchWrapper.ts | 4 +-- apps/www/lib/gaEvents.ts | 20 ++++++++++++ apps/www/lib/telemetry.ts | 38 ++++++++++++++++++++++ apps/www/pages/_app.tsx | 2 +- 7 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 apps/www/hooks/useGoogleAnalyticsProps.tsx create mode 100644 apps/www/lib/gaEvents.ts create mode 100644 apps/www/lib/telemetry.ts diff --git a/apps/www/components/Hero.tsx b/apps/www/components/Hero.tsx index e97cd31fa74..5557da55251 100644 --- a/apps/www/components/Hero.tsx +++ b/apps/www/components/Hero.tsx @@ -1,10 +1,18 @@ -import { Button, IconBookOpen, Space } from 'ui' +import { Button, IconBookOpen } from 'ui' import Link from 'next/link' import { useRouter } from 'next/router' import SectionContainer from './Layouts/SectionContainer' +import Telemetry, { GoogleAnalyticsEvent } from '~/lib/telemetry' +import gaEvents from '~/lib/gaEvents' +import useGoogleAnalyticsProps from '~/hooks/useGoogleAnalyticsProps' const Hero = () => { const { basePath } = useRouter() + const googleAnalyticsProps = useGoogleAnalyticsProps() + + const handleClick = (event: GoogleAnalyticsEvent) => { + Telemetry.sendEvent(event, googleAnalyticsProps) + } return (
@@ -41,14 +49,23 @@ const Hero = () => {
- - diff --git a/apps/www/hooks/useGoogleAnalyticsProps.tsx b/apps/www/hooks/useGoogleAnalyticsProps.tsx new file mode 100644 index 00000000000..a3254a8b307 --- /dev/null +++ b/apps/www/hooks/useGoogleAnalyticsProps.tsx @@ -0,0 +1,13 @@ +import { useRouter } from 'next/router' + +const useGoogleAnalyticsProps = () => { + const { locale } = useRouter() + + return { + screenResolution: + typeof window !== 'undefined' ? `${window.innerWidth}x${window.innerHeight}` : undefined, + language: locale ?? 'en-US', + } +} + +export default useGoogleAnalyticsProps diff --git a/apps/www/lib/constants.ts b/apps/www/lib/constants.ts index e3c815b5dca..1b7537a438d 100644 --- a/apps/www/lib/constants.ts +++ b/apps/www/lib/constants.ts @@ -1,5 +1,7 @@ export const APP_NAME = 'Supabase' export const DESCRIPTION = 'The Open Source Alternative to Firebase.' +export const IS_PROD = process.env.NEXT_PUBLIC_VERCEL_ENV === 'production' +export const API_URL = !IS_PROD ? process.env.NEXT_PUBLIC_API_URL : '/api' // Launch Weeek export const SAMPLE_TICKET_NUMBER = 1234 diff --git a/apps/www/lib/fetchWrapper.ts b/apps/www/lib/fetchWrapper.ts index c573a0d37dc..0313e49565f 100644 --- a/apps/www/lib/fetchWrapper.ts +++ b/apps/www/lib/fetchWrapper.ts @@ -1,7 +1,5 @@ interface DataProps { - referrer?: string - title: string - route?: string + [prop: string]: any } export const post = (url: string, data: DataProps, options = {}) => { diff --git a/apps/www/lib/gaEvents.ts b/apps/www/lib/gaEvents.ts new file mode 100644 index 00000000000..53908751474 --- /dev/null +++ b/apps/www/lib/gaEvents.ts @@ -0,0 +1,20 @@ +import { GoogleAnalyticsEvent } from './telemetry' + +interface GoogleAnalyticsEventType { + [key: string]: GoogleAnalyticsEvent +} + +const GoogleAnalyticsEvents: GoogleAnalyticsEventType = { + www_hp_hero_startProject: { + action: 'www_hp_hero_startProject', + category: 'link', + label: 'Clicks on “Start your project” button in the home page hero', + }, + www_hp_hero_documentation: { + action: 'www_hp_hero_documentation', + category: 'link', + label: 'Clicks on “Documentation” button in the home page hero', + }, +} + +export default GoogleAnalyticsEvents diff --git a/apps/www/lib/telemetry.ts b/apps/www/lib/telemetry.ts new file mode 100644 index 00000000000..cdaa08cf1e8 --- /dev/null +++ b/apps/www/lib/telemetry.ts @@ -0,0 +1,38 @@ +import { post } from '~/lib/fetchWrapper' +import { API_URL, IS_PROD } from 'lib/constants' + +export interface GoogleAnalyticsEvent { + category: string + action: string + label: string + value?: string +} + +export interface GoogleAnalyticsProps { + screenResolution?: string + language: string +} + +// This event is the same as in studio/lib/telemetry.tx +// but uses different ENV variables for www + +const sendEvent = (event: GoogleAnalyticsEvent, gaProps: GoogleAnalyticsProps) => { + if (!IS_PROD) return + + const { category, action, label, value } = event + + return post(`${API_URL}/telemetry/event`, { + action: action, + category: category, + label: label, + value: value, + ga: { + screen_resolution: gaProps?.screenResolution, + language: gaProps?.language, + }, + }) +} + +export default { + sendEvent, +} diff --git a/apps/www/pages/_app.tsx b/apps/www/pages/_app.tsx index 2ccb053b3ae..60a1a4684fe 100644 --- a/apps/www/pages/_app.tsx +++ b/apps/www/pages/_app.tsx @@ -5,7 +5,7 @@ import { useRouter } from 'next/router' import { useEffect } from 'react' import Meta from '~/components/Favicons' import '../styles/index.css' -import { post } from './../lib/fetchWrapper' +import { post } from '~/lib/fetchWrapper' import { AuthProvider, ThemeProvider } from 'common' import Head from 'next/head' From da33e3bea6a2507da6f995d5ecdfd47ba678f72f Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Wed, 3 May 2023 16:09:32 +0200 Subject: [PATCH 05/39] chore: navigate with router --- apps/www/components/Hero.tsx | 58 ++++++++++++++++++------------------ apps/www/lib/constants.ts | 2 +- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/apps/www/components/Hero.tsx b/apps/www/components/Hero.tsx index 5557da55251..53df050350e 100644 --- a/apps/www/components/Hero.tsx +++ b/apps/www/components/Hero.tsx @@ -7,11 +7,12 @@ import gaEvents from '~/lib/gaEvents' import useGoogleAnalyticsProps from '~/hooks/useGoogleAnalyticsProps' const Hero = () => { - const { basePath } = useRouter() + const router = useRouter() const googleAnalyticsProps = useGoogleAnalyticsProps() - const handleClick = (event: GoogleAnalyticsEvent) => { + const trackAndNavigate = (url: string, event: GoogleAnalyticsEvent) => { Telemetry.sendEvent(event, googleAnalyticsProps) + router.push(url) } return ( @@ -47,29 +48,28 @@ const Hero = () => {
- - - - - - - - - - + +
backed by @@ -77,22 +77,22 @@ const Hero = () => {
Y Combinator Mozilla Coatue Felicis
diff --git a/apps/www/lib/constants.ts b/apps/www/lib/constants.ts index 1b7537a438d..5586bb59f81 100644 --- a/apps/www/lib/constants.ts +++ b/apps/www/lib/constants.ts @@ -1,7 +1,7 @@ export const APP_NAME = 'Supabase' export const DESCRIPTION = 'The Open Source Alternative to Firebase.' export const IS_PROD = process.env.NEXT_PUBLIC_VERCEL_ENV === 'production' -export const API_URL = !IS_PROD ? process.env.NEXT_PUBLIC_API_URL : '/api' +export const API_URL = IS_PROD ? process.env.NEXT_PUBLIC_API_URL : '/api' // Launch Weeek export const SAMPLE_TICKET_NUMBER = 1234 From 5e2dca6cc07904482590eefb7e07e2ddcd3c8a9a Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Wed, 3 May 2023 16:19:51 +0200 Subject: [PATCH 06/39] chore: update API_URL preview check --- apps/www/components/Hero.tsx | 5 ++--- apps/www/lib/constants.ts | 3 ++- apps/www/lib/gaEvents.ts | 4 ++-- apps/www/lib/telemetry.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/www/components/Hero.tsx b/apps/www/components/Hero.tsx index 53df050350e..32f699d2939 100644 --- a/apps/www/components/Hero.tsx +++ b/apps/www/components/Hero.tsx @@ -1,5 +1,4 @@ import { Button, IconBookOpen } from 'ui' -import Link from 'next/link' import { useRouter } from 'next/router' import SectionContainer from './Layouts/SectionContainer' import Telemetry, { GoogleAnalyticsEvent } from '~/lib/telemetry' @@ -10,8 +9,8 @@ const Hero = () => { const router = useRouter() const googleAnalyticsProps = useGoogleAnalyticsProps() - const trackAndNavigate = (url: string, event: GoogleAnalyticsEvent) => { - Telemetry.sendEvent(event, googleAnalyticsProps) + const trackAndNavigate = async (url: string, event: GoogleAnalyticsEvent) => { + await Telemetry.sendEvent(event, googleAnalyticsProps) router.push(url) } diff --git a/apps/www/lib/constants.ts b/apps/www/lib/constants.ts index 5586bb59f81..445c0a6810a 100644 --- a/apps/www/lib/constants.ts +++ b/apps/www/lib/constants.ts @@ -1,7 +1,8 @@ export const APP_NAME = 'Supabase' export const DESCRIPTION = 'The Open Source Alternative to Firebase.' export const IS_PROD = process.env.NEXT_PUBLIC_VERCEL_ENV === 'production' -export const API_URL = IS_PROD ? process.env.NEXT_PUBLIC_API_URL : '/api' +export const IS_PREVIEW = process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview' +export const API_URL = process.env.NEXT_PUBLIC_API_URL // Launch Weeek export const SAMPLE_TICKET_NUMBER = 1234 diff --git a/apps/www/lib/gaEvents.ts b/apps/www/lib/gaEvents.ts index 53908751474..167aa356a0c 100644 --- a/apps/www/lib/gaEvents.ts +++ b/apps/www/lib/gaEvents.ts @@ -8,12 +8,12 @@ const GoogleAnalyticsEvents: GoogleAnalyticsEventType = { www_hp_hero_startProject: { action: 'www_hp_hero_startProject', category: 'link', - label: 'Clicks on “Start your project” button in the home page hero', + label: '', }, www_hp_hero_documentation: { action: 'www_hp_hero_documentation', category: 'link', - label: 'Clicks on “Documentation” button in the home page hero', + label: '', }, } diff --git a/apps/www/lib/telemetry.ts b/apps/www/lib/telemetry.ts index cdaa08cf1e8..0c6fb2ed9fe 100644 --- a/apps/www/lib/telemetry.ts +++ b/apps/www/lib/telemetry.ts @@ -1,5 +1,5 @@ import { post } from '~/lib/fetchWrapper' -import { API_URL, IS_PROD } from 'lib/constants' +import { API_URL, IS_PROD, IS_PREVIEW } from 'lib/constants' export interface GoogleAnalyticsEvent { category: string @@ -17,7 +17,7 @@ export interface GoogleAnalyticsProps { // but uses different ENV variables for www const sendEvent = (event: GoogleAnalyticsEvent, gaProps: GoogleAnalyticsProps) => { - if (!IS_PROD) return + if (!IS_PROD && !IS_PREVIEW) return const { category, action, label, value } = event From 6c33e30f4c91fb8a41ea123d25905314aacddacd Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Thu, 4 May 2023 12:42:11 +0200 Subject: [PATCH 07/39] chore: update telemetry API_URL on page load From 77ecbde3c7873f9f3b4732a54c564bfdda222298 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Thu, 4 May 2023 12:59:16 +0200 Subject: [PATCH 08/39] chore: update telemetry API_URL on page load --- apps/www/pages/_app.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/www/pages/_app.tsx b/apps/www/pages/_app.tsx index 60a1a4684fe..dce4295802c 100644 --- a/apps/www/pages/_app.tsx +++ b/apps/www/pages/_app.tsx @@ -1,4 +1,4 @@ -import { APP_NAME, DESCRIPTION } from 'lib/constants' +import { API_URL, APP_NAME, DESCRIPTION } from 'lib/constants' import { DefaultSeo } from 'next-seo' import { AppProps } from 'next/app' import { useRouter } from 'next/router' @@ -13,7 +13,7 @@ export default function MyApp({ Component, pageProps }: AppProps) { const router = useRouter() function handlePageTelemetry(route: string) { - return post(`https://api.supabase.io/platform/telemetry/page`, { + return post(`${API_URL}/telemetry/page`, { referrer: document.referrer, title: document.title, route, From eaddf931eb820248938555f4bbd5ec43aea2e642 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Thu, 4 May 2023 13:49:16 +0200 Subject: [PATCH 09/39] chore: update hardcoded API_URL in docs page telemetry --- apps/docs/lib/constants.ts | 1 + apps/docs/pages/_app.tsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/docs/lib/constants.ts b/apps/docs/lib/constants.ts index d1bbedbf47c..42c12c0d3f4 100644 --- a/apps/docs/lib/constants.ts +++ b/apps/docs/lib/constants.ts @@ -1,2 +1,3 @@ export const IS_PLATFORM = process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' export const LOCAL_SUPABASE = process.env.NEXT_PUBLIC_LOCAL_SUPABASE === 'true' +export const API_URL = process.env.NEXT_PUBLIC_API_URL diff --git a/apps/docs/pages/_app.tsx b/apps/docs/pages/_app.tsx index 0ac7df56c63..ed75637efaa 100644 --- a/apps/docs/pages/_app.tsx +++ b/apps/docs/pages/_app.tsx @@ -10,7 +10,7 @@ import { CommandMenuProvider } from 'ui' import components from '~/components' import Favicons from '~/components/Favicons' import SiteLayout from '~/layouts/SiteLayout' -import { IS_PLATFORM, LOCAL_SUPABASE } from '~/lib/constants' +import { API_URL, IS_PLATFORM, LOCAL_SUPABASE } from '~/lib/constants' import { post } from '~/lib/fetchWrappers' import '../styles/ch.scss' import '../styles/main.scss?v=1.0.0' @@ -25,7 +25,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { ) function handlePageTelemetry(route: string) { - return post(`https://api.supabase.io/platform/telemetry/page`, { + return post(`${API_URL}/telemetry/page`, { referrer: document.referrer, title: document.title, route, From 03f7a509285e05365bbafead2eb47df9c70627ee Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Thu, 4 May 2023 17:12:32 +0200 Subject: [PATCH 10/39] chore: add product links ga events --- apps/www/components/Features/index.tsx | 48 ++++++++++++++++++++++-- apps/www/components/Hero.tsx | 52 ++++++++++++-------------- apps/www/components/TextLink.tsx | 28 +++++++------- apps/www/lib/gaEvents.ts | 25 +++++++++++++ 4 files changed, 107 insertions(+), 46 deletions(-) diff --git a/apps/www/components/Features/index.tsx b/apps/www/components/Features/index.tsx index b5c8dc3f68f..132690287bf 100644 --- a/apps/www/components/Features/index.tsx +++ b/apps/www/components/Features/index.tsx @@ -1,12 +1,46 @@ -import { Button, Badge, IconArrowRight } from 'ui' -import SectionHeader from 'components/UI/SectionHeader' +import { Badge } from 'ui' import Solutions from 'data/Solutions.json' -import Link from 'next/link' +import useGoogleAnalyticsProps from '~/hooks/useGoogleAnalyticsProps' +import Telemetry from '~/lib/telemetry' +import gaEvents from '~/lib/gaEvents' import SectionContainer from '../Layouts/SectionContainer' import ProductIcon from '../ProductIcon' import TextLink from '../TextLink' const Features = () => { + const googleAnalyticsProps = useGoogleAnalyticsProps() + + const sendTelemetryEvent = async (product: any) => { + switch (product) { + case 'Database': + return await Telemetry.sendEvent( + gaEvents['www_hp_subhero_products_database'], + googleAnalyticsProps + ) + case 'Authentication': + return await Telemetry.sendEvent( + gaEvents['www_hp_subhero_products_auth'], + googleAnalyticsProps + ) + case 'Storage': + return await Telemetry.sendEvent( + gaEvents['www_hp_subhero_products_storage'], + googleAnalyticsProps + ) + case 'Edge Functions': + console.log('edge!!') + return await Telemetry.sendEvent( + gaEvents['www_hp_subhero_products_edgeFunctions'], + googleAnalyticsProps + ) + case 'Realtime': + return await Telemetry.sendEvent( + gaEvents['www_hp_subhero_products_realtime'], + googleAnalyticsProps + ) + } + } + const IconSections = Object.values(Solutions).map((solution: any) => { const { name, description, icon, label, url } = solution if (solution.name === 'Realtime') return null @@ -24,7 +58,13 @@ const Features = () => { {label}
)} - {url && } + {url && ( + sendTelemetryEvent(name)} + /> + )}
) }) diff --git a/apps/www/components/Hero.tsx b/apps/www/components/Hero.tsx index 32f699d2939..b532225359b 100644 --- a/apps/www/components/Hero.tsx +++ b/apps/www/components/Hero.tsx @@ -1,17 +1,17 @@ import { Button, IconBookOpen } from 'ui' import { useRouter } from 'next/router' +import Link from 'next/link' import SectionContainer from './Layouts/SectionContainer' import Telemetry, { GoogleAnalyticsEvent } from '~/lib/telemetry' import gaEvents from '~/lib/gaEvents' import useGoogleAnalyticsProps from '~/hooks/useGoogleAnalyticsProps' const Hero = () => { - const router = useRouter() + const { basePath } = useRouter() const googleAnalyticsProps = useGoogleAnalyticsProps() - const trackAndNavigate = async (url: string, event: GoogleAnalyticsEvent) => { + const sendTelemetryEvent = async (event: GoogleAnalyticsEvent) => { await Telemetry.sendEvent(event, googleAnalyticsProps) - router.push(url) } return ( @@ -47,28 +47,22 @@ const Hero = () => {
- - + + sendTelemetryEvent(gaEvents['www_hp_hero_startProject'])}> + + + + + sendTelemetryEvent(gaEvents['www_hp_hero_documentation'])} + > + + +
backed by @@ -76,22 +70,22 @@ const Hero = () => {
Y Combinator Mozilla Coatue Felicis
diff --git a/apps/www/components/TextLink.tsx b/apps/www/components/TextLink.tsx index 3bd3edfc10e..5da959a41fe 100644 --- a/apps/www/components/TextLink.tsx +++ b/apps/www/components/TextLink.tsx @@ -1,20 +1,22 @@ -import { IconArrowRight, IconChevronRight } from 'ui' +import { IconChevronRight } from 'ui' import Link from 'next/link' -function TextLink({ url, label }: any) { +function TextLink({ url, label, ...props }: any) { return ( - -
- {`${label} about ${url}`} - {label} - - + + ) } diff --git a/apps/www/lib/gaEvents.ts b/apps/www/lib/gaEvents.ts index 167aa356a0c..73599720f86 100644 --- a/apps/www/lib/gaEvents.ts +++ b/apps/www/lib/gaEvents.ts @@ -15,6 +15,31 @@ const GoogleAnalyticsEvents: GoogleAnalyticsEventType = { category: 'link', label: '', }, + www_hp_subhero_products_database: { + action: 'www_hp_subhero_products_database', + category: 'link', + label: '', + }, + www_hp_subhero_products_auth: { + action: 'www_hp_subhero_products_auth', + category: 'link', + label: '', + }, + www_hp_subhero_products_storage: { + action: 'www_hp_subhero_products_storage', + category: 'link', + label: '', + }, + www_hp_subhero_products_edgeFunctions: { + action: 'www_hp_subhero_products_edgeFunctions', + category: 'link', + label: '', + }, + www_hp_subhero_products_realtime: { + action: 'www_hp_subhero_products_realtime', + category: 'link', + label: '', + }, } export default GoogleAnalyticsEvents From 4715044f5672be33ccbf53df4bd3e3d576ed1ab5 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Thu, 4 May 2023 17:22:46 +0200 Subject: [PATCH 11/39] chore: fix TextLink href --- apps/www/components/Features/index.tsx | 1 - apps/www/components/TextLink.tsx | 4 ++-- apps/www/data/Solutions.json | 8 -------- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/apps/www/components/Features/index.tsx b/apps/www/components/Features/index.tsx index 132690287bf..bb84b4dbf4c 100644 --- a/apps/www/components/Features/index.tsx +++ b/apps/www/components/Features/index.tsx @@ -28,7 +28,6 @@ const Features = () => { googleAnalyticsProps ) case 'Edge Functions': - console.log('edge!!') return await Telemetry.sendEvent( gaEvents['www_hp_subhero_products_edgeFunctions'], googleAnalyticsProps diff --git a/apps/www/components/TextLink.tsx b/apps/www/components/TextLink.tsx index 5da959a41fe..73d54f6136e 100644 --- a/apps/www/components/TextLink.tsx +++ b/apps/www/components/TextLink.tsx @@ -1,9 +1,9 @@ import { IconChevronRight } from 'ui' import Link from 'next/link' -function TextLink({ url, label, ...props }: any) { +function TextLink({ url = '', label, ...props }: any) { return ( - + Date: Thu, 4 May 2023 17:24:50 +0200 Subject: [PATCH 12/39] chore: fix docs link --- apps/www/components/Hero.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/components/Hero.tsx b/apps/www/components/Hero.tsx index b532225359b..28cbc079bca 100644 --- a/apps/www/components/Hero.tsx +++ b/apps/www/components/Hero.tsx @@ -54,7 +54,7 @@ const Hero = () => { - + sendTelemetryEvent(gaEvents['www_hp_hero_documentation'])} > From ac10f883b6c483a7f406dcc8df5cd7bfc4460827 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 5 May 2023 17:44:31 +0800 Subject: [PATCH 13/39] Hook up endpoints --- .../api-authorization-approve-mutation.ts | 31 ++-- .../api-authorization-decline-mutation.ts | 22 +-- .../api-authorization-query.ts | 28 ++- studio/pages/authorize.tsx | 170 ++++++++++++++---- 4 files changed, 186 insertions(+), 65 deletions(-) diff --git a/studio/data/api-authorization/api-authorization-approve-mutation.ts b/studio/data/api-authorization/api-authorization-approve-mutation.ts index 5654041d540..5ac01111adc 100644 --- a/studio/data/api-authorization/api-authorization-approve-mutation.ts +++ b/studio/data/api-authorization/api-authorization-approve-mutation.ts @@ -1,21 +1,29 @@ -import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { useMutation, UseMutationOptions } from '@tanstack/react-query' import { post } from 'lib/common/fetch' import { API_ADMIN_URL } from 'lib/constants' -// import { resourceKeys } from './keys' export type ApiAuthorizationApproveVariables = { id: string + organization_id: string } -export async function approveApiAuthorization({ id }: ApiAuthorizationApproveVariables) { +export type ApiAuthorizationApproveResponse = { + url: string +} + +export async function approveApiAuthorization({ + id, + organization_id, +}: ApiAuthorizationApproveVariables) { if (!id) throw new Error('Authorization ID is required') + if (!organization_id) throw new Error('Organization slug is required') const response = await post( - `${API_ADMIN_URL}/oauth/authorization/${id}?skip_browser_redirect=true`, - {} + `${API_ADMIN_URL}/oauth/authorizations/${id}?skip_browser_redirect=true`, + { organization_id } ) if (response.error) throw response.error - return response + return response as ApiAuthorizationApproveResponse } type ApiAuthorizationApproveData = Awaited> @@ -27,17 +35,8 @@ export const useApiAuthorizationApproveMutation = ({ UseMutationOptions, 'mutationFn' > = {}) => { - const queryClient = useQueryClient() - return useMutation( (vars) => approveApiAuthorization(vars), - { - async onSuccess(data, variables, context) { - // const { id } = variables - // await queryClient.invalidateQueries(networkRestrictionKeys.list(projectRef)) - // await onSuccess?.(data, variables, context) - }, - ...options, - } + options ) } diff --git a/studio/data/api-authorization/api-authorization-decline-mutation.ts b/studio/data/api-authorization/api-authorization-decline-mutation.ts index f623f7d2905..7825bb01688 100644 --- a/studio/data/api-authorization/api-authorization-decline-mutation.ts +++ b/studio/data/api-authorization/api-authorization-decline-mutation.ts @@ -1,18 +1,21 @@ -import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { useMutation, UseMutationOptions } from '@tanstack/react-query' import { delete_ } from 'lib/common/fetch' import { API_ADMIN_URL } from 'lib/constants' -// import { resourceKeys } from './keys' export type ApiAuthorizationDeclineVariables = { id: string } +export type ApiAuthorizationDeclineResponse = { + id: string +} + export async function declineApiAuthorization({ id }: ApiAuthorizationDeclineVariables) { if (!id) throw new Error('Authorization ID is required') - const response = await delete_(`${API_ADMIN_URL}/oauth/authorization/${id}`, {}) + const response = await delete_(`${API_ADMIN_URL}/oauth/authorizations/${id}`, {}) if (response.error) throw response.error - return response + return response as ApiAuthorizationDeclineResponse } type ApiAuthorizationDeclineData = Awaited> @@ -24,17 +27,8 @@ export const useApiAuthorizationDeclineMutation = ({ UseMutationOptions, 'mutationFn' > = {}) => { - const queryClient = useQueryClient() - return useMutation( (vars) => declineApiAuthorization(vars), - { - async onSuccess(data, variables, context) { - // const { id } = variables - // await queryClient.invalidateQueries(networkRestrictionKeys.list(projectRef)) - // await onSuccess?.(data, variables, context) - }, - ...options, - } + options ) } diff --git a/studio/data/api-authorization/api-authorization-query.ts b/studio/data/api-authorization/api-authorization-query.ts index f687e49c403..bf73ad67872 100644 --- a/studio/data/api-authorization/api-authorization-query.ts +++ b/studio/data/api-authorization/api-authorization-query.ts @@ -9,7 +9,12 @@ export type ApiAuthorizationVariables = { } export type ApiAuthorizationResponse = { - id: string + name: string + website: string + domain: string + expires_at: string + approved_at: string | null + approved_organization_slug?: string } export async function getApiAuthorizationDetails( @@ -18,8 +23,25 @@ export async function getApiAuthorizationDetails( ) { if (!id) throw new Error('Authorization ID is required') - const response = await get(`${API_ADMIN_URL}/oauth/authorization/${id}`, { signal }) - if (response.error) throw response.error + const response = await get(`${API_ADMIN_URL}/oauth/authorizations/${id}`, { signal }) + if (response.error) { + // 404 is a valid error in which the auth id is invalid + const isInvalid = + (response.error as any)?.code === 404 && + (response.error as any)?.message?.includes('OAuth authorization request does not exist') + + if (isInvalid) { + return { + name: '', + website: '', + domain: '', + expires_at: '', + approved_at: null, + } as ApiAuthorizationResponse + } + + throw response.error + } return response as ApiAuthorizationResponse } diff --git a/studio/pages/authorize.tsx b/studio/pages/authorize.tsx index 6161eca20ec..5ea6e61ae0a 100644 --- a/studio/pages/authorize.tsx +++ b/studio/pages/authorize.tsx @@ -1,29 +1,101 @@ import { useParams } from 'common' - -import { withAuth } from 'hooks' -import { NextPageWithLayout } from 'types' -import { FormPanel } from 'components/ui/Forms' import APIAuthorizationLayout from 'components/layouts/APIAuthorizationLayout' -import { Alert, Button, Listbox } from 'ui' -import { useOrganizationsQuery } from 'data/organizations/organizations-query' +import { FormPanel } from 'components/ui/Forms' import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import { useApiAuthorizationApproveMutation } from 'data/api-authorization/api-authorization-approve-mutation' +import { useApiAuthorizationDeclineMutation } from 'data/api-authorization/api-authorization-decline-mutation' import { useApiAuthorizationQuery } from 'data/api-authorization/api-authorization-query' +import { useOrganizationsQuery } from 'data/organizations/organizations-query' +import dayjs from 'dayjs' +import { useStore, withAuth } from 'hooks' +import { useRouter } from 'next/router' +import { useEffect, useState } from 'react' +import { NextPageWithLayout } from 'types' +import { Alert, Button, Listbox } from 'ui' + +// Need to handle if no organizations in account +// Need to handle if not logged in yet state const APIAuthorizationPage: NextPageWithLayout = () => { + const { ui } = useStore() + const router = useRouter() const { auth_id } = useParams() - const { data: organizations, isLoading: isLoadingOrganizations } = useOrganizationsQuery() - // const { data: apiAuthDetails, isLoading, isSuccess, isError } = useApiAuthorizationQuery({ id: auth_id }) + const [isSubmitting, setIsSubmitting] = useState(false) + const [selectedOrg, setSelectedOrg] = useState() - // [Joshen] To be replaced with actual API call above - const apiAuthDetails = { - icon: 'https://cdn-icons-png.flaticon.com/512/5969/5969044.png', - host: 'cloudflare.com', - name: 'Cloudflare', + const { data: organizations, isLoading: isLoadingOrganizations } = useOrganizationsQuery() + const { data: requester, isLoading, isError } = useApiAuthorizationQuery({ id: auth_id }) + const isApproved = requester?.approved_at !== null + const isInvalid = requester?.name.length === 0 && requester.expires_at.length === 0 + const isExpired = dayjs().isAfter(dayjs(requester?.expires_at)) + + const { mutateAsync: approveRequest } = useApiAuthorizationApproveMutation() + const { mutateAsync: declineRequest } = useApiAuthorizationDeclineMutation() + + useEffect(() => { + if (!isLoadingOrganizations) { + setSelectedOrg(organizations?.[0].slug ?? undefined) + } + }, [isLoadingOrganizations]) + + const onApproveRequest = async () => { + if (!auth_id) { + return ui.setNotification({ + category: 'error', + message: 'Unable to approve request: auth_id is missing ', + }) + } + if (!selectedOrg) { + return ui.setNotification({ + category: 'error', + message: 'Unable to approve request: No organization selected', + }) + } + + try { + setIsSubmitting(true) + const res = await approveRequest({ id: auth_id, organization_id: selectedOrg }) + router.push(res.url) + } catch (error: any) { + ui.setNotification({ + category: 'error', + message: `Failed to approve request: ${error.message}`, + }) + setIsSubmitting(false) + } } - // [Joshen] To be replaced with actual API call above - const isApiAuthDetailsError = false - const isExpired = false + const onDeclineRequest = async () => { + if (!auth_id) + return ui.setNotification({ + category: 'error', + message: 'Unable to decline request: auth_id is missing ', + }) + + try { + setIsSubmitting(true) + await declineRequest({ id: auth_id }) + router.push('/projects') + } catch (error: any) { + ui.setNotification({ + category: 'error', + message: `Failed to decline request: ${error.message}`, + }) + setIsSubmitting(false) + } + } + + if (isLoading) { + return ( + Authorize API access

}> +
+ + + +
+
+ ) + } if (auth_id === undefined) { return ( @@ -37,9 +109,9 @@ const APIAuthorizationPage: NextPageWithLayout = () => { ) } - if (isApiAuthDetailsError) { + if (isInvalid || isError) { return ( - Authorization for API access

}> + Authorize API access

}>
{ ) } + if (isApproved) { + const approvedOrganization = organizations?.find( + (org) => org.slug === requester.approved_organization_slug + ) + + return ( + Authorize API access for {requester?.name}

}> +
+ +

+ {requester.name} has read and write access to the organization " + {approvedOrganization?.name ?? 'Unknown'}" and all of its projects +

+

+ Approved on: {dayjs(requester.approved_at).format('DD MMM YYYY HH:mm:ss (ZZ)')} +

+
+
+
+ ) + } + return ( Authorize API access for {apiAuthDetails.name}

} + header={

Authorize API access for {requester?.name}

} footer={
- -
@@ -76,23 +174,26 @@ const APIAuthorizationPage: NextPageWithLayout = () => {
+ className="w-8 h-8 md:w-10 md:h-10 bg-center bg-no-repeat bg-cover flex items-center justify-center" + // [Joshen] For when we support icons + // style={{ backgroundImage: `url('${requester?.icon}')` }} + > +

{requester?.name[0]}

+

- {apiAuthDetails.name} is requesting API access to an organization. The application will - be able to{' '} - - read and write the organization's settings and projects + {requester?.name} ({requester?.domain}) is requesting API access to an organization. The + application will be able to{' '} + + read and write the organization's settings and all of its projects.

{/* Expiry warning */} {isExpired && ( - + Please retry your authorization request from the requesting app )} @@ -104,12 +205,17 @@ const APIAuthorizationPage: NextPageWithLayout = () => {
) : ( - + {(organizations ?? []).map((organization) => ( {organization.name} From ffcfca5f552bbb4122a20f8c7d11018bd77e5aa6 Mon Sep 17 00:00:00 2001 From: "joel@joellee.org" Date: Sat, 6 May 2023 01:13:38 +0800 Subject: [PATCH 14/39] fix: add exchangeCodeForSession to spec --- spec/supabase_js_v2.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/supabase_js_v2.yml b/spec/supabase_js_v2.yml index 780e982f411..b7c659d38f6 100644 --- a/spec/supabase_js_v2.yml +++ b/spec/supabase_js_v2.yml @@ -567,6 +567,20 @@ functions: if (event == 'USER_UPDATED') console.log('USER_UPDATED', session) }) ``` + - id: exchange-code-for-session + title: 'exchangeCodeForSession()' + $ref: '@supabase/gotrue-js.GoTrueClient.exchangeCodeForSession' + notes: | + - Used when `flowType` is set to True in client options. + examples: + - id: exchange-auth-code + name: Exchange Auth Code + isSpotlight: true + code: | + ```js + supabase.auth.exchangeCodeForSession('34e770dd-9ff9-416c-87fa-43b31d7ef225') + ``` + - id: mfa-enroll title: 'mfa.enroll()' $ref: '@supabase/gotrue-js.GoTrueMFAApi.enroll' From 30adf466f00af4188b4b6b42c50ca91640f050db Mon Sep 17 00:00:00 2001 From: "joel@joellee.org" Date: Sat, 6 May 2023 01:18:05 +0800 Subject: [PATCH 15/39] fix: update client spec --- spec/common-client-libs-sections.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/common-client-libs-sections.json b/spec/common-client-libs-sections.json index 06bacd153c6..52b654279d2 100644 --- a/spec/common-client-libs-sections.json +++ b/spec/common-client-libs-sections.json @@ -558,6 +558,14 @@ "product": "auth", "type": "function" }, + { + "id": "exchange-code-for-session", + "title": "Exchange an auth code for a session", + "slug": "auth-exchangecodeforsession", + "product": "auth", + "type": "function" + + }, { "id": "mfa-enroll", "title": "Enroll a factor", From 6c8952711d4e9456f01dbbc83aa6cc4d1ead178a Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Sat, 6 May 2023 15:53:27 +0800 Subject: [PATCH 16/39] Small improvements --- .../api-authorization-query.ts | 21 ++----------------- studio/pages/authorize.tsx | 9 ++++---- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/studio/data/api-authorization/api-authorization-query.ts b/studio/data/api-authorization/api-authorization-query.ts index bf73ad67872..652c19c4d5f 100644 --- a/studio/data/api-authorization/api-authorization-query.ts +++ b/studio/data/api-authorization/api-authorization-query.ts @@ -24,29 +24,12 @@ export async function getApiAuthorizationDetails( if (!id) throw new Error('Authorization ID is required') const response = await get(`${API_ADMIN_URL}/oauth/authorizations/${id}`, { signal }) - if (response.error) { - // 404 is a valid error in which the auth id is invalid - const isInvalid = - (response.error as any)?.code === 404 && - (response.error as any)?.message?.includes('OAuth authorization request does not exist') - - if (isInvalid) { - return { - name: '', - website: '', - domain: '', - expires_at: '', - approved_at: null, - } as ApiAuthorizationResponse - } - - throw response.error - } + if (response.error) throw response.error return response as ApiAuthorizationResponse } export type ResourceData = Awaited> -export type ResourceError = unknown +export type ResourceError = { errorEventId: string; message: string } export const useApiAuthorizationQuery = ( { id }: ApiAuthorizationVariables, diff --git a/studio/pages/authorize.tsx b/studio/pages/authorize.tsx index 5ea6e61ae0a..86559bebe6e 100644 --- a/studio/pages/authorize.tsx +++ b/studio/pages/authorize.tsx @@ -24,9 +24,8 @@ const APIAuthorizationPage: NextPageWithLayout = () => { const [selectedOrg, setSelectedOrg] = useState() const { data: organizations, isLoading: isLoadingOrganizations } = useOrganizationsQuery() - const { data: requester, isLoading, isError } = useApiAuthorizationQuery({ id: auth_id }) + const { data: requester, isLoading, isError, error } = useApiAuthorizationQuery({ id: auth_id }) const isApproved = requester?.approved_at !== null - const isInvalid = requester?.name.length === 0 && requester.expires_at.length === 0 const isExpired = dayjs().isAfter(dayjs(requester?.expires_at)) const { mutateAsync: approveRequest } = useApiAuthorizationApproveMutation() @@ -75,6 +74,7 @@ const APIAuthorizationPage: NextPageWithLayout = () => { try { setIsSubmitting(true) await declineRequest({ id: auth_id }) + ui.setNotification({ category: 'success', message: 'Declined API authorization request' }) router.push('/projects') } catch (error: any) { ui.setNotification({ @@ -109,7 +109,7 @@ const APIAuthorizationPage: NextPageWithLayout = () => { ) } - if (isInvalid || isError) { + if (isError) { return ( Authorize API access

}>
@@ -118,7 +118,8 @@ const APIAuthorizationPage: NextPageWithLayout = () => { variant="warning" title="Failed to fetch details for API authorization request" > - Please retry your authorization request from the requesting app +

Please retry your authorization request from the requesting app

+ {error !== undefined &&

Error: {error?.message}

}
From bd60d6c7134f933ce50de73f0672e9e469870643 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Sat, 6 May 2023 16:05:38 +0800 Subject: [PATCH 17/39] Small fix --- studio/pages/authorize.tsx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/studio/pages/authorize.tsx b/studio/pages/authorize.tsx index 86559bebe6e..cc40fb28461 100644 --- a/studio/pages/authorize.tsx +++ b/studio/pages/authorize.tsx @@ -85,18 +85,6 @@ const APIAuthorizationPage: NextPageWithLayout = () => { } } - if (isLoading) { - return ( - Authorize API access

}> -
- - - -
-
- ) - } - if (auth_id === undefined) { return ( Authorization for API access

}> @@ -109,6 +97,18 @@ const APIAuthorizationPage: NextPageWithLayout = () => { ) } + if (isLoading) { + return ( + Authorize API access

}> +
+ + + +
+
+ ) + } + if (isError) { return ( Authorize API access

}> From 0e99870f11e351f6bd7c2f7dd904c5b1edd1f250 Mon Sep 17 00:00:00 2001 From: "joel@joellee.org" Date: Mon, 8 May 2023 00:06:51 +0800 Subject: [PATCH 18/39] fix: minor misinformation --- spec/supabase_js_v2.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/supabase_js_v2.yml b/spec/supabase_js_v2.yml index b7c659d38f6..3c2ec6c0a9f 100644 --- a/spec/supabase_js_v2.yml +++ b/spec/supabase_js_v2.yml @@ -571,7 +571,7 @@ functions: title: 'exchangeCodeForSession()' $ref: '@supabase/gotrue-js.GoTrueClient.exchangeCodeForSession' notes: | - - Used when `flowType` is set to True in client options. + - Used when `flowType` is set to `pkce` in client options. examples: - id: exchange-auth-code name: Exchange Auth Code From 10d52861dc2ad5f541a05dd64c85eb5c156066de Mon Sep 17 00:00:00 2001 From: phamhieu Date: Mon, 8 May 2023 09:27:46 +0700 Subject: [PATCH 19/39] feat: shared useGoogleAnalyticsProps --- apps/docs/lib/fetchWrappers.tsx | 4 +-- apps/docs/pages/_app.tsx | 7 +++- apps/www/components/Features/index.tsx | 2 +- apps/www/components/Hero.tsx | 2 +- apps/www/hooks/useGoogleAnalyticsProps.tsx | 13 ------- apps/www/package.json | 1 + apps/www/pages/_app.tsx | 17 ++++++++- package-lock.json | 35 ++++++++++++++++++- packages/common/hooks/index.ts | 1 + .../common/hooks/useGoogleAnalyticsProps.ts | 12 +++++++ 10 files changed, 73 insertions(+), 21 deletions(-) delete mode 100644 apps/www/hooks/useGoogleAnalyticsProps.tsx create mode 100644 packages/common/hooks/useGoogleAnalyticsProps.ts diff --git a/apps/docs/lib/fetchWrappers.tsx b/apps/docs/lib/fetchWrappers.tsx index c573a0d37dc..0313e49565f 100644 --- a/apps/docs/lib/fetchWrappers.tsx +++ b/apps/docs/lib/fetchWrappers.tsx @@ -1,7 +1,5 @@ interface DataProps { - referrer?: string - title: string - route?: string + [prop: string]: any } export const post = (url: string, data: DataProps, options = {}) => { diff --git a/apps/docs/pages/_app.tsx b/apps/docs/pages/_app.tsx index f66ae722cd3..e80d0dd598d 100644 --- a/apps/docs/pages/_app.tsx +++ b/apps/docs/pages/_app.tsx @@ -1,6 +1,6 @@ import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' import { SessionContextProvider } from '@supabase/auth-helpers-react' -import { AuthProvider, ThemeProvider } from 'common' +import { AuthProvider, ThemeProvider, useGoogleAnalyticsProps } from 'common' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import ReactMarkdown from 'react-markdown' @@ -19,6 +19,7 @@ import '../styles/prism-okaidia.scss' function MyApp({ Component, pageProps }: AppPropsWithLayout) { const router = useRouter() + const googleAnalyticsProps = useGoogleAnalyticsProps() const [supabase] = useState(() => IS_PLATFORM || LOCAL_SUPABASE ? createBrowserSupabaseClient() : undefined @@ -29,6 +30,10 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { referrer: document.referrer, title: document.title, route, + ga: { + screen_resolution: googleAnalyticsProps?.screenResolution, + language: googleAnalyticsProps?.language, + }, }) } diff --git a/apps/www/components/Features/index.tsx b/apps/www/components/Features/index.tsx index bb84b4dbf4c..2b14cc6fbfe 100644 --- a/apps/www/components/Features/index.tsx +++ b/apps/www/components/Features/index.tsx @@ -1,11 +1,11 @@ import { Badge } from 'ui' import Solutions from 'data/Solutions.json' -import useGoogleAnalyticsProps from '~/hooks/useGoogleAnalyticsProps' import Telemetry from '~/lib/telemetry' import gaEvents from '~/lib/gaEvents' import SectionContainer from '../Layouts/SectionContainer' import ProductIcon from '../ProductIcon' import TextLink from '../TextLink' +import { useGoogleAnalyticsProps } from 'common/hooks/useGoogleAnalyticsProps' const Features = () => { const googleAnalyticsProps = useGoogleAnalyticsProps() diff --git a/apps/www/components/Hero.tsx b/apps/www/components/Hero.tsx index 28cbc079bca..e8d336787af 100644 --- a/apps/www/components/Hero.tsx +++ b/apps/www/components/Hero.tsx @@ -4,7 +4,7 @@ import Link from 'next/link' import SectionContainer from './Layouts/SectionContainer' import Telemetry, { GoogleAnalyticsEvent } from '~/lib/telemetry' import gaEvents from '~/lib/gaEvents' -import useGoogleAnalyticsProps from '~/hooks/useGoogleAnalyticsProps' +import { useGoogleAnalyticsProps } from 'common/hooks/useGoogleAnalyticsProps' const Hero = () => { const { basePath } = useRouter() diff --git a/apps/www/hooks/useGoogleAnalyticsProps.tsx b/apps/www/hooks/useGoogleAnalyticsProps.tsx deleted file mode 100644 index a3254a8b307..00000000000 --- a/apps/www/hooks/useGoogleAnalyticsProps.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useRouter } from 'next/router' - -const useGoogleAnalyticsProps = () => { - const { locale } = useRouter() - - return { - screenResolution: - typeof window !== 'undefined' ? `${window.innerWidth}x${window.innerHeight}` : undefined, - language: locale ?? 'en-US', - } -} - -export default useGoogleAnalyticsProps diff --git a/apps/www/package.json b/apps/www/package.json index cadcf99b872..8242300daea 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -20,6 +20,7 @@ "@next/mdx": "^12.3.2", "@supabase/auth-ui-react": "^0.1.6", "@supabase/supabase-js": "^2.13.0", + "browser-session-tabs": "^3.0.0", "classnames": "^2.3.1", "cobe": "^0.6.2", "common": "*", diff --git a/apps/www/pages/_app.tsx b/apps/www/pages/_app.tsx index dce4295802c..f2e7ad106a0 100644 --- a/apps/www/pages/_app.tsx +++ b/apps/www/pages/_app.tsx @@ -6,20 +6,35 @@ import { useEffect } from 'react' import Meta from '~/components/Favicons' import '../styles/index.css' import { post } from '~/lib/fetchWrapper' -import { AuthProvider, ThemeProvider } from 'common' +import { AuthProvider, ThemeProvider, useGoogleAnalyticsProps } from 'common' +import { BrowserTabTracker } from 'browser-session-tabs' +import { v4 as uuidv4 } from 'uuid' import Head from 'next/head' export default function MyApp({ Component, pageProps }: AppProps) { const router = useRouter() + const googleAnalyticsProps = useGoogleAnalyticsProps() function handlePageTelemetry(route: string) { return post(`${API_URL}/telemetry/page`, { referrer: document.referrer, title: document.title, route, + ga: { + screen_resolution: googleAnalyticsProps?.screenResolution, + language: googleAnalyticsProps?.language, + }, }) } + useEffect(() => { + // Generate browser session id for anon tracking + BrowserTabTracker.initialize({ + storageKey: 'supabase.browser.session', + sessionIdGenerator: () => uuidv4(), + }) + }, []) + useEffect(() => { function handleRouteChange(url: string) { handlePageTelemetry(url) diff --git a/package-lock.json b/package-lock.json index 0881d78353b..efb8b271642 100644 --- a/package-lock.json +++ b/package-lock.json @@ -673,6 +673,7 @@ "@next/mdx": "^12.3.2", "@supabase/auth-ui-react": "^0.1.6", "@supabase/supabase-js": "^2.13.0", + "browser-session-tabs": "^3.0.0", "classnames": "^2.3.1", "cobe": "^0.6.2", "common": "*", @@ -12807,6 +12808,22 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", "dev": true }, + "node_modules/browser-session-tabs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/browser-session-tabs/-/browser-session-tabs-3.0.0.tgz", + "integrity": "sha512-HBJWkCbcDYFL+hlSGld54SF1SgaiSXnw9vaTPdDeOxT9HCTFt3hVdArIroQ3HDhh9vhqkbl6TkMjTFok0ZfwIA==", + "dependencies": { + "js-cookie": "^3.0.1" + } + }, + "node_modules/browser-session-tabs/node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + }, "node_modules/browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -48910,6 +48927,21 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", "dev": true }, + "browser-session-tabs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/browser-session-tabs/-/browser-session-tabs-3.0.0.tgz", + "integrity": "sha512-HBJWkCbcDYFL+hlSGld54SF1SgaiSXnw9vaTPdDeOxT9HCTFt3hVdArIroQ3HDhh9vhqkbl6TkMjTFok0ZfwIA==", + "requires": { + "js-cookie": "^3.0.1" + }, + "dependencies": { + "js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==" + } + } + }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -68107,7 +68139,7 @@ "@types/react": "^17.0.37", "@types/react-copy-to-clipboard": "^5.0.4", "@types/react-dom": "^17.0.11", - "@types/react-syntax-highlighter": "*", + "@types/react-syntax-highlighter": "^15.5.6", "autoprefixer": "^10.4.2", "clsx": "^1.2.1", "cmdk-supabase": "^0.2.2", @@ -69873,6 +69905,7 @@ "@types/react-dom": "^17.0.11", "@types/react-syntax-highlighter": "^13.5.2", "autoprefixer": "^10.4.12", + "browser-session-tabs": "^3.0.0", "classnames": "^2.3.1", "cobe": "^0.6.2", "common": "*", diff --git a/packages/common/hooks/index.ts b/packages/common/hooks/index.ts index 7560f7ebcf5..1d07190bbf5 100644 --- a/packages/common/hooks/index.ts +++ b/packages/common/hooks/index.ts @@ -1,2 +1,3 @@ export * from './useBreakpoint' export * from './useParams' +export * from './useGoogleAnalyticsProps' diff --git a/packages/common/hooks/useGoogleAnalyticsProps.ts b/packages/common/hooks/useGoogleAnalyticsProps.ts new file mode 100644 index 00000000000..3b247008f9f --- /dev/null +++ b/packages/common/hooks/useGoogleAnalyticsProps.ts @@ -0,0 +1,12 @@ +import { useRouter } from 'next/router' + +const isBrowser = typeof window !== 'undefined' + +export function useGoogleAnalyticsProps() { + const { locale } = useRouter() + + return { + screenResolution: isBrowser ? `${window.innerWidth}x${window.innerHeight}` : undefined, + language: locale ?? 'en-US', + } +} From f26d4a823282122b7bf20935a1604b6fc91d54e6 Mon Sep 17 00:00:00 2001 From: phamhieu Date: Mon, 8 May 2023 09:33:57 +0700 Subject: [PATCH 20/39] fix: use shared useGoogleAnalyticsProps --- .../SQLEditor/SQLTemplates/SQLTemplates.tsx | 7 ++++--- .../SQLEditor/UtilityPanel/ResultsDropdown.tsx | 6 ++++-- studio/components/ui/PageTelemetry.tsx | 8 ++++---- studio/lib/auth.tsx | 6 ++++-- studio/stores/UiStore.ts | 17 +---------------- 5 files changed, 17 insertions(+), 27 deletions(-) diff --git a/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx b/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx index 43548b7ec7d..d1ea1631330 100644 --- a/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx +++ b/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx @@ -8,7 +8,7 @@ import { useProfileQuery } from 'data/profile/profile-query' import { SQL_TEMPLATES } from 'components/interfaces/SQLEditor/SQLEditor.constants' import SQLCard from './SQLCard' import { createSqlSnippetSkeleton } from '../SQLEditor.utils' -import { useParams } from 'common' +import { useGoogleAnalyticsProps, useParams } from 'common' import { useRouter } from 'next/router' import { SqlSnippet } from 'data/content/sql-snippets-query' import { useSqlEditorStateSnapshot } from 'state/sql-editor' @@ -21,6 +21,7 @@ const SQLTemplates = observer(() => { const { data: profile } = useProfileQuery() const [sql, quickStart] = partition(SQL_TEMPLATES, { type: 'template' }) + const googleAnalyticsProps = useGoogleAnalyticsProps() const snap = useSqlEditorStateSnapshot() const canCreateSQLSnippet = checkPermissions(PermissionAction.CREATE, 'user_content', { resource: { type: 'sql', owner_id: profile?.id }, @@ -77,7 +78,7 @@ const SQLTemplates = observer(() => { action: 'script_clicked', label: x.title, }, - ui.googleAnalyticsProps + googleAnalyticsProps ) }} /> @@ -111,7 +112,7 @@ const SQLTemplates = observer(() => { action: 'quickstart_clicked', label: x.title, }, - ui.googleAnalyticsProps + googleAnalyticsProps ) }} /> diff --git a/studio/components/interfaces/SQLEditor/UtilityPanel/ResultsDropdown.tsx b/studio/components/interfaces/SQLEditor/UtilityPanel/ResultsDropdown.tsx index 2ba8e6a3324..0f9ac2fe627 100644 --- a/studio/components/interfaces/SQLEditor/UtilityPanel/ResultsDropdown.tsx +++ b/studio/components/interfaces/SQLEditor/UtilityPanel/ResultsDropdown.tsx @@ -9,6 +9,7 @@ import { CSVLink } from 'react-csv' import { useSqlEditorStateSnapshot } from 'state/sql-editor' // @ts-ignore import MarkdownTable from 'markdown-table' +import { useGoogleAnalyticsProps } from 'common' export type ResultsDropdownProps = { id: string @@ -17,6 +18,7 @@ export type ResultsDropdownProps = { const ResultsDropdown = ({ id }: ResultsDropdownProps) => { const { project } = useProjectContext() const snap = useSqlEditorStateSnapshot() + const googleAnalyticsProps = useGoogleAnalyticsProps() const result = snap.results?.[id]?.[0] ?? undefined const { ui } = useStore() const csvRef = useRef(null) @@ -30,7 +32,7 @@ const ResultsDropdown = ({ id }: ResultsDropdownProps) => { csvRef.current?.link.click() Telemetry.sendEvent( { category: 'sql_editor', action: 'sql_download_csv', label: '' }, - ui.googleAnalyticsProps + googleAnalyticsProps ) } @@ -53,7 +55,7 @@ const ResultsDropdown = ({ id }: ResultsDropdownProps) => { ui.setNotification({ category: 'success', message: 'Copied results to clipboard' }) Telemetry.sendEvent( { category: 'sql_editor', action: 'sql_copy_as_markdown', label: '' }, - ui.googleAnalyticsProps + googleAnalyticsProps ) }) } diff --git a/studio/components/ui/PageTelemetry.tsx b/studio/components/ui/PageTelemetry.tsx index 412336e28d0..b6f2591b0b4 100644 --- a/studio/components/ui/PageTelemetry.tsx +++ b/studio/components/ui/PageTelemetry.tsx @@ -1,4 +1,4 @@ -import { useStore } from 'hooks' +import { useGoogleAnalyticsProps } from 'common' import { post } from 'lib/common/fetch' import { API_URL, IS_PLATFORM } from 'lib/constants' import { observer } from 'mobx-react-lite' @@ -25,7 +25,7 @@ function sanitizePageViewRoute(_route?: string) { const PageTelemetry: FC = ({ children }) => { const router = useRouter() - const { ui } = useStore() + const googleAnalyticsProps = useGoogleAnalyticsProps() useEffect(() => { function handleRouteChange(url: string) { @@ -71,8 +71,8 @@ const PageTelemetry: FC = ({ children }) => { title: document.title, route, ga: { - screen_resolution: ui.googleAnalyticsProps?.screenResolution, - language: ui.googleAnalyticsProps?.language, + screen_resolution: googleAnalyticsProps?.screenResolution, + language: googleAnalyticsProps?.language, }, }) } diff --git a/studio/lib/auth.tsx b/studio/lib/auth.tsx index c7b962a2010..f02f3688130 100644 --- a/studio/lib/auth.tsx +++ b/studio/lib/auth.tsx @@ -1,18 +1,20 @@ import { AuthContext as AuthContextInternal, AuthProvider as AuthProviderInternal, - AuthProviderProps, gotrueClient, + useGoogleAnalyticsProps, } from 'common' import { useProfileQuery } from 'data/profile/profile-query' import { useStore } from 'hooks' import { PropsWithChildren, useEffect } from 'react' import { GOTRUE_ERRORS, IS_PLATFORM } from './constants' +import Telemetry from 'lib/telemetry' export const AuthContext = AuthContextInternal export const AuthProvider = ({ children }: PropsWithChildren<{}>) => { const { ui, app } = useStore() + const googleAnalyticsProps = useGoogleAnalyticsProps() // Check for unverified GitHub users after a GitHub sign in useEffect(() => { @@ -34,7 +36,7 @@ export const AuthProvider = ({ children }: PropsWithChildren<{}>) => { // Track telemetry for the current user useProfileQuery({ onSuccess(profile) { - ui.setProfile(profile) + Telemetry.sendIdentify(profile, googleAnalyticsProps) // [Joshen] Temp fix: For new users, the GET profile call also creates a default org // But because the dashboard's logged in state is using gotrue as the source of truth diff --git a/studio/stores/UiStore.ts b/studio/stores/UiStore.ts index 1b91898ae31..0e7b35339f9 100644 --- a/studio/stores/UiStore.ts +++ b/studio/stores/UiStore.ts @@ -1,8 +1,7 @@ import { uuidv4 } from 'lib/helpers' import { action, makeAutoObservable } from 'mobx' -import { Project, Notification, User, Organization, ProjectBase, Permission } from 'types' +import { Project, Notification, Organization, ProjectBase, Permission } from 'types' import { IRootStore } from './RootStore' -import Telemetry, { GoogleAnalyticsProps } from 'lib/telemetry' export interface IUiStore { language: 'en-US' @@ -12,12 +11,10 @@ export interface IUiStore { selectedOrganization?: Organization notification?: Notification permissions?: Permission[] - googleAnalyticsProps?: GoogleAnalyticsProps load: () => void setProjectRef: (ref?: string) => void setOrganizationSlug: (slug?: string) => void setNotification: (notification: Notification) => string - setProfile: (value: User) => void setPermissions: (permissions?: Permission[]) => void } export default class UiStore implements IUiStore { @@ -83,14 +80,6 @@ export default class UiStore implements IUiStore { return undefined } - get googleAnalyticsProps() { - return { - screenResolution: - typeof window !== 'undefined' ? `${window.innerWidth}x${window.innerHeight}` : undefined, - language: this.language, - } - } - load() { if (typeof window === 'undefined') return } @@ -109,10 +98,6 @@ export default class UiStore implements IUiStore { return id } - setProfile(value: User) { - Telemetry.sendIdentify(value, this.googleAnalyticsProps) - } - setPermissions(permissions?: any) { this.permissions = permissions } From 5ab4f79a3167ddf08820ef59f3c346009be1cd4d Mon Sep 17 00:00:00 2001 From: phamhieu Date: Mon, 8 May 2023 09:47:00 +0700 Subject: [PATCH 21/39] feat: send browser session on telemetry call --- apps/docs/package.json | 1 + apps/docs/pages/_app.tsx | 11 +++++++++++ apps/www/lib/telemetry.ts | 2 ++ apps/www/pages/_app.tsx | 1 + package-lock.json | 4 ++++ studio/components/ui/PageTelemetry.tsx | 2 ++ studio/lib/telemetry.ts | 3 +++ studio/package.json | 1 + studio/pages/_app.tsx | 10 ++++++++++ 9 files changed, 35 insertions(+) diff --git a/apps/docs/package.json b/apps/docs/package.json index 0eb29301d62..7f8438339e1 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -56,6 +56,7 @@ "@supabase/supabase-js": "^2.13.0", "algoliasearch": "^4.14.2", "babel": "^6.23.0", + "browser-session-tabs": "^3.0.0", "clsx": "^1.2.1", "common": "*", "config": "*", diff --git a/apps/docs/pages/_app.tsx b/apps/docs/pages/_app.tsx index e80d0dd598d..e0ad82b5602 100644 --- a/apps/docs/pages/_app.tsx +++ b/apps/docs/pages/_app.tsx @@ -12,6 +12,8 @@ import Favicons from '~/components/Favicons' import SiteLayout from '~/layouts/SiteLayout' import { API_URL, IS_PLATFORM, LOCAL_SUPABASE } from '~/lib/constants' import { post } from '~/lib/fetchWrappers' +import { BrowserTabTracker } from 'browser-session-tabs' +import { v4 as uuidv4 } from 'uuid' import '../styles/ch.scss' import '../styles/main.scss?v=1.0.0' import '../styles/new-docs.scss' @@ -33,10 +35,19 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { ga: { screen_resolution: googleAnalyticsProps?.screenResolution, language: googleAnalyticsProps?.language, + session_id: BrowserTabTracker.sessionId, }, }) } + useEffect(() => { + // Generate browser session id for anon tracking + BrowserTabTracker.initialize({ + storageKey: 'supabase.browser.session', + sessionIdGenerator: () => uuidv4(), + }) + }, []) + useEffect(() => { function handleRouteChange(url: string) { /* diff --git a/apps/www/lib/telemetry.ts b/apps/www/lib/telemetry.ts index 0c6fb2ed9fe..aa0097d1f17 100644 --- a/apps/www/lib/telemetry.ts +++ b/apps/www/lib/telemetry.ts @@ -1,5 +1,6 @@ import { post } from '~/lib/fetchWrapper' import { API_URL, IS_PROD, IS_PREVIEW } from 'lib/constants' +import { BrowserTabTracker } from 'browser-session-tabs' export interface GoogleAnalyticsEvent { category: string @@ -29,6 +30,7 @@ const sendEvent = (event: GoogleAnalyticsEvent, gaProps: GoogleAnalyticsProps) = ga: { screen_resolution: gaProps?.screenResolution, language: gaProps?.language, + session_id: BrowserTabTracker.sessionId, }, }) } diff --git a/apps/www/pages/_app.tsx b/apps/www/pages/_app.tsx index f2e7ad106a0..ab0dbf807d2 100644 --- a/apps/www/pages/_app.tsx +++ b/apps/www/pages/_app.tsx @@ -23,6 +23,7 @@ export default function MyApp({ Component, pageProps }: AppProps) { ga: { screen_resolution: googleAnalyticsProps?.screenResolution, language: googleAnalyticsProps?.language, + session_id: BrowserTabTracker.sessionId, }, }) } diff --git a/package-lock.json b/package-lock.json index efb8b271642..4b3477ab38d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,7 @@ "@supabase/supabase-js": "^2.13.0", "algoliasearch": "^4.14.2", "babel": "^6.23.0", + "browser-session-tabs": "^3.0.0", "clsx": "^1.2.1", "common": "*", "config": "*", @@ -39206,6 +39207,7 @@ "analytics-node": "^3.5.0", "awesome-debounce-promise": "^2.1.0", "blueimp-md5": "^2.19.0", + "browser-session-tabs": "^3.0.0", "clipboard": "^2.0.8", "clsx": "^1.2.1", "common": "*", @@ -51570,6 +51572,7 @@ "@types/react": "17.0.39", "algoliasearch": "^4.14.2", "babel": "^6.23.0", + "browser-session-tabs": "^3.0.0", "clsx": "^1.2.1", "common": "*", "config": "*", @@ -66453,6 +66456,7 @@ "aws-sdk": "^2.1354.0", "babel-loader": "^8.2.3", "blueimp-md5": "^2.19.0", + "browser-session-tabs": "^3.0.0", "clipboard": "^2.0.8", "clsx": "^1.2.1", "common": "*", diff --git a/studio/components/ui/PageTelemetry.tsx b/studio/components/ui/PageTelemetry.tsx index b6f2591b0b4..54b9155cec1 100644 --- a/studio/components/ui/PageTelemetry.tsx +++ b/studio/components/ui/PageTelemetry.tsx @@ -1,3 +1,4 @@ +import { BrowserTabTracker } from 'browser-session-tabs' import { useGoogleAnalyticsProps } from 'common' import { post } from 'lib/common/fetch' import { API_URL, IS_PLATFORM } from 'lib/constants' @@ -73,6 +74,7 @@ const PageTelemetry: FC = ({ children }) => { ga: { screen_resolution: googleAnalyticsProps?.screenResolution, language: googleAnalyticsProps?.language, + session_id: BrowserTabTracker.sessionId, }, }) } diff --git a/studio/lib/telemetry.ts b/studio/lib/telemetry.ts index ea5dcb38bcb..1fc185b1699 100644 --- a/studio/lib/telemetry.ts +++ b/studio/lib/telemetry.ts @@ -1,6 +1,7 @@ import { post } from 'lib/common/fetch' import { API_URL, IS_PLATFORM } from 'lib/constants' import { User } from 'types' +import { BrowserTabTracker } from 'browser-session-tabs' export interface GoogleAnalyticsProps { screenResolution?: string @@ -28,6 +29,7 @@ const sendEvent = ( ga: { screen_resolution: gaProps?.screenResolution, language: gaProps?.language, + session_id: BrowserTabTracker.sessionId, }, }) } @@ -44,6 +46,7 @@ const sendIdentify = (user: User, gaProps?: GoogleAnalyticsProps) => { ga: { screen_resolution: gaProps?.screenResolution, language: gaProps?.language, + session_id: BrowserTabTracker.sessionId, }, }) } diff --git a/studio/package.json b/studio/package.json index 2f845d6011e..e85e5682e10 100644 --- a/studio/package.json +++ b/studio/package.json @@ -41,6 +41,7 @@ "analytics-node": "^3.5.0", "awesome-debounce-promise": "^2.1.0", "blueimp-md5": "^2.19.0", + "browser-session-tabs": "^3.0.0", "clipboard": "^2.0.8", "clsx": "^1.2.1", "common": "*", diff --git a/studio/pages/_app.tsx b/studio/pages/_app.tsx index 8a9144cf26f..c35c5c382ae 100644 --- a/studio/pages/_app.tsx +++ b/studio/pages/_app.tsx @@ -50,6 +50,8 @@ import Favicons from 'components/head/Favicons' import { IS_PLATFORM } from 'lib/constants' import { SessionContextProvider } from '@supabase/auth-helpers-react' import { createClient } from '@supabase/supabase-js' +import { BrowserTabTracker } from 'browser-session-tabs' +import { v4 as uuidv4 } from 'uuid' dayjs.extend(customParseFormat) dayjs.extend(utc) @@ -74,6 +76,14 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) { const getSavingState = () => rootStore.content.savingState + useEffect(() => { + // Generate browser session id for anon tracking + BrowserTabTracker.initialize({ + storageKey: 'supabase.browser.session', + sessionIdGenerator: () => uuidv4(), + }) + }, []) + // prompt the user if they try and leave with unsaved content store changes useEffect(() => { const warningText = 'You have unsaved changes - are you sure you wish to leave this page?' From daff6551d4962e1e393a6ac37807c59f3e754a7a Mon Sep 17 00:00:00 2001 From: Kang Ming Date: Mon, 8 May 2023 12:11:18 +0800 Subject: [PATCH 22/39] update auth docs --- spec/supabase_js_v2.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/spec/supabase_js_v2.yml b/spec/supabase_js_v2.yml index 780e982f411..1786a900190 100644 --- a/spec/supabase_js_v2.yml +++ b/spec/supabase_js_v2.yml @@ -400,6 +400,9 @@ functions: - id: get-session title: 'getSession()' $ref: '@supabase/gotrue-js.GoTrueClient.getSession' + notes: | + - This method retrieves the current local session (i.e local storage). + - If the session has an expired access token, this method will use the refresh token to get a new session. examples: - id: get-the-session-data name: Get the session data @@ -412,8 +415,8 @@ functions: title: 'getUser()' $ref: '@supabase/gotrue-js.GoTrueClient.getUser' notes: | - - This method gets the user object from the current session. - - Fetches the user object from the database instead of local session. + - This method fetches the user object from the database instead of local session. + - This method is useful for checking if the user is authorized because it validates the user's access token JWT on the server. - Should be used only when you require the most current user data. For faster results, `getSession().session.user` is recommended. examples: - id: get-the-logged-in-user-with-the-current-existing-session @@ -486,7 +489,7 @@ functions: title: 'refreshSession()' $ref: '@supabase/gotrue-js.GoTrueClient.refreshSession' notes: | - - This method will refresh the session whether the current one is expired or not. + - This method will refresh and return a new session whether the current one is expired or not. examples: - id: refresh-session-using-the-current-session name: Refresh session using the current session From 00e76cd00da28b441d8ee60e79c7d7e3d05e1790 Mon Sep 17 00:00:00 2001 From: phamhieu Date: Mon, 8 May 2023 15:29:47 +0700 Subject: [PATCH 23/39] feat: send page params on telemetry event --- apps/www/components/Features/index.tsx | 17 ++++++++++++----- apps/www/components/Hero.tsx | 5 +++-- apps/www/lib/telemetry.ts | 10 +++++++++- .../SQLEditor/SQLTemplates/SQLTemplates.tsx | 6 ++++-- .../SQLEditor/UtilityPanel/ResultsDropdown.tsx | 8 ++++++-- studio/lib/telemetry.ts | 7 ++++++- 6 files changed, 40 insertions(+), 13 deletions(-) diff --git a/apps/www/components/Features/index.tsx b/apps/www/components/Features/index.tsx index 2b14cc6fbfe..4b291e78767 100644 --- a/apps/www/components/Features/index.tsx +++ b/apps/www/components/Features/index.tsx @@ -6,8 +6,10 @@ import SectionContainer from '../Layouts/SectionContainer' import ProductIcon from '../ProductIcon' import TextLink from '../TextLink' import { useGoogleAnalyticsProps } from 'common/hooks/useGoogleAnalyticsProps' +import { useRouter } from 'next/router' const Features = () => { + const router = useRouter() const googleAnalyticsProps = useGoogleAnalyticsProps() const sendTelemetryEvent = async (product: any) => { @@ -15,27 +17,32 @@ const Features = () => { case 'Database': return await Telemetry.sendEvent( gaEvents['www_hp_subhero_products_database'], - googleAnalyticsProps + googleAnalyticsProps, + router ) case 'Authentication': return await Telemetry.sendEvent( gaEvents['www_hp_subhero_products_auth'], - googleAnalyticsProps + googleAnalyticsProps, + router ) case 'Storage': return await Telemetry.sendEvent( gaEvents['www_hp_subhero_products_storage'], - googleAnalyticsProps + googleAnalyticsProps, + router ) case 'Edge Functions': return await Telemetry.sendEvent( gaEvents['www_hp_subhero_products_edgeFunctions'], - googleAnalyticsProps + googleAnalyticsProps, + router ) case 'Realtime': return await Telemetry.sendEvent( gaEvents['www_hp_subhero_products_realtime'], - googleAnalyticsProps + googleAnalyticsProps, + router ) } } diff --git a/apps/www/components/Hero.tsx b/apps/www/components/Hero.tsx index e8d336787af..33a1e3deec6 100644 --- a/apps/www/components/Hero.tsx +++ b/apps/www/components/Hero.tsx @@ -7,11 +7,12 @@ import gaEvents from '~/lib/gaEvents' import { useGoogleAnalyticsProps } from 'common/hooks/useGoogleAnalyticsProps' const Hero = () => { - const { basePath } = useRouter() + const router = useRouter() + const { basePath } = router const googleAnalyticsProps = useGoogleAnalyticsProps() const sendTelemetryEvent = async (event: GoogleAnalyticsEvent) => { - await Telemetry.sendEvent(event, googleAnalyticsProps) + await Telemetry.sendEvent(event, googleAnalyticsProps, router) } return ( diff --git a/apps/www/lib/telemetry.ts b/apps/www/lib/telemetry.ts index aa0097d1f17..e64f0992b68 100644 --- a/apps/www/lib/telemetry.ts +++ b/apps/www/lib/telemetry.ts @@ -1,6 +1,7 @@ import { post } from '~/lib/fetchWrapper' import { API_URL, IS_PROD, IS_PREVIEW } from 'lib/constants' import { BrowserTabTracker } from 'browser-session-tabs' +import { NextRouter } from 'next/router' export interface GoogleAnalyticsEvent { category: string @@ -17,7 +18,11 @@ export interface GoogleAnalyticsProps { // This event is the same as in studio/lib/telemetry.tx // but uses different ENV variables for www -const sendEvent = (event: GoogleAnalyticsEvent, gaProps: GoogleAnalyticsProps) => { +const sendEvent = ( + event: GoogleAnalyticsEvent, + gaProps: GoogleAnalyticsProps, + router: NextRouter +) => { if (!IS_PROD && !IS_PREVIEW) return const { category, action, label, value } = event @@ -27,6 +32,9 @@ const sendEvent = (event: GoogleAnalyticsEvent, gaProps: GoogleAnalyticsProps) = category: category, label: label, value: value, + page_referrer: document?.referrer, + page_title: document?.title, + page_location: router.asPath, ga: { screen_resolution: gaProps?.screenResolution, language: gaProps?.language, diff --git a/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx b/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx index d1ea1631330..f400dbd6a13 100644 --- a/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx +++ b/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx @@ -78,7 +78,8 @@ const SQLTemplates = observer(() => { action: 'script_clicked', label: x.title, }, - googleAnalyticsProps + googleAnalyticsProps, + router ) }} /> @@ -112,7 +113,8 @@ const SQLTemplates = observer(() => { action: 'quickstart_clicked', label: x.title, }, - googleAnalyticsProps + googleAnalyticsProps, + router ) }} /> diff --git a/studio/components/interfaces/SQLEditor/UtilityPanel/ResultsDropdown.tsx b/studio/components/interfaces/SQLEditor/UtilityPanel/ResultsDropdown.tsx index 0f9ac2fe627..4d47dcedb13 100644 --- a/studio/components/interfaces/SQLEditor/UtilityPanel/ResultsDropdown.tsx +++ b/studio/components/interfaces/SQLEditor/UtilityPanel/ResultsDropdown.tsx @@ -10,6 +10,7 @@ import { useSqlEditorStateSnapshot } from 'state/sql-editor' // @ts-ignore import MarkdownTable from 'markdown-table' import { useGoogleAnalyticsProps } from 'common' +import { useRouter } from 'next/router' export type ResultsDropdownProps = { id: string @@ -22,6 +23,7 @@ const ResultsDropdown = ({ id }: ResultsDropdownProps) => { const result = snap.results?.[id]?.[0] ?? undefined const { ui } = useStore() const csvRef = useRef(null) + const router = useRouter() const csvData = useMemo( () => (result?.rows ? compact(Array.from(result.rows || [])) : ''), @@ -32,7 +34,8 @@ const ResultsDropdown = ({ id }: ResultsDropdownProps) => { csvRef.current?.link.click() Telemetry.sendEvent( { category: 'sql_editor', action: 'sql_download_csv', label: '' }, - googleAnalyticsProps + googleAnalyticsProps, + router ) } @@ -55,7 +58,8 @@ const ResultsDropdown = ({ id }: ResultsDropdownProps) => { ui.setNotification({ category: 'success', message: 'Copied results to clipboard' }) Telemetry.sendEvent( { category: 'sql_editor', action: 'sql_copy_as_markdown', label: '' }, - googleAnalyticsProps + googleAnalyticsProps, + router ) }) } diff --git a/studio/lib/telemetry.ts b/studio/lib/telemetry.ts index 1fc185b1699..f3a75970c6a 100644 --- a/studio/lib/telemetry.ts +++ b/studio/lib/telemetry.ts @@ -2,6 +2,7 @@ import { post } from 'lib/common/fetch' import { API_URL, IS_PLATFORM } from 'lib/constants' import { User } from 'types' import { BrowserTabTracker } from 'browser-session-tabs' +import { NextRouter } from 'next/router' export interface GoogleAnalyticsProps { screenResolution?: string @@ -15,7 +16,8 @@ const sendEvent = ( label: string value?: string }, - gaProps?: GoogleAnalyticsProps + gaProps: GoogleAnalyticsProps, + router: NextRouter ) => { if (!IS_PLATFORM) return @@ -26,6 +28,9 @@ const sendEvent = ( category: category, label: label, value: value, + page_referrer: document?.referrer, + page_title: document?.title, + page_location: router.asPath, ga: { screen_resolution: gaProps?.screenResolution, language: gaProps?.language, From 944de361cce482c2de3f6e3a5b480c4956940708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Mon, 8 May 2023 11:17:20 +0100 Subject: [PATCH 24/39] Last touches to case studies. Removed links from all the brand mentions. --- apps/www/_customers/mendableai.mdx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/www/_customers/mendableai.mdx b/apps/www/_customers/mendableai.mdx index 54f35d8b054..b1e5f6369e3 100644 --- a/apps/www/_customers/mendableai.mdx +++ b/apps/www/_customers/mendableai.mdx @@ -2,7 +2,7 @@ title: Mendable.ai switches from Pinecone to Supabase for PostgreSQL vector embeddings. name: Mendable.ai description: How Mendable.ai boosts efficiency and accuracy of chat powered search for documentation using Supabase with pg_vector. -author: rory_wilding +author: copple author_title: Supabase author_url: https://github.com/kiwicopple author_image_url: https://avatars2.githubusercontent.com/u/10214025?s=400&u=c6775be2ae667e2acae3ccd347fed62bb3f5b3e7&v=4 @@ -24,14 +24,14 @@ misc: [{ label: 'Backed by', text: 'Y Combinator' }] about: Mendable.ai is Chat Powered Search for Documentation. --- -[Mendable.ai](http://mendable.ai/) provides a chat-powered search engine for technical documentation. Their AI-powered search tool makes it easier for developers and other technical users to find relevant information in complex documentation. Users can simply ask questions in natural language, and the tool returns the most relevant answers. [Mendable.ai](http://mendable.ai/)'s search engine also provides detailed analytics, which helps teams identify knowledge gaps and areas for improvement in their documentation. +[Mendable.ai](http://mendable.ai/) provides a chat-powered search engine for technical documentation. Their AI-powered search tool makes it easier for developers and other technical users to find relevant information in complex documentation. Users can simply ask questions in natural language, and the tool returns the most relevant answers. Mendable.ai's search engine also provides detailed analytics, which helps teams identify knowledge gaps and areas for improvement in their documentation. ## The Challenge -[Mendable.ai](http://mendable.ai/) needed to store and search through large amounts of vector data to improve the efficiency and accuracy of their similarity search operations for their Chat Powered Search for Documentation. They tried Faiss, Weviate, and Pinecone., but found them to be expensive and not very intuitive, especially when it came to storing metadata along with the vectors. +Mendable.ai needed to store and search through large amounts of vector data to improve the efficiency and accuracy of their similarity search operations for their Chat Powered Search for Documentation. They tried Faiss, Weviate, and Pinecone., but found them to be expensive and not very intuitive, especially when it came to storing metadata along with the vectors. Why they chose Supabase: -[Mendable.ai](http://mendable.ai/) discovered that Supabase supports pg_vector and found it to be a simple and cost-effective solution. They were impressed with the open-source nature of Supabase, as well as its ability to store metadata alongside the vectors. They also appreciated the intuitive interface and ease of use. +Mendable.ai discovered that Supabase supports pg_vector and found it to be a simple and cost-effective solution. They were impressed with the open-source nature of Supabase, as well as its ability to store metadata alongside the vectors. They also appreciated the intuitive interface and ease of use. We tried other vector databases - we tried Faiss, we tried Weviate, we tried Pinecone. We found them to be incredibly expensive and not very intuitive. If you’re just doing vector search they’re great, but if you need to store a bunch of metadata that becomes a huge pain. @@ -39,15 +39,15 @@ Why they chose Supabase: ## What They Built -Using Supabase's pg_vector, [Mendable.ai](http://mendable.ai/) was able to build a more efficient and accurate search function for their AI chatbot. By storing vector data alongside metadata in Supabase, [Mendable.ai](http://mendable.ai/) was able to quickly and easily search through their customers documentation to find the most relevant responses to queries. They found that Supabase's solution was just as performant as dedicated vector databases, but without the high cost. +Using Supabase's pg_vector, Mendable.ai was able to build a more efficient and accurate search function for their AI chatbot. By storing vector data alongside metadata in Supabase, Mendable.ai was able to quickly and easily search through their customers documentation to find the most relevant responses to queries. They found that Supabase's solution was just as performant as dedicated vector databases, but without the high cost. ## The Results -Thanks to Supabase's pg_vector, [Mendable.ai](http://mendable.ai/) was able to significantly improve the efficiency and accuracy of their Chat Powered Search for Documentation. They were able to build faster and more cost-effectively using Supabase’s open source stack. +Thanks to Supabase's pg_vector, Mendable.ai was able to significantly improve the efficiency and accuracy of their Chat Powered Search for Documentation. They were able to build faster and more cost-effectively using Supabase’s open source stack. ## Tech stack -[Mendable.ai](http://mendable.ai/)'s tech stack includes React, Next.js, Express, Vercel, and Supabase. +Mendable.ai's tech stack includes React, Next.js, Express, Vercel, and Supabase. We looked at the alternatives and chose Supabase because it’s open source, it’s simpler, and, for all the ways we need use it, Supabase has been just as performant - if not more performant - than the other vector databases. From 073e7020e98a2803eba458d2e48eb9eb0932281b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Mon, 8 May 2023 11:50:57 +0100 Subject: [PATCH 25/39] Edits to add suggestions by Customer. --- apps/www/_customers/mendableai.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/_customers/mendableai.mdx b/apps/www/_customers/mendableai.mdx index b1e5f6369e3..db3cc007355 100644 --- a/apps/www/_customers/mendableai.mdx +++ b/apps/www/_customers/mendableai.mdx @@ -28,7 +28,7 @@ about: Mendable.ai is Chat Powered Search for Documentation. ## The Challenge -Mendable.ai needed to store and search through large amounts of vector data to improve the efficiency and accuracy of their similarity search operations for their Chat Powered Search for Documentation. They tried Faiss, Weviate, and Pinecone., but found them to be expensive and not very intuitive, especially when it came to storing metadata along with the vectors. +Mendable.ai was experienveing tremendous success, with WAUs growing nearly 300% since March, and started looking for a tool to store and search through large amounts of vector data to improve the efficiency and accuracy of their similarity search operations for their Chat Powered Search for Documentation. They tried Faiss, Weviate, and Pinecone, but found them to be expensive and not very intuitive, especially when it came to storing metadata along with the vectors. Why they chose Supabase: Mendable.ai discovered that Supabase supports pg_vector and found it to be a simple and cost-effective solution. They were impressed with the open-source nature of Supabase, as well as its ability to store metadata alongside the vectors. They also appreciated the intuitive interface and ease of use. From 604b0fbffc0d77e2ab17b142f3f3ea56d3f99187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Mon, 8 May 2023 11:51:34 +0100 Subject: [PATCH 26/39] Add correct author --- apps/www/_customers/mendableai.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/_customers/mendableai.mdx b/apps/www/_customers/mendableai.mdx index db3cc007355..50551eb1431 100644 --- a/apps/www/_customers/mendableai.mdx +++ b/apps/www/_customers/mendableai.mdx @@ -2,7 +2,7 @@ title: Mendable.ai switches from Pinecone to Supabase for PostgreSQL vector embeddings. name: Mendable.ai description: How Mendable.ai boosts efficiency and accuracy of chat powered search for documentation using Supabase with pg_vector. -author: copple +author: paul_copplestone author_title: Supabase author_url: https://github.com/kiwicopple author_image_url: https://avatars2.githubusercontent.com/u/10214025?s=400&u=c6775be2ae667e2acae3ccd347fed62bb3f5b3e7&v=4 From d3ac673cfb20c32a8f679a3e220268e4f531cd4d Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 8 May 2023 19:49:16 +0800 Subject: [PATCH 27/39] Swap logic for isLoading and auth id undefined --- studio/pages/authorize.tsx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/studio/pages/authorize.tsx b/studio/pages/authorize.tsx index cc40fb28461..86559bebe6e 100644 --- a/studio/pages/authorize.tsx +++ b/studio/pages/authorize.tsx @@ -85,18 +85,6 @@ const APIAuthorizationPage: NextPageWithLayout = () => { } } - if (auth_id === undefined) { - return ( - Authorization for API access

}> -
- - Please provide a valid authorization ID in the URL - -
-
- ) - } - if (isLoading) { return ( Authorize API access

}> @@ -109,6 +97,18 @@ const APIAuthorizationPage: NextPageWithLayout = () => { ) } + if (auth_id === undefined) { + return ( + Authorization for API access

}> +
+ + Please provide a valid authorization ID in the URL + +
+
+ ) + } + if (isError) { return ( Authorize API access

}> From 581746e25f56783627fc37b9d665765066f7df99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Mon, 8 May 2023 14:34:07 +0200 Subject: [PATCH 28/39] fix(billing): tax id prefixes --- .../BillingSettings/TaxID/TaxID.constants.ts | 33 ++++++++++++++++++- .../BillingSettings/TaxID/TaxID.utils.ts | 9 +++-- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/studio/components/interfaces/Organization/BillingSettings/TaxID/TaxID.constants.ts b/studio/components/interfaces/Organization/BillingSettings/TaxID/TaxID.constants.ts index e20cebbba26..b52e11f899b 100644 --- a/studio/components/interfaces/Organization/BillingSettings/TaxID/TaxID.constants.ts +++ b/studio/components/interfaces/Organization/BillingSettings/TaxID/TaxID.constants.ts @@ -8,6 +8,7 @@ export interface TaxId { code: string country: string placeholder: string + vatPrefix?: string } export interface StripeTaxId { @@ -30,6 +31,7 @@ export const TAX_IDS: TaxId[] = [ code: 'eu_vat', country: 'Austria', placeholder: 'ATU12345678', + vatPrefix: 'ATU', }, { name: 'AU ABN', @@ -48,12 +50,14 @@ export const TAX_IDS: TaxId[] = [ code: 'eu_vat', country: 'Belgium', placeholder: 'BE0123456789', + vatPrefix: 'BE', }, { name: 'BG VAT', code: 'eu_vat', country: 'Bulgaria', placeholder: 'BG0123456789', + vatPrefix: 'BG', }, { name: 'BR CNPJ', @@ -108,6 +112,7 @@ export const TAX_IDS: TaxId[] = [ code: 'ch_vat', country: 'Switzerland', placeholder: 'CHE-123.456.789 MWST', + vatPrefix: 'CHE', }, { name: 'CL TIN', @@ -120,30 +125,35 @@ export const TAX_IDS: TaxId[] = [ code: 'eu_vat', country: 'Cyprus', placeholder: 'CY12345678Z', + vatPrefix: 'CY', }, { name: 'CZ VAT', code: 'eu_vat', country: 'Czech Republic', placeholder: 'CZ1234567890', + vatPrefix: 'CZ', }, { name: 'DE VAT', code: 'eu_vat', country: 'Germany', placeholder: 'DE123456789', + vatPrefix: 'DE', }, { name: 'DK VAT', code: 'eu_vat', country: 'Denmark', placeholder: 'DK12345678', + vatPrefix: 'DK', }, { name: 'EE VAT', code: 'eu_vat', country: 'Estonia', placeholder: 'EE123456789', + vatPrefix: 'EE', }, { name: 'ES CIF', @@ -155,25 +165,29 @@ export const TAX_IDS: TaxId[] = [ name: 'ES VAT', code: 'eu_vat', country: 'Spain', - placeholder: 'ESA1234567Z', + placeholder: 'ESX1234567Z', + vatPrefix: 'ES', }, { name: 'FI VAT', code: 'eu_vat', country: 'Finland', placeholder: 'FI12345678', + vatPrefix: 'FI', }, { name: 'FR VAT', code: 'eu_vat', country: 'France', placeholder: 'FRAB123456789', + vatPrefix: 'FR', }, { name: 'GB VAT', code: 'eu_vat', country: 'United Kingdom', placeholder: 'GB123456789', + vatPrefix: 'GB', }, { name: 'GE VAT', @@ -186,6 +200,7 @@ export const TAX_IDS: TaxId[] = [ code: 'eu_vat', country: 'Greece', placeholder: 'EL123456789', + vatPrefix: 'EL', }, { name: 'HK BR', @@ -198,12 +213,14 @@ export const TAX_IDS: TaxId[] = [ code: 'eu_vat', country: 'Croatia', placeholder: 'HR12345678912', + vatPrefix: 'HR', }, { name: 'HU VAT', code: 'eu_vat', country: 'Hungary', placeholder: 'HU12345678912', + vatPrefix: 'HU', }, { name: 'ID NPWP', @@ -216,6 +233,7 @@ export const TAX_IDS: TaxId[] = [ code: 'eu_vat', country: 'Ireland', placeholder: 'IE1234567AB', + vatPrefix: 'IE', }, { name: 'IL VAT', @@ -240,6 +258,7 @@ export const TAX_IDS: TaxId[] = [ code: 'eu_vat', country: 'Italy', placeholder: 'IT12345678912', + vatPrefix: 'IT', }, { name: 'JP CN', @@ -270,24 +289,28 @@ export const TAX_IDS: TaxId[] = [ code: 'eu_vat', country: 'Lithuania', placeholder: 'LT123456789123', + vatPrefix: 'LT', }, { name: 'LU VAT', code: 'eu_vat', country: 'Luxembourg', placeholder: 'LU12345678', + vatPrefix: 'LU', }, { name: 'LV VAT', code: 'eu_vat', country: 'Latvia', placeholder: 'LV12345678912', + vatPrefix: 'LV', }, { name: 'MT VAT', code: 'eu_vat', country: 'Malta', placeholder: 'MT12345678', + vatPrefix: 'MT', }, { name: 'MX RFC', @@ -318,6 +341,7 @@ export const TAX_IDS: TaxId[] = [ code: 'eu_vat', country: 'Netherlands', placeholder: 'NL123456789B12', + vatPrefix: 'NL', }, { name: 'NO VAT', @@ -336,18 +360,21 @@ export const TAX_IDS: TaxId[] = [ code: 'eu_vat', country: 'Poland', placeholder: 'PL1234567890', + vatPrefix: 'PL', }, { name: 'PT VAT', code: 'eu_vat', country: 'Portugal', placeholder: 'PT123456789', + vatPrefix: 'PT', }, { name: 'RO VAT', code: 'eu_vat', country: 'Romania', placeholder: 'RO1234567891', + vatPrefix: 'RO', }, { name: 'RU INN', @@ -372,6 +399,7 @@ export const TAX_IDS: TaxId[] = [ code: 'eu_vat', country: 'Sweden', placeholder: 'SE123456789123', + vatPrefix: 'SE', }, { name: 'SG GST', @@ -390,12 +418,14 @@ export const TAX_IDS: TaxId[] = [ code: 'eu_vat', country: 'Slovenia', placeholder: 'SI12345678', + vatPrefix: 'SI', }, { name: 'SK VAT', code: 'eu_vat', country: 'Slovakia', placeholder: 'SK1234567891', + vatPrefix: 'SK', }, { name: 'TH VAT', @@ -426,6 +456,7 @@ export const TAX_IDS: TaxId[] = [ code: 'eu_vat', country: 'United Kingdom (Northern Ireland)', placeholder: 'XI123456789', + vatPrefix: 'XI', }, { name: 'ZA VAT', diff --git a/studio/components/interfaces/Organization/BillingSettings/TaxID/TaxID.utils.ts b/studio/components/interfaces/Organization/BillingSettings/TaxID/TaxID.utils.ts index f30e7ca3b5c..90a37a324e0 100644 --- a/studio/components/interfaces/Organization/BillingSettings/TaxID/TaxID.utils.ts +++ b/studio/components/interfaces/Organization/BillingSettings/TaxID/TaxID.utils.ts @@ -11,12 +11,11 @@ export const sanitizeTaxID = (taxId: StripeTaxId) => { const selectedTaxId = TAX_IDS.find((option) => option.name === taxId.name) - // The prefix of the country name (ex: "BE" for Belgium, "ESA" for Spain)") - const countryPrefix = selectedTaxId?.placeholder.match(/[a-z]+|[^a-z]+/gi)?.[0] + const vatIdPrefix = selectedTaxId?.vatPrefix - // if the value doesn't start with the country code, prepend them - if (taxId.type === 'eu_vat' && countryPrefix && !taxId.value.startsWith(countryPrefix)) { - newID.value = `${countryPrefix}${taxId.value}` + // if the value doesn't start with the prefix, prepend them + if (vatIdPrefix && !taxId.value.startsWith(vatIdPrefix)) { + newID.value = `${vatIdPrefix}${taxId.value}` } return newID From dd17057599ae66f614861cda2245b5eac96c57b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Mon, 8 May 2023 14:17:51 +0100 Subject: [PATCH 29/39] Set up redirects for old case studies. --- apps/www/lib/redirects.js | 54 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index 5c57d2f3456..62f67487eb7 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -1913,4 +1913,58 @@ module.exports = [ source: '/docs/guides/cli/local-development', destination: '/docs/guides/getting-started/local-development', }, + + // old case studies moved to /customers + + { + permanent: true, + source: '/blog/case-study-monitoro', + destination: '/customers', + }, + { + permanent: true, + source: '/blog/case-study-tayfa', + destination: '/customers', + }, + { + permanent: true, + source: '/blog/case-study-xendit', + destination: '/customers/xendit', + }, + { + permanent: true, + source: '/blog/case-study-roboflow', + destination: '/customers/roboflow', + }, + { + permanent: true, + source: '/blog/toad-a-link-shortener-with-simple-apis-for-low-coders', + destination: '/customers', + }, + { + permanent: true, + source: '/blog/epsilon3-self-hosting', + destination: '/customers/epilson3', + }, + { + permanent: true, + source: '/blog/spot-flutter-with-postgres', + destination: '/customers', + }, + { + permanent: true, + source: '/blog/mobbin-supabase-200000-users', + destination: '/customers/mobbin', + }, + { + permanent: true, + source: '/blog/replenysh-time-to-value-in-less-than-24-hours', + destination: '/customers/replenysh', + }, + { + permanent: true, + source: '/blog/how-supabase-accelerates-development-of-all-pull-together', + destination: '/customers', + }, +] ] From 9afe4306cbf7069029ef28033ea5ce015eb7f75a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Mon, 8 May 2023 14:21:08 +0100 Subject: [PATCH 30/39] Small fix --- apps/www/lib/redirects.js | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index 62f67487eb7..0a0d1525e5d 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -1967,4 +1967,3 @@ module.exports = [ destination: '/customers', }, ] -] From b558f123b6a41eb429c730930eb291d25c947e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Mon, 8 May 2023 14:45:47 +0100 Subject: [PATCH 31/39] Fix ulr --- apps/www/lib/redirects.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index 0a0d1525e5d..e82b0bc4b65 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -1944,7 +1944,7 @@ module.exports = [ { permanent: true, source: '/blog/epsilon3-self-hosting', - destination: '/customers/epilson3', + destination: '/customers/epsilon3', }, { permanent: true, From 4ab4b4f2b4312b3df83c5daabf85dbedf5f2f55f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Mon, 8 May 2023 14:55:17 +0100 Subject: [PATCH 32/39] Update url. --- apps/www/lib/redirects.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index e82b0bc4b65..512cae718a4 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -1934,7 +1934,7 @@ module.exports = [ { permanent: true, source: '/blog/case-study-roboflow', - destination: '/customers/roboflow', + destination: '/customers', }, { permanent: true, From 9f24793c4b10e59306b847fe4dede9905a34d221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Mon, 8 May 2023 15:00:17 +0100 Subject: [PATCH 33/39] Update apps/www/_customers/mendableai.mdx Co-authored-by: Francesco Sansalvadore --- apps/www/_customers/mendableai.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/_customers/mendableai.mdx b/apps/www/_customers/mendableai.mdx index 50551eb1431..1817fac4642 100644 --- a/apps/www/_customers/mendableai.mdx +++ b/apps/www/_customers/mendableai.mdx @@ -28,7 +28,7 @@ about: Mendable.ai is Chat Powered Search for Documentation. ## The Challenge -Mendable.ai was experienveing tremendous success, with WAUs growing nearly 300% since March, and started looking for a tool to store and search through large amounts of vector data to improve the efficiency and accuracy of their similarity search operations for their Chat Powered Search for Documentation. They tried Faiss, Weviate, and Pinecone, but found them to be expensive and not very intuitive, especially when it came to storing metadata along with the vectors. +Mendable.ai was experiencing tremendous success, with WAUs growing nearly 300% since March, and started looking for a tool to store and search through large amounts of vector data to improve the efficiency and accuracy of their similarity search operations for their Chat Powered Search for Documentation. They tried Faiss, Weviate, and Pinecone, but found them to be expensive and not very intuitive, especially when it came to storing metadata along with the vectors. Why they chose Supabase: Mendable.ai discovered that Supabase supports pg_vector and found it to be a simple and cost-effective solution. They were impressed with the open-source nature of Supabase, as well as its ability to store metadata alongside the vectors. They also appreciated the intuitive interface and ease of use. From a1de79b3f38ae4d0c2e4750681b51df42417b649 Mon Sep 17 00:00:00 2001 From: Inian Date: Mon, 8 May 2023 22:04:40 +0800 Subject: [PATCH 34/39] fix: signed url should use public url for studio --- studio/pages/api/storage/[ref]/buckets/[id]/objects/sign.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/pages/api/storage/[ref]/buckets/[id]/objects/sign.ts b/studio/pages/api/storage/[ref]/buckets/[id]/objects/sign.ts index 8677334f21b..13383de212c 100644 --- a/studio/pages/api/storage/[ref]/buckets/[id]/objects/sign.ts +++ b/studio/pages/api/storage/[ref]/buckets/[id]/objects/sign.ts @@ -2,7 +2,7 @@ import { createClient } from '@supabase/supabase-js' import apiWrapper from 'lib/api/apiWrapper' import { NextApiRequest, NextApiResponse } from 'next' -const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!) +const supabase = createClient(process.env.SUPABASE_PUBLIC_URL!, process.env.SUPABASE_SERVICE_KEY!) export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) From 53048d7d4ab24ffc0ab17677e5c44af331833d6b Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Mon, 8 May 2023 16:14:01 +0200 Subject: [PATCH 35/39] chore: add mendeable to CustomerStories.json --- apps/www/_customers/mendableai.mdx | 10 +++++++--- apps/www/data/CustomerStories.json | 17 +++++++++++++++++ apps/www/public/customers-rss.xml | 19 +++++++++++++------ 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/apps/www/_customers/mendableai.mdx b/apps/www/_customers/mendableai.mdx index 1817fac4642..70fa0b0075b 100644 --- a/apps/www/_customers/mendableai.mdx +++ b/apps/www/_customers/mendableai.mdx @@ -1,7 +1,7 @@ --- title: Mendable.ai switches from Pinecone to Supabase for PostgreSQL vector embeddings. name: Mendable.ai -description: How Mendable.ai boosts efficiency and accuracy of chat powered search for documentation using Supabase with pg_vector. +description: How Mendable.ai boosts efficiency and accuracy of chat powered search for documentation using Supabase with pg_vector. author: paul_copplestone author_title: Supabase author_url: https://github.com/kiwicopple @@ -34,7 +34,9 @@ Why they chose Supabase: Mendable.ai discovered that Supabase supports pg_vector and found it to be a simple and cost-effective solution. They were impressed with the open-source nature of Supabase, as well as its ability to store metadata alongside the vectors. They also appreciated the intuitive interface and ease of use. - We tried other vector databases - we tried Faiss, we tried Weviate, we tried Pinecone. We found them to be incredibly expensive and not very intuitive. If you’re just doing vector search they’re great, but if you need to store a bunch of metadata that becomes a huge pain. + We tried other vector databases - we tried Faiss, we tried Weviate, we tried Pinecone. We found + them to be incredibly expensive and not very intuitive. If you’re just doing vector search they’re + great, but if you need to store a bunch of metadata that becomes a huge pain. ## What They Built @@ -50,5 +52,7 @@ Thanks to Supabase's pg_vector, Mendable.ai was able to significantly improve th Mendable.ai's tech stack includes React, Next.js, Express, Vercel, and Supabase. - We looked at the alternatives and chose Supabase because it’s open source, it’s simpler, and, for all the ways we need use it, Supabase has been just as performant - if not more performant - than the other vector databases. + We looked at the alternatives and chose Supabase because it’s open source, it’s simpler, and, for + all the ways we need use it, Supabase has been just as performant - if not more performant - than + the other vector databases. diff --git a/apps/www/data/CustomerStories.json b/apps/www/data/CustomerStories.json index d97f6e79991..c55ae5a858e 100644 --- a/apps/www/data/CustomerStories.json +++ b/apps/www/data/CustomerStories.json @@ -1,4 +1,21 @@ [ + { + "type": "Customer Story", + "title": "Mendable.ai switches from Pinecone to Supabase for PostgreSQL vector embeddings.", + "description": "How Mendable.ai boosts efficiency and accuracy of chat powered search for documentation using Supabase with pg_vector.", + "imgUrl": "images/customers/logos/mendableai.png", + "logoUrl": "images/customers/logos/mendableai.png", + "organization": "Mendable.ai", + "url": "/customers/mendableai", + "path": "/customers/mendableai", + "postMeta": { + "name": "Paul Copplestone", + "avatarUrl": "https://avatars0.githubusercontent.com/u/10214025?v=4", + "publishDate": "Feb 16, 2023", + "readLength": 6 + }, + "ctaText": "View story" + }, { "type": "Customer Story", "title": "HappyTeams unlocks better performance and reduces cost with Supabase.", diff --git a/apps/www/public/customers-rss.xml b/apps/www/public/customers-rss.xml index e8c128abd81..6835a30b98e 100644 --- a/apps/www/public/customers-rss.xml +++ b/apps/www/public/customers-rss.xml @@ -5,42 +5,49 @@ https://supabase.com Latest news from Supabase en - Wed, 15 Feb 2023 03:03:30 +0000 + Thu, 04 May 2023 22:10:00 +0000 + https://supabase.com/customers/mendableai + Mendable.ai switches from Pinecone to Supabase for PostgreSQL vector embeddings. + https://supabase.com/customers/mendableai + How Mendable.ai boosts efficiency and accuracy of chat powered search for documentation using Supabase with pg_vector. + Thu, 04 May 2023 22:10:00 +0000 + + https://supabase.com/customers/happyteams HappyTeams unlocks better performance and reduces cost with Supabase. https://supabase.com/customers/happyteams How a bootstrapped startup migrated from Heroku to Supabase in 30 minutes and never looked back. - Wed, 15 Feb 2023 03:03:30 +0000 + Wed, 15 Feb 2023 23:11:00 +0000 https://supabase.com/customers/xendit Xendit use Supabase and create a full solution shipped to production in less than one week. https://supabase.com/customers/xendit As a payment processor, Xendit are responsible for verifying that all transactions are legal. - Mon, 13 Feb 2023 03:03:30 +0000 + Mon, 13 Feb 2023 23:11:00 +0000 https://supabase.com/customers/replenysh Replenysh uses Supabase to implement OTP in less than 24 hours. https://supabase.com/customers/replenysh With Supabase, Replenysh gets a slick auth experience, reduces DevOps overhead, and continues to scale with Postgres. - Mon, 13 Feb 2023 03:03:30 +0000 + Mon, 13 Feb 2023 23:11:00 +0000 https://supabase.com/customers/mobbin How Mobbin migrated 200,000 users from Firebase for a better authentication experience. https://supabase.com/customers/mobbin Mobbin helps over 200,000 creators globally search and view the latest design patterns from well-known apps. - Mon, 13 Feb 2023 03:03:30 +0000 + Mon, 13 Feb 2023 23:11:00 +0000 https://supabase.com/customers/epsilon3 Epsilon3 digitize paper-based procedures in the space industry using telemetry data, simplifying testing and operations. https://supabase.com/customers/epsilon3 Epsilon3 uses Supabase to help teams execute secure and reliable operations in an industry where project spend runs into the billions. - Mon, 13 Feb 2023 03:03:30 +0000 + Mon, 13 Feb 2023 23:11:00 +0000 From 5f38868d4826055c96800af976cc7348f1cecca3 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Mon, 8 May 2023 16:17:55 +0200 Subject: [PATCH 36/39] fix prettier on redirects.js --- apps/www/lib/redirects.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index 512cae718a4..db6e50c2f8f 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -1913,19 +1913,19 @@ module.exports = [ source: '/docs/guides/cli/local-development', destination: '/docs/guides/getting-started/local-development', }, - - // old case studies moved to /customers - + + // old case studies moved to /customers + { permanent: true, source: '/blog/case-study-monitoro', destination: '/customers', - }, + }, { permanent: true, source: '/blog/case-study-tayfa', destination: '/customers', - }, + }, { permanent: true, source: '/blog/case-study-xendit', From 03cfeb488b67042a0b8297945ee97bd265c579d0 Mon Sep 17 00:00:00 2001 From: Faizan Rasool Date: Mon, 8 May 2023 10:49:00 -0400 Subject: [PATCH 37/39] Update expo documentation --- .../guides/getting-started/tutorials/with-expo.mdx | 12 ++++++------ package-lock.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx index aa420134d55..d4b0805ec49 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx @@ -45,21 +45,21 @@ These variables will be exposed on the browser, and that's completely fine since [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database. ```ts title=lib/supabase.ts -import * as SecureStore from "expo-secure-store"; +import 'react-native-url-polyfill/auto' +import * as SecureStore from 'expo-secure-store' import { createClient } from '@supabase/supabase-js' - const ExpoSecureStoreAdapter = { getItem: (key: string) => { - return SecureStore.getItemAsync(key); + return SecureStore.getItemAsync(key) }, setItem: (key: string, value: string) => { - SecureStore.setItemAsync(key, value); + SecureStore.setItemAsync(key, value) }, removeItem: (key: string) => { - SecureStore.deleteItemAsync(key); + SecureStore.deleteItemAsync(key) }, -}; +} const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL const supabaseAnonKey = YOUR_REACT_NATIVE_SUPABASE_ANON_KEY diff --git a/package-lock.json b/package-lock.json index 0881d78353b..9ed78cb140f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68107,7 +68107,7 @@ "@types/react": "^17.0.37", "@types/react-copy-to-clipboard": "^5.0.4", "@types/react-dom": "^17.0.11", - "@types/react-syntax-highlighter": "*", + "@types/react-syntax-highlighter": "^15.5.6", "autoprefixer": "^10.4.2", "clsx": "^1.2.1", "cmdk-supabase": "^0.2.2", From 0edff93e46c4409ce8e9b87af90f3255c3fdd163 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Tue, 9 May 2023 08:50:57 +0200 Subject: [PATCH 38/39] chore: rename telemetryProps --- apps/docs/pages/_app.tsx | 8 ++++---- apps/www/components/Features/index.tsx | 14 +++++++------- apps/www/components/Hero.tsx | 10 +++++----- apps/www/lib/gaEvents.ts | 10 +++++----- apps/www/lib/telemetry.ts | 10 +++------- apps/www/pages/_app.tsx | 8 ++++---- ...oogleAnalyticsProps.ts => useTelemetryProps.ts} | 2 +- .../SQLEditor/SQLTemplates/SQLTemplates.tsx | 8 ++++---- .../SQLEditor/UtilityPanel/ResultsDropdown.tsx | 8 ++++---- studio/components/ui/PageTelemetry.tsx | 8 ++++---- studio/lib/auth.tsx | 6 +++--- studio/lib/telemetry.ts | 6 +++--- 12 files changed, 47 insertions(+), 51 deletions(-) rename packages/common/hooks/{useGoogleAnalyticsProps.ts => useTelemetryProps.ts} (85%) diff --git a/apps/docs/pages/_app.tsx b/apps/docs/pages/_app.tsx index e0ad82b5602..7234e438f59 100644 --- a/apps/docs/pages/_app.tsx +++ b/apps/docs/pages/_app.tsx @@ -1,6 +1,6 @@ import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' import { SessionContextProvider } from '@supabase/auth-helpers-react' -import { AuthProvider, ThemeProvider, useGoogleAnalyticsProps } from 'common' +import { AuthProvider, ThemeProvider, useTelemetryProps } from 'common' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import ReactMarkdown from 'react-markdown' @@ -21,7 +21,7 @@ import '../styles/prism-okaidia.scss' function MyApp({ Component, pageProps }: AppPropsWithLayout) { const router = useRouter() - const googleAnalyticsProps = useGoogleAnalyticsProps() + const telemetryProps = useTelemetryProps() const [supabase] = useState(() => IS_PLATFORM || LOCAL_SUPABASE ? createBrowserSupabaseClient() : undefined @@ -33,8 +33,8 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { title: document.title, route, ga: { - screen_resolution: googleAnalyticsProps?.screenResolution, - language: googleAnalyticsProps?.language, + screen_resolution: telemetryProps?.screenResolution, + language: telemetryProps?.language, session_id: BrowserTabTracker.sessionId, }, }) diff --git a/apps/www/components/Features/index.tsx b/apps/www/components/Features/index.tsx index 4b291e78767..ab6510718e5 100644 --- a/apps/www/components/Features/index.tsx +++ b/apps/www/components/Features/index.tsx @@ -5,43 +5,43 @@ import gaEvents from '~/lib/gaEvents' import SectionContainer from '../Layouts/SectionContainer' import ProductIcon from '../ProductIcon' import TextLink from '../TextLink' -import { useGoogleAnalyticsProps } from 'common/hooks/useGoogleAnalyticsProps' +import { useTelemetryProps } from 'common/hooks/useTelemetryProps' import { useRouter } from 'next/router' const Features = () => { const router = useRouter() - const googleAnalyticsProps = useGoogleAnalyticsProps() + const telemetryProps = useTelemetryProps() const sendTelemetryEvent = async (product: any) => { switch (product) { case 'Database': return await Telemetry.sendEvent( gaEvents['www_hp_subhero_products_database'], - googleAnalyticsProps, + telemetryProps, router ) case 'Authentication': return await Telemetry.sendEvent( gaEvents['www_hp_subhero_products_auth'], - googleAnalyticsProps, + telemetryProps, router ) case 'Storage': return await Telemetry.sendEvent( gaEvents['www_hp_subhero_products_storage'], - googleAnalyticsProps, + telemetryProps, router ) case 'Edge Functions': return await Telemetry.sendEvent( gaEvents['www_hp_subhero_products_edgeFunctions'], - googleAnalyticsProps, + telemetryProps, router ) case 'Realtime': return await Telemetry.sendEvent( gaEvents['www_hp_subhero_products_realtime'], - googleAnalyticsProps, + telemetryProps, router ) } diff --git a/apps/www/components/Hero.tsx b/apps/www/components/Hero.tsx index 33a1e3deec6..d2decb0ab08 100644 --- a/apps/www/components/Hero.tsx +++ b/apps/www/components/Hero.tsx @@ -2,17 +2,17 @@ import { Button, IconBookOpen } from 'ui' import { useRouter } from 'next/router' import Link from 'next/link' import SectionContainer from './Layouts/SectionContainer' -import Telemetry, { GoogleAnalyticsEvent } from '~/lib/telemetry' +import Telemetry, { TelemetryEvent } from '~/lib/telemetry' import gaEvents from '~/lib/gaEvents' -import { useGoogleAnalyticsProps } from 'common/hooks/useGoogleAnalyticsProps' +import { useTelemetryProps } from 'common/hooks/useTelemetryProps' const Hero = () => { const router = useRouter() const { basePath } = router - const googleAnalyticsProps = useGoogleAnalyticsProps() + const telemetryProps = useTelemetryProps() - const sendTelemetryEvent = async (event: GoogleAnalyticsEvent) => { - await Telemetry.sendEvent(event, googleAnalyticsProps, router) + const sendTelemetryEvent = async (event: TelemetryEvent) => { + await Telemetry.sendEvent(event, telemetryProps, router) } return ( diff --git a/apps/www/lib/gaEvents.ts b/apps/www/lib/gaEvents.ts index 73599720f86..a9db7dead20 100644 --- a/apps/www/lib/gaEvents.ts +++ b/apps/www/lib/gaEvents.ts @@ -1,10 +1,10 @@ -import { GoogleAnalyticsEvent } from './telemetry' +import { TelemetryEvent } from './telemetry' -interface GoogleAnalyticsEventType { - [key: string]: GoogleAnalyticsEvent +interface TelemetryEventType { + [key: string]: TelemetryEvent } -const GoogleAnalyticsEvents: GoogleAnalyticsEventType = { +const TelemetryEvents: TelemetryEventType = { www_hp_hero_startProject: { action: 'www_hp_hero_startProject', category: 'link', @@ -42,4 +42,4 @@ const GoogleAnalyticsEvents: GoogleAnalyticsEventType = { }, } -export default GoogleAnalyticsEvents +export default TelemetryEvents diff --git a/apps/www/lib/telemetry.ts b/apps/www/lib/telemetry.ts index e64f0992b68..579e3a14f5b 100644 --- a/apps/www/lib/telemetry.ts +++ b/apps/www/lib/telemetry.ts @@ -3,14 +3,14 @@ import { API_URL, IS_PROD, IS_PREVIEW } from 'lib/constants' import { BrowserTabTracker } from 'browser-session-tabs' import { NextRouter } from 'next/router' -export interface GoogleAnalyticsEvent { +export interface TelemetryEvent { category: string action: string label: string value?: string } -export interface GoogleAnalyticsProps { +export interface TelemetryProps { screenResolution?: string language: string } @@ -18,11 +18,7 @@ export interface GoogleAnalyticsProps { // This event is the same as in studio/lib/telemetry.tx // but uses different ENV variables for www -const sendEvent = ( - event: GoogleAnalyticsEvent, - gaProps: GoogleAnalyticsProps, - router: NextRouter -) => { +const sendEvent = (event: TelemetryEvent, gaProps: TelemetryProps, router: NextRouter) => { if (!IS_PROD && !IS_PREVIEW) return const { category, action, label, value } = event diff --git a/apps/www/pages/_app.tsx b/apps/www/pages/_app.tsx index ab0dbf807d2..1b5db4d340b 100644 --- a/apps/www/pages/_app.tsx +++ b/apps/www/pages/_app.tsx @@ -6,14 +6,14 @@ import { useEffect } from 'react' import Meta from '~/components/Favicons' import '../styles/index.css' import { post } from '~/lib/fetchWrapper' -import { AuthProvider, ThemeProvider, useGoogleAnalyticsProps } from 'common' +import { AuthProvider, ThemeProvider, useTelemetryProps } from 'common' import { BrowserTabTracker } from 'browser-session-tabs' import { v4 as uuidv4 } from 'uuid' import Head from 'next/head' export default function MyApp({ Component, pageProps }: AppProps) { const router = useRouter() - const googleAnalyticsProps = useGoogleAnalyticsProps() + const telemetryProps = useTelemetryProps() function handlePageTelemetry(route: string) { return post(`${API_URL}/telemetry/page`, { @@ -21,8 +21,8 @@ export default function MyApp({ Component, pageProps }: AppProps) { title: document.title, route, ga: { - screen_resolution: googleAnalyticsProps?.screenResolution, - language: googleAnalyticsProps?.language, + screen_resolution: telemetryProps?.screenResolution, + language: telemetryProps?.language, session_id: BrowserTabTracker.sessionId, }, }) diff --git a/packages/common/hooks/useGoogleAnalyticsProps.ts b/packages/common/hooks/useTelemetryProps.ts similarity index 85% rename from packages/common/hooks/useGoogleAnalyticsProps.ts rename to packages/common/hooks/useTelemetryProps.ts index 3b247008f9f..73e63cd5e4f 100644 --- a/packages/common/hooks/useGoogleAnalyticsProps.ts +++ b/packages/common/hooks/useTelemetryProps.ts @@ -2,7 +2,7 @@ import { useRouter } from 'next/router' const isBrowser = typeof window !== 'undefined' -export function useGoogleAnalyticsProps() { +export function useTelemetryProps() { const { locale } = useRouter() return { diff --git a/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx b/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx index f400dbd6a13..86f850fbce4 100644 --- a/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx +++ b/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx @@ -8,7 +8,7 @@ import { useProfileQuery } from 'data/profile/profile-query' import { SQL_TEMPLATES } from 'components/interfaces/SQLEditor/SQLEditor.constants' import SQLCard from './SQLCard' import { createSqlSnippetSkeleton } from '../SQLEditor.utils' -import { useGoogleAnalyticsProps, useParams } from 'common' +import { useTelemetryProps, useParams } from 'common' import { useRouter } from 'next/router' import { SqlSnippet } from 'data/content/sql-snippets-query' import { useSqlEditorStateSnapshot } from 'state/sql-editor' @@ -21,7 +21,7 @@ const SQLTemplates = observer(() => { const { data: profile } = useProfileQuery() const [sql, quickStart] = partition(SQL_TEMPLATES, { type: 'template' }) - const googleAnalyticsProps = useGoogleAnalyticsProps() + const telemetryProps = useTelemetryProps() const snap = useSqlEditorStateSnapshot() const canCreateSQLSnippet = checkPermissions(PermissionAction.CREATE, 'user_content', { resource: { type: 'sql', owner_id: profile?.id }, @@ -78,7 +78,7 @@ const SQLTemplates = observer(() => { action: 'script_clicked', label: x.title, }, - googleAnalyticsProps, + telemetryProps, router ) }} @@ -113,7 +113,7 @@ const SQLTemplates = observer(() => { action: 'quickstart_clicked', label: x.title, }, - googleAnalyticsProps, + telemetryProps, router ) }} diff --git a/studio/components/interfaces/SQLEditor/UtilityPanel/ResultsDropdown.tsx b/studio/components/interfaces/SQLEditor/UtilityPanel/ResultsDropdown.tsx index 4d47dcedb13..50565d6c697 100644 --- a/studio/components/interfaces/SQLEditor/UtilityPanel/ResultsDropdown.tsx +++ b/studio/components/interfaces/SQLEditor/UtilityPanel/ResultsDropdown.tsx @@ -9,7 +9,7 @@ import { CSVLink } from 'react-csv' import { useSqlEditorStateSnapshot } from 'state/sql-editor' // @ts-ignore import MarkdownTable from 'markdown-table' -import { useGoogleAnalyticsProps } from 'common' +import { useTelemetryProps } from 'common' import { useRouter } from 'next/router' export type ResultsDropdownProps = { @@ -19,7 +19,7 @@ export type ResultsDropdownProps = { const ResultsDropdown = ({ id }: ResultsDropdownProps) => { const { project } = useProjectContext() const snap = useSqlEditorStateSnapshot() - const googleAnalyticsProps = useGoogleAnalyticsProps() + const telemetryProps = useTelemetryProps() const result = snap.results?.[id]?.[0] ?? undefined const { ui } = useStore() const csvRef = useRef(null) @@ -34,7 +34,7 @@ const ResultsDropdown = ({ id }: ResultsDropdownProps) => { csvRef.current?.link.click() Telemetry.sendEvent( { category: 'sql_editor', action: 'sql_download_csv', label: '' }, - googleAnalyticsProps, + telemetryProps, router ) } @@ -58,7 +58,7 @@ const ResultsDropdown = ({ id }: ResultsDropdownProps) => { ui.setNotification({ category: 'success', message: 'Copied results to clipboard' }) Telemetry.sendEvent( { category: 'sql_editor', action: 'sql_copy_as_markdown', label: '' }, - googleAnalyticsProps, + telemetryProps, router ) }) diff --git a/studio/components/ui/PageTelemetry.tsx b/studio/components/ui/PageTelemetry.tsx index 54b9155cec1..eeda65b6071 100644 --- a/studio/components/ui/PageTelemetry.tsx +++ b/studio/components/ui/PageTelemetry.tsx @@ -1,5 +1,5 @@ import { BrowserTabTracker } from 'browser-session-tabs' -import { useGoogleAnalyticsProps } from 'common' +import { useTelemetryProps } from 'common' import { post } from 'lib/common/fetch' import { API_URL, IS_PLATFORM } from 'lib/constants' import { observer } from 'mobx-react-lite' @@ -26,7 +26,7 @@ function sanitizePageViewRoute(_route?: string) { const PageTelemetry: FC = ({ children }) => { const router = useRouter() - const googleAnalyticsProps = useGoogleAnalyticsProps() + const telemetryProps = useTelemetryProps() useEffect(() => { function handleRouteChange(url: string) { @@ -72,8 +72,8 @@ const PageTelemetry: FC = ({ children }) => { title: document.title, route, ga: { - screen_resolution: googleAnalyticsProps?.screenResolution, - language: googleAnalyticsProps?.language, + screen_resolution: telemetryProps?.screenResolution, + language: telemetryProps?.language, session_id: BrowserTabTracker.sessionId, }, }) diff --git a/studio/lib/auth.tsx b/studio/lib/auth.tsx index f02f3688130..55b17f74866 100644 --- a/studio/lib/auth.tsx +++ b/studio/lib/auth.tsx @@ -2,7 +2,7 @@ import { AuthContext as AuthContextInternal, AuthProvider as AuthProviderInternal, gotrueClient, - useGoogleAnalyticsProps, + useTelemetryProps, } from 'common' import { useProfileQuery } from 'data/profile/profile-query' import { useStore } from 'hooks' @@ -14,7 +14,7 @@ export const AuthContext = AuthContextInternal export const AuthProvider = ({ children }: PropsWithChildren<{}>) => { const { ui, app } = useStore() - const googleAnalyticsProps = useGoogleAnalyticsProps() + const telemetryProps = useTelemetryProps() // Check for unverified GitHub users after a GitHub sign in useEffect(() => { @@ -36,7 +36,7 @@ export const AuthProvider = ({ children }: PropsWithChildren<{}>) => { // Track telemetry for the current user useProfileQuery({ onSuccess(profile) { - Telemetry.sendIdentify(profile, googleAnalyticsProps) + Telemetry.sendIdentify(profile, telemetryProps) // [Joshen] Temp fix: For new users, the GET profile call also creates a default org // But because the dashboard's logged in state is using gotrue as the source of truth diff --git a/studio/lib/telemetry.ts b/studio/lib/telemetry.ts index f3a75970c6a..8b75269c02a 100644 --- a/studio/lib/telemetry.ts +++ b/studio/lib/telemetry.ts @@ -4,7 +4,7 @@ import { User } from 'types' import { BrowserTabTracker } from 'browser-session-tabs' import { NextRouter } from 'next/router' -export interface GoogleAnalyticsProps { +export interface TelemetryProps { screenResolution?: string language: string } @@ -16,7 +16,7 @@ const sendEvent = ( label: string value?: string }, - gaProps: GoogleAnalyticsProps, + gaProps: TelemetryProps, router: NextRouter ) => { if (!IS_PLATFORM) return @@ -43,7 +43,7 @@ const sendEvent = ( * TODO: GA4 doesn't have identify method. * We may or may not need gaClientId here. Confirm later */ -const sendIdentify = (user: User, gaProps?: GoogleAnalyticsProps) => { +const sendIdentify = (user: User, gaProps?: TelemetryProps) => { if (!IS_PLATFORM) return return post(`${API_URL}/telemetry/identify`, { From 82951ed7ff6828ab2898cb3aa69a5c9bb2e3a07b Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Tue, 9 May 2023 08:56:03 +0200 Subject: [PATCH 39/39] chore: remove browser-session-tabs --- apps/docs/package.json | 1 - apps/docs/pages/_app.tsx | 11 -------- apps/www/lib/telemetry.ts | 2 -- apps/www/package.json | 1 - apps/www/pages/_app.tsx | 11 -------- package-lock.json | 37 -------------------------- packages/common/hooks/index.ts | 2 +- studio/components/ui/PageTelemetry.tsx | 2 -- studio/lib/telemetry.ts | 3 --- studio/package.json | 1 - studio/pages/_app.tsx | 10 ------- 11 files changed, 1 insertion(+), 80 deletions(-) diff --git a/apps/docs/package.json b/apps/docs/package.json index 7f8438339e1..0eb29301d62 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -56,7 +56,6 @@ "@supabase/supabase-js": "^2.13.0", "algoliasearch": "^4.14.2", "babel": "^6.23.0", - "browser-session-tabs": "^3.0.0", "clsx": "^1.2.1", "common": "*", "config": "*", diff --git a/apps/docs/pages/_app.tsx b/apps/docs/pages/_app.tsx index 7234e438f59..7c3519884b8 100644 --- a/apps/docs/pages/_app.tsx +++ b/apps/docs/pages/_app.tsx @@ -12,8 +12,6 @@ import Favicons from '~/components/Favicons' import SiteLayout from '~/layouts/SiteLayout' import { API_URL, IS_PLATFORM, LOCAL_SUPABASE } from '~/lib/constants' import { post } from '~/lib/fetchWrappers' -import { BrowserTabTracker } from 'browser-session-tabs' -import { v4 as uuidv4 } from 'uuid' import '../styles/ch.scss' import '../styles/main.scss?v=1.0.0' import '../styles/new-docs.scss' @@ -35,19 +33,10 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { ga: { screen_resolution: telemetryProps?.screenResolution, language: telemetryProps?.language, - session_id: BrowserTabTracker.sessionId, }, }) } - useEffect(() => { - // Generate browser session id for anon tracking - BrowserTabTracker.initialize({ - storageKey: 'supabase.browser.session', - sessionIdGenerator: () => uuidv4(), - }) - }, []) - useEffect(() => { function handleRouteChange(url: string) { /* diff --git a/apps/www/lib/telemetry.ts b/apps/www/lib/telemetry.ts index 579e3a14f5b..a880edb6f5b 100644 --- a/apps/www/lib/telemetry.ts +++ b/apps/www/lib/telemetry.ts @@ -1,6 +1,5 @@ import { post } from '~/lib/fetchWrapper' import { API_URL, IS_PROD, IS_PREVIEW } from 'lib/constants' -import { BrowserTabTracker } from 'browser-session-tabs' import { NextRouter } from 'next/router' export interface TelemetryEvent { @@ -34,7 +33,6 @@ const sendEvent = (event: TelemetryEvent, gaProps: TelemetryProps, router: NextR ga: { screen_resolution: gaProps?.screenResolution, language: gaProps?.language, - session_id: BrowserTabTracker.sessionId, }, }) } diff --git a/apps/www/package.json b/apps/www/package.json index 8242300daea..cadcf99b872 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -20,7 +20,6 @@ "@next/mdx": "^12.3.2", "@supabase/auth-ui-react": "^0.1.6", "@supabase/supabase-js": "^2.13.0", - "browser-session-tabs": "^3.0.0", "classnames": "^2.3.1", "cobe": "^0.6.2", "common": "*", diff --git a/apps/www/pages/_app.tsx b/apps/www/pages/_app.tsx index 1b5db4d340b..7329be0174e 100644 --- a/apps/www/pages/_app.tsx +++ b/apps/www/pages/_app.tsx @@ -7,8 +7,6 @@ import Meta from '~/components/Favicons' import '../styles/index.css' import { post } from '~/lib/fetchWrapper' import { AuthProvider, ThemeProvider, useTelemetryProps } from 'common' -import { BrowserTabTracker } from 'browser-session-tabs' -import { v4 as uuidv4 } from 'uuid' import Head from 'next/head' export default function MyApp({ Component, pageProps }: AppProps) { @@ -23,19 +21,10 @@ export default function MyApp({ Component, pageProps }: AppProps) { ga: { screen_resolution: telemetryProps?.screenResolution, language: telemetryProps?.language, - session_id: BrowserTabTracker.sessionId, }, }) } - useEffect(() => { - // Generate browser session id for anon tracking - BrowserTabTracker.initialize({ - storageKey: 'supabase.browser.session', - sessionIdGenerator: () => uuidv4(), - }) - }, []) - useEffect(() => { function handleRouteChange(url: string) { handlePageTelemetry(url) diff --git a/package-lock.json b/package-lock.json index 4b3477ab38d..9ed78cb140f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,6 @@ "@supabase/supabase-js": "^2.13.0", "algoliasearch": "^4.14.2", "babel": "^6.23.0", - "browser-session-tabs": "^3.0.0", "clsx": "^1.2.1", "common": "*", "config": "*", @@ -674,7 +673,6 @@ "@next/mdx": "^12.3.2", "@supabase/auth-ui-react": "^0.1.6", "@supabase/supabase-js": "^2.13.0", - "browser-session-tabs": "^3.0.0", "classnames": "^2.3.1", "cobe": "^0.6.2", "common": "*", @@ -12809,22 +12807,6 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", "dev": true }, - "node_modules/browser-session-tabs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/browser-session-tabs/-/browser-session-tabs-3.0.0.tgz", - "integrity": "sha512-HBJWkCbcDYFL+hlSGld54SF1SgaiSXnw9vaTPdDeOxT9HCTFt3hVdArIroQ3HDhh9vhqkbl6TkMjTFok0ZfwIA==", - "dependencies": { - "js-cookie": "^3.0.1" - } - }, - "node_modules/browser-session-tabs/node_modules/js-cookie": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", - "engines": { - "node": ">=14" - } - }, "node_modules/browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -39207,7 +39189,6 @@ "analytics-node": "^3.5.0", "awesome-debounce-promise": "^2.1.0", "blueimp-md5": "^2.19.0", - "browser-session-tabs": "^3.0.0", "clipboard": "^2.0.8", "clsx": "^1.2.1", "common": "*", @@ -48929,21 +48910,6 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", "dev": true }, - "browser-session-tabs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/browser-session-tabs/-/browser-session-tabs-3.0.0.tgz", - "integrity": "sha512-HBJWkCbcDYFL+hlSGld54SF1SgaiSXnw9vaTPdDeOxT9HCTFt3hVdArIroQ3HDhh9vhqkbl6TkMjTFok0ZfwIA==", - "requires": { - "js-cookie": "^3.0.1" - }, - "dependencies": { - "js-cookie": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==" - } - } - }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -51572,7 +51538,6 @@ "@types/react": "17.0.39", "algoliasearch": "^4.14.2", "babel": "^6.23.0", - "browser-session-tabs": "^3.0.0", "clsx": "^1.2.1", "common": "*", "config": "*", @@ -66456,7 +66421,6 @@ "aws-sdk": "^2.1354.0", "babel-loader": "^8.2.3", "blueimp-md5": "^2.19.0", - "browser-session-tabs": "^3.0.0", "clipboard": "^2.0.8", "clsx": "^1.2.1", "common": "*", @@ -69909,7 +69873,6 @@ "@types/react-dom": "^17.0.11", "@types/react-syntax-highlighter": "^13.5.2", "autoprefixer": "^10.4.12", - "browser-session-tabs": "^3.0.0", "classnames": "^2.3.1", "cobe": "^0.6.2", "common": "*", diff --git a/packages/common/hooks/index.ts b/packages/common/hooks/index.ts index 1d07190bbf5..65b5a76ec98 100644 --- a/packages/common/hooks/index.ts +++ b/packages/common/hooks/index.ts @@ -1,3 +1,3 @@ export * from './useBreakpoint' export * from './useParams' -export * from './useGoogleAnalyticsProps' +export * from './useTelemetryProps' diff --git a/studio/components/ui/PageTelemetry.tsx b/studio/components/ui/PageTelemetry.tsx index eeda65b6071..f6de24e69b6 100644 --- a/studio/components/ui/PageTelemetry.tsx +++ b/studio/components/ui/PageTelemetry.tsx @@ -1,4 +1,3 @@ -import { BrowserTabTracker } from 'browser-session-tabs' import { useTelemetryProps } from 'common' import { post } from 'lib/common/fetch' import { API_URL, IS_PLATFORM } from 'lib/constants' @@ -74,7 +73,6 @@ const PageTelemetry: FC = ({ children }) => { ga: { screen_resolution: telemetryProps?.screenResolution, language: telemetryProps?.language, - session_id: BrowserTabTracker.sessionId, }, }) } diff --git a/studio/lib/telemetry.ts b/studio/lib/telemetry.ts index 8b75269c02a..9cf951fc0c6 100644 --- a/studio/lib/telemetry.ts +++ b/studio/lib/telemetry.ts @@ -1,7 +1,6 @@ import { post } from 'lib/common/fetch' import { API_URL, IS_PLATFORM } from 'lib/constants' import { User } from 'types' -import { BrowserTabTracker } from 'browser-session-tabs' import { NextRouter } from 'next/router' export interface TelemetryProps { @@ -34,7 +33,6 @@ const sendEvent = ( ga: { screen_resolution: gaProps?.screenResolution, language: gaProps?.language, - session_id: BrowserTabTracker.sessionId, }, }) } @@ -51,7 +49,6 @@ const sendIdentify = (user: User, gaProps?: TelemetryProps) => { ga: { screen_resolution: gaProps?.screenResolution, language: gaProps?.language, - session_id: BrowserTabTracker.sessionId, }, }) } diff --git a/studio/package.json b/studio/package.json index e85e5682e10..2f845d6011e 100644 --- a/studio/package.json +++ b/studio/package.json @@ -41,7 +41,6 @@ "analytics-node": "^3.5.0", "awesome-debounce-promise": "^2.1.0", "blueimp-md5": "^2.19.0", - "browser-session-tabs": "^3.0.0", "clipboard": "^2.0.8", "clsx": "^1.2.1", "common": "*", diff --git a/studio/pages/_app.tsx b/studio/pages/_app.tsx index c35c5c382ae..8a9144cf26f 100644 --- a/studio/pages/_app.tsx +++ b/studio/pages/_app.tsx @@ -50,8 +50,6 @@ import Favicons from 'components/head/Favicons' import { IS_PLATFORM } from 'lib/constants' import { SessionContextProvider } from '@supabase/auth-helpers-react' import { createClient } from '@supabase/supabase-js' -import { BrowserTabTracker } from 'browser-session-tabs' -import { v4 as uuidv4 } from 'uuid' dayjs.extend(customParseFormat) dayjs.extend(utc) @@ -76,14 +74,6 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) { const getSavingState = () => rootStore.content.savingState - useEffect(() => { - // Generate browser session id for anon tracking - BrowserTabTracker.initialize({ - storageKey: 'supabase.browser.session', - sessionIdGenerator: () => uuidv4(), - }) - }, []) - // prompt the user if they try and leave with unsaved content store changes useEffect(() => { const warningText = 'You have unsaved changes - are you sure you wish to leave this page?'