@devtable/dashboard 0.3.0 → 1.0.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 (31) 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 +1111 -492
  5. package/dist/dashboard.umd.js +16 -5
  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/layout/read-only.d.ts +16 -0
  18. package/dist/main/index.d.ts +2 -9
  19. package/dist/main/main.d.ts +11 -0
  20. package/dist/main/read-only.d.ts +10 -0
  21. package/dist/panel/index.d.ts +3 -3
  22. package/dist/panel/settings/pick-data-source/index.d.ts +5 -0
  23. package/dist/panel/settings/viz-config/preview-viz.d.ts +5 -0
  24. package/dist/style.css +1 -1
  25. package/dist/types/dashboard.d.ts +8 -1
  26. package/package.json +2 -2
  27. package/dist/panel/settings/query-editor/index.d.ts +0 -2
  28. package/dist/panel/settings/query-editor/sql-query-editor/index.d.ts +0 -5
  29. package/dist/panel/settings/query-result/index.d.ts +0 -5
  30. package/dist/panel/settings/sql-snippets/form.d.ts +0 -9
  31. package/dist/panel/settings/sql-snippets/index.d.ts +0 -5
@@ -32,11 +32,11 @@ 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 { LoadingOverlay, Table, Group, Text, Select, Textarea, ActionIcon, TextInput, Button, useMantineTheme, ColorSwatch, Switch, Slider, JsonInput, Divider, Box, Modal, AppShell, Tabs, Tooltip, Menu, Container, SegmentedControl } 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 { DeviceFloppy, Trash, InfoCircle, Refresh, Settings, Paint, PlayerPlay, PlaylistAdd, ClipboardText, Database, Recycle, Share } from "tabler-icons-react";
39
+ import { useInputState, useElementSize, randomId } from "@mantine/hooks";
40
40
  import ReactEChartsCore from "echarts-for-react/lib/core";
41
41
  import * as echarts from "echarts/core";
42
42
  import { SunburstChart, BarChart, LineChart } from "echarts/charts";
@@ -44,9 +44,9 @@ import { CanvasRenderer } from "echarts/renderers";
44
44
  import { GridComponent, LegendComponent, TooltipComponent, VisualMapComponent } from "echarts/components";
45
45
  import numbro from "numbro";
46
46
  import "echarts-gl";
47
+ import { useForm, Controller } from "react-hook-form";
48
+ import { formList, useForm as useForm$1 } from "@mantine/form";
47
49
  import { Prism } from "@mantine/prism";
