@graphenedata/cli 0.0.14 → 0.0.16
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/LICENSE.md +3 -3
- package/README.md +138 -0
- package/THIRD_PARTY_NOTICES.md +1 -0
- package/bin.js +2 -2
- package/dist/cli/bigQuery-I3F46SC6.js +75 -0
- package/dist/cli/bigQuery-I3F46SC6.js.map +7 -0
- package/dist/cli/chunk-OVWODUTJ.js +12849 -0
- package/dist/cli/chunk-OVWODUTJ.js.map +7 -0
- package/dist/cli/chunk-QAXEOZ43.js +53 -0
- package/dist/cli/chunk-QAXEOZ43.js.map +7 -0
- package/dist/cli/cli.js +245 -10290
- package/dist/cli/clickhouse-ZN5AN2UL.js +64 -0
- package/dist/cli/clickhouse-ZN5AN2UL.js.map +7 -0
- package/dist/cli/duckdb-IYBIO5KJ.js +87 -0
- package/dist/cli/duckdb-IYBIO5KJ.js.map +7 -0
- package/dist/cli/serve2-TNN5EROW.js +447 -0
- package/dist/cli/serve2-TNN5EROW.js.map +7 -0
- package/dist/cli/snowflake-MOQB5GA4.js +128 -0
- package/dist/cli/snowflake-MOQB5GA4.js.map +7 -0
- package/dist/index.d.ts +63 -0
- package/dist/lang/index.d.ts +63 -0
- package/dist/skills/graphene/SKILL.md +235 -0
- package/dist/skills/graphene/references/big-value.md +20 -0
- package/dist/skills/graphene/references/date-range.md +64 -0
- package/dist/skills/graphene/references/dropdown.md +62 -0
- package/dist/skills/graphene/references/echarts.md +162 -0
- package/dist/skills/graphene/references/gsql.md +393 -0
- package/dist/skills/graphene/references/model-gsql.md +72 -0
- package/dist/skills/graphene/references/table.md +143 -0
- package/dist/skills/graphene/references/text-input.md +29 -0
- package/dist/ui/app.css +263 -299
- package/dist/ui/component-utilities/dataShaping.ts +484 -0
- package/dist/ui/component-utilities/dataSummary.ts +57 -0
- package/dist/ui/component-utilities/enrich.ts +763 -0
- package/dist/ui/component-utilities/format.ts +177 -0
- package/dist/ui/component-utilities/inputUtils.ts +48 -9
- package/dist/ui/component-utilities/theme.ts +200 -0
- package/dist/ui/component-utilities/themeStores.ts +26 -21
- package/dist/ui/component-utilities/types.ts +70 -0
- package/dist/ui/components/AreaChart.svelte +57 -105
- package/dist/ui/components/BarChart.svelte +71 -129
- package/dist/ui/components/BigValue.svelte +24 -40
- package/dist/ui/components/Column.svelte +11 -19
- package/dist/ui/components/DateRange.svelte +71 -34
- package/dist/ui/components/Dropdown.svelte +82 -49
- package/dist/ui/components/DropdownOption.svelte +1 -2
- package/dist/ui/components/ECharts.svelte +179 -60
- package/dist/ui/components/InlineDelta.svelte +51 -32
- package/dist/ui/components/LineChart.svelte +54 -125
- package/dist/ui/components/PieChart.svelte +27 -37
- package/dist/ui/components/QueryLoad.svelte +78 -44
- package/dist/ui/components/Row.svelte +2 -1
- package/dist/ui/components/ScatterPlot.svelte +52 -0
- package/dist/ui/components/Skeleton.svelte +32 -0
- package/dist/ui/components/Table.svelte +3 -2
- package/dist/ui/components/TableGroupRow.svelte +28 -36
- package/dist/ui/components/TableHarness.svelte +32 -0
- package/dist/ui/components/TableHeader.svelte +34 -59
- package/dist/ui/components/TableRow.svelte +15 -39
- package/dist/ui/components/TableSubtotalRow.svelte +26 -21
- package/dist/ui/components/TableTotalRow.svelte +27 -37
- package/dist/ui/components/TextInput.svelte +17 -14
- package/dist/ui/components/Value.svelte +25 -0
- package/dist/ui/components/_Table.svelte +80 -76
- package/dist/ui/internal/ChartGallery.svelte +527 -0
- package/dist/ui/internal/ErrorDisplay.svelte +60 -0
- package/dist/ui/internal/LocalApp.svelte +87 -19
- package/dist/ui/internal/PageNavGroup.svelte +269 -0
- package/dist/ui/internal/Sidebar.svelte +178 -0
- package/dist/ui/internal/SidebarToggle.svelte +47 -0
- package/dist/ui/internal/StyleGallery.svelte +244 -0
- package/dist/ui/internal/clientCache.ts +15 -13
- package/dist/ui/internal/pageInputs.svelte.js +292 -0
- package/dist/ui/internal/queryEngine.ts +124 -132
- package/dist/ui/internal/runSocket.ts +59 -0
- package/dist/ui/internal/sidebar.svelte.js +18 -0
- package/dist/ui/internal/telemetry.ts +52 -17
- package/dist/ui/internal/types.d.ts +7 -0
- package/dist/ui/web.js +55 -13
- package/package.json +40 -41
- package/dist/docs/agent-instructions.md +0 -18
- package/dist/docs/base.md +0 -98
- package/dist/docs/cli.md +0 -22
- package/dist/docs/graphene.md +0 -1462
- package/dist/ui/component-utilities/autoFormatting.js +0 -301
- package/dist/ui/component-utilities/builtInFormats.js +0 -482
- package/dist/ui/component-utilities/chartContext.js +0 -12
- package/dist/ui/component-utilities/chartWindowDebug.js +0 -21
- package/dist/ui/component-utilities/checkInputs.js +0 -95
- package/dist/ui/component-utilities/convert.js +0 -15
- package/dist/ui/component-utilities/dateParsing.js +0 -57
- package/dist/ui/component-utilities/dropdownContext.ts +0 -1
- package/dist/ui/component-utilities/echarts.js +0 -272
- package/dist/ui/component-utilities/echartsThemes.js +0 -453
- package/dist/ui/component-utilities/formatTitle.js +0 -24
- package/dist/ui/component-utilities/formatting.js +0 -250
- package/dist/ui/component-utilities/getColumnExtents.js +0 -79
- package/dist/ui/component-utilities/getColumnSummary.js +0 -67
- package/dist/ui/component-utilities/getCompletedData.js +0 -114
- package/dist/ui/component-utilities/getDistinctCount.js +0 -7
- package/dist/ui/component-utilities/getDistinctValues.js +0 -15
- package/dist/ui/component-utilities/getSeriesConfig.js +0 -237
- package/dist/ui/component-utilities/getSortedData.js +0 -7
- package/dist/ui/component-utilities/getStackPercentages.js +0 -43
- package/dist/ui/component-utilities/getStackedData.js +0 -17
- package/dist/ui/component-utilities/getYAxisIndex.js +0 -15
- package/dist/ui/component-utilities/globalContexts.js +0 -1
- package/dist/ui/component-utilities/helpers/getCompletedData.helpers.js +0 -119
- package/dist/ui/component-utilities/replaceNulls.js +0 -14
- package/dist/ui/component-utilities/tableUtils.ts +0 -120
- package/dist/ui/components/Area.svelte +0 -214
- package/dist/ui/components/Bar.svelte +0 -350
- package/dist/ui/components/Chart.svelte +0 -989
- package/dist/ui/components/ErrorChart.svelte +0 -118
- package/dist/ui/components/Line.svelte +0 -227
- package/dist/ui/internal/NavSidebar.svelte +0 -396
- package/dist/ui/internal/PageError.svelte +0 -23
- package/dist/ui/internal/checkSocket.ts +0 -48
- package/dist/ui/internal/theme.ts +0 -88
- package/dist/ui/public/inter-latin-ext.woff2 +0 -0
- package/dist/ui/public/inter-latin.woff2 +0 -0
|
@@ -1,30 +1,40 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {onDestroy, onMount, type Snippet} from 'svelte'
|
|
3
|
-
import
|
|
2
|
+
import {onDestroy, onMount, untrack, type Snippet} from 'svelte'
|
|
3
|
+
import type {GrapheneError} from '../../lang/index.d.ts'
|
|
4
|
+
import ErrorDisplay from '../internal/ErrorDisplay.svelte'
|
|
5
|
+
import type {QueryResult} from '../component-utilities/types.ts'
|
|
6
|
+
import {componentLogger} from '../internal/telemetry.ts'
|
|
7
|
+
import Skeleton from './Skeleton.svelte'
|
|
4
8
|
|
|
5
9
|
interface Props {
|
|
6
|
-
data: string |
|
|
10
|
+
data: string | QueryResult
|
|
7
11
|
height?: number
|
|
8
12
|
fields?: Record<string, string | string[]>
|
|
9
|
-
|
|
13
|
+
inline?: boolean
|
|
14
|
+
componentId?: string
|
|
15
|
+
children?: Snippet<[QueryResult]>
|
|
10
16
|
}
|
|
11
17
|
|
|
12
|
-
let {data, height = 200, fields = {}, children}: Props = $props()
|
|
18
|
+
let {data, height = 200, fields = {}, inline = false, componentId = undefined, children}: Props = $props()
|
|
19
|
+
let logger = untrack(() => componentLogger(componentId || 'QueryLoad', componentId ? {} : {data: typeof data == 'string' ? data : undefined, ...fields}))
|
|
13
20
|
|
|
14
|
-
let
|
|
15
|
-
let loaded:
|
|
21
|
+
let error: GrapheneError | null = $state(null)
|
|
22
|
+
let loaded: QueryResult | null = $state(null)
|
|
23
|
+
let tooltipId = `query-error-${Math.random().toString(36).slice(2)}`
|
|
16
24
|
|
|
17
|
-
let handleResults = (result:
|
|
18
|
-
|
|
19
|
-
loaded = result
|
|
25
|
+
let handleResults = (result: QueryResult) => {
|
|
26
|
+
error = result?.error || null
|
|
27
|
+
loaded = {rows: result?.rows ?? [], fields: result?.fields ?? [], error: result?.error, sql: result?.sql}
|
|
28
|
+
if (result?.error) logger.error(result.error, {...result.error, componentId: logger.id})
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
onMount(() => {
|
|
23
32
|
if (typeof data !== 'string') {
|
|
24
|
-
|
|
33
|
+
error = data.error || null
|
|
34
|
+
loaded = {rows: data.rows ?? [], fields: data.fields ?? [], error: data.error, sql: data.sql}
|
|
25
35
|
} else {
|
|
26
36
|
let usedFields = Object.fromEntries(Object.entries(fields).filter(e => !!e[1]))
|
|
27
|
-
window.$GRAPHENE.query(data, usedFields, handleResults)
|
|
37
|
+
window.$GRAPHENE.query(data, usedFields, handleResults, logger.id)
|
|
28
38
|
}
|
|
29
39
|
})
|
|
30
40
|
|
|
@@ -33,45 +43,28 @@
|
|
|
33
43
|
})
|
|
34
44
|
</script>
|
|
35
45
|
|
|
36
|
-
{#if
|
|
37
|
-
|
|
46
|
+
{#if error}
|
|
47
|
+
{#if inline}
|
|
48
|
+
<span class="inline-error">
|
|
49
|
+
<button class="inline-error__icon" type="button" aria-label="Query failed" aria-describedby={tooltipId}>!</button>
|
|
50
|
+
<span class="inline-error__tooltip" id={tooltipId} role="tooltip">
|
|
51
|
+
<ErrorDisplay {error} />
|
|
52
|
+
</span>
|
|
53
|
+
</span>
|
|
54
|
+
{:else}
|
|
55
|
+
<div style="min-height:{height}px;width:100%;display:grid;align-content:center;padding:8px;box-sizing:border-box">
|
|
56
|
+
<ErrorDisplay {error} />
|
|
57
|
+
</div>
|
|
58
|
+
{/if}
|
|
38
59
|
{:else if !loaded}
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
</div>
|
|
42
|
-
{:else if loaded.length == 0}
|
|
60
|
+
<Skeleton />
|
|
61
|
+
{:else if loaded.rows.length == 0}
|
|
43
62
|
<div class="empty-chart" role="note">Dataset is empty - query ran successfully, but no data was returned from the database</div>
|
|
44
63
|
{:else}
|
|
45
64
|
{@render children?.(loaded)}
|
|
46
65
|
{/if}
|
|
47
66
|
|
|
48
67
|
<style>
|
|
49
|
-
.ql-skeleton {
|
|
50
|
-
width: 100%;
|
|
51
|
-
position: relative;
|
|
52
|
-
overflow: hidden;
|
|
53
|
-
background: var(--chart-skeleton-bg, #f3f4f6);
|
|
54
|
-
border-radius: 4px;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.ql-skeleton__pulse {
|
|
58
|
-
position: absolute;
|
|
59
|
-
inset: 0;
|
|
60
|
-
transform: translateX(-100%);
|
|
61
|
-
background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.55) 50%, rgba(255, 255, 255, 0) 100%);
|
|
62
|
-
animation: ql-pulse 1.4s ease-in-out infinite;
|
|
63
|
-
content: '';
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
@keyframes ql-pulse {
|
|
67
|
-
0% {
|
|
68
|
-
transform: translateX(-100%);
|
|
69
|
-
}
|
|
70
|
-
100% {
|
|
71
|
-
transform: translateX(100%);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
68
|
.empty-chart {
|
|
76
69
|
width: 100%;
|
|
77
70
|
padding: 12px;
|
|
@@ -83,4 +76,45 @@
|
|
|
83
76
|
text-align: center;
|
|
84
77
|
background: rgba(243, 244, 246, 0.6);
|
|
85
78
|
}
|
|
79
|
+
|
|
80
|
+
.inline-error {
|
|
81
|
+
position: relative;
|
|
82
|
+
display: inline-flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
vertical-align: middle;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.inline-error__icon {
|
|
88
|
+
width: 1.05em;
|
|
89
|
+
height: 1.05em;
|
|
90
|
+
padding: 0;
|
|
91
|
+
display: inline-flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
justify-content: center;
|
|
94
|
+
border: 1px solid var(--graphene-error-border, #ef4444);
|
|
95
|
+
border-radius: 999px;
|
|
96
|
+
background: var(--graphene-error-background, #fef2f2);
|
|
97
|
+
color: var(--graphene-error-content-strong, #b91c1c);
|
|
98
|
+
cursor: help;
|
|
99
|
+
font: inherit;
|
|
100
|
+
font-size: 0.75em;
|
|
101
|
+
font-weight: 700;
|
|
102
|
+
line-height: 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.inline-error__tooltip {
|
|
106
|
+
display: none;
|
|
107
|
+
position: absolute;
|
|
108
|
+
z-index: 1000;
|
|
109
|
+
top: calc(100% + 8px);
|
|
110
|
+
left: 0;
|
|
111
|
+
width: min(420px, 80vw);
|
|
112
|
+
text-align: left;
|
|
113
|
+
filter: drop-shadow(0 8px 18px rgba(15, 23, 42, 0.18));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.inline-error:hover .inline-error__tooltip,
|
|
117
|
+
.inline-error:focus-within .inline-error__tooltip {
|
|
118
|
+
display: block;
|
|
119
|
+
}
|
|
86
120
|
</style>
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
div {
|
|
5
5
|
display: flex;
|
|
6
6
|
flex-direction: row;
|
|
7
|
-
gap:
|
|
7
|
+
gap: 1.5rem;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/* Ensure styles apply to slotted children (not scoped) */
|
|
11
11
|
div > :global(*) {
|
|
12
12
|
flex: 1 1 0;
|
|
13
|
+
min-width: 0; /* avoids some rendering instability */
|
|
13
14
|
}
|
|
14
15
|
</style>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import ECharts from './ECharts.svelte'
|
|
3
|
+
import {parseCommaList} from '../component-utilities/inputUtils.ts'
|
|
4
|
+
import type {EChartsConfig, QueryResult, SeriesWithGroupingHint} from '../component-utilities/types.ts'
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
data: string | QueryResult
|
|
8
|
+
x: string
|
|
9
|
+
y: string
|
|
10
|
+
splitBy?: string
|
|
11
|
+
title?: string
|
|
12
|
+
height?: string | number
|
|
13
|
+
width?: string | number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
data,
|
|
18
|
+
x,
|
|
19
|
+
y,
|
|
20
|
+
splitBy = undefined,
|
|
21
|
+
title = undefined,
|
|
22
|
+
height = undefined,
|
|
23
|
+
width = undefined,
|
|
24
|
+
}: Props = $props()
|
|
25
|
+
|
|
26
|
+
function buildConfig(): EChartsConfig {
|
|
27
|
+
let yFields = parseCommaList(y)
|
|
28
|
+
if (splitBy && yFields.length > 1) throw new Error('ScatterPlot does not support splitBy with multiple y fields')
|
|
29
|
+
|
|
30
|
+
let series: SeriesWithGroupingHint[]
|
|
31
|
+
|
|
32
|
+
if (splitBy) {
|
|
33
|
+
// "tall" data, one template split into one series per splitBy value by enrich()
|
|
34
|
+
series = [{type: 'scatter' as const, encode: {x, y: yFields[0], splitBy}}]
|
|
35
|
+
} else {
|
|
36
|
+
// "wide" data, one scatter series per field listed in y
|
|
37
|
+
series = yFields.map(field => ({type: 'scatter' as const, name: field, encode: {x, y: field}}))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
title: title ? {text: title} : undefined,
|
|
42
|
+
tooltip: {trigger: 'item'},
|
|
43
|
+
legend: {show: Boolean(splitBy || yFields.length > 1)},
|
|
44
|
+
grid: {left: 56, bottom: 52},
|
|
45
|
+
xAxis: {name: x, nameLocation: 'middle', nameGap: 28},
|
|
46
|
+
yAxis: {name: y, nameLocation: 'middle', nameGap: 40},
|
|
47
|
+
series,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<ECharts data={data} config={buildConfig()} {height} {width} />
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {height}: {height: number} = $props()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<div class='ql-skeleton' style={`height:${height}px`} role="status" aria-live="polite">
|
|
6
|
+
<span class="ql-skeleton__pulse"></span>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<style>
|
|
10
|
+
.ql-skeleton {
|
|
11
|
+
width: 100%;
|
|
12
|
+
height: 100%;
|
|
13
|
+
position: relative;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
background: var(--chart-skeleton-bg, #f3f4f6);
|
|
16
|
+
border-radius: 4px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.ql-skeleton__pulse {
|
|
20
|
+
position: absolute;
|
|
21
|
+
inset: 0;
|
|
22
|
+
transform: translateX(-100%);
|
|
23
|
+
background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.55) 50%, rgba(255, 255, 255, 0) 100%);
|
|
24
|
+
animation: ql-pulse 1.4s ease-in-out infinite;
|
|
25
|
+
content: '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@keyframes ql-pulse {
|
|
29
|
+
0% { transform: translateX(-100%); }
|
|
30
|
+
100% { transform: translateX(100%); }
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type {Snippet} from 'svelte'
|
|
3
|
+
import type {QueryResult} from '../component-utilities/types.ts'
|
|
3
4
|
import QueryLoad from './QueryLoad.svelte'
|
|
4
5
|
import TableInner from './_Table.svelte'
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
|
-
data: string
|
|
8
|
+
data: string | QueryResult
|
|
8
9
|
children?: Snippet
|
|
9
10
|
[key: string]: unknown
|
|
10
11
|
}
|
|
@@ -14,7 +15,7 @@
|
|
|
14
15
|
let spreadProps = $derived(Object.fromEntries(Object.entries(restProps).filter(([, value]) => value !== undefined)))
|
|
15
16
|
</script>
|
|
16
17
|
|
|
17
|
-
{#snippet tableContent(loaded:
|
|
18
|
+
{#snippet tableContent(loaded: QueryResult)}
|
|
18
19
|
{#if children}
|
|
19
20
|
<TableInner {...spreadProps} data={loaded} {children} />
|
|
20
21
|
{:else}
|
|
@@ -2,15 +2,14 @@
|
|
|
2
2
|
import TableCell from './TableCell.svelte'
|
|
3
3
|
import TableGroupToggle from './TableGroupToggle.svelte'
|
|
4
4
|
import InlineDelta from './InlineDelta.svelte'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {toBoolean} from '../component-utilities/
|
|
5
|
+
import {summarizeColumn, type SummaryMetric} from '../component-utilities/dataSummary.ts'
|
|
6
|
+
import {formatFromField} from '../component-utilities/format.ts'
|
|
7
|
+
import {toBoolean} from '../component-utilities/inputUtils'
|
|
8
8
|
|
|
9
9
|
interface Props {
|
|
10
10
|
groupName: string
|
|
11
11
|
currentGroupData?: any[]
|
|
12
12
|
toggled?: boolean
|
|
13
|
-
columnSummary?: any[]
|
|
14
13
|
rowNumbers?: boolean | string
|
|
15
14
|
rowColor?: string
|
|
16
15
|
subtotals?: boolean | string
|
|
@@ -20,7 +19,7 @@
|
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
let {
|
|
23
|
-
groupName, currentGroupData = [], toggled = true,
|
|
22
|
+
groupName, currentGroupData = [], toggled = true,
|
|
24
23
|
rowNumbers: rowNumbersProp = undefined, rowColor = undefined, subtotals: subtotalsProp = true,
|
|
25
24
|
orderedColumns = [], compact: compactProp = undefined, onToggle,
|
|
26
25
|
}: Props = $props()
|
|
@@ -29,6 +28,16 @@
|
|
|
29
28
|
let subtotals = $derived(toBoolean(subtotalsProp) ?? true)
|
|
30
29
|
let compact = $derived(toBoolean(compactProp))
|
|
31
30
|
|
|
31
|
+
const SUPPORTED_METRICS: SummaryMetric[] = ['sum', 'mean', 'median', 'min', 'max', 'count', 'countDistinct']
|
|
32
|
+
|
|
33
|
+
const getAggregateValue = (rows: Record<string, unknown>[], column: any) => {
|
|
34
|
+
let metric = column?.totalAgg as SummaryMetric | undefined
|
|
35
|
+
if (!metric && String(column?.type || '').toLowerCase() === 'number') metric = 'sum'
|
|
36
|
+
if (!metric || !SUPPORTED_METRICS.includes(metric)) return '-'
|
|
37
|
+
let summary = summarizeColumn(rows, column.field ?? {name: column.id, type: column.type}, [metric])
|
|
38
|
+
return summary[metric] ?? null
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
const toggleGroup = () => onToggle?.({groupName})
|
|
33
42
|
|
|
34
43
|
const handleKeydown = (event: KeyboardEvent) => {
|
|
@@ -38,12 +47,6 @@
|
|
|
38
47
|
}
|
|
39
48
|
}
|
|
40
49
|
|
|
41
|
-
const resolveFormat = (column: any, summary: any) => {
|
|
42
|
-
if (column.subtotalFmt) return getFormatObjectFromString(column.subtotalFmt)
|
|
43
|
-
if (column.totalFmt) return getFormatObjectFromString(column.totalFmt)
|
|
44
|
-
if (column.fmt) return getFormatObjectFromString(column.fmt, summary.format?.valueType)
|
|
45
|
-
return summary.format
|
|
46
|
-
}
|
|
47
50
|
</script>
|
|
48
51
|
|
|
49
52
|
<tr
|
|
@@ -63,8 +66,6 @@
|
|
|
63
66
|
{/if}
|
|
64
67
|
|
|
65
68
|
{#each orderedColumns as column, index (index)}
|
|
66
|
-
{@const summary = safeExtractColumn(column, columnSummary)}
|
|
67
|
-
{@const format = resolveFormat(column, summary)}
|
|
68
69
|
{#if index === 0}
|
|
69
70
|
{#if rowNumbers}
|
|
70
71
|
<!-- Covered by the row-number label cell -->
|
|
@@ -77,30 +78,21 @@
|
|
|
77
78
|
</TableCell>
|
|
78
79
|
{/if}
|
|
79
80
|
{:else if subtotals}
|
|
80
|
-
<TableCell class={
|
|
81
|
-
{#if
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
chip={column.chip}
|
|
94
|
-
/>
|
|
95
|
-
{:else}
|
|
96
|
-
{formatValue(
|
|
97
|
-
aggregateColumn(currentGroupData, column.id, column.totalAgg, summary.type, column.weightCol),
|
|
98
|
-
format,
|
|
99
|
-
summary.columnUnitSummary,
|
|
100
|
-
)}
|
|
101
|
-
{/if}
|
|
81
|
+
<TableCell class={column.type} {compact} align={column.align}>
|
|
82
|
+
{#if column.contentType === 'delta'}
|
|
83
|
+
<InlineDelta
|
|
84
|
+
value={getAggregateValue(currentGroupData, column)}
|
|
85
|
+
downIsGood={column.downIsGood}
|
|
86
|
+
field={column.field}
|
|
87
|
+
showValue={column.showValue}
|
|
88
|
+
showSymbol={column.deltaSymbol}
|
|
89
|
+
align={column.align}
|
|
90
|
+
neutralMin={column.neutralMin ?? 0}
|
|
91
|
+
neutralMax={column.neutralMax ?? 0}
|
|
92
|
+
chip={column.chip}
|
|
93
|
+
/>
|
|
102
94
|
{:else}
|
|
103
|
-
{column.
|
|
95
|
+
{formatFromField(column.field, getAggregateValue(currentGroupData, column))}
|
|
104
96
|
{/if}
|
|
105
97
|
</TableCell>
|
|
106
98
|
{:else}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Column from './Column.svelte'
|
|
3
|
+
import Table from './Table.svelte'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
data: any
|
|
7
|
+
tableProps?: Record<string, unknown>
|
|
8
|
+
columns?: Record<string, unknown>[]
|
|
9
|
+
width?: number | string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let {data, tableProps = {}, columns = [], width = 880}: Props = $props()
|
|
13
|
+
let wrapperWidth = $derived(typeof width === 'number' ? `${width}px` : width)
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div class="table-harness" style:width={wrapperWidth}>
|
|
17
|
+
{#if columns.length > 0}
|
|
18
|
+
<Table data={data} {...tableProps}>
|
|
19
|
+
{#each columns as column, index (index)}
|
|
20
|
+
<Column {...column} />
|
|
21
|
+
{/each}
|
|
22
|
+
</Table>
|
|
23
|
+
{:else}
|
|
24
|
+
<Table data={data} {...tableProps} />
|
|
25
|
+
{/if}
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<style>
|
|
29
|
+
.table-harness {
|
|
30
|
+
max-width: 100%;
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import SortIcon from './SortIcon.svelte'
|
|
3
|
-
import {
|
|
4
|
-
import {toBoolean} from '../component-utilities/convert'
|
|
3
|
+
import {toBoolean} from '../component-utilities/inputUtils'
|
|
5
4
|
|
|
6
5
|
interface Props {
|
|
7
6
|
rowNumbers?: boolean | string
|
|
8
7
|
headerColor?: string
|
|
9
8
|
headerFontColor?: string
|
|
10
9
|
orderedColumns?: any[]
|
|
11
|
-
columnSummary?: any[]
|
|
12
10
|
sortable?: boolean | string
|
|
13
11
|
sortClick?: (columnId: string) => () => void
|
|
14
12
|
formatColumnTitles?: boolean | string
|
|
@@ -20,7 +18,7 @@
|
|
|
20
18
|
|
|
21
19
|
let {
|
|
22
20
|
rowNumbers: rowNumbersProp = false, headerColor = undefined, headerFontColor = undefined,
|
|
23
|
-
orderedColumns = [],
|
|
21
|
+
orderedColumns = [], sortable: sortableProp = true, sortClick = () => () => {},
|
|
24
22
|
formatColumnTitles: formatColumnTitlesProp = true, sortObj = {col: null, ascending: null},
|
|
25
23
|
wrapTitles: wrapTitlesProp = false, compact: compactProp = false, link = undefined,
|
|
26
24
|
}: Props = $props()
|
|
@@ -31,12 +29,11 @@
|
|
|
31
29
|
let wrapTitles = $derived(toBoolean(wrapTitlesProp) ?? false)
|
|
32
30
|
let compact = $derived(toBoolean(compactProp) ?? false)
|
|
33
31
|
|
|
34
|
-
const
|
|
35
|
-
if (column.align
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return 'header-title--align-start'
|
|
32
|
+
const getHeaderAlignment = (column: any) => {
|
|
33
|
+
if (column.align) return column.align
|
|
34
|
+
if (['sparkline', 'sparkbar', 'sparkarea', 'bar'].includes(column.contentType)) return 'center'
|
|
35
|
+
if (column.type === 'number') return 'right'
|
|
36
|
+
return 'left'
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
const computeGroupSpans = (columns: any[]) => {
|
|
@@ -55,10 +52,10 @@
|
|
|
55
52
|
return sortObj.ascending ? 'ascending' : 'descending'
|
|
56
53
|
}
|
|
57
54
|
|
|
58
|
-
const resolveHeaderTitle = (column: any
|
|
55
|
+
const resolveHeaderTitle = (column: any) => {
|
|
59
56
|
if (column.title) return column.title
|
|
60
|
-
if (formatColumnTitles) return
|
|
61
|
-
return
|
|
57
|
+
if (formatColumnTitles) return column.defaultTitle ?? column.id
|
|
58
|
+
return column.id
|
|
62
59
|
}
|
|
63
60
|
|
|
64
61
|
let columnsWithGroupSpan = $derived(computeGroupSpans(orderedColumns))
|
|
@@ -71,7 +68,7 @@
|
|
|
71
68
|
{#if rowNumbers}
|
|
72
69
|
<th class={`header-index ${compact ? 'header-index--compact' : ''}`} style:background-color={headerColor}></th>
|
|
73
70
|
{/if}
|
|
74
|
-
{#each columnsWithGroupSpan as column (column.id)}
|
|
71
|
+
{#each columnsWithGroupSpan as column (column.identifier ?? column.id)}
|
|
75
72
|
{#if column.colGroup && column.isNewGroup}
|
|
76
73
|
<th class="header-group" colspan={column.span}>
|
|
77
74
|
<div class="header-group__label">{column.colGroup}</div>
|
|
@@ -95,33 +92,28 @@
|
|
|
95
92
|
style:color={headerFontColor}
|
|
96
93
|
></th>
|
|
97
94
|
{/if}
|
|
98
|
-
{#each orderedColumns as column (column.id)}
|
|
99
|
-
{@const summary = safeExtractColumn(column, columnSummary)}
|
|
95
|
+
{#each orderedColumns as column (column.identifier ?? column.id)}
|
|
100
96
|
<th
|
|
101
97
|
role="columnheader"
|
|
102
|
-
class={`header-cell ${
|
|
98
|
+
class={`header-cell ${column.type ?? ''} ${compact ? 'header-cell--compact' : ''}`}
|
|
103
99
|
style:color={headerFontColor}
|
|
104
100
|
style:background={headerColor}
|
|
105
|
-
style:text-align={
|
|
101
|
+
style:text-align={getHeaderAlignment(column)}
|
|
106
102
|
style:cursor={sortable ? 'pointer' : 'auto'}
|
|
107
103
|
onclick={sortable ? sortClick(column.id) : undefined}
|
|
108
104
|
aria-sort={getAriaSortValue(column.id)}
|
|
109
105
|
>
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
{
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
106
|
+
<span class={`header-title__text ${wrapTitles || column.wrapTitle ? 'header-title__text--wrap' : ''}`}>
|
|
107
|
+
{resolveHeaderTitle(column)}
|
|
108
|
+
{#if column.description}
|
|
109
|
+
<span class="header-title__info" title={column.description}>ⓘ</span>
|
|
110
|
+
{/if}
|
|
111
|
+
</span>
|
|
112
|
+
{#if sortObj.col === column.id}
|
|
117
113
|
<span class="header-sort-indicator">
|
|
118
|
-
{
|
|
119
|
-
<SortIcon ascending={sortObj.ascending ?? undefined} />
|
|
120
|
-
{:else}
|
|
121
|
-
<span class="header-sort-placeholder"><SortIcon ascending /></span>
|
|
122
|
-
{/if}
|
|
114
|
+
<SortIcon ascending={sortObj.ascending ?? undefined} />
|
|
123
115
|
</span>
|
|
124
|
-
|
|
116
|
+
{/if}
|
|
125
117
|
</th>
|
|
126
118
|
{/each}
|
|
127
119
|
{#if link}
|
|
@@ -178,40 +170,24 @@
|
|
|
178
170
|
}
|
|
179
171
|
|
|
180
172
|
.header-cell {
|
|
173
|
+
position: relative;
|
|
181
174
|
padding: 2px 13px 2px 6px;
|
|
182
175
|
vertical-align: bottom;
|
|
183
176
|
}
|
|
184
177
|
|
|
185
|
-
.header-cell
|
|
186
|
-
padding:
|
|
187
|
-
font-size: 12px;
|
|
178
|
+
.header-cell:first-child {
|
|
179
|
+
padding-left: 3px;
|
|
188
180
|
}
|
|
189
181
|
|
|
190
|
-
.header-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
justify-content: space-between;
|
|
194
|
-
gap: 4px;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
.header-title--wrap {
|
|
198
|
-
align-items: stretch;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.header-title--align-end {
|
|
202
|
-
justify-content: flex-end;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.header-title--align-center {
|
|
206
|
-
justify-content: center;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
.header-title--align-start {
|
|
210
|
-
justify-content: flex-start;
|
|
182
|
+
.header-cell--compact {
|
|
183
|
+
padding: 1px 16.5px 1px 1px;
|
|
184
|
+
font-size: 12px;
|
|
211
185
|
}
|
|
212
186
|
|
|
213
187
|
.header-title__text {
|
|
214
188
|
display: inline-block;
|
|
189
|
+
max-width: 100%;
|
|
190
|
+
text-align: inherit;
|
|
215
191
|
letter-spacing: -0.015em;
|
|
216
192
|
}
|
|
217
193
|
|
|
@@ -227,14 +203,13 @@
|
|
|
227
203
|
}
|
|
228
204
|
|
|
229
205
|
.header-sort-indicator {
|
|
206
|
+
position: absolute;
|
|
207
|
+
right: 1px;
|
|
208
|
+
bottom: 4px;
|
|
230
209
|
display: inline-flex;
|
|
231
210
|
align-items: center;
|
|
232
211
|
}
|
|
233
212
|
|
|
234
|
-
.header-sort-placeholder {
|
|
235
|
-
visibility: hidden;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
213
|
.header-link-cell {
|
|
239
214
|
width: 24px;
|
|
240
215
|
}
|