diff --git a/apps/studio/components/interfaces/Auth/Overview/OverviewMetrics.tsx b/apps/studio/components/interfaces/Auth/Overview/OverviewMetrics.tsx index a6442abc2e3..b627da3e46c 100644 --- a/apps/studio/components/interfaces/Auth/Overview/OverviewMetrics.tsx +++ b/apps/studio/components/interfaces/Auth/Overview/OverviewMetrics.tsx @@ -1,25 +1,13 @@ import { useQuery } from '@tanstack/react-query' import dayjs from 'dayjs' -import { ChevronRight, ExternalLink, HelpCircle, Telescope } from 'lucide-react' +import { ChevronRight, ExternalLink, Telescope, BarChart2, Bot } from 'lucide-react' import Link from 'next/link' import { useRouter } from 'next/router' import { useParams } from 'common' import { getStatusLevel } from 'components/interfaces/UnifiedLogs/UnifiedLogs.utils' import AlertError from 'components/ui/AlertError' -import { ButtonTooltip } from 'components/ui/ButtonTooltip' -import { DataTableColumnStatusCode } from 'components/ui/DataTable/DataTableColumn/DataTableColumnStatusCode' -import { - Card, - CardContent, - CardHeader, - CardTitle, - cn, - Skeleton, - Tooltip, - TooltipContent, - TooltipTrigger, -} from 'ui' +import { cn, Tooltip, TooltipContent, TooltipTrigger, Button } from 'ui' import { PageSection, PageSectionContent, @@ -27,6 +15,17 @@ import { PageSectionSummary, PageSectionTitle, } from 'ui-patterns/PageSection' +import { + Chart, + ChartCard, + ChartHeader, + ChartActions, + ChartMetric, + ChartTitle, + ChartContent, + ChartEmptyState, + ChartLoadingState, +} from 'ui-patterns/Chart' import { AuthErrorCodeRow, fetchTopAuthErrorCodes, @@ -39,9 +38,13 @@ import { calculatePercentageChange, getApiSuccessRates, getAuthSuccessRates, - getChangeColor, getMetricValues, } from './OverviewUsage.constants' +import { getStatusColor } from 'components/ui/DataTable/DataTable.utils' +import { AiIconAnimation } from 'ui' +import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider' +import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' +import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state' const StatCard = ({ title, @@ -63,84 +66,55 @@ const StatCard = ({ tooltip?: string }) => { const router = useRouter() - const isZeroChange = previous === 0 - const changeColor = isZeroChange - ? 'text-foreground-lighter' - : invert - ? previous >= 0 - ? 'text-destructive' - : 'text-brand' - : getChangeColor(previous) const formattedCurrent = suffix === 'ms' ? current.toFixed(2) : suffix === '%' ? current.toFixed(1) : Math.round(current).toLocaleString() - const signChar = previous > 0 ? '+' : previous < 0 ? '-' : '' + // const signChar = previous > 0 ? '+' : previous < 0 ? '-' : '' + + const actions = [ + { + label: 'Go to Auth Report', + icon: , + onClick: href ? () => router.push(href) : undefined, + }, + ] return ( - - - - {title} - {tooltip && ( - - - - - -

{tooltip}

-
-
- )} -
- } - className="w-6 h-6 absolute right-4 top-3" - onClick={() => router.push(href || '')} - tooltip={{ - content: { - side: 'top', - text: 'Go to Auth Report', - }, - }} - /> -
- - {loading ? ( -
- - -
- ) : ( -
-

{`${formattedCurrent}${suffix}`}

- - {`${signChar}${Math.abs(previous).toFixed(1)}%`} - -
- )} -
-
+ + + + + + + + ) } const LogsLink = ({ href }: { href: string }) => ( - - - + - Go to logs + Go to Logs ) @@ -171,6 +145,8 @@ export const OverviewMetrics = ({ metrics, isLoading, error }: OverviewMetricsPr const { ref } = useParams() const endDate = dayjs().toISOString() const startDate = dayjs().subtract(24, 'hour').toISOString() + const aiSnap = useAiAssistantStateSnapshot() + const { openSidebar } = useSidebarManagerSnapshot() const { current: activeUsersCurrent, previous: activeUsersPrevious } = getMetricValues( metrics, @@ -218,6 +194,20 @@ export const OverviewMetrics = ({ metrics, isLoading, error }: OverviewMetricsPr ? (codeErrData?.result as unknown[]).filter(isAuthErrorCodeRow) : [] + const errorCodesActions = [ + { + label: 'Ask Assistant about Error Codes', + icon: , + onClick: () => { + openSidebar(SIDEBAR_KEYS.AI_ASSISTANT) + aiSnap.newChat({ + name: 'Auth Help', + initialInput: `Can you explain to me what the authentication error codes mean?`, + }) + }, + }, + ] + return ( <> @@ -236,11 +226,11 @@ export const OverviewMetrics = ({ metrics, isLoading, error }: OverviewMetricsPr Usage - + Go to observability - + @@ -292,112 +282,143 @@ export const OverviewMetrics = ({ metrics, isLoading, error }: OverviewMetricsPr /> -
- - 0 ? 'pb-4' : 'pb-0')}> - Auth API Errors - - - - isLoading={isLoadingResp} - data={responseErrors} - columns={[ - { - key: 'request', - header: 'Request', - className: 'w-[60px]', - render: (row) => ( - - {row.method} - - ), - }, - { - key: 'status_code', - header: 'Status', - className: 'w-[60px]', - render: (row) => ( - - ), - }, - { - key: 'path', - header: 'Path', - className: 'flex-shrink-0 w-52', - render: (row) => ( -
- {row.path} -
- ), - }, - { - key: 'count', - header: 'Count', - className: 'text-right flex-shrink-0 ml-auto justify-end', - render: (row) => ( -
{row.count}
- ), - }, - { - key: 'actions', - header: '', - className: 'w-6', - render: (row) => ( -
- -
- ), - }, - ]} - /> -
-
+
+ + + + Auth API Errors + + + } + title="No data to show" + description="It may take up to 24 hours for data to refresh" + /> +
+ } + loadingState={ +
+ +
+ } + > + + isLoading={isLoadingResp} + data={responseErrors} + columns={[ + { + key: 'request', + header: 'Request', + className: 'w-auto !pr-0', + render: (row) => { + const level = getStatusLevel(row.status_code) + const colors = getStatusColor(level) + return ( +
+ + + {row.method} + + + {row.status_code} + + +
+ ) + }, + }, + { + key: 'path', + header: 'Path', + className: 'w-full', + render: (row) => ( + + {row.path} + + ), + }, + { + key: 'count', + header: 'Count', + className: 'text-right flex-shrink-0 ml-auto justify-end', + render: (row) => ( +
+
{row.count}
+ +
+ ), + }, + ]} + /> + + + - - 0 ? 'pb-4' : 'pb-0')}> - Auth Server Errors - - - - isLoading={isLoadingCodes} - data={errorCodes} - columns={[ - { - key: 'error_code', - header: 'Error code', - className: 'w-full', - render: (row) => ( -
- {row.error_code} -
- ), - }, - { - key: 'count', - header: 'Count', - className: 'text-right', - render: (row) => ( -
{row.count}
- ), - }, - { - key: 'actions', - header: '', - className: 'text-right', - render: (row) => ( -
- -
- ), - }, - ]} - /> -
-
+ + + + Auth Server Errors + + + + } + title="No data to show" + description="It may take up to 24 hours for data to refresh" + /> +
+ } + loadingState={ +
+ +
+ } + > + + isLoading={isLoadingCodes} + data={errorCodes} + columns={[ + { + key: 'error_code', + header: 'Error code', + className: 'w-full', + render: (row) => ( + + {row.error_code} + + ), + }, + { + key: 'count', + header: 'Count', + className: 'text-right', + render: (row) => ( +
+
{row.count}
+ +
+ ), + }, + ]} + /> + + +
diff --git a/apps/studio/components/interfaces/Auth/Overview/OverviewTable.tsx b/apps/studio/components/interfaces/Auth/Overview/OverviewTable.tsx index 3f2451a66f3..f77385f990b 100644 --- a/apps/studio/components/interfaces/Auth/Overview/OverviewTable.tsx +++ b/apps/studio/components/interfaces/Auth/Overview/OverviewTable.tsx @@ -21,15 +21,12 @@ export function OverviewTable({ columns, data, isLoading, emptyMessage }: Ove const hasData = !isLoading && data.length > 0 return ( - +
{hasData && ( {columns.map((col) => ( - + {col.header} ))} @@ -61,14 +58,13 @@ export function OverviewTable({ columns, data, isLoading, emptyMessage }: Ove (data as unknown as T[]).map((row, idx) => ( {columns.map((col) => ( - + {col.render ? col.render(row) : (row as any)[col.key as string]} ))} )) )} - {data.length > 0 && }
) diff --git a/packages/ui-patterns/src/Chart/index.tsx b/packages/ui-patterns/src/Chart/index.tsx index 6ad5bd1f724..4466af61155 100644 --- a/packages/ui-patterns/src/Chart/index.tsx +++ b/packages/ui-patterns/src/Chart/index.tsx @@ -62,6 +62,8 @@ interface ChartProps extends React.HTMLAttributes { className?: string } +const chartTableClasses = `[&_tr]:border-b [&_tr]:border-border [&_thead_tr]:!bg-transparent [&_thead_th]:!py-2 [&_thead_th]:!px-6 [&_thead_th]:h-auto [&_tbody_td]:py-2.5 [&_tbody_td]:px-6 [&_tbody_td]:text-xs [&_table]:mb-1 [&_table]:border-b [&_table]:border-border` + const Chart = React.forwardRef( ({ children, isLoading = false, isDisabled = false, className, ...props }, ref) => { return ( @@ -138,7 +140,7 @@ const ChartTitle = React.forwardRef( className="text-foreground-lighter hover:text-foreground-light transition-colors cursor-help" /> - {tooltip} + {tooltip} )} @@ -280,7 +282,7 @@ const ChartMetric = React.forwardRef( className="text-foreground-lighter hover:text-foreground-light transition-colors cursor-help" /> - {tooltip} + {tooltip} )} @@ -336,7 +338,7 @@ const ChartContent = React.forwardRef( } return ( -
+
{content}
) @@ -478,7 +480,6 @@ const ChartDisabledState = ({ icon, label, description, actions }: ChartDisabled ChartDisabledState.displayName = 'ChartDisabledState' /* Chart Footer */ -const chartTableClasses = `[&_tr]:border-b [&_tr]:border-border [&_thead_tr]:!bg-transparent [&_thead_th]:!py-2 [&_thead_th]:h-auto [&_tbody_td]:py-2.5 [&_tbody_td]:text-xs [&_table]:mb-1 [&_table]:border-b [&_table]:border-border` interface ChartFooterProps extends React.HTMLAttributes { children: React.ReactNode