GridResize.tsx185 lines · main
1// @ts-nocheck
2import RGL, { WidthProvider } from 'react-grid-layout'
3
4import 'react-grid-layout/css/styles.css'
5import 'react-resizable/css/styles.css'
6
7import { useParams } from 'common'
8import { toast } from 'sonner'
9
10import { createSqlSnippetSkeletonV2 } from '../SQLEditor/SQLEditor.utils'
11import { ChartConfig } from '../SQLEditor/UtilityPanel/ChartConfig'
12import { ReportBlock } from './ReportBlock/ReportBlock'
13import { LAYOUT_COLUMN_COUNT } from './Reports.constants'
14import { DEFAULT_CHART_CONFIG } from '@/components/ui/QueryBlock/QueryBlock'
15import { AnalyticsInterval } from '@/data/analytics/constants'
16import {
17 UpsertContentPayload,
18 useContentUpsertMutation,
19} from '@/data/content/content-upsert-mutation'
20import { useSendEventMutation } from '@/data/telemetry/send-event-mutation'
21import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
22import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
23import { useProfile } from '@/lib/profile'
24import type { Dashboards } from '@/types'
25
26const ReactGridLayout = WidthProvider(RGL)
27
28interface GridResizeProps {
29 startDate: string
30 endDate: string
31 interval: AnalyticsInterval
32 editableReport: Dashboards.Content
33 disableUpdate: boolean
34 isRefreshing: boolean
35 onRemoveChart: ({ metric }: { metric: { key: string } }) => void
36 onUpdateChart: (
37 id: string,
38 {
39 chart,
40 chartConfig,
41 }: { chart?: Partial<Dashboards.Chart>; chartConfig?: Partial<ChartConfig> }
42 ) => void
43 setEditableReport: (payload: any) => void
44}
45
46export const GridResize = ({
47 startDate,
48 endDate,
49 interval,
50 editableReport,
51 disableUpdate,
52 isRefreshing,
53 onRemoveChart,
54 onUpdateChart,
55 setEditableReport,
56}: GridResizeProps) => {
57 const { ref } = useParams()
58 const { profile } = useProfile()
59 const { data: project } = useSelectedProjectQuery()
60 const { data: selectedOrg } = useSelectedOrganizationQuery()
61
62 const { mutate: sendEvent } = useSendEventMutation()
63 const { mutate: upsertContent } = useContentUpsertMutation()
64
65 const onUpdateLayout = (layout: RGL.Layout[]) => {
66 const updatedLayout = [...editableReport.layout]
67
68 layout.forEach((chart) => {
69 const chartIdx = updatedLayout.findIndex((y) => chart.i === y.id)
70 if (chartIdx !== undefined && chartIdx >= 0) {
71 updatedLayout[chartIdx] = {
72 ...updatedLayout[chartIdx],
73 w: chart.w,
74 h: chart.h,
75 x: chart.x,
76 y: chart.y,
77 }
78 }
79 })
80
81 setEditableReport({ ...editableReport, layout: updatedLayout })
82 }
83
84 const onDropBlock = async (layout: RGL.Layout[], layoutItem: RGL.Layout, e: any) => {
85 if (!ref) return console.error('Project ref is required')
86 if (!profile) return console.error('Profile is required')
87 if (!project) return console.error('Project is required')
88
89 const data = e.dataTransfer.getData('application/json')
90 if (!data) return
91
92 const queryData = JSON.parse(data)
93 const { label, sql, config } = queryData
94 if (!label || !sql) return console.error('SQL and Label required')
95
96 const toastId = toast.loading(`Creating new query: ${label}`)
97
98 const payload = createSqlSnippetSkeletonV2({
99 name: label,
100 sql,
101 owner_id: profile?.id,
102 project_id: project?.id,
103 }) as UpsertContentPayload
104
105 const updatedLayout = layout.map((x) => {
106 const existingBlock = editableReport.layout.find((y) => x.i === y.id)
107 if (existingBlock) {
108 return { ...existingBlock, x: x.x, y: x.y }
109 } else {
110 return {
111 id: payload.id,
112 attribute: `new_snippet_${payload.id}`,
113 chartConfig: { ...DEFAULT_CHART_CONFIG, ...(config ?? {}) },
114 label,
115 chart_type: 'bar',
116 h: layoutItem.h,
117 w: layoutItem.w,
118 x: layoutItem.x,
119 y: layoutItem.y,
120 }
121 }
122 })
123 setEditableReport({ ...editableReport, layout: updatedLayout })
124
125 upsertContent(
126 { projectRef: ref, payload },
127 {
128 onSuccess: () => {
129 toast.success(`Successfully created new query: ${label}`, { id: toastId })
130 const finalLayout = updatedLayout.map((x) => {
131 if (x.id === payload.id) {
132 return { ...x, attribute: `snippet_${payload.id}` }
133 } else return x
134 })
135 setEditableReport({ ...editableReport, layout: finalLayout })
136 },
137 }
138 )
139 sendEvent({
140 action: 'custom_report_assistant_sql_block_added',
141 groups: { project: ref ?? 'Unknown', organization: selectedOrg?.slug ?? 'Unknown' },
142 })
143 }
144
145 if (!editableReport) return null
146
147 return (
148 <ReactGridLayout
149 autoSize
150 isDraggable
151 isDroppable
152 isResizable
153 rowHeight={270}
154 cols={LAYOUT_COLUMN_COUNT}
155 containerPadding={[0, 0]}
156 resizeHandles={['sw', 'se']}
157 compactType="vertical"
158 onDrop={onDropBlock}
159 onDragStop={onUpdateLayout}
160 onResizeStop={onUpdateLayout}
161 draggableHandle=".grid-item-drag-handle"
162 >
163 {editableReport.layout.map((item) => {
164 return (
165 <div
166 key={item.id}
167 data-grid={{ ...item, h: 1, minH: 1, maxH: 1, minW: 1, maxW: LAYOUT_COLUMN_COUNT }}
168 >
169 <ReportBlock
170 key={item.id}
171 item={item}
172 startDate={startDate}
173 endDate={endDate}
174 interval={interval}
175 disableUpdate={disableUpdate}
176 isRefreshing={isRefreshing}
177 onRemoveChart={onRemoveChart}
178 onUpdateChart={(config) => onUpdateChart(item.id, config)}
179 />
180 </div>
181 )
182 })}
183 </ReactGridLayout>
184 )
185}