mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
chore(studio): graduate homeNew experiment (#43437)
## Summary
The `homeNew` PostHog experiment has concluded. This PR graduates it by
making the new homepage (`ProjectHome`, formerly `HomeV2`) the permanent
default for all users, and removes all dead code from the old
experiment.
## Changes
- Remove `homeNew` PostHog feature flag checks and `home_new` experiment
exposure tracking from 3 files
- Rename `HomeNew/` → `ProjectHome/` directory and `HomeV2` →
`ProjectHome` export
- Delete old `Home/Home.tsx` component (shared components like
`ProjectList/` are kept — still used by org pages)
- Delete `pages/project/[ref]/building.tsx` and add a server-side
redirect from `/project/:ref/building` → `/project/:ref` to prevent 404s
during rollout (old cached JS bundles may still route to `/building`)
- Simplify `ContentWrapper` building-state logic in `ProjectLayout` —
always redirect building projects to home, always suppress building
interstitial on home page
- Always route to `/project/{ref}` after project creation (remove
`/building` path)
- Update all Observability imports from `HomeNew` → `ProjectHome`
## Self-hosted behavior change
Self-hosted Studio previously showed the old `Home` component (client
libraries + example projects) since PostHog flags don't load. This PR
changes self-hosted to show `ProjectHome` (TopSection with service
status + instance diagram, advisor, custom reports). All sections query
backend APIs that exist on self-hosted. E2E tests pass against the
self-hosted build.
## Testing
- [x] `pnpm turbo run build --filter=studio` passes
- [x] No remaining references to `homeNew`, `home_new`, or `HomeNew` in
codebase
- [x] No broken imports to deleted files
- [x] Self-hosted E2E tests pass (145 passed, 1 flaky, 4 skipped)
- [x] `/building` redirect added to both platform and self-hosted config
blocks
**Quick test:**
1. Navigate to any project homepage — should render the ProjectHome
component
2. Create a new project — should redirect to `/project/{ref}` (not
`/building`)
3. Visit a project in `COMING_UP` state on a non-home route — should
redirect to home
4. Visit `/project/{ref}/building` directly — should 302 redirect to
`/project/{ref}`
## Linear
- fixes GROWTH-671
This commit is contained in:
1 parent
fc7fb50aa2
commit
01c178e159
36 files changed
+73
-451
No files matched your search
@@ -326,7 +326,6 @@
|
||||
"components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx": 1,
|
||||
"components/interfaces/Functions/EdgeFunctionsListItem.tsx": 2,
|
||||
"components/interfaces/Functions/FunctionsNav.tsx": 1,
|
||||
"components/interfaces/Home/Home.tsx": 6,
|
||||
"components/interfaces/Home/ProjectList/ProjectList.tsx": 1,
|
||||
"components/interfaces/Home/ProjectUsage.tsx": 1,
|
||||
"components/interfaces/Home/ServiceStatus.tsx": 1,
|
||||
|
||||
@@ -1,286 +0,0 @@
|
||||
import dayjs from 'dayjs'
|
||||
import Link from 'next/link'
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { AdvisorWidget } from 'components/interfaces/Home/AdvisorWidget'
|
||||
import { ClientLibrary } from 'components/interfaces/Home/ClientLibrary'
|
||||
import { ExampleProject } from 'components/interfaces/Home/ExampleProject'
|
||||
import { EXAMPLE_PROJECTS } from 'components/interfaces/Home/Home.constants'
|
||||
import { NewProjectPanel } from 'components/interfaces/Home/NewProjectPanel/NewProjectPanel'
|
||||
import { ProjectUsageSection } from 'components/interfaces/Home/ProjectUsageSection'
|
||||
import { ServiceStatus } from 'components/interfaces/Home/ServiceStatus'
|
||||
import { ProjectPausedState } from 'components/layouts/ProjectLayout/PausedState/ProjectPausedState'
|
||||
import { ComputeBadgeWrapper } from 'components/ui/ComputeBadgeWrapper'
|
||||
import { InlineLink } from 'components/ui/InlineLink'
|
||||
import { ProjectUpgradeFailedBanner } from 'components/ui/ProjectUpgradeFailedBanner'
|
||||
import { useBranchesQuery } from 'data/branches/branches-query'
|
||||
import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query'
|
||||
import { useProjectDetailQuery } from 'data/projects/project-detail-query'
|
||||
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { useTablesQuery } from 'data/tables/tables-query'
|
||||
import { useCustomContent } from 'hooks/custom-content/useCustomContent'
|
||||
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
|
||||
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
|
||||
import { useIsOrioleDb, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
|
||||
import { DOCS_URL, IS_PLATFORM, PROJECT_STATUS } from 'lib/constants'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import {
|
||||
Badge,
|
||||
cn,
|
||||
Tabs_Shadcn_,
|
||||
TabsContent_Shadcn_,
|
||||
TabsList_Shadcn_,
|
||||
TabsTrigger_Shadcn_,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from 'ui'
|
||||
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
|
||||
|
||||
export const Home = () => {
|
||||
const { data: project } = useSelectedProjectQuery()
|
||||
const { data: organization } = useSelectedOrganizationQuery()
|
||||
const { data: parentProject } = useProjectDetailQuery({ ref: project?.parent_project_ref })
|
||||
const isOrioleDb = useIsOrioleDb()
|
||||
const snap = useAppStateSnapshot()
|
||||
const { ref, enableBranching } = useParams()
|
||||
|
||||
const { projectHomepageExampleProjects, projectHomepageClientLibraries: clientLibraries } =
|
||||
useCustomContent(['project_homepage:example_projects', 'project_homepage:client_libraries'])
|
||||
|
||||
const {
|
||||
projectHomepageShowInstanceSize: showInstanceSize,
|
||||
projectHomepageShowExamples: showExamples,
|
||||
} = useIsFeatureEnabled(['project_homepage:show_instance_size', 'project_homepage:show_examples'])
|
||||
|
||||
const hasShownEnableBranchingModalRef = useRef(false)
|
||||
const isPaused = project?.status === PROJECT_STATUS.INACTIVE
|
||||
const isNewProject = dayjs(project?.inserted_at).isAfter(dayjs().subtract(2, 'day'))
|
||||
|
||||
useEffect(() => {
|
||||
if (enableBranching && !hasShownEnableBranchingModalRef.current) {
|
||||
hasShownEnableBranchingModalRef.current = true
|
||||
snap.setShowCreateBranchModal(true)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [enableBranching])
|
||||
|
||||
const { data: tablesData, isPending: isLoadingTables } = useTablesQuery({
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString,
|
||||
schema: 'public',
|
||||
})
|
||||
const { data: functionsData, isPending: isLoadingFunctions } = useEdgeFunctionsQuery({
|
||||
projectRef: project?.ref,
|
||||
})
|
||||
const { data: replicasData, isPending: isLoadingReplicas } = useReadReplicasQuery({
|
||||
projectRef: project?.ref,
|
||||
})
|
||||
|
||||
const { data: branches } = useBranchesQuery({
|
||||
projectRef: project?.parent_project_ref ?? project?.ref,
|
||||
})
|
||||
|
||||
const mainBranch = branches?.find((branch) => branch.is_default)
|
||||
const currentBranch = branches?.find((branch) => branch.project_ref === project?.ref)
|
||||
const isMainBranch = currentBranch?.name === mainBranch?.name
|
||||
let projectName = 'Welcome to your project'
|
||||
|
||||
if (currentBranch && !isMainBranch) {
|
||||
projectName = currentBranch?.name
|
||||
} else if (project?.name) {
|
||||
projectName = project?.name
|
||||
}
|
||||
|
||||
const tablesCount = Math.max(0, tablesData?.length ?? 0)
|
||||
const functionsCount = Math.max(0, functionsData?.length ?? 0)
|
||||
// [Joshen] JFYI minus 1 as the replicas endpoint returns the primary DB minimally
|
||||
const replicasCount = Math.max(0, (replicasData?.length ?? 1) - 1)
|
||||
|
||||
if (isPaused) {
|
||||
return (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<ProjectPausedState />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full px-4">
|
||||
<div className={cn('py-16 ', !isPaused && 'border-b border-muted ')}>
|
||||
<div className="mx-auto max-w-7xl flex flex-col gap-y-4">
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-6 justify-between w-full">
|
||||
<div className="flex flex-col md:flex-row md:items-end gap-3 w-full">
|
||||
<div>
|
||||
{!isMainBranch && (
|
||||
<Link
|
||||
href={`/project/${parentProject?.ref}`}
|
||||
className="text-sm text-foreground-light"
|
||||
>
|
||||
{parentProject?.name}
|
||||
</Link>
|
||||
)}
|
||||
<h1 className="text-3xl">{projectName}</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-2 mb-1">
|
||||
{isOrioleDb && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Badge variant="warning">OrioleDB</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" align="start" className="max-w-80 text-center">
|
||||
This project is using Postgres with OrioleDB which is currently in preview and
|
||||
not suitable for production workloads. View our{' '}
|
||||
<InlineLink href={`${DOCS_URL}/guides/database/orioledb`}>
|
||||
documentation
|
||||
</InlineLink>{' '}
|
||||
for all limitations.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{showInstanceSize && (
|
||||
<ComputeBadgeWrapper
|
||||
projectRef={project?.ref}
|
||||
slug={organization?.slug}
|
||||
cloudProvider={project?.cloud_provider}
|
||||
computeSize={project?.infra_compute_size}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
{project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && (
|
||||
<div className="flex items-center gap-x-6">
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<Link
|
||||
href={`/project/${ref}/editor`}
|
||||
className="transition text-foreground-light hover:text-foreground text-sm"
|
||||
>
|
||||
Tables
|
||||
</Link>
|
||||
|
||||
{isLoadingTables ? (
|
||||
<ShimmeringLoader className="w-full h-[32px] w-6 p-0" />
|
||||
) : (
|
||||
<p className="text-2xl tabular-nums">{tablesCount}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<Link
|
||||
href={`/project/${ref}/functions`}
|
||||
className="transition text-foreground-light hover:text-foreground text-sm"
|
||||
>
|
||||
Functions
|
||||
</Link>
|
||||
{isLoadingFunctions ? (
|
||||
<ShimmeringLoader className="w-full h-[32px] w-6 p-0" />
|
||||
) : (
|
||||
<p className="text-2xl tabular-nums">{functionsCount}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{IS_PLATFORM && (
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<Link
|
||||
href={`/project/${ref}/settings/infrastructure`}
|
||||
className="transition text-foreground-light hover:text-foreground text-sm"
|
||||
>
|
||||
Replicas
|
||||
</Link>
|
||||
{isLoadingReplicas ? (
|
||||
<ShimmeringLoader className="w-full h-[32px] w-6 p-0" />
|
||||
) : (
|
||||
<p className="text-2xl tabular-nums">{replicasCount}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{IS_PLATFORM && project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && (
|
||||
<div className="ml-6 border-l flex items-center w-[145px] justify-end">
|
||||
<ServiceStatus />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ProjectUpgradeFailedBanner />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<>
|
||||
<div className="py-16 border-b border-muted">
|
||||
<div className="mx-auto max-w-7xl space-y-16 @container">
|
||||
{IS_PLATFORM && project?.status !== PROJECT_STATUS.INACTIVE && (
|
||||
<>{isNewProject ? <NewProjectPanel /> : <ProjectUsageSection />}</>
|
||||
)}
|
||||
{!isNewProject && project?.status !== PROJECT_STATUS.INACTIVE && <AdvisorWidget />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-surface-100/5 py-16">
|
||||
<div className="mx-auto max-w-7xl space-y-16 @container">
|
||||
{project?.status !== PROJECT_STATUS.INACTIVE && (
|
||||
<>
|
||||
<div className="space-y-8">
|
||||
<h2 className="text-lg">Client libraries</h2>
|
||||
<div className="grid grid-cols-2 gap-x-8 gap-y-8 md:gap-12 mb-12 md:grid-cols-3">
|
||||
{clientLibraries!.map((library) => (
|
||||
// [Alaister]: Looks like the useCustomContent has wonky types. I'll look at a fix later.
|
||||
<ClientLibrary key={(library as any).language} {...(library as any)} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{showExamples && (
|
||||
<div className="flex flex-col gap-y-8">
|
||||
<h4 className="text-lg">Example projects</h4>
|
||||
{!!projectHomepageExampleProjects ? (
|
||||
<div className="grid gap-2 md:gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
{/* [Alaister]: Looks like the useCustomContent has wonky types. I'll look at a fix later. */}
|
||||
{(projectHomepageExampleProjects as any)
|
||||
.sort((a: any, b: any) => a.title.localeCompare(b.title))
|
||||
.map((project: any) => (
|
||||
<ExampleProject key={project.url} {...project} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center">
|
||||
<Tabs_Shadcn_ defaultValue="app" className="w-full">
|
||||
<TabsList_Shadcn_ className="flex gap-4 mb-8">
|
||||
<TabsTrigger_Shadcn_ value="app">App Frameworks</TabsTrigger_Shadcn_>
|
||||
<TabsTrigger_Shadcn_ value="mobile">
|
||||
Mobile Frameworks
|
||||
</TabsTrigger_Shadcn_>
|
||||
</TabsList_Shadcn_>
|
||||
<TabsContent_Shadcn_ value="app">
|
||||
<div className="grid gap-2 md:gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
{EXAMPLE_PROJECTS.filter((project) => project.type === 'app')
|
||||
.sort((a, b) => a.title.localeCompare(b.title))
|
||||
.map((project) => (
|
||||
<ExampleProject key={project.url} {...project} />
|
||||
))}
|
||||
</div>
|
||||
</TabsContent_Shadcn_>
|
||||
<TabsContent_Shadcn_ value="mobile">
|
||||
<div className="grid gap-2 md:gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
{EXAMPLE_PROJECTS.filter((project) => project.type === 'mobile')
|
||||
.sort((a, b) => a.title.localeCompare(b.title))
|
||||
.map((project) => (
|
||||
<ExampleProject key={project.url} {...project} />
|
||||
))}
|
||||
</div>
|
||||
</TabsContent_Shadcn_>
|
||||
</Tabs_Shadcn_>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { LogsBarChartDatum } from '../HomeNew/ProjectUsage.metrics'
|
||||
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
|
||||
import {
|
||||
calculateErrorRate,
|
||||
calculateSuccessRate,
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
sumErrors,
|
||||
sumTotal,
|
||||
sumWarnings,
|
||||
} from '../HomeNew/ProjectUsage.metrics'
|
||||
import type { LogsBarChartDatum } from '../HomeNew/ProjectUsage.metrics'
|
||||
} from '../ProjectHome/ProjectUsage.metrics'
|
||||
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
|
||||
import { useServiceHealthMetrics } from './useServiceHealthMetrics'
|
||||
|
||||
export type ServiceKey = 'db' | 'functions' | 'auth' | 'storage' | 'realtime' | 'postgrest'
|
||||
|
||||
@@ -3,7 +3,7 @@ import Link from 'next/link'
|
||||
import { Button, Card, CardContent, CardFooter, CardHeader, CardTitle, cn, Loading } from 'ui'
|
||||
import { LogsBarChart } from 'ui-patterns/LogsBarChart'
|
||||
|
||||
import type { LogsBarChartDatum } from '../HomeNew/ProjectUsage.metrics'
|
||||
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
|
||||
import { getHealthStatus, type ServiceKey } from './ObservabilityOverview.utils'
|
||||
|
||||
const colorClassMap: Record<string, string> = {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Badge, Card, CardContent, Loading, Tooltip, TooltipContent, TooltipTrig
|
||||
import { LogsBarChart } from 'ui-patterns/LogsBarChart'
|
||||
|
||||
import { ButtonTooltip } from '../../ui/ButtonTooltip'
|
||||
import type { LogsBarChartDatum } from '../HomeNew/ProjectUsage.metrics'
|
||||
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
|
||||
import { getHealthStatus, type ServiceKey } from './ObservabilityOverview.utils'
|
||||
|
||||
type ServiceConfig = {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { get } from 'data/fetchers'
|
||||
|
||||
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
|
||||
import { generateRegexpWhere } from '../Reports/Reports.constants'
|
||||
import type { LogsBarChartDatum } from '../HomeNew/ProjectUsage.metrics'
|
||||
|
||||
type PostgrestMetricsVariables = {
|
||||
projectRef: string
|
||||
|
||||
@@ -4,15 +4,15 @@ import { useFillTimeseriesSorted } from 'hooks/analytics/useFillTimeseriesSorted
|
||||
import useTimeseriesUnixToIso from 'hooks/analytics/useTimeseriesUnixToIso'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import type { LogsBarChartDatum } from '../HomeNew/ProjectUsage.metrics'
|
||||
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
|
||||
import { LogsTableName } from '../Settings/Logs/Logs.constants'
|
||||
import { genChartQuery } from '../Settings/Logs/Logs.utils'
|
||||
import {
|
||||
type RawChartData,
|
||||
calculateAggregatedMetrics,
|
||||
calculateDateRange,
|
||||
calculateHealthMetrics,
|
||||
transformToBarChartData,
|
||||
type RawChartData,
|
||||
} from './useServiceHealthMetrics.utils'
|
||||
|
||||
export type ServiceKey = 'db' | 'functions' | 'auth' | 'storage' | 'realtime' | 'postgrest'
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import dayjs from 'dayjs'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { LogsBarChartDatum } from '../HomeNew/ProjectUsage.metrics'
|
||||
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
|
||||
import {
|
||||
calculateAggregatedMetrics,
|
||||
calculateDateRange,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import type { LogsBarChartDatum } from '../HomeNew/ProjectUsage.metrics'
|
||||
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
|
||||
import {
|
||||
computeSuccessAndNonSuccessRates,
|
||||
sumErrors,
|
||||
sumTotal,
|
||||
sumWarnings,
|
||||
} from '../HomeNew/ProjectUsage.metrics'
|
||||
} from '../ProjectHome/ProjectUsage.metrics'
|
||||
|
||||
/**
|
||||
* Calculates the date range for fetching service health metrics
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
@@ -1,4 +1,5 @@
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import type { LogsBarChartDatum } from './ProjectUsage.metrics'
|
||||
|
||||
/**
|
||||
+21
-58
@@ -1,21 +1,16 @@
|
||||
import {
|
||||
closestCenter,
|
||||
DndContext,
|
||||
DragEndEvent,
|
||||
PointerSensor,
|
||||
closestCenter,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core'
|
||||
import { SortableContext, arrayMove, rectSortingStrategy, useSortable } from '@dnd-kit/sortable'
|
||||
import { arrayMove, rectSortingStrategy, SortableContext, useSortable } from '@dnd-kit/sortable'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import dayjs from 'dayjs'
|
||||
import { Plus, RefreshCw } from 'lucide-react'
|
||||
import type { CSSProperties, DragEvent, ReactNode } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { keepPreviousData } from '@tanstack/react-query'
|
||||
import { useParams } from 'common'
|
||||
import { SnippetDropdown } from 'components/interfaces/HomeNew/SnippetDropdown'
|
||||
import { SnippetDropdown } from 'components/interfaces/ProjectHome/SnippetDropdown'
|
||||
import { ReportBlock } from 'components/interfaces/Reports/ReportBlock/ReportBlock'
|
||||
import { createSqlSnippetSkeletonV2 } from 'components/interfaces/SQLEditor/SQLEditor.utils'
|
||||
import type { ChartConfig } from 'components/interfaces/SQLEditor/UtilityPanel/ChartConfig'
|
||||
@@ -29,17 +24,20 @@ import {
|
||||
UpsertContentPayload,
|
||||
useContentUpsertMutation,
|
||||
} from 'data/content/content-upsert-mutation'
|
||||
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
|
||||
import dayjs from 'dayjs'
|
||||
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
|
||||
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
|
||||
import { uuidv4 } from 'lib/helpers'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import { useTrack } from 'lib/telemetry/track'
|
||||
import { Plus, RefreshCw } from 'lucide-react'
|
||||
import type { CSSProperties, DragEvent, ReactNode } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
import type { Dashboards } from 'types'
|
||||
import { Button } from 'ui'
|
||||
import { Row } from 'ui-patterns'
|
||||
import { keepPreviousData } from '@tanstack/react-query'
|
||||
|
||||
export function CustomReportSection() {
|
||||
const startDate = dayjs().subtract(7, 'day').toISOString()
|
||||
@@ -48,8 +46,7 @@ export function CustomReportSection() {
|
||||
const { ref } = useParams()
|
||||
const { profile } = useProfile()
|
||||
const state = useDatabaseSelectorStateSnapshot()
|
||||
const { data: organization } = useSelectedOrganizationQuery()
|
||||
const { mutate: sendEvent } = useSendEventMutation()
|
||||
const track = useTrack()
|
||||
const { invalidateInfraMonitoringQuery } = useInvalidateAnalyticsQuery()
|
||||
const { data: project } = useSelectedProjectQuery()
|
||||
|
||||
@@ -203,19 +200,7 @@ export function CustomReportSection() {
|
||||
},
|
||||
})
|
||||
|
||||
if (ref && organization?.slug) {
|
||||
sendEvent({
|
||||
action: 'home_custom_report_block_added',
|
||||
properties: {
|
||||
block_id: snippet.id,
|
||||
position: 0,
|
||||
},
|
||||
groups: {
|
||||
project: ref,
|
||||
organization: organization.slug,
|
||||
},
|
||||
})
|
||||
}
|
||||
track('home_custom_report_block_added', { block_id: snippet.id, position: 0 })
|
||||
return
|
||||
}
|
||||
const current = [...editableReport.layout]
|
||||
@@ -225,19 +210,10 @@ export function CustomReportSection() {
|
||||
setEditableReport(updated)
|
||||
persistReport(updated)
|
||||
|
||||
if (ref && organization?.slug) {
|
||||
sendEvent({
|
||||
action: 'home_custom_report_block_added',
|
||||
properties: {
|
||||
block_id: snippet.id,
|
||||
position: current.length - 1,
|
||||
},
|
||||
groups: {
|
||||
project: ref,
|
||||
organization: organization.slug,
|
||||
},
|
||||
})
|
||||
}
|
||||
track('home_custom_report_block_added', {
|
||||
block_id: snippet.id,
|
||||
position: current.length - 1,
|
||||
})
|
||||
},
|
||||
[
|
||||
editableReport,
|
||||
@@ -245,8 +221,7 @@ export function CustomReportSection() {
|
||||
ref,
|
||||
profile,
|
||||
upsertContent,
|
||||
organization,
|
||||
sendEvent,
|
||||
track,
|
||||
findNextPlacement,
|
||||
createSnippetChartBlock,
|
||||
persistReport,
|
||||
@@ -265,17 +240,8 @@ export function CustomReportSection() {
|
||||
setEditableReport(updated)
|
||||
persistReport(updated)
|
||||
|
||||
if (ref && organization?.slug && removedChart) {
|
||||
sendEvent({
|
||||
action: 'home_custom_report_block_removed',
|
||||
properties: {
|
||||
block_id: String(removedChart.id),
|
||||
},
|
||||
groups: {
|
||||
project: ref,
|
||||
organization: organization.slug,
|
||||
},
|
||||
})
|
||||
if (removedChart) {
|
||||
track('home_custom_report_block_removed', { block_id: String(removedChart.id) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,12 +291,9 @@ export function CustomReportSection() {
|
||||
// Handle success optimistically
|
||||
toast.success(`Successfully created new query: ${label}`, { id: toastId })
|
||||
addSnippetToReport({ id: payload.id, name: label })
|
||||
sendEvent({
|
||||
action: 'custom_report_assistant_sql_block_added',
|
||||
groups: { project: ref, organization: organization?.slug ?? 'Unknown' },
|
||||
})
|
||||
track('custom_report_assistant_sql_block_added')
|
||||
},
|
||||
[ref, profile, project, upsertContent, addSnippetToReport, sendEvent, organization]
|
||||
[ref, profile, project, upsertContent, addSnippetToReport, track]
|
||||
)
|
||||
|
||||
const handleDragOver = (e: DragEvent<HTMLDivElement>) => {
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+13
-24
@@ -1,32 +1,30 @@
|
||||
import { DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'
|
||||
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'
|
||||
import dayjs from 'dayjs'
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
import { IS_PLATFORM, useFlag, useParams } from 'common'
|
||||
import { ProjectUsageSection as ProjectUsageSectionV1 } from 'components/interfaces/Home/ProjectUsageSection'
|
||||
import { SortableSection } from 'components/interfaces/HomeNew/SortableSection'
|
||||
import { TopSection } from 'components/interfaces/HomeNew/TopSection'
|
||||
import { SortableSection } from 'components/interfaces/ProjectHome/SortableSection'
|
||||
import { TopSection } from 'components/interfaces/ProjectHome/TopSection'
|
||||
import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
|
||||
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
|
||||
import dayjs from 'dayjs'
|
||||
import { useLocalStorage } from 'hooks/misc/useLocalStorage'
|
||||
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
|
||||
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
|
||||
import { PROJECT_STATUS } from 'lib/constants'
|
||||
import { useTrack } from 'lib/telemetry/track'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { cn } from 'ui'
|
||||
|
||||
import { AdvisorSection } from './AdvisorSection'
|
||||
import { CustomReportSection } from './CustomReportSection'
|
||||
import { type GettingStartedState } from './GettingStarted/GettingStarted.types'
|
||||
import { GettingStartedSection } from './GettingStarted/GettingStartedSection'
|
||||
import { ProjectUsageSection as ProjectUsageSectionV2 } from './ProjectUsageSection'
|
||||
|
||||
export const HomeV2 = () => {
|
||||
export const ProjectHome = () => {
|
||||
const { enableBranching } = useParams()
|
||||
const snap = useAppStateSnapshot()
|
||||
const { data: project } = useSelectedProjectQuery()
|
||||
const { data: organization } = useSelectedOrganizationQuery()
|
||||
const { mutate: sendEvent } = useSendEventMutation()
|
||||
const track = useTrack()
|
||||
|
||||
const showHomepageUsageV2 = useFlag('newHomepageUsageV2')
|
||||
|
||||
@@ -58,20 +56,11 @@ export const HomeV2 = () => {
|
||||
const newIndex = items.indexOf(String(over.id))
|
||||
if (oldIndex === -1 || newIndex === -1) return items
|
||||
|
||||
if (project?.ref && organization?.slug) {
|
||||
sendEvent({
|
||||
action: 'home_section_rows_moved',
|
||||
properties: {
|
||||
section_moved: String(active.id),
|
||||
old_position: oldIndex,
|
||||
new_position: newIndex,
|
||||
},
|
||||
groups: {
|
||||
project: project.ref,
|
||||
organization: organization.slug,
|
||||
},
|
||||
})
|
||||
}
|
||||
track('home_section_rows_moved', {
|
||||
section_moved: String(active.id),
|
||||
old_position: oldIndex,
|
||||
new_position: newIndex,
|
||||
})
|
||||
|
||||
return arrayMove(items, oldIndex, newIndex)
|
||||
})
|
||||
File renamed without changes.
+2
-1
@@ -1,4 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
computeSuccessAndNonSuccessRates,
|
||||
sumErrors,
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { ActivityStats } from 'components/interfaces/HomeNew/ActivityStats'
|
||||
import { ProjectConnectionHoverCard } from 'components/interfaces/HomeNew/ProjectConnectionHoverCard'
|
||||
import { ActivityStats } from 'components/interfaces/ProjectHome/ActivityStats'
|
||||
import { ProjectConnectionHoverCard } from 'components/interfaces/ProjectHome/ProjectConnectionHoverCard'
|
||||
import { ProjectPausedState } from 'components/layouts/ProjectLayout/PausedState/ProjectPausedState'
|
||||
import { ComputeBadgeWrapper } from 'components/ui/ComputeBadgeWrapper'
|
||||
import { InlineLink } from 'components/ui/InlineLink'
|
||||
@@ -35,7 +35,6 @@ import { useCustomContent } from '@/hooks/custom-content/useCustomContent'
|
||||
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
|
||||
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
||||
import { withAuth } from '@/hooks/misc/withAuth'
|
||||
import { usePHFlag } from '@/hooks/ui/useFlag'
|
||||
import { PROJECT_STATUS } from '@/lib/constants'
|
||||
import { buildStudioPageTitle } from '@/lib/page-title'
|
||||
import { useAppStateSnapshot } from '@/state/app-state'
|
||||
@@ -277,8 +276,6 @@ const ContentWrapper = ({ isLoading, isBlocking = true, children }: ContentWrapp
|
||||
const { ref } = useParams()
|
||||
const state = useDatabaseSelectorStateSnapshot()
|
||||
const { data: selectedProject } = useSelectedProjectQuery()
|
||||
const isHomeNew = usePHFlag('homeNew') === 'new-home'
|
||||
|
||||
const isBackupsPage = router.pathname.includes('/project/[ref]/database/backups')
|
||||
const isHomePage = router.pathname === '/project/[ref]'
|
||||
|
||||
@@ -298,13 +295,10 @@ const ContentWrapper = ({ isLoading, isBlocking = true, children }: ContentWrapp
|
||||
const isProjectPauseFailed = selectedProject?.status === PROJECT_STATUS.PAUSE_FAILED
|
||||
const isProjectOffline = selectedProject?.postgrestStatus === 'OFFLINE'
|
||||
|
||||
// handle redirect to home for building state
|
||||
const shouldRedirectToHomeForBuilding =
|
||||
isProjectBuilding && requiresDbConnection && isHomeNew && !isHomePage
|
||||
const shouldRedirectToHomeForBuilding = isProjectBuilding && requiresDbConnection && !isHomePage
|
||||
|
||||
// We won't be showing the building state with the new home page
|
||||
const shouldShowBuildingState =
|
||||
isProjectBuilding && requiresDbConnection && !(isHomeNew && isHomePage)
|
||||
// Don't show building state on the home page — it handles building state inline
|
||||
const shouldShowBuildingState = isProjectBuilding && requiresDbConnection && !isHomePage
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldRedirectToHomeForBuilding && ref) {
|
||||
|
||||
@@ -88,6 +88,11 @@ const nextConfig = {
|
||||
destination: '/sign-in',
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: '/project/:ref/building',
|
||||
destination: '/project/:ref',
|
||||
permanent: false,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
@@ -120,6 +125,11 @@ const nextConfig = {
|
||||
destination: '/project/default',
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: '/project/:ref/building',
|
||||
destination: '/project/:ref',
|
||||
permanent: false,
|
||||
},
|
||||
]),
|
||||
{
|
||||
source: '/project/:ref/auth',
|
||||
|
||||
@@ -85,7 +85,6 @@ const Wizard: NextPageWithLayout = () => {
|
||||
const projectCreationDisabled = useFlag('disableProjectCreationAndUpdate')
|
||||
const showPostgresVersionSelector = useFlag('showPostgresVersionSelector')
|
||||
const cloudProviderEnabled = useFlag('enableFlyCloudProvider')
|
||||
const isHomeNew = usePHFlag('homeNew') === 'new-home'
|
||||
|
||||
const showNonProdFields = process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod'
|
||||
const isNotOnHigherPlan = !['team', 'enterprise', 'platform'].includes(currentOrg?.plan.id ?? '')
|
||||
@@ -253,7 +252,7 @@ const Wizard: NextPageWithLayout = () => {
|
||||
organization: res.organization_slug,
|
||||
}
|
||||
)
|
||||
router.push(isHomeNew ? `/project/${res.ref}` : `/project/${res.ref}/building`)
|
||||
router.push(`/project/${res.ref}`)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import DefaultLayout from 'components/layouts/DefaultLayout'
|
||||
import { ProjectLayoutWithAuth } from 'components/layouts/ProjectLayout'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
import type { NextPageWithLayout } from 'types'
|
||||
|
||||
const ProjectBuildingPage: NextPageWithLayout = () => {
|
||||
return <RedirectToDashboard />
|
||||
}
|
||||
|
||||
ProjectBuildingPage.getLayout = (page) => (
|
||||
<DefaultLayout>
|
||||
<ProjectLayoutWithAuth title="Project Building">{page}</ProjectLayoutWithAuth>
|
||||
</DefaultLayout>
|
||||
)
|
||||
|
||||
export default ProjectBuildingPage
|
||||
|
||||
const RedirectToDashboard = () => {
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
|
||||
useEffect(() => {
|
||||
// Use redirect to reload store data properly after project has been
|
||||
// been created or unpaused, this is necessarily especially for unpausing
|
||||
// so that the dashboard fetches the updated connection strings
|
||||
// window.location.replace(`/project/${ref}`)
|
||||
|
||||
// TODO: for experiment only
|
||||
// Cos we already pulling new connectionString after project building/restoring
|
||||
// just use normal router.push is enough
|
||||
router.push(`/project/${ref}`)
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,25 +1,10 @@
|
||||
import { IS_PLATFORM } from 'common'
|
||||
import { Home } from 'components/interfaces/Home/Home'
|
||||
import { HomeV2 } from 'components/interfaces/HomeNew/Home'
|
||||
import { ProjectHome } from 'components/interfaces/ProjectHome/Home'
|
||||
import DefaultLayout from 'components/layouts/DefaultLayout'
|
||||
import { ProjectLayoutWithAuth } from 'components/layouts/ProjectLayout'
|
||||
import { useTrackExperimentExposure } from 'hooks/misc/useTrackExperimentExposure'
|
||||
import { usePHFlag } from 'hooks/ui/useFlag'
|
||||
import type { NextPageWithLayout } from 'types'
|
||||
|
||||
const HomePage: NextPageWithLayout = () => {
|
||||
const homeNewVariant = usePHFlag<string>('homeNew')
|
||||
const isHomeNew = homeNewVariant === 'new-home'
|
||||
|
||||
useTrackExperimentExposure(
|
||||
'home_new',
|
||||
IS_PLATFORM && typeof homeNewVariant !== 'boolean' ? homeNewVariant : undefined
|
||||
)
|
||||
|
||||
if (isHomeNew) {
|
||||
return <HomeV2 />
|
||||
}
|
||||
return <Home />
|
||||
return <ProjectHome />
|
||||
}
|
||||
|
||||
HomePage.getLayout = (page) => (
|
||||
|
||||
@@ -286,9 +286,7 @@ export interface MwDiagData {
|
||||
export function parseMwDiagCookie(cookieHeader: string): MwDiagData | null {
|
||||
try {
|
||||
const cookies = cookieHeader.split(';')
|
||||
const match = cookies
|
||||
.map((c) => c.trim())
|
||||
.find((c) => c.startsWith(`${MW_DIAG_COOKIE_NAME}=`))
|
||||
const match = cookies.map((c) => c.trim()).find((c) => c.startsWith(`${MW_DIAG_COOKIE_NAME}=`))
|
||||
|
||||
if (!match) return null
|
||||
|
||||
|
||||
@@ -13,7 +13,11 @@ import { IS_PLATFORM, IS_PROD, LOCAL_STORAGE_KEYS } from './constants'
|
||||
import { useFeatureFlags } from './feature-flags'
|
||||
import { post } from './fetchWrappers'
|
||||
import type { FirstReferrerData, MwDiagData } from './first-referrer-cookie'
|
||||
import { isExternalReferrer, parseFirstReferrerCookie, parseMwDiagCookie } from './first-referrer-cookie'
|
||||
import {
|
||||
isExternalReferrer,
|
||||
parseFirstReferrerCookie,
|
||||
parseMwDiagCookie,
|
||||
} from './first-referrer-cookie'
|
||||
import { ensurePlatformSuffix, isBrowser } from './helpers'
|
||||
import { useParams, useTelemetryCookie } from './hooks'
|
||||
import { posthogClient, type ClientTelemetryEvent } from './posthog-client'
|
||||
|
||||
Reference in new issue
Block a user