diff --git a/apps/docs/features/app.providers.tsx b/apps/docs/features/app.providers.tsx
index 8513316a789..ed5654a9f59 100644
--- a/apps/docs/features/app.providers.tsx
+++ b/apps/docs/features/app.providers.tsx
@@ -1,9 +1,10 @@
import { type PropsWithChildren } from 'react'
-import { ThemeProvider } from 'common'
+import { FeatureFlagProvider, ThemeProvider } from 'common'
import { SonnerToaster } from 'ui'
import { CommandProvider } from 'ui-patterns/CommandMenu'
import SiteLayout from '~/layouts/SiteLayout'
+import { API_URL } from '~/lib/constants'
import { AuthContainer } from './auth/auth.client'
import { DocsCommandMenu } from './command'
import { QueryClientProvider } from './data/queryClient.client'
@@ -18,20 +19,22 @@ function GlobalProviders({ children }: PropsWithChildren) {
return (
-
-
-
-
-
-
- {children}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+
)
diff --git a/apps/docs/features/telemetry/telemetry.client.tsx b/apps/docs/features/telemetry/telemetry.client.tsx
index 5264e668827..ee1853b8d74 100644
--- a/apps/docs/features/telemetry/telemetry.client.tsx
+++ b/apps/docs/features/telemetry/telemetry.client.tsx
@@ -1,86 +1,19 @@
'use client'
-import { isBrowser, useTelemetryCookie } from 'common'
-import { usePathname } from 'next/navigation'
-import { useCallback, useEffect } from 'react'
+import { IS_PLATFORM, PageTelemetry as PageTelemetryImpl } from 'common'
import { useConsent } from 'ui-patterns/ConsentToast'
-import { IS_PLATFORM } from '~/lib/constants'
-import { unauthedAllowedPost } from '~/lib/fetch/fetchWrappers'
-
-const useTelemetryWithConsent = () => {
- const pathname = usePathname()
- const { hasAcceptedConsent } = useConsent()
-
- const sendPageTelemetry = useCallback(() => {
- if (!(IS_PLATFORM && hasAcceptedConsent)) return
-
- const title = typeof document !== 'undefined' ? document?.title : ''
- const referrer = typeof document !== 'undefined' ? document?.referrer : ''
-
- unauthedAllowedPost('/platform/telemetry/page', {
- body: {
- pathname,
- page_url: isBrowser ? window.location.href : '',
- page_title: title,
- ph: {
- referrer,
- language: navigator.language ?? 'en-US',
- user_agent: navigator.userAgent,
- search: isBrowser ? window.location.search : '',
- viewport_height: isBrowser ? window.innerHeight : 0,
- viewport_width: isBrowser ? window.innerWidth : 0,
- },
- },
- headers: { Version: '2' },
- credentials: 'include',
- }).catch((e) => {
- console.error('Problem sending telemetry page:', e)
- })
- }, [pathname, hasAcceptedConsent])
-
- const sendPageLeaveTelemetry = useCallback(() => {
- if (!(IS_PLATFORM && hasAcceptedConsent)) return
-
- const title = typeof document !== 'undefined' ? document?.title : ''
-
- unauthedAllowedPost('/platform/telemetry/page-leave', {
- body: {
- pathname,
- page_url: isBrowser ? window.location.href : '',
- page_title: title,
- },
- credentials: 'include',
- }).catch((e) => {
- console.error('Problem sending telemetry page-leave:', e)
- })
- }, [pathname, hasAcceptedConsent])
-
- return { sendPageTelemetry, sendPageLeaveTelemetry }
-}
+import { API_URL } from '~/lib/constants'
const PageTelemetry = () => {
- const pathname = usePathname()
-
- const title = typeof document !== 'undefined' ? document?.title : ''
- const referrer = typeof document !== 'undefined' ? document?.referrer : ''
const { hasAcceptedConsent } = useConsent()
- useTelemetryCookie({ hasAcceptedConsent, title, referrer })
- const { sendPageTelemetry, sendPageLeaveTelemetry } = useTelemetryWithConsent()
-
- useEffect(() => {
- if (pathname) sendPageTelemetry()
- }, [pathname, sendPageTelemetry])
-
- useEffect(() => {
- if (!pathname) return
-
- const handleBeforeUnload = () => sendPageLeaveTelemetry()
- window.addEventListener('beforeunload', handleBeforeUnload)
- return () => window.removeEventListener('beforeunload', handleBeforeUnload)
- }, [pathname, sendPageLeaveTelemetry])
-
- return null
+ return (
+
+ )
}
export { PageTelemetry }
diff --git a/apps/docs/lib/constants.ts b/apps/docs/lib/constants.ts
index fd52e23ad1d..60b43f4d101 100644
--- a/apps/docs/lib/constants.ts
+++ b/apps/docs/lib/constants.ts
@@ -1,7 +1,7 @@
export const API_URL = (
process.env.NODE_ENV === 'development'
? process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080'
- : process.env.NEXT_PUBLIC_API_URL
+ : process.env.NEXT_PUBLIC_API_URL!
)?.replace(/\/platform$/, '')
export const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH || '/docs'
export const IS_DEV = process.env.NODE_ENV === 'development'
diff --git a/apps/studio/components/ui/Flag/FlagContext.ts b/apps/studio/components/ui/Flag/FlagContext.ts
deleted file mode 100644
index a848bb0f63e..00000000000
--- a/apps/studio/components/ui/Flag/FlagContext.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { createContext } from 'react'
-
-const FlagContext = createContext({ configcat: {}, posthog: {} })
-
-export default FlagContext
diff --git a/apps/studio/components/ui/Flag/FlagProvider.tsx b/apps/studio/components/ui/Flag/FlagProvider.tsx
deleted file mode 100644
index 317af7401d4..00000000000
--- a/apps/studio/components/ui/Flag/FlagProvider.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import type { User } from '@supabase/supabase-js'
-import { FlagValues } from '@vercel/flags/react'
-import { PropsWithChildren, useEffect, useState } from 'react'
-
-import { useUser } from 'lib/auth'
-import { getFlags } from 'lib/configcat'
-import { IS_PLATFORM } from 'lib/constants'
-import { CallFeatureFlagsResponse, getPHFeatureFlags } from 'lib/posthog'
-import FlagContext from './FlagContext'
-
-var getCookies = function () {
- var pairs = document.cookie.split(';')
- var cookies: Record = {}
- for (var i = 0; i < pairs.length; i++) {
- var [t_key, value] = pairs[i].split('=')
- const key = t_key.trim()
-
- cookies[key] = unescape(value)
- }
- return cookies
-}
-
-export type FlagProviderStore = {
- configcat: { [key: string]: boolean | string }
- posthog: CallFeatureFlagsResponse
-}
-
-const FlagProvider = ({ children }: PropsWithChildren<{}>) => {
- const user = useUser()
-
- const { Provider } = FlagContext
- const [store, setStore] = useState({ configcat: {}, posthog: {} })
-
- const processFlags = async (user?: User) => {
- const flagStore: FlagProviderStore = { configcat: {}, posthog: {} }
-
- // Load PH flags
- if (user) {
- const flags = await getPHFeatureFlags()
- if (flags) flagStore.posthog = flags
- }
-
- // Load ConfigCat flags
- const flagValues = await getFlags(user)
- let overridesCookieValue: Record = {}
- try {
- const cookies = getCookies()
- overridesCookieValue = JSON.parse(cookies['vercel-flag-overrides'])
- } catch {}
-
- flagValues.forEach((item) => {
- flagStore['configcat'][item.settingKey] =
- overridesCookieValue[item.settingKey] ?? item.settingValue
- })
- setStore(flagStore)
- }
-
- useEffect(() => {
- // [Joshen] getFlags get triggered everytime the tab refocuses but this should be okay
- // as per https://configcat.com/docs/sdk-reference/js/#polling-modes:
- // The polling downloads the config.json at the set interval and are stored in the internal cache
- // which subsequently all getValueAsync() calls are served from there
- if (IS_PLATFORM) processFlags(user ?? undefined)
- }, [user])
-
- return (
-
- {/*
- [Joshen] Just support configcat flags in Vercel flags for now for simplicity
- although I think it should be fairly simply to support PH too
- */}
-
- {children}
-
- )
-}
-
-export default FlagProvider
diff --git a/apps/studio/components/ui/PageTelemetry.tsx b/apps/studio/components/ui/GroupsTelemetry.tsx
similarity index 64%
rename from apps/studio/components/ui/PageTelemetry.tsx
rename to apps/studio/components/ui/GroupsTelemetry.tsx
index 0dc86e373e6..5925692f290 100644
--- a/apps/studio/components/ui/PageTelemetry.tsx
+++ b/apps/studio/components/ui/GroupsTelemetry.tsx
@@ -1,17 +1,15 @@
+/* eslint-disable react-hooks/rules-of-hooks */
import * as Sentry from '@sentry/nextjs'
import { useRouter } from 'next/router'
-import { PropsWithChildren, useEffect } from 'react'
+import { useEffect } from 'react'
import { useParams, useTelemetryCookie, useUser } from 'common'
import { useSendGroupsIdentifyMutation } from 'data/telemetry/send-groups-identify-mutation'
import { useSendGroupsResetMutation } from 'data/telemetry/send-groups-reset-mutation'
-import { useSendPageLeaveMutation } from 'data/telemetry/send-page-leave-mutation'
-import { useSendPageMutation } from 'data/telemetry/send-page-mutation'
import { usePrevious } from 'hooks/deprecated'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { IS_PLATFORM, LOCAL_STORAGE_KEYS } from 'lib/constants'
import { useAppStateSnapshot } from 'state/app-state'
-import { useConsent } from 'ui-patterns/ConsentToast'
const getAnonId = async (id: string) => {
const encoder = new TextEncoder()
@@ -23,19 +21,19 @@ const getAnonId = async (id: string) => {
return base64String
}
-const PageTelemetry = ({ children }: PropsWithChildren<{}>) => {
+const GroupsTelemetry = ({ hasAcceptedConsent }: { hasAcceptedConsent: boolean }) => {
+ // Although this is "technically" breaking the rules of hooks
+ // IS_PLATFORM never changes within a session, so this won't cause any issues
+ if (!IS_PLATFORM) return null
+
const user = useUser()
const router = useRouter()
const { ref, slug } = useParams()
const snap = useAppStateSnapshot()
const organization = useSelectedOrganization()
- const { consentValue, hasAcceptedConsent } = useConsent()
const previousPathname = usePrevious(router.pathname)
- const trackTelemetryPH = consentValue === 'true'
- const { mutate: sendPage } = useSendPageMutation()
- const { mutateAsync: sendPageLeave } = useSendPageLeaveMutation()
const { mutate: sendGroupsIdentify } = useSendGroupsIdentifyMutation()
const { mutate: sendGroupsReset } = useSendGroupsResetMutation()
@@ -44,34 +42,11 @@ const PageTelemetry = ({ children }: PropsWithChildren<{}>) => {
useTelemetryCookie({ hasAcceptedConsent, title, referrer })
useEffect(() => {
- if (consentValue !== null) {
+ if (hasAcceptedConsent) {
snap.setIsOptedInTelemetry(hasAcceptedConsent)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [consentValue])
-
- useEffect(() => {
- function handleRouteChange() {
- if (snap.isOptedInTelemetry) handlePageTelemetry(window.location.href)
- }
-
- // Listen for page changes after a navigation or when the query changes
- router.events.on('routeChangeComplete', handleRouteChange)
- return () => {
- router.events.off('routeChangeComplete', handleRouteChange)
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [router, snap.isOptedInTelemetry])
-
- useEffect(() => {
- // Send page telemetry on first page load
- // Waiting for router ready before sending page_view
- // if not the path will be dynamic route instead of the browser url
- if (router.isReady && snap.isOptedInTelemetry) {
- handlePageTelemetry(window.location.href)
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [router.isReady, snap.isOptedInTelemetry])
+ }, [hasAcceptedConsent])
useEffect(() => {
// don't set the sentry user id if the user hasn't logged in (so that Sentry errors show null user id instead of anonymous id)
@@ -107,7 +82,7 @@ const PageTelemetry = ({ children }: PropsWithChildren<{}>) => {
const isLeavingOrgRoute =
(previousPathname ?? '').includes('[slug]') && !router.pathname.includes('[slug]')
- if (trackTelemetryPH) {
+ if (hasAcceptedConsent) {
if (ref && (isLandingOnProjectRoute || isEnteringProjectRoute)) {
sendGroupsIdentify({ organization_slug: organization?.slug, project_ref: ref as string })
} else if (slug && (isLandingOnOrgRoute || isEnteringOrgRoute)) {
@@ -121,24 +96,9 @@ const PageTelemetry = ({ children }: PropsWithChildren<{}>) => {
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [trackTelemetryPH, slug, ref, router.pathname])
+ }, [hasAcceptedConsent, slug, ref, router.pathname])
- useEffect(() => {
- const handleBeforeUnload = async () => {
- if (snap.isOptedInTelemetry) await sendPageLeave()
- }
- window.addEventListener('beforeunload', handleBeforeUnload)
-
- return () => {
- window.removeEventListener('beforeunload', handleBeforeUnload)
- }
- }, [])
-
- const handlePageTelemetry = async (route: string) => {
- if (IS_PLATFORM) sendPage({ url: route })
- }
-
- return <>{children}>
+ return null
}
-export default PageTelemetry
+export default GroupsTelemetry
diff --git a/apps/studio/data/content/content-count-query.ts b/apps/studio/data/content/content-count-query.ts
index ac7fc907823..d7c61b9ad39 100644
--- a/apps/studio/data/content/content-count-query.ts
+++ b/apps/studio/data/content/content-count-query.ts
@@ -28,7 +28,7 @@ export async function getContentCount(
if (name) query.name = name
const { data, error } = await get('/platform/projects/{ref}/content/count', {
- params: { path: { ref: projectRef }, query: { type, visibility, favorite, name } },
+ params: { path: { ref: projectRef }, query },
signal,
})
diff --git a/apps/studio/data/telemetry/send-event-mutation.ts b/apps/studio/data/telemetry/send-event-mutation.ts
index fc9b17384c8..cdf9bcf2b95 100644
--- a/apps/studio/data/telemetry/send-event-mutation.ts
+++ b/apps/studio/data/telemetry/send-event-mutation.ts
@@ -69,7 +69,7 @@ export type SendEventVariables =
| AssistantSqlDiffHandlerEvaluatedEvent
| AssistantEditInSqlEditorClickedEvent
-type SendEventPayload = components['schemas']['TelemetryEventBodyV2']
+type SendEventPayload = components['schemas']['TelemetryEventBodyV2Dto']
export async function sendEvent({ body }: { body: SendEventPayload }) {
const consent =
diff --git a/apps/studio/data/telemetry/send-page-leave-mutation.ts b/apps/studio/data/telemetry/send-page-leave-mutation.ts
deleted file mode 100644
index 97d3b7cf9e0..00000000000
--- a/apps/studio/data/telemetry/send-page-leave-mutation.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { useMutation, UseMutationOptions } from '@tanstack/react-query'
-import { components } from 'api-types'
-
-import { LOCAL_STORAGE_KEYS } from 'common'
-import { handleError, post } from 'data/fetchers'
-import { IS_PLATFORM } from 'lib/constants'
-import { useRouter } from 'next/router'
-import type { ResponseError } from 'types'
-
-type SendPageLeaveBody = components['schemas']['TelemetryPageLeaveBody']
-
-export async function sendPageLeave({ body }: { body: SendPageLeaveBody }) {
- const consent =
- (typeof window !== 'undefined'
- ? localStorage.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT)
- : null) === 'true'
-
- if (!consent || !IS_PLATFORM) return undefined
-
- const { data, error } = await post(`/platform/telemetry/page-leave`, {
- body,
- credentials: 'include',
- })
- if (error) handleError(error)
- return data
-}
-
-type SendPageLeaveData = Awaited>
-
-export const useSendPageLeaveMutation = ({
- onSuccess,
- onError,
- ...options
-}: Omit, 'mutationFn'> = {}) => {
- const router = useRouter()
-
- const url = typeof window !== 'undefined' ? window.location.href : ''
- const title = typeof document !== 'undefined' ? document?.title : ''
-
- const body = {
- page_url: url,
- page_title: title,
- pathname: router.pathname,
- } as SendPageLeaveBody
-
- return useMutation((vars) => sendPageLeave({ body }), {
- async onSuccess(data, variables, context) {
- await onSuccess?.(data, variables, context)
- },
- async onError(data, variables, context) {
- if (onError === undefined) {
- console.error(`Failed to send Telemetry page leave: ${data.message}`)
- } else {
- onError(data, variables, context)
- }
- },
- ...options,
- })
-}
diff --git a/apps/studio/data/telemetry/send-page-mutation.ts b/apps/studio/data/telemetry/send-page-mutation.ts
deleted file mode 100644
index 498b83af048..00000000000
--- a/apps/studio/data/telemetry/send-page-mutation.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import { useMutation, UseMutationOptions } from '@tanstack/react-query'
-
-import { components } from 'api-types'
-import { isBrowser, LOCAL_STORAGE_KEYS } from 'common'
-import { handleError, post } from 'data/fetchers'
-import { IS_PLATFORM } from 'lib/constants'
-import { useRouter } from 'next/router'
-import type { ResponseError } from 'types'
-
-type SendPage = components['schemas']['TelemetryPageBodyV2']
-
-export type SendPageVariables = {
- url: string
-}
-
-type SendPagePayload = any
-
-export async function sendPage({ body }: { body: SendPagePayload }) {
- const consent =
- (typeof window !== 'undefined'
- ? localStorage.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT)
- : null) === 'true'
-
- if (!consent || !IS_PLATFORM) return undefined
-
- const headers = { Version: '2' }
- const { data, error } = await post(`/platform/telemetry/page`, { body, headers })
- if (error) handleError(error)
- return data
-}
-
-type SendPageData = Awaited>
-
-export const useSendPageMutation = ({
- onSuccess,
- onError,
- ...options
-}: Omit, 'mutationFn'> = {}) => {
- const router = useRouter()
-
- const title = typeof document !== 'undefined' ? document?.title : ''
- const referrer = typeof document !== 'undefined' ? document?.referrer : ''
-
- return useMutation(
- (vars) => {
- const { url } = vars
- const type = 'PH'
- const body: SendPage = {
- page_url: url,
- page_title: title,
- pathname: router.pathname,
- ph: {
- referrer,
- language: router?.locale ?? 'en-US',
- user_agent: navigator.userAgent,
- search: window.location.search,
- viewport_height: isBrowser ? window.innerHeight : 0,
- viewport_width: isBrowser ? window.innerWidth : 0,
- },
- }
- return sendPage({ body })
- },
- {
- async onSuccess(data, variables, context) {
- await onSuccess?.(data, variables, context)
- },
- async onError(data, variables, context) {
- if (onError === undefined) {
- console.error(`Failed to send Telemetry page: ${data.message}`)
- } else {
- onError(data, variables, context)
- }
- },
- ...options,
- }
- )
-}
diff --git a/apps/studio/hooks/ui/useFlag.ts b/apps/studio/hooks/ui/useFlag.ts
index 798013f0c99..1a8f85b6047 100644
--- a/apps/studio/hooks/ui/useFlag.ts
+++ b/apps/studio/hooks/ui/useFlag.ts
@@ -1,8 +1,6 @@
-import { useContext } from 'react'
import * as Sentry from '@sentry/nextjs'
-import FlagContext from 'components/ui/Flag/FlagContext'
-import { FlagProviderStore } from 'components/ui/Flag/FlagProvider'
+import { useFeatureFlags } from 'common'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
import { trackFeatureFlag } from 'lib/posthog'
@@ -13,7 +11,7 @@ const isObjectEmpty = (obj: Object) => {
export function useFlag(name: string) {
const project = useSelectedProject()
- const flagStore = useContext(FlagContext) as FlagProviderStore
+ const flagStore = useFeatureFlags()
const store = flagStore.configcat
@@ -29,8 +27,9 @@ export function useFlag(name: string) {
return store[name] as T
}
+// TODO(Alaister): move this to packages/common/feature-flags.tsx and rename to useFlag
export function usePHFlag(name: string) {
- const flagStore = useContext(FlagContext) as FlagProviderStore
+ const flagStore = useFeatureFlags()
// [Joshen] Prepend PH flags with "PH" in local storage for easier identification of PH flags
const [trackedValue, setTrackedValue] = useLocalStorageQuery(`ph_${name}`, '')
diff --git a/apps/studio/lib/configcat.ts b/apps/studio/lib/configcat.ts
index c566a75449a..6f609b50158 100644
--- a/apps/studio/lib/configcat.ts
+++ b/apps/studio/lib/configcat.ts
@@ -1,4 +1,3 @@
-import type { User } from '@supabase/supabase-js'
import * as configcat from 'configcat-js'
let client: configcat.IConfigCatClient
@@ -17,9 +16,10 @@ function getClient() {
return client
}
-export async function getFlags(user?: User) {
- if (user) {
- return getClient().getAllValuesAsync(new configcat.User(user?.email ?? ''))
+export async function getFlags(userEmail: string = '') {
+ if (userEmail) {
+ return getClient().getAllValuesAsync(new configcat.User(userEmail))
}
+
return []
}
diff --git a/apps/studio/lib/constants/index.ts b/apps/studio/lib/constants/index.ts
index 4ef94bbce92..bf29f79cefa 100644
--- a/apps/studio/lib/constants/index.ts
+++ b/apps/studio/lib/constants/index.ts
@@ -4,7 +4,7 @@ export * from './infrastructure'
export const IS_PLATFORM = process.env.NEXT_PUBLIC_IS_PLATFORM === 'true'
export const DEFAULT_HOME = IS_PLATFORM ? '/projects' : '/project/default'
-export const API_URL = IS_PLATFORM ? process.env.NEXT_PUBLIC_API_URL : '/api'
+export const API_URL = IS_PLATFORM ? process.env.NEXT_PUBLIC_API_URL! : '/api'
export const PG_META_URL = IS_PLATFORM
? process.env.PLATFORM_PG_META_URL
: process.env.STUDIO_PG_META_URL
diff --git a/apps/studio/pages/_app.tsx b/apps/studio/pages/_app.tsx
index 0fee2f145de..0575a04b0c0 100644
--- a/apps/studio/pages/_app.tsx
+++ b/apps/studio/pages/_app.tsx
@@ -22,14 +22,14 @@ import { TooltipProvider } from '@radix-ui/react-tooltip'
import * as Sentry from '@sentry/nextjs'
import { Hydrate, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
-import { ThemeProvider, useThemeSandbox } from 'common'
+import { FeatureFlagProvider, PageTelemetry, ThemeProvider, useThemeSandbox } from 'common'
import dayjs from 'dayjs'
import customParseFormat from 'dayjs/plugin/customParseFormat'
import relativeTime from 'dayjs/plugin/relativeTime'
import timezone from 'dayjs/plugin/timezone'
import utc from 'dayjs/plugin/utc'
import Head from 'next/head'
-import { ErrorInfo, useMemo, useState } from 'react'
+import { ErrorInfo } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import MetaFaviconsPagesRouter from 'common/MetaFavicons/pages-router'
@@ -40,15 +40,16 @@ import { FeaturePreviewContextProvider } from 'components/interfaces/App/Feature
import FeaturePreviewModal from 'components/interfaces/App/FeaturePreview/FeaturePreviewModal'
import { GenerateSql } from 'components/interfaces/SqlGenerator/SqlGenerator'
import { ErrorBoundaryState } from 'components/ui/ErrorBoundaryState'
-import FlagProvider from 'components/ui/Flag/FlagProvider'
-import PageTelemetry from 'components/ui/PageTelemetry'
+import GroupsTelemetry from 'components/ui/GroupsTelemetry'
import { useRootQueryClient } from 'data/query-client'
import { AuthProvider } from 'lib/auth'
-import { BASE_PATH, IS_PLATFORM } from 'lib/constants'
+import { getFlags as getConfigCatFlags } from 'lib/configcat'
+import { API_URL, BASE_PATH, IS_PLATFORM } from 'lib/constants'
import { ProfileProvider } from 'lib/profile'
import HCaptchaLoadedStore from 'stores/hcaptcha-loaded-store'
import { AppPropsWithLayout } from 'types'
import { SonnerToaster } from 'ui'
+import { useConsent } from 'ui-patterns'
import { CommandProvider } from 'ui-patterns/CommandMenu'
dayjs.extend(customParseFormat)
@@ -79,14 +80,6 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) {
const getLayout = Component.getLayout ?? ((page) => page)
- const TelemetryContainer = useMemo(
- // eslint-disable-next-line react/display-name
- () => (props: any) => {
- return IS_PLATFORM ? {props.children} : <>{props.children}>
- },
- []
- )
-
const errorBoundaryHandler = (error: Error, info: ErrorInfo) => {
Sentry.withScope(function (scope) {
scope.setTag('globalErrorBoundary', true)
@@ -98,6 +91,11 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) {
useThemeSandbox()
+ // Although this is "technically" breaking the rules of hooks
+ // IS_PLATFORM never changes within a session, so this won't cause any issues
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const { hasAcceptedConsent } = IS_PLATFORM ? useConsent() : { hasAcceptedConsent: true }
+
const isTestEnv = process.env.NEXT_PUBLIC_NODE_ENV === 'test'
return (
@@ -105,44 +103,52 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) {
-
+
Supabase
-
-
-
-
-
-
-
-
- {getLayout()}
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ {getLayout()}
+
+
+
+
+
+
+
+
+
+
+
+
+
{!isTestEnv && }
{!isTestEnv && }
-
+
diff --git a/apps/studio/pages/api/projects/[ref]/content/count.ts b/apps/studio/pages/api/projects/[ref]/content/count.ts
index 2cc917b30f5..568bfd57a06 100644
--- a/apps/studio/pages/api/projects/[ref]/content/count.ts
+++ b/apps/studio/pages/api/projects/[ref]/content/count.ts
@@ -19,5 +19,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
type ResponseData = extractResponse<'/platform/projects/{ref}/content/count', 'get'>
const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => {
- return res.status(200).json({ count: 1 })
+ return res.status(200).json({
+ favorites: 0,
+ private: 0,
+ shared: 0,
+ })
}
diff --git a/apps/studio/tests/pages/projects/LogsPreviewer.test.tsx b/apps/studio/tests/pages/projects/LogsPreviewer.test.tsx
index 88303b214a2..beb0d7e66f3 100644
--- a/apps/studio/tests/pages/projects/LogsPreviewer.test.tsx
+++ b/apps/studio/tests/pages/projects/LogsPreviewer.test.tsx
@@ -16,6 +16,7 @@ import { render } from '../../helpers'
vi.mock('common', () => ({
useParams: vi.fn().mockReturnValue({}),
useIsLoggedIn: vi.fn(),
+ isBrowser: false,
}))
vi.mock('lib/gotrue', () => ({
auth: { onAuthStateChange: vi.fn() },
diff --git a/apps/www/lib/constants.ts b/apps/www/lib/constants.ts
index 9d3e63f6e6e..008cd6e24b6 100644
--- a/apps/www/lib/constants.ts
+++ b/apps/www/lib/constants.ts
@@ -3,7 +3,7 @@ export const DEFAULT_META_DESCRIPTION =
'Build production-grade applications with a Postgres database, Authentication, instant APIs, Realtime, Functions, Storage and Vector embeddings. Start for free.'
export const IS_PROD = process.env.NEXT_PUBLIC_VERCEL_ENV === 'production'
export const IS_PREVIEW = process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview'
-export const API_URL = process.env.NEXT_PUBLIC_API_URL
+export const API_URL = process.env.NEXT_PUBLIC_API_URL!
// Products
diff --git a/apps/www/pages/_app.tsx b/apps/www/pages/_app.tsx
index 23ce6964d85..5e10bd6f589 100644
--- a/apps/www/pages/_app.tsx
+++ b/apps/www/pages/_app.tsx
@@ -4,17 +4,15 @@ import '../styles/index.css'
import {
AuthProvider,
- IS_PROD,
+ FeatureFlagProvider,
+ PageTelemetry,
ThemeProvider,
- useTelemetryCookie,
- useTelemetryProps,
useThemeSandbox,
} from 'common'
import { DefaultSeo } from 'next-seo'
import { AppProps } from 'next/app'
import Head from 'next/head'
import { useRouter } from 'next/router'
-import { useEffect } from 'react'
import { SonnerToaster, themes } from 'ui'
import { CommandProvider } from 'ui-patterns/CommandMenu'
import { useConsent } from 'ui-patterns/ConsentToast'
@@ -24,82 +22,15 @@ import MetaFaviconsPagesRouter, {
DEFAULT_FAVICON_THEME_COLOR,
} from 'common/MetaFavicons/pages-router'
import { WwwCommandMenu } from '~/components/CommandMenu'
-import { API_URL, APP_NAME, DEFAULT_META_DESCRIPTION, IS_PREVIEW } from '~/lib/constants'
-import { post } from '~/lib/fetchWrapper'
+import { API_URL, APP_NAME, DEFAULT_META_DESCRIPTION } from '~/lib/constants'
import useDarkLaunchWeeks from '../hooks/useDarkLaunchWeeks'
export default function App({ Component, pageProps }: AppProps) {
const router = useRouter()
- const telemetryProps = useTelemetryProps()
const { hasAcceptedConsent } = useConsent()
- const IS_DEV = !IS_PROD && !IS_PREVIEW
- const blockEvents = IS_DEV || !hasAcceptedConsent
-
- const title = typeof document !== 'undefined' ? document?.title : ''
- const referrer = typeof document !== 'undefined' ? document?.referrer : ''
-
- const { search, language, viewport_height, viewport_width } = telemetryProps
useThemeSandbox()
- useTelemetryCookie({ hasAcceptedConsent, title, referrer })
-
- function handlePageTelemetry(url: string) {
- return post(
- `${API_URL}/telemetry/page`,
- {
- page_url: url,
- page_title: title,
- pathname: router.pathname,
- ph: {
- referrer,
- language,
- search,
- viewport_height,
- viewport_width,
- user_agent: navigator.userAgent,
- },
- },
- { headers: { Version: '2' }, credentials: 'include' }
- )
- }
-
- useEffect(() => {
- if (blockEvents) return
-
- function handleRouteChange() {
- handlePageTelemetry(window.location.href)
- }
-
- // Listen for page changes after a navigation or when the query changes
- router.events.on('routeChangeComplete', handleRouteChange)
- return () => {
- router.events.off('routeChangeComplete', handleRouteChange)
- }
- }, [router.events, blockEvents])
-
- useEffect(() => {
- if (!router.isReady) return
- if (blockEvents) return
- handlePageTelemetry(window.location.href)
- }, [router.isReady, blockEvents])
-
- useEffect(() => {
- const handleBeforeUnload = async () => {
- if (!blockEvents) {
- await post(`${API_URL}/telemetry/page-leave`, {
- page_url: window.location.href,
- page_title: title,
- pathname: router.pathname,
- })
- }
- }
- window.addEventListener('beforeunload', handleBeforeUnload)
- return () => {
- window.removeEventListener('beforeunload', handleBeforeUnload)
- }
- }, [blockEvents, router.pathname, title])
-
const site_title = `${APP_NAME} | The Open Source Firebase Alternative`
const { basePath } = useRouter()
@@ -153,18 +84,21 @@ export default function App({ Component, pageProps }: AppProps) {
/>
- theme.value)}
- enableSystem
- disableTransitionOnChange
- forcedTheme={forceDarkMode ? 'dark' : undefined}
- >
-
-
-
-
-
-
+
+ theme.value)}
+ enableSystem
+ disableTransitionOnChange
+ forcedTheme={forceDarkMode ? 'dark' : undefined}
+ >
+
+
+
+
+
+
+
+
>
)
diff --git a/packages/api-types/types/api.d.ts b/packages/api-types/types/api.d.ts
index 47efd7a6e4b..7326b710f31 100644
--- a/packages/api-types/types/api.d.ts
+++ b/packages/api-types/types/api.d.ts
@@ -873,8 +873,8 @@ export interface paths {
patch: operations['ContentController_updateContent']
}
'/platform/projects/{ref}/content/count': {
- /** Gets the count of a user's content */
- get: operations['ContentController_getContentCount']
+ /** Gets the user's content counts */
+ get: operations['ContentController_getContentCountV2']
}
'/platform/projects/{ref}/content/folders': {
/** Gets project's content root folder */
@@ -1144,7 +1144,7 @@ export interface paths {
}
'/platform/telemetry/page-leave': {
/** Send analytics page leave event */
- post: operations['TelemetryPageLeaveController_pageLeave']
+ post: operations['TelemetryPageLeaveController_trackPageLeave']
}
'/platform/telemetry/reset': {
/** Reset analytics */
@@ -1879,8 +1879,8 @@ export interface paths {
patch: operations['ContentController_updateContent']
}
'/v0/projects/{ref}/content/count': {
- /** Gets the count of a user's content */
- get: operations['ContentController_getContentCount']
+ /** Gets the user's content counts */
+ get: operations['ContentController_getContentCountV2']
}
'/v0/projects/{ref}/content/item/{id}': {
/** Gets project's content by the given id */
@@ -2127,6 +2127,10 @@ export interface paths {
/** Deletes the given project */
delete: operations['v1-delete-a-project']
}
+ '/v1/projects/{ref}/analytics/endpoints/logs.all': {
+ /** Gets project's logs */
+ get: operations['V1ProjectLogsController_getLogs']
+ }
'/v1/projects/{ref}/api-keys': {
/** Get project api keys */
get: operations['v1-get-project-api-keys']
@@ -2334,6 +2338,16 @@ export interface paths {
/** Disables project's readonly mode for the next 15 minutes */
post: operations['v1-disable-readonly-mode-temporarily']
}
+ '/v1/projects/{ref}/restore': {
+ /** Lists available restore versions for the given project */
+ get: operations['v1-list-available-restore-versions']
+ /** Restores the given project */
+ post: operations['v1-restore-a-project']
+ }
+ '/v1/projects/{ref}/restore/cancel': {
+ /** Cancels the given project restoration */
+ post: operations['v1-cancel-a-project-restoration']
+ }
'/v1/projects/{ref}/secrets': {
/**
* List all secrets
@@ -2784,19 +2798,19 @@ export interface components {
release_channel: string
/** @enum {string} */
status:
+ | 'INACTIVE'
| 'ACTIVE_HEALTHY'
| 'ACTIVE_UNHEALTHY'
| 'COMING_UP'
+ | 'UNKNOWN'
| 'GOING_DOWN'
- | 'INACTIVE'
| 'INIT_FAILED'
| 'REMOVED'
- | 'RESTARTING'
- | 'UNKNOWN'
+ | 'RESTORING'
| 'UPGRADING'
| 'PAUSING'
- | 'RESTORING'
| 'RESTORE_FAILED'
+ | 'RESTARTING'
| 'PAUSE_FAILED'
| 'RESIZING'
}
@@ -2840,6 +2854,19 @@ export interface components {
result: components['schemas']['CustomHostnameDetails']
success: boolean
}
+ CloneBackupsResponse: {
+ backups: components['schemas']['Backup'][]
+ physicalBackupData: {
+ earliestPhysicalBackupDateUnix?: number
+ latestPhysicalBackupDateUnix?: number
+ }
+ pitr_enabled: boolean
+ region: string
+ target_compute_size: string
+ target_volume_size_gb: number
+ tierKey: string
+ walg_enabled: boolean
+ }
CloneProjectDto: {
/** @default 0 */
cloneBackupId?: number
@@ -3311,6 +3338,22 @@ export interface components {
CustomSupabaseInternalRequests: {
ami: components['schemas']['AmiSearchOptions']
}
+ /** @enum {string} */
+ database_status:
+ | 'ACTIVE_HEALTHY'
+ | 'ACTIVE_UNHEALTHY'
+ | 'COMING_UP'
+ | 'GOING_DOWN'
+ | 'INIT_FAILED'
+ | 'REMOVED'
+ | 'RESTORING'
+ | 'UNKNOWN'
+ | 'INIT_READ_REPLICA'
+ | 'INIT_READ_REPLICA_FAILED'
+ | 'RESTARTING'
+ | 'RESIZING'
+ /** @enum {string} */
+ database_type: 'PRIMARY' | 'READ_REPLICA'
DatabaseDetailResponse: {
/** @enum {string} */
cloud_provider: 'AWS' | 'FLY'
@@ -3334,27 +3377,11 @@ export interface components {
| 'REMOVED'
| 'RESTORING'
| 'UNKNOWN'
- | 'UPGRADING'
| 'INIT_READ_REPLICA'
| 'INIT_READ_REPLICA_FAILED'
| 'RESTARTING'
| 'RESIZING'
}
- /** @enum {string} */
- DatabaseStatus:
- | 'ACTIVE_HEALTHY'
- | 'ACTIVE_UNHEALTHY'
- | 'COMING_UP'
- | 'GOING_DOWN'
- | 'INIT_FAILED'
- | 'REMOVED'
- | 'RESTORING'
- | 'UNKNOWN'
- | 'UPGRADING'
- | 'INIT_READ_REPLICA'
- | 'INIT_READ_REPLICA_FAILED'
- | 'RESTARTING'
- | 'RESIZING'
DatabaseStatusResponse: {
identifier: string
replicaInitializationStatus?: Record
@@ -3368,14 +3395,11 @@ export interface components {
| 'REMOVED'
| 'RESTORING'
| 'UNKNOWN'
- | 'UPGRADING'
| 'INIT_READ_REPLICA'
| 'INIT_READ_REPLICA_FAILED'
| 'RESTARTING'
| 'RESIZING'
}
- /** @enum {string} */
- DatabaseType: 'PRIMARY' | 'READ_REPLICA'
DatabaseUpgradeStatus: {
/** @enum {string} */
error?:
@@ -3661,6 +3685,11 @@ export interface components {
GetContentCountResponse: {
count: number
}
+ GetContentCountV2Response: {
+ favorites: number
+ private: number
+ shared: number
+ }
GetMetricsBody: {
/** @enum {string} */
interval: '1d' | '3d' | '7d'
@@ -3729,6 +3758,9 @@ export interface components {
}
updated_at: string
}
+ GetProjectAvailableRestoreVersionsResponse: {
+ available_versions: components['schemas']['ProjectAvailableRestoreVersion'][]
+ }
GetProjectByFlyExtensionIdResponse: {
ref: string
}
@@ -4387,19 +4419,19 @@ export interface components {
* @enum {string}
*/
status:
+ | 'INACTIVE'
| 'ACTIVE_HEALTHY'
| 'ACTIVE_UNHEALTHY'
| 'COMING_UP'
+ | 'UNKNOWN'
| 'GOING_DOWN'
- | 'INACTIVE'
| 'INIT_FAILED'
| 'REMOVED'
- | 'RESTARTING'
- | 'UNKNOWN'
+ | 'RESTORING'
| 'UPGRADING'
| 'PAUSING'
- | 'RESTORING'
| 'RESTORE_FAILED'
+ | 'RESTARTING'
| 'PAUSE_FAILED'
| 'RESIZING'
/**
@@ -4483,7 +4515,7 @@ export interface components {
}
OrganizationSlugResponse: {
billing_email: string | null
- billing_metadata?: Record
+ billing_metadata: unknown
has_oriole_project: boolean
id: number
name: string
@@ -4900,6 +4932,23 @@ export interface components {
username: string
}
/** @enum {string} */
+ project_status:
+ | 'INACTIVE'
+ | 'ACTIVE_HEALTHY'
+ | 'ACTIVE_UNHEALTHY'
+ | 'COMING_UP'
+ | 'UNKNOWN'
+ | 'GOING_DOWN'
+ | 'INIT_FAILED'
+ | 'REMOVED'
+ | 'RESTORING'
+ | 'UPGRADING'
+ | 'PAUSING'
+ | 'RESTORE_FAILED'
+ | 'RESTARTING'
+ | 'PAUSE_FAILED'
+ | 'RESIZING'
+ /** @enum {string} */
ProjectAddonPricingInterval: 'monthly' | 'hourly'
ProjectAddonsResponse: {
available_addons: components['schemas']['AvailableAddonResponse'][]
@@ -4936,8 +4985,17 @@ export interface components {
db_schema: string
endpoint: string
}
+ ProjectAvailableRestoreVersion: {
+ /** @enum {string} */
+ postgres_engine: '13' | '14' | '15' | '17-oriole'
+ /** @enum {string} */
+ release_channel: 'internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn' | 'preview'
+ version: string
+ }
ProjectClonedResponse: {
source_project_ref: string
+ target_disk_size_gb: number
+ target_instance_size: string
target_project_ref: string
}
ProjectClonedStatusResponse: {
@@ -4961,8 +5019,8 @@ export interface components {
identifier: string
infra_compute_size?: components['schemas']['DbInstanceSize']
region: string
- status: components['schemas']['DatabaseStatus']
- type: components['schemas']['DatabaseType']
+ status: components['schemas']['database_status']
+ type: components['schemas']['database_type']
}
ProjectDetailResponse: {
cloud_provider: string
@@ -4984,19 +5042,19 @@ export interface components {
restUrl: string
/** @enum {string} */
status:
+ | 'INACTIVE'
| 'ACTIVE_HEALTHY'
| 'ACTIVE_UNHEALTHY'
| 'COMING_UP'
+ | 'UNKNOWN'
| 'GOING_DOWN'
- | 'INACTIVE'
| 'INIT_FAILED'
| 'REMOVED'
- | 'RESTARTING'
- | 'UNKNOWN'
+ | 'RESTORING'
| 'UPGRADING'
| 'PAUSING'
- | 'RESTORING'
| 'RESTORE_FAILED'
+ | 'RESTARTING'
| 'PAUSE_FAILED'
| 'RESIZING'
subscription_id: string
@@ -5150,26 +5208,11 @@ export interface components {
ssl_enforced: boolean
status: string
}
- /** @enum {string} */
- ProjectStatus:
- | 'ACTIVE_HEALTHY'
- | 'ACTIVE_UNHEALTHY'
- | 'COMING_UP'
- | 'GOING_DOWN'
- | 'INACTIVE'
- | 'INIT_FAILED'
- | 'REMOVED'
- | 'RESTARTING'
- | 'UNKNOWN'
- | 'UPGRADING'
- | 'PAUSING'
- | 'RESTORING'
- | 'RESTORE_FAILED'
- | 'PAUSE_FAILED'
- | 'RESIZING'
ProjectUnpauseVersionInfo: {
- postgres_engine: components['schemas']['PostgresEngine']
- release_channel: components['schemas']['ReleaseChannel']
+ /** @enum {string} */
+ postgres_engine: '13' | '14' | '15' | '17-oriole'
+ /** @enum {string} */
+ release_channel: 'internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn' | 'preview'
version: string
}
ProjectUpgradeEligibilityResponse: {
@@ -5197,7 +5240,7 @@ export interface components {
name: string
ref: string
region: string
- status: components['schemas']['ProjectStatus']
+ status: components['schemas']['project_status']
}
Provider: {
created_at?: string
@@ -5263,7 +5306,6 @@ export interface components {
| 'REMOVED'
| 'RESTORING'
| 'UNKNOWN'
- | 'UPGRADING'
| 'INIT_READ_REPLICA'
| 'INIT_READ_REPLICA_FAILED'
| 'RESTARTING'
@@ -5337,19 +5379,19 @@ export interface components {
* @enum {string}
*/
status:
+ | 'INACTIVE'
| 'ACTIVE_HEALTHY'
| 'ACTIVE_UNHEALTHY'
| 'COMING_UP'
+ | 'UNKNOWN'
| 'GOING_DOWN'
- | 'INACTIVE'
| 'INIT_FAILED'
| 'REMOVED'
- | 'RESTARTING'
- | 'UNKNOWN'
+ | 'RESTORING'
| 'UPGRADING'
| 'PAUSING'
- | 'RESTORING'
| 'RESTORE_FAILED'
+ | 'RESTARTING'
| 'PAUSE_FAILED'
| 'RESIZING'
/**
@@ -5393,6 +5435,18 @@ export interface components {
id: number
recovery_time_target: string
}
+ RestoreProjectBodyDto: {
+ /**
+ * @description Postgres engine version. If not provided, the latest version from the given release channel will be used.
+ * @enum {string}
+ */
+ postgres_engine?: '15' | '17-oriole'
+ /**
+ * @description Release channel version. If not provided, GeneralAvailability will be used.
+ * @enum {string}
+ */
+ release_channel?: 'internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn' | 'preview'
+ }
RestrictionData: {
grace_period_end?: string
report_date?: string
@@ -5631,7 +5685,7 @@ export interface components {
data: components['schemas']['SnippetMeta'][]
}
SnippetMeta: {
- description?: string
+ description: string | null
id: string
inserted_at: string
name: string
@@ -5651,7 +5705,7 @@ export interface components {
}
SnippetResponse: {
content: components['schemas']['SnippetContent']
- description?: string
+ description: string | null
id: string
inserted_at: string
name: string
@@ -5855,21 +5909,22 @@ export interface components {
TelemetryCallFeatureFlagsResponseDto: {
[key: string]: unknown
}
- TelemetryEventBodyV2: {
+ TelemetryEventBodyV2Dto: {
action: string
- custom_properties: Record
+ custom_properties: {
+ [key: string]: unknown
+ }
page_title: string
page_url: string
pathname: string
- ph: components['schemas']['TelemetryEventPostHog']
- }
- TelemetryEventPostHog: {
- language: string
- referrer: string
- search: string
- user_agent: string
- viewport_height: number
- viewport_width: number
+ ph: {
+ language: string
+ referrer: string
+ search: string
+ user_agent: string
+ viewport_height: number
+ viewport_width: number
+ }
}
TelemetryFeatureFlagBodyDto: {
feature_flag_name: string
@@ -5888,25 +5943,30 @@ export interface components {
project_ref?: string
user_id: string
}
- TelemetryPageBodyV2: {
+ TelemetryPageBodyV2Dto: {
+ feature_flags?: {
+ [key: string]: unknown
+ }
page_title: string
page_url: string
pathname: string
- ph: components['schemas']['TelemetryPagePostHog']
+ ph: {
+ language: string
+ referrer: string
+ search: string
+ user_agent: string
+ viewport_height: number
+ viewport_width: number
+ }
}
- TelemetryPageLeaveBody: {
+ TelemetryPageLeaveBodyDto: {
+ feature_flags?: {
+ [key: string]: unknown
+ }
page_title: string
page_url: string
pathname: string
}
- TelemetryPagePostHog: {
- language: string
- referrer: string
- search: string
- user_agent: string
- viewport_height: number
- viewport_width: number
- }
ThirdPartyAuth: {
custom_jwks?: unknown
id: string
@@ -5932,11 +5992,19 @@ export interface components {
UnpauseProjectAvailableVersionsResponse: {
available_versions: components['schemas']['ProjectUnpauseVersionInfo'][]
}
- UnpauseProjectBody: {
- postgres_engine?: components['schemas']['PostgresEngine']
- release_channel?: components['schemas']['ReleaseChannel']
+ UnpauseProjectBodyDto: {
+ /**
+ * @description Postgres engine version. If not provided, the latest version from the given release channel will be used.
+ * @enum {string}
+ */
+ postgres_engine?: '15' | '17-oriole'
+ /**
+ * @description Release channel version. If not provided, GeneralAvailability will be used.
+ * @enum {string}
+ */
+ release_channel?: 'internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn' | 'preview'
}
- UnpauseProjectInfo: {
+ UnpauseProjectResponse: {
back_ups: components['schemas']['BackupId'][]
cloud_provider: string
id: number
@@ -6710,7 +6778,8 @@ export interface components {
updated_at?: string
}
UpdateVercelConnectionsBody: {
- env_sync_targets?: ('production' | 'preview' | 'development')[]
+ /** @enum {array} */
+ env_sync_targets?: production | preview | development
public_env_var_prefix?: string
}
UpgradeDatabaseBody: {
@@ -6822,6 +6891,26 @@ export interface components {
total: number
users: components['schemas']['UserBody'][]
}
+ V1AnalyticsResponse: {
+ error?: OneOf<
+ [
+ {
+ code?: number
+ errors?: {
+ domain?: string
+ location?: string
+ locationType?: string
+ message?: string
+ reason?: string
+ }[]
+ message?: string
+ status?: string
+ },
+ string,
+ ]
+ >
+ result?: Record[]
+ }
V1Backup: {
inserted_at: string
is_physical_backup: boolean
@@ -6982,19 +7071,19 @@ export interface components {
region: string
/** @enum {string} */
status:
+ | 'INACTIVE'
| 'ACTIVE_HEALTHY'
| 'ACTIVE_UNHEALTHY'
| 'COMING_UP'
+ | 'UNKNOWN'
| 'GOING_DOWN'
- | 'INACTIVE'
| 'INIT_FAILED'
| 'REMOVED'
- | 'RESTARTING'
- | 'UNKNOWN'
+ | 'RESTORING'
| 'UPGRADING'
| 'PAUSING'
- | 'RESTORING'
| 'RESTORE_FAILED'
+ | 'RESTARTING'
| 'PAUSE_FAILED'
| 'RESIZING'
}
@@ -7018,19 +7107,19 @@ export interface components {
region: string
/** @enum {string} */
status:
+ | 'INACTIVE'
| 'ACTIVE_HEALTHY'
| 'ACTIVE_UNHEALTHY'
| 'COMING_UP'
+ | 'UNKNOWN'
| 'GOING_DOWN'
- | 'INACTIVE'
| 'INIT_FAILED'
| 'REMOVED'
- | 'RESTARTING'
- | 'UNKNOWN'
+ | 'RESTORING'
| 'UPGRADING'
| 'PAUSING'
- | 'RESTORING'
| 'RESTORE_FAILED'
+ | 'RESTARTING'
| 'PAUSE_FAILED'
| 'RESIZING'
}
@@ -8019,7 +8108,7 @@ export interface operations {
responses: {
200: {
content: {
- 'application/json': components['schemas']['BackupsResponse']
+ 'application/json': components['schemas']['CloneBackupsResponse']
}
}
/** @description Failed to list available valid backups */
@@ -11850,16 +11939,12 @@ export interface operations {
/** Gets project's logs */
LogsController_getApiPaths: {
parameters: {
- query: {
- sql: string
- project: string
- iso_timestamp_start: string
- iso_timestamp_end: string
- timestamp_start: string
- timestamp_end: string
+ query?: {
+ iso_timestamp_end?: string
+ iso_timestamp_start?: string
+ sql?: string
}
path: {
- /** @description Project ref */
ref: string
}
}
@@ -12874,13 +12959,11 @@ export interface operations {
}
}
}
- /** Gets the count of a user's content */
- ContentController_getContentCount: {
+ /** Gets the user's content counts */
+ ContentController_getContentCountV2: {
parameters: {
query?: {
- visibility?: string
type?: 'sql' | 'report' | 'log_sql'
- favorite?: boolean
name?: string
}
path: {
@@ -12890,10 +12973,10 @@ export interface operations {
responses: {
200: {
content: {
- 'application/json': components['schemas']['GetContentCountResponse']
+ 'application/json': components['schemas']['GetContentCountV2Response']
}
}
- /** @description Failed to retrieve user's content count */
+ /** @description Failed to retrieve user's content counts */
500: {
content: never
}
@@ -13479,19 +13562,18 @@ export interface operations {
UnpauseController_unpauseProject: {
parameters: {
path: {
- /** @description Project ref */
ref: string
}
}
requestBody: {
content: {
- 'application/json': components['schemas']['UnpauseProjectBody']
+ 'application/json': components['schemas']['UnpauseProjectBodyDto']
}
}
responses: {
- 201: {
+ 200: {
content: {
- 'application/json': components['schemas']['UnpauseProjectInfo']
+ 'application/json': components['schemas']['UnpauseProjectResponse']
}
}
403: {
@@ -13507,12 +13589,11 @@ export interface operations {
UnpauseController_cancelProjectRestoration: {
parameters: {
path: {
- /** @description Project ref */
ref: string
}
}
responses: {
- 201: {
+ 200: {
content: {
'application/json': components['schemas']['RestoreCancellation']
}
@@ -13530,7 +13611,6 @@ export interface operations {
UnpauseController_getAvailableImageVersions: {
parameters: {
path: {
- /** @description Project ref */
ref: string
}
}
@@ -14374,7 +14454,7 @@ export interface operations {
TelemetryEventController_sendServerEventV2: {
requestBody: {
content: {
- 'application/json': components['schemas']['TelemetryEventBodyV2']
+ 'application/json': components['schemas']['TelemetryEventBodyV2Dto']
}
}
responses: {
@@ -14474,7 +14554,7 @@ export interface operations {
TelemetryPageController_sendServerPageV2: {
requestBody: {
content: {
- 'application/json': components['schemas']['TelemetryPageBodyV2']
+ 'application/json': components['schemas']['TelemetryPageBodyV2Dto']
}
}
responses: {
@@ -14488,13 +14568,17 @@ export interface operations {
}
}
/** Send analytics page leave event */
- TelemetryPageLeaveController_pageLeave: {
+ TelemetryPageLeaveController_trackPageLeave: {
requestBody: {
content: {
- 'application/json': components['schemas']['TelemetryPageLeaveBody']
+ 'application/json': components['schemas']['TelemetryPageLeaveBodyDto']
}
}
responses: {
+ /** @description Page leave event sent */
+ 200: {
+ content: never
+ }
201: {
content: never
}
@@ -16079,6 +16163,29 @@ export interface operations {
}
}
}
+ /** Gets project's logs */
+ V1ProjectLogsController_getLogs: {
+ parameters: {
+ query?: {
+ iso_timestamp_end?: string
+ iso_timestamp_start?: string
+ sql?: string
+ }
+ path: {
+ ref: string
+ }
+ }
+ responses: {
+ 200: {
+ content: {
+ 'application/json': components['schemas']['V1AnalyticsResponse']
+ }
+ }
+ 403: {
+ content: never
+ }
+ }
+ }
/** Get project api keys */
'v1-get-project-api-keys': {
parameters: {
@@ -17189,6 +17296,61 @@ export interface operations {
}
}
}
+ /** Lists available restore versions for the given project */
+ 'v1-list-available-restore-versions': {
+ parameters: {
+ path: {
+ ref: string
+ }
+ }
+ responses: {
+ 200: {
+ content: {
+ 'application/json': components['schemas']['GetProjectAvailableRestoreVersionsResponse']
+ }
+ }
+ 403: {
+ content: never
+ }
+ }
+ }
+ /** Restores the given project */
+ 'v1-restore-a-project': {
+ parameters: {
+ path: {
+ ref: string
+ }
+ }
+ requestBody: {
+ content: {
+ 'application/json': components['schemas']['RestoreProjectBodyDto']
+ }
+ }
+ responses: {
+ 200: {
+ content: never
+ }
+ 403: {
+ content: never
+ }
+ }
+ }
+ /** Cancels the given project restoration */
+ 'v1-cancel-a-project-restoration': {
+ parameters: {
+ path: {
+ ref: string
+ }
+ }
+ responses: {
+ 200: {
+ content: never
+ }
+ 403: {
+ content: never
+ }
+ }
+ }
/** [Beta] Get project's SSL enforcement configuration. */
'v1-get-ssl-enforcement-config': {
parameters: {
diff --git a/packages/common/feature-flags.tsx b/packages/common/feature-flags.tsx
new file mode 100644
index 00000000000..a715439a88b
--- /dev/null
+++ b/packages/common/feature-flags.tsx
@@ -0,0 +1,138 @@
+import { FlagValues } from '@vercel/flags/react'
+import { createContext, PropsWithChildren, useContext, useEffect, useState } from 'react'
+
+import { components } from 'api-types'
+import { useUser } from './auth'
+import { LOCAL_STORAGE_KEYS } from './constants'
+import { get, post } from './fetchWrappers'
+import { ensurePlatformSuffix } from './helpers'
+
+type TrackFeatureFlagVariables = components['schemas']['TelemetryFeatureFlagBodyDto']
+export type CallFeatureFlagsResponse = components['schemas']['TelemetryCallFeatureFlagsResponseDto']
+
+export async function getFeatureFlags(API_URL: string) {
+ const data = await get(`${ensurePlatformSuffix(API_URL)}/telemetry/feature-flags`)
+
+ return data as CallFeatureFlagsResponse
+}
+
+export async function trackFeatureFlag(API_URL: string, body: TrackFeatureFlagVariables) {
+ const consent =
+ (typeof window !== 'undefined'
+ ? localStorage.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT)
+ : null) === 'true'
+
+ if (!consent) return undefined
+ await post(`${ensurePlatformSuffix(API_URL)}/telemetry/feature-flags/track`, { body })
+}
+
+export type FeatureFlagContextType = {
+ API_URL?: string
+ configcat: { [key: string]: boolean | number | string | null }
+ posthog: CallFeatureFlagsResponse
+ hasLoaded?: boolean
+}
+
+const FeatureFlagContext = createContext({
+ API_URL: undefined,
+ configcat: {},
+ posthog: {},
+ hasLoaded: false,
+})
+
+function getCookies() {
+ const pairs = document.cookie.split(';')
+ let cookies: Record = {}
+ for (var i = 0; i < pairs.length; i++) {
+ var [t_key, value] = pairs[i].split('=')
+ const key = t_key.trim()
+
+ cookies[key] = unescape(value)
+ }
+ return cookies
+}
+
+export const FeatureFlagProvider = ({
+ API_URL,
+ enabled = true,
+ getConfigCatFlags,
+ children,
+}: PropsWithChildren<{
+ API_URL: string
+ enabled?: boolean
+ getConfigCatFlags?: (
+ userEmail?: string
+ ) => Promise<{ settingKey: string; settingValue: boolean | number | string | null | undefined }[]>
+}>) => {
+ const user = useUser()
+
+ const [store, setStore] = useState({
+ API_URL,
+ configcat: {},
+ posthog: {},
+ hasLoaded: false,
+ })
+
+ useEffect(() => {
+ let mounted = true
+
+ async function processFlags() {
+ if (!enabled) return
+
+ let flagStore: FeatureFlagContextType = { configcat: {}, posthog: {} }
+
+ // Load PH flags
+ const flags = await getFeatureFlags(API_URL)
+ if (flags) {
+ flagStore.posthog = flags
+ }
+
+ // Load ConfigCat flags
+ if (typeof getConfigCatFlags === 'function') {
+ const flagValues = await getConfigCatFlags(user?.email)
+ let overridesCookieValue: Record = {}
+ try {
+ const cookies = getCookies()
+ overridesCookieValue = JSON.parse(cookies['vercel-flag-overrides'])
+ } catch {}
+
+ flagValues.forEach((item) => {
+ flagStore['configcat'][item.settingKey] =
+ overridesCookieValue[item.settingKey] ??
+ (item.settingValue === null ? null : item.settingValue ?? false)
+ })
+ }
+
+ flagStore.hasLoaded = true
+
+ if (mounted) {
+ setStore(flagStore)
+ }
+ }
+
+ // [Joshen] getFlags get triggered everytime the tab refocuses but this should be okay
+ // as per https://configcat.com/docs/sdk-reference/js/#polling-modes:
+ // The polling downloads the config.json at the set interval and are stored in the internal cache
+ // which subsequently all getValueAsync() calls are served from there
+ processFlags()
+
+ return () => {
+ mounted = false
+ }
+ }, [enabled, user?.email])
+
+ return (
+
+ {/*
+ [Joshen] Just support configcat flags in Vercel flags for now for simplicity
+ although I think it should be fairly simply to support PH too
+ */}
+
+ {children}
+
+ )
+}
+
+export const useFeatureFlags = () => {
+ return useContext(FeatureFlagContext)
+}
diff --git a/packages/common/fetchWrappers.tsx b/packages/common/fetchWrappers.tsx
index 4afdf9a9acc..3a02252edd7 100644
--- a/packages/common/fetchWrappers.tsx
+++ b/packages/common/fetchWrappers.tsx
@@ -1,13 +1,43 @@
+import { getAccessToken } from './auth'
+
interface DataProps {
[prop: string]: any
}
-export const post = (url: string, data: DataProps, options = {} as { [key: string]: any }) => {
+export async function get(url: string, options = {} as { [key: string]: any }) {
const { headers: optionHeaders, ...otherOptions } = options
+ const accessToken = await getAccessToken()
+
let headers = new Headers({
'Content-Type': 'application/json',
Accept: 'application/json',
+ ...(accessToken && { Authorization: `Bearer ${accessToken}` }),
+ ...optionHeaders,
+ })
+
+ return fetch(url, {
+ method: 'GET',
+ headers,
+ credentials: 'include',
+ referrerPolicy: 'no-referrer-when-downgrade',
+ ...otherOptions,
+ })
+ .then((res) => res.json())
+ .catch((error) => {
+ throw error
+ })
+}
+
+export async function post(url: string, data: DataProps, options = {} as { [key: string]: any }) {
+ const { headers: optionHeaders, ...otherOptions } = options
+
+ const accessToken = await getAccessToken()
+
+ let headers = new Headers({
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ ...(accessToken && { Authorization: `Bearer ${accessToken}` }),
...optionHeaders,
})
@@ -19,6 +49,6 @@ export const post = (url: string, data: DataProps, options = {} as { [key: strin
body: JSON.stringify(data),
...otherOptions,
}).catch((error) => {
- console.error('Error at fetchWrapper - post:', error)
+ throw error
})
}
diff --git a/packages/common/helpers.ts b/packages/common/helpers.ts
index 981ed7c3acf..4511d81f718 100644
--- a/packages/common/helpers.ts
+++ b/packages/common/helpers.ts
@@ -33,3 +33,7 @@ export const useReducedMotion = (): boolean => {
() => false
)
}
+
+export function ensurePlatformSuffix(apiUrl: string) {
+ return apiUrl.endsWith('/platform') ? apiUrl : `${apiUrl}/platform`
+}
diff --git a/packages/common/hooks/useTelemetryProps.ts b/packages/common/hooks/useTelemetryProps.ts
index d70b3d76ac0..6218986da72 100644
--- a/packages/common/hooks/useTelemetryProps.ts
+++ b/packages/common/hooks/useTelemetryProps.ts
@@ -22,7 +22,7 @@ export function useTelemetryProps(): TelemetryProps {
return {
screenResolution: isBrowser ? `${window.innerWidth}x${window.innerHeight}` : undefined,
page_url: isBrowser ? window.location.href : '',
- pathname: router?.pathname ?? pathname ?? window.location.pathname,
+ pathname: router?.pathname ?? pathname ?? (isBrowser ? window.location.pathname : ''),
search: isBrowser ? window.location.search : '',
viewport_height: isBrowser ? window.innerHeight : 0,
viewport_width: isBrowser ? window.innerWidth : 0,
diff --git a/packages/common/index.tsx b/packages/common/index.tsx
index a74063ca7a5..25a77e6adb4 100644
--- a/packages/common/index.tsx
+++ b/packages/common/index.tsx
@@ -9,3 +9,4 @@ export * from './hooks'
export * from './MetaFavicons/pages-router'
export * from './Providers'
export * from './telemetry'
+export * from './feature-flags'
diff --git a/packages/common/package.json b/packages/common/package.json
index 9b6a54a54f8..a75ccd196f4 100644
--- a/packages/common/package.json
+++ b/packages/common/package.json
@@ -12,6 +12,8 @@
},
"dependencies": {
"@types/dat.gui": "^0.7.12",
+ "@vercel/flags": "^2.6.0",
+ "api-types": "workspace:*",
"config": "workspace:*",
"dat.gui": "^0.7.9",
"lodash": "^4.17.21",
diff --git a/packages/common/telemetry.ts b/packages/common/telemetry.ts
deleted file mode 100644
index 5bd26e90f9d..00000000000
--- a/packages/common/telemetry.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { post } from './fetchWrappers'
-
-interface TelemetryData {
- page_url: string
- page_title: string
- pathname: string
- ph: {
- referrer: string
- language: string
- search: string
- viewport_height: number
- viewport_width: number
- user_agent: string
- }
-}
-
-export function handlePageTelemetry(API_URL: string, route: string, telemetryData: TelemetryData) {
- const { page_url } = telemetryData
-
- const { search, language, viewport_height, viewport_width, referrer, user_agent } =
- telemetryData.ph
-
- return post(
- `${API_URL}/telemetry/page`,
- {
- page_url,
- page_title: document.title,
- pathname: route,
- ph: {
- referrer: referrer ?? document.referrer,
- language,
- search,
- viewport_height,
- viewport_width,
- user_agent: user_agent ?? navigator.userAgent,
- },
- },
- { headers: { Version: '2' }, credentials: 'include' }
- )
-}
-
-export function handleResetTelemetry(API_URL: string) {
- return post(`${API_URL}/telemetry/reset`, {})
-}
diff --git a/packages/common/telemetry.tsx b/packages/common/telemetry.tsx
new file mode 100644
index 00000000000..78b6ccc7964
--- /dev/null
+++ b/packages/common/telemetry.tsx
@@ -0,0 +1,156 @@
+import { components } from 'api-types'
+import { useRouter } from 'next/compat/router'
+import { usePathname } from 'next/navigation'
+import { useCallback, useEffect, useRef } from 'react'
+import { useFeatureFlags } from './feature-flags'
+import { post } from './fetchWrappers'
+import { ensurePlatformSuffix, isBrowser } from './helpers'
+import { useTelemetryCookie } from './hooks'
+
+export function handlePageTelemetry(
+ API_URL: string,
+ pathname?: string,
+ featureFlags?: {
+ [key: string]: unknown
+ },
+ telemetryDataOverride?: components['schemas']['TelemetryPageBodyV2Dto']
+) {
+ return post(
+ `${ensurePlatformSuffix(API_URL)}/telemetry/page`,
+ telemetryDataOverride !== undefined
+ ? { feature_flags: featureFlags, ...telemetryDataOverride }
+ : {
+ page_url: isBrowser ? window.location.href : '',
+ page_title: isBrowser ? document?.title : '',
+ pathname: pathname ? pathname : isBrowser ? window.location.pathname : '',
+ ph: {
+ referrer: isBrowser ? document?.referrer : '',
+ language: navigator.language ?? 'en-US',
+ user_agent: navigator.userAgent,
+ search: isBrowser ? window.location.search : '',
+ viewport_height: isBrowser ? window.innerHeight : 0,
+ viewport_width: isBrowser ? window.innerWidth : 0,
+ },
+ feature_flags: featureFlags,
+ },
+ { headers: { Version: '2' } }
+ )
+}
+
+export function handlePageLeaveTelemetry(
+ API_URL: string,
+ pathname: string,
+ featureFlags?: {
+ [key: string]: unknown
+ }
+) {
+ return post(`${ensurePlatformSuffix(API_URL)}/telemetry/page-leave`, {
+ body: {
+ pathname,
+ page_url: isBrowser ? window.location.href : '',
+ page_title: isBrowser ? document?.title : '',
+ feature_flags: featureFlags,
+ },
+ })
+}
+
+export function handleResetTelemetry(API_URL: string) {
+ return post(`${API_URL}/telemetry/reset`, {})
+}
+
+export const PageTelemetry = ({
+ API_URL,
+ hasAcceptedConsent,
+ enabled = true,
+}: {
+ API_URL: string
+ hasAcceptedConsent: boolean
+ enabled?: boolean
+}) => {
+ const router = useRouter()
+
+ const pagesPathname = router?.pathname
+ const appPathname = usePathname()
+
+ const featureFlags = useFeatureFlags()
+
+ const title = typeof document !== 'undefined' ? document?.title : ''
+ const referrer = typeof document !== 'undefined' ? document?.referrer : ''
+ useTelemetryCookie({ hasAcceptedConsent, title, referrer })
+
+ const sendPageTelemetry = useCallback(() => {
+ if (!(enabled && hasAcceptedConsent)) return Promise.resolve()
+
+ return handlePageTelemetry(
+ API_URL,
+ pagesPathname ?? appPathname ?? undefined,
+ featureFlags.posthog
+ ).catch((e) => {
+ console.error('Problem sending telemetry page:', e)
+ })
+ }, [API_URL, pagesPathname, appPathname, hasAcceptedConsent, featureFlags.posthog])
+
+ const sendPageLeaveTelemetry = useCallback(() => {
+ if (!(enabled && hasAcceptedConsent)) return Promise.resolve()
+
+ return handlePageTelemetry(
+ API_URL,
+ pagesPathname ?? appPathname ?? undefined,
+ featureFlags.posthog
+ ).catch((e) => {
+ console.error('Problem sending telemetry page-leave:', e)
+ })
+ }, [pagesPathname, appPathname, hasAcceptedConsent, featureFlags.posthog])
+
+ const hasSentInitialPageTelemetryRef = useRef(false)
+
+ useEffect(() => {
+ // Send page telemetry on first page load
+ // Waiting for router ready before sending page_view
+ // if not the path will be dynamic route instead of the browser url
+ if (
+ (router?.isReady ?? true) &&
+ featureFlags.hasLoaded &&
+ !hasSentInitialPageTelemetryRef.current
+ ) {
+ sendPageTelemetry()
+ hasSentInitialPageTelemetryRef.current = true
+ }
+ }, [router?.isReady, featureFlags.hasLoaded])
+
+ useEffect(() => {
+ // For pages router
+ if (router === null) return
+
+ function handleRouteChange() {
+ sendPageTelemetry()
+ }
+
+ // Listen for page changes after a navigation or when the query changes
+ router.events.on('routeChangeComplete', handleRouteChange)
+
+ return () => {
+ router.events.off('routeChangeComplete', handleRouteChange)
+ }
+ }, [router])
+
+ useEffect(() => {
+ // For app router
+ if (router !== null) return
+
+ if (appPathname) {
+ sendPageTelemetry()
+ }
+ }, [appPathname, router, sendPageTelemetry])
+
+ useEffect(() => {
+ if (!enabled) return
+
+ const handleBeforeUnload = () => sendPageLeaveTelemetry()
+
+ window.addEventListener('beforeunload', handleBeforeUnload)
+ return () => window.removeEventListener('beforeunload', handleBeforeUnload)
+ }, [enabled, sendPageLeaveTelemetry])
+
+ return null
+}
diff --git a/packages/ui-patterns/ConsentToast/index.tsx b/packages/ui-patterns/ConsentToast/index.tsx
index ecaf811ba15..d407f95686d 100644
--- a/packages/ui-patterns/ConsentToast/index.tsx
+++ b/packages/ui-patterns/ConsentToast/index.tsx
@@ -5,13 +5,14 @@ import {
handlePageTelemetry,
isBrowser,
useBreakpoint,
+ useFeatureFlags,
useTelemetryProps,
} from 'common'
-
import { noop } from 'lodash'
import { useCallback, useEffect, useRef, useState } from 'react'
import { toast } from 'sonner'
import { Button, cn } from 'ui'
+import { proxy, useSnapshot } from 'valtio'
import { PrivacySettings } from '../PrivacySettings'
interface ConsentToastProps {
@@ -77,13 +78,23 @@ export const ConsentToast = ({ onAccept = noop, onOptOut = noop }: ConsentToastP
)
}
+// [Alaister]: Using global state here so multiple components can access the consent value
+const consentState = proxy({
+ consentValue: (isBrowser ? localStorage?.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT) : null) as
+ | string
+ | null,
+ setConsentValue: (value: string | null) => {
+ consentState.consentValue = value
+ },
+})
+
export const useConsent = () => {
const { TELEMETRY_CONSENT, TELEMETRY_DATA } = LOCAL_STORAGE_KEYS
const consentToastId = useRef()
const telemetryProps = useTelemetryProps()
+ const featureFlags = useFeatureFlags()
- const initialValue = isBrowser ? localStorage?.getItem(TELEMETRY_CONSENT) : null
- const [consentValue, setConsentValue] = useState(initialValue)
+ const { consentValue } = useSnapshot(consentState)
const handleConsent = (value: 'true' | 'false') => {
if (!isBrowser) return
@@ -97,7 +108,8 @@ export const useConsent = () => {
const telemetryData = JSON.parse(decodeURIComponent(encodedData))
handlePageTelemetry(
process.env.NEXT_PUBLIC_API_URL!,
- window.location.pathname,
+ telemetryProps.pathname,
+ featureFlags.posthog,
telemetryData
)
// remove the telemetry cookie
@@ -106,24 +118,10 @@ export const useConsent = () => {
console.error('Invalid telemetry data:', error)
}
} else {
- const telemetryData = {
- page_url: telemetryProps.page_url,
- page_title: typeof document !== 'undefined' ? document?.title : '',
- pathname: telemetryProps.pathname,
- ph: {
- referrer: typeof document !== 'undefined' ? document?.referrer : '',
- language: telemetryProps.language,
- search: telemetryProps.search,
- viewport_height: telemetryProps.viewport_height,
- viewport_width: telemetryProps.viewport_width,
- user_agent: navigator.userAgent,
- },
- }
-
handlePageTelemetry(
process.env.NEXT_PUBLIC_API_URL!,
- window.location.pathname,
- telemetryData
+ telemetryProps.pathname,
+ featureFlags.posthog
)
}
} else {
@@ -131,7 +129,7 @@ export const useConsent = () => {
document.cookie = `${TELEMETRY_DATA}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`
}
- setConsentValue(value)
+ consentState.setConsentValue(value)
localStorage.setItem(TELEMETRY_CONSENT, value)
if (consentToastId.current) {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d73f05dd8ff..fd4ae6a9f58 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1327,6 +1327,12 @@ importers:
'@types/dat.gui':
specifier: ^0.7.12
version: 0.7.12
+ '@vercel/flags':
+ specifier: ^2.6.0
+ version: 2.6.0(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ api-types:
+ specifier: workspace:*
+ version: link:../api-types
config:
specifier: workspace:*
version: link:../config