@lunora/studio 0.0.0 → 1.0.0-alpha.1
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 +105 -0
- package/README.md +123 -9
- package/__assets__/package-og.svg +14 -0
- package/dist/index.d.ts +1402 -0
- package/dist/index.js +41 -0
- package/dist/mount.d.ts +21 -0
- package/dist/mount.js +26 -0
- package/dist/packem_shared/ADMIN_FUNCTION_PREFIX-DmBqMZ-z.js +45 -0
- package/dist/packem_shared/ApiDocsPanel-DpRjJhG5.js +842 -0
- package/dist/packem_shared/ApiReferencePanel-DMIUp-kK.js +229 -0
- package/dist/packem_shared/ApiTab-DURGU15e.js +251 -0
- package/dist/packem_shared/AuditPanel-BC59Nhst.js +212 -0
- package/dist/packem_shared/CommandPalette-Dx_CoB9i.js +373 -0
- package/dist/packem_shared/ConfirmButton-WQVUoGFb.js +59 -0
- package/dist/packem_shared/ConnectionBadge-Bxagrip8.js +111 -0
- package/dist/packem_shared/DEFAULT_AUTO_REFRESH_MS-Vxwaxx51.js +50 -0
- package/dist/packem_shared/DEFAULT_INSIGHT_THRESHOLDS-DjF0h-gA.js +89 -0
- package/dist/packem_shared/DataBrowser-Coz6jJE6.js +4542 -0
- package/dist/packem_shared/DataFilters-FNquMaiu.js +249 -0
- package/dist/packem_shared/ErrorBoundary-BzAApI7J.js +66 -0
- package/dist/packem_shared/ExportImportPanel-WO34fJxy.js +193 -0
- package/dist/packem_shared/FileBrowser-Zcr-Qgxo.js +2932 -0
- package/dist/packem_shared/FunctionRunner-j0Rd5m9t.js +343 -0
- package/dist/packem_shared/FunctionStatsPanel-DboBl-XL.js +432 -0
- package/dist/packem_shared/GlobalDataBrowser-9MhPEfgN.js +318 -0
- package/dist/packem_shared/HealthPanel-DOIgbUtx.js +640 -0
- package/dist/packem_shared/HomePanel-bdOCNA-p.js +1273 -0
- package/dist/packem_shared/InsightsPanel-DaZPnSgt.js +423 -0
- package/dist/packem_shared/LogsPanel-CWdqAGpQ.js +839 -0
- package/dist/packem_shared/MailPanel-D_EGtDnS.js +447 -0
- package/dist/packem_shared/MetricsPanel-E4Gv6wTO.js +1625 -0
- package/dist/packem_shared/MigrationsPanel-DQdPY9io.js +246 -0
- package/dist/packem_shared/OpenRpcReferencePanel-j2p3HB0s.js +191 -0
- package/dist/packem_shared/PitrPanel-BbBkQR6t.js +252 -0
- package/dist/packem_shared/STUDIO_ROOT_CLASS-D12gX2dV.js +3 -0
- package/dist/packem_shared/ScheduledJobs-Ok1CYYwI.js +159 -0
- package/dist/packem_shared/SchemaViewer-D8XGnp-X.js +2512 -0
- package/dist/packem_shared/SecurityAdvisorPanel-Cdm2IxLW.js +79 -0
- package/dist/packem_shared/SettingsPanel-D3WF2mBU.js +176 -0
- package/dist/packem_shared/ShardInput-DNCsT1KW.js +107 -0
- package/dist/packem_shared/SqlEditorPanel-BuQ7f2Hs.js +13 -0
- package/dist/packem_shared/Studio-D36od9Oz.js +33 -0
- package/dist/packem_shared/StudioApp-dvywkJ8I.js +383 -0
- package/dist/packem_shared/StudioI18nProvider-Dcajsznk.js +48 -0
- package/dist/packem_shared/TableEditor-DIVDk3vT.js +371 -0
- package/dist/packem_shared/advisor-view-DBlzJi6C.js +159 -0
- package/dist/packem_shared/aggregateMetrics-D4nUHEKU.js +108 -0
- package/dist/packem_shared/app.d-CCmwDEVs.d.ts +300 -0
- package/dist/packem_shared/badge-B2PKA1-5.js +49 -0
- package/dist/packem_shared/bar-chart-CzJAgqkp.js +3245 -0
- package/dist/packem_shared/button-BhsN2uZH.js +49 -0
- package/dist/packem_shared/card-DURq3ElK.js +175 -0
- package/dist/packem_shared/cf-links-BZfRdxSE.js +8 -0
- package/dist/packem_shared/checkbox-UNkzAxl-.js +63 -0
- package/dist/packem_shared/createStudioI18n-CgvlmDkN.js +27 -0
- package/dist/packem_shared/data-grid-CCh2Couo.js +183 -0
- package/dist/packem_shared/dropdown-menu-WY4B_eJO.js +280 -0
- package/dist/packem_shared/empty-state-DY_oe0k6.js +98 -0
- package/dist/packem_shared/grid-features-DTjG6Sex.js +840 -0
- package/dist/packem_shared/input-XH4r1Pt1.js +53 -0
- package/dist/packem_shared/internal-BBZYexre.js +68 -0
- package/dist/packem_shared/label-D8ykjn5J.js +46 -0
- package/dist/packem_shared/live-status-bPff1O7Y.js +44 -0
- package/dist/packem_shared/reference-view-BCKIoai7.js +2180 -0
- package/dist/packem_shared/shard-history-DyebH1R5.js +38 -0
- package/dist/packem_shared/sparkline-10dG-_f0.js +93 -0
- package/dist/packem_shared/sql-editor-panel-CW2y2x9h.js +2562 -0
- package/dist/packem_shared/storage-tier-CL98eOvn.js +85 -0
- package/dist/packem_shared/studio-BDVd7rIV.js +10303 -0
- package/dist/packem_shared/table-_RzNvy3R.js +246 -0
- package/dist/packem_shared/table-list-sidebar-aZHLq70w.js +832 -0
- package/dist/packem_shared/textarea-D3gaCU_-.js +46 -0
- package/dist/packem_shared/use-live-admin-D1h1Fzsd.js +73 -0
- package/dist/packem_shared/use-live-shard-seed-B74RYcOy.js +76 -0
- package/dist/packem_shared/useDebounced-Dxncpg6z.js +32 -0
- package/dist/packem_shared/utils-B05Dmz_H.js +8 -0
- package/dist/packem_shared/virtual-rect-CVMUskSm.js +10 -0
- package/dist/standalone/studio.js +356 -0
- package/dist/styles.css +2 -0
- package/package.json +77 -17
- package/src/theme.css +59 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { c } from 'react/compiler-runtime';
|
|
2
|
+
import { useLunora } from '@lunora/react';
|
|
3
|
+
import { useNavigate, useRouter, useSearch } from '@tanstack/react-router';
|
|
4
|
+
import { useRef, useState, useEffect } from 'react';
|
|
5
|
+
import { T as TIER_META } from './storage-tier-CL98eOvn.js';
|
|
6
|
+
import { useT } from './createStudioI18n-CgvlmDkN.js';
|
|
7
|
+
import { f as fireAndForget } from './internal-BBZYexre.js';
|
|
8
|
+
import { DataBrowser } from './DataBrowser-Coz6jJE6.js';
|
|
9
|
+
import { GlobalDataBrowser } from './GlobalDataBrowser-9MhPEfgN.js';
|
|
10
|
+
import { jsxDEV } from 'react/jsx-dev-runtime';
|
|
11
|
+
|
|
12
|
+
const VALID_OPERATORS = /* @__PURE__ */ new Set(["contains", "eq", "gt", "gte", "lt", "lte", "ne"]);
|
|
13
|
+
const isFilterClause = (entry) => {
|
|
14
|
+
if (entry === null || typeof entry !== "object") {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const candidate = entry;
|
|
18
|
+
return typeof candidate.column === "string" && typeof candidate.operator === "string" && VALID_OPERATORS.has(candidate.operator);
|
|
19
|
+
};
|
|
20
|
+
const parseFilters = (raw) => {
|
|
21
|
+
if (typeof raw !== "string" || raw === "") {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(raw);
|
|
26
|
+
return Array.isArray(parsed) ? parsed.filter(isFilterClause) : [];
|
|
27
|
+
} catch {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const parseOrder = (raw) => {
|
|
32
|
+
if (typeof raw !== "string" || raw === "") {
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
const separator = raw.lastIndexOf(":");
|
|
36
|
+
if (separator <= 0) {
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
const column = raw.slice(0, separator);
|
|
40
|
+
const direction = raw.slice(separator + 1);
|
|
41
|
+
if (direction !== "asc" && direction !== "desc") {
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
column,
|
|
46
|
+
direction
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
const searchToDataView = (search) => {
|
|
50
|
+
const filters = parseFilters(search["filters"]);
|
|
51
|
+
const orderBy = parseOrder(search["order"]);
|
|
52
|
+
return {
|
|
53
|
+
filters: filters.length > 0 ? filters : void 0,
|
|
54
|
+
orderBy,
|
|
55
|
+
search: typeof search["search"] === "string" && search["search"] !== "" ? search["search"] : void 0,
|
|
56
|
+
shard: typeof search["shard"] === "string" && search["shard"] !== "" ? search["shard"] : void 0,
|
|
57
|
+
table: typeof search["table"] === "string" && search["table"] !== "" ? search["table"] : void 0,
|
|
58
|
+
tier: search["schema"] === "global" ? "global" : "shard"
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
const dataViewToSearch = (view) => {
|
|
62
|
+
const filters = view.filters ?? [];
|
|
63
|
+
return {
|
|
64
|
+
filters: filters.length > 0 ? JSON.stringify(filters) : void 0,
|
|
65
|
+
order: view.orderBy === void 0 ? void 0 : `${view.orderBy.column}:${view.orderBy.direction}`,
|
|
66
|
+
schema: view.tier === "global" ? "global" : void 0,
|
|
67
|
+
search: view.search !== void 0 && view.search !== "" ? view.search : void 0,
|
|
68
|
+
shard: view.shard !== void 0 && view.shard !== "" ? view.shard : void 0,
|
|
69
|
+
table: view.table !== void 0 && view.table !== "" ? view.table : void 0
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const STORAGE_KEY = "lunora-studio-saved-queries";
|
|
74
|
+
const MAX_SAVED = 50;
|
|
75
|
+
const store = () => {
|
|
76
|
+
try {
|
|
77
|
+
return globalThis.localStorage;
|
|
78
|
+
} catch {
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const isSavedQuery = (entry) => entry !== null && typeof entry === "object" && typeof entry.name === "string" && typeof entry.view === "object" && entry.view !== null;
|
|
83
|
+
const loadSavedQueries = () => {
|
|
84
|
+
try {
|
|
85
|
+
const raw = store()?.getItem(STORAGE_KEY);
|
|
86
|
+
if (raw === null || raw === void 0) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
const parsed = JSON.parse(raw);
|
|
90
|
+
return Array.isArray(parsed) ? parsed.filter(isSavedQuery) : [];
|
|
91
|
+
} catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const persist = (queries) => {
|
|
96
|
+
const storage = store();
|
|
97
|
+
if (storage !== void 0) {
|
|
98
|
+
try {
|
|
99
|
+
storage.setItem(STORAGE_KEY, JSON.stringify(queries));
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const saveQuery = (name, view) => {
|
|
105
|
+
const trimmed = name.trim();
|
|
106
|
+
if (trimmed === "") {
|
|
107
|
+
return loadSavedQueries();
|
|
108
|
+
}
|
|
109
|
+
const next = [{
|
|
110
|
+
name: trimmed,
|
|
111
|
+
view
|
|
112
|
+
}, ...loadSavedQueries().filter((entry) => entry.name !== trimmed)].slice(0, MAX_SAVED);
|
|
113
|
+
persist(next);
|
|
114
|
+
return next;
|
|
115
|
+
};
|
|
116
|
+
const deleteSavedQuery = (name) => {
|
|
117
|
+
const next = loadSavedQueries().filter((entry) => entry.name !== name);
|
|
118
|
+
persist(next);
|
|
119
|
+
return next;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const SchemaSwitch = (t0) => {
|
|
123
|
+
const $ = c(15);
|
|
124
|
+
const {
|
|
125
|
+
onChange,
|
|
126
|
+
tier
|
|
127
|
+
} = t0;
|
|
128
|
+
const t = useT();
|
|
129
|
+
let t1;
|
|
130
|
+
if ($[0] !== onChange) {
|
|
131
|
+
t1 = (event) => {
|
|
132
|
+
onChange(event.target.value);
|
|
133
|
+
};
|
|
134
|
+
$[0] = onChange;
|
|
135
|
+
$[1] = t1;
|
|
136
|
+
} else {
|
|
137
|
+
t1 = $[1];
|
|
138
|
+
}
|
|
139
|
+
const onSelect = t1;
|
|
140
|
+
let t2;
|
|
141
|
+
if ($[2] !== t) {
|
|
142
|
+
t2 = t("Storage tier");
|
|
143
|
+
$[2] = t;
|
|
144
|
+
$[3] = t2;
|
|
145
|
+
} else {
|
|
146
|
+
t2 = $[3];
|
|
147
|
+
}
|
|
148
|
+
let t3;
|
|
149
|
+
if ($[4] !== t2) {
|
|
150
|
+
t3 = /* @__PURE__ */ jsxDEV("span", {
|
|
151
|
+
className: "font-mono text-[11px] tracking-wide uppercase text-muted-foreground",
|
|
152
|
+
children: t2
|
|
153
|
+
}, void 0, false);
|
|
154
|
+
$[4] = t2;
|
|
155
|
+
$[5] = t3;
|
|
156
|
+
} else {
|
|
157
|
+
t3 = $[5];
|
|
158
|
+
}
|
|
159
|
+
const t4 = TIER_META[tier];
|
|
160
|
+
let t5;
|
|
161
|
+
let t6;
|
|
162
|
+
if ($[6] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
163
|
+
t5 = /* @__PURE__ */ jsxDEV("option", {
|
|
164
|
+
value: "shard",
|
|
165
|
+
children: TIER_META.shard.label
|
|
166
|
+
}, void 0, false);
|
|
167
|
+
t6 = /* @__PURE__ */ jsxDEV("option", {
|
|
168
|
+
value: "global",
|
|
169
|
+
children: TIER_META.global.label
|
|
170
|
+
}, void 0, false);
|
|
171
|
+
$[6] = t5;
|
|
172
|
+
$[7] = t6;
|
|
173
|
+
} else {
|
|
174
|
+
t5 = $[6];
|
|
175
|
+
t6 = $[7];
|
|
176
|
+
}
|
|
177
|
+
let t7;
|
|
178
|
+
if ($[8] !== onSelect || $[9] !== t4.title || $[10] !== tier) {
|
|
179
|
+
t7 = /* @__PURE__ */ jsxDEV("select", {
|
|
180
|
+
className: "h-8 w-full rounded-md border border-border bg-background px-2 text-sm outline-none focus-visible:border-ring",
|
|
181
|
+
"data-testid": "table-editor-schema",
|
|
182
|
+
id: "table-editor-schema",
|
|
183
|
+
onChange: onSelect,
|
|
184
|
+
title: t4.title,
|
|
185
|
+
value: tier,
|
|
186
|
+
children: [t5, t6]
|
|
187
|
+
}, void 0, true);
|
|
188
|
+
$[8] = onSelect;
|
|
189
|
+
$[9] = t4.title;
|
|
190
|
+
$[10] = tier;
|
|
191
|
+
$[11] = t7;
|
|
192
|
+
} else {
|
|
193
|
+
t7 = $[11];
|
|
194
|
+
}
|
|
195
|
+
let t8;
|
|
196
|
+
if ($[12] !== t3 || $[13] !== t7) {
|
|
197
|
+
t8 = /* @__PURE__ */ jsxDEV("label", {
|
|
198
|
+
className: "flex w-full flex-col gap-1",
|
|
199
|
+
htmlFor: "table-editor-schema",
|
|
200
|
+
children: [t3, t7]
|
|
201
|
+
}, void 0, true);
|
|
202
|
+
$[12] = t3;
|
|
203
|
+
$[13] = t7;
|
|
204
|
+
$[14] = t8;
|
|
205
|
+
} else {
|
|
206
|
+
t8 = $[14];
|
|
207
|
+
}
|
|
208
|
+
return t8;
|
|
209
|
+
};
|
|
210
|
+
const TableEditor = ({
|
|
211
|
+
editable = false,
|
|
212
|
+
initialShardKey
|
|
213
|
+
}) => {
|
|
214
|
+
const client = useLunora();
|
|
215
|
+
const navigate = useNavigate();
|
|
216
|
+
const router = useRouter();
|
|
217
|
+
const search = useSearch({
|
|
218
|
+
strict: false
|
|
219
|
+
});
|
|
220
|
+
const view = searchToDataView(search);
|
|
221
|
+
const searchRef = useRef(search);
|
|
222
|
+
searchRef.current = search;
|
|
223
|
+
const onDataRoute = () => router.state.location.pathname.split("/").findLast(Boolean) === "data";
|
|
224
|
+
const tier = view.tier ?? "shard";
|
|
225
|
+
const tableParameter = view.table;
|
|
226
|
+
const [savedQueries, setSavedQueries] = useState(() => loadSavedQueries());
|
|
227
|
+
const [globalTableNames, setGlobalTableNames] = useState(() => /* @__PURE__ */ new Set());
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
fireAndForget((async () => {
|
|
230
|
+
try {
|
|
231
|
+
const tables = await client.listGlobalTables();
|
|
232
|
+
setGlobalTableNames(new Set(tables.map((table) => table.name)));
|
|
233
|
+
} catch {
|
|
234
|
+
}
|
|
235
|
+
})());
|
|
236
|
+
}, [client]);
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
if (tier === "shard" && tableParameter !== void 0 && globalTableNames.has(tableParameter)) {
|
|
239
|
+
fireAndForget(navigate({
|
|
240
|
+
replace: true,
|
|
241
|
+
search: (previous) => {
|
|
242
|
+
return {
|
|
243
|
+
...previous,
|
|
244
|
+
schema: "global"
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
to: "/data"
|
|
248
|
+
}));
|
|
249
|
+
}
|
|
250
|
+
}, [globalTableNames, navigate, tableParameter, tier]);
|
|
251
|
+
const selectTier = (next) => {
|
|
252
|
+
fireAndForget(navigate({
|
|
253
|
+
search: (previous_0) => {
|
|
254
|
+
return {
|
|
255
|
+
...previous_0,
|
|
256
|
+
schema: next === "global" ? "global" : void 0,
|
|
257
|
+
table: void 0
|
|
258
|
+
};
|
|
259
|
+
},
|
|
260
|
+
to: "/data"
|
|
261
|
+
}));
|
|
262
|
+
};
|
|
263
|
+
const onSelectTable = (table_0) => {
|
|
264
|
+
if (!onDataRoute()) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
fireAndForget(navigate({
|
|
268
|
+
search: (previous_1) => {
|
|
269
|
+
return {
|
|
270
|
+
...previous_1,
|
|
271
|
+
table: table_0
|
|
272
|
+
};
|
|
273
|
+
},
|
|
274
|
+
to: "/data"
|
|
275
|
+
}));
|
|
276
|
+
};
|
|
277
|
+
const onNavigateToGlobal = (table_1) => {
|
|
278
|
+
fireAndForget(navigate({
|
|
279
|
+
search: (previous_2) => {
|
|
280
|
+
return {
|
|
281
|
+
...previous_2,
|
|
282
|
+
schema: "global",
|
|
283
|
+
table: table_1
|
|
284
|
+
};
|
|
285
|
+
},
|
|
286
|
+
to: "/data"
|
|
287
|
+
}));
|
|
288
|
+
};
|
|
289
|
+
const onViewChange = (next_0) => {
|
|
290
|
+
const patch = dataViewToSearch(next_0);
|
|
291
|
+
const {
|
|
292
|
+
current
|
|
293
|
+
} = searchRef;
|
|
294
|
+
const unchanged = (current["filters"] ?? void 0) === patch.filters && (current["order"] ?? void 0) === patch.order && (current["search"] ?? void 0) === patch.search && (current["shard"] ?? void 0) === patch.shard;
|
|
295
|
+
if (unchanged || !onDataRoute()) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
fireAndForget(navigate({
|
|
299
|
+
replace: true,
|
|
300
|
+
search: (previous_3) => {
|
|
301
|
+
return {
|
|
302
|
+
...previous_3,
|
|
303
|
+
filters: patch.filters,
|
|
304
|
+
order: patch.order,
|
|
305
|
+
search: patch.search,
|
|
306
|
+
shard: patch.shard
|
|
307
|
+
};
|
|
308
|
+
},
|
|
309
|
+
to: "/data"
|
|
310
|
+
}));
|
|
311
|
+
};
|
|
312
|
+
const onCopyLink = () => {
|
|
313
|
+
try {
|
|
314
|
+
fireAndForget(globalThis.navigator.clipboard.writeText(globalThis.location.href));
|
|
315
|
+
} catch {
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
const onSaveQuery = (name, snapshot) => {
|
|
319
|
+
setSavedQueries(saveQuery(name, snapshot));
|
|
320
|
+
};
|
|
321
|
+
const onDeleteQuery = (name_0) => {
|
|
322
|
+
setSavedQueries(deleteSavedQuery(name_0));
|
|
323
|
+
};
|
|
324
|
+
const onApplyQuery = (query) => {
|
|
325
|
+
const patch_0 = dataViewToSearch(query.view);
|
|
326
|
+
fireAndForget(navigate({
|
|
327
|
+
search: () => {
|
|
328
|
+
return {
|
|
329
|
+
filters: patch_0.filters,
|
|
330
|
+
order: patch_0.order,
|
|
331
|
+
schema: patch_0.schema,
|
|
332
|
+
search: patch_0.search,
|
|
333
|
+
shard: patch_0.shard,
|
|
334
|
+
table: patch_0.table
|
|
335
|
+
};
|
|
336
|
+
},
|
|
337
|
+
to: "/data"
|
|
338
|
+
}));
|
|
339
|
+
};
|
|
340
|
+
const queryBar = {
|
|
341
|
+
onApplyQuery,
|
|
342
|
+
onCopyLink,
|
|
343
|
+
onDeleteQuery,
|
|
344
|
+
onSaveQuery,
|
|
345
|
+
saved: savedQueries
|
|
346
|
+
};
|
|
347
|
+
const schemaSwitch = /* @__PURE__ */ jsxDEV(SchemaSwitch, {
|
|
348
|
+
onChange: selectTier,
|
|
349
|
+
tier
|
|
350
|
+
}, void 0, false);
|
|
351
|
+
return tier === "global" ? /* @__PURE__ */ jsxDEV(GlobalDataBrowser, {
|
|
352
|
+
initialTable: tableParameter,
|
|
353
|
+
onSelectTable,
|
|
354
|
+
schemaSwitch
|
|
355
|
+
}, void 0, false) : /* @__PURE__ */ jsxDEV(DataBrowser, {
|
|
356
|
+
editable,
|
|
357
|
+
globalTableNames,
|
|
358
|
+
initialFilters: view.filters,
|
|
359
|
+
initialOrderBy: view.orderBy,
|
|
360
|
+
initialSearch: view.search,
|
|
361
|
+
initialShardKey: view.shard ?? initialShardKey,
|
|
362
|
+
onNavigateToGlobal,
|
|
363
|
+
onSelectTable,
|
|
364
|
+
onViewChange,
|
|
365
|
+
queryBar,
|
|
366
|
+
schemaSwitch,
|
|
367
|
+
tableParam: tableParameter
|
|
368
|
+
}, void 0, false);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
export { TableEditor };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { useState, useMemo } from 'react';
|
|
2
|
+
import { C as Card, a as CardContent } from './card-DURq3ElK.js';
|
|
3
|
+
import { T as Table, a as TableHeader, b as TableRow, c as TableHead, d as TableBody, e as TableCell } from './table-_RzNvy3R.js';
|
|
4
|
+
import { useT } from './createStudioI18n-CgvlmDkN.js';
|
|
5
|
+
import { c as cn } from './utils-B05Dmz_H.js';
|
|
6
|
+
import { jsxDEV } from 'react/jsx-dev-runtime';
|
|
7
|
+
|
|
8
|
+
const ADVISORY_LEVEL = {
|
|
9
|
+
ERROR: "error",
|
|
10
|
+
INFO: "info",
|
|
11
|
+
WARN: "warning"
|
|
12
|
+
};
|
|
13
|
+
const advisoryRow = (finding) => {
|
|
14
|
+
const table = typeof finding.metadata["table"] === "string" ? finding.metadata["table"] : void 0;
|
|
15
|
+
return {
|
|
16
|
+
description: `${finding.detail} ${finding.remediation}`,
|
|
17
|
+
entity: table,
|
|
18
|
+
issueType: finding.title,
|
|
19
|
+
key: finding.cacheKey,
|
|
20
|
+
level: ADVISORY_LEVEL[finding.level]
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
const LEVELS = ["error", "warning", "info"];
|
|
24
|
+
const LEVEL_DOT = {
|
|
25
|
+
error: "bg-destructive",
|
|
26
|
+
info: "bg-info",
|
|
27
|
+
warning: "bg-warning"
|
|
28
|
+
};
|
|
29
|
+
const levelLabel = (t, level) => ({
|
|
30
|
+
error: t("Errors"),
|
|
31
|
+
info: t("Info"),
|
|
32
|
+
warning: t("Warnings")
|
|
33
|
+
})[level];
|
|
34
|
+
const levelCount = (t, level, count) => ({
|
|
35
|
+
error: t("{count} errors", {
|
|
36
|
+
count
|
|
37
|
+
}),
|
|
38
|
+
info: t("{count} suggestions", {
|
|
39
|
+
count
|
|
40
|
+
}),
|
|
41
|
+
warning: t("{count} warnings", {
|
|
42
|
+
count
|
|
43
|
+
})
|
|
44
|
+
})[level];
|
|
45
|
+
const emptyTitle = (t, level) => ({
|
|
46
|
+
error: t("No errors detected"),
|
|
47
|
+
info: t("No suggestions"),
|
|
48
|
+
warning: t("No warnings detected")
|
|
49
|
+
})[level];
|
|
50
|
+
const AdvisorView = ({
|
|
51
|
+
error = null,
|
|
52
|
+
rows,
|
|
53
|
+
testId,
|
|
54
|
+
toolbar
|
|
55
|
+
}) => {
|
|
56
|
+
const t = useT();
|
|
57
|
+
const [active, setActive] = useState("error");
|
|
58
|
+
const counts = useMemo(() => {
|
|
59
|
+
const tally = {
|
|
60
|
+
error: 0,
|
|
61
|
+
info: 0,
|
|
62
|
+
warning: 0
|
|
63
|
+
};
|
|
64
|
+
for (const row of rows ?? []) {
|
|
65
|
+
tally[row.level] += 1;
|
|
66
|
+
}
|
|
67
|
+
return tally;
|
|
68
|
+
}, [rows]);
|
|
69
|
+
const visible = (rows ?? []).filter((row_0) => row_0.level === active);
|
|
70
|
+
const selectTab = (event) => {
|
|
71
|
+
setActive(event.currentTarget.dataset.level);
|
|
72
|
+
};
|
|
73
|
+
return /* @__PURE__ */ jsxDEV("div", {
|
|
74
|
+
className: "flex flex-col gap-3",
|
|
75
|
+
"data-testid": testId,
|
|
76
|
+
children: [/* @__PURE__ */ jsxDEV("div", {
|
|
77
|
+
className: "flex border-b border-border",
|
|
78
|
+
role: "tablist",
|
|
79
|
+
children: LEVELS.map((level) => /* @__PURE__ */ jsxDEV("button", {
|
|
80
|
+
"aria-selected": active === level,
|
|
81
|
+
className: "flex min-w-32 flex-col gap-0.5 border-b-2 border-transparent px-4 py-2 text-start outline-none transition-colors hover:bg-muted/40 focus-visible:bg-muted/40 aria-selected:border-foreground",
|
|
82
|
+
"data-level": level,
|
|
83
|
+
"data-testid": `${testId}-tab-${level}`,
|
|
84
|
+
onClick: selectTab,
|
|
85
|
+
role: "tab",
|
|
86
|
+
type: "button",
|
|
87
|
+
children: [/* @__PURE__ */ jsxDEV("span", {
|
|
88
|
+
className: "flex items-center gap-2 text-sm font-medium text-foreground",
|
|
89
|
+
children: [/* @__PURE__ */ jsxDEV("span", {
|
|
90
|
+
"aria-hidden": "true",
|
|
91
|
+
className: cn("size-2 rounded-sm", LEVEL_DOT[level])
|
|
92
|
+
}, void 0, false), levelLabel(t, level)]
|
|
93
|
+
}, void 0, true), /* @__PURE__ */ jsxDEV("span", {
|
|
94
|
+
className: "ps-4 text-xs text-muted-foreground",
|
|
95
|
+
children: levelCount(t, level, counts[level])
|
|
96
|
+
}, void 0, false)]
|
|
97
|
+
}, level, true))
|
|
98
|
+
}, void 0, false), toolbar !== void 0 && /* @__PURE__ */ jsxDEV("div", {
|
|
99
|
+
className: "flex flex-wrap items-center gap-2",
|
|
100
|
+
children: toolbar
|
|
101
|
+
}, void 0, false), error !== null && /* @__PURE__ */ jsxDEV("p", {
|
|
102
|
+
className: "text-sm text-destructive",
|
|
103
|
+
"data-testid": `${testId}-error`,
|
|
104
|
+
role: "alert",
|
|
105
|
+
children: error
|
|
106
|
+
}, void 0, false), /* @__PURE__ */ jsxDEV(Card, {
|
|
107
|
+
className: "overflow-hidden py-0",
|
|
108
|
+
children: /* @__PURE__ */ jsxDEV(CardContent, {
|
|
109
|
+
className: "px-0",
|
|
110
|
+
children: /* @__PURE__ */ jsxDEV(Table, {
|
|
111
|
+
children: [/* @__PURE__ */ jsxDEV(TableHeader, {
|
|
112
|
+
children: /* @__PURE__ */ jsxDEV(TableRow, {
|
|
113
|
+
children: [/* @__PURE__ */ jsxDEV(TableHead, {
|
|
114
|
+
children: t("Issue type")
|
|
115
|
+
}, void 0, false), /* @__PURE__ */ jsxDEV(TableHead, {
|
|
116
|
+
children: t("Entity/item")
|
|
117
|
+
}, void 0, false), /* @__PURE__ */ jsxDEV(TableHead, {
|
|
118
|
+
children: t("Description")
|
|
119
|
+
}, void 0, false)]
|
|
120
|
+
}, void 0, true)
|
|
121
|
+
}, void 0, false), /* @__PURE__ */ jsxDEV(TableBody, {
|
|
122
|
+
children: visible.length === 0 ? /* @__PURE__ */ jsxDEV(TableRow, {
|
|
123
|
+
children: /* @__PURE__ */ jsxDEV(TableCell, {
|
|
124
|
+
className: "h-40 text-center align-middle text-muted-foreground",
|
|
125
|
+
colSpan: 3,
|
|
126
|
+
"data-testid": `${testId}-empty`,
|
|
127
|
+
children: [/* @__PURE__ */ jsxDEV("span", {
|
|
128
|
+
className: "block text-sm font-medium text-foreground",
|
|
129
|
+
children: emptyTitle(t, active)
|
|
130
|
+
}, void 0, false), /* @__PURE__ */ jsxDEV("span", {
|
|
131
|
+
className: "block text-sm",
|
|
132
|
+
children: t("Nothing to report for this deployment.")
|
|
133
|
+
}, void 0, false)]
|
|
134
|
+
}, void 0, true)
|
|
135
|
+
}, void 0, false) : visible.map((row_1) => /* @__PURE__ */ jsxDEV(TableRow, {
|
|
136
|
+
children: [/* @__PURE__ */ jsxDEV(TableCell, {
|
|
137
|
+
className: "font-medium text-foreground",
|
|
138
|
+
children: row_1.issueType
|
|
139
|
+
}, void 0, false), /* @__PURE__ */ jsxDEV(TableCell, {
|
|
140
|
+
className: "font-mono text-xs text-muted-foreground",
|
|
141
|
+
children: row_1.entity ?? "—"
|
|
142
|
+
}, void 0, false), /* @__PURE__ */ jsxDEV(TableCell, {
|
|
143
|
+
className: "text-muted-foreground",
|
|
144
|
+
children: [/* @__PURE__ */ jsxDEV("span", {
|
|
145
|
+
children: row_1.description
|
|
146
|
+
}, void 0, false), row_1.action !== void 0 && /* @__PURE__ */ jsxDEV("span", {
|
|
147
|
+
className: "mt-1.5 flex flex-wrap items-center gap-1.5",
|
|
148
|
+
children: row_1.action
|
|
149
|
+
}, void 0, false)]
|
|
150
|
+
}, void 0, true)]
|
|
151
|
+
}, row_1.key, true))
|
|
152
|
+
}, void 0, false)]
|
|
153
|
+
}, void 0, true)
|
|
154
|
+
}, void 0, false)
|
|
155
|
+
}, void 0, false)]
|
|
156
|
+
}, void 0, true);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export { AdvisorView as A, advisoryRow as a };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const aggregateMetrics = (results) => {
|
|
2
|
+
let totalRequests = 0;
|
|
3
|
+
let totalErrors = 0;
|
|
4
|
+
let totalDatabaseSize = 0;
|
|
5
|
+
let reachable = 0;
|
|
6
|
+
let failed = 0;
|
|
7
|
+
let cacheHits = 0;
|
|
8
|
+
let cacheTotal = 0;
|
|
9
|
+
for (const {
|
|
10
|
+
metrics
|
|
11
|
+
} of results) {
|
|
12
|
+
if (metrics === null) {
|
|
13
|
+
failed += 1;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
reachable += 1;
|
|
17
|
+
totalRequests += metrics.requests;
|
|
18
|
+
totalErrors += metrics.errors;
|
|
19
|
+
totalDatabaseSize += metrics.databaseSize ?? 0;
|
|
20
|
+
if (metrics.cache !== null) {
|
|
21
|
+
cacheHits += metrics.cache.hits;
|
|
22
|
+
cacheTotal += metrics.cache.hits + metrics.cache.misses;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
failed,
|
|
27
|
+
// eslint-disable-next-line unicorn/no-null -- hitRate is part of the public AggregateMetrics type, which models "no cache" as null
|
|
28
|
+
hitRate: cacheTotal === 0 ? null : cacheHits / cacheTotal,
|
|
29
|
+
reachable,
|
|
30
|
+
totalDatabaseSize,
|
|
31
|
+
totalErrors,
|
|
32
|
+
totalRequests
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
const shardsToAggregate = (current, recents) => {
|
|
36
|
+
const seen = /* @__PURE__ */ new Set();
|
|
37
|
+
const out = [];
|
|
38
|
+
for (const shard of ["", current.trim(), ...recents]) {
|
|
39
|
+
if (!seen.has(shard)) {
|
|
40
|
+
seen.add(shard);
|
|
41
|
+
out.push(shard);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
};
|
|
46
|
+
const percentile = (values, p) => {
|
|
47
|
+
if (values.length === 0) {
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
values.sort((a, b) => a - b);
|
|
51
|
+
if (p <= 0) {
|
|
52
|
+
return values[0] ?? 0;
|
|
53
|
+
}
|
|
54
|
+
if (p >= 100) {
|
|
55
|
+
return values.at(-1) ?? 0;
|
|
56
|
+
}
|
|
57
|
+
const index = Math.ceil(p / 100 * values.length) - 1;
|
|
58
|
+
return values[index] ?? 0;
|
|
59
|
+
};
|
|
60
|
+
const computeLatencyPercentiles = (snapshot) => {
|
|
61
|
+
const snap = snapshot;
|
|
62
|
+
const functionList = snap.functions;
|
|
63
|
+
if (!functionList || functionList.length === 0) {
|
|
64
|
+
return {
|
|
65
|
+
p90: 0,
|
|
66
|
+
p95: 0
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const CAP = 1e3;
|
|
70
|
+
const samples = [];
|
|
71
|
+
for (const functionStat of functionList) {
|
|
72
|
+
if (functionStat.calls <= 0) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const avg = functionStat.totalDurationMs / functionStat.calls;
|
|
76
|
+
const reps = Math.min(functionStat.calls, CAP);
|
|
77
|
+
for (let index = 0; index < reps; index += 1) {
|
|
78
|
+
samples.push(avg);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (samples.length === 0) {
|
|
82
|
+
return {
|
|
83
|
+
p90: 0,
|
|
84
|
+
p95: 0
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
p90: percentile([...samples], 90),
|
|
89
|
+
p95: percentile([...samples], 95)
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
const computeDelta = (baseline, current, direction = "neutral") => {
|
|
93
|
+
const delta = current - baseline;
|
|
94
|
+
const pct = baseline === 0 ? null : delta / baseline * 100;
|
|
95
|
+
return {
|
|
96
|
+
delta,
|
|
97
|
+
direction,
|
|
98
|
+
pct
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
const enrichQueryStats = (entries) => entries.map((entry) => {
|
|
102
|
+
return {
|
|
103
|
+
...entry,
|
|
104
|
+
avgDurationMs: entry.execCount > 0 ? entry.totalDurationMs / entry.execCount : 0
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export { aggregateMetrics, computeDelta, computeLatencyPercentiles, enrichQueryStats, percentile, shardsToAggregate };
|