MetricOptions.tsx194 lines · main
1// @ts-nocheck
2import { useDebounce } from '@uidotdev/usehooks'
3import { useParams } from 'common'
4import { Home, Plus } from 'lucide-react'
5import { useState } from 'react'
6import {
7 Command,
8 CommandGroup,
9 CommandInput,
10 CommandItem,
11 CommandList,
12 DropdownMenuCheckboxItem,
13 DropdownMenuPortal,
14 DropdownMenuSub,
15 DropdownMenuSubContent,
16 DropdownMenuSubTrigger,
17 SQL_ICON,
18} from 'ui'
19import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
20
21import { DEPRECATED_REPORTS } from './Reports.constants'
22import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
23import { useContentQuery } from '@/data/content/content-query'
24import { useSendEventMutation } from '@/data/telemetry/send-event-mutation'
25import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
26import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
27import { Metric, METRIC_CATEGORIES, METRICS } from '@/lib/constants/metrics'
28import { editorPanelState } from '@/state/editor-panel-state'
29import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state'
30import type { Dashboards } from '@/types'
31
32interface MetricOptionsProps {
33 config?: Dashboards.Content
34 handleChartSelection: ({
35 metric,
36 isAddingChart,
37 }: {
38 metric: Metric
39 isAddingChart: boolean
40 }) => void
41}
42
43export const MetricOptions = ({ config, handleChartSelection }: MetricOptionsProps) => {
44 const { ref: projectRef } = useParams()
45 const { data: selectedOrganization } = useSelectedOrganizationQuery()
46 const { openSidebar } = useSidebarManagerSnapshot()
47 const [search, setSearch] = useState('')
48
49 const { projectAuthAll: authEnabled, projectStorageAll: storageEnabled } = useIsFeatureEnabled([
50 'project_auth:all',
51 'project_storage:all',
52 ])
53
54 const metricCategories = Object.values(METRIC_CATEGORIES).filter(({ key }) => {
55 if (key === 'api_auth') return authEnabled
56 if (key === 'api_storage') return storageEnabled
57 return true
58 })
59
60 const { mutate: sendEvent } = useSendEventMutation()
61
62 const debouncedSearch = useDebounce(search, 300)
63 const {
64 data,
65 isPending: isLoading,
66 refetch,
67 } = useContentQuery({
68 projectRef,
69 type: 'sql',
70 name: debouncedSearch.length === 0 ? undefined : debouncedSearch,
71 })
72 const snippets = data?.content
73
74 return (
75 <>
76 {metricCategories.map((cat) => {
77 return (
78 <DropdownMenuSub key={cat.key}>
79 <DropdownMenuSubTrigger className="space-x-2">
80 {cat.icon ? cat.icon() : <Home size={14} />}
81 <p>{cat.label}</p>
82 </DropdownMenuSubTrigger>
83 <DropdownMenuPortal>
84 <DropdownMenuSubContent>
85 {METRICS.filter(
86 (metric) =>
87 !DEPRECATED_REPORTS.includes(metric.key) && metric?.category?.key === cat.key
88 ).map((metric) => {
89 return (
90 <DropdownMenuCheckboxItem
91 key={metric.key}
92 className="cursor-pointer"
93 checked={config?.layout?.some((x: any) => x.attribute === metric.key)}
94 onCheckedChange={(e) => handleChartSelection({ metric, isAddingChart: e })}
95 >
96 <div className="flex flex-col space-y-0">
97 <span>{metric.label}</span>
98 </div>
99 </DropdownMenuCheckboxItem>
100 )
101 })}
102 </DropdownMenuSubContent>
103 </DropdownMenuPortal>
104 </DropdownMenuSub>
105 )
106 })}
107 <DropdownMenuSub
108 onOpenChange={(open) => {
109 if (open) refetch()
110 }}
111 >
112 <DropdownMenuSubTrigger className="space-x-2">
113 <SQL_ICON
114 size={14}
115 strokeWidth={1.5}
116 className="fill-foreground-light w-5 h-4 shrink-0 grow-0 -ml-0.5"
117 />
118 <p>SQL Snippets</p>
119 </DropdownMenuSubTrigger>
120 <DropdownMenuPortal>
121 <DropdownMenuSubContent className="p-0">
122 <Command shouldFilter={false}>
123 <CommandInput
124 autoFocus
125 placeholder="Search snippets..."
126 value={search}
127 onValueChange={setSearch}
128 />
129 <CommandList>
130 {isLoading ? (
131 <div className="flex flex-col p-1 gap-y-1">
132 <ShimmeringLoader />
133 <ShimmeringLoader className="w-3/4" />
134 </div>
135 ) : !snippets?.length ? (
136 <p className="text-xs text-center text-foreground-lighter py-3">
137 No snippets found
138 </p>
139 ) : null}
140 <CommandGroup>
141 {snippets?.map((snippet) => (
142 <CommandItem
143 key={snippet.id}
144 value={snippet.id}
145 className="cursor-pointer"
146 onSelect={() => {
147 if (!config?.layout.find((x) => x.id === snippet.id)) {
148 handleChartSelection({
149 metric: {
150 id: snippet.id,
151 key: `snippet_${snippet.id}`,
152 label: snippet.name,
153 },
154 isAddingChart: true,
155 })
156 sendEvent({
157 action: 'custom_report_add_sql_block_clicked',
158 groups: {
159 project: projectRef ?? 'Unknown',
160 organization: selectedOrganization?.slug ?? 'Unknown',
161 },
162 })
163 }
164 }}
165 >
166 {snippet.name}
167 </CommandItem>
168 ))}
169 </CommandGroup>
170 </CommandList>
171
172 <div className="h-px bg-border-overlay -mx-1" />
173
174 <CommandGroup>
175 <CommandItem
176 className="cursor-pointer w-full"
177 onSelect={() => {
178 editorPanelState.openAsNew()
179 openSidebar(SIDEBAR_KEYS.EDITOR_PANEL)
180 }}
181 >
182 <div className="w-full flex items-center gap-2">
183 <Plus size={14} strokeWidth={1.5} />
184 <p>Create snippet</p>
185 </div>
186 </CommandItem>
187 </CommandGroup>
188 </Command>
189 </DropdownMenuSubContent>
190 </DropdownMenuPortal>
191 </DropdownMenuSub>
192 </>
193 )
194}