@devtable/dashboard 0.4.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/api-caller/index.d.ts +9 -2
  2. package/dist/contexts/definition-context.d.ts +2 -1
  3. package/dist/contexts/panel-context.d.ts +2 -2
  4. package/dist/dashboard.es.js +1212 -500
  5. package/dist/dashboard.umd.js +17 -6
  6. package/dist/definition-editor/data-source-editor/context-and-snippets.d.ts +5 -0
  7. package/dist/definition-editor/data-source-editor/data-preview.d.ts +4 -0
  8. package/dist/definition-editor/data-source-editor/editor.d.ts +6 -0
  9. package/dist/definition-editor/data-source-editor/form.d.ts +8 -0
  10. package/dist/definition-editor/data-source-editor/index.d.ts +7 -0
  11. package/dist/definition-editor/data-source-editor/select-or-add-data-source.d.ts +7 -0
  12. package/dist/definition-editor/index.d.ts +2 -0
  13. package/dist/{panel/settings/context-info/index.d.ts → definition-editor/sql-snippet-editor/context-info.d.ts} +0 -0
  14. package/dist/definition-editor/sql-snippet-editor/editor.d.ts +5 -0
  15. package/dist/definition-editor/sql-snippet-editor/index.d.ts +7 -0
  16. package/dist/layout/index.d.ts +1 -2
  17. package/dist/main/main.d.ts +3 -1
  18. package/dist/main/read-only.d.ts +3 -1
  19. package/dist/panel/index.d.ts +1 -1
  20. package/dist/panel/panel-description.d.ts +7 -0
  21. package/dist/panel/settings/{viz-config → panel-config}/description.d.ts +0 -0
  22. package/dist/panel/settings/panel-config/index.d.ts +5 -0
  23. package/dist/panel/settings/panel-config/preview-panel.d.ts +2 -0
  24. package/dist/panel/settings/{viz-config → panel-config}/title.d.ts +0 -0
  25. package/dist/panel/settings/pick-data-source/index.d.ts +5 -0
  26. package/dist/panel/settings/viz-config/preview-viz.d.ts +5 -0
  27. package/dist/style.css +1 -1
  28. package/dist/types/dashboard.d.ts +8 -1
  29. package/package.json +4 -2
  30. package/dist/panel/settings/query-editor/index.d.ts +0 -2
  31. package/dist/panel/settings/query-editor/sql-query-editor/index.d.ts +0 -5
  32. package/dist/panel/settings/query-result/index.d.ts +0 -5
  33. package/dist/panel/settings/sql-snippets/form.d.ts +0 -9
  34. package/dist/panel/settings/sql-snippets/index.d.ts +0 -5
