ReportWidget.tsx115 lines · main
1import { useParams } from 'common'
2import { ExternalLink, HelpCircle } from 'lucide-react'
3import { NextRouter, useRouter } from 'next/router'
4import { ReactNode } from 'react'
5import { Button, cn, Loading, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
6
7import type { LogsEndpointParams } from '../Settings/Logs/Logs.types'
8import type { BaseReportParams, ReportQueryType } from './Reports.types'
9import Panel from '@/components/ui/Panel'
10
11export interface ReportWidgetProps<T = any> {
12 data: T[]
13 title: string
14 description?: string
15 error?: string | Object | null
16 tooltip?: string | ReactNode
17 className?: string
18 contentClassName?: string
19 headerClassName?: string
20 renderer: (props: ReportWidgetRendererProps) => ReactNode
21 append?: (props: ReportWidgetRendererProps) => ReactNode
22 // for overriding props, such as data
23 appendProps?: Partial<ReportWidgetRendererProps>
24 // omitting params will hide the "View in logs explorer" button
25 params?: BaseReportParams | LogsEndpointParams
26 queryType?: ReportQueryType
27 isLoading: boolean
28 resolvedSql?: string
29}
30
31export interface ReportWidgetRendererProps<T = any> extends ReportWidgetProps<T> {
32 router: NextRouter
33 projectRef: string
34}
35
36const ReportWidget = (props: ReportWidgetProps) => {
37 const router = useRouter()
38 const { ref } = useParams()
39 const projectRef = ref as string
40
41 return (
42 <Panel noMargin noHideOverflow className={cn('pb-0', props.className)} wrapWithLoading={false}>
43 <Panel.Content className={cn('space-y-4', props.contentClassName)}>
44 <div className={cn('flex flex-row items-start justify-between', props.headerClassName)}>
45 <div className="gap-2">
46 <div className="flex flex-row gap-2">
47 <h3 className="w-full h-6">{props.title}</h3>{' '}
48 {props?.tooltip && (
49 <Tooltip>
50 <TooltipTrigger>
51 <HelpCircle className="text-foreground-light" size={14} strokeWidth={1.5} />
52 </TooltipTrigger>
53 <TooltipContent side="bottom">{props.tooltip}</TooltipContent>
54 </Tooltip>
55 )}
56 </div>
57 <p className="text-sm text-foreground-light">{props.description}</p>
58 </div>
59 {props.params && (
60 <Tooltip>
61 <TooltipTrigger asChild>
62 <Button
63 type="default"
64 icon={<ExternalLink />}
65 className="px-1"
66 onClick={() => {
67 const isDbQueryType = props.queryType === 'db'
68
69 const pathname = isDbQueryType
70 ? `/project/${projectRef}/sql/new`
71 : `/project/${projectRef}/logs/explorer`
72
73 const query: Record<string, string | undefined> = {}
74
75 if (isDbQueryType) {
76 query.content = props.resolvedSql
77 } else {
78 query.q = props.params?.sql
79 query.its = props.params?.iso_timestamp_start || ''
80 query.ite = props.params?.iso_timestamp_end || ''
81 }
82
83 router.push({ pathname, query })
84 }}
85 />
86 </TooltipTrigger>
87 <TooltipContent side="left">
88 {props.queryType === 'db' ? 'Open in SQL Editor' : 'Open in Logs Explorer'}
89 </TooltipContent>
90 </Tooltip>
91 )}
92 </div>
93
94 <Loading active={props.isLoading}>
95 {props.data === undefined
96 ? null
97 : props.renderer({ ...props, router, projectRef: projectRef as string })}
98 </Loading>
99 </Panel.Content>
100
101 {props.append && (
102 <>
103 {props.append({
104 ...props,
105 ...(props.appendProps || {}),
106 router,
107 projectRef: projectRef as string,
108 })}
109 </>
110 )}
111 </Panel>
112 )
113}
114
115export default ReportWidget