Move the user activity into the user panel.

This commit is contained in:
Ivan Vasilov committed 2026-07-28 14:04:37 +02:00
1 parent 397ecce23c
commit 8e8d2a9ca4
16 files changed
+196 -447

No files matched your search

-1
View File
@@ -276,7 +276,6 @@ These are the layout-only TanStack files. Most hold a single product layout comp
- [x] A `routes/project/$ref/observability/query-performance.tsx` ← `pages/project/[ref]/observability/query-performance.tsx`
- [x] A `routes/project/$ref/observability/realtime.tsx` ← `pages/project/[ref]/observability/realtime.tsx`
- [x] A `routes/project/$ref/observability/storage.tsx` ← `pages/project/[ref]/observability/storage.tsx`
- [x] A `routes/project/$ref/observability/user-activity.tsx` ← `pages/project/[ref]/observability/user-activity.tsx`
### Project shell — `/advisors/*`
@@ -0,0 +1,153 @@
import { useParams } from 'common'
import { useMemo, useState } from 'react'
import { Button, cn } from 'ui'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import {
buildUserActivitySearch,
isErrorLevel,
mapLogRowToActivityEvent,
type UserActivityEvent,
} from './UserActivity.constants'
import { UserActivityEventDetailSheet } from './UserActivityEventDetailSheet'
import { UserActivityTimeline } from './UserActivityTimeline'
import { PANEL_PADDING } from './Users.constants'
import { REPORT_DATERANGE_HELPER_LABELS } from '@/components/interfaces/Reports/Reports.constants'
import { LogsDatePicker } from '@/components/interfaces/Settings/Logs/Logs.DatePickers'
import UpgradePrompt from '@/components/interfaces/Settings/Logs/UpgradePrompt'
import { AlertError } from '@/components/ui/AlertError'
import { User } from '@/data/auth/users-infinite-query'
import { useUnifiedLogsInfiniteQuery } from '@/data/logs/unified-logs-infinite-query'
import { useReportDateRange } from '@/hooks/misc/useReportDateRange'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
type UserActivityFilter = 'all' | 'errors'
interface UserActivityTabProps {
user: User
}
export const UserActivityTab = ({ user }: UserActivityTabProps) => {
const { ref: projectRef } = useParams()
const [filter, setFilter] = useState<UserActivityFilter>('all')
const [showDatePicker, setShowDatePicker] = useState(false)
const [payloadEvent, setPayloadEvent] = useState<UserActivityEvent | null>(null)
const {
selectedDateRange,
datePickerValue,
datePickerHelpers,
showUpgradePrompt,
setShowUpgradePrompt,
handleDatePickerChange,
} = useReportDateRange(REPORT_DATERANGE_HELPER_LABELS.LAST_60_MINUTES)
const search = useMemo(
() =>
buildUserActivitySearch({
user: user.id,
date: [
new Date(selectedDateRange.period_start.date),
new Date(selectedDateRange.period_end.date),
],
}),
[user.id, selectedDateRange]
)
const {
data,
isLoading,
isPlaceholderData,
isError,
error,
hasNextPage,
fetchNextPage,
isFetchingNextPage,
} = useUnifiedLogsInfiniteQuery({ projectRef, search })
const events = useMemo(() => {
const rows = data?.pages?.flatMap((page) => page.data ?? []) ?? []
const mapped = rows.map(mapLogRowToActivityEvent)
return filter === 'errors' ? mapped.filter((event) => isErrorLevel(event.level)) : mapped
}, [data?.pages, filter])
return (
<div className={cn('flex flex-col gap-y-6', PANEL_PADDING)}>
<div className="flex flex-wrap items-center justify-end gap-x-2 gap-y-2">
<div className="flex items-center border border-strong rounded-full w-min h-7">
<button
tabIndex={0}
className={cn(
'text-xs w-[80px] h-full text-center rounded-l-full flex items-center justify-center transition',
filter === 'all'
? 'bg-overlay-hover text-foreground'
: 'hover:bg-surface-200 text-foreground-light'
)}
onClick={() => setFilter('all')}
>
All events
</button>
<div className="h-full w-px border-r border-strong" />
<button
tabIndex={0}
className={cn(
'text-xs w-[90px] h-full text-center rounded-r-full flex items-center justify-center transition',
filter === 'errors'
? 'bg-overlay-hover text-foreground'
: 'hover:bg-surface-200 text-foreground-light'
)}
onClick={() => setFilter('errors')}
>
Errors only
</button>
</div>
<LogsDatePicker
onSubmit={handleDatePickerChange}
value={datePickerValue}
helpers={datePickerHelpers}
open={showDatePicker}
onOpenChange={setShowDatePicker}
shortcutId={SHORTCUT_IDS.OBSERVABILITY_TOGGLE_DATE_PICKER}
/>
<UpgradePrompt
show={showUpgradePrompt}
setShowUpgradePrompt={setShowUpgradePrompt}
title="Report date range"
description="Report data can be stored for a maximum of 3 months depending on the plan that your project is on."
source="userActivityDateRange"
/>
</div>
{isLoading || isPlaceholderData ? (
<GenericSkeletonLoader />
) : isError ? (
<AlertError error={error} subject="Failed to load user activity" />
) : (
<>
<UserActivityTimeline
events={events}
projectRef={projectRef}
onViewPayload={setPayloadEvent}
dateRangeStart={selectedDateRange.period_start.date}
dateRangeEnd={selectedDateRange.period_end.date}
/>
{hasNextPage && (
<Button
variant="outline"
size="tiny"
className="self-center"
loading={isFetchingNextPage}
onClick={() => fetchNextPage()}
>
Load more
</Button>
)}
</>
)}
<UserActivityEventDetailSheet event={payloadEvent} onClose={() => setPayloadEvent(null)} />
</div>
)
}
@@ -1,4 +1,5 @@
import { screen } from '@testing-library/react'
import { useFlag } from 'common'
import { http, HttpResponse } from 'msw'
import { ResizablePanelGroup } from 'ui'
import { beforeEach, describe, expect, it, vi } from 'vitest'
@@ -27,6 +28,12 @@ vi.mock('@/hooks/misc/useSelectedProject', () => ({
.mockReturnValue({ data: { ref: 'project-ref', connectionString: 'postgres://' } }),
}))
// Defaults the `UserActivity` ConfigCat flag to off; individual tests override this.
vi.mock('common', async (importOriginal) => ({
...(await importOriginal<typeof import('common')>()),
useFlag: vi.fn().mockReturnValue(false),
}))
// Heavy tab bodies — the sanctioned use of vi.mock. This spec only asserts tab
// visibility, not what the tabs render.
vi.mock('./UserOverview', () => ({
@@ -35,6 +42,9 @@ vi.mock('./UserOverview', () => ({
vi.mock('./UserLogs', () => ({
UserLogs: () => <div data-testid="user-logs" />,
}))
vi.mock('./UserActivityTab', () => ({
UserActivityTab: () => <div data-testid="user-activity" />,
}))
const renderPanel = (disabledFeatures: string[] = []) => {
// The user query flows through pg-meta SQL; mock it at the network boundary.
@@ -88,4 +98,19 @@ describe('UserPanel', () => {
expect(screen.queryByRole('tab', { name: 'Logs' })).not.toBeInTheDocument()
expect(screen.getByRole('tab', { name: 'Raw JSON' })).toBeInTheDocument()
})
it('shows the Activity tab when the UserActivity flag is enabled', async () => {
vi.mocked(useFlag).mockReturnValue(true)
renderPanel([])
expect(await screen.findByRole('tab', { name: 'Overview' })).toBeInTheDocument()
expect(screen.getByRole('tab', { name: 'Activity' })).toBeInTheDocument()
})
it('hides the Activity tab when the UserActivity flag is disabled', async () => {
renderPanel([])
expect(await screen.findByRole('tab', { name: 'Overview' })).toBeInTheDocument()
expect(screen.queryByRole('tab', { name: 'Activity' })).not.toBeInTheDocument()
})
})
@@ -1,3 +1,4 @@
import { useFlag } from 'common'
import { X } from 'lucide-react'
import { parseAsString, useQueryState } from 'nuqs'
import { useState } from 'react'
@@ -15,6 +16,7 @@ import {
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { SimpleCodeBlock } from 'ui-patterns/SimpleCodeBlock'
import { UserActivityTab } from './UserActivityTab'
import { UserLogs } from './UserLogs'
import { UserOverview } from './UserOverview'
import { PANEL_PADDING } from './Users.constants'
@@ -26,13 +28,14 @@ import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
export const UserPanel = () => {
const { data: project } = useSelectedProjectQuery()
const showLogs = useIsFeatureEnabled('logs:all')
const showActivity = useFlag('UserActivity')
const [selectedId, setSelectedId] = useQueryState(
'show',
parseAsString.withOptions({ history: 'push', clearOnDefault: true })
)
const [view, setView] = useState<'overview' | 'raw' | 'logs'>('overview')
const [view, setView] = useState<'overview' | 'raw' | 'logs' | 'activity'>('overview')
const [searchQuery, setSearchQuery] = useState('')
const { data: selectedUser, isPending } = useUserQuery({
@@ -69,7 +72,7 @@ export const UserPanel = () => {
<Tabs
value={view}
className="flex flex-col h-full"
onValueChange={(value) => setView(value as 'overview' | 'raw' | 'logs')}
onValueChange={(value) => setView(value as 'overview' | 'raw' | 'logs' | 'activity')}
>
{isPending ? (
<div>
@@ -95,6 +98,14 @@ export const UserPanel = () => {
Logs
</TabsTrigger>
)}
{showActivity && (
<TabsTrigger
value="activity"
className="px-0 pb-0 h-full text-xs data-[state=active]:bg-transparent shadow-none!"
>
Activity
</TabsTrigger>
)}
<TabsTrigger
value="raw"
className="px-0 pb-0 h-full text-xs data-[state=active]:bg-transparent shadow-none!"
@@ -113,6 +124,11 @@ export const UserPanel = () => {
{selectedUser && <UserLogs user={selectedUser} />}
</TabsContent>
)}
{showActivity && (
<TabsContent value="activity" className={cn('mt-0 grow min-h-0 overflow-y-auto')}>
{selectedUser && <UserActivityTab user={selectedUser} />}
</TabsContent>
)}
<TabsContent
value="raw"
className={cn('mt-0 grow min-h-0 overflow-y-auto', PANEL_PADDING)}
@@ -1,175 +0,0 @@
import { useParams } from 'common'
import { UserSearch } from 'lucide-react'
import { parseAsString, useQueryState } from 'nuqs'
import { useMemo, useState } from 'react'
import { Badge, Button, cn } from 'ui'
import { EmptyStatePresentational } from 'ui-patterns/EmptyStatePresentational'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import {
buildUserActivitySearch,
isErrorLevel,
mapLogRowToActivityEvent,
type UserActivityEvent,
} from './UserActivity.constants'
import { UserActivityEventDetailSheet } from './UserActivityEventDetailSheet'
import { UserActivitySelector } from './UserActivitySelector'
import { UserActivityTimeline } from './UserActivityTimeline'
import { REPORT_DATERANGE_HELPER_LABELS } from '@/components/interfaces/Reports/Reports.constants'
import { LogsDatePicker } from '@/components/interfaces/Settings/Logs/Logs.DatePickers'
import UpgradePrompt from '@/components/interfaces/Settings/Logs/UpgradePrompt'
import { AlertError } from '@/components/ui/AlertError'
import { useUnifiedLogsInfiniteQuery } from '@/data/logs/unified-logs-infinite-query'
import { useReportDateRange } from '@/hooks/misc/useReportDateRange'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
type UserActivityFilter = 'all' | 'errors'
export const UserActivity = () => {
const { ref: projectRef } = useParams()
const { data: project } = useSelectedProjectQuery()
const [selectedUserId, setSelectedUserId] = useQueryState('user', parseAsString)
const [filter, setFilter] = useState<UserActivityFilter>('all')
const [showDatePicker, setShowDatePicker] = useState(false)
const [payloadEvent, setPayloadEvent] = useState<UserActivityEvent | null>(null)
const {
selectedDateRange,
datePickerValue,
datePickerHelpers,
showUpgradePrompt,
setShowUpgradePrompt,
handleDatePickerChange,
} = useReportDateRange(REPORT_DATERANGE_HELPER_LABELS.LAST_60_MINUTES)
const search = useMemo(
() =>
buildUserActivitySearch({
user: selectedUserId ?? '',
date: [
new Date(selectedDateRange.period_start.date),
new Date(selectedDateRange.period_end.date),
],
}),
[selectedUserId, selectedDateRange]
)
const {
data,
isLoading,
isPlaceholderData,
isError,
error,
hasNextPage,
fetchNextPage,
isFetchingNextPage,
} = useUnifiedLogsInfiniteQuery({ projectRef, search }, { enabled: !!selectedUserId })
const events = useMemo(() => {
const rows = data?.pages?.flatMap((page) => page.data ?? []) ?? []
const mapped = rows.map(mapLogRowToActivityEvent)
return filter === 'errors' ? mapped.filter((event) => isErrorLevel(event.level)) : mapped
}, [data?.pages, filter])
return (
<div className="flex flex-col gap-y-6">
<div className="flex flex-col gap-y-3">
<div className="flex flex-col justify-between gap-4 md:flex-row md:items-center">
<div className="flex items-center gap-x-2">
<h1 className="text-xl text-foreground">User Activity</h1>
<Badge variant="warning">Beta</Badge>
</div>
</div>
<div className="flex flex-wrap items-center justify-end gap-x-2 gap-y-2">
<UserActivitySelector
projectRef={projectRef}
connectionString={project?.connectionString}
value={selectedUserId}
onChange={setSelectedUserId}
/>
<div className="flex items-center border border-strong rounded-full w-min h-7">
<button
tabIndex={0}
className={cn(
'text-xs w-[80px] h-full text-center rounded-l-full flex items-center justify-center transition',
filter === 'all'
? 'bg-overlay-hover text-foreground'
: 'hover:bg-surface-200 text-foreground-light'
)}
onClick={() => setFilter('all')}
>
All events
</button>
<div className="h-full w-px border-r border-strong" />
<button
tabIndex={0}
className={cn(
'text-xs w-[90px] h-full text-center rounded-r-full flex items-center justify-center transition',
filter === 'errors'
? 'bg-overlay-hover text-foreground'
: 'hover:bg-surface-200 text-foreground-light'
)}
onClick={() => setFilter('errors')}
>
Errors only
</button>
</div>
<LogsDatePicker
onSubmit={handleDatePickerChange}
value={datePickerValue}
helpers={datePickerHelpers}
open={showDatePicker}
onOpenChange={setShowDatePicker}
shortcutId={SHORTCUT_IDS.OBSERVABILITY_TOGGLE_DATE_PICKER}
/>
<UpgradePrompt
show={showUpgradePrompt}
setShowUpgradePrompt={setShowUpgradePrompt}
title="Report date range"
description="Report data can be stored for a maximum of 3 months depending on the plan that your project is on."
source="userActivityDateRange"
/>
</div>
</div>
{!selectedUserId ? (
<EmptyStatePresentational
icon={UserSearch}
title="Select a user"
description="Choose a user from the dropdown above to view their activity."
/>
) : isLoading || isPlaceholderData ? (
<GenericSkeletonLoader />
) : isError ? (
<AlertError error={error} subject="Failed to load user activity" />
) : (
<>
<UserActivityTimeline
events={events}
projectRef={projectRef}
onViewPayload={setPayloadEvent}
dateRangeStart={selectedDateRange.period_start.date}
dateRangeEnd={selectedDateRange.period_end.date}
/>
{hasNextPage && (
<Button
variant="outline"
size="tiny"
className="self-center"
loading={isFetchingNextPage}
onClick={() => fetchNextPage()}
>
Load more
</Button>
)}
</>
)}
<UserActivityEventDetailSheet event={payloadEvent} onClose={() => setPayloadEvent(null)} />
</div>
)
}
@@ -1,203 +0,0 @@
import { keepPreviousData } from '@tanstack/react-query'
import { useDebounce } from '@uidotdev/usehooks'
import { ChevronDown, User as IconUser, Loader2, Search, X } from 'lucide-react'
import { useMemo, useState } from 'react'
import {
Button,
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
Popover,
PopoverContent,
PopoverTrigger,
} from 'ui'
import { getAvatarUrl, getDisplayName } from '@/components/interfaces/Auth/Users/Users.utils'
import { AlertError } from '@/components/ui/AlertError'
import { useUserQuery } from '@/data/auth/user-query'
import { User, useUsersInfiniteQuery } from '@/data/auth/users-infinite-query'
interface UserActivitySelectorProps {
projectRef: string | undefined
connectionString: string | null | undefined
value: string | null
onChange: (userId: string | null) => void
}
export const UserActivitySelector = ({
projectRef,
connectionString,
value,
onChange,
}: UserActivitySelectorProps) => {
const [open, setOpen] = useState(false)
const [searchText, setSearchText] = useState('')
const debouncedSearchText = useDebounce(searchText, 300)
// The last user picked from the list, kept locally so the trigger can show a display
// name/avatar immediately without waiting on a lookup.
const [resolvedUser, setResolvedUser] = useState<User | null>(null)
// On a fresh page load with only `?user=<id>` in the URL (no picked-user cache yet),
// look the user up by id so the trigger can show their name instead of the raw id.
const isResolved = resolvedUser?.id === value
const { data: fetchedUser, isLoading: isLoadingUser } = useUserQuery(
{ projectRef, connectionString, userId: value },
{ enabled: !!value && !isResolved }
)
const displayUser = isResolved ? resolvedUser : (fetchedUser ?? null)
const isResolvingUser = !!value && !displayUser && isLoadingUser
const {
data,
isSuccess,
isPending: isLoading,
isError,
error,
isFetching,
isPlaceholderData,
} = useUsersInfiniteQuery(
{ projectRef, connectionString, keywords: debouncedSearchText.trim().toLowerCase() },
{ placeholderData: keepPreviousData, enabled: open }
)
const users = useMemo(() => data?.pages.flatMap((page) => page.result) ?? [], [data?.pages])
const isSearching = isPlaceholderData && isFetching
const handleSelect = (user: User) => {
if (!user.id) return
setResolvedUser(user)
onChange(user.id)
setOpen(false)
setSearchText('')
}
const displayName = displayUser
? getDisplayName(displayUser, displayUser.email ?? displayUser.phone ?? displayUser.id ?? '')
: null
return (
<Popover open={open} onOpenChange={setOpen} modal={false}>
<PopoverTrigger asChild>
<Button
variant="outline"
size="tiny"
className="h-7 justify-start"
iconRight={<ChevronDown className="text-foreground-muted" size={14} strokeWidth={1.5} />}
>
{isResolvingUser ? (
<span className="flex items-center gap-x-2">
<Loader2 className="animate-spin text-foreground-lighter" size={14} />
<span className="text-xs text-foreground-light">Loading user...</span>
</span>
) : value ? (
<span className="flex items-center gap-x-2">
<UserAvatar user={displayUser} />
<span className="text-xs text-foreground">{displayName ?? value}</span>
</span>
) : (
'Select a user'
)}
</Button>
</PopoverTrigger>
<PopoverContent className="p-0 w-80" align="start">
<div className="p-3 flex flex-col gap-y-2">
<InputGroup>
<InputGroupInput
size="tiny"
className="pr-10 border-none"
placeholder="Search by id, email, phone, or name..."
onChange={(e) => setSearchText(e.target.value)}
value={searchText}
autoFocus
/>
<InputGroupAddon>
{isSearching ? (
<Loader2
className="animate-spin text-foreground-lighter"
size={16}
strokeWidth={1.5}
/>
) : (
<Search className="text-foreground-lighter" size={16} strokeWidth={1.5} />
)}
</InputGroupAddon>
{searchText && (
<InputGroupAddon align="inline-end">
<InputGroupButton size="tiny" variant="text" onClick={() => setSearchText('')}>
<span className="sr-only">Clear search</span>
<X size={12} />
</InputGroupButton>
</InputGroupAddon>
)}
</InputGroup>
{isLoading && (
<div className="flex flex-col gap-2 items-center justify-center h-24">
<Loader2 className="animate-spin" size={24} />
<span className="text-foreground-light text-sm">Loading users...</span>
</div>
)}
{isError && <AlertError error={error} subject="Failed to retrieve users" />}
{isSuccess &&
(users.length > 0 ? (
<ul className="divide-y max-h-[240px] overflow-y-auto -mx-3">
{users.map((user) => (
<li key={user.id}>
<UserActivitySelectorRow user={user} onClick={handleSelect} />
</li>
))}
</ul>
) : (
<div className="flex items-center justify-center h-16">
<p className="text-foreground-light text-xs" role="status">
No users found
</p>
</div>
))}
</div>
</PopoverContent>
</Popover>
)
}
const UserAvatar = ({ user }: { user: User | null }) => {
const avatarUrl = user ? getAvatarUrl(user) : undefined
if (avatarUrl) {
return <img className="rounded-full w-5 h-5" src={avatarUrl} alt="" />
}
return (
<div className="rounded-full w-[21px] h-[21px] bg-surface-300 border flex items-center justify-center text-foreground-lighter">
<IconUser size={12} strokeWidth={2} />
</div>
)
}
const UserActivitySelectorRow = ({
user,
onClick,
}: {
user: User
onClick: (user: User) => void
}) => {
const emailOrPhone = user.email || user.phone
const displayName = getDisplayName(user, '')
const showDisplayName = displayName && displayName !== emailOrPhone
return (
<button
type="button"
tabIndex={0}
onClick={() => onClick(user)}
className="w-full flex items-center gap-x-3 px-3 py-2 text-left hover:bg-surface-200"
>
<UserAvatar user={user} />
<span className="text-sm flex flex-col">
<span className="text-foreground">{emailOrPhone}</span>
{showDisplayName && <span className="text-foreground-lighter text-xs">{displayName}</span>}
</span>
</button>
)
}
@@ -48,7 +48,6 @@ export const useGenerateObservabilityMenu = () => {
const showOverview = useFlag('observabilityOverview')
const topForPostgres = useFlag('topForPostgres')
const userActivity = useFlag('UserActivity')
const { isSupamonitorEnabled } = useSupamonitorStatus()
const storageSupported = useIsFeatureEnabled('project_storage:all')
@@ -65,15 +64,6 @@ export const useGenerateObservabilityMenu = () => {
},
]
: []),
...(userActivity
? [
{
name: 'User Activity',
key: 'user-activity',
url: `${baseUrl}/user-activity${preservedQueryParams}`,
},
]
: []),
...(isSupamonitorEnabled
? [
{
@@ -1,19 +0,0 @@
import { UserActivity } from '@/components/interfaces/Observability/UserActivity/UserActivity'
import { ReportPadding } from '@/components/interfaces/Reports/ReportPadding'
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/ObservabilityLayout'
import type { NextPageWithLayout } from '@/types'
const UserActivityPage: NextPageWithLayout = () => (
<ReportPadding>
<UserActivity />
</ReportPadding>
)
UserActivityPage.getLayout = (page) => (
<DefaultLayout>
<ObservabilityLayout title="User Activity">{page}</ObservabilityLayout>
</DefaultLayout>
)
export default UserActivityPage
-23
View File
@@ -111,7 +111,6 @@ import { Route as ProjectRefSettingsAddonsRouteImport } from './routes/project/$
import { Route as ProjectRefRealtimeSettingsRouteImport } from './routes/project/$ref/realtime/settings'
import { Route as ProjectRefRealtimePoliciesRouteImport } from './routes/project/$ref/realtime/policies'
import { Route as ProjectRefRealtimeInspectorRouteImport } from './routes/project/$ref/realtime/inspector'
import { Route as ProjectRefObservabilityUserActivityRouteImport } from './routes/project/$ref/observability/user-activity'
import { Route as ProjectRefObservabilityStorageRouteImport } from './routes/project/$ref/observability/storage'
import { Route as ProjectRefObservabilityRealtimeRouteImport } from './routes/project/$ref/observability/realtime'
import { Route as ProjectRefObservabilityQueryPerformanceRouteImport } from './routes/project/$ref/observability/query-performance'
@@ -850,12 +849,6 @@ const ProjectRefRealtimeInspectorRoute =
path: '/inspector',
getParentRoute: () => ProjectRefRealtimeRoute,
} as any)
const ProjectRefObservabilityUserActivityRoute =
ProjectRefObservabilityUserActivityRouteImport.update({
id: '/user-activity',
path: '/user-activity',
getParentRoute: () => ProjectRefObservabilityRoute,
} as any)
const ProjectRefObservabilityStorageRoute =
ProjectRefObservabilityStorageRouteImport.update({
id: '/storage',
@@ -2200,7 +2193,6 @@ export interface FileRoutesByFullPath {
'/project/$ref/observability/query-performance': typeof ProjectRefObservabilityQueryPerformanceRoute
'/project/$ref/observability/realtime': typeof ProjectRefObservabilityRealtimeRoute
'/project/$ref/observability/storage': typeof ProjectRefObservabilityStorageRoute
'/project/$ref/observability/user-activity': typeof ProjectRefObservabilityUserActivityRoute
'/project/$ref/realtime/inspector': typeof ProjectRefRealtimeInspectorRoute
'/project/$ref/realtime/policies': typeof ProjectRefRealtimePoliciesRoute
'/project/$ref/realtime/settings': typeof ProjectRefRealtimeSettingsRoute
@@ -2497,7 +2489,6 @@ export interface FileRoutesByTo {
'/project/$ref/observability/query-performance': typeof ProjectRefObservabilityQueryPerformanceRoute
'/project/$ref/observability/realtime': typeof ProjectRefObservabilityRealtimeRoute
'/project/$ref/observability/storage': typeof ProjectRefObservabilityStorageRoute
'/project/$ref/observability/user-activity': typeof ProjectRefObservabilityUserActivityRoute
'/project/$ref/realtime/inspector': typeof ProjectRefRealtimeInspectorRoute
'/project/$ref/realtime/policies': typeof ProjectRefRealtimePoliciesRoute
'/project/$ref/realtime/settings': typeof ProjectRefRealtimeSettingsRoute
@@ -2807,7 +2798,6 @@ export interface FileRoutesById {
'/project/$ref/observability/query-performance': typeof ProjectRefObservabilityQueryPerformanceRoute
'/project/$ref/observability/realtime': typeof ProjectRefObservabilityRealtimeRoute
'/project/$ref/observability/storage': typeof ProjectRefObservabilityStorageRoute
'/project/$ref/observability/user-activity': typeof ProjectRefObservabilityUserActivityRoute
'/project/$ref/realtime/inspector': typeof ProjectRefRealtimeInspectorRoute
'/project/$ref/realtime/policies': typeof ProjectRefRealtimePoliciesRoute
'/project/$ref/realtime/settings': typeof ProjectRefRealtimeSettingsRoute
@@ -3117,7 +3107,6 @@ export interface FileRouteTypes {
| '/project/$ref/observability/query-performance'
| '/project/$ref/observability/realtime'
| '/project/$ref/observability/storage'
| '/project/$ref/observability/user-activity'
| '/project/$ref/realtime/inspector'
| '/project/$ref/realtime/policies'
| '/project/$ref/realtime/settings'
@@ -3414,7 +3403,6 @@ export interface FileRouteTypes {
| '/project/$ref/observability/query-performance'
| '/project/$ref/observability/realtime'
| '/project/$ref/observability/storage'
| '/project/$ref/observability/user-activity'
| '/project/$ref/realtime/inspector'
| '/project/$ref/realtime/policies'
| '/project/$ref/realtime/settings'
@@ -3723,7 +3711,6 @@ export interface FileRouteTypes {
| '/project/$ref/observability/query-performance'
| '/project/$ref/observability/realtime'
| '/project/$ref/observability/storage'
| '/project/$ref/observability/user-activity'
| '/project/$ref/realtime/inspector'
| '/project/$ref/realtime/policies'
| '/project/$ref/realtime/settings'
@@ -4712,13 +4699,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ProjectRefRealtimeInspectorRouteImport
parentRoute: typeof ProjectRefRealtimeRoute
}
'/project/$ref/observability/user-activity': {
id: '/project/$ref/observability/user-activity'
path: '/user-activity'
fullPath: '/project/$ref/observability/user-activity'
preLoaderRoute: typeof ProjectRefObservabilityUserActivityRouteImport
parentRoute: typeof ProjectRefObservabilityRoute
}
'/project/$ref/observability/storage': {
id: '/project/$ref/observability/storage'
path: '/storage'
@@ -6605,7 +6585,6 @@ interface ProjectRefObservabilityRouteChildren {
ProjectRefObservabilityQueryPerformanceRoute: typeof ProjectRefObservabilityQueryPerformanceRoute
ProjectRefObservabilityRealtimeRoute: typeof ProjectRefObservabilityRealtimeRoute
ProjectRefObservabilityStorageRoute: typeof ProjectRefObservabilityStorageRoute
ProjectRefObservabilityUserActivityRoute: typeof ProjectRefObservabilityUserActivityRoute
ProjectRefObservabilityIndexRoute: typeof ProjectRefObservabilityIndexRoute
}
@@ -6628,8 +6607,6 @@ const ProjectRefObservabilityRouteChildren: ProjectRefObservabilityRouteChildren
ProjectRefObservabilityQueryPerformanceRoute,
ProjectRefObservabilityRealtimeRoute: ProjectRefObservabilityRealtimeRoute,
ProjectRefObservabilityStorageRoute: ProjectRefObservabilityStorageRoute,
ProjectRefObservabilityUserActivityRoute:
ProjectRefObservabilityUserActivityRoute,
ProjectRefObservabilityIndexRoute: ProjectRefObservabilityIndexRoute,
}
@@ -1,14 +0,0 @@
import { createFileRoute } from '@tanstack/react-router'
import UserActivityPage from '@/pages/project/[ref]/observability/user-activity'
export const Route = createFileRoute('/project/$ref/observability/user-activity')({
component: ObservabilityUserActivityRoute,
staticData: {
observabilityLayoutTitle: 'User Activity',
},
})
function ObservabilityUserActivityRoute() {
return <UserActivityPage dehydratedState={undefined} />
}