48
- import { formList, useForm } from "@mantine/form";
49
- import { useForm as useForm$1, Controller } from "react-hook-form";
50
50
  var DashboardMode = /* @__PURE__ */ ((DashboardMode2) => {
51
51
  DashboardMode2["Use"] = "use";
52
52
  DashboardMode2["Edit"] = "edit";
@@ -87,7 +87,14 @@ const post = getRequest("POST");
87
87
  function formatSQL(sql, params) {
88
88
  const names = Object.keys(params);
89
89
  const vals = Object.values(params);
90
- return new Function(...names, `return \`${sql}\`;`)(...vals);
90
+ try {
91
+ return new Function(...names, `return \`${sql}\`;`)(...vals);
92
+ } catch (error) {
93
+ if (names.length === 0 && sql.includes("$")) {
94
+ throw new Error("[formatSQL] insufficient params");
95
+ }
96
+ throw error;
97
+ }
91
98
  }
92
99
  function getSQLParams(context, definitions) {
93
100
  const sqlSnippetRecord = definitions.sqlSnippets.reduce((ret, curr) => {
@@ -96,25 +103,26 @@ function getSQLParams(context, definitions) {
96
103
  }, {});
97
104
  return _.merge({}, sqlSnippetRecord, context);
98
105
  }
99
- const queryBySQL = (sql, context, definitions, title) => async () => {
100
- if (!sql) {
106
+ const queryBySQL = ({ context, definitions, title, dataSource }) => async () => {
107
+ if (!dataSource || !dataSource.sql) {
101
108
  return [];
102
109
  }
110
+ const { type, key, sql } = dataSource;
103
111
  const needParams = sql.includes("$");
104
- const params = getSQLParams(context, definitions);
105
- console.log(needParams, params);
106
- if (needParams && Object.keys(params).length === 0) {
107
- console.error(`[queryBySQL] insufficient params for {${title}}'s SQL`);
112
+ try {
113
+ const params = getSQLParams(context, definitions);
114
+ const formattedSQL = formatSQL(sql, params);
115
+ if (needParams) {
116
+ console.groupCollapsed(`Final SQL for: ${title}`);
117
+ console.log(formattedSQL);
118
+ console.groupEnd();
119
+ }
120
+ const res = await post("/query", { type, key, sql: formattedSQL });
121
+ return res;
122
+ } catch (error) {
123
+ console.error(error);
108
124
  return [];
109
125
  }
110
- const formattedSQL = formatSQL(sql, params);
111
- if (needParams) {
112
- console.groupCollapsed(`Final SQL for: ${title}`);
113
- console.log(formattedSQL);
114
- console.groupEnd();
115
- }
116
- const res = await post("/query", { sql: formattedSQL });
117
- return res;
118
126
  };
119
127
  const initialContext$2 = {};
120
128
  const initialContextInfoContext = initialContext$2;
@@ -128,8 +136,8 @@ const initialContext$1 = {
128
136
  description: "",
129
137
  setDescription: () => {
130
138
  },
131
- sql: "",
132
- setSQL: () => {
139
+ dataSourceID: "",
140
+ setDataSourceID: () => {
133
141
  },
134
142
  viz: {
135
143
  type: "",
@@ -141,6 +149,15 @@ const initialContext$1 = {
141
149
  }
142
150
  };
143
151
  const PanelContext = React.createContext(initialContext$1);
152
+ const initialContext = {
153
+ sqlSnippets: [],
154
+ setSQLSnippets: () => {
155
+ },
156
+ dataSources: [],
157
+ setDataSources: () => {
158
+ }
159
+ };
160
+ const DefinitionContext = React.createContext(initialContext);
144
161
  var jsxRuntime = { exports: {} };
145
162
  var reactJsxRuntime_production_min = {};
146
163
  /**
@@ -174,6 +191,152 @@ reactJsxRuntime_production_min.jsxs = q;
174
191
  const jsx = jsxRuntime.exports.jsx;
175
192
  const jsxs = jsxRuntime.exports.jsxs;
176
193
  const Fragment = jsxRuntime.exports.Fragment;
194
+ function DataPreview({
195
+ id
196
+ }) {
197
+ const definitions = React.useContext(DefinitionContext);
198
+ const contextInfo = React.useContext(ContextInfoContext);
199
+ const dataSource = React.useMemo(() => {
200
+ return definitions.dataSources.find((d) => d.id === id);
201
+ }, [definitions.dataSources, id]);
202
+ const {
203
+ data = [],
204
+ loading
205
+ } = useRequest(queryBySQL({
206
+ context: contextInfo,
207
+ definitions,
208
+ title: id,
209
+ dataSource
210
+ }), {
211
+ refreshDeps: [contextInfo, definitions, dataSource]
212
+ });
213
+ if (loading) {
214
+ return /* @__PURE__ */ jsx(LoadingOverlay, {
215
+ visible: loading
216
+ });
217
+ }
218
+ if (data.length === 0) {
219
+ return /* @__PURE__ */ jsx(Table, {});
220
+ }
221
+ return /* @__PURE__ */ jsxs(Group, {
222
+ my: "xl",
223
+ direction: "column",
224
+ grow: true,
225
+ sx: {
226
+ border: "1px solid #eee"
227
+ },
228
+ children: [/* @__PURE__ */ jsx(Group, {
229
+ position: "left",
230
+ py: "md",
231
+ pl: "md",
232
+ sx: {
233
+ borderBottom: "1px solid #eee",
234
+ background: "#efefef"
235
+ },
236
+ children: /* @__PURE__ */ jsx(Text, {
237
+ weight: 500,
238
+ children: "Preview Data"
239
+ })
240
+ }), /* @__PURE__ */ jsxs(Table, {
241
+ children: [/* @__PURE__ */ jsx("thead", {
242
+ children: /* @__PURE__ */ jsx("tr", {
243
+ children: Object.keys(data == null ? void 0 : data[0]).map((label) => /* @__PURE__ */ jsx("th", {
244
+ children: label
245
+ }, label))
246
+ })
247
+ }), /* @__PURE__ */ jsx("tbody", {
248
+ children: data.map((row, index2) => /* @__PURE__ */ jsx("tr", {
249
+ children: Object.values(row).map((v, i) => /* @__PURE__ */ jsx("td", {
250
+ children: /* @__PURE__ */ jsx(Group, {
251
+ sx: {
252
+ "&, .mantine-Text-root": {
253
+ fontFamily: "monospace"
254
+ }
255
+ },
256
+ children: /* @__PURE__ */ jsx(Text, {
257
+ children: v
258
+ })
259
+ })
260
+ }, `${v}--${i}`))
261
+ }, `row-${index2}`))
262
+ })]
263
+ })]
264
+ });
265
+ }
266
+ function PickDataSource({}) {
267
+ const {
268
+ dataSources
269
+ } = React.useContext(DefinitionContext);
270
+ const {
271
+ dataSourceID,
272
+ setDataSourceID,
273
+ data,
274
+ loading
275
+ } = React.useContext(PanelContext);
276
+ const options = React.useMemo(() => {
277
+ return dataSources.map((d) => ({
278
+ value: d.id,
279
+ label: d.id
280
+ }));
281
+ }, [dataSources]);
282
+ return /* @__PURE__ */ jsxs(Group, {
283
+ direction: "column",
284
+ grow: true,
285
+ noWrap: true,
286
+ children: [/* @__PURE__ */ jsxs(Group, {
287
+ position: "left",
288
+ sx: {
289
+ maxWidth: "600px",
290
+ alignItems: "baseline"
291
+ },
292
+ children: [/* @__PURE__ */ jsx(Text, {
293
+ children: "Select a Data Source"
294
+ }), /* @__PURE__ */ jsx(Select, {
295
+ data: options,
296
+ value: dataSourceID,
297
+ onChange: setDataSourceID,
298
+ allowDeselect: false,
299
+ clearable: false,
300
+ sx: {
301
+ flexGrow: 1
302
+ }
303
+ })]
304
+ }), /* @__PURE__ */ jsx(DataPreview, {
305
+ id: dataSourceID
306
+ })]
307
+ });
308
+ }
309
+ function EditDescription() {
310
+ const {
311
+ description,
312
+ setDescription
313
+ } = React.useContext(PanelContext);
314
+ const [localDesc, setLocalDesc] = useInputState(description);
315
+ const changed = description !== localDesc;
316
+ const submit = React.useCallback(() => {
317
+ if (!changed) {
318
+ return;
319
+ }
320
+ setDescription(localDesc);
321
+ }, [changed, localDesc]);
322
+ return /* @__PURE__ */ jsx(Textarea, {
323
+ label: "Panel Description",
324
+ value: localDesc,
325
+ onChange: setLocalDesc,
326
+ minRows: 2,
327
+ rightSection: /* @__PURE__ */ jsx(ActionIcon, {
328
+ disabled: !changed,
329
+ onClick: submit,
330
+ sx: {
331
+ alignSelf: "flex-start",
332
+ marginTop: "4px"
333
+ },
334
+ children: /* @__PURE__ */ jsx(DeviceFloppy, {
335
+ size: 20
336
+ })
337
+ })
338
+ });
339
+ }
177
340
  class ErrorBoundary extends React.Component {
178
341
  constructor(props) {
179
342
  super(props);
@@ -245,8 +408,8 @@ function Sunbrust({
245
408
  }
246
409
  }
247
410
  }
248
- }), []);
249
- const option = _.merge(defaultOption$1, labelOption, restConf, {
411
+ }), [max]);
412
+ const option = _.merge({}, defaultOption$1, labelOption, restConf, {
250
413
  series: {
251
414
  data: chartData
252
415
  }
@@ -561,7 +724,7 @@ function VizBar3D({
561
724
  }
562
725
  });
563
726
  }
564
- var index$3 = "";
727
+ var index$2 = "";
565
728
  function renderViz(width, height, data, viz) {
566
729
  const props = {
567
730
  width,
@@ -614,328 +777,90 @@ function Viz({
614
777
  }), !empty && renderViz(width, height, data, viz)]
615
778
  });
616
779
  }
617
- function ContextInfo({}) {
618
- const contextInfo = React.useContext(ContextInfoContext);
619
- const sampleSQL = `SELECT *
620
- FROM commit
621
- WHERE author_time BETWEEN '\${timeRange?.[0].toISOString()}' AND '\${timeRange?.[1].toISOString()}'`;
622
- return /* @__PURE__ */ jsxs(Group, {
623
- direction: "column",
624
- children: [/* @__PURE__ */ jsx(Prism, {
625
- language: "sql",
626
- sx: {
627
- width: "100%"
628
- },
629
- noCopy: true,
630
- colorScheme: "dark",
631
- children: `-- You may refer context data *by name*
632
- -- in SQL or VizConfig.
633
-
634
- ${sampleSQL}`
635
- }), /* @__PURE__ */ jsx(Text, {
636
- weight: 700,
637
- children: "Avaiable context entries"
638
- }), /* @__PURE__ */ jsx(Prism, {
639
- language: "json",
640
- sx: {
641
- width: "100%"
642
- },
643
- noCopy: true,
644
- colorScheme: "dark",
645
- children: JSON.stringify(contextInfo, null, 2)
646
- })]
647
- });
648
- }
649
- var index$2 = "";
650
- function SQLQueryEditor({}) {
780
+ function PreviewViz({}) {
651
781
  const {
652
- sql,
653
- setSQL
782
+ data,
783
+ loading,
784
+ viz
654
785
  } = React.useContext(PanelContext);
655
- const [showPreview, setShowPreview] = React.useState(true);
656
- const handleChange = (e) => {
657
- setSQL(e.target.value);
658
- };
659
- const togglePreview = React.useCallback(() => {
660
- setShowPreview((v) => !v);
661
- }, []);
662
- const format = React.useCallback(() => {
663
- setSQL((sql2) => sql2.trim());
664
- }, [setSQL]);
665
- return /* @__PURE__ */ jsxs(Box, {
666
- className: "sql-query-editor-root",
667
- sx: {
668
- height: "100%"
669
- },
670
- children: [/* @__PURE__ */ jsx(Textarea, {
671
- value: sql,
672
- onChange: handleChange,
673
- minRows: 20,
674
- maxRows: 20
675
- }), /* @__PURE__ */ jsxs(Group, {
676
- m: "md",
677
- position: "right",
678
- children: [/* @__PURE__ */ jsx(Button, {
679
- color: "blue",
680
- onClick: format,
681
- children: "Format"
682
- }), /* @__PURE__ */ jsx(Button, {
683
- variant: "default",
684
- onClick: togglePreview,
685
- children: "Toggle Preview"
686
- })]
687
- }), showPreview && /* @__PURE__ */ jsx(Prism, {
688
- language: "sql",
689
- withLineNumbers: true,
690
- noCopy: true,
691
- colorScheme: "dark",
692
- children: sql
693
- })]
786
+ return /* @__PURE__ */ jsx(ErrorBoundary, {
787
+ children: /* @__PURE__ */ jsx(Viz, {
788
+ viz,
789
+ data,
790
+ loading
791
+ })
694
792
  });
695
793
  }
696
- const QueryEditor = SQLQueryEditor;
697
- function QueryResult({}) {
794
+ function EditTitle() {
698
795
  const {
699
- data
796
+ title,
797
+ setTitle
700
798
  } = React.useContext(PanelContext);
701
- return /* @__PURE__ */ jsxs("div", {
702
- className: "query-result-root",
703
- children: [/* @__PURE__ */ jsxs(Group, {
704
- mb: "xs",
705
- children: [/* @__PURE__ */ jsx(Text, {
706
- weight: "bold",
707
- children: "Data Length: "
708
- }), data.length]
709
- }), /* @__PURE__ */ jsx(Prism, {
710
- language: "json",
711
- colorScheme: "dark",
712
- children: JSON.stringify(data, null, 2)
713
- })]
799
+ const [localTitle, setLocalTitle] = useInputState(title);
800
+ const changed = title !== localTitle;
801
+ const submit = React.useCallback(() => {
802
+ if (!changed) {
803
+ return;
804
+ }
805
+ setTitle(localTitle);
806
+ }, [changed, localTitle]);
807
+ return /* @__PURE__ */ jsx(TextInput, {
808
+ label: "Panel Title",
809
+ value: localTitle,
810
+ onChange: setLocalTitle,
811
+ rightSection: /* @__PURE__ */ jsx(ActionIcon, {
812
+ disabled: !changed,
813
+ onClick: submit,
814
+ children: /* @__PURE__ */ jsx(DeviceFloppy, {
815
+ size: 20
816
+ })
817
+ })
714
818
  });
715
819
  }
716
- const initialContext = {
717
- sqlSnippets: [],
718
- setSQLSnippets: () => {
719
- }
720
- };
721
- const DefinitionContext = React.createContext(initialContext);
722
- function SQLSnippetsForm({
723
- sqlSnippets,
724
- setSQLSnippets
820
+ function VizBar3DPanel({
821
+ conf,
822
+ setConf
725
823
  }) {
726
- const initialValues = React.useMemo(() => ({
727
- snippets: formList(sqlSnippets != null ? sqlSnippets : [])
728
- }), [sqlSnippets]);
729
- const form = useForm({
730
- initialValues
731
- });
732
- const addSnippet = () => form.addListItem("snippets", {
733
- key: randomId(),
734
- value: ""
824
+ const defaultValues = _.assign({}, {
825
+ "x_axis_data_key": "x",
826
+ "y_axis_data_key": "y",
827
+ "z_axis_data_key": "z",
828
+ "xAxis3D": {
829
+ "type": "value",
830
+ "name": "X Axis Name"
831
+ },
832
+ "yAxis3D": {
833
+ "type": "value",
834
+ "name": "Y Axis Name"
835
+ },
836
+ "zAxis3D": {
837
+ "type": "value",
838
+ "name": "Z Axis Name"
839
+ }
840
+ }, conf);
841
+ const {
842
+ control,
843
+ handleSubmit,
844
+ formState
845
+ } = useForm({
846
+ defaultValues
735
847
  });
736
- const changed = React.useMemo(() => !_.isEqual(form.values, initialValues), [form.values, initialValues]);
737
- const submit = ({
738
- snippets
739
- }) => {
740
- setSQLSnippets(snippets);
741
- };
742
848
  return /* @__PURE__ */ jsx(Group, {
743
849
  direction: "column",
744
- sx: {
745
- width: "100%",
746
- position: "relative"
747
- },
850
+ mt: "md",
851
+ spacing: "xs",
748
852
  grow: true,
749
853
  children: /* @__PURE__ */ jsxs("form", {
750
- onSubmit: form.onSubmit(submit),
751
- children: [form.values.snippets.map((_item, index2) => /* @__PURE__ */ jsxs(Group, {
752
- direction: "column",
854
+ onSubmit: handleSubmit(setConf),
855
+ children: [/* @__PURE__ */ jsx(Text, {
856
+ children: "X Axis"
857
+ }), /* @__PURE__ */ jsxs(Group, {
858
+ position: "apart",
753
859
  grow: true,
754
- my: 0,
755
860
  p: "md",
756
- pr: 40,
757
- sx: {
758
- border: "1px solid #eee",
759
- position: "relative"
760
- },
761
- children: [/* @__PURE__ */ jsx(TextInput, __spreadValues({
762
- label: "Key",
763
- required: true
764
- }, form.getListInputProps("snippets", index2, "key"))), /* @__PURE__ */ jsx(Textarea, __spreadValues({
765
- minRows: 3,
766
- label: "Value",
767
- required: true
768
- }, form.getListInputProps("snippets", index2, "value"))), /* @__PURE__ */ jsx(ActionIcon, {
769
- color: "red",
770
- variant: "hover",
771
- onClick: () => form.removeListItem("snippets", index2),
772
- sx: {
773
- position: "absolute",
774
- top: 15,
775
- right: 5
776
- },
777
- children: /* @__PURE__ */ jsx(Trash, {
778
- size: 16
779
- })
780
- })]
781
- }, index2)), /* @__PURE__ */ jsxs(Group, {
782
- position: "center",
783
- mt: "xl",
784
- grow: true,
785
861
  sx: {
786
- width: "80%"
787
- },
788
- mx: "auto",
789
- children: [/* @__PURE__ */ jsx(Button, {
790
- variant: "default",
791
- onClick: addSnippet,
792
- children: "Add a snippet"
793
- }), /* @__PURE__ */ jsx(Button, {
794
- color: "blue",
795
- type: "submit",
796
- disabled: !changed,
797
- children: "Submit"
798
- })]
799
- })]
800
- })
801
- });
802
- }
803
- function SQLSnippetsTab({}) {
804
- const {
805
- sqlSnippets,
806
- setSQLSnippets
807
- } = React.useContext(DefinitionContext);
808
- const sampleSQL = `SELECT *
809
- FROM commit
810
- WHERE \${author_time_condition}`;
811
- return /* @__PURE__ */ jsxs(Group, {
812
- direction: "column",
813
- children: [/* @__PURE__ */ jsx(Prism, {
814
- language: "sql",
815
- sx: {
816
- width: "100%"
817
- },
818
- noCopy: true,
819
- trim: false,
820
- colorScheme: "dark",
821
- children: `-- You may refer context data *by name*
822
- -- in SQL or VizConfig.
823
-
824
- ${sampleSQL}
825
-
826
- -- where author_time_condition is:
827
- author_time BETWEEN '\${timeRange?.[0].toISOString()}' AND '\${timeRange?.[1].toISOString()}'
828
- `
829
- }), /* @__PURE__ */ jsx(Text, {
830
- weight: 700,
831
- children: "SQL Snippets"
832
- }), /* @__PURE__ */ jsx(SQLSnippetsForm, {
833
- sqlSnippets,
834
- setSQLSnippets
835
- })]
836
- });
837
- }
838
- function EditDescription() {
839
- const {
840
- description,
841
- setDescription
842
- } = React.useContext(PanelContext);
843
- const [localDesc, setLocalDesc] = useInputState(description);
844
- const changed = description !== localDesc;
845
- const submit = React.useCallback(() => {
846
- if (!changed) {
847
- return;
848
- }
849
- setDescription(localDesc);
850
- }, [changed, localDesc]);
851
- return /* @__PURE__ */ jsx(Textarea, {
852
- label: "Panel Description",
853
- value: localDesc,
854
- onChange: setLocalDesc,
855
- minRows: 2,
856
- rightSection: /* @__PURE__ */ jsx(ActionIcon, {
857
- disabled: !changed,
858
- onClick: submit,
859
- sx: {
860
- alignSelf: "flex-start",
861
- marginTop: "4px"
862
- },
863
- children: /* @__PURE__ */ jsx(DeviceFloppy, {
864
- size: 20
865
- })
866
- })
867
- });
868
- }
869
- function EditTitle() {
870
- const {
871
- title,
872
- setTitle
873
- } = React.useContext(PanelContext);
874
- const [localTitle, setLocalTitle] = useInputState(title);
875
- const changed = title !== localTitle;
876
- const submit = React.useCallback(() => {
877
- if (!changed) {
878
- return;
879
- }
880
- setTitle(localTitle);
881
- }, [changed, localTitle]);
882
- return /* @__PURE__ */ jsx(TextInput, {
883
- label: "Panel Title",
884
- value: localTitle,
885
- onChange: setLocalTitle,
886
- rightSection: /* @__PURE__ */ jsx(ActionIcon, {
887
- disabled: !changed,
888
- onClick: submit,
889
- children: /* @__PURE__ */ jsx(DeviceFloppy, {
890
- size: 20
891
- })
892
- })
893
- });
894
- }
895
- function VizBar3DPanel({
896
- conf,
897
- setConf
898
- }) {
899
- const defaultValues = _.assign({}, {
900
- "x_axis_data_key": "x",
901
- "y_axis_data_key": "y",
902
- "z_axis_data_key": "z",
903
- "xAxis3D": {
904
- "type": "value",
905
- "name": "X Axis Name"
906
- },
907
- "yAxis3D": {
908
- "type": "value",
909
- "name": "Y Axis Name"
910
- },
911
- "zAxis3D": {
912
- "type": "value",
913
- "name": "Z Axis Name"
914
- }
915
- }, conf);
916
- const {
917
- control,
918
- handleSubmit,
919
- formState
920
- } = useForm$1({
921
- defaultValues
922
- });
923
- return /* @__PURE__ */ jsx(Group, {
924
- direction: "column",
925
- mt: "md",
926
- spacing: "xs",
927
- grow: true,
928
- children: /* @__PURE__ */ jsxs("form", {
929
- onSubmit: handleSubmit(setConf),
930
- children: [/* @__PURE__ */ jsx(Text, {
931
- children: "X Axis"
932
- }), /* @__PURE__ */ jsxs(Group, {
933
- position: "apart",
934
- grow: true,
935
- p: "md",
936
- sx: {
937
- position: "relative",
938
- border: "1px solid #eee"
862
+ position: "relative",
863
+ border: "1px solid #eee"
939
864
  },
940
865
  children: [/* @__PURE__ */ jsx(Controller, {
941
866
  name: "x_axis_data_key",
@@ -1120,7 +1045,7 @@ function VizLineBarChartPanel({
1120
1045
  const initialValues = React.useMemo(() => __spreadValues({
1121
1046
  series: formList(series != null ? series : [])
1122
1047
  }, restConf), [series, restConf]);
1123
- const form = useForm({
1048
+ const form = useForm$1({
1124
1049
  initialValues
1125
1050
  });
1126
1051
  const addSeries = () => form.addListItem("series", {
@@ -1232,7 +1157,7 @@ function SunburstPanel({
1232
1157
  },
1233
1158
  setConf
1234
1159
  }) {
1235
- const form = useForm({
1160
+ const form = useForm$1({
1236
1161
  initialValues: {
1237
1162
  label_field,
1238
1163
  value_field
@@ -1319,7 +1244,7 @@ function VizTablePanel(_a) {
1319
1244
  ]), {
1320
1245
  setConf
1321
1246
  } = _b;
1322
- const form = useForm({
1247
+ const form = useForm$1({
1323
1248
  initialValues: __spreadValues({
1324
1249
  id_field: "id",
1325
1250
  use_raw_columns: true,
@@ -1594,7 +1519,7 @@ function VizTextPanel({
1594
1519
  setConf
1595
1520
  }) {
1596
1521
  var _a;
1597
- const form = useForm({
1522
+ const form = useForm$1({
1598
1523
  initialValues: {
1599
1524
  paragraphs: formList((_a = conf.paragraphs) != null ? _a : sampleParagraphs)
1600
1525
  }
@@ -1789,9 +1714,34 @@ function EditVizConf() {
1789
1714
  }
1790
1715
  function VizConfig({}) {
1791
1716
  return /* @__PURE__ */ jsxs(Group, {
1717
+ direction: "row",
1792
1718
  grow: true,
1793
- direction: "column",
1794
- children: [/* @__PURE__ */ jsx(EditTitle, {}), /* @__PURE__ */ jsx(EditDescription, {}), /* @__PURE__ */ jsx(Divider, {}), /* @__PURE__ */ jsx(EditVizConf, {})]
1719
+ noWrap: true,
1720
+ align: "stretch",
1721
+ sx: {
1722
+ height: "100%"
1723
+ },
1724
+ children: [/* @__PURE__ */ jsxs(Group, {
1725
+ grow: true,
1726
+ direction: "column",
1727
+ sx: {
1728
+ width: "40%",
1729
+ flexShrink: 0,
1730
+ flexGrow: 0
1731
+ },
1732
+ children: [/* @__PURE__ */ jsx(EditTitle, {}), /* @__PURE__ */ jsx(EditDescription, {}), /* @__PURE__ */ jsx(Divider, {
1733
+ sx: {
1734
+ flexGrow: 0
1735
+ }
1736
+ }), /* @__PURE__ */ jsx(EditVizConf, {})]
1737
+ }), /* @__PURE__ */ jsx(Box, {
1738
+ sx: {
1739
+ height: "100%",
1740
+ flexGrow: 1,
1741
+ maxWidth: "60%"
1742
+ },
1743
+ children: /* @__PURE__ */ jsx(PreviewViz, {})
1744
+ })]
1795
1745
  });
1796
1746
  }
1797
1747
  function PanelSettingsModal({
@@ -1833,40 +1783,17 @@ function PanelSettingsModal({
1833
1783
  }
1834
1784
  },
1835
1785
  padding: "md",
1836
- navbar: /* @__PURE__ */ jsx(Navbar, {
1837
- width: {
1838
- base: "40%"
1839
- },
1840
- height: "100%",
1841
- p: "xs",
1842
- children: /* @__PURE__ */ jsxs(Tabs, {
1843
- initialTab: 1,
1844
- children: [/* @__PURE__ */ jsx(Tabs.Tab, {
1845
- label: "Context",
1846
- children: /* @__PURE__ */ jsx(ContextInfo, {})
1847
- }), /* @__PURE__ */ jsx(Tabs.Tab, {
1848
- label: "SQL Snippets",
1849
- children: /* @__PURE__ */ jsx(SQLSnippetsTab, {})
1850
- }), /* @__PURE__ */ jsx(Tabs.Tab, {
1851
- label: "SQL",
1852
- children: /* @__PURE__ */ jsx(QueryEditor, {})
1853
- }), /* @__PURE__ */ jsxs(Tabs.Tab, {
1854
- label: "Data",
1855
- children: [/* @__PURE__ */ jsx(LoadingOverlay, {
1856
- visible: loading
1857
- }), /* @__PURE__ */ jsx(QueryResult, {})]
1858
- }), /* @__PURE__ */ jsx(Tabs.Tab, {
1859
- label: "Viz Config",
1860
- children: /* @__PURE__ */ jsx(VizConfig, {})
1861
- })]
1862
- })
1863
- }),
1864
- children: /* @__PURE__ */ jsx(ErrorBoundary, {
1865
- children: /* @__PURE__ */ jsx(Viz, {
1866
- viz,
1867
- data,
1868
- loading
1869
- })
1786
+ children: /* @__PURE__ */ jsxs(Tabs, {
1787
+ initialTab: 2,
1788
+ children: [/* @__PURE__ */ jsxs(Tabs.Tab, {
1789
+ label: "Data Source",
1790
+ children: [/* @__PURE__ */ jsx(LoadingOverlay, {
1791
+ visible: loading
1792
+ }), /* @__PURE__ */ jsx(PickDataSource, {})]
1793
+ }), /* @__PURE__ */ jsx(Tabs.Tab, {
1794
+ label: "Viz Config",
1795
+ children: /* @__PURE__ */ jsx(VizConfig, {})
1796
+ })]
1870
1797
  })
1871
1798
  })
1872
1799
  });
@@ -1948,7 +1875,7 @@ function PanelTitleBar({}) {
1948
1875
  var index$1 = "";
1949
1876
  function Panel({
1950
1877
  viz: initialViz,
1951
- sql: initialSQL,
1878
+ dataSourceID: initialDataSourceID,
1952
1879
  title: initialTitle,
1953
1880
  description: initialDesc,
1954
1881
  update,
@@ -1959,24 +1886,35 @@ function Panel({
1959
1886
  const definitions = React.useContext(DefinitionContext);
1960
1887
  const [title, setTitle] = React.useState(initialTitle);
1961
1888
  const [description, setDescription] = React.useState(initialDesc);
1962
- const [sql, setSQL] = React.useState(initialSQL);
1889
+ const [dataSourceID, setDataSourceID] = React.useState(initialDataSourceID);
1963
1890
  const [viz, setViz] = React.useState(initialViz);
1891
+ const dataSource = React.useMemo(() => {
1892
+ if (!dataSourceID) {
1893
+ return void 0;
1894
+ }
1895
+ return definitions.dataSources.find((d) => d.id === dataSourceID);
1896
+ }, [dataSourceID, definitions.dataSources]);
1964
1897
  React.useEffect(() => {
1965
- update({
1898
+ update == null ? void 0 : update({
1966
1899
  id,
1967
1900
  layout,
1968
1901
  title,
1969
1902
  description,
1970
- sql,
1903
+ dataSourceID,
1971
1904
  viz
1972
1905
  });
1973
- }, [title, description, sql, viz, id, layout]);
1906
+ }, [title, description, dataSource, viz, id, layout, dataSourceID]);
1974
1907
  const {
1975
1908
  data = [],
1976
1909
  loading,
1977
1910
  refresh
1978
- } = useRequest(queryBySQL(sql, contextInfo, definitions, title), {
1979
- refreshDeps: [contextInfo, definitions]
1911
+ } = useRequest(queryBySQL({
1912
+ context: contextInfo,
1913
+ definitions,
1914
+ title,
1915
+ dataSource
1916
+ }), {
1917
+ refreshDeps: [contextInfo, definitions, dataSource]
1980
1918
  });
1981
1919
  const refreshData = refresh;
1982
1920
  return /* @__PURE__ */ jsx(PanelContext.Provider, {
@@ -1987,8 +1925,8 @@ function Panel({
1987
1925
  setTitle,
1988
1926
  description,
1989
1927
  setDescription,
1990
- sql,
1991
- setSQL,
1928
+ dataSourceID,
1929
+ setDataSourceID,
1992
1930
  viz,
1993
1931
  setViz,
1994
1932
  refreshData
@@ -2006,7 +1944,7 @@ function Panel({
2006
1944
  });
2007
1945
  }
2008
1946
  var index = "";
2009
- const ResponsiveReactGridLayout = WidthProvider(Responsive);
1947
+ const ResponsiveReactGridLayout$1 = WidthProvider(Responsive);
2010
1948
  function DashboardLayout({
2011
1949
  panels,
2012
1950
  setPanels,
@@ -2042,9 +1980,9 @@ function DashboardLayout({
2042
1980
  const newPanels = panels.map((p2) => __spreadProps(__spreadValues({}, p2), {
2043
1981
  layout: m2.get(p2.id)
2044
1982
  }));
2045
- setPanels.setState(newPanels);
1983
+ setPanels(newPanels);
2046
1984
  }, [panels, setPanels]);
2047
- return /* @__PURE__ */ jsx(ResponsiveReactGridLayout, {
1985
+ return /* @__PURE__ */ jsx(ResponsiveReactGridLayout$1, {
2048
1986
  onBreakpointChange,
2049
1987
  onLayoutChange,
2050
1988
  className,
@@ -2065,7 +2003,10 @@ function DashboardLayout({
2065
2003
  }, rest), {
2066
2004
  destroy: () => onRemoveItem(id),
2067
2005
  update: (panel) => {
2068
- setPanels.setItem(index2, panel);
2006
+ setPanels((prevs) => {
2007
+ prevs.splice(index2, 1, panel);
2008
+ return prevs;
2009
+ });
2069
2010
  }
2070
2011
  }))
2071
2012
  }, id);
@@ -2102,93 +2043,696 @@ function ModeToggler({
2102
2043
  }]
2103
2044
  });
2104
2045
  }
2105
- function DashboardActions({
2106
- mode,
2107
- setMode,
2108
- hasChanges,
2109
- addPanel,
2110
- saveChanges
2111
- }) {
2112
- const inEditMode = mode === DashboardMode.Edit;
2046
+ const example = `
2047
+ -- You may reference context data or SQL snippets *by name*
2048
+ -- in SQL or VizConfig.
2049
+ SELECT *
2050
+ FROM commit
2051
+ WHERE
2052
+ -- context data
2053
+ author_time BETWEEN '\${timeRange?.[0].toISOString()}' AND '\${timeRange?.[1].toISOString()}'
2054
+ -- SQL snippets
2055
+ AND \${author_email_condition}
2056
+ \${order_by_clause}
2057
+ `;
2058
+ function ContextAndSnippets({}) {
2059
+ const contextInfo = React.useContext(ContextInfoContext);
2060
+ const {
2061
+ sqlSnippets
2062
+ } = React.useContext(DefinitionContext);
2063
+ const snippets = React.useMemo(() => {
2064
+ const snippets2 = sqlSnippets.reduce((prev, curr) => {
2065
+ prev[curr.key] = curr.value;
2066
+ return prev;
2067
+ }, {});
2068
+ return JSON.stringify(snippets2, null, 2);
2069
+ }, [sqlSnippets]);
2070
+ const context = React.useMemo(() => {
2071
+ return JSON.stringify(contextInfo, null, 2);
2072
+ }, [contextInfo]);
2113
2073
  return /* @__PURE__ */ jsxs(Group, {
2114
- position: "apart",
2115
- pt: "sm",
2116
- pb: "xs",
2074
+ direction: "column",
2075
+ grow: true,
2076
+ sx: {
2077
+ border: "1px solid #eee",
2078
+ maxWidth: "48%",
2079
+ overflow: "hidden"
2080
+ },
2117
2081
  children: [/* @__PURE__ */ jsx(Group, {
2118
2082
  position: "left",
2119
- children: /* @__PURE__ */ jsx(ModeToggler, {
2120
- mode,
2121
- setMode
2083
+ pl: "md",
2084
+ py: "md",
2085
+ sx: {
2086
+ borderBottom: "1px solid #eee",
2087
+ background: "#efefef",
2088
+ flexGrow: 0
2089
+ },
2090
+ children: /* @__PURE__ */ jsx(Text, {
2091
+ weight: 500,
2092
+ children: "Context"
2122
2093
  })
2123
- }), inEditMode && /* @__PURE__ */ jsxs(Group, {
2124
- position: "right",
2125
- children: [/* @__PURE__ */ jsx(Button, {
2126
- variant: "default",
2127
- size: "sm",
2128
- onClick: addPanel,
2129
- leftIcon: /* @__PURE__ */ jsx(PlaylistAdd, {
2130
- size: 20
2131
- }),
2132
- children: "Add a Panel"
2133
- }), /* @__PURE__ */ jsx(Button, {
2134
- variant: "default",
2135
- size: "sm",
2136
- onClick: saveChanges,
2137
- disabled: !hasChanges,
2138
- leftIcon: /* @__PURE__ */ jsx(DeviceFloppy, {
2139
- size: 20
2140
- }),
2141
- children: "Save Changes"
2142
- }), /* @__PURE__ */ jsx(Button, {
2143
- color: "red",
2144
- size: "sm",
2145
- disabled: !hasChanges,
2146
- leftIcon: /* @__PURE__ */ jsx(Recycle, {
2147
- size: 20
2148
- }),
2149
- children: "Revert Changes"
2094
+ }), /* @__PURE__ */ jsxs(Group, {
2095
+ direction: "column",
2096
+ px: "md",
2097
+ pb: "md",
2098
+ sx: {
2099
+ width: "100%"
2100
+ },
2101
+ children: [/* @__PURE__ */ jsx(Prism, {
2102
+ language: "sql",
2103
+ sx: {
2104
+ width: "100%"
2105
+ },
2106
+ noCopy: true,
2107
+ colorScheme: "dark",
2108
+ children: example
2109
+ }), /* @__PURE__ */ jsx(Text, {
2110
+ weight: 500,
2111
+ sx: {
2112
+ flexGrow: 0
2113
+ },
2114
+ children: "Avaiable context"
2115
+ }), /* @__PURE__ */ jsx(Prism, {
2116
+ language: "json",
2117
+ sx: {
2118
+ width: "100%"
2119
+ },
2120
+ noCopy: true,
2121
+ colorScheme: "dark",
2122
+ children: context
2123
+ }), /* @__PURE__ */ jsx(Text, {
2124
+ weight: 500,
2125
+ sx: {
2126
+ flexGrow: 0
2127
+ },
2128
+ children: "Avaiable SQL Snippets"
2129
+ }), /* @__PURE__ */ jsx(Prism, {
2130
+ language: "json",
2131
+ sx: {
2132
+ width: "100%"
2133
+ },
2134
+ noCopy: true,
2135
+ colorScheme: "dark",
2136
+ children: snippets
2150
2137
  })]
2151
- }), !inEditMode && /* @__PURE__ */ jsx(Button, {
2152
- variant: "default",
2153
- size: "sm",
2154
- disabled: true,
2155
- leftIcon: /* @__PURE__ */ jsx(Share, {
2156
- size: 20
2157
- }),
2158
- children: "Share"
2159
2138
  })]
2160
2139
  });
2161
2140
  }
2162
- function Dashboard({
2163
- dashboard,
2164
- update,
2165
- className = "dashboard"
2141
+ function DataSourceForm({
2142
+ value,
2143
+ onChange
2166
2144
  }) {
2167
- const [layoutFrozen, freezeLayout] = React.useState(false);
2168
- const [breakpoint, setBreakpoint] = React.useState();
2169
- const [localCols, setLocalCols] = React.useState();
2170
- const [panels, setPanels] = useListState(dashboard.panels);
2171
- const [sqlSnippets, setSQLSnippets] = React.useState(dashboard.definition.sqlSnippets);
2172
- const [mode, setMode] = React.useState(DashboardMode.Edit);
2173
- const hasChanges = React.useMemo(() => {
2174
- const cleanJSON = (v) => JSON.parse(JSON.stringify(v));
2175
- const panelsEqual = _.isEqual(cleanJSON(panels), cleanJSON(dashboard.panels));
2176
- if (!panelsEqual) {
2177
- return true;
2178
- }
2179
- return !_.isEqual(sqlSnippets, dashboard.definition.sqlSnippets);
2180
- }, [dashboard, panels, sqlSnippets]);
2181
- const saveDashboardChanges = async () => {
2182
- const d = _.merge({}, dashboard, {
2183
- panels
2184
- }, {
2185
- definition: {
2186
- sqlSnippets
2187
- }
2188
- });
2189
- await update(d);
2190
- };
2191
- const addPanel = () => {
2145
+ const form = useForm$1({
2146
+ initialValues: value
2147
+ });
2148
+ const submit = React.useCallback((values) => {
2149
+ onChange(values);
2150
+ }, [onChange]);
2151
+ const changed = React.useMemo(() => {
2152
+ return !_.isEqual(value, form.values);
2153
+ }, [value, form.values]);
2154
+ React.useEffect(() => {
2155
+ form.reset();
2156
+ }, [value]);
2157
+ return /* @__PURE__ */ jsx(Group, {
2158
+ direction: "column",
2159
+ grow: true,
2160
+ sx: {
2161
+ border: "1px solid #eee",
2162
+ flexGrow: 1
2163
+ },
2164
+ children: /* @__PURE__ */ jsxs("form", {
2165
+ onSubmit: form.onSubmit(submit),
2166
+ children: [/* @__PURE__ */ jsxs(Group, {
2167
+ position: "left",
2168
+ py: "md",
2169
+ pl: "md",
2170
+ sx: {
2171
+ borderBottom: "1px solid #eee",
2172
+ background: "#efefef"
2173
+ },
2174
+ children: [/* @__PURE__ */ jsx(Text, {
2175
+ weight: 500,
2176
+ children: "Data Source Configuration"
2177
+ }), /* @__PURE__ */ jsx(ActionIcon, {
2178
+ type: "submit",
2179
+ mr: 5,
2180
+ variant: "filled",
2181
+ color: "blue",
2182
+ disabled: !changed,
2183
+ children: /* @__PURE__ */ jsx(DeviceFloppy, {
2184
+ size: 20
2185
+ })
2186
+ })]
2187
+ }), /* @__PURE__ */ jsxs(Group, {
2188
+ direction: "column",
2189
+ grow: true,
2190
+ my: 0,
2191
+ p: "md",
2192
+ pr: 40,
2193
+ children: [/* @__PURE__ */ jsxs(Group, {
2194
+ grow: true,
2195
+ children: [/* @__PURE__ */ jsx(TextInput, __spreadValues({
2196
+ placeholder: "An ID unique in this dashboard",
2197
+ label: "ID",
2198
+ required: true,
2199
+ sx: {
2200
+ flex: 1
2201
+ }
2202
+ }, form.getInputProps("id"))), /* @__PURE__ */ jsx(TextInput, __spreadValues({
2203
+ placeholder: "TODO: use a dedicated UI component for this",
2204
+ label: "Data Source Key",
2205
+ required: true,
2206
+ sx: {
2207
+ flex: 1
2208
+ }
2209
+ }, form.getInputProps("key"))), /* @__PURE__ */ jsx(TextInput, __spreadValues({
2210
+ placeholder: "Type of the data source",
2211
+ label: "Type",
2212
+ disabled: true,
2213
+ sx: {
2214
+ flex: 1
2215
+ }
2216
+ }, form.getInputProps("type")))]
2217
+ }), /* @__PURE__ */ jsx(Textarea, __spreadValues({
2218
+ autosize: true,
2219
+ minRows: 12,
2220
+ maxRows: 24
2221
+ }, form.getInputProps("sql")))]
2222
+ })]
2223
+ })
2224
+ });
2225
+ }
2226
+ function DataSourceEditor({
2227
+ id
2228
+ }) {
2229
+ const {
2230
+ dataSources,
2231
+ setDataSources
2232
+ } = React.useContext(DefinitionContext);
2233
+ const dataSource = React.useMemo(() => {
2234
+ return dataSources.find((d) => d.id === id);
2235
+ }, [dataSources, id]);
2236
+ const update = React.useCallback((value) => {
2237
+ const index2 = dataSources.findIndex((d) => d.id === value.id);
2238
+ if (!index2) {
2239
+ console.error(new Error("Invalid data source id when updating by id"));
2240
+ return;
2241
+ }
2242
+ setDataSources((prevs) => {
2243
+ return prevs.map((p2) => {
2244
+ if (p2.id === value.id) {
2245
+ return value;
2246
+ }
2247
+ return p2;
2248
+ });
2249
+ });
2250
+ }, [setDataSources]);
2251
+ if (!dataSource) {
2252
+ return /* @__PURE__ */ jsx("span", {
2253
+ children: "Invalid Data Source ID"
2254
+ });
2255
+ }
2256
+ return /* @__PURE__ */ jsxs(Group, {
2257
+ direction: "row",
2258
+ position: "apart",
2259
+ grow: true,
2260
+ align: "stretch",
2261
+ noWrap: true,
2262
+ children: [/* @__PURE__ */ jsx(DataSourceForm, {
2263
+ value: dataSource,
2264
+ onChange: update
2265
+ }), /* @__PURE__ */ jsx(ContextAndSnippets, {})]
2266
+ });
2267
+ }
2268
+ function SelectOrAddDataSource({
2269
+ id,
2270
+ setID
2271
+ }) {
2272
+ const {
2273
+ dataSources,
2274
+ setDataSources
2275
+ } = React.useContext(DefinitionContext);
2276
+ React.useEffect(() => {
2277
+ var _a, _b;
2278
+ if (!id) {
2279
+ setID((_b = (_a = dataSources[0]) == null ? void 0 : _a.id) != null ? _b : "");
2280
+ }
2281
+ }, [id, setID, dataSources]);
2282
+ const options = React.useMemo(() => {
2283
+ return dataSources.map((d) => ({
2284
+ value: d.id,
2285
+ label: d.id
2286
+ }));
2287
+ }, [dataSources]);
2288
+ const add = React.useCallback(() => {
2289
+ const newDataSource = {
2290
+ id: randomId(),
2291
+ type: "postgresql",
2292
+ key: "",
2293
+ sql: ""
2294
+ };
2295
+ setDataSources((prevs) => [...prevs, newDataSource]);
2296
+ }, [setDataSources]);
2297
+ return /* @__PURE__ */ jsx(Group, {
2298
+ pb: "xl",
2299
+ children: /* @__PURE__ */ jsxs(Group, {
2300
+ position: "left",
2301
+ sx: {
2302
+ maxWidth: "600px",
2303
+ alignItems: "baseline"
2304
+ },
2305
+ children: [/* @__PURE__ */ jsx(Text, {
2306
+ children: "Select a Data Source"
2307
+ }), /* @__PURE__ */ jsx(Select, {
2308
+ data: options,
2309
+ value: id,
2310
+ onChange: setID,
2311
+ allowDeselect: false,
2312
+ clearable: false,
2313
+ sx: {
2314
+ flexGrow: 1
2315
+ }
2316
+ }), /* @__PURE__ */ jsx(Text, {
2317
+ children: "or"
2318
+ }), /* @__PURE__ */ jsx(Group, {
2319
+ position: "center",
2320
+ mt: "md",
2321
+ children: /* @__PURE__ */ jsx(Button, {
2322
+ onClick: add,
2323
+ children: "Add a Data Source"
2324
+ })
2325
+ })]
2326
+ })
2327
+ });
2328
+ }
2329
+ function EditDataSourcesModal({
2330
+ opened,
2331
+ close
2332
+ }) {
2333
+ const [id, setID] = React.useState("");
2334
+ const {
2335
+ freezeLayout
2336
+ } = React.useContext(LayoutStateContext);
2337
+ React.useEffect(() => {
2338
+ freezeLayout(opened);
2339
+ }, [opened]);
2340
+ return /* @__PURE__ */ jsx(Modal, {
2341
+ size: "96vw",
2342
+ overflow: "inside",
2343
+ opened,
2344
+ onClose: close,
2345
+ title: "Data Sources",
2346
+ trapFocus: true,
2347
+ onDragStart: (e) => {
2348
+ e.stopPropagation();
2349
+ },
2350
+ children: /* @__PURE__ */ jsxs(AppShell, {
2351
+ sx: {
2352
+ height: "90vh",
2353
+ maxHeight: "calc(100vh - 185px)",
2354
+ ".mantine-AppShell-body": {
2355
+ height: "100%"
2356
+ },
2357
+ main: {
2358
+ height: "100%",
2359
+ width: "100%",
2360
+ padding: 0,
2361
+ margin: 0
2362
+ }
2363
+ },
2364
+ padding: "md",
2365
+ header: /* @__PURE__ */ jsx(SelectOrAddDataSource, {
2366
+ id,
2367
+ setID
2368
+ }),
2369
+ children: [/* @__PURE__ */ jsx(DataSourceEditor, {
2370
+ id
2371
+ }), /* @__PURE__ */ jsx(DataPreview, {
2372
+ id
2373
+ })]
2374
+ })
2375
+ });
2376
+ }
2377
+ function ContextInfo({}) {
2378
+ const contextInfo = React.useContext(ContextInfoContext);
2379
+ const sampleSQL = `SELECT *
2380
+ FROM commit
2381
+ WHERE author_time BETWEEN '\${timeRange?.[0].toISOString()}' AND '\${timeRange?.[1].toISOString()}'`;
2382
+ return /* @__PURE__ */ jsxs(Group, {
2383
+ direction: "column",
2384
+ grow: true,
2385
+ sx: {
2386
+ border: "1px solid #eee",
2387
+ maxWidth: "48%",
2388
+ overflow: "hidden"
2389
+ },
2390
+ children: [/* @__PURE__ */ jsx(Group, {
2391
+ position: "left",
2392
+ pl: "md",
2393
+ py: "md",
2394
+ sx: {
2395
+ borderBottom: "1px solid #eee",
2396
+ background: "#efefef",
2397
+ flexGrow: 0
2398
+ },
2399
+ children: /* @__PURE__ */ jsx(Text, {
2400
+ weight: 500,
2401
+ children: "Context"
2402
+ })
2403
+ }), /* @__PURE__ */ jsxs(Group, {
2404
+ direction: "column",
2405
+ px: "md",
2406
+ pb: "md",
2407
+ sx: {
2408
+ width: "100%"
2409
+ },
2410
+ children: [/* @__PURE__ */ jsx(Prism, {
2411
+ language: "sql",
2412
+ sx: {
2413
+ width: "100%"
2414
+ },
2415
+ noCopy: true,
2416
+ colorScheme: "dark",
2417
+ children: `-- You may refer context data *by name*
2418
+ -- in SQL or VizConfig.
2419
+
2420
+ ${sampleSQL}`
2421
+ }), /* @__PURE__ */ jsx(Text, {
2422
+ weight: 500,
2423
+ sx: {
2424
+ flexGrow: 0
2425
+ },
2426
+ children: "Avaiable context entries"
2427
+ }), /* @__PURE__ */ jsx(Prism, {
2428
+ language: "json",
2429
+ sx: {
2430
+ width: "100%"
2431
+ },
2432
+ noCopy: true,
2433
+ colorScheme: "dark",
2434
+ children: JSON.stringify(contextInfo, null, 2)
2435
+ })]
2436
+ })]
2437
+ });
2438
+ }
2439
+ function SQLSnippetsEditor({}) {
2440
+ const {
2441
+ sqlSnippets,
2442
+ setSQLSnippets
2443
+ } = React.useContext(DefinitionContext);
2444
+ const sampleSQL = `SELECT *
2445
+ FROM commit
2446
+ WHERE \${author_time_condition}`;
2447
+ const initialValues = React.useMemo(() => ({
2448
+ snippets: formList(sqlSnippets != null ? sqlSnippets : [])
2449
+ }), [sqlSnippets]);
2450
+ const form = useForm$1({
2451
+ initialValues
2452
+ });
2453
+ const addSnippet = () => form.addListItem("snippets", {
2454
+ key: randomId(),
2455
+ value: ""
2456
+ });
2457
+ const changed = React.useMemo(() => !_.isEqual(form.values, initialValues), [form.values, initialValues]);
2458
+ const submit = ({
2459
+ snippets
2460
+ }) => {
2461
+ setSQLSnippets(snippets);
2462
+ };
2463
+ return /* @__PURE__ */ jsxs(Group, {
2464
+ direction: "column",
2465
+ grow: true,
2466
+ sx: {
2467
+ border: "1px solid #eee"
2468
+ },
2469
+ children: [/* @__PURE__ */ jsxs(Group, {
2470
+ position: "left",
2471
+ pl: "md",
2472
+ py: "md",
2473
+ sx: {
2474
+ borderBottom: "1px solid #eee",
2475
+ background: "#efefef",
2476
+ flexGrow: 0
2477
+ },
2478
+ children: [/* @__PURE__ */ jsx(Text, {
2479
+ weight: 500,
2480
+ children: "SQL Snippets"
2481
+ }), /* @__PURE__ */ jsx(ActionIcon, {
2482
+ type: "submit",
2483
+ mr: 5,
2484
+ variant: "filled",
2485
+ color: "blue",
2486
+ disabled: !changed,
2487
+ children: /* @__PURE__ */ jsx(DeviceFloppy, {
2488
+ size: 20
2489
+ })
2490
+ })]
2491
+ }), /* @__PURE__ */ jsxs(Group, {
2492
+ px: "md",
2493
+ pb: "md",
2494
+ children: [/* @__PURE__ */ jsx(Prism, {
2495
+ language: "sql",
2496
+ sx: {
2497
+ width: "100%"
2498
+ },
2499
+ noCopy: true,
2500
+ trim: false,
2501
+ colorScheme: "dark",
2502
+ children: `-- You may refer context data *by name*
2503
+ -- in SQL or VizConfig.
2504
+
2505
+ ${sampleSQL}
2506
+
2507
+ -- where author_time_condition is:
2508
+ author_time BETWEEN '\${timeRange?.[0].toISOString()}' AND '\${timeRange?.[1].toISOString()}'
2509
+ `
2510
+ }), /* @__PURE__ */ jsx(Group, {
2511
+ direction: "column",
2512
+ sx: {
2513
+ width: "100%",
2514
+ position: "relative"
2515
+ },
2516
+ grow: true,
2517
+ children: /* @__PURE__ */ jsxs("form", {
2518
+ onSubmit: form.onSubmit(submit),
2519
+ children: [form.values.snippets.map((_item, index2) => /* @__PURE__ */ jsxs(Group, {
2520
+ direction: "column",
2521
+ grow: true,
2522
+ my: 0,
2523
+ p: "md",
2524
+ pr: 40,
2525
+ sx: {
2526
+ border: "1px solid #eee",
2527
+ position: "relative"
2528
+ },
2529
+ children: [/* @__PURE__ */ jsx(TextInput, __spreadValues({
2530
+ label: "Key",
2531
+ required: true
2532
+ }, form.getListInputProps("snippets", index2, "key"))), /* @__PURE__ */ jsx(Textarea, __spreadValues({
2533
+ minRows: 3,
2534
+ label: "Value",
2535
+ required: true
2536
+ }, form.getListInputProps("snippets", index2, "value"))), /* @__PURE__ */ jsx(ActionIcon, {
2537
+ color: "red",
2538
+ variant: "hover",
2539
+ onClick: () => form.removeListItem("snippets", index2),
2540
+ sx: {
2541
+ position: "absolute",
2542
+ top: 15,
2543
+ right: 5
2544
+ },
2545
+ children: /* @__PURE__ */ jsx(Trash, {
2546
+ size: 16
2547
+ })
2548
+ })]
2549
+ }, index2)), /* @__PURE__ */ jsx(Group, {
2550
+ position: "center",
2551
+ mt: "xl",
2552
+ grow: true,
2553
+ sx: {
2554
+ width: "40%"
2555
+ },
2556
+ mx: "auto",
2557
+ children: /* @__PURE__ */ jsx(Button, {
2558
+ variant: "default",
2559
+ onClick: addSnippet,
2560
+ children: "Add a snippet"
2561
+ })
2562
+ })]
2563
+ })
2564
+ })]
2565
+ })]
2566
+ });
2567
+ }
2568
+ function EditSQLSnippetsModal({
2569
+ opened,
2570
+ close
2571
+ }) {
2572
+ const {
2573
+ freezeLayout
2574
+ } = React.useContext(LayoutStateContext);
2575
+ React.useEffect(() => {
2576
+ freezeLayout(opened);
2577
+ }, [opened]);
2578
+ return /* @__PURE__ */ jsx(Modal, {
2579
+ size: "96vw",
2580
+ overflow: "inside",
2581
+ opened,
2582
+ onClose: close,
2583
+ title: "SQL Snippets",
2584
+ trapFocus: true,
2585
+ onDragStart: (e) => {
2586
+ e.stopPropagation();
2587
+ },
2588
+ children: /* @__PURE__ */ jsx(AppShell, {
2589
+ sx: {
2590
+ height: "90vh",
2591
+ maxHeight: "calc(100vh - 185px)",
2592
+ ".mantine-AppShell-body": {
2593
+ height: "100%"
2594
+ },
2595
+ main: {
2596
+ height: "100%",
2597
+ width: "100%",
2598
+ padding: 0,
2599
+ margin: 0
2600
+ }
2601
+ },
2602
+ padding: "md",
2603
+ children: /* @__PURE__ */ jsxs(Group, {
2604
+ direction: "row",
2605
+ position: "apart",
2606
+ grow: true,
2607
+ align: "stretch",
2608
+ noWrap: true,
2609
+ children: [/* @__PURE__ */ jsx(SQLSnippetsEditor, {}), /* @__PURE__ */ jsx(ContextInfo, {})]
2610
+ })
2611
+ })
2612
+ });
2613
+ }
2614
+ function DashboardActions({
2615
+ mode,
2616
+ setMode,
2617
+ hasChanges,
2618
+ addPanel,
2619
+ saveChanges
2620
+ }) {
2621
+ const [dataSourcesOpened, setDataSourcesOpened] = React.useState(false);
2622
+ const openDataSources = () => setDataSourcesOpened(true);
2623
+ const closeDataSources = () => setDataSourcesOpened(false);
2624
+ const [sqlSnippetsOpened, setSQLSnippetsOpened] = React.useState(false);
2625
+ const openSQLSnippets = () => setSQLSnippetsOpened(true);
2626
+ const closeSQLSnippets = () => setSQLSnippetsOpened(false);
2627
+ const inEditMode = mode === DashboardMode.Edit;
2628
+ return /* @__PURE__ */ jsxs(Group, {
2629
+ position: "apart",
2630
+ pt: "sm",
2631
+ pb: "xs",
2632
+ children: [/* @__PURE__ */ jsx(Group, {
2633
+ position: "left",
2634
+ children: /* @__PURE__ */ jsx(ModeToggler, {
2635
+ mode,
2636
+ setMode
2637
+ })
2638
+ }), inEditMode && /* @__PURE__ */ jsxs(Fragment, {
2639
+ children: [/* @__PURE__ */ jsxs(Group, {
2640
+ position: "right",
2641
+ children: [/* @__PURE__ */ jsx(Button, {
2642
+ variant: "default",
2643
+ size: "sm",
2644
+ onClick: addPanel,
2645
+ leftIcon: /* @__PURE__ */ jsx(PlaylistAdd, {
2646
+ size: 20
2647
+ }),
2648
+ children: "Add a Panel"
2649
+ }), /* @__PURE__ */ jsx(Button, {
2650
+ variant: "default",
2651
+ size: "sm",
2652
+ onClick: openSQLSnippets,
2653
+ leftIcon: /* @__PURE__ */ jsx(ClipboardText, {
2654
+ size: 20
2655
+ }),
2656
+ children: "SQL Snippets"
2657
+ }), /* @__PURE__ */ jsx(Button, {
2658
+ variant: "default",
2659
+ size: "sm",
2660
+ onClick: openDataSources,
2661
+ leftIcon: /* @__PURE__ */ jsx(Database, {
2662
+ size: 20
2663
+ }),
2664
+ children: "Data Sources"
2665
+ }), /* @__PURE__ */ jsx(Button, {
2666
+ variant: "default",
2667
+ size: "sm",
2668
+ onClick: saveChanges,
2669
+ disabled: !hasChanges,
2670
+ leftIcon: /* @__PURE__ */ jsx(DeviceFloppy, {
2671
+ size: 20
2672
+ }),
2673
+ children: "Save Changes"
2674
+ }), /* @__PURE__ */ jsx(Button, {
2675
+ color: "red",
2676
+ size: "sm",
2677
+ disabled: !hasChanges,
2678
+ leftIcon: /* @__PURE__ */ jsx(Recycle, {
2679
+ size: 20
2680
+ }),
2681
+ children: "Revert Changes"
2682
+ })]
2683
+ }), /* @__PURE__ */ jsx(EditDataSourcesModal, {
2684
+ opened: dataSourcesOpened,
2685
+ close: closeDataSources
2686
+ }), /* @__PURE__ */ jsx(EditSQLSnippetsModal, {
2687
+ opened: sqlSnippetsOpened,
2688
+ close: closeSQLSnippets
2689
+ })]
2690
+ }), !inEditMode && /* @__PURE__ */ jsx(Button, {
2691
+ variant: "default",
2692
+ size: "sm",
2693
+ disabled: true,
2694
+ leftIcon: /* @__PURE__ */ jsx(Share, {
2695
+ size: 20
2696
+ }),
2697
+ children: "Share"
2698
+ })]
2699
+ });
2700
+ }
2701
+ function Dashboard({
2702
+ context,
2703
+ dashboard,
2704
+ update,
2705
+ className = "dashboard"
2706
+ }) {
2707
+ const [layoutFrozen, freezeLayout] = React.useState(false);
2708
+ const [breakpoint, setBreakpoint] = React.useState();
2709
+ const [localCols, setLocalCols] = React.useState();
2710
+ const [panels, setPanels] = React.useState(dashboard.panels);
2711
+ const [sqlSnippets, setSQLSnippets] = React.useState(dashboard.definition.sqlSnippets);
2712
+ const [dataSources, setDataSources] = React.useState(dashboard.definition.dataSources);
2713
+ const [mode, setMode] = React.useState(DashboardMode.Edit);
2714
+ const hasChanges = React.useMemo(() => {
2715
+ const cleanJSON = (v) => JSON.parse(JSON.stringify(v));
2716
+ const panelsEqual = _.isEqual(cleanJSON(panels), cleanJSON(dashboard.panels));
2717
+ if (!panelsEqual) {
2718
+ return true;
2719
+ }
2720
+ if (!_.isEqual(sqlSnippets, dashboard.definition.sqlSnippets)) {
2721
+ return true;
2722
+ }
2723
+ return !_.isEqual(dataSources, dashboard.definition.dataSources);
2724
+ }, [dashboard, panels, sqlSnippets, dataSources]);
2725
+ const saveDashboardChanges = async () => {
2726
+ const d = _.merge({}, dashboard, {
2727
+ panels
2728
+ }, {
2729
+ definition: {
2730
+ sqlSnippets
2731
+ }
2732
+ });
2733
+ await update(d);
2734
+ };
2735
+ const addPanel = () => {
2192
2736
  const id = randomId();
2193
2737
  const newItem = {
2194
2738
  id,
@@ -2200,51 +2744,126 @@ function Dashboard({
2200
2744
  },
2201
2745
  title: `New Panel - ${id}`,
2202
2746
  description: "description goes here",
2203
- sql: "",
2747
+ dataSourceID: "",
2204
2748
  viz: {
2205
2749
  type: "table",
2206
2750
  conf: {}
2207
2751
  }
2208
2752
  };
2209
- setPanels.append(newItem);
2753
+ setPanels((prevs) => [...prevs, newItem]);
2210
2754
  };
2211
2755
  const removePanelByID = (id) => {
2212
2756
  const index2 = panels.findIndex((p2) => p2.id === id);
2213
- setPanels.remove(index2);
2757
+ setPanels((prevs) => {
2758
+ prevs.splice(index2, 1);
2759
+ return prevs;
2760
+ });
2214
2761
  };
2215
2762
  const inEditMode = mode === DashboardMode.Edit;
2216
2763
  const definitions = React.useMemo(() => ({
2217
2764
  sqlSnippets,
2218
- setSQLSnippets
2219
- }), [sqlSnippets, setSQLSnippets]);
2220
- return /* @__PURE__ */ jsx("div", {
2765
+ setSQLSnippets,
2766
+ dataSources,
2767
+ setDataSources
2768
+ }), [sqlSnippets, setSQLSnippets, dataSources, setDataSources]);
2769
+ return /* @__PURE__ */ jsx(ContextInfoContext.Provider, {
2770
+ value: context,
2771
+ children: /* @__PURE__ */ jsx("div", {
2772
+ className,
2773
+ children: /* @__PURE__ */ jsx(DefinitionContext.Provider, {
2774
+ value: definitions,
2775
+ children: /* @__PURE__ */ jsxs(LayoutStateContext.Provider, {
2776
+ value: {
2777
+ layoutFrozen,
2778
+ freezeLayout,
2779
+ mode,
2780
+ inEditMode
2781
+ },
2782
+ children: [/* @__PURE__ */ jsx(DashboardActions, {
2783
+ mode,
2784
+ setMode,
2785
+ hasChanges,
2786
+ addPanel,
2787
+ saveChanges: saveDashboardChanges
2788
+ }), /* @__PURE__ */ jsx(DashboardLayout, {
2789
+ panels,
2790
+ setPanels,
2791
+ isDraggable: inEditMode && !layoutFrozen,
2792
+ isResizable: inEditMode && !layoutFrozen,
2793
+ onRemoveItem: removePanelByID,
2794
+ setLocalCols,
2795
+ setBreakpoint
2796
+ })]
2797
+ })
2798
+ })
2799
+ })
2800
+ });
2801
+ }
2802
+ const ResponsiveReactGridLayout = WidthProvider(Responsive);
2803
+ function ReadOnlyDashboardLayout({
2804
+ panels,
2805
+ className = "layout",
2806
+ cols = {
2807
+ lg: 12,
2808
+ md: 10,
2809
+ sm: 8,
2810
+ xs: 6,
2811
+ xxs: 4
2812
+ },
2813
+ rowHeight = 10
2814
+ }) {
2815
+ return /* @__PURE__ */ jsx(ResponsiveReactGridLayout, {
2221
2816
  className,
2222
- children: /* @__PURE__ */ jsx(DefinitionContext.Provider, {
2223
- value: definitions,
2224
- children: /* @__PURE__ */ jsxs(LayoutStateContext.Provider, {
2225
- value: {
2226
- layoutFrozen,
2227
- freezeLayout,
2228
- mode,
2229
- inEditMode
2230
- },
2231
- children: [/* @__PURE__ */ jsx(DashboardActions, {
2232
- mode,
2233
- setMode,
2234
- hasChanges,
2235
- addPanel,
2236
- saveChanges: saveDashboardChanges
2237
- }), /* @__PURE__ */ jsx(DashboardLayout, {
2238
- panels,
2239
- setPanels,
2240
- isDraggable: inEditMode && !layoutFrozen,
2241
- isResizable: inEditMode && !layoutFrozen,
2242
- onRemoveItem: removePanelByID,
2243
- setLocalCols,
2244
- setBreakpoint
2245
- })]
2817
+ cols,
2818
+ rowHeight,
2819
+ isDraggable: false,
2820
+ isResizable: false,
2821
+ children: panels.map((_a) => {
2822
+ var _b = _a, {
2823
+ id
2824
+ } = _b, rest = __objRest(_b, [
2825
+ "id"
2826
+ ]);
2827
+ return /* @__PURE__ */ jsx("div", {
2828
+ "data-grid": rest.layout,
2829
+ children: /* @__PURE__ */ jsx(Panel, __spreadValues({
2830
+ id
2831
+ }, rest))
2832
+ }, id);
2833
+ })
2834
+ });
2835
+ }
2836
+ function ReadOnlyDashboard({
2837
+ context,
2838
+ dashboard,
2839
+ className = "dashboard"
2840
+ }) {
2841
+ const definition = React.useMemo(() => __spreadProps(__spreadValues({}, dashboard.definition), {
2842
+ setSQLSnippets: () => {
2843
+ },
2844
+ setDataSources: () => {
2845
+ }
2846
+ }), [dashboard]);
2847
+ return /* @__PURE__ */ jsx(ContextInfoContext.Provider, {
2848
+ value: context,
2849
+ children: /* @__PURE__ */ jsx("div", {
2850
+ className,
2851
+ children: /* @__PURE__ */ jsx(DefinitionContext.Provider, {
2852
+ value: definition,
2853
+ children: /* @__PURE__ */ jsx(LayoutStateContext.Provider, {
2854
+ value: {
2855
+ layoutFrozen: true,
2856
+ freezeLayout: () => {
2857
+ },
2858
+ mode: DashboardMode.Use,
2859
+ inEditMode: false
2860
+ },
2861
+ children: /* @__PURE__ */ jsx(ReadOnlyDashboardLayout, {
2862
+ panels: dashboard.panels
2863
+ })
2864
+ })
2246
2865
  })
2247
2866
  })
2248
2867
  });
2249
2868
  }
2250
- export { ContextInfoContext, Dashboard, DashboardLayout, DashboardMode, DefinitionContext, LayoutStateContext, Panel, PanelContext, initialContextInfoContext };
2869
+ export { ContextInfoContext, Dashboard, DashboardLayout, DashboardMode, DefinitionContext, LayoutStateContext, Panel, PanelContext, ReadOnlyDashboard, initialContextInfoContext };