@@ -32,11 +32,12 @@ var __objRest = (source, exclude) => {
32
32
  import React from "react";
33
33
  import _ from "lodash";
34
34
  import { WidthProvider, Responsive } from "react-grid-layout";
35
- import { Text, Table, Group, LoadingOverlay, Box, Textarea, Button, TextInput, ActionIcon, useMantineTheme, ColorSwatch, Select, Switch, Slider, JsonInput, Divider, Modal, AppShell, Navbar, Tabs, Tooltip, Menu, Container, SegmentedControl } from "@mantine/core";
35
+ import { Popover, Tooltip, Group, Text, ActionIcon, TextInput, Box, LoadingOverlay, Table, Select, Button, useMantineTheme, ColorSwatch, Switch, Slider, JsonInput, Modal, AppShell, Tabs, Menu, Divider, Container, SegmentedControl, Textarea } from "@mantine/core";
36
36
  import { useRequest } from "ahooks";
37
37
  import axios from "axios";
38
- import { Trash, DeviceFloppy, InfoCircle, Refresh, Settings, Paint, PlayerPlay, PlaylistAdd, Recycle, Share } from "tabler-icons-react";
39
- import { useElementSize, randomId, useInputState, useListState } from "@mantine/hooks";
38
+ import { InfoCircle, DeviceFloppy, Trash, Refresh, Settings, Paint, PlayerPlay, PlaylistAdd, ClipboardText, Database, Recycle, Share } from "tabler-icons-react";
39
+ import RichTextEditor, { RichTextEditor as RichTextEditor$1 } from "@mantine/rte";
40
+ import { useInputState, useElementSize, randomId } from "@mantine/hooks";
40
41
  import ReactEChartsCore from "echarts-for-react/lib/core";
41
42
  import * as echarts from "echarts/core";
42
43
  import { SunburstChart, BarChart, LineChart } from "echarts/charts";
@@ -44,9 +45,9 @@ import { CanvasRenderer } from "echarts/renderers";
44
45
  import { GridComponent, LegendComponent, TooltipComponent, VisualMapComponent } from "echarts/components";
45
46
  import numbro from "numbro";
46
47
  import "echarts-gl";
48
+ import { useForm, Controller } from "react-hook-form";
49
+ import { formList, useForm as useForm$1 } from "@mantine/form";
47
50
  import { Prism } from "@mantine/prism";
48
- import { formList, useForm } from "@mantine/form";
49
- import { useForm as useForm$1, Controller } from "react-hook-form";
50
51
  var DashboardMode = /* @__PURE__ */ ((DashboardMode2) => {
51
52
  DashboardMode2["Use"] = "use";
52
53
  DashboardMode2["Edit"] = "edit";
@@ -87,7 +88,14 @@ const post = getRequest("POST");
87
88
  function formatSQL(sql, params) {
88
89
  const names = Object.keys(params);
89
90
  const vals = Object.values(params);
90
- return new Function(...names, `return \`${sql}\`;`)(...vals);
91
+ try {
92
+ return new Function(...names, `return \`${sql}\`;`)(...vals);
93
+ } catch (error) {
94
+ if (names.length === 0 && sql.includes("$")) {
95
+ throw new Error("[formatSQL] insufficient params");
96
+ }
97
+ throw error;
98
+ }
91
99
  }
92
100
  function getSQLParams(context, definitions) {
93
101
  const sqlSnippetRecord = definitions.sqlSnippets.reduce((ret, curr) => {
@@ -96,24 +104,26 @@ function getSQLParams(context, definitions) {
96
104
  }, {});
97
105
  return _.merge({}, sqlSnippetRecord, context);
98
106
  }
99
- const queryBySQL = (sql, context, definitions, title) => async () => {
100
- if (!sql) {
107
+ const queryBySQL = ({ context, definitions, title, dataSource }) => async () => {
108
+ if (!dataSource || !dataSource.sql) {
101
109
  return [];
102
110
  }
111
+ const { type, key, sql } = dataSource;
103
112
  const needParams = sql.includes("$");
104
- const params = getSQLParams(context, definitions);
105
- if (needParams && Object.keys(params).length === 0) {
106
- console.error(`[queryBySQL] insufficient params for {${title}}'s SQL`);
113
+ try {
114
+ const params = getSQLParams(context, definitions);
115
+ const formattedSQL = formatSQL(sql, params);
116
+ if (needParams) {
117
+ console.groupCollapsed(`Final SQL for: ${title}`);
118
+ console.log(formattedSQL);
119
+ console.groupEnd();
120
+ }
121
+ const res = await post("/query", { type, key, sql: formattedSQL });
122
+ return res;
123
+ } catch (error) {
124
+ console.error(error);
107
125
  return [];
108
126
  }
109
- const formattedSQL = formatSQL(sql, params);
110
- if (needParams) {
111
- console.groupCollapsed(`Final SQL for: ${title}`);
112
- console.log(formattedSQL);
113
- console.groupEnd();
114
- }
115
- const res = await post("/query", { sql: formattedSQL });
116
- return res;
117
127
  };
118
128
  const initialContext$2 = {};
119
129
  const initialContextInfoContext = initialContext$2;
@@ -127,8 +137,8 @@ const initialContext$1 = {
127
137
  description: "",
128
138
  setDescription: () => {
129
139
  },
130
- sql: "",
131
- setSQL: () => {
140
+ dataSourceID: "",
141
+ setDataSourceID: () => {
132
142
  },
133
143
  viz: {
134
144
  type: "",
@@ -140,6 +150,15 @@ const initialContext$1 = {
140
150
  }
141
151
  };
142
152
  const PanelContext = React.createContext(initialContext$1);
153
+ const initialContext = {
154
+ sqlSnippets: [],
155
+ setSQLSnippets: () => {
156
+ },
157
+ dataSources: [],
158
+ setDataSources: () => {
159
+ }
160
+ };
161
+ const DefinitionContext = React.createContext(initialContext);
143
162
  var jsxRuntime = { exports: {} };
144
163
  var reactJsxRuntime_production_min = {};
145
164
  /**
@@ -173,6 +192,105 @@ reactJsxRuntime_production_min.jsxs = q;
173
192
  const jsx = jsxRuntime.exports.jsx;
174
193
  const jsxs = jsxRuntime.exports.jsxs;
175
194
  const Fragment = jsxRuntime.exports.Fragment;
195
+ function DescriptionPopover({
196
+ position,
197
+ trigger = "click"
198
+ }) {
199
+ const {
200
+ freezeLayout
201
+ } = React.useContext(LayoutStateContext);
202
+ const [opened, setOpened] = React.useState(false);
203
+ const {
204
+ description
205
+ } = React.useContext(PanelContext);
206
+ React.useEffect(() => {
207
+ freezeLayout(opened);
208
+ }, [opened]);
209
+ if (!description) {
210
+ return null;
211
+ }
212
+ const target = trigger === "click" ? /* @__PURE__ */ jsx(Tooltip, {
213
+ label: "Click to see description",
214
+ openDelay: 500,
215
+ children: /* @__PURE__ */ jsx(InfoCircle, {
216
+ size: 20,
217
+ onClick: () => setOpened((v) => !v),
218
+ style: {
219
+ verticalAlign: "baseline",
220
+ cursor: "pointer"
221
+ }
222
+ })
223
+ }) : /* @__PURE__ */ jsx(InfoCircle, {
224
+ size: 20,
225
+ onMouseEnter: () => setOpened(true),
226
+ onMouseLeave: () => setOpened(false),
227
+ style: {
228
+ verticalAlign: "baseline",
229
+ cursor: "pointer"
230
+ }
231
+ });
232
+ return /* @__PURE__ */ jsx(Popover, {
233
+ opened,
234
+ onClose: () => setOpened(false),
235
+ withCloseButton: true,
236
+ withArrow: true,
237
+ trapFocus: true,
238
+ closeOnEscape: false,
239
+ placement: "center",
240
+ position,
241
+ target,
242
+ children: /* @__PURE__ */ jsx(RichTextEditor, {
243
+ readOnly: true,
244
+ value: description,
245
+ onChange: _.noop,
246
+ sx: {
247
+ border: "none"
248
+ }
249
+ })
250
+ });
251
+ }
252
+ function EditDescription() {
253
+ const {
254
+ description,
255
+ setDescription
256
+ } = React.useContext(PanelContext);
257
+ const [value, onChange] = React.useState(description);
258
+ const changed = description !== value;
259
+ const submit = React.useCallback(() => {
260
+ if (!changed) {
261
+ return;
262
+ }
263
+ setDescription(value);
264
+ }, [changed, value]);
265
+ return /* @__PURE__ */ jsxs(Group, {
266
+ direction: "column",
267
+ sx: {
268
+ flexGrow: 1
269
+ },
270
+ children: [/* @__PURE__ */ jsxs(Group, {
271
+ align: "end",
272
+ children: [/* @__PURE__ */ jsx(Text, {
273
+ children: "Description"
274
+ }), /* @__PURE__ */ jsx(ActionIcon, {
275
+ variant: "hover",
276
+ color: "blue",
277
+ disabled: !changed,
278
+ onClick: submit,
279
+ children: /* @__PURE__ */ jsx(DeviceFloppy, {
280
+ size: 20
281
+ })
282
+ })]
283
+ }), /* @__PURE__ */ jsx(RichTextEditor$1, {
284
+ value,
285
+ onChange,
286
+ sx: {
287
+ flexGrow: 1
288
+ },
289
+ sticky: true,
290
+ p: "0"
291
+ })]
292
+ });
293
+ }
176
294
  class ErrorBoundary extends React.Component {
177
295
  constructor(props) {
178
296
  super(props);
@@ -194,6 +312,240 @@ class ErrorBoundary extends React.Component {
194
312
  return this.props.children;
195
313
  }
196
314
  }
315
+ function PreviewPanel() {
316
+ const {
317
+ title
318
+ } = React.useContext(PanelContext);
319
+ return /* @__PURE__ */ jsx(ErrorBoundary, {
320
+ children: /* @__PURE__ */ jsxs(Group, {
321
+ direction: "column",
322
+ grow: true,
323
+ noWrap: true,
324
+ mx: "auto",
325
+ mt: "xl",
326
+ p: "5px",
327
+ spacing: "xs",
328
+ sx: {
329
+ width: "600px",
330
+ height: "450px",
331
+ background: "transparent",
332
+ borderRadius: "5px",
333
+ boxShadow: "0px 0px 10px 0px rgba(0,0,0,.2)"
334
+ },
335
+ children: [/* @__PURE__ */ jsxs(Group, {
336
+ position: "apart",
337
+ noWrap: true,
338
+ sx: {
339
+ borderBottom: "1px solid #eee",
340
+ paddingBottom: "5px",
341
+ flexGrow: 0,
342
+ flexShrink: 0
343
+ },
344
+ children: [/* @__PURE__ */ jsx(Group, {
345
+ children: /* @__PURE__ */ jsx(DescriptionPopover, {
346
+ position: "bottom",
347
+ trigger: "hover"
348
+ })
349
+ }), /* @__PURE__ */ jsx(Group, {
350
+ grow: true,
351
+ position: "center",
352
+ children: /* @__PURE__ */ jsx(Text, {
353
+ lineClamp: 1,
354
+ weight: "bold",
355
+ children: title
356
+ })
357
+ }), /* @__PURE__ */ jsx(Group, {
358
+ position: "right",
359
+ spacing: 0,
360
+ sx: {
361
+ height: "28px"
362
+ }
363
+ })]
364
+ }), /* @__PURE__ */ jsx(Group, {
365
+ sx: {
366
+ background: "#eee",
367
+ flexGrow: 1
368
+ }
369
+ })]
370
+ })
371
+ });
372
+ }
373
+ function EditTitle() {
374
+ const {
375
+ title,
376
+ setTitle
377
+ } = React.useContext(PanelContext);
378
+ const [localTitle, setLocalTitle] = useInputState(title);
379
+ const changed = title !== localTitle;
380
+ const submit = React.useCallback(() => {
381
+ if (!changed) {
382
+ return;
383
+ }
384
+ setTitle(localTitle);
385
+ }, [changed, localTitle]);
386
+ return /* @__PURE__ */ jsx(TextInput, {
387
+ value: localTitle,
388
+ onChange: setLocalTitle,
389
+ label: /* @__PURE__ */ jsxs(Group, {
390
+ align: "end",
391
+ children: [/* @__PURE__ */ jsx(Text, {
392
+ children: "Panel Title"
393
+ }), /* @__PURE__ */ jsx(ActionIcon, {
394
+ variant: "hover",
395
+ color: "blue",
396
+ disabled: !changed,
397
+ onClick: submit,
398
+ children: /* @__PURE__ */ jsx(DeviceFloppy, {
399
+ size: 20
400
+ })
401
+ })]
402
+ })
403
+ });
404
+ }
405
+ function PanelConfig({}) {
406
+ return /* @__PURE__ */ jsxs(Group, {
407
+ direction: "row",
408
+ grow: true,
409
+ noWrap: true,
410
+ align: "stretch",
411
+ sx: {
412
+ height: "100%"
413
+ },
414
+ children: [/* @__PURE__ */ jsxs(Group, {
415
+ grow: true,
416
+ direction: "column",
417
+ sx: {
418
+ width: "40%",
419
+ flexShrink: 0,
420
+ flexGrow: 0,
421
+ height: "100%"
422
+ },
423
+ children: [/* @__PURE__ */ jsx(EditTitle, {}), /* @__PURE__ */ jsx(EditDescription, {})]
424
+ }), /* @__PURE__ */ jsx(Box, {
425
+ sx: {
426
+ height: "100%",
427
+ flexGrow: 1,
428
+ maxWidth: "60%"
429
+ },
430
+ children: /* @__PURE__ */ jsx(PreviewPanel, {})
431
+ })]
432
+ });
433
+ }
434
+ function DataPreview({
435
+ id
436
+ }) {
437
+ const definitions = React.useContext(DefinitionContext);
438
+ const contextInfo = React.useContext(ContextInfoContext);
439
+ const dataSource = React.useMemo(() => {
440
+ return definitions.dataSources.find((d) => d.id === id);
441
+ }, [definitions.dataSources, id]);
442
+ const {
443
+ data = [],
444
+ loading
445
+ } = useRequest(queryBySQL({
446
+ context: contextInfo,
447
+ definitions,
448
+ title: id,
449
+ dataSource
450
+ }), {
451
+ refreshDeps: [contextInfo, definitions, dataSource]
452
+ });
453
+ if (loading) {
454
+ return /* @__PURE__ */ jsx(LoadingOverlay, {
455
+ visible: loading
456
+ });
457
+ }
458
+ if (data.length === 0) {
459
+ return /* @__PURE__ */ jsx(Table, {});
460
+ }
461
+ return /* @__PURE__ */ jsxs(Group, {
462
+ my: "xl",
463
+ direction: "column",
464
+ grow: true,
465
+ sx: {
466
+ border: "1px solid #eee"
467
+ },
468
+ children: [/* @__PURE__ */ jsx(Group, {
469
+ position: "left",
470
+ py: "md",
471
+ pl: "md",
472
+ sx: {
473
+ borderBottom: "1px solid #eee",
474
+ background: "#efefef"
475
+ },
476
+ children: /* @__PURE__ */ jsx(Text, {
477
+ weight: 500,
478
+ children: "Preview Data"
479
+ })
480
+ }), /* @__PURE__ */ jsxs(Table, {
481
+ children: [/* @__PURE__ */ jsx("thead", {
482
+ children: /* @__PURE__ */ jsx("tr", {
483
+ children: Object.keys(data == null ? void 0 : data[0]).map((label) => /* @__PURE__ */ jsx("th", {
484
+ children: label
485
+ }, label))
486
+ })
487
+ }), /* @__PURE__ */ jsx("tbody", {
488
+ children: data.map((row, index2) => /* @__PURE__ */ jsx("tr", {
489
+ children: Object.values(row).map((v, i) => /* @__PURE__ */ jsx("td", {
490
+ children: /* @__PURE__ */ jsx(Group, {
491
+ sx: {
492
+ "&, .mantine-Text-root": {
493
+ fontFamily: "monospace"
494
+ }
495
+ },
496
+ children: /* @__PURE__ */ jsx(Text, {
497
+ children: v
498
+ })
499
+ })
500
+ }, `${v}--${i}`))
501
+ }, `row-${index2}`))
502
+ })]
503
+ })]
504
+ });
505
+ }
506
+ function PickDataSource({}) {
507
+ const {
508
+ dataSources
509
+ } = React.useContext(DefinitionContext);
510
+ const {
511
+ dataSourceID,
512
+ setDataSourceID,
513
+ data,
514
+ loading
515
+ } = React.useContext(PanelContext);
516
+ const options = React.useMemo(() => {
517
+ return dataSources.map((d) => ({
518
+ value: d.id,
519
+ label: d.id
520
+ }));
521
+ }, [dataSources]);
522
+ return /* @__PURE__ */ jsxs(Group, {
523
+ direction: "column",
524
+ grow: true,
525
+ noWrap: true,
526
+ children: [/* @__PURE__ */ jsxs(Group, {
527
+ position: "left",
528
+ sx: {
529
+ maxWidth: "600px",
530
+ alignItems: "baseline"
531
+ },
532
+ children: [/* @__PURE__ */ jsx(Text, {
533
+ children: "Select a Data Source"
534
+ }), /* @__PURE__ */ jsx(Select, {
535
+ data: options,
536
+ value: dataSourceID,
537
+ onChange: setDataSourceID,
538
+ allowDeselect: false,
539
+ clearable: false,
540
+ sx: {
541
+ flexGrow: 1
542
+ }
543
+ })]
544
+ }), /* @__PURE__ */ jsx(DataPreview, {
545
+ id: dataSourceID
546
+ })]
547
+ });
548
+ }
197
549
  echarts.use([SunburstChart, CanvasRenderer]);
198
550
  const defaultOption$1 = {
199
551
  tooltip: {
@@ -244,8 +596,8 @@ function Sunbrust({
244
596
  }
245
597
  }
246
598
  }
247
- }), []);
248
- const option = _.merge(defaultOption$1, labelOption, restConf, {
599
+ }), [max]);
600
+ const option = _.merge({}, defaultOption$1, labelOption, restConf, {
249
601
  series: {
250
602
  data: chartData
251
603
  }
@@ -560,7 +912,7 @@ function VizBar3D({
560
912
  }
561
913
  });
562
914
  }
563
- var index$3 = "";
915
+ var index$2 = "";
564
916
  function renderViz(width, height, data, viz) {
565
917
  const props = {
566
918
  width,
@@ -613,287 +965,23 @@ function Viz({
613
965
  }), !empty && renderViz(width, height, data, viz)]
614
966
  });
615
967
  }
616
- function ContextInfo({}) {
617
- const contextInfo = React.useContext(ContextInfoContext);
618
- const sampleSQL = `SELECT *
619
- FROM commit
620
- WHERE author_time BETWEEN '\${timeRange?.[0].toISOString()}' AND '\${timeRange?.[1].toISOString()}'`;
621
- return /* @__PURE__ */ jsxs(Group, {
622
- direction: "column",
623
- children: [/* @__PURE__ */ jsx(Prism, {
624
- language: "sql",
625
- sx: {
626
- width: "100%"
627
- },
628
- noCopy: true,
629
- colorScheme: "dark",
630
- children: `-- You may refer context data *by name*
631
- -- in SQL or VizConfig.
632
-
633
- ${sampleSQL}`
634
- }), /* @__PURE__ */ jsx(Text, {
635
- weight: 700,
636
- children: "Avaiable context entries"
637
- }), /* @__PURE__ */ jsx(Prism, {
638
- language: "json",
639
- sx: {
640
- width: "100%"
641
- },
642
- noCopy: true,
643
- colorScheme: "dark",
644
- children: JSON.stringify(contextInfo, null, 2)
645
- })]
646
- });
647
- }
648
- var index$2 = "";
649
- function SQLQueryEditor({}) {
650
- const {
651
- sql,
652
- setSQL
653
- } = React.useContext(PanelContext);
654
- const [showPreview, setShowPreview] = React.useState(true);
655
- const handleChange = (e) => {
656
- setSQL(e.target.value);
657
- };
658
- const togglePreview = React.useCallback(() => {
659
- setShowPreview((v) => !v);
660
- }, []);
661
- const format = React.useCallback(() => {
662
- setSQL((sql2) => sql2.trim());
663
- }, [setSQL]);
664
- return /* @__PURE__ */ jsxs(Box, {
665
- className: "sql-query-editor-root",
666
- sx: {
667
- height: "100%"
668
- },
669
- children: [/* @__PURE__ */ jsx(Textarea, {
670
- value: sql,
671
- onChange: handleChange,
672
- minRows: 20,
673
- maxRows: 20
674
- }), /* @__PURE__ */ jsxs(Group, {
675
- m: "md",
676
- position: "right",
677
- children: [/* @__PURE__ */ jsx(Button, {
678
- color: "blue",
679
- onClick: format,
680
- children: "Format"
681
- }), /* @__PURE__ */ jsx(Button, {
682
- variant: "default",
683
- onClick: togglePreview,
684
- children: "Toggle Preview"
685
- })]
686
- }), showPreview && /* @__PURE__ */ jsx(Prism, {
687
- language: "sql",
688
- withLineNumbers: true,
689
- noCopy: true,
690
- colorScheme: "dark",
691
- children: sql
692
- })]
693
- });
694
- }
695
- const QueryEditor = SQLQueryEditor;
696
- function QueryResult({}) {
968
+ function PreviewViz({}) {
697
969
  const {
698
- data
970
+ data,
971
+ loading,
972
+ viz
699
973
  } = React.useContext(PanelContext);
700
- return /* @__PURE__ */ jsxs("div", {
701
- className: "query-result-root",
702
- children: [/* @__PURE__ */ jsxs(Group, {
703
- mb: "xs",
704
- children: [/* @__PURE__ */ jsx(Text, {
705
- weight: "bold",
706
- children: "Data Length: "
707
- }), data.length]
708
- }), /* @__PURE__ */ jsx(Prism, {
709
- language: "json",
710
- colorScheme: "dark",
711
- children: JSON.stringify(data, null, 2)
712
- })]
974
+ return /* @__PURE__ */ jsx(ErrorBoundary, {
975
+ children: /* @__PURE__ */ jsx(Viz, {
976
+ viz,
977
+ data,
978
+ loading
979
+ })
713
980
  });
714
981
  }
715
- const initialContext = {
716
- sqlSnippets: [],
717
- setSQLSnippets: () => {
718
- }
719
- };
720
- const DefinitionContext = React.createContext(initialContext);
721
- function SQLSnippetsForm({
722
- sqlSnippets,
723
- setSQLSnippets
724
- }) {
725
- const initialValues = React.useMemo(() => ({
726
- snippets: formList(sqlSnippets != null ? sqlSnippets : [])
727
- }), [sqlSnippets]);
728
- const form = useForm({
729
- initialValues
730
- });
731
- const addSnippet = () => form.addListItem("snippets", {
732
- key: randomId(),
733
- value: ""
734
- });
735
- const changed = React.useMemo(() => !_.isEqual(form.values, initialValues), [form.values, initialValues]);
736
- const submit = ({
737
- snippets
738
- }) => {
739
- setSQLSnippets(snippets);
740
- };
741
- return /* @__PURE__ */ jsx(Group, {
742
- direction: "column",
743
- sx: {
744
- width: "100%",
745
- position: "relative"
746
- },
747
- grow: true,
748
- children: /* @__PURE__ */ jsxs("form", {
749
- onSubmit: form.onSubmit(submit),
750
- children: [form.values.snippets.map((_item, index2) => /* @__PURE__ */ jsxs(Group, {
751
- direction: "column",
752
- grow: true,
753
- my: 0,
754
- p: "md",
755
- pr: 40,
756
- sx: {
757
- border: "1px solid #eee",
758
- position: "relative"
759
- },
760
- children: [/* @__PURE__ */ jsx(TextInput, __spreadValues({
761
- label: "Key",
762
- required: true
763
- }, form.getListInputProps("snippets", index2, "key"))), /* @__PURE__ */ jsx(Textarea, __spreadValues({
764
- minRows: 3,
765
- label: "Value",
766
- required: true
767
- }, form.getListInputProps("snippets", index2, "value"))), /* @__PURE__ */ jsx(ActionIcon, {
768
- color: "red",
769
- variant: "hover",
770
- onClick: () => form.removeListItem("snippets", index2),
771
- sx: {
772
- position: "absolute",
773
- top: 15,
774
- right: 5
775
- },
776
- children: /* @__PURE__ */ jsx(Trash, {
777
- size: 16
778
- })
779
- })]
780
- }, index2)), /* @__PURE__ */ jsxs(Group, {
781
- position: "center",
782
- mt: "xl",
783
- grow: true,
784
- sx: {
785
- width: "80%"
786
- },
787
- mx: "auto",
788
- children: [/* @__PURE__ */ jsx(Button, {
789
- variant: "default",
790
- onClick: addSnippet,
791
- children: "Add a snippet"
792
- }), /* @__PURE__ */ jsx(Button, {
793
- color: "blue",
794
- type: "submit",
795
- disabled: !changed,
796
- children: "Submit"
797
- })]
798
- })]
799
- })
800
- });
801
- }
802
- function SQLSnippetsTab({}) {
803
- const {
804
- sqlSnippets,
805
- setSQLSnippets
806
- } = React.useContext(DefinitionContext);
807
- const sampleSQL = `SELECT *
808
- FROM commit
809
- WHERE \${author_time_condition}`;
810
- return /* @__PURE__ */ jsxs(Group, {
811
- direction: "column",
812
- children: [/* @__PURE__ */ jsx(Prism, {
813
- language: "sql",
814
- sx: {
815
- width: "100%"
816
- },
817
- noCopy: true,
818
- trim: false,
819
- colorScheme: "dark",
820
- children: `-- You may refer context data *by name*
821
- -- in SQL or VizConfig.
822
-
823
- ${sampleSQL}
824
-
825
- -- where author_time_condition is:
826
- author_time BETWEEN '\${timeRange?.[0].toISOString()}' AND '\${timeRange?.[1].toISOString()}'
827
- `
828
- }), /* @__PURE__ */ jsx(Text, {
829
- weight: 700,
830
- children: "SQL Snippets"
831
- }), /* @__PURE__ */ jsx(SQLSnippetsForm, {
832
- sqlSnippets,
833
- setSQLSnippets
834
- })]
835
- });
836
- }
837
- function EditDescription() {
838
- const {
839
- description,
840
- setDescription
841
- } = React.useContext(PanelContext);
842
- const [localDesc, setLocalDesc] = useInputState(description);
843
- const changed = description !== localDesc;
844
- const submit = React.useCallback(() => {
845
- if (!changed) {
846
- return;
847
- }
848
- setDescription(localDesc);
849
- }, [changed, localDesc]);
850
- return /* @__PURE__ */ jsx(Textarea, {
851
- label: "Panel Description",
852
- value: localDesc,
853
- onChange: setLocalDesc,
854
- minRows: 2,
855
- rightSection: /* @__PURE__ */ jsx(ActionIcon, {
856
- disabled: !changed,
857
- onClick: submit,
858
- sx: {
859
- alignSelf: "flex-start",
860
- marginTop: "4px"
861
- },
862
- children: /* @__PURE__ */ jsx(DeviceFloppy, {
863
- size: 20
864
- })
865
- })
866
- });
867
- }
868
- function EditTitle() {
869
- const {
870
- title,
871
- setTitle
872
- } = React.useContext(PanelContext);
873
- const [localTitle, setLocalTitle] = useInputState(title);
874
- const changed = title !== localTitle;
875
- const submit = React.useCallback(() => {
876
- if (!changed) {
877
- return;
878
- }
879
- setTitle(localTitle);
880
- }, [changed, localTitle]);
881
- return /* @__PURE__ */ jsx(TextInput, {
882
- label: "Panel Title",
883
- value: localTitle,
884
- onChange: setLocalTitle,
885
- rightSection: /* @__PURE__ */ jsx(ActionIcon, {
886
- disabled: !changed,
887
- onClick: submit,
888
- children: /* @__PURE__ */ jsx(DeviceFloppy, {
889
- size: 20
890
- })
891
- })
892
- });
893
- }
894
- function VizBar3DPanel({
895
- conf,
896
- setConf
982
+ function VizBar3DPanel({
983
+ conf,
984
+ setConf
897
985
  }) {
898
986
  const defaultValues = _.assign({}, {
899
987
  "x_axis_data_key": "x",
@@ -916,7 +1004,7 @@ function VizBar3DPanel({
916
1004
  control,
917
1005
  handleSubmit,
918
1006
  formState
919
- } = useForm$1({
1007
+ } = useForm({
920
1008
  defaultValues
921
1009
  });
922
1010
  return /* @__PURE__ */ jsx(Group, {
@@ -1119,7 +1207,7 @@ function VizLineBarChartPanel({
1119
1207
  const initialValues = React.useMemo(() => __spreadValues({
1120
1208
  series: formList(series != null ? series : [])
1121
1209
  }, restConf), [series, restConf]);
1122
- const form = useForm({
1210
+ const form = useForm$1({
1123
1211
  initialValues
1124
1212
  });
1125
1213
  const addSeries = () => form.addListItem("series", {
@@ -1231,7 +1319,7 @@ function SunburstPanel({
1231
1319
  },
1232
1320
  setConf
1233
1321
  }) {
1234
- const form = useForm({
1322
+ const form = useForm$1({
1235
1323
  initialValues: {
1236
1324
  label_field,
1237
1325
  value_field
@@ -1318,7 +1406,7 @@ function VizTablePanel(_a) {
1318
1406
  ]), {
1319
1407
  setConf
1320
1408
  } = _b;
1321
- const form = useForm({
1409
+ const form = useForm$1({
1322
1410
  initialValues: __spreadValues({
1323
1411
  id_field: "id",
1324
1412
  use_raw_columns: true,
@@ -1593,7 +1681,7 @@ function VizTextPanel({
1593
1681
  setConf
1594
1682
  }) {
1595
1683
  var _a;
1596
- const form = useForm({
1684
+ const form = useForm$1({
1597
1685
  initialValues: {
1598
1686
  paragraphs: formList((_a = conf.paragraphs) != null ? _a : sampleParagraphs)
1599
1687
  }
@@ -1788,9 +1876,31 @@ function EditVizConf() {
1788
1876
  }
1789
1877
  function VizConfig({}) {
1790
1878
  return /* @__PURE__ */ jsxs(Group, {
1879
+ direction: "row",
1791
1880
  grow: true,
1792
- direction: "column",
1793
- children: [/* @__PURE__ */ jsx(EditTitle, {}), /* @__PURE__ */ jsx(EditDescription, {}), /* @__PURE__ */ jsx(Divider, {}), /* @__PURE__ */ jsx(EditVizConf, {})]
1881
+ noWrap: true,
1882
+ align: "stretch",
1883
+ sx: {
1884
+ height: "100%"
1885
+ },
1886
+ children: [/* @__PURE__ */ jsx(Group, {
1887
+ grow: true,
1888
+ direction: "column",
1889
+ noWrap: true,
1890
+ sx: {
1891
+ width: "40%",
1892
+ flexShrink: 0,
1893
+ flexGrow: 0
1894
+ },
1895
+ children: /* @__PURE__ */ jsx(EditVizConf, {})
1896
+ }), /* @__PURE__ */ jsx(Box, {
1897
+ sx: {
1898
+ height: "100%",
1899
+ flexGrow: 1,
1900
+ maxWidth: "60%"
1901
+ },
1902
+ children: /* @__PURE__ */ jsx(PreviewViz, {})
1903
+ })]
1794
1904
  });
1795
1905
  }
1796
1906
  function PanelSettingsModal({
@@ -1832,40 +1942,20 @@ function PanelSettingsModal({
1832
1942
  }
1833
1943
  },
1834
1944
  padding: "md",
1835
- navbar: /* @__PURE__ */ jsx(Navbar, {
1836
- width: {
1837
- base: "40%"
1838
- },
1839
- height: "100%",
1840
- p: "xs",
1841
- children: /* @__PURE__ */ jsxs(Tabs, {
1842
- initialTab: 1,
1843
- children: [/* @__PURE__ */ jsx(Tabs.Tab, {
1844
- label: "Context",
1845
- children: /* @__PURE__ */ jsx(ContextInfo, {})
1846
- }), /* @__PURE__ */ jsx(Tabs.Tab, {
1847
- label: "SQL Snippets",
1848
- children: /* @__PURE__ */ jsx(SQLSnippetsTab, {})
1849
- }), /* @__PURE__ */ jsx(Tabs.Tab, {
1850
- label: "SQL",
1851
- children: /* @__PURE__ */ jsx(QueryEditor, {})
1852
- }), /* @__PURE__ */ jsxs(Tabs.Tab, {
1853
- label: "Data",
1854
- children: [/* @__PURE__ */ jsx(LoadingOverlay, {
1855
- visible: loading
1856
- }), /* @__PURE__ */ jsx(QueryResult, {})]
1857
- }), /* @__PURE__ */ jsx(Tabs.Tab, {
1858
- label: "Viz Config",
1859
- children: /* @__PURE__ */ jsx(VizConfig, {})
1860
- })]
1861
- })
1862
- }),
1863
- children: /* @__PURE__ */ jsx(ErrorBoundary, {
1864
- children: /* @__PURE__ */ jsx(Viz, {
1865
- viz,
1866
- data,
1867
- loading
1868
- })
1945
+ children: /* @__PURE__ */ jsxs(Tabs, {
1946
+ initialTab: 2,
1947
+ children: [/* @__PURE__ */ jsxs(Tabs.Tab, {
1948
+ label: "Data Source",
1949
+ children: [/* @__PURE__ */ jsx(LoadingOverlay, {
1950
+ visible: loading
1951
+ }), /* @__PURE__ */ jsx(PickDataSource, {})]
1952
+ }), /* @__PURE__ */ jsx(Tabs.Tab, {
1953
+ label: "Panel",
1954
+ children: /* @__PURE__ */ jsx(PanelConfig, {})
1955
+ }), /* @__PURE__ */ jsx(Tabs.Tab, {
1956
+ label: "Visualization",
1957
+ children: /* @__PURE__ */ jsx(VizConfig, {})
1958
+ })]
1869
1959
  })
1870
1960
  })
1871
1961
  });
@@ -1876,8 +1966,6 @@ function PanelTitleBar({}) {
1876
1966
  const close = () => setOpened(false);
1877
1967
  const {
1878
1968
  title,
1879
- description,
1880
- loading,
1881
1969
  refreshData
1882
1970
  } = React.useContext(PanelContext);
1883
1971
  const {
@@ -1891,17 +1979,7 @@ function PanelTitleBar({}) {
1891
1979
  paddingBottom: "5px"
1892
1980
  },
1893
1981
  children: [/* @__PURE__ */ jsx(Group, {
1894
- children: description && /* @__PURE__ */ jsx(Tooltip, {
1895
- label: description,
1896
- withArrow: true,
1897
- children: /* @__PURE__ */ jsx(InfoCircle, {
1898
- size: 12,
1899
- style: {
1900
- verticalAlign: "baseline",
1901
- cursor: "pointer"
1902
- }
1903
- })
1904
- })
1982
+ children: /* @__PURE__ */ jsx(DescriptionPopover, {})
1905
1983
  }), /* @__PURE__ */ jsx(Group, {
1906
1984
  grow: true,
1907
1985
  position: "center",
@@ -1947,7 +2025,7 @@ function PanelTitleBar({}) {
1947
2025
  var index$1 = "";
1948
2026
  function Panel({
1949
2027
  viz: initialViz,
1950
- sql: initialSQL,
2028
+ dataSourceID: initialDataSourceID,
1951
2029
  title: initialTitle,
1952
2030
  description: initialDesc,
1953
2031
  update,
@@ -1958,24 +2036,35 @@ function Panel({
1958
2036
  const definitions = React.useContext(DefinitionContext);
1959
2037
  const [title, setTitle] = React.useState(initialTitle);
1960
2038
  const [description, setDescription] = React.useState(initialDesc);
1961
- const [sql, setSQL] = React.useState(initialSQL);
2039
+ const [dataSourceID, setDataSourceID] = React.useState(initialDataSourceID);
1962
2040
  const [viz, setViz] = React.useState(initialViz);
2041
+ const dataSource = React.useMemo(() => {
2042
+ if (!dataSourceID) {
2043
+ return void 0;
2044
+ }
2045
+ return definitions.dataSources.find((d) => d.id === dataSourceID);
2046
+ }, [dataSourceID, definitions.dataSources]);
1963
2047
  React.useEffect(() => {
1964
2048
  update == null ? void 0 : update({
1965
2049
  id,
1966
2050
  layout,
1967
2051
  title,
1968
2052
  description,
1969
- sql,
2053
+ dataSourceID,
1970
2054
  viz
1971
2055
  });
1972
- }, [title, description, sql, viz, id, layout]);
2056
+ }, [title, description, dataSource, viz, id, layout, dataSourceID]);
1973
2057
  const {
1974
2058
  data = [],
1975
2059
  loading,
1976
2060
  refresh
1977
- } = useRequest(queryBySQL(sql, contextInfo, definitions, title), {
1978
- refreshDeps: [contextInfo, definitions]
2061
+ } = useRequest(queryBySQL({
2062
+ context: contextInfo,
2063
+ definitions,
2064
+ title,
2065
+ dataSource
2066
+ }), {
2067
+ refreshDeps: [contextInfo, definitions, dataSource]
1979
2068
  });
1980
2069
  const refreshData = refresh;
1981
2070
  return /* @__PURE__ */ jsx(PanelContext.Provider, {
@@ -1986,8 +2075,8 @@ function Panel({
1986
2075
  setTitle,
1987
2076
  description,
1988
2077
  setDescription,
1989
- sql,
1990
- setSQL,
2078
+ dataSourceID,
2079
+ setDataSourceID,
1991
2080
  viz,
1992
2081
  setViz,
1993
2082
  refreshData
@@ -2041,7 +2130,7 @@ function DashboardLayout({
2041
2130
  const newPanels = panels.map((p2) => __spreadProps(__spreadValues({}, p2), {
2042
2131
  layout: m2.get(p2.id)
2043
2132
  }));
2044
- setPanels.setState(newPanels);
2133
+ setPanels(newPanels);
2045
2134
  }, [panels, setPanels]);
2046
2135
  return /* @__PURE__ */ jsx(ResponsiveReactGridLayout$1, {
2047
2136
  onBreakpointChange,
@@ -2064,7 +2153,10 @@ function DashboardLayout({
2064
2153
  }, rest), {
2065
2154
  destroy: () => onRemoveItem(id),
2066
2155
  update: (panel) => {
2067
- setPanels.setItem(index2, panel);
2156
+ setPanels((prevs) => {
2157
+ prevs.splice(index2, 1, panel);
2158
+ return [...prevs];
2159
+ });
2068
2160
  }
2069
2161
  }))
2070
2162
  }, id);
@@ -2101,147 +2193,763 @@ function ModeToggler({
2101
2193
  }]
2102
2194
  });
2103
2195
  }
2104
- function DashboardActions({
2105
- mode,
2106
- setMode,
2107
- hasChanges,
2108
- addPanel,
2109
- saveChanges
2110
- }) {
2111
- const inEditMode = mode === DashboardMode.Edit;
2196
+ const example = `
2197
+ -- You may reference context data or SQL snippets *by name*
2198
+ -- in SQL or VizConfig.
2199
+ SELECT *
2200
+ FROM commit
2201
+ WHERE
2202
+ -- context data
2203
+ author_time BETWEEN '\${timeRange?.[0].toISOString()}' AND '\${timeRange?.[1].toISOString()}'
2204
+ -- SQL snippets
2205
+ AND \${author_email_condition}
2206
+ \${order_by_clause}
2207
+ `;
2208
+ function ContextAndSnippets({}) {
2209
+ const contextInfo = React.useContext(ContextInfoContext);
2210
+ const {
2211
+ sqlSnippets
2212
+ } = React.useContext(DefinitionContext);
2213
+ const snippets = React.useMemo(() => {
2214
+ const snippets2 = sqlSnippets.reduce((prev, curr) => {
2215
+ prev[curr.key] = curr.value;
2216
+ return prev;
2217
+ }, {});
2218
+ return JSON.stringify(snippets2, null, 2);
2219
+ }, [sqlSnippets]);
2220
+ const context = React.useMemo(() => {
2221
+ return JSON.stringify(contextInfo, null, 2);
2222
+ }, [contextInfo]);
2112
2223
  return /* @__PURE__ */ jsxs(Group, {
2113
- position: "apart",
2114
- pt: "sm",
2115
- pb: "xs",
2224
+ direction: "column",
2225
+ grow: true,
2226
+ sx: {
2227
+ border: "1px solid #eee",
2228
+ maxWidth: "48%",
2229
+ overflow: "hidden"
2230
+ },
2116
2231
  children: [/* @__PURE__ */ jsx(Group, {
2117
2232
  position: "left",
2118
- children: /* @__PURE__ */ jsx(ModeToggler, {
2119
- mode,
2120
- setMode
2233
+ pl: "md",
2234
+ py: "md",
2235
+ sx: {
2236
+ borderBottom: "1px solid #eee",
2237
+ background: "#efefef",
2238
+ flexGrow: 0
2239
+ },
2240
+ children: /* @__PURE__ */ jsx(Text, {
2241
+ weight: 500,
2242
+ children: "Context"
2121
2243
  })
2122
- }), inEditMode && /* @__PURE__ */ jsxs(Group, {
2123
- position: "right",
2124
- children: [/* @__PURE__ */ jsx(Button, {
2125
- variant: "default",
2126
- size: "sm",
2127
- onClick: addPanel,
2128
- leftIcon: /* @__PURE__ */ jsx(PlaylistAdd, {
2129
- size: 20
2130
- }),
2131
- children: "Add a Panel"
2132
- }), /* @__PURE__ */ jsx(Button, {
2133
- variant: "default",
2134
- size: "sm",
2135
- onClick: saveChanges,
2136
- disabled: !hasChanges,
2137
- leftIcon: /* @__PURE__ */ jsx(DeviceFloppy, {
2138
- size: 20
2139
- }),
2140
- children: "Save Changes"
2141
- }), /* @__PURE__ */ jsx(Button, {
2142
- color: "red",
2143
- size: "sm",
2144
- disabled: !hasChanges,
2145
- leftIcon: /* @__PURE__ */ jsx(Recycle, {
2146
- size: 20
2147
- }),
2148
- children: "Revert Changes"
2244
+ }), /* @__PURE__ */ jsxs(Group, {
2245
+ direction: "column",
2246
+ px: "md",
2247
+ pb: "md",
2248
+ sx: {
2249
+ width: "100%"
2250
+ },
2251
+ children: [/* @__PURE__ */ jsx(Prism, {
2252
+ language: "sql",
2253
+ sx: {
2254
+ width: "100%"
2255
+ },
2256
+ noCopy: true,
2257
+ colorScheme: "dark",
2258
+ children: example
2259
+ }), /* @__PURE__ */ jsx(Text, {
2260
+ weight: 500,
2261
+ sx: {
2262
+ flexGrow: 0
2263
+ },
2264
+ children: "Avaiable context"
2265
+ }), /* @__PURE__ */ jsx(Prism, {
2266
+ language: "json",
2267
+ sx: {
2268
+ width: "100%"
2269
+ },
2270
+ noCopy: true,
2271
+ colorScheme: "dark",
2272
+ children: context
2273
+ }), /* @__PURE__ */ jsx(Text, {
2274
+ weight: 500,
2275
+ sx: {
2276
+ flexGrow: 0
2277
+ },
2278
+ children: "Avaiable SQL Snippets"
2279
+ }), /* @__PURE__ */ jsx(Prism, {
2280
+ language: "json",
2281
+ sx: {
2282
+ width: "100%"
2283
+ },
2284
+ noCopy: true,
2285
+ colorScheme: "dark",
2286
+ children: snippets
2149
2287
  })]
2150
- }), !inEditMode && /* @__PURE__ */ jsx(Button, {
2151
- variant: "default",
2152
- size: "sm",
2153
- disabled: true,
2154
- leftIcon: /* @__PURE__ */ jsx(Share, {
2155
- size: 20
2156
- }),
2157
- children: "Share"
2158
2288
  })]
2159
2289
  });
2160
2290
  }
2161
- function Dashboard({
2162
- dashboard,
2163
- update,
2164
- className = "dashboard"
2291
+ function DataSourceForm({
2292
+ value,
2293
+ onChange
2165
2294
  }) {
2166
- const [layoutFrozen, freezeLayout] = React.useState(false);
2167
- const [breakpoint, setBreakpoint] = React.useState();
2168
- const [localCols, setLocalCols] = React.useState();
2169
- const [panels, setPanels] = useListState(dashboard.panels);
2170
- const [sqlSnippets, setSQLSnippets] = React.useState(dashboard.definition.sqlSnippets);
2171
- const [mode, setMode] = React.useState(DashboardMode.Edit);
2172
- const hasChanges = React.useMemo(() => {
2173
- const cleanJSON = (v) => JSON.parse(JSON.stringify(v));
2174
- const panelsEqual = _.isEqual(cleanJSON(panels), cleanJSON(dashboard.panels));
2175
- if (!panelsEqual) {
2176
- return true;
2177
- }
2178
- return !_.isEqual(sqlSnippets, dashboard.definition.sqlSnippets);
2179
- }, [dashboard, panels, sqlSnippets]);
2180
- const saveDashboardChanges = async () => {
2181
- const d = _.merge({}, dashboard, {
2182
- panels
2183
- }, {
2184
- definition: {
2185
- sqlSnippets
2186
- }
2187
- });
2188
- await update(d);
2189
- };
2190
- const addPanel = () => {
2191
- const id = randomId();
2192
- const newItem = {
2193
- id,
2194
- layout: {
2195
- x: 0,
2196
- y: Infinity,
2197
- w: 3,
2295
+ const form = useForm$1({
2296
+ initialValues: value
2297
+ });
2298
+ const submit = React.useCallback((values) => {
2299
+ onChange(values);
2300
+ }, [onChange]);
2301
+ const changed = React.useMemo(() => {
2302
+ return !_.isEqual(value, form.values);
2303
+ }, [value, form.values]);
2304
+ React.useEffect(() => {
2305
+ form.reset();
2306
+ }, [value]);
2307
+ return /* @__PURE__ */ jsx(Group, {
2308
+ direction: "column",
2309
+ grow: true,
2310
+ sx: {
2311
+ border: "1px solid #eee",
2312
+ flexGrow: 1
2313
+ },
2314
+ children: /* @__PURE__ */ jsxs("form", {
2315
+ onSubmit: form.onSubmit(submit),
2316
+ children: [/* @__PURE__ */ jsxs(Group, {
2317
+ position: "left",
2318
+ py: "md",
2319
+ pl: "md",
2320
+ sx: {
2321
+ borderBottom: "1px solid #eee",
2322
+ background: "#efefef"
2323
+ },
2324
+ children: [/* @__PURE__ */ jsx(Text, {
2325
+ weight: 500,
2326
+ children: "Data Source Configuration"
2327
+ }), /* @__PURE__ */ jsx(ActionIcon, {
2328
+ type: "submit",
2329
+ mr: 5,
2330
+ variant: "filled",
2331
+ color: "blue",
2332
+ disabled: !changed,
2333
+ children: /* @__PURE__ */ jsx(DeviceFloppy, {
2334
+ size: 20
2335
+ })
2336
+ })]
2337
+ }), /* @__PURE__ */ jsxs(Group, {
2338
+ direction: "column",
2339
+ grow: true,
2340
+ my: 0,
2341
+ p: "md",
2342
+ pr: 40,
2343
+ children: [/* @__PURE__ */ jsxs(Group, {
2344
+ grow: true,
2345
+ children: [/* @__PURE__ */ jsx(TextInput, __spreadValues({
2346
+ placeholder: "An ID unique in this dashboard",
2347
+ label: "ID",
2348
+ required: true,
2349
+ sx: {
2350
+ flex: 1
2351
+ }
2352
+ }, form.getInputProps("id"))), /* @__PURE__ */ jsx(TextInput, __spreadValues({
2353
+ placeholder: "TODO: use a dedicated UI component for this",
2354
+ label: "Data Source Key",
2355
+ required: true,
2356
+ sx: {
2357
+ flex: 1
2358
+ }
2359
+ }, form.getInputProps("key"))), /* @__PURE__ */ jsx(TextInput, __spreadValues({
2360
+ placeholder: "Type of the data source",
2361
+ label: "Type",
2362
+ disabled: true,
2363
+ sx: {
2364
+ flex: 1
2365
+ }
2366
+ }, form.getInputProps("type")))]
2367
+ }), /* @__PURE__ */ jsx(Textarea, __spreadValues({
2368
+ autosize: true,
2369
+ minRows: 12,
2370
+ maxRows: 24
2371
+ }, form.getInputProps("sql")))]
2372
+ })]
2373
+ })
2374
+ });
2375
+ }
2376
+ function DataSourceEditor({
2377
+ id
2378
+ }) {
2379
+ const {
2380
+ dataSources,
2381
+ setDataSources
2382
+ } = React.useContext(DefinitionContext);
2383
+ const dataSource = React.useMemo(() => {
2384
+ return dataSources.find((d) => d.id === id);
2385
+ }, [dataSources, id]);
2386
+ const update = React.useCallback((value) => {
2387
+ const index2 = dataSources.findIndex((d) => d.id === value.id);
2388
+ if (!index2) {
2389
+ console.error(new Error("Invalid data source id when updating by id"));
2390
+ return;
2391
+ }
2392
+ setDataSources((prevs) => {
2393
+ const index22 = prevs.findIndex((p2) => p2.id === id);
2394
+ prevs.splice(index22, 1, value);
2395
+ return [...prevs];
2396
+ });
2397
+ }, [setDataSources]);
2398
+ if (!dataSource) {
2399
+ return /* @__PURE__ */ jsx("span", {
2400
+ children: "Invalid Data Source ID"
2401
+ });
2402
+ }
2403
+ return /* @__PURE__ */ jsxs(Group, {
2404
+ direction: "row",
2405
+ position: "apart",
2406
+ grow: true,
2407
+ align: "stretch",
2408
+ noWrap: true,
2409
+ children: [/* @__PURE__ */ jsx(DataSourceForm, {
2410
+ value: dataSource,
2411
+ onChange: update
2412
+ }), /* @__PURE__ */ jsx(ContextAndSnippets, {})]
2413
+ });
2414
+ }
2415
+ function SelectOrAddDataSource({
2416
+ id,
2417
+ setID
2418
+ }) {
2419
+ const {
2420
+ dataSources,
2421
+ setDataSources
2422
+ } = React.useContext(DefinitionContext);
2423
+ const chooseDefault = React.useCallback(() => {
2424
+ var _a, _b;
2425
+ setID((_b = (_a = dataSources[0]) == null ? void 0 : _a.id) != null ? _b : "");
2426
+ }, [setID, dataSources]);
2427
+ React.useEffect(() => {
2428
+ if (!id) {
2429
+ chooseDefault();
2430
+ return;
2431
+ }
2432
+ const index2 = dataSources.findIndex((d) => d.id === id);
2433
+ if (index2 === -1) {
2434
+ chooseDefault();
2435
+ }
2436
+ }, [id, dataSources, chooseDefault]);
2437
+ const options = React.useMemo(() => {
2438
+ return dataSources.map((d) => ({
2439
+ value: d.id,
2440
+ label: d.id
2441
+ }));
2442
+ }, [dataSources]);
2443
+ const add = React.useCallback(() => {
2444
+ const newDataSource = {
2445
+ id: randomId(),
2446
+ type: "postgresql",
2447
+ key: "",
2448
+ sql: ""
2449
+ };
2450
+ setDataSources((prevs) => [...prevs, newDataSource]);
2451
+ }, [setDataSources]);
2452
+ return /* @__PURE__ */ jsx(Group, {
2453
+ pb: "xl",
2454
+ children: /* @__PURE__ */ jsxs(Group, {
2455
+ position: "left",
2456
+ sx: {
2457
+ maxWidth: "600px",
2458
+ alignItems: "baseline"
2459
+ },
2460
+ children: [/* @__PURE__ */ jsx(Text, {
2461
+ children: "Select a Data Source"
2462
+ }), /* @__PURE__ */ jsx(Select, {
2463
+ data: options,
2464
+ value: id,
2465
+ onChange: setID,
2466
+ allowDeselect: false,
2467
+ clearable: false,
2468
+ sx: {
2469
+ flexGrow: 1
2470
+ }
2471
+ }), /* @__PURE__ */ jsx(Text, {
2472
+ children: "or"
2473
+ }), /* @__PURE__ */ jsx(Group, {
2474
+ position: "center",
2475
+ mt: "md",
2476
+ children: /* @__PURE__ */ jsx(Button, {
2477
+ onClick: add,
2478
+ children: "Add a Data Source"
2479
+ })
2480
+ })]
2481
+ })
2482
+ });
2483
+ }
2484
+ function EditDataSourcesModal({
2485
+ opened,
2486
+ close
2487
+ }) {
2488
+ const [id, setID] = React.useState("");
2489
+ const {
2490
+ freezeLayout
2491
+ } = React.useContext(LayoutStateContext);
2492
+ React.useEffect(() => {
2493
+ freezeLayout(opened);
2494
+ }, [opened]);
2495
+ return /* @__PURE__ */ jsx(Modal, {
2496
+ size: "96vw",
2497
+ overflow: "inside",
2498
+ opened,
2499
+ onClose: close,
2500
+ title: "Data Sources",
2501
+ trapFocus: true,
2502
+ onDragStart: (e) => {
2503
+ e.stopPropagation();
2504
+ },
2505
+ children: /* @__PURE__ */ jsxs(AppShell, {
2506
+ sx: {
2507
+ height: "90vh",
2508
+ maxHeight: "calc(100vh - 185px)",
2509
+ ".mantine-AppShell-body": {
2510
+ height: "100%"
2511
+ },
2512
+ main: {
2513
+ height: "100%",
2514
+ width: "100%",
2515
+ padding: 0,
2516
+ margin: 0
2517
+ }
2518
+ },
2519
+ padding: "md",
2520
+ header: /* @__PURE__ */ jsx(SelectOrAddDataSource, {
2521
+ id,
2522
+ setID
2523
+ }),
2524
+ children: [/* @__PURE__ */ jsx(DataSourceEditor, {
2525
+ id
2526
+ }), /* @__PURE__ */ jsx(DataPreview, {
2527
+ id
2528
+ })]
2529
+ })
2530
+ });
2531
+ }
2532
+ function ContextInfo({}) {
2533
+ const contextInfo = React.useContext(ContextInfoContext);
2534
+ const sampleSQL = `SELECT *
2535
+ FROM commit
2536
+ WHERE author_time BETWEEN '\${timeRange?.[0].toISOString()}' AND '\${timeRange?.[1].toISOString()}'`;
2537
+ return /* @__PURE__ */ jsxs(Group, {
2538
+ direction: "column",
2539
+ grow: true,
2540
+ sx: {
2541
+ border: "1px solid #eee",
2542
+ maxWidth: "48%",
2543
+ overflow: "hidden"
2544
+ },
2545
+ children: [/* @__PURE__ */ jsx(Group, {
2546
+ position: "left",
2547
+ pl: "md",
2548
+ py: "md",
2549
+ sx: {
2550
+ borderBottom: "1px solid #eee",
2551
+ background: "#efefef",
2552
+ flexGrow: 0
2553
+ },
2554
+ children: /* @__PURE__ */ jsx(Text, {
2555
+ weight: 500,
2556
+ children: "Context"
2557
+ })
2558
+ }), /* @__PURE__ */ jsxs(Group, {
2559
+ direction: "column",
2560
+ px: "md",
2561
+ pb: "md",
2562
+ sx: {
2563
+ width: "100%"
2564
+ },
2565
+ children: [/* @__PURE__ */ jsx(Prism, {
2566
+ language: "sql",
2567
+ sx: {
2568
+ width: "100%"
2569
+ },
2570
+ noCopy: true,
2571
+ colorScheme: "dark",
2572
+ children: `-- You may refer context data *by name*
2573
+ -- in SQL or VizConfig.
2574
+
2575
+ ${sampleSQL}`
2576
+ }), /* @__PURE__ */ jsx(Text, {
2577
+ weight: 500,
2578
+ sx: {
2579
+ flexGrow: 0
2580
+ },
2581
+ children: "Avaiable context entries"
2582
+ }), /* @__PURE__ */ jsx(Prism, {
2583
+ language: "json",
2584
+ sx: {
2585
+ width: "100%"
2586
+ },
2587
+ noCopy: true,
2588
+ colorScheme: "dark",
2589
+ children: JSON.stringify(contextInfo, null, 2)
2590
+ })]
2591
+ })]
2592
+ });
2593
+ }
2594
+ function SQLSnippetsEditor({}) {
2595
+ const {
2596
+ sqlSnippets,
2597
+ setSQLSnippets
2598
+ } = React.useContext(DefinitionContext);
2599
+ const sampleSQL = `SELECT *
2600
+ FROM commit
2601
+ WHERE \${author_time_condition}`;
2602
+ const initialValues = React.useMemo(() => ({
2603
+ snippets: formList(sqlSnippets != null ? sqlSnippets : [])
2604
+ }), [sqlSnippets]);
2605
+ const form = useForm$1({
2606
+ initialValues
2607
+ });
2608
+ const addSnippet = () => form.addListItem("snippets", {
2609
+ key: randomId(),
2610
+ value: ""
2611
+ });
2612
+ const changed = React.useMemo(() => !_.isEqual(form.values, initialValues), [form.values, initialValues]);
2613
+ const submit = ({
2614
+ snippets
2615
+ }) => {
2616
+ setSQLSnippets(snippets);
2617
+ };
2618
+ return /* @__PURE__ */ jsxs(Group, {
2619
+ direction: "column",
2620
+ grow: true,
2621
+ sx: {
2622
+ border: "1px solid #eee"
2623
+ },
2624
+ children: [/* @__PURE__ */ jsxs(Group, {
2625
+ position: "left",
2626
+ pl: "md",
2627
+ py: "md",
2628
+ sx: {
2629
+ borderBottom: "1px solid #eee",
2630
+ background: "#efefef",
2631
+ flexGrow: 0
2632
+ },
2633
+ children: [/* @__PURE__ */ jsx(Text, {
2634
+ weight: 500,
2635
+ children: "SQL Snippets"
2636
+ }), /* @__PURE__ */ jsx(ActionIcon, {
2637
+ type: "submit",
2638
+ mr: 5,
2639
+ variant: "filled",
2640
+ color: "blue",
2641
+ disabled: !changed,
2642
+ children: /* @__PURE__ */ jsx(DeviceFloppy, {
2643
+ size: 20
2644
+ })
2645
+ })]
2646
+ }), /* @__PURE__ */ jsxs(Group, {
2647
+ px: "md",
2648
+ pb: "md",
2649
+ children: [/* @__PURE__ */ jsx(Prism, {
2650
+ language: "sql",
2651
+ sx: {
2652
+ width: "100%"
2653
+ },
2654
+ noCopy: true,
2655
+ trim: false,
2656
+ colorScheme: "dark",
2657
+ children: `-- You may refer context data *by name*
2658
+ -- in SQL or VizConfig.
2659
+
2660
+ ${sampleSQL}
2661
+
2662
+ -- where author_time_condition is:
2663
+ author_time BETWEEN '\${timeRange?.[0].toISOString()}' AND '\${timeRange?.[1].toISOString()}'
2664
+ `
2665
+ }), /* @__PURE__ */ jsx(Group, {
2666
+ direction: "column",
2667
+ sx: {
2668
+ width: "100%",
2669
+ position: "relative"
2670
+ },
2671
+ grow: true,
2672
+ children: /* @__PURE__ */ jsxs("form", {
2673
+ onSubmit: form.onSubmit(submit),
2674
+ children: [form.values.snippets.map((_item, index2) => /* @__PURE__ */ jsxs(Group, {
2675
+ direction: "column",
2676
+ grow: true,
2677
+ my: 0,
2678
+ p: "md",
2679
+ pr: 40,
2680
+ sx: {
2681
+ border: "1px solid #eee",
2682
+ position: "relative"
2683
+ },
2684
+ children: [/* @__PURE__ */ jsx(TextInput, __spreadValues({
2685
+ label: "Key",
2686
+ required: true
2687
+ }, form.getListInputProps("snippets", index2, "key"))), /* @__PURE__ */ jsx(Textarea, __spreadValues({
2688
+ minRows: 3,
2689
+ label: "Value",
2690
+ required: true
2691
+ }, form.getListInputProps("snippets", index2, "value"))), /* @__PURE__ */ jsx(ActionIcon, {
2692
+ color: "red",
2693
+ variant: "hover",
2694
+ onClick: () => form.removeListItem("snippets", index2),
2695
+ sx: {
2696
+ position: "absolute",
2697
+ top: 15,
2698
+ right: 5
2699
+ },
2700
+ children: /* @__PURE__ */ jsx(Trash, {
2701
+ size: 16
2702
+ })
2703
+ })]
2704
+ }, index2)), /* @__PURE__ */ jsx(Group, {
2705
+ position: "center",
2706
+ mt: "xl",
2707
+ grow: true,
2708
+ sx: {
2709
+ width: "40%"
2710
+ },
2711
+ mx: "auto",
2712
+ children: /* @__PURE__ */ jsx(Button, {
2713
+ variant: "default",
2714
+ onClick: addSnippet,
2715
+ children: "Add a snippet"
2716
+ })
2717
+ })]
2718
+ })
2719
+ })]
2720
+ })]
2721
+ });
2722
+ }
2723
+ function EditSQLSnippetsModal({
2724
+ opened,
2725
+ close
2726
+ }) {
2727
+ const {
2728
+ freezeLayout
2729
+ } = React.useContext(LayoutStateContext);
2730
+ React.useEffect(() => {
2731
+ freezeLayout(opened);
2732
+ }, [opened]);
2733
+ return /* @__PURE__ */ jsx(Modal, {
2734
+ size: "96vw",
2735
+ overflow: "inside",
2736
+ opened,
2737
+ onClose: close,
2738
+ title: "SQL Snippets",
2739
+ trapFocus: true,
2740
+ onDragStart: (e) => {
2741
+ e.stopPropagation();
2742
+ },
2743
+ children: /* @__PURE__ */ jsx(AppShell, {
2744
+ sx: {
2745
+ height: "90vh",
2746
+ maxHeight: "calc(100vh - 185px)",
2747
+ ".mantine-AppShell-body": {
2748
+ height: "100%"
2749
+ },
2750
+ main: {
2751
+ height: "100%",
2752
+ width: "100%",
2753
+ padding: 0,
2754
+ margin: 0
2755
+ }
2756
+ },
2757
+ padding: "md",
2758
+ children: /* @__PURE__ */ jsxs(Group, {
2759
+ direction: "row",
2760
+ position: "apart",
2761
+ grow: true,
2762
+ align: "stretch",
2763
+ noWrap: true,
2764
+ children: [/* @__PURE__ */ jsx(SQLSnippetsEditor, {}), /* @__PURE__ */ jsx(ContextInfo, {})]
2765
+ })
2766
+ })
2767
+ });
2768
+ }
2769
+ function DashboardActions({
2770
+ mode,
2771
+ setMode,
2772
+ hasChanges,
2773
+ addPanel,
2774
+ saveChanges
2775
+ }) {
2776
+ const [dataSourcesOpened, setDataSourcesOpened] = React.useState(false);
2777
+ const openDataSources = () => setDataSourcesOpened(true);
2778
+ const closeDataSources = () => setDataSourcesOpened(false);
2779
+ const [sqlSnippetsOpened, setSQLSnippetsOpened] = React.useState(false);
2780
+ const openSQLSnippets = () => setSQLSnippetsOpened(true);
2781
+ const closeSQLSnippets = () => setSQLSnippetsOpened(false);
2782
+ const inEditMode = mode === DashboardMode.Edit;
2783
+ return /* @__PURE__ */ jsxs(Group, {
2784
+ position: "apart",
2785
+ pt: "sm",
2786
+ pb: "xs",
2787
+ children: [/* @__PURE__ */ jsx(Group, {
2788
+ position: "left",
2789
+ children: /* @__PURE__ */ jsx(ModeToggler, {
2790
+ mode,
2791
+ setMode
2792
+ })
2793
+ }), inEditMode && /* @__PURE__ */ jsxs(Fragment, {
2794
+ children: [/* @__PURE__ */ jsxs(Group, {
2795
+ position: "right",
2796
+ children: [/* @__PURE__ */ jsx(Button, {
2797
+ variant: "default",
2798
+ size: "sm",
2799
+ onClick: addPanel,
2800
+ leftIcon: /* @__PURE__ */ jsx(PlaylistAdd, {
2801
+ size: 20
2802
+ }),
2803
+ children: "Add a Panel"
2804
+ }), /* @__PURE__ */ jsx(Button, {
2805
+ variant: "default",
2806
+ size: "sm",
2807
+ onClick: openSQLSnippets,
2808
+ leftIcon: /* @__PURE__ */ jsx(ClipboardText, {
2809
+ size: 20
2810
+ }),
2811
+ children: "SQL Snippets"
2812
+ }), /* @__PURE__ */ jsx(Button, {
2813
+ variant: "default",
2814
+ size: "sm",
2815
+ onClick: openDataSources,
2816
+ leftIcon: /* @__PURE__ */ jsx(Database, {
2817
+ size: 20
2818
+ }),
2819
+ children: "Data Sources"
2820
+ }), /* @__PURE__ */ jsx(Button, {
2821
+ variant: "default",
2822
+ size: "sm",
2823
+ onClick: saveChanges,
2824
+ disabled: !hasChanges,
2825
+ leftIcon: /* @__PURE__ */ jsx(DeviceFloppy, {
2826
+ size: 20
2827
+ }),
2828
+ children: "Save Changes"
2829
+ }), /* @__PURE__ */ jsx(Button, {
2830
+ color: "red",
2831
+ size: "sm",
2832
+ disabled: !hasChanges,
2833
+ leftIcon: /* @__PURE__ */ jsx(Recycle, {
2834
+ size: 20
2835
+ }),
2836
+ children: "Revert Changes"
2837
+ })]
2838
+ }), /* @__PURE__ */ jsx(EditDataSourcesModal, {
2839
+ opened: dataSourcesOpened,
2840
+ close: closeDataSources
2841
+ }), /* @__PURE__ */ jsx(EditSQLSnippetsModal, {
2842
+ opened: sqlSnippetsOpened,
2843
+ close: closeSQLSnippets
2844
+ })]
2845
+ }), !inEditMode && /* @__PURE__ */ jsx(Button, {
2846
+ variant: "default",
2847
+ size: "sm",
2848
+ disabled: true,
2849
+ leftIcon: /* @__PURE__ */ jsx(Share, {
2850
+ size: 20
2851
+ }),
2852
+ children: "Share"
2853
+ })]
2854
+ });
2855
+ }
2856
+ function Dashboard({
2857
+ context,
2858
+ dashboard,
2859
+ update,
2860
+ className = "dashboard"
2861
+ }) {
2862
+ const [layoutFrozen, freezeLayout] = React.useState(false);
2863
+ const [breakpoint, setBreakpoint] = React.useState();
2864
+ const [localCols, setLocalCols] = React.useState();
2865
+ const [panels, setPanels] = React.useState(dashboard.panels);
2866
+ const [sqlSnippets, setSQLSnippets] = React.useState(dashboard.definition.sqlSnippets);
2867
+ const [dataSources, setDataSources] = React.useState(dashboard.definition.dataSources);
2868
+ const [mode, setMode] = React.useState(DashboardMode.Edit);
2869
+ const hasChanges = React.useMemo(() => {
2870
+ const cleanJSON = (v) => JSON.parse(JSON.stringify(v));
2871
+ const panelsEqual = _.isEqual(cleanJSON(panels), cleanJSON(dashboard.panels));
2872
+ if (!panelsEqual) {
2873
+ return true;
2874
+ }
2875
+ if (!_.isEqual(sqlSnippets, dashboard.definition.sqlSnippets)) {
2876
+ return true;
2877
+ }
2878
+ return !_.isEqual(dataSources, dashboard.definition.dataSources);
2879
+ }, [dashboard, panels, sqlSnippets, dataSources]);
2880
+ const saveDashboardChanges = async () => {
2881
+ const d = _.merge({}, dashboard, {
2882
+ panels
2883
+ }, {
2884
+ definition: {
2885
+ sqlSnippets
2886
+ }
2887
+ });
2888
+ await update(d);
2889
+ };
2890
+ const addPanel = () => {
2891
+ const id = randomId();
2892
+ const newItem = {
2893
+ id,
2894
+ layout: {
2895
+ x: 0,
2896
+ y: Infinity,
2897
+ w: 3,
2198
2898
  h: 4
2199
2899
  },
2200
2900
  title: `New Panel - ${id}`,
2201
2901
  description: "description goes here",
2202
- sql: "",
2902
+ dataSourceID: "",
2203
2903
  viz: {
2204
2904
  type: "table",
2205
2905
  conf: {}
2206
2906
  }
2207
2907
  };
2208
- setPanels.append(newItem);
2908
+ setPanels((prevs) => [...prevs, newItem]);
2209
2909
  };
2210
2910
  const removePanelByID = (id) => {
2211
2911
  const index2 = panels.findIndex((p2) => p2.id === id);
2212
- setPanels.remove(index2);
2912
+ setPanels((prevs) => {
2913
+ prevs.splice(index2, 1);
2914
+ return [...prevs];
2915
+ });
2213
2916
  };
2214
2917
  const inEditMode = mode === DashboardMode.Edit;
2215
2918
  const definitions = React.useMemo(() => ({
2216
2919
  sqlSnippets,
2217
- setSQLSnippets
2218
- }), [sqlSnippets, setSQLSnippets]);
2219
- return /* @__PURE__ */ jsx("div", {
2220
- className,
2221
- children: /* @__PURE__ */ jsx(DefinitionContext.Provider, {
2222
- value: definitions,
2223
- children: /* @__PURE__ */ jsxs(LayoutStateContext.Provider, {
2224
- value: {
2225
- layoutFrozen,
2226
- freezeLayout,
2227
- mode,
2228
- inEditMode
2229
- },
2230
- children: [/* @__PURE__ */ jsx(DashboardActions, {
2231
- mode,
2232
- setMode,
2233
- hasChanges,
2234
- addPanel,
2235
- saveChanges: saveDashboardChanges
2236
- }), /* @__PURE__ */ jsx(DashboardLayout, {
2237
- panels,
2238
- setPanels,
2239
- isDraggable: inEditMode && !layoutFrozen,
2240
- isResizable: inEditMode && !layoutFrozen,
2241
- onRemoveItem: removePanelByID,
2242
- setLocalCols,
2243
- setBreakpoint
2244
- })]
2920
+ setSQLSnippets,
2921
+ dataSources,
2922
+ setDataSources
2923
+ }), [sqlSnippets, setSQLSnippets, dataSources, setDataSources]);
2924
+ return /* @__PURE__ */ jsx(ContextInfoContext.Provider, {
2925
+ value: context,
2926
+ children: /* @__PURE__ */ jsx("div", {
2927
+ className,
2928
+ children: /* @__PURE__ */ jsx(DefinitionContext.Provider, {
2929
+ value: definitions,
2930
+ children: /* @__PURE__ */ jsxs(LayoutStateContext.Provider, {
2931
+ value: {
2932
+ layoutFrozen,
2933
+ freezeLayout,
2934
+ mode,
2935
+ inEditMode
2936
+ },
2937
+ children: [/* @__PURE__ */ jsx(DashboardActions, {
2938
+ mode,
2939
+ setMode,
2940
+ hasChanges,
2941
+ addPanel,
2942
+ saveChanges: saveDashboardChanges
2943
+ }), /* @__PURE__ */ jsx(DashboardLayout, {
2944
+ panels,
2945
+ setPanels,
2946
+ isDraggable: inEditMode && !layoutFrozen,
2947
+ isResizable: inEditMode && !layoutFrozen,
2948
+ onRemoveItem: removePanelByID,
2949
+ setLocalCols,
2950
+ setBreakpoint
2951
+ })]
2952
+ })
2245
2953
  })
2246
2954
  })
2247
2955
  });
@@ -2281,29 +2989,33 @@ function ReadOnlyDashboardLayout({
2281
2989
  });
2282
2990
  }
2283
2991
  function ReadOnlyDashboard({
2992
+ context,
2284
2993
  dashboard,
2285
2994
  className = "dashboard"
2286
2995
  }) {
2287
- const [panels, setPanels] = useListState(dashboard.panels);
2288
- const [sqlSnippets, setSQLSnippets] = React.useState(dashboard.definition.sqlSnippets);
2289
- const definitions = React.useMemo(() => ({
2290
- sqlSnippets,
2291
- setSQLSnippets
2292
- }), [sqlSnippets, setSQLSnippets]);
2293
- return /* @__PURE__ */ jsx("div", {
2294
- className,
2295
- children: /* @__PURE__ */ jsx(DefinitionContext.Provider, {
2296
- value: definitions,
2297
- children: /* @__PURE__ */ jsx(LayoutStateContext.Provider, {
2298
- value: {
2299
- layoutFrozen: true,
2300
- freezeLayout: () => {
2996
+ const definition = React.useMemo(() => __spreadProps(__spreadValues({}, dashboard.definition), {
2997
+ setSQLSnippets: () => {
2998
+ },
2999
+ setDataSources: () => {
3000
+ }
3001
+ }), [dashboard]);
3002
+ return /* @__PURE__ */ jsx(ContextInfoContext.Provider, {
3003
+ value: context,
3004
+ children: /* @__PURE__ */ jsx("div", {
3005
+ className,
3006
+ children: /* @__PURE__ */ jsx(DefinitionContext.Provider, {
3007
+ value: definition,
3008
+ children: /* @__PURE__ */ jsx(LayoutStateContext.Provider, {
3009
+ value: {
3010
+ layoutFrozen: true,
3011
+ freezeLayout: () => {
3012
+ },
3013
+ mode: DashboardMode.Use,
3014
+ inEditMode: false
2301
3015
  },
2302
- mode: DashboardMode.Use,
2303
- inEditMode: false
2304
- },
2305
- children: /* @__PURE__ */ jsx(ReadOnlyDashboardLayout, {
2306
- panels
3016
+ children: /* @__PURE__ */ jsx(ReadOnlyDashboardLayout, {
3017
+ panels: dashboard.panels
3018
+ })
2307
3019
  })
2308
3020
  })
2309
3021
  })