-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathanalytics-model.ts
More file actions
118 lines (102 loc) · 3.45 KB
/
Copy pathanalytics-model.ts
File metadata and controls
118 lines (102 loc) · 3.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import type { AnalyticsFormat, AnalyticsVisualization } from "./bundle-contract.ts";
export type AnalyticsScalar = string | number | boolean | null;
export type AnalyticsRow = Readonly<Record<string, AnalyticsScalar>>;
export type AnalyticalColumn = Readonly<{
name: string;
logicalType: string;
nullable: boolean;
}>;
export type QueryResultExtent =
| Readonly<{ kind: "exact"; rows: number }>
| Readonly<{ kind: "lower-bound"; rows: number }>;
export type CanonicalQueryResult = Readonly<{
id: string;
generation: string;
generationId: number | null;
columns: readonly AnalyticalColumn[];
rows: readonly AnalyticsRow[];
datumKeys: readonly string[];
parameters: Readonly<{ rangeDays: number; maxRows: number }>;
extent: QueryResultExtent;
elapsedMs: number;
truncated: boolean;
cached: boolean;
}>;
export type ChartVisualization = Extract<AnalyticsVisualization, { kind: "bar" | "line" }>;
export type AnalyticsChartTheme = Readonly<{
foreground: string;
muted: string;
border: string;
surface: string;
series: string;
}>;
export type ChartComponentFamily = "aria" | "dataset" | "grid" | "series" | "tooltip" | "xAxis" | "yAxis";
export type InteractiveDatumMeta = Readonly<{
datumKey: string;
semanticKey?: string;
dataIndex: number;
label: string;
value: AnalyticsScalar;
row: AnalyticsRow;
predicate: Readonly<{ field: string; operator: "eq"; value: AnalyticsScalar }>;
}>;
export type CompiledFigure<Option = unknown> = Readonly<{
visualization: ChartVisualization;
option: Option;
renderer: "svg";
instanceKey: string;
structuralSignature: string;
componentTopology: Readonly<Record<ChartComponentFamily, readonly string[]>>;
datumIndex: ReadonlyMap<string, InteractiveDatumMeta>;
dataIndex: ReadonlyMap<number, InteractiveDatumMeta>;
semanticIndex: ReadonlyMap<string, readonly string[]>;
plottedRows: readonly AnalyticsRow[];
plottedDatumKeys: readonly string[];
plottedCount: number;
total: QueryResultExtent;
accessibleData: readonly AnalyticsRow[];
exportData: readonly AnalyticsRow[];
format: AnalyticsFormat;
}>;
export type ChartHitTarget =
| Readonly<{ kind: "datum"; datum: InteractiveDatumMeta }>
| Readonly<{ kind: "figure" }>;
export type ChartIntent = Readonly<{
kind: "open-context-menu";
figureId: string;
clientX: number;
clientY: number;
target: ChartHitTarget;
source: "pointer" | "keyboard";
}>;
export type FigureRuntimeController = Readonly<{
exportSvg(): Promise<string | null>;
}>;
export function createResultGeneration(
generationId: number | null,
_queryId: string,
sql: string,
rangeDays: number,
): string {
return `analytics:${generationId ?? "cold"}:${rangeDays}:${stableHash(sql)}`;
}
export function createDatumKeys(generation: string, rows: readonly AnalyticsRow[]): string[] {
const occurrences = new Map<string, number>();
return rows.map((row) => {
const encoded = stableRow(row);
const occurrence = occurrences.get(encoded) ?? 0;
occurrences.set(encoded, occurrence + 1);
return `${generation}:${stableHash(encoded)}:${occurrence}`;
});
}
export function stableRow(row: AnalyticsRow): string {
return JSON.stringify(Object.keys(row).sort().map((key) => [key, row[key]]));
}
export function stableHash(value: string): string {
let hash = 0x811c9dc5;
for (let index = 0; index < value.length; index += 1) {
hash ^= value.charCodeAt(index);
hash = Math.imul(hash, 0x01000193);
}
return (hash >>> 0).toString(36);
}