@marimo-team/islands 0.23.9-dev9 → 0.23.10-dev0
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/{ConnectedDataExplorerComponent-OzrfMM5L.js → ConnectedDataExplorerComponent-CyV83R2m.js} +4 -4
- package/dist/assets/__vite-browser-external-Ci2ZQfXU.js +1 -0
- package/dist/assets/{worker-CpBbwbQo.js → worker-ip3AI_sN.js} +2 -2
- package/dist/{chat-ui-BDI3FMI8.js → chat-ui-ChD4VvCo.js} +3060 -3033
- package/dist/{code-visibility-DgHF4q8X.js → code-visibility-CjGICDxg.js} +1368 -1204
- package/dist/{formats-DQ5qjo_Q.js → formats-DHxc-FdY.js} +1 -1
- package/dist/{glide-data-editor-DqRY9naW.js → glide-data-editor-BOmK9ETQ.js} +2 -2
- package/dist/{html-to-image-CiSinpSR.js → html-to-image-BHv7CEU_.js} +2145 -2153
- package/dist/{input-CZD2z6X2.js → input-_2sjvfne.js} +1 -1
- package/dist/main.js +680 -705
- package/dist/{mermaid-IU93XzmY.js → mermaid-lXOw5Py9.js} +2 -2
- package/dist/{process-output-5qJjMRKh.js → process-output-BvySRgli.js} +33 -25
- package/dist/{reveal-component-qpHJES_u.js → reveal-component-DVWED--8.js} +312 -291
- package/dist/{spec-a6DaqW__.js → spec-B96zNUEA.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/{toDate-ZVVIBmdk.js → toDate-x-WRDCH7.js} +1 -1
- package/dist/{useAsyncData-C008zUPi.js → useAsyncData-iRgKDT5s.js} +1 -1
- package/dist/{useDeepCompareMemoize-BrA3_n61.js → useDeepCompareMemoize-CkQ57VS2.js} +1 -1
- package/dist/{useLifecycle-BNaoJ5a4.js → useLifecycle-BBO9PIph.js} +1 -1
- package/dist/{useTheme-7O0YWlE5.js → useTheme-DHIrRQOe.js} +34 -21
- package/dist/{vega-component-DJNmOdUj.js → vega-component-Dq-SH463.js} +5 -5
- package/package.json +1 -1
- package/src/components/ai/__tests__/ai-utils.test.ts +43 -38
- package/src/components/ai/ai-model-dropdown.tsx +2 -2
- package/src/components/app-config/ai-config.tsx +147 -16
- package/src/components/app-config/user-config-form.tsx +37 -1
- package/src/components/chat/__tests__/chat-utils.test.ts +269 -0
- package/src/components/chat/chat-panel.tsx +38 -5
- package/src/components/chat/chat-utils.ts +14 -58
- package/src/components/data-table/TableBottomBar.tsx +5 -8
- package/src/components/data-table/__tests__/column-explorer.test.tsx +128 -0
- package/src/components/data-table/__tests__/header-items.test.tsx +220 -10
- package/src/components/data-table/column-explorer-panel/column-explorer.tsx +95 -29
- package/src/components/data-table/column-header.tsx +17 -12
- package/src/components/data-table/data-table.tsx +4 -0
- package/src/components/data-table/export-actions.tsx +19 -12
- package/src/components/data-table/header-items.tsx +40 -16
- package/src/components/data-table/hooks/use-column-visibility.ts +14 -0
- package/src/components/data-table/schemas.ts +2 -2
- package/src/components/data-table/table-explorer-panel/table-explorer-panel.tsx +16 -6
- package/src/components/databases/display.tsx +2 -0
- package/src/components/datasources/__tests__/utils.test.ts +82 -0
- package/src/components/datasources/utils.ts +16 -15
- package/src/components/editor/Disconnected.tsx +1 -60
- package/src/components/editor/__tests__/viewer-banner.test.tsx +89 -0
- package/src/components/editor/actions/pair-with-agent-modal.tsx +1 -0
- package/src/components/editor/actions/useCellActionButton.tsx +3 -3
- package/src/components/editor/actions/useNotebookActions.tsx +5 -2
- package/src/components/editor/cell/code/cell-editor.tsx +25 -5
- package/src/components/editor/chrome/types.ts +13 -6
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +6 -4
- package/src/components/editor/chrome/wrapper/footer-items/ai-status.tsx +10 -1
- package/src/components/editor/chrome/wrapper/sidebar.tsx +7 -5
- package/src/components/editor/errors/auto-fix.tsx +3 -3
- package/src/components/editor/header/__tests__/status.test.tsx +0 -15
- package/src/components/editor/header/app-header.tsx +1 -4
- package/src/components/editor/header/status.tsx +4 -13
- package/src/components/editor/navigation/__tests__/navigation.test.ts +15 -0
- package/src/components/editor/navigation/navigation.ts +5 -0
- package/src/components/editor/output/MarimoErrorOutput.tsx +103 -25
- package/src/components/editor/output/MarimoTracebackOutput.tsx +28 -39
- package/src/components/editor/renderers/cell-array.tsx +27 -24
- package/src/components/editor/renderers/slides-layout/__tests__/compute-slide-cells.test.ts +30 -17
- package/src/components/editor/renderers/slides-layout/compute-slide-cells.ts +17 -8
- package/src/components/editor/renderers/slides-layout/slides-layout.tsx +10 -12
- package/src/components/editor/viewer-banner.tsx +82 -0
- package/src/components/slides/minimap.tsx +45 -9
- package/src/components/slides/reveal-component.tsx +82 -37
- package/src/components/slides/slide-cell-view.tsx +12 -1
- package/src/components/slides/slide-form.tsx +11 -3
- package/src/components/static-html/static-banner.tsx +28 -22
- package/src/core/ai/__tests__/model-registry.test.ts +72 -60
- package/src/core/ai/model-registry.ts +33 -28
- package/src/core/cells/__tests__/actions.test.ts +48 -0
- package/src/core/cells/actions.ts +5 -6
- package/src/core/codemirror/__tests__/setup.test.ts +29 -0
- package/src/core/codemirror/cells/traceback-decorations.ts +1 -1
- package/src/core/codemirror/cm.ts +50 -3
- package/src/core/codemirror/completion/hints.ts +4 -1
- package/src/core/codemirror/format.ts +1 -0
- package/src/core/codemirror/keymaps/vim.ts +63 -0
- package/src/core/codemirror/language/languages/sql/sql.ts +1 -0
- package/src/core/codemirror/language/languages/sql/utils.ts +2 -0
- package/src/core/config/__tests__/config-schema.test.ts +4 -0
- package/src/core/config/config-schema.ts +4 -0
- package/src/core/config/config.ts +16 -0
- package/src/core/edit-app.tsx +3 -0
- package/src/core/islands/bootstrap.ts +2 -0
- package/src/core/kernel/__tests__/handlers.test.ts +5 -0
- package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +0 -13
- package/src/core/websocket/types.ts +0 -6
- package/src/core/websocket/useMarimoKernelConnection.tsx +3 -12
- package/src/css/app/Cell.css +0 -1
- package/src/plugins/impl/DataTablePlugin.tsx +48 -22
- package/src/plugins/impl/chat/ChatPlugin.tsx +7 -1
- package/src/plugins/impl/chat/__tests__/chat-ui.test.ts +278 -0
- package/src/plugins/impl/chat/chat-ui.tsx +106 -59
- package/src/plugins/impl/chat/types.ts +5 -0
- package/src/utils/__tests__/json-parser.test.ts +1 -69
- package/src/utils/json/json-parser.ts +0 -30
- package/dist/assets/__vite-browser-external-CAdMKBac.js +0 -1
|
@@ -29,10 +29,13 @@ import { formattingExample } from "./column-formatting/feature";
|
|
|
29
29
|
import { formatOptions } from "./column-formatting/types";
|
|
30
30
|
import { NAMELESS_COLUMN_PREFIX } from "./columns";
|
|
31
31
|
|
|
32
|
-
export function
|
|
33
|
-
column
|
|
34
|
-
locale
|
|
35
|
-
|
|
32
|
+
export function FormatOptions<TData, TValue>({
|
|
33
|
+
column,
|
|
34
|
+
locale,
|
|
35
|
+
}: {
|
|
36
|
+
column: Column<TData, TValue>;
|
|
37
|
+
locale: string;
|
|
38
|
+
}) {
|
|
36
39
|
const dataType: DataType | undefined = column.columnDef.meta?.dataType;
|
|
37
40
|
const columnFormatOptions = dataType ? formatOptions[dataType] : [];
|
|
38
41
|
|
|
@@ -83,9 +86,11 @@ export function renderFormatOptions<TData, TValue>(
|
|
|
83
86
|
);
|
|
84
87
|
}
|
|
85
88
|
|
|
86
|
-
export function
|
|
87
|
-
column
|
|
88
|
-
|
|
89
|
+
export function ColumnWrapping<TData, TValue>({
|
|
90
|
+
column,
|
|
91
|
+
}: {
|
|
92
|
+
column: Column<TData, TValue>;
|
|
93
|
+
}) {
|
|
89
94
|
if (!column.getCanWrap?.() || !column.getColumnWrapping) {
|
|
90
95
|
return null;
|
|
91
96
|
}
|
|
@@ -108,9 +113,11 @@ export function renderColumnWrapping<TData, TValue>(
|
|
|
108
113
|
);
|
|
109
114
|
}
|
|
110
115
|
|
|
111
|
-
export function
|
|
112
|
-
column
|
|
113
|
-
|
|
116
|
+
export function ColumnPinning<TData, TValue>({
|
|
117
|
+
column,
|
|
118
|
+
}: {
|
|
119
|
+
column: Column<TData, TValue>;
|
|
120
|
+
}) {
|
|
114
121
|
if (!column.getCanPin?.() || !column.getIsPinned) {
|
|
115
122
|
return null;
|
|
116
123
|
}
|
|
@@ -157,7 +164,11 @@ export function HideColumn<TData, TValue>({
|
|
|
157
164
|
);
|
|
158
165
|
}
|
|
159
166
|
|
|
160
|
-
export function
|
|
167
|
+
export function CopyColumn<TData, TValue>({
|
|
168
|
+
column,
|
|
169
|
+
}: {
|
|
170
|
+
column: Column<TData, TValue>;
|
|
171
|
+
}) {
|
|
161
172
|
if (!column.getCanCopy?.()) {
|
|
162
173
|
return null;
|
|
163
174
|
}
|
|
@@ -177,10 +188,19 @@ export function renderCopyColumn<TData, TValue>(column: Column<TData, TValue>) {
|
|
|
177
188
|
const AscIcon = ArrowUpNarrowWideIcon;
|
|
178
189
|
const DescIcon = ArrowDownWideNarrowIcon;
|
|
179
190
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
)
|
|
191
|
+
/**
|
|
192
|
+
* `table` is optional: it is only needed to detect multi-column sorting and
|
|
193
|
+
* offer "Clear all sorts". Call sites that build their header inside column
|
|
194
|
+
* definitions (where the table instance isn't yet in scope) omit it and fall
|
|
195
|
+
* back to single-column "Clear sort".
|
|
196
|
+
*/
|
|
197
|
+
export function Sorts<TData, TValue>({
|
|
198
|
+
column,
|
|
199
|
+
table,
|
|
200
|
+
}: {
|
|
201
|
+
column: Column<TData, TValue>;
|
|
202
|
+
table?: Table<TData>;
|
|
203
|
+
}) {
|
|
184
204
|
if (!column.getCanSort()) {
|
|
185
205
|
return null;
|
|
186
206
|
}
|
|
@@ -271,7 +291,11 @@ export function renderSortIcon<TData, TValue>(column: Column<TData, TValue>) {
|
|
|
271
291
|
return <Icon className="h-3 w-3" />;
|
|
272
292
|
}
|
|
273
293
|
|
|
274
|
-
export function
|
|
294
|
+
export function DataType<TData, TValue>({
|
|
295
|
+
column,
|
|
296
|
+
}: {
|
|
297
|
+
column: Column<TData, TValue>;
|
|
298
|
+
}) {
|
|
275
299
|
const dtype: string | undefined = column.columnDef.meta?.dtype;
|
|
276
300
|
if (!dtype) {
|
|
277
301
|
return null;
|
|
@@ -40,3 +40,17 @@ export function getUserColumnVisibilityCounts<TData>(
|
|
|
40
40
|
hidden: userColumns.length - visible,
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
+
|
|
44
|
+
// When columns are clipped server-side, the TanStack instance only holds the
|
|
45
|
+
// rendered subset, so visible/hidden math must use that subset's total. The
|
|
46
|
+
// dataset-wide value is still correct for the no-hidden "N columns" label.
|
|
47
|
+
export function getColumnCountForDisplay<TData>(
|
|
48
|
+
table: Table<TData>,
|
|
49
|
+
datasetTotalColumns: number,
|
|
50
|
+
): { totalColumns: number; hiddenColumns: number } {
|
|
51
|
+
const counts = getUserColumnVisibilityCounts(table);
|
|
52
|
+
return {
|
|
53
|
+
totalColumns: counts.hidden > 0 ? counts.total : datasetTotalColumns,
|
|
54
|
+
hiddenColumns: counts.hidden,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -4,7 +4,7 @@ import z from "zod";
|
|
|
4
4
|
import { rpc } from "@/plugins/core/rpc";
|
|
5
5
|
|
|
6
6
|
export type DownloadAsArgs = (req: {
|
|
7
|
-
format: "csv" | "json" | "parquet";
|
|
7
|
+
format: "csv" | "json" | "parquet" | "tsv";
|
|
8
8
|
}) => Promise<{
|
|
9
9
|
url: string;
|
|
10
10
|
filename: string;
|
|
@@ -15,7 +15,7 @@ export type DownloadAsArgs = (req: {
|
|
|
15
15
|
export const DownloadAsSchema = rpc
|
|
16
16
|
.input(
|
|
17
17
|
z.object({
|
|
18
|
-
format: z.enum(["csv", "json", "parquet"]),
|
|
18
|
+
format: z.enum(["csv", "json", "parquet", "tsv"]),
|
|
19
19
|
}),
|
|
20
20
|
)
|
|
21
21
|
.output(
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
"use no memo";
|
|
3
|
+
|
|
4
|
+
// tanstack/table is not compatible with React compiler
|
|
5
|
+
// https://github.com/TanStack/table/issues/5567
|
|
2
6
|
|
|
3
7
|
import { Fill } from "@marimo-team/react-slotz";
|
|
4
|
-
import type {
|
|
5
|
-
|
|
8
|
+
import type {
|
|
9
|
+
OnChangeFn,
|
|
10
|
+
RowSelectionState,
|
|
11
|
+
Table,
|
|
12
|
+
} from "@tanstack/react-table";
|
|
6
13
|
import { Button } from "@/components/ui/button";
|
|
7
14
|
import { Tabs, TabsContent } from "@/components/ui/tabs";
|
|
8
15
|
import { SlotNames } from "@/core/slots/slots";
|
|
@@ -19,7 +26,7 @@ import { ColumnExplorerPanel } from "../column-explorer-panel/column-explorer";
|
|
|
19
26
|
import { RowViewerPanel } from "../row-viewer-panel/row-viewer";
|
|
20
27
|
import type { FieldTypesWithExternalType, TooManyRows } from "../types";
|
|
21
28
|
|
|
22
|
-
export interface TableExplorerPanelProps {
|
|
29
|
+
export interface TableExplorerPanelProps<TData> {
|
|
23
30
|
// Row viewer props
|
|
24
31
|
rowIdx: number;
|
|
25
32
|
setRowIdx: (rowIdx: number) => void;
|
|
@@ -33,6 +40,7 @@ export interface TableExplorerPanelProps {
|
|
|
33
40
|
previewColumn?: PreviewColumn;
|
|
34
41
|
totalColumns: number;
|
|
35
42
|
tableId: string;
|
|
43
|
+
table: Table<TData>;
|
|
36
44
|
// Visibility flags
|
|
37
45
|
showRowExplorer: boolean;
|
|
38
46
|
showColumnExplorer: boolean;
|
|
@@ -46,7 +54,7 @@ const tabTriggerClassName =
|
|
|
46
54
|
const activeClassName = "text-primary";
|
|
47
55
|
const inactiveClassName = "hover:text-foreground";
|
|
48
56
|
|
|
49
|
-
export
|
|
57
|
+
export function TableExplorerPanel<TData>({
|
|
50
58
|
// Row viewer
|
|
51
59
|
rowIdx,
|
|
52
60
|
setRowIdx,
|
|
@@ -60,13 +68,14 @@ export const TableExplorerPanel: React.FC<TableExplorerPanelProps> = ({
|
|
|
60
68
|
previewColumn,
|
|
61
69
|
totalColumns,
|
|
62
70
|
tableId,
|
|
71
|
+
table,
|
|
63
72
|
// Visibility
|
|
64
73
|
showRowExplorer,
|
|
65
74
|
showColumnExplorer,
|
|
66
75
|
// Tab state
|
|
67
76
|
activeTab,
|
|
68
77
|
onTabChange,
|
|
69
|
-
})
|
|
78
|
+
}: TableExplorerPanelProps<TData>) {
|
|
70
79
|
const showTabs = showRowExplorer && showColumnExplorer;
|
|
71
80
|
|
|
72
81
|
const rowViewer = (
|
|
@@ -89,6 +98,7 @@ export const TableExplorerPanel: React.FC<TableExplorerPanelProps> = ({
|
|
|
89
98
|
totalRows={totalRows}
|
|
90
99
|
totalColumns={totalColumns}
|
|
91
100
|
tableId={tableId}
|
|
101
|
+
table={table}
|
|
92
102
|
/>
|
|
93
103
|
);
|
|
94
104
|
|
|
@@ -158,4 +168,4 @@ export const TableExplorerPanel: React.FC<TableExplorerPanelProps> = ({
|
|
|
158
168
|
</TabsContent>
|
|
159
169
|
</Tabs>
|
|
160
170
|
);
|
|
161
|
-
}
|
|
171
|
+
}
|
|
@@ -313,6 +313,26 @@ describe("sqlCode", () => {
|
|
|
313
313
|
);
|
|
314
314
|
});
|
|
315
315
|
|
|
316
|
+
it("should preserve dots inside quoted schema names", () => {
|
|
317
|
+
const sqlTableContext: SQLTableContext = {
|
|
318
|
+
engine: "postgres",
|
|
319
|
+
schema: "analytics.events",
|
|
320
|
+
defaultSchema: "public",
|
|
321
|
+
defaultDatabase: "mydb",
|
|
322
|
+
database: "remote",
|
|
323
|
+
dialect: "postgres",
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const result = sqlCode({
|
|
327
|
+
table: mockTable,
|
|
328
|
+
columnName: mockColumn.name,
|
|
329
|
+
sqlTableContext,
|
|
330
|
+
});
|
|
331
|
+
expect(result).toBe(
|
|
332
|
+
'_df = mo.sql(f"""\nSELECT "email" FROM "remote"."analytics.events"."users" LIMIT 100\n""", engine=postgres)',
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
|
|
316
336
|
it("should not quote * column name", () => {
|
|
317
337
|
const sqlTableContext: SQLTableContext = {
|
|
318
338
|
engine: "postgres",
|
|
@@ -334,6 +354,68 @@ describe("sqlCode", () => {
|
|
|
334
354
|
});
|
|
335
355
|
});
|
|
336
356
|
|
|
357
|
+
describe("Dremio dialect", () => {
|
|
358
|
+
it("should quote reserved column names and table path parts", () => {
|
|
359
|
+
const sqlTableContext: SQLTableContext = {
|
|
360
|
+
engine: "dremio_conn",
|
|
361
|
+
schema: "operations",
|
|
362
|
+
defaultSchema: "",
|
|
363
|
+
defaultDatabase: "",
|
|
364
|
+
database: "lakehouse",
|
|
365
|
+
dialect: "dremio",
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const result = sqlCode({
|
|
369
|
+
table: { ...mockTable, name: "shipments" as const },
|
|
370
|
+
columnName: "order",
|
|
371
|
+
sqlTableContext,
|
|
372
|
+
});
|
|
373
|
+
expect(result).toBe(
|
|
374
|
+
'_df = mo.sql(f"""\nSELECT "order" FROM "lakehouse"."operations"."shipments" LIMIT 100\n""", engine=dremio_conn)',
|
|
375
|
+
);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("should not quote * column name", () => {
|
|
379
|
+
const sqlTableContext: SQLTableContext = {
|
|
380
|
+
engine: "dremio_conn",
|
|
381
|
+
schema: "operations",
|
|
382
|
+
defaultSchema: "",
|
|
383
|
+
defaultDatabase: "",
|
|
384
|
+
database: "lakehouse",
|
|
385
|
+
dialect: "dremio",
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const result = sqlCode({
|
|
389
|
+
table: { ...mockTable, name: "customers" as const },
|
|
390
|
+
columnName: "*",
|
|
391
|
+
sqlTableContext,
|
|
392
|
+
});
|
|
393
|
+
expect(result).toBe(
|
|
394
|
+
'_df = mo.sql(f"""\nSELECT * FROM "lakehouse"."operations"."customers" LIMIT 100\n""", engine=dremio_conn)',
|
|
395
|
+
);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("should preserve dots inside quoted schema names", () => {
|
|
399
|
+
const sqlTableContext: SQLTableContext = {
|
|
400
|
+
engine: "dremio_conn",
|
|
401
|
+
schema: "samples.dremio.com",
|
|
402
|
+
defaultSchema: "",
|
|
403
|
+
defaultDatabase: "",
|
|
404
|
+
database: "Samples",
|
|
405
|
+
dialect: "dremio",
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const result = sqlCode({
|
|
409
|
+
table: { ...mockTable, name: "airlines" as const },
|
|
410
|
+
columnName: "*",
|
|
411
|
+
sqlTableContext,
|
|
412
|
+
});
|
|
413
|
+
expect(result).toBe(
|
|
414
|
+
'_df = mo.sql(f"""\nSELECT * FROM "Samples"."samples.dremio.com"."airlines" LIMIT 100\n""", engine=dremio_conn)',
|
|
415
|
+
);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
337
419
|
describe("fallback behavior", () => {
|
|
338
420
|
it("should use default formatter for unknown dialect", () => {
|
|
339
421
|
const sqlTableContext: SQLTableContext = {
|
|
@@ -15,9 +15,9 @@ export function isSchemaless(schemaName: string) {
|
|
|
15
15
|
|
|
16
16
|
interface SqlCodeFormatter {
|
|
17
17
|
/**
|
|
18
|
-
* Format the table
|
|
18
|
+
* Format the table path based on dialect-specific rules
|
|
19
19
|
*/
|
|
20
|
-
|
|
20
|
+
formatTablePath: (tablePath: string[]) => string;
|
|
21
21
|
/**
|
|
22
22
|
* Format the SELECT clause
|
|
23
23
|
*/
|
|
@@ -25,7 +25,7 @@ interface SqlCodeFormatter {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const defaultFormatter: SqlCodeFormatter = {
|
|
28
|
-
|
|
28
|
+
formatTablePath: (tablePath: string[]) => tablePath.join("."),
|
|
29
29
|
formatSelectClause: (columnName: string, tableName: string) =>
|
|
30
30
|
`SELECT ${columnName} FROM ${tableName} LIMIT 100`,
|
|
31
31
|
};
|
|
@@ -41,7 +41,8 @@ function getFormatter(dialect: string): SqlCodeFormatter {
|
|
|
41
41
|
const quote = BigQueryDialect.spec.identifierQuotes;
|
|
42
42
|
return {
|
|
43
43
|
// BigQuery uses backticks for identifiers
|
|
44
|
-
|
|
44
|
+
formatTablePath: (tablePath: string[]) =>
|
|
45
|
+
`${quote}${tablePath.join(".")}${quote}`,
|
|
45
46
|
formatSelectClause: defaultFormatter.formatSelectClause,
|
|
46
47
|
};
|
|
47
48
|
}
|
|
@@ -49,7 +50,7 @@ function getFormatter(dialect: string): SqlCodeFormatter {
|
|
|
49
50
|
case "sqlserver":
|
|
50
51
|
case "microsoft sql server":
|
|
51
52
|
return {
|
|
52
|
-
|
|
53
|
+
formatTablePath: defaultFormatter.formatTablePath,
|
|
53
54
|
formatSelectClause: (columnName: string, tableName: string) =>
|
|
54
55
|
`SELECT TOP 100 ${columnName} FROM ${tableName}`,
|
|
55
56
|
};
|
|
@@ -57,12 +58,11 @@ function getFormatter(dialect: string): SqlCodeFormatter {
|
|
|
57
58
|
case "postgres":
|
|
58
59
|
case "postgresql":
|
|
59
60
|
case "duckdb":
|
|
61
|
+
case "dremio":
|
|
60
62
|
// Quote column and table names to avoid raising errors on weird characters
|
|
61
63
|
return {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return parts.map((part) => `"${part}"`).join(".");
|
|
65
|
-
},
|
|
64
|
+
formatTablePath: (tablePath: string[]) =>
|
|
65
|
+
tablePath.map((part) => `"${part}"`).join("."),
|
|
66
66
|
formatSelectClause: (columnName: string, tableName: string) =>
|
|
67
67
|
`SELECT ${columnName === "*" ? "*" : `"${columnName}"`} FROM ${tableName} LIMIT 100`,
|
|
68
68
|
};
|
|
@@ -114,26 +114,27 @@ export function sqlCode({
|
|
|
114
114
|
database,
|
|
115
115
|
dialect,
|
|
116
116
|
} = sqlTableContext;
|
|
117
|
-
|
|
117
|
+
const tablePath = [table.name];
|
|
118
118
|
|
|
119
119
|
// Set the fully qualified table name based on schema and database
|
|
120
120
|
if (isSchemaless(schema)) {
|
|
121
|
-
|
|
122
|
-
database
|
|
121
|
+
if (database !== defaultDatabase) {
|
|
122
|
+
tablePath.unshift(database);
|
|
123
|
+
}
|
|
123
124
|
} else {
|
|
124
125
|
// Include schema if it's not the default schema
|
|
125
126
|
if (schema !== defaultSchema) {
|
|
126
|
-
|
|
127
|
+
tablePath.unshift(schema);
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
// Include database if it's not the default database
|
|
130
131
|
if (database !== defaultDatabase) {
|
|
131
|
-
|
|
132
|
+
tablePath.unshift(database);
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
const formatter = getFormatter(dialect);
|
|
136
|
-
const formattedTableName = formatter.
|
|
137
|
+
const formattedTableName = formatter.formatTablePath(tablePath);
|
|
137
138
|
const selectClause = formatter.formatSelectClause(
|
|
138
139
|
columnName,
|
|
139
140
|
formattedTableName,
|
|
@@ -1,69 +1,10 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import { ArrowRightSquareIcon } from "lucide-react";
|
|
4
|
-
import { API } from "@/core/network/api";
|
|
5
|
-
import { Banner } from "@/plugins/impl/common/error-banner";
|
|
6
|
-
import { prettyError } from "@/utils/errors";
|
|
7
|
-
import { reloadSafe } from "@/utils/reload-safe";
|
|
8
|
-
import { Button } from "../ui/button";
|
|
9
|
-
import { toast } from "../ui/use-toast";
|
|
10
|
-
|
|
11
3
|
interface DisconnectedProps {
|
|
12
4
|
reason: string;
|
|
13
|
-
canTakeover: boolean | undefined;
|
|
14
5
|
}
|
|
15
6
|
|
|
16
|
-
export const Disconnected = ({
|
|
17
|
-
reason,
|
|
18
|
-
canTakeover = false,
|
|
19
|
-
}: DisconnectedProps) => {
|
|
20
|
-
const handleTakeover = async () => {
|
|
21
|
-
try {
|
|
22
|
-
const searchParams = new URL(window.location.href).searchParams;
|
|
23
|
-
await API.post(`/kernel/takeover?${searchParams.toString()}`, {});
|
|
24
|
-
|
|
25
|
-
// Refresh the page to reconnect
|
|
26
|
-
reloadSafe();
|
|
27
|
-
} catch (error) {
|
|
28
|
-
toast({
|
|
29
|
-
title: "Failed to take over session",
|
|
30
|
-
description: prettyError(error),
|
|
31
|
-
variant: "danger",
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
if (canTakeover) {
|
|
37
|
-
return (
|
|
38
|
-
<div className="flex justify-center">
|
|
39
|
-
<Banner
|
|
40
|
-
kind="info"
|
|
41
|
-
className="mt-10 flex flex-col rounded p-3 max-w-[800px] mx-4"
|
|
42
|
-
>
|
|
43
|
-
<div className="flex justify-between">
|
|
44
|
-
<span className="font-bold text-xl flex items-center mb-2">
|
|
45
|
-
Notebook already connected
|
|
46
|
-
</span>
|
|
47
|
-
</div>
|
|
48
|
-
<div className="flex justify-between items-end text-base gap-20">
|
|
49
|
-
<span>{reason}</span>
|
|
50
|
-
{canTakeover && (
|
|
51
|
-
<Button
|
|
52
|
-
onClick={handleTakeover}
|
|
53
|
-
variant="outline"
|
|
54
|
-
data-testid="takeover-button"
|
|
55
|
-
className="shrink-0"
|
|
56
|
-
>
|
|
57
|
-
<ArrowRightSquareIcon className="w-4 h-4 mr-2" />
|
|
58
|
-
Take over session
|
|
59
|
-
</Button>
|
|
60
|
-
)}
|
|
61
|
-
</div>
|
|
62
|
-
</Banner>
|
|
63
|
-
</div>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
7
|
+
export const Disconnected = ({ reason }: DisconnectedProps) => {
|
|
67
8
|
return (
|
|
68
9
|
<div className="font-mono text-center text-base text-(--red-11)">
|
|
69
10
|
<p>{reason}</p>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { createStore, Provider } from "jotai";
|
|
4
|
+
import { describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
6
|
+
import { layoutStateAtom } from "@/core/layout/layout";
|
|
7
|
+
import { kioskModeAtom, viewStateAtom } from "@/core/mode";
|
|
8
|
+
import { API } from "@/core/network/api";
|
|
9
|
+
import { ViewerBanner } from "../viewer-banner";
|
|
10
|
+
|
|
11
|
+
describe("ViewerBanner", () => {
|
|
12
|
+
it("renders nothing when not in kiosk mode", () => {
|
|
13
|
+
const store = createStore();
|
|
14
|
+
store.set(kioskModeAtom, false);
|
|
15
|
+
const { container } = render(
|
|
16
|
+
<Provider store={store}>
|
|
17
|
+
<TooltipProvider>
|
|
18
|
+
<ViewerBanner />
|
|
19
|
+
</TooltipProvider>
|
|
20
|
+
</Provider>,
|
|
21
|
+
);
|
|
22
|
+
expect(container).toBeEmptyDOMElement();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("renders nothing for an intentional kiosk client (?kiosk=true)", () => {
|
|
26
|
+
const store = createStore();
|
|
27
|
+
store.set(kioskModeAtom, true);
|
|
28
|
+
window.history.pushState({}, "", "/?kiosk=true");
|
|
29
|
+
try {
|
|
30
|
+
const { container } = render(
|
|
31
|
+
<Provider store={store}>
|
|
32
|
+
<TooltipProvider>
|
|
33
|
+
<ViewerBanner />
|
|
34
|
+
</TooltipProvider>
|
|
35
|
+
</Provider>,
|
|
36
|
+
);
|
|
37
|
+
expect(container).toBeEmptyDOMElement();
|
|
38
|
+
} finally {
|
|
39
|
+
window.history.pushState({}, "", "/");
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("renders nothing in a non-vertical layout (grid/slides)", () => {
|
|
44
|
+
const store = createStore();
|
|
45
|
+
store.set(kioskModeAtom, true);
|
|
46
|
+
store.set(layoutStateAtom, { selectedLayout: "grid", layoutData: {} });
|
|
47
|
+
const { container } = render(
|
|
48
|
+
<Provider store={store}>
|
|
49
|
+
<TooltipProvider>
|
|
50
|
+
<ViewerBanner />
|
|
51
|
+
</TooltipProvider>
|
|
52
|
+
</Provider>,
|
|
53
|
+
);
|
|
54
|
+
expect(container).toBeEmptyDOMElement();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("renders nothing in present mode", () => {
|
|
58
|
+
const store = createStore();
|
|
59
|
+
store.set(kioskModeAtom, true);
|
|
60
|
+
store.set(viewStateAtom, { mode: "present", cellAnchor: null });
|
|
61
|
+
const { container } = render(
|
|
62
|
+
<Provider store={store}>
|
|
63
|
+
<TooltipProvider>
|
|
64
|
+
<ViewerBanner />
|
|
65
|
+
</TooltipProvider>
|
|
66
|
+
</Provider>,
|
|
67
|
+
);
|
|
68
|
+
expect(container).toBeEmptyDOMElement();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("shows take over and posts without reload when viewing", () => {
|
|
72
|
+
const store = createStore();
|
|
73
|
+
store.set(kioskModeAtom, true);
|
|
74
|
+
const post = vi.spyOn(API, "post").mockResolvedValue({} as never);
|
|
75
|
+
render(
|
|
76
|
+
<Provider store={store}>
|
|
77
|
+
<TooltipProvider>
|
|
78
|
+
<ViewerBanner />
|
|
79
|
+
</TooltipProvider>
|
|
80
|
+
</Provider>,
|
|
81
|
+
);
|
|
82
|
+
const button = screen.getByTestId("takeover-button");
|
|
83
|
+
button.click();
|
|
84
|
+
expect(post).toHaveBeenCalledWith(
|
|
85
|
+
expect.stringContaining("/kernel/takeover"),
|
|
86
|
+
{},
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
import { switchLanguage } from "@/core/codemirror/language/extension";
|
|
49
49
|
import { MARKDOWN_INITIAL_HIDE_CODE } from "@/core/codemirror/language/languages/markdown";
|
|
50
50
|
import {
|
|
51
|
-
|
|
51
|
+
aiFeaturesEnabledAtom,
|
|
52
52
|
appWidthAtom,
|
|
53
53
|
autoInstantiateAtom,
|
|
54
54
|
} from "@/core/config/config";
|
|
@@ -100,7 +100,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
|
|
|
100
100
|
const deleteCell = useDeleteCellCallback();
|
|
101
101
|
const { openModal } = useImperativeModal();
|
|
102
102
|
const setAiCompletionCell = useSetAtom(aiCompletionCellAtom);
|
|
103
|
-
const
|
|
103
|
+
const aiFeaturesEnabled = useAtomValue(aiFeaturesEnabledAtom);
|
|
104
104
|
const autoInstantiate = useAtomValue(autoInstantiateAtom);
|
|
105
105
|
const kioskMode = useAtomValue(kioskModeAtom);
|
|
106
106
|
const appWidth = useAtomValue(appWidthAtom);
|
|
@@ -162,7 +162,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
|
|
|
162
162
|
{
|
|
163
163
|
icon: <SparklesIcon size={13} strokeWidth={1.5} />,
|
|
164
164
|
label: "Refactor with AI",
|
|
165
|
-
hidden: !
|
|
165
|
+
hidden: !aiFeaturesEnabled,
|
|
166
166
|
handle: () => {
|
|
167
167
|
setAiCompletionCell((current) =>
|
|
168
168
|
current?.cellId === cellId ? null : { cellId },
|
|
@@ -142,9 +142,10 @@ export function useNotebookActions() {
|
|
|
142
142
|
const { selectedLayout } = useLayoutState();
|
|
143
143
|
const { setLayoutView } = useLayoutActions();
|
|
144
144
|
const togglePresenting = useTogglePresenting();
|
|
145
|
-
// Fallback: if sharing is undefined,
|
|
145
|
+
// Fallback: if sharing is undefined, all options are enabled by default
|
|
146
146
|
const sharingHtmlEnabled = resolvedConfig.sharing?.html ?? true;
|
|
147
147
|
const sharingWasmEnabled = resolvedConfig.sharing?.wasm ?? true;
|
|
148
|
+
const sharingMolabEnabled = resolvedConfig.sharing?.molab ?? true;
|
|
148
149
|
|
|
149
150
|
// Server-side PDF export is always available outside WASM.
|
|
150
151
|
// Browser print fallback is used in WASM.
|
|
@@ -360,7 +361,8 @@ export function useNotebookActions() {
|
|
|
360
361
|
icon: <Share2Icon size={14} strokeWidth={1.5} />,
|
|
361
362
|
label: "Share",
|
|
362
363
|
handle: NOOP_HANDLER,
|
|
363
|
-
hidden:
|
|
364
|
+
hidden:
|
|
365
|
+
!sharingHtmlEnabled && !sharingWasmEnabled && !sharingMolabEnabled,
|
|
364
366
|
dropdown: [
|
|
365
367
|
{
|
|
366
368
|
icon: <GlobeIcon size={14} strokeWidth={1.5} />,
|
|
@@ -387,6 +389,7 @@ export function useNotebookActions() {
|
|
|
387
389
|
{
|
|
388
390
|
icon: <MarimoPlusIcon size={14} strokeWidth={1.5} />,
|
|
389
391
|
label: "Create molab notebook",
|
|
392
|
+
hidden: !sharingMolabEnabled,
|
|
390
393
|
handle: async () => {
|
|
391
394
|
const code = await readCode();
|
|
392
395
|
const url = createShareableLink({
|