GridResize.tsx185 lines · main
| 1 | // @ts-nocheck |
| 2 | import RGL, { WidthProvider } from 'react-grid-layout' |
| 3 | |
| 4 | import 'react-grid-layout/css/styles.css' |
| 5 | import 'react-resizable/css/styles.css' |
| 6 | |
| 7 | import { useParams } from 'common' |
| 8 | import { toast } from 'sonner' |
| 9 | |
| 10 | import { createSqlSnippetSkeletonV2 } from '../SQLEditor/SQLEditor.utils' |
| 11 | import { ChartConfig } from '../SQLEditor/UtilityPanel/ChartConfig' |
| 12 | import { ReportBlock } from './ReportBlock/ReportBlock' |
| 13 | import { LAYOUT_COLUMN_COUNT } from './Reports.constants' |
| 14 | import { DEFAULT_CHART_CONFIG } from '@/components/ui/QueryBlock/QueryBlock' |
| 15 | import { AnalyticsInterval } from '@/data/analytics/constants' |
| 16 | import { |
| 17 | UpsertContentPayload, |
| 18 | useContentUpsertMutation, |
| 19 | } from '@/data/content/content-upsert-mutation' |
| 20 | import { useSendEventMutation } from '@/data/telemetry/send-event-mutation' |
| 21 | import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' |
| 22 | import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' |
| 23 | import { useProfile } from '@/lib/profile' |
| 24 | import type { Dashboards } from '@/types' |
| 25 | |
| 26 | const ReactGridLayout = WidthProvider(RGL) |
| 27 | |
| 28 | interface 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 | |
| 46 | export 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 | } |