@graphenedata/cli 0.0.3 → 0.0.5
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.
- package/cli.ts +7 -43
- package/dist/cli/cli.js +509 -277
- package/dist/docs/graphene.md +924 -63
- package/dist/ui/component-utilities/echarts.js +3 -1
- package/dist/ui/component-utilities/themeStores.ts +35 -7
- package/dist/ui/components/AreaChart.svelte +2 -1
- package/dist/ui/components/BarChart.svelte +2 -1
- package/dist/ui/components/BigValue.svelte +1 -1
- package/dist/ui/components/Chart.svelte +10 -1
- package/dist/ui/components/ECharts.svelte +2 -0
- package/dist/ui/components/LineChart.svelte +2 -1
- package/dist/ui/components/PieChart.svelte +1 -1
- package/dist/ui/components/QueryLoad.svelte +5 -6
- package/dist/ui/components/TableRow.svelte +1 -1
- package/dist/ui/components/_Table.svelte +2 -0
- package/dist/ui/internal/queryEngine.ts +16 -13
- package/dist/ui/internal/telemetry.ts +5 -3
- package/dist/ui/web.js +26 -11
- package/package.json +2 -1
- package/dist/docs/data_apps/components/charts/annotations.md +0 -673
- package/dist/docs/data_apps/components/charts/area-chart.md +0 -202
- package/dist/docs/data_apps/components/charts/bar-chart.md +0 -317
- package/dist/docs/data_apps/components/charts/box-plot.md +0 -190
- package/dist/docs/data_apps/components/charts/bubble-chart.md +0 -151
- package/dist/docs/data_apps/components/charts/calendar-heatmap.md +0 -112
- package/dist/docs/data_apps/components/charts/custom-echarts.md +0 -308
- package/dist/docs/data_apps/components/charts/echarts-options.md +0 -217
- package/dist/docs/data_apps/components/charts/funnel-chart.md +0 -106
- package/dist/docs/data_apps/components/charts/heatmap.md +0 -180
- package/dist/docs/data_apps/components/charts/histogram.md +0 -107
- package/dist/docs/data_apps/components/charts/line-chart.md +0 -265
- package/dist/docs/data_apps/components/charts/mixed-type-charts.md +0 -240
- package/dist/docs/data_apps/components/charts/sankey-diagram.md +0 -301
- package/dist/docs/data_apps/components/charts/scatter-plot.md +0 -134
- package/dist/docs/data_apps/components/charts/sparkline.md +0 -68
- package/dist/docs/data_apps/components/data/big-value.md +0 -153
- package/dist/docs/data_apps/components/data/delta.md +0 -89
- package/dist/docs/data_apps/components/data/table.md +0 -470
- package/dist/docs/data_apps/components/data/value.md +0 -97
- package/dist/docs/data_apps/components/inputs/button-group.md +0 -154
- package/dist/docs/data_apps/components/inputs/checkbox.md +0 -52
- package/dist/docs/data_apps/components/inputs/date-input.md +0 -131
- package/dist/docs/data_apps/components/inputs/date-range.md +0 -124
- package/dist/docs/data_apps/components/inputs/dimension-grid.md +0 -67
- package/dist/docs/data_apps/components/inputs/dropdown.md +0 -199
- package/dist/docs/data_apps/components/inputs/index.md +0 -3
- package/dist/docs/data_apps/components/inputs/slider.md +0 -126
- package/dist/docs/data_apps/components/inputs/text-input.md +0 -86
- package/dist/docs/data_apps/components/maps/area-map.md +0 -397
- package/dist/docs/data_apps/components/maps/base-map.md +0 -269
- package/dist/docs/data_apps/components/maps/bubble-map.md +0 -361
- package/dist/docs/data_apps/components/maps/point-map.md +0 -326
- package/dist/docs/data_apps/components/maps/us-map.md +0 -167
- package/dist/docs/data_apps/components/ui/accordion.md +0 -116
- package/dist/docs/data_apps/components/ui/alert.md +0 -37
- package/dist/docs/data_apps/components/ui/big-link.md +0 -19
- package/dist/docs/data_apps/components/ui/details.md +0 -58
- package/dist/docs/data_apps/components/ui/download-data.md +0 -41
- package/dist/docs/data_apps/components/ui/embed.md +0 -47
- package/dist/docs/data_apps/components/ui/grid.md +0 -45
- package/dist/docs/data_apps/components/ui/image.md +0 -61
- package/dist/docs/data_apps/components/ui/info.md +0 -47
- package/dist/docs/data_apps/components/ui/last-refreshed.md +0 -28
- package/dist/docs/data_apps/components/ui/link-button.md +0 -20
- package/dist/docs/data_apps/components/ui/link.md +0 -40
- package/dist/docs/data_apps/components/ui/modal.md +0 -57
- package/dist/docs/data_apps/components/ui/note.md +0 -32
- package/dist/docs/data_apps/components/ui/print-format-components.md +0 -85
- package/dist/docs/data_apps/components/ui/tabs.md +0 -122
|
@@ -12,7 +12,7 @@ import * as chartWindowDebug from './chartWindowDebug'
|
|
|
12
12
|
* } EChartsActionOptions
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
const ANIMATION_DURATION =
|
|
15
|
+
const ANIMATION_DURATION = 0
|
|
16
16
|
const pendingChartsKey = Symbol.for('graphene.pendingCharts')
|
|
17
17
|
|
|
18
18
|
/** @returns {Set<number> | null} */
|
|
@@ -139,6 +139,7 @@ const echartsAction = (node, options) => {
|
|
|
139
139
|
// Initial options set:
|
|
140
140
|
chart.setOption({
|
|
141
141
|
...options.config,
|
|
142
|
+
animation: false,
|
|
142
143
|
animationDuration: ANIMATION_DURATION,
|
|
143
144
|
animationDurationUpdate: ANIMATION_DURATION,
|
|
144
145
|
})
|
|
@@ -219,6 +220,7 @@ const echartsAction = (node, options) => {
|
|
|
219
220
|
chart.setOption(
|
|
220
221
|
{
|
|
221
222
|
...options.config,
|
|
223
|
+
animation: false,
|
|
222
224
|
animationDuration: ANIMATION_DURATION,
|
|
223
225
|
animationDurationUpdate: ANIMATION_DURATION,
|
|
224
226
|
},
|
|
@@ -12,7 +12,7 @@ type ThemeStores = {
|
|
|
12
12
|
activeAppearance: Readable<Appearance>
|
|
13
13
|
theme: Readable<Theme>
|
|
14
14
|
resolveColor: <T>(input: T) => Readable<T | string | undefined>
|
|
15
|
-
resolveColorsObject: (input: Record<string, unknown> | undefined) => Readable<Record<string, string | undefined> | undefined>
|
|
15
|
+
resolveColorsObject: (input: Record<string, unknown> | string | undefined) => Readable<Record<string, string | undefined> | undefined>
|
|
16
16
|
resolveColorPalette: (input: unknown) => Readable<string[] | undefined>
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -55,6 +55,14 @@ const isReadable = <T>(value: unknown): value is Readable<T> => {
|
|
|
55
55
|
return Boolean(value && typeof value === 'object' && 'subscribe' in (value as any))
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
const parseJson = (value: string): unknown => {
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(value)
|
|
61
|
+
} catch {
|
|
62
|
+
return undefined
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
58
66
|
const normalizeColor = (value: unknown): string | undefined => {
|
|
59
67
|
if (value == null) return undefined
|
|
60
68
|
if (Array.isArray(value)) return normalizeColor(value[0])
|
|
@@ -64,25 +72,38 @@ const normalizeColor = (value: unknown): string | undefined => {
|
|
|
64
72
|
if (!trimmed) return undefined
|
|
65
73
|
if (trimmed === 'true' || trimmed === 'false') return trimmed
|
|
66
74
|
if (DEFAULT_THEME.colors[trimmed]) return DEFAULT_THEME.colors[trimmed]
|
|
67
|
-
|
|
68
|
-
if (
|
|
75
|
+
let lower = trimmed.toLowerCase()
|
|
76
|
+
if (trimmed.startsWith('#')) return trimmed
|
|
77
|
+
if (lower.startsWith('rgb') || lower.startsWith('hsl')) return trimmed
|
|
69
78
|
return trimmed
|
|
70
79
|
}
|
|
71
80
|
|
|
72
|
-
const resolveColor = <T>(input: T): Readable<T | string | undefined> => {
|
|
81
|
+
export const resolveColor = <T>(input: T): Readable<T | string | undefined> => {
|
|
73
82
|
if (isReadable<T | string | undefined>(input)) return input
|
|
74
83
|
return readable(normalizeColor(input) as T | string | undefined)
|
|
75
84
|
}
|
|
76
85
|
|
|
77
|
-
const resolveColorsObject = (input: Record<string, unknown> | undefined): Readable<Record<string, string | undefined> | undefined> => {
|
|
86
|
+
export const resolveColorsObject = (input: Record<string, unknown> | string | undefined): Readable<Record<string, string | undefined> | undefined> => {
|
|
78
87
|
if (isReadable<Record<string, string | undefined> | undefined>(input)) return input
|
|
79
88
|
if (!input) return readable(undefined)
|
|
80
89
|
|
|
81
|
-
let
|
|
90
|
+
let record: Record<string, unknown> | undefined = input as Record<string, unknown>
|
|
91
|
+
if (typeof input === 'string') {
|
|
92
|
+
let parsed = parseJson(input.trim())
|
|
93
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
94
|
+
record = parsed as Record<string, unknown>
|
|
95
|
+
} else {
|
|
96
|
+
return readable(undefined)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!record) return readable(undefined)
|
|
101
|
+
|
|
102
|
+
let entries = Object.entries(record).map(([key, value]) => [key, normalizeColor(value)] as const)
|
|
82
103
|
return readable(Object.fromEntries(entries))
|
|
83
104
|
}
|
|
84
105
|
|
|
85
|
-
const resolveColorPalette = (input: unknown): Readable<string[] | undefined> => {
|
|
106
|
+
export const resolveColorPalette = (input: unknown): Readable<string[] | undefined> => {
|
|
86
107
|
if (isReadable<string[] | undefined>(input)) return input
|
|
87
108
|
if (input == null) return readable(DEFAULT_PALETTE)
|
|
88
109
|
|
|
@@ -90,6 +111,13 @@ const resolveColorPalette = (input: unknown): Readable<string[] | undefined> =>
|
|
|
90
111
|
let key = input.trim()
|
|
91
112
|
if (!key || key === 'default') return readable(DEFAULT_PALETTE)
|
|
92
113
|
if (DEFAULT_THEME.colorPalettes[key]) return readable(DEFAULT_THEME.colorPalettes[key])
|
|
114
|
+
if (key.startsWith('[')) {
|
|
115
|
+
let parsed = parseJson(key)
|
|
116
|
+
if (Array.isArray(parsed)) {
|
|
117
|
+
let values = parsed.map(item => normalizeColor(item)).filter(Boolean) as string[]
|
|
118
|
+
return readable(values.length ? values : DEFAULT_PALETTE)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
93
121
|
if (key.includes(',')) {
|
|
94
122
|
let values = key.split(',').map(part => normalizeColor(part)).filter(Boolean) as string[]
|
|
95
123
|
return readable(values.length ? values : DEFAULT_PALETTE)
|
|
@@ -83,9 +83,10 @@
|
|
|
83
83
|
export let xLabelWrap = undefined
|
|
84
84
|
</script>
|
|
85
85
|
|
|
86
|
-
<QueryLoad data={data} fields={
|
|
86
|
+
<QueryLoad data={data} fields={{x, y, series}} let:loaded>
|
|
87
87
|
<Chart
|
|
88
88
|
data={loaded}
|
|
89
|
+
chartContext={{data, x, y, series}}
|
|
89
90
|
{x}
|
|
90
91
|
{y}
|
|
91
92
|
{xFmt}
|
|
@@ -114,9 +114,10 @@
|
|
|
114
114
|
export let xLabelWrap = undefined
|
|
115
115
|
</script>
|
|
116
116
|
|
|
117
|
-
<QueryLoad data={data} fields={
|
|
117
|
+
<QueryLoad data={data} fields={{x, y, y2, series}} let:loaded>
|
|
118
118
|
<Chart
|
|
119
119
|
data={loaded}
|
|
120
|
+
chartContext={{data, x, y, y2, series}}
|
|
120
121
|
{x}
|
|
121
122
|
{y}
|
|
122
123
|
{y2}
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
}
|
|
35
35
|
</script>
|
|
36
36
|
|
|
37
|
-
<QueryLoad {data} fields={
|
|
37
|
+
<QueryLoad {data} fields={{value}} let:loaded>
|
|
38
38
|
<div class="big-value">
|
|
39
39
|
{#if title}<div class="big-value__title">{title}</div>{/if}
|
|
40
40
|
{#if subtitle}<div class="big-value__subtitle">{subtitle}</div>{/if}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import checkInputs from '../component-utilities/checkInputs.js'
|
|
24
24
|
import {getThemeStores} from '../component-utilities/themeStores'
|
|
25
25
|
import {toBoolean} from '../component-utilities/convert'
|
|
26
|
+
import {logError} from '../internal/telemetry.ts'
|
|
26
27
|
|
|
27
28
|
const {theme, resolveColor, resolveColorsObject, resolveColorPalette} = getThemeStores()
|
|
28
29
|
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
// ---------------------------------------------------------------------------------------
|
|
32
33
|
// Data and columns:
|
|
33
34
|
export let data = undefined
|
|
35
|
+
export let chartContext = undefined
|
|
34
36
|
export let queryID = undefined
|
|
35
37
|
export let x = undefined
|
|
36
38
|
export let y = undefined
|
|
@@ -1044,6 +1046,12 @@
|
|
|
1044
1046
|
error = e.message
|
|
1045
1047
|
let setTextRed = '\x1b[31m%s\x1b[0m'
|
|
1046
1048
|
console.error(setTextRed, `Error in ${chartType}: ${e.message}`)
|
|
1049
|
+
|
|
1050
|
+
// Make an "id" for the chart so its clear to users/agents exactly which caused an error.
|
|
1051
|
+
let fieldStr = Object.entries(chartContext || {}).filter(([_, val]) => !!val).map(([name, val]) => `${name}="${val}"`)
|
|
1052
|
+
let id = `${title || chartType} (${fieldStr.join(' ')})`
|
|
1053
|
+
logError(e, {id})
|
|
1054
|
+
|
|
1047
1055
|
props.update((d) => {
|
|
1048
1056
|
return {...d, error}
|
|
1049
1057
|
})
|
|
@@ -1059,11 +1067,12 @@
|
|
|
1059
1067
|
{width}
|
|
1060
1068
|
{data}
|
|
1061
1069
|
{queryID}
|
|
1070
|
+
chartTitle={title}
|
|
1062
1071
|
{echartsOptions}
|
|
1063
1072
|
{seriesOptions}
|
|
1064
1073
|
{connectGroup}
|
|
1065
1074
|
{xAxisLabelOverflow}
|
|
1066
|
-
seriesColors={seriesColorsStore}
|
|
1075
|
+
seriesColors={$seriesColorsStore}
|
|
1067
1076
|
/>
|
|
1068
1077
|
{:else}
|
|
1069
1078
|
<ErrorChart {error} title={chartType} />
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
export let seriesColors: any = undefined
|
|
17
17
|
export let connectGroup: string | undefined = undefined
|
|
18
18
|
export let xAxisLabelOverflow: 'truncate' | 'break' | undefined = undefined
|
|
19
|
+
export let chartTitle: string | undefined = undefined
|
|
19
20
|
|
|
20
21
|
const dispatch = createEventDispatcher()
|
|
21
22
|
const isBrowser = typeof window !== 'undefined'
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
{:else}
|
|
34
35
|
<div
|
|
35
36
|
class="echarts-chart"
|
|
37
|
+
data-chart-title={chartTitle ?? undefined}
|
|
36
38
|
data-query-id={queryID}
|
|
37
39
|
style={`height:${toDimension(height, '240px')};width:${toDimension(width, '100%')}`}
|
|
38
40
|
use:echarts={{
|
|
@@ -99,9 +99,10 @@
|
|
|
99
99
|
</script>
|
|
100
100
|
|
|
101
101
|
|
|
102
|
-
<QueryLoad data={data} fields={
|
|
102
|
+
<QueryLoad data={data} fields={{x, y, y2, series}} let:loaded>
|
|
103
103
|
<Chart
|
|
104
104
|
data={loaded}
|
|
105
|
+
chartContext={{data, x, y, y2, series}}
|
|
105
106
|
{x}
|
|
106
107
|
{y}
|
|
107
108
|
{y2}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
<style></style>
|
|
18
18
|
|
|
19
|
-
<QueryLoad data={data} fields={
|
|
19
|
+
<QueryLoad data={data} fields={{category, value}} let:loaded>
|
|
20
20
|
<ECharts data={loaded} {echartsOptions} {seriesOptions} {seriesColors} config={{
|
|
21
21
|
title: {
|
|
22
22
|
text: title,
|
|
@@ -4,22 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
export let data: string | {rows?: any[]}
|
|
6
6
|
export let height = 200
|
|
7
|
-
export let fields: string
|
|
7
|
+
export let fields: Record<string, string> = {}
|
|
8
8
|
|
|
9
9
|
let errors: Error[] | null = null
|
|
10
10
|
let loaded: any[] | null = null
|
|
11
11
|
|
|
12
|
-
let handleResults = (
|
|
13
|
-
errors =
|
|
14
|
-
loaded =
|
|
12
|
+
let handleResults = (result) => {
|
|
13
|
+
errors = result.errors || null
|
|
14
|
+
loaded = result.rows
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
onMount(() => {
|
|
18
18
|
if (typeof data !== 'string') {
|
|
19
19
|
loaded = data.rows
|
|
20
20
|
} else {
|
|
21
|
-
let usedFields = fields.filter(
|
|
22
|
-
if (usedFields.length == 0) usedFields = ['*']
|
|
21
|
+
let usedFields = Object.fromEntries(Object.entries(fields).filter(e => !!e[1]))
|
|
23
22
|
window.$GRAPHENE.query(data, usedFields, handleResults)
|
|
24
23
|
}
|
|
25
24
|
})
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import {getFinalColumnOrder} from '../component-utilities/tableUtils'
|
|
16
16
|
import {getThemeStores} from '../component-utilities/themeStores'
|
|
17
17
|
import {toBoolean} from '../component-utilities/convert'
|
|
18
|
+
import {logError} from '../internal/telemetry.js'
|
|
18
19
|
|
|
19
20
|
const {resolveColor} = getThemeStores()
|
|
20
21
|
|
|
@@ -141,6 +142,7 @@
|
|
|
141
142
|
}
|
|
142
143
|
} catch (thrown) {
|
|
143
144
|
let message = thrown instanceof Error ? thrown.message : 'Unable to prepare dataset'
|
|
145
|
+
logError(thrown, {id: 'DataTable'})
|
|
144
146
|
error = message
|
|
145
147
|
if (strictBuild) throw thrown
|
|
146
148
|
}
|
|
@@ -4,12 +4,6 @@
|
|
|
4
4
|
import {cacheRead, cacheWrite, getHashes} from './clientCache'
|
|
5
5
|
import {errorProvider} from './telemetry.ts'
|
|
6
6
|
|
|
7
|
-
interface QueryError {
|
|
8
|
-
file: string
|
|
9
|
-
message: string
|
|
10
|
-
from: {lineText: string}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
7
|
interface QueryResult {
|
|
14
8
|
rows?: any[]
|
|
15
9
|
errors?: Error[]
|
|
@@ -25,10 +19,11 @@ type ResultHandler = (res: QueryResult) => void
|
|
|
25
19
|
|
|
26
20
|
interface QueryNode {
|
|
27
21
|
name?: string
|
|
22
|
+
source?: string
|
|
28
23
|
contents: string
|
|
29
24
|
callback?: ResultHandler
|
|
30
25
|
loading: boolean
|
|
31
|
-
fields: string
|
|
26
|
+
fields: Map<string, string>
|
|
32
27
|
errors: Error[]
|
|
33
28
|
}
|
|
34
29
|
|
|
@@ -38,7 +33,7 @@ let queries = [] as QueryNode[]
|
|
|
38
33
|
|
|
39
34
|
function registerQuery (name: string, contents: string) {
|
|
40
35
|
queries = queries.filter(q => q.name !== name)
|
|
41
|
-
queries.push({name, contents, loading: false, fields:
|
|
36
|
+
queries.push({name, contents, loading: false, fields: new Map(), errors: []})
|
|
42
37
|
}
|
|
43
38
|
|
|
44
39
|
function updateParam (name: string, value: any) {
|
|
@@ -46,9 +41,12 @@ function updateParam (name: string, value: any) {
|
|
|
46
41
|
runAll() // for now, do the easy thing and reload it all
|
|
47
42
|
}
|
|
48
43
|
|
|
49
|
-
function query (source: string, fields: string
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
function query (source: string, fields: Record<string, string>, callback: ResultHandler) {
|
|
45
|
+
// using Map here because it preserves the order in which we add fields to the select, which we use when we get the result.
|
|
46
|
+
let map = new Map(Object.entries(fields))
|
|
47
|
+
let exprs = map.size > 0 ? Array.from(map.values()) : ['*']
|
|
48
|
+
let contents = `from ${source} select ${exprs.join(', ')}`
|
|
49
|
+
queries.push({contents, callback, loading: false, fields: map, errors: [], source})
|
|
52
50
|
runAll()
|
|
53
51
|
}
|
|
54
52
|
|
|
@@ -87,7 +85,11 @@ async function runNode (n: QueryNode) {
|
|
|
87
85
|
} else { // request failed. Record it
|
|
88
86
|
let isJson = response.headers.get('Content-Type') === 'application/json'
|
|
89
87
|
let body = isJson ? await response.json() : await response.text()
|
|
90
|
-
n.errors = Array.isArray(body) ? body : [body]
|
|
88
|
+
n.errors = Array.isArray(body) ? body : [{message: body}]
|
|
89
|
+
|
|
90
|
+
let fieldIds = Array.from(n.fields.entries()).map(([name, val]) => `${name}="${val}"`)
|
|
91
|
+
let idStr = `Query (data="${n.source}" ` + fieldIds.join(' ') + ')'
|
|
92
|
+
n.errors.forEach(e => e.id = idStr)
|
|
91
93
|
n.callback({errors: n.errors})
|
|
92
94
|
}
|
|
93
95
|
} catch (e) {
|
|
@@ -113,13 +115,14 @@ function translateData (data: any, node: QueryNode) {
|
|
|
113
115
|
let rows = data.rows || []
|
|
114
116
|
rows.dataLoaded = true // evidence components need this to be set
|
|
115
117
|
rows._evidenceColumnTypes = []
|
|
118
|
+
let requestFields = Array.from(node.fields.values())
|
|
116
119
|
|
|
117
120
|
data.fields.forEach((field, index) => {
|
|
118
121
|
let name = field.name
|
|
119
122
|
|
|
120
123
|
// server gives names like `col_1` to unnamed expressions but we translate it back into the original expression like `avg(price)`
|
|
121
124
|
if (field.name.match(/col_\d+/)) {
|
|
122
|
-
name =
|
|
125
|
+
name = requestFields[index]
|
|
123
126
|
rows.forEach(r => {
|
|
124
127
|
r[name] = r[field.name]
|
|
125
128
|
delete r[field.name]
|
|
@@ -12,7 +12,9 @@ window.addEventListener('unhandledrejection', (event) => {
|
|
|
12
12
|
staticErrors.push(event.reason)
|
|
13
13
|
})
|
|
14
14
|
|
|
15
|
-
export function
|
|
15
|
+
export function logError (e: Error | string, ctx?: any) {
|
|
16
|
+
if (typeof e === 'string') e = new Error(e)
|
|
17
|
+
if (ctx) Object.assign(e, ctx)
|
|
16
18
|
staticErrors.push(e)
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -21,6 +23,6 @@ export function errorProvider (key:string, fn: ErrorProvider) {
|
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export function getErrors (): Error[] {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
let provided = Object.values(errorProviders).flatMap(p => p())
|
|
27
|
+
return staticErrors.concat(provided)
|
|
26
28
|
}
|
package/dist/ui/web.js
CHANGED
|
@@ -69,25 +69,41 @@ let socket = null
|
|
|
69
69
|
|
|
70
70
|
connectWebSocket()
|
|
71
71
|
|
|
72
|
-
async function
|
|
72
|
+
async function captureChart (chartTitle) {
|
|
73
|
+
await waitForQueriesToFinish()
|
|
74
|
+
let errors = getErrors()
|
|
75
|
+
let escaped = window.CSS.escape(chartTitle)
|
|
76
|
+
let canvas = document.querySelector(`[data-chart-title="${escaped}"] canvas`)
|
|
77
|
+
|
|
78
|
+
if (!canvas) {
|
|
79
|
+
errors.push({message: `Could not find chart titled "${chartTitle}"`})
|
|
80
|
+
return {stillLoading: isLoading(), screenshot: null, errors}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {stillLoading: isLoading(), screenshot: canvas.toDataURL('image/png'), errors}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function takeScreenshot () {
|
|
87
|
+
await waitForQueriesToFinish()
|
|
73
88
|
if (!window.html2canvas) {
|
|
74
89
|
let html2canvas = await import('html2canvas')
|
|
75
90
|
window.html2canvas = html2canvas.default
|
|
76
91
|
}
|
|
92
|
+
let canvas = await window.html2canvas(document.body, {useCORS: true, allowTaint: true, scale: 1})
|
|
93
|
+
let errors = getErrors().map(e => ({message: e.message, id: e.id}))
|
|
94
|
+
return {stillLoading: isLoading(), screenshot: canvas?.toDataURL('image/png'), errors}
|
|
95
|
+
}
|
|
77
96
|
|
|
78
|
-
|
|
97
|
+
async function waitForQueriesToFinish () {
|
|
79
98
|
let startTime = Date.now()
|
|
80
99
|
while (isLoading() && Date.now() - startTime < 20_000) {
|
|
81
100
|
await new Promise(resolve => setTimeout(resolve, 100))
|
|
82
101
|
}
|
|
83
|
-
|
|
84
|
-
let targetElement = chartName ? document.querySelector(`[name="${chartName}"]`) : document.body
|
|
85
|
-
let canvas = targetElement && await window.html2canvas(targetElement, {useCORS: true, allowTaint: true, scale: 1})
|
|
86
|
-
return {stillLoading: isLoading(), screenshot: canvas?.toDataURL('image/png')}
|
|
87
102
|
}
|
|
88
103
|
|
|
89
104
|
function connectWebSocket () {
|
|
90
|
-
|
|
105
|
+
let wsUrl = `ws://${window.location.host}/_api/ws`
|
|
106
|
+
socket = new WebSocket(wsUrl)
|
|
91
107
|
socket.onclose = () => setTimeout(connectWebSocket, 2000)
|
|
92
108
|
|
|
93
109
|
socket.onopen = () => {
|
|
@@ -95,12 +111,11 @@ function connectWebSocket () {
|
|
|
95
111
|
}
|
|
96
112
|
|
|
97
113
|
socket.onmessage = async (event) => {
|
|
98
|
-
console.log('Got message', event.data)
|
|
99
114
|
let {type, requestId, chart} = JSON.parse(event.data)
|
|
100
115
|
|
|
101
|
-
if (type === '
|
|
102
|
-
let
|
|
103
|
-
socket.send(JSON.stringify({type: '
|
|
116
|
+
if (type === 'check') {
|
|
117
|
+
let result = chart ? await captureChart(chart) : await takeScreenshot()
|
|
118
|
+
socket.send(JSON.stringify({type: 'checkResponse', requestId, ...result}))
|
|
104
119
|
}
|
|
105
120
|
}
|
|
106
121
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"main": "cli.ts",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "Graphene Systems Inc",
|
|
6
|
-
"version": "0.0.
|
|
6
|
+
"version": "0.0.5",
|
|
7
7
|
"license": "Elastic-2.0",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=16"
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"mdsvex": "^0.12.6",
|
|
45
45
|
"nanoid": "3.3.8",
|
|
46
46
|
"sanitize-html": "^2.17.0",
|
|
47
|
+
"snowflake-sdk": "^2.3.1",
|
|
47
48
|
"ssf": "^0.11.2",
|
|
48
49
|
"svelte": "4.2.19",
|
|
49
50
|
"unist-util-visit": "4.1.2",
|