@marimo-team/islands 0.23.11-dev67 → 0.23.11-dev8
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/dist/{chat-ui-C4-GMW9y.js → chat-ui-c1FdlK-C.js} +2 -2
- package/dist/{code-visibility-UH2371Sv.js → code-visibility-C-06v5KM.js} +20 -18
- package/dist/{html-to-image-BwfcJJxA.js → html-to-image-D5-EpALB.js} +2158 -2132
- package/dist/main.js +5 -5
- package/dist/{process-output-DsloEDna.js → process-output-WDZE0cyS.js} +1 -1
- package/dist/{reveal-component-BNU1V1x5.js → reveal-component-yjK700z1.js} +2 -2
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/datasources/__tests__/column-preview.test.tsx +97 -0
- package/src/components/datasources/__tests__/filter-empty.test.ts +81 -0
- package/src/components/datasources/__tests__/utils.test.ts +62 -1
- package/src/components/datasources/column-preview.tsx +2 -4
- package/src/components/datasources/components.tsx +15 -7
- package/src/components/datasources/datasources.tsx +311 -178
- package/src/components/datasources/utils.ts +40 -1
- package/src/components/editor/ai/ai-completion-editor.tsx +6 -5
- package/src/components/editor/connections/components.tsx +13 -0
- package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +4 -4
- package/src/components/editor/connections/storage/as-code.ts +11 -4
- package/src/core/ai/__tests__/strip-wrapping-backticks.test.ts +133 -0
- package/src/core/ai/stream-completion-text.ts +48 -0
- package/src/core/ai/strip-wrapping-backticks.ts +88 -0
- package/src/core/codemirror/ai/request.ts +2 -14
- package/src/core/datasets/__tests__/data-source.test.ts +226 -0
- package/src/core/datasets/data-source-connections.ts +88 -24
package/package.json
CHANGED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { render } from "@testing-library/react";
|
|
4
|
+
import { Provider } from "jotai";
|
|
5
|
+
import type React from "react";
|
|
6
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
7
|
+
import { MockRequestClient } from "@/__mocks__/requests";
|
|
8
|
+
import type { SQLTableContext } from "@/core/datasets/data-source-connections";
|
|
9
|
+
import type { DataTable, DataTableColumn } from "@/core/kernel/messages";
|
|
10
|
+
import { requestClientAtom } from "@/core/network/requests";
|
|
11
|
+
import { store } from "@/core/state/jotai";
|
|
12
|
+
import { DatasetColumnPreview } from "../column-preview";
|
|
13
|
+
|
|
14
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
15
|
+
<Provider store={store}>{children}</Provider>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const table: DataTable = {
|
|
19
|
+
name: "users",
|
|
20
|
+
columns: [],
|
|
21
|
+
source: "my_engine",
|
|
22
|
+
// Not "connection"/"catalog", so a preview is requested on mount.
|
|
23
|
+
source_type: "duckdb",
|
|
24
|
+
type: "table",
|
|
25
|
+
engine: "my_engine" as DataTable["engine"],
|
|
26
|
+
indexes: null,
|
|
27
|
+
num_columns: null,
|
|
28
|
+
num_rows: null,
|
|
29
|
+
variable_name: null,
|
|
30
|
+
primary_keys: null,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const column = { name: "email" } as DataTableColumn;
|
|
34
|
+
|
|
35
|
+
function renderPreview(sqlTableContext?: SQLTableContext) {
|
|
36
|
+
const client = MockRequestClient.create();
|
|
37
|
+
store.set(requestClientAtom, client);
|
|
38
|
+
render(
|
|
39
|
+
<DatasetColumnPreview
|
|
40
|
+
table={table}
|
|
41
|
+
column={column}
|
|
42
|
+
preview={undefined}
|
|
43
|
+
onAddColumnChart={vi.fn()}
|
|
44
|
+
sqlTableContext={sqlTableContext}
|
|
45
|
+
/>,
|
|
46
|
+
{ wrapper },
|
|
47
|
+
);
|
|
48
|
+
return client;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const ctx = (
|
|
52
|
+
overrides: Partial<SQLTableContext> & { database: string; schema: string },
|
|
53
|
+
): SQLTableContext => ({
|
|
54
|
+
engine: "my_engine",
|
|
55
|
+
dialect: "duckdb",
|
|
56
|
+
...overrides,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("DatasetColumnPreview fullyQualifiedTableName", () => {
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
vi.clearAllMocks();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("uses just the table name without a SQL context", () => {
|
|
65
|
+
const client = renderPreview(undefined);
|
|
66
|
+
expect(client.previewDatasetColumn).toHaveBeenCalledWith(
|
|
67
|
+
expect.objectContaining({ fullyQualifiedTableName: "users" }),
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("qualifies with database and schema for flat engines", () => {
|
|
72
|
+
const client = renderPreview(ctx({ database: "db", schema: "public" }));
|
|
73
|
+
expect(client.previewDatasetColumn).toHaveBeenCalledWith(
|
|
74
|
+
expect.objectContaining({ fullyQualifiedTableName: "db.public.users" }),
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("does not emit a double dot for schemaless tables", () => {
|
|
79
|
+
// Regression: previously produced "db..users".
|
|
80
|
+
const client = renderPreview(ctx({ database: "db", schema: "" }));
|
|
81
|
+
expect(client.previewDatasetColumn).toHaveBeenCalledWith(
|
|
82
|
+
expect.objectContaining({ fullyQualifiedTableName: "db.users" }),
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("includes the full schema path for nested namespaces", () => {
|
|
87
|
+
// Regression: previously dropped schemaPath and emitted "top.deep.users".
|
|
88
|
+
const client = renderPreview(
|
|
89
|
+
ctx({ database: "top", schema: "deep", schemaPath: ["nested", "deep"] }),
|
|
90
|
+
);
|
|
91
|
+
expect(client.previewDatasetColumn).toHaveBeenCalledWith(
|
|
92
|
+
expect.objectContaining({
|
|
93
|
+
fullyQualifiedTableName: "top.nested.deep.users",
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -28,11 +28,15 @@ function makeSchema(opts: {
|
|
|
28
28
|
name: string;
|
|
29
29
|
tables: DataTable[];
|
|
30
30
|
tables_resolved?: boolean;
|
|
31
|
+
child_schemas?: DatabaseSchema[];
|
|
32
|
+
child_schemas_resolved?: boolean;
|
|
31
33
|
}): DatabaseSchema {
|
|
32
34
|
return {
|
|
33
35
|
name: opts.name,
|
|
34
36
|
tables: opts.tables,
|
|
35
37
|
tables_resolved: opts.tables_resolved ?? true,
|
|
38
|
+
child_schemas: opts.child_schemas ?? [],
|
|
39
|
+
child_schemas_resolved: opts.child_schemas_resolved ?? true,
|
|
36
40
|
};
|
|
37
41
|
}
|
|
38
42
|
|
|
@@ -180,4 +184,81 @@ describe("filterEmptyDatabases", () => {
|
|
|
180
184
|
|
|
181
185
|
expect(databases).toEqual(snapshot);
|
|
182
186
|
});
|
|
187
|
+
|
|
188
|
+
it("keeps a namespace that has only child namespaces (no own tables)", () => {
|
|
189
|
+
const databases = [
|
|
190
|
+
makeDatabase("iceberg", [
|
|
191
|
+
makeSchema({
|
|
192
|
+
name: "top",
|
|
193
|
+
tables: [],
|
|
194
|
+
child_schemas: [
|
|
195
|
+
makeSchema({ name: "nested", tables: [makeTable("t1")] }),
|
|
196
|
+
],
|
|
197
|
+
}),
|
|
198
|
+
]),
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
expect(filterEmptyDatabases(databases)).toBe(databases);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("preserves a namespace whose child schemas are deferred", () => {
|
|
205
|
+
const databases = [
|
|
206
|
+
makeDatabase("iceberg", [
|
|
207
|
+
makeSchema({
|
|
208
|
+
name: "top",
|
|
209
|
+
tables: [],
|
|
210
|
+
child_schemas: [],
|
|
211
|
+
child_schemas_resolved: false,
|
|
212
|
+
}),
|
|
213
|
+
]),
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
expect(filterEmptyDatabases(databases)).toBe(databases);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("hides a nested namespace that is resolved-empty", () => {
|
|
220
|
+
const databases = [
|
|
221
|
+
makeDatabase("iceberg", [
|
|
222
|
+
makeSchema({
|
|
223
|
+
name: "top",
|
|
224
|
+
tables: [makeTable("t1")],
|
|
225
|
+
child_schemas: [
|
|
226
|
+
makeSchema({ name: "empty_child", tables: [] }),
|
|
227
|
+
makeSchema({ name: "full_child", tables: [makeTable("t2")] }),
|
|
228
|
+
],
|
|
229
|
+
}),
|
|
230
|
+
]),
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
expect(filterEmptyDatabases(databases)).toEqual([
|
|
234
|
+
makeDatabase("iceberg", [
|
|
235
|
+
makeSchema({
|
|
236
|
+
name: "top",
|
|
237
|
+
tables: [makeTable("t1")],
|
|
238
|
+
child_schemas: [
|
|
239
|
+
makeSchema({ name: "full_child", tables: [makeTable("t2")] }),
|
|
240
|
+
],
|
|
241
|
+
}),
|
|
242
|
+
]),
|
|
243
|
+
]);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("hides a parent namespace when all its descendants are empty", () => {
|
|
247
|
+
const databases = [
|
|
248
|
+
makeDatabase("iceberg", [
|
|
249
|
+
makeSchema({
|
|
250
|
+
name: "top",
|
|
251
|
+
tables: [],
|
|
252
|
+
child_schemas: [makeSchema({ name: "empty_child", tables: [] })],
|
|
253
|
+
}),
|
|
254
|
+
makeSchema({ name: "other", tables: [makeTable("t1")] }),
|
|
255
|
+
]),
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
expect(filterEmptyDatabases(databases)).toEqual([
|
|
259
|
+
makeDatabase("iceberg", [
|
|
260
|
+
makeSchema({ name: "other", tables: [makeTable("t1")] }),
|
|
261
|
+
]),
|
|
262
|
+
]);
|
|
263
|
+
});
|
|
183
264
|
});
|
|
@@ -4,7 +4,7 @@ import { describe, expect, it } from "vitest";
|
|
|
4
4
|
import type { SQLTableContext } from "@/core/datasets/data-source-connections";
|
|
5
5
|
import { DUCKDB_ENGINE } from "@/core/datasets/engines";
|
|
6
6
|
import type { DataTable, DataTableColumn } from "@/core/kernel/messages";
|
|
7
|
-
import { sqlCode } from "../utils";
|
|
7
|
+
import { sqlCode, tableUniqueId } from "../utils";
|
|
8
8
|
|
|
9
9
|
describe("sqlCode", () => {
|
|
10
10
|
const mockTable: DataTable = {
|
|
@@ -458,3 +458,64 @@ describe("sqlCode", () => {
|
|
|
458
458
|
});
|
|
459
459
|
});
|
|
460
460
|
});
|
|
461
|
+
|
|
462
|
+
describe("tableUniqueId", () => {
|
|
463
|
+
const ctx = (
|
|
464
|
+
overrides: Partial<SQLTableContext> & { database: string; schema: string },
|
|
465
|
+
): SQLTableContext => ({
|
|
466
|
+
engine: "e",
|
|
467
|
+
dialect: "duckdb",
|
|
468
|
+
...overrides,
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it("returns just the table name without a context", () => {
|
|
472
|
+
expect(tableUniqueId(undefined, "t")).toBe("t");
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it("uses database + schema for flat engines", () => {
|
|
476
|
+
expect(tableUniqueId(ctx({ database: "db", schema: "public" }), "t")).toBe(
|
|
477
|
+
"db.public.t",
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("does not duplicate the leaf schema for nested namespaces", () => {
|
|
482
|
+
// Regression: previously produced "top.nested.nested.t".
|
|
483
|
+
expect(
|
|
484
|
+
tableUniqueId(
|
|
485
|
+
ctx({ database: "top", schema: "nested", schemaPath: ["nested"] }),
|
|
486
|
+
"t",
|
|
487
|
+
),
|
|
488
|
+
).toBe("top.nested.t");
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it("includes the full schema path for deeply nested namespaces", () => {
|
|
492
|
+
expect(
|
|
493
|
+
tableUniqueId(
|
|
494
|
+
ctx({
|
|
495
|
+
database: "top",
|
|
496
|
+
schema: "deep",
|
|
497
|
+
schemaPath: ["nested", "deep"],
|
|
498
|
+
}),
|
|
499
|
+
"t",
|
|
500
|
+
),
|
|
501
|
+
).toBe("top.nested.deep.t");
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it("falls back to the flat schema when schemaPath is empty", () => {
|
|
505
|
+
expect(
|
|
506
|
+
tableUniqueId(
|
|
507
|
+
ctx({ database: "db", schema: "public", schemaPath: [] }),
|
|
508
|
+
"t",
|
|
509
|
+
),
|
|
510
|
+
).toBe("db.public.t");
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it("does not emit a double dot for schemaless tables", () => {
|
|
514
|
+
expect(
|
|
515
|
+
tableUniqueId(ctx({ database: "db", schema: "", schemaPath: [] }), "t"),
|
|
516
|
+
).toBe("db.t");
|
|
517
|
+
expect(tableUniqueId(ctx({ database: "db", schema: "" }), "t")).toBe(
|
|
518
|
+
"db.t",
|
|
519
|
+
);
|
|
520
|
+
});
|
|
521
|
+
});
|
|
@@ -29,7 +29,7 @@ import { Button } from "../ui/button";
|
|
|
29
29
|
import { Tooltip } from "../ui/tooltip";
|
|
30
30
|
import { ColumnPreviewContainer } from "./components";
|
|
31
31
|
import { InstallPackageButton } from "./install-package-button";
|
|
32
|
-
import { convertStatsName, sqlCode } from "./utils";
|
|
32
|
+
import { convertStatsName, sqlCode, tableUniqueId } from "./utils";
|
|
33
33
|
|
|
34
34
|
export const DatasetColumnPreview: React.FC<{
|
|
35
35
|
table: DataTable;
|
|
@@ -48,9 +48,7 @@ export const DatasetColumnPreview: React.FC<{
|
|
|
48
48
|
tableName: table.name,
|
|
49
49
|
columnName: column.name,
|
|
50
50
|
sourceType: table.source_type,
|
|
51
|
-
fullyQualifiedTableName: sqlTableContext
|
|
52
|
-
? `${sqlTableContext.database}.${sqlTableContext.schema}.${table.name}`
|
|
53
|
-
: table.name,
|
|
51
|
+
fullyQualifiedTableName: tableUniqueId(sqlTableContext, table.name),
|
|
54
52
|
});
|
|
55
53
|
};
|
|
56
54
|
|
|
@@ -14,25 +14,31 @@ export const RotatingChevron: React.FC<{ isExpanded: boolean }> = ({
|
|
|
14
14
|
export const DatasourceLabel: React.FC<{
|
|
15
15
|
children: React.ReactNode;
|
|
16
16
|
className?: string;
|
|
17
|
-
|
|
17
|
+
style?: CSSProperties;
|
|
18
|
+
}> = ({ children, className, style }) => {
|
|
18
19
|
return (
|
|
19
20
|
<div
|
|
20
21
|
className={cn(
|
|
21
22
|
"flex gap-1.5 items-center font-bold py-1.5 text-muted-foreground bg-(--slate-2) text-sm",
|
|
22
23
|
className,
|
|
23
24
|
)}
|
|
25
|
+
style={style}
|
|
24
26
|
>
|
|
25
27
|
{children}
|
|
26
28
|
</div>
|
|
27
29
|
);
|
|
28
30
|
};
|
|
29
31
|
|
|
30
|
-
export const EmptyState: React.FC<{
|
|
31
|
-
content
|
|
32
|
-
className
|
|
33
|
-
|
|
32
|
+
export const EmptyState: React.FC<{
|
|
33
|
+
content: string;
|
|
34
|
+
className?: string;
|
|
35
|
+
style?: CSSProperties;
|
|
36
|
+
}> = ({ content, className, style }) => {
|
|
34
37
|
return (
|
|
35
|
-
<div
|
|
38
|
+
<div
|
|
39
|
+
className={cn("text-sm text-muted-foreground py-1", className)}
|
|
40
|
+
style={style}
|
|
41
|
+
>
|
|
36
42
|
{content}
|
|
37
43
|
</div>
|
|
38
44
|
);
|
|
@@ -61,13 +67,15 @@ export const ErrorState: React.FC<{
|
|
|
61
67
|
export const LoadingState: React.FC<{
|
|
62
68
|
message: string;
|
|
63
69
|
className?: string;
|
|
64
|
-
|
|
70
|
+
style?: CSSProperties;
|
|
71
|
+
}> = ({ message, className, style }) => {
|
|
65
72
|
return (
|
|
66
73
|
<div
|
|
67
74
|
className={cn(
|
|
68
75
|
"text-sm bg-blue-50 dark:bg-(--accent) text-blue-500 dark:text-blue-50 flex items-center gap-2 p-2 h-8",
|
|
69
76
|
className,
|
|
70
77
|
)}
|
|
78
|
+
style={style}
|
|
71
79
|
>
|
|
72
80
|
<LoaderCircle className="h-4 w-4 animate-spin" />
|
|
73
81
|
{message}
|