From 9a2140b29118054ac793e2d91e152937f650bc84 Mon Sep 17 00:00:00 2001 From: Jordi Enric <37541088+jordienr@users.noreply.github.com> Date: Tue, 23 Sep 2025 13:56:03 +0200 Subject: [PATCH 1/7] chart footer tooltip improvs (#38858) improvs --- .../components/ui/Charts/ComposedChart.utils.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/studio/components/ui/Charts/ComposedChart.utils.tsx b/apps/studio/components/ui/Charts/ComposedChart.utils.tsx index ecbdbfd2a5e03..abddab6bd6491 100644 --- a/apps/studio/components/ui/Charts/ComposedChart.utils.tsx +++ b/apps/studio/components/ui/Charts/ComposedChart.utils.tsx @@ -2,7 +2,7 @@ import dayjs from 'dayjs' import { useState } from 'react' -import { cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui' +import { cn, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from 'ui' import { CHART_COLORS, DateTimeFormats } from './Charts.constants' import { numberFormatter } from './Charts.utils' import { formatBytes } from 'lib/helpers' @@ -293,12 +293,12 @@ const CustomLabel = ({ const LabelItem = ({ entry }: { entry: any }) => { const attribute = attributes?.find((a) => a.attribute === entry.name) const isMax = entry.name === maxValueAttribute?.attribute - const isHovered = hoveredLabel === entry.name const isHidden = hiddenAttributes?.has(entry.name) + const color = isHidden ? 'gray' : entry.color const Label = () => (
- {getIcon(entry.name, entry.color)} + {getIcon(entry.name, color)} {attribute?.label || entry.name} @@ -310,17 +310,17 @@ const CustomLabel = ({ return (
)} diff --git a/apps/studio/components/ui/Charts/ChartHighlightActions.tsx b/apps/studio/components/ui/Charts/ChartHighlightActions.tsx index 21633ec2dd7ad..7a66eaef0bcd4 100644 --- a/apps/studio/components/ui/Charts/ChartHighlightActions.tsx +++ b/apps/studio/components/ui/Charts/ChartHighlightActions.tsx @@ -1,7 +1,7 @@ -import React, { useEffect, useState } from 'react' -import { useRouter } from 'next/router' import dayjs from 'dayjs' -import { ArrowRight, LogsIcon, SearchIcon } from 'lucide-react' +import { ArrowRight, SearchIcon } from 'lucide-react' +import { ReactNode, useEffect, useMemo, useState } from 'react' + import { cn, DropdownMenu, @@ -12,17 +12,33 @@ import { DropdownMenuTrigger, } from 'ui' import { ChartHighlight } from './useChartHighlight' -import { UpdateDateRange } from 'pages/project/[ref]/reports/database' -const ChartHighlightActions = ({ +export type UpdateDateRange = (from: string, to: string) => void + +export type ChartHighlightActionContext = { + start: string + end: string + clear: () => void +} + +export type ChartHighlightAction = { + id: string + label: string | ((ctx: ChartHighlightActionContext) => string) + icon?: ReactNode + isDisabled?: (ctx: ChartHighlightActionContext) => boolean + rightSlot?: ReactNode | ((ctx: ChartHighlightActionContext) => ReactNode) + onSelect: (ctx: ChartHighlightActionContext) => void +} + +export const ChartHighlightActions = ({ chartHighlight, updateDateRange, + actions, }: { chartHighlight?: ChartHighlight - updateDateRange: UpdateDateRange + updateDateRange?: UpdateDateRange + actions?: ChartHighlightAction[] }) => { - const router = useRouter() - const { ref } = router.query const { left: selectedRangeStart, right: selectedRangeEnd, clearHighlight } = chartHighlight ?? {} const [isOpen, setIsOpen] = useState(!!chartHighlight?.popoverPosition) @@ -30,18 +46,34 @@ const ChartHighlightActions = ({ setIsOpen(!!chartHighlight?.popoverPosition && selectedRangeStart !== selectedRangeEnd) }, [chartHighlight?.popoverPosition]) - const disableZoomIn = dayjs(selectedRangeEnd).diff(dayjs(selectedRangeStart), 'minutes') < 10 - const handleZoomIn = () => { - if (disableZoomIn) return - updateDateRange(selectedRangeStart!, selectedRangeEnd!) - clearHighlight && clearHighlight() - } + const ctx: ChartHighlightActionContext | undefined = + selectedRangeStart && selectedRangeEnd && clearHighlight + ? { start: selectedRangeStart, end: selectedRangeEnd, clear: clearHighlight } + : undefined - const handleOpenLogsExplorer = () => { - const rangeQueryParams = `?its=${selectedRangeStart}&ite=${selectedRangeEnd}` - router.push(`/project/${ref}/logs/postgres-logs${rangeQueryParams}`) - clearHighlight && clearHighlight() - } + const defaultActions: ChartHighlightAction[] = useMemo(() => { + if (!updateDateRange || !ctx) return [] + const isDisabled = dayjs(ctx.end).diff(dayjs(ctx.start), 'minutes') < 10 + return [ + { + id: 'zoom-in', + label: 'Zoom in', + icon: , + rightSlot: isDisabled ? Min. 10 minutes : null, + isDisabled: () => isDisabled, + onSelect: ({ start, end, clear }) => { + if (isDisabled) return + updateDateRange(start, end) + clear() + }, + }, + ] + }, [ctx, updateDateRange]) + + const allActions: ChartHighlightAction[] = useMemo(() => { + const provided = actions ?? [] + return [...defaultActions, ...provided] + }, [defaultActions, actions]) return ( @@ -54,37 +86,41 @@ const ChartHighlightActions = ({ }} /> - + {dayjs(selectedRangeStart).format('MMM D, H:mm')} {dayjs(selectedRangeEnd).format('MMM D, H:mm')} - - - - - - + {allActions.map((action) => { + const disabled = ctx && action.isDisabled ? action.isDisabled(ctx) : false + let labelNode: ReactNode = null + if (typeof action.label === 'function') { + labelNode = ctx ? action.label(ctx) : null + } else { + labelNode = action.label + } + let rightNode: ReactNode = null + if (typeof action.rightSlot === 'function') { + rightNode = ctx ? action.rightSlot(ctx) : null + } else { + rightNode = action.rightSlot ?? null + } + return ( + + + + ) + })} ) } - -export default ChartHighlightActions diff --git a/apps/studio/components/ui/Charts/ComposedChart.tsx b/apps/studio/components/ui/Charts/ComposedChart.tsx index 36a353c918e1a..8177227a80e10 100644 --- a/apps/studio/components/ui/Charts/ComposedChart.tsx +++ b/apps/studio/components/ui/Charts/ComposedChart.tsx @@ -19,7 +19,7 @@ import { import { CategoricalChartState } from 'recharts/types/chart/types' import { cn } from 'ui' import { ChartHeader } from './ChartHeader' -import ChartHighlightActions from './ChartHighlightActions' +import { ChartHighlightActions, ChartHighlightAction } from './ChartHighlightActions' import { CHART_COLORS, DateTimeFormats, @@ -64,6 +64,7 @@ export interface ComposedChartProps extends CommonChartProps { syncId?: string docsUrl?: string sql?: string + highlightActions?: ChartHighlightAction[] } export function ComposedChart({ @@ -101,6 +102,7 @@ export function ComposedChart({ syncId, docsUrl, sql, + highlightActions, }: ComposedChartProps) { const { resolvedTheme } = useTheme() const { hoveredIndex, syncTooltip, setHover, clearHover } = useChartHoverState( @@ -336,6 +338,7 @@ export function ComposedChart({ { setIsActiveHoveredChart(true) if (e.activeTooltipIndex !== focusDataIndex) { @@ -390,7 +393,7 @@ export function ComposedChart({ /> - showTooltip ? ( + showTooltip && !showHighlightActions ? ( )} - + {data && (
{ + return [ + { + id: 'open-logs', + label: 'Open in Postgres Logs', + icon: , + onSelect: ({ start, end }) => { + const projectRef = ref as string + if (!projectRef) return + const url = `/project/${projectRef}/logs/postgres-logs?its=${start}&ite=${end}` + router.push(url) + }, + }, + ] + }, [ref]) + if (loading) { return ( -

Loading data for {label}

-
+ ) } @@ -288,6 +302,7 @@ const ComposedChartHandler = ({ valuePrecision={valuePrecision} hideChartType={hideChartType} syncId={syncId} + highlightActions={highlightActions} {...otherProps} /> diff --git a/apps/studio/data/reports/database-charts.ts b/apps/studio/data/reports/database-charts.ts index 2f4eb36e21df2..ea287b5b8c94d 100644 --- a/apps/studio/data/reports/database-charts.ts +++ b/apps/studio/data/reports/database-charts.ts @@ -2,10 +2,10 @@ import { numberFormatter } from 'components/ui/Charts/Charts.utils' import { ReportAttributes } from 'components/ui/Charts/ComposedChart.utils' import { formatBytes } from 'lib/helpers' import { Organization } from 'types' -import { Project } from '../projects/project-detail-query' import { DiskAttributesData } from '../config/disk-attributes-query' import { MaxConnectionsData } from '../database/max-connections-query' import { PgbouncerConfigData } from '../database/pgbouncer-config-query' +import { Project } from '../projects/project-detail-query' export const getReportAttributes = ( org: Organization, @@ -111,14 +111,14 @@ export const getReportAttributes = ( { attribute: 'disk_iops_write', provider: 'infra-monitoring', - label: 'write IOPS', + label: 'Write IOPS', tooltip: 'Number of write operations per second. High values indicate frequent data writes, logging, or transaction activity', }, { attribute: 'disk_iops_read', provider: 'infra-monitoring', - label: 'read IOPS', + label: 'Read IOPS', tooltip: 'Number of read operations per second. High values suggest frequent disk reads due to queries or poor caching', }, @@ -261,7 +261,7 @@ export const getReportAttributesV2: ( { attribute: 'ram_usage_cache_and_buffers', provider: 'infra-monitoring', - label: 'Cache + buffers', + label: 'Cache + Buffers', tooltip: 'RAM used by the operating system page cache and PostgreSQL buffers to accelerate disk reads/writes', }, @@ -366,14 +366,14 @@ export const getReportAttributesV2: ( { attribute: 'disk_iops_write', provider: 'infra-monitoring', - label: 'write IOPS', + label: 'Write IOPS', tooltip: 'Number of write operations per second. High values indicate frequent data writes, logging, or transaction activity', }, { attribute: 'disk_iops_read', provider: 'infra-monitoring', - label: 'read IOPS', + label: 'Read IOPS', tooltip: 'Number of read operations per second. High values suggest frequent disk reads due to queries or poor caching', }, diff --git a/apps/studio/pages/project/[ref]/reports/auth.tsx b/apps/studio/pages/project/[ref]/reports/auth.tsx index 1a26d3fdee983..e077eb62bfcef 100644 --- a/apps/studio/pages/project/[ref]/reports/auth.tsx +++ b/apps/studio/pages/project/[ref]/reports/auth.tsx @@ -1,7 +1,7 @@ import { useQueryClient } from '@tanstack/react-query' import { useParams } from 'common' import dayjs from 'dayjs' -import { ArrowRight, RefreshCw } from 'lucide-react' +import { ArrowRight, LogsIcon, RefreshCw } from 'lucide-react' import { useState } from 'react' import { ReportChartV2 } from 'components/interfaces/Reports/v2/ReportChartV2' @@ -22,6 +22,8 @@ import { useReportDateRange } from 'hooks/misc/useReportDateRange' import type { NextPageWithLayout } from 'types' import { createAuthReportConfig } from 'data/reports/v2/auth.config' import { ReportSettings } from 'components/ui/Charts/ReportSettings' +import type { ChartHighlightAction } from 'components/ui/Charts/ChartHighlightActions' +import { useRouter } from 'next/router' const AuthReport: NextPageWithLayout = () => { return ( @@ -91,6 +93,29 @@ const AuthUsage = () => { setTimeout(() => setIsRefreshing(false), 1000) } + const router = useRouter() + + const highlightActions: ChartHighlightAction[] = [ + { + id: 'api-gateway-logs', + label: 'Open in API Gateway Logs', + icon: , + onSelect: ({ start, end, clear }) => { + const url = `/project/${ref}/logs/edge-logs?its=${start}&ite=${end}&f={"product":{"auth":true}}` + router.push(url) + }, + }, + { + id: 'auth-logs', + label: 'Open in Auth Logs', + icon: , + onSelect: ({ start, end, clear }) => { + const url = `/project/${ref}/logs/auth-logs?its=${start}&ite=${end}` + router.push(url) + }, + }, + ] + return ( <> @@ -149,6 +174,7 @@ const AuthUsage = () => { filters={{ status_code: null, }} + highlightActions={highlightActions} /> ))}
From b3b5652555d92b80615392e49d55eb04c959f2b9 Mon Sep 17 00:00:00 2001 From: Pamela Chia Date: Tue, 23 Sep 2025 06:22:27 -0700 Subject: [PATCH 3/7] Feat/add tracking and phflag for new home (#38853) * add phflag * track homev2 clicks * track v2 drags * remove old home tracking and extra visibility tracking * add advisor, usage, and report clicks * fix import type * track remove charts from home * rename actions to follow conventions * fix lints * simplify activity stats and add issue count property * reduce useSelectedProjectQuery * revert onClick and improve event properties * Fix broken images --------- Co-authored-by: Joshen Lim --- .../interfaces/HomeNew/ActivityStats.tsx | 12 + .../interfaces/HomeNew/AdvisorSection.tsx | 54 +++- .../HomeNew/CustomReportSection.tsx | 48 +++ .../HomeNew/GettingStarted/GettingStarted.tsx | 66 +++- .../GettingStarted/GettingStartedSection.tsx | 140 +++++++-- .../components/interfaces/HomeNew/Home.tsx | 18 ++ .../HomeNew/ProjectUsageSection.tsx | 54 +++- apps/studio/components/ui/SingleStat.tsx | 36 ++- apps/studio/pages/project/[ref]/index.tsx | 4 +- packages/common/telemetry-constants.ts | 293 ++++++++++++++++++ 10 files changed, 680 insertions(+), 45 deletions(-) diff --git a/apps/studio/components/interfaces/HomeNew/ActivityStats.tsx b/apps/studio/components/interfaces/HomeNew/ActivityStats.tsx index f8230dfd20af4..d740bc3d8e5ca 100644 --- a/apps/studio/components/interfaces/HomeNew/ActivityStats.tsx +++ b/apps/studio/components/interfaces/HomeNew/ActivityStats.tsx @@ -62,6 +62,10 @@ export const ActivityStats = () => { href={`/project/${ref}/database/migrations`} icon={} label={Last migration} + trackingProperties={{ + stat_type: 'migrations', + stat_value: migrationsData?.length ?? 0, + }} value={ isLoadingMigrations ? ( @@ -81,6 +85,10 @@ export const ActivityStats = () => { href={`/project/${ref}/database/backups/scheduled`} icon={} label={Last backup} + trackingProperties={{ + stat_type: 'backups', + stat_value: backupsData?.backups?.length ?? 0, + }} value={ isLoadingBackups ? ( @@ -103,6 +111,10 @@ export const ActivityStats = () => { href={`/project/${ref}/branches`} icon={} label={{isDefaultProject ? 'Recent branch' : 'Branch Created'}} + trackingProperties={{ + stat_type: 'branches', + stat_value: branchesData?.length ?? 0, + }} value={ isLoadingBranches ? ( diff --git a/apps/studio/components/interfaces/HomeNew/AdvisorSection.tsx b/apps/studio/components/interfaces/HomeNew/AdvisorSection.tsx index e3a5b9d3cf759..65858938c2637 100644 --- a/apps/studio/components/interfaces/HomeNew/AdvisorSection.tsx +++ b/apps/studio/components/interfaces/HomeNew/AdvisorSection.tsx @@ -10,6 +10,8 @@ import { } from 'components/interfaces/Linter/Linter.utils' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { Lint, useProjectLintsQuery } from 'data/lint/lint-query' +import { useSendEventMutation } from 'data/telemetry/send-event-mutation' +import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' import { AiIconAnimation, @@ -32,6 +34,8 @@ export const AdvisorSection = () => { const { ref: projectRef } = useParams() const { data: lints, isLoading: isLoadingLints } = useProjectLintsQuery({ projectRef }) const snap = useAiAssistantStateSnapshot() + const { mutate: sendEvent } = useSendEventMutation() + const { data: organization } = useSelectedOrganizationQuery() const [selectedLint, setSelectedLint] = useState(null) @@ -54,11 +58,40 @@ export const AdvisorSection = () => { const handleAskAssistant = useCallback(() => { snap.toggleAssistant() - }, [snap]) + if (projectRef && organization?.slug) { + sendEvent({ + action: 'home_advisor_ask_assistant_clicked', + properties: { + issues_count: totalErrors, + }, + groups: { + project: projectRef, + organization: organization.slug, + }, + }) + } + }, [snap, sendEvent, projectRef, organization, totalErrors]) - const handleCardClick = useCallback((lint: Lint) => { - setSelectedLint(lint) - }, []) + const handleCardClick = useCallback( + (lint: Lint) => { + setSelectedLint(lint) + if (projectRef && organization?.slug) { + sendEvent({ + action: 'home_advisor_issue_card_clicked', + properties: { + issue_category: lint.categories[0] || 'UNKNOWN', + issue_name: lint.name, + issues_count: totalErrors, + }, + groups: { + project: projectRef, + organization: organization.slug, + }, + }) + } + }, + [sendEvent, projectRef, organization] + ) return (
@@ -111,6 +144,19 @@ export const AdvisorSection = () => { open: true, initialInput: createLintSummaryPrompt(lint), }) + if (projectRef && organization?.slug) { + sendEvent({ + action: 'home_advisor_fix_issue_clicked', + properties: { + issue_category: lint.categories[0] || 'UNKNOWN', + issue_name: lint.name, + }, + groups: { + project: projectRef, + organization: organization.slug, + }, + }) + } }} tooltip={{ content: { side: 'bottom', text: 'Help me fix this issue' }, diff --git a/apps/studio/components/interfaces/HomeNew/CustomReportSection.tsx b/apps/studio/components/interfaces/HomeNew/CustomReportSection.tsx index 00e487c2052f4..8737860d1dd96 100644 --- a/apps/studio/components/interfaces/HomeNew/CustomReportSection.tsx +++ b/apps/studio/components/interfaces/HomeNew/CustomReportSection.tsx @@ -23,7 +23,9 @@ import { AnalyticsInterval } from 'data/analytics/constants' import { useContentInfiniteQuery } from 'data/content/content-infinite-query' import { Content } from 'data/content/content-query' import { useContentUpsertMutation } from 'data/content/content-upsert-mutation' +import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { uuidv4 } from 'lib/helpers' import { useProfile } from 'lib/profile' import type { Dashboards } from 'types' @@ -35,6 +37,8 @@ export function CustomReportSection() { const endDate = dayjs().toISOString() const { ref } = useParams() const { profile } = useProfile() + const { mutate: sendEvent } = useSendEventMutation() + const { data: organization } = useSelectedOrganizationQuery() const { data: reportsData } = useContentInfiniteQuery( { projectRef: ref, type: 'report', name: 'Home', limit: 1 }, @@ -185,6 +189,20 @@ export function CustomReportSection() { content: newReport, }, }) + + if (ref && organization?.slug) { + sendEvent({ + action: 'home_custom_report_block_added', + properties: { + block_id: snippet.id, + position: 0, + }, + groups: { + project: ref, + organization: organization.slug, + }, + }) + } return } const current = [...editableReport.layout] @@ -193,16 +211,46 @@ export function CustomReportSection() { const updated = { ...editableReport, layout: current } 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, + }, + }) + } } const handleRemoveChart = ({ metric }: { metric: { key: string } }) => { if (!editableReport) return + const removedChart = editableReport.layout.find( + (x) => x.attribute === (metric.key as unknown as Dashboards.Chart['attribute']) + ) const nextLayout = editableReport.layout.filter( (x) => x.attribute !== (metric.key as unknown as Dashboards.Chart['attribute']) ) const updated = { ...editableReport, layout: nextLayout } 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, + }, + }) + } } const handleUpdateChart = ( diff --git a/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStarted.tsx b/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStarted.tsx index e5fa1e266c177..b07d0819f5685 100644 --- a/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStarted.tsx +++ b/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStarted.tsx @@ -1,18 +1,45 @@ +import { Check, ChevronLeft, ChevronRight } from 'lucide-react' +import Image from 'next/image' import Link from 'next/link' import { useEffect, useState } from 'react' -import { Check, ChevronLeft, ChevronRight } from 'lucide-react' -import { cn, Button, Card, CardContent, CardHeader, CardTitle, Badge } from 'ui' - -import { GettingStartedStep } from './GettingStartedSection' -import Image from 'next/image' import { BASE_PATH } from 'lib/constants' +import { Badge, Button, Card, CardContent, cn } from 'ui' +import { GettingStartedAction, GettingStartedStep } from './GettingStartedSection' + +// Determine action type for tracking +const getActionType = (action: GettingStartedAction): 'primary' | 'ai_assist' | 'external_link' => { + // Check if it's an AI assist action (has AiIconAnimation or "Do it for me"/"Generate" labels) + if ( + action.label?.toLowerCase().includes('do it for me') || + action.label?.toLowerCase().includes('generate') || + action.label?.toLowerCase().includes('create policies for me') + ) { + return 'ai_assist' + } + // Check if it's an external link (href that doesn't start with /project/) + if (action.href && !action.href.startsWith('/project/')) { + return 'external_link' + } + return 'primary' +} export interface GettingStartedProps { steps: GettingStartedStep[] + onStepClick: ({ + stepIndex, + stepTitle, + actionType, + wasCompleted, + }: { + stepIndex: number + stepTitle: string + actionType: 'primary' | 'ai_assist' | 'external_link' + wasCompleted: boolean + }) => void } -export function GettingStarted({ steps }: GettingStartedProps) { +export function GettingStarted({ steps, onStepClick }: GettingStartedProps) { const [activeStepKey, setActiveStepKey] = useState(steps[0]?.key ?? null) useEffect(() => { @@ -172,6 +199,8 @@ export function GettingStarted({ steps }: GettingStartedProps) { return
{action.component}
} + const actionType = getActionType(action) + if (action.href) { return ( ) } @@ -191,7 +232,16 @@ export function GettingStarted({ steps }: GettingStartedProps) { key={`${activeStep.key}-action-${i}`} type={action.variant ?? 'default'} icon={action.icon} - onClick={action.onClick} + onClick={() => { + action.onClick?.() + onStepClick({ + stepIndex: activeStepIndex, + stepTitle: activeStep.title, + actionType, + wasCompleted: activeStep.status === 'complete', + }) + }} + className="text-foreground-light hover:text-foreground" > {action.label} diff --git a/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStartedSection.tsx b/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStartedSection.tsx index 571e8df0ca250..c567f0d3714fd 100644 --- a/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStartedSection.tsx +++ b/apps/studio/components/interfaces/HomeNew/GettingStarted/GettingStartedSection.tsx @@ -1,26 +1,27 @@ -import { useParams } from 'common' -import { useBranchesQuery } from 'data/branches/branches-query' -import { useTablesQuery } from 'data/tables/tables-query' -import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' -import { useRouter } from 'next/router' -import { useCallback, useMemo, useState } from 'react' -import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' - -import { GettingStarted } from './GettingStarted' -import { FrameworkSelector } from './FrameworkSelector' import { + BarChart3, Code, Database, + GitBranch, + Shield, Table, - User, + Table2, Upload, + User, UserPlus, - BarChart3, - Shield, - Table2, - GitBranch, } from 'lucide-react' +import { useRouter } from 'next/router' +import { useCallback, useMemo, useState } from 'react' + +import { useParams } from 'common' import { FRAMEWORKS } from 'components/interfaces/Connect/Connect.constants' +import { useBranchesQuery } from 'data/branches/branches-query' +import { useTablesQuery } from 'data/tables/tables-query' +import { useSendEventMutation } from 'data/telemetry/send-event-mutation' +import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' +import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' +import { BASE_PATH } from 'lib/constants' +import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' import { AiIconAnimation, Button, @@ -30,7 +31,8 @@ import { ToggleGroup, ToggleGroupItem, } from 'ui' -import { BASE_PATH } from 'lib/constants' +import { FrameworkSelector } from './FrameworkSelector' +import { GettingStarted } from './GettingStarted' export type GettingStartedAction = { label: string @@ -61,13 +63,16 @@ export function GettingStartedSection({ onChange: (v: GettingStartedState) => void }) { const { data: project } = useSelectedProjectQuery() + const { data: organization } = useSelectedOrganizationQuery() const { ref } = useParams() const aiSnap = useAiAssistantStateSnapshot() const router = useRouter() + const { mutate: sendEvent } = useSendEventMutation() // Local state for framework selector preview const [selectedFramework, setSelectedFramework] = useState(FRAMEWORKS[0]?.key ?? 'nextjs') const workflow: 'no-code' | 'code' | null = value === 'code' || value === 'no-code' ? value : null + const [previousWorkflow, setPreviousWorkflow] = useState<'no-code' | 'code' | null>(null) const { data: tablesData } = useTablesQuery({ projectRef: project?.ref, @@ -156,7 +161,7 @@ export function GettingStartedSection({ status: tablesCount > 0 ? 'complete' : 'incomplete', title: 'Design your database schema', icon: , - image: '/img/getting-started/declarative-schemas.png', + image: `${BASE_PATH}/img/getting-started/declarative-schemas.png`, description: 'Next, create a schema file that defines the structure of your database, either following our declarative schema guide or asking the AI assistant to generate one for you.', actions: [ @@ -324,7 +329,7 @@ export function GettingStartedSection({ status: tablesCount > 0 ? 'complete' : 'incomplete', title: 'Create your first table', icon: , - image: '/img/getting-started/sample.png', + image: `${BASE_PATH}/img/getting-started/sample.png`, description: "To kick off your new project, let's start by creating your very first database table using either the table editor or the AI assistant to shape the structure for you.", actions: [ @@ -488,7 +493,24 @@ export function GettingStartedSection({ v && onChange(v as 'no-code' | 'code')} + onValueChange={(v) => { + if (v) { + const newWorkflow = v as 'no-code' | 'code' + setPreviousWorkflow(workflow) + onChange(newWorkflow) + sendEvent({ + action: 'home_getting_started_workflow_clicked', + properties: { + workflow: newWorkflow === 'no-code' ? 'no_code' : 'code', + is_switch: previousWorkflow !== null, + }, + groups: { + project: project?.ref || '', + organization: organization?.slug || '', + }, + }) + } + }} > -
@@ -545,7 +591,21 @@ export function GettingStartedSection({