@marimo-team/frontend 0.24.1-dev41 → 0.24.1-dev43
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/assets/{CellStatus-CfNQG_fZ.js → CellStatus-B6CWKORY.js} +1 -1
- package/dist/assets/ConnectedDataExplorerComponent-DZnvpjue.js +1 -0
- package/dist/assets/JsonOutput-BI2qFHVr.js +50 -0
- package/dist/assets/MarimoErrorOutput-BXh29HCp.js +6 -0
- package/dist/assets/RenderHTML-COTZYPO7.js +1 -0
- package/dist/assets/RunButton-DTUzITFm.js +1 -0
- package/dist/assets/VisuallyHidden-DurJ_JBW.js +1 -0
- package/dist/assets/{add-cell-with-ai-CGBEoX6T.js → add-cell-with-ai-B0RdHenk.js} +19 -19
- package/dist/assets/add-connection-dialog-CcnJ2LkX.js +166 -0
- package/dist/assets/agent-panel-PB0I1dIC.js +286 -0
- package/dist/assets/ai-model-dropdown-DjQy2bo-.js +3 -0
- package/dist/assets/app-config-button-COFLeJCU.js +1 -0
- package/dist/assets/{blob-ChElOQvm.js → blob-BBiZd3vX.js} +1 -1
- package/dist/assets/{cache-panel-CLfGxru6.js → cache-panel-MAXVmKkG.js} +1 -1
- package/dist/assets/cell-editor-5wcJHF0s.js +24 -0
- package/dist/assets/cell-link-BbAJokoX.js +1 -0
- package/dist/assets/cells-B1S_6FG3.js +227 -0
- package/dist/assets/chat-display-ClG9MRI_.js +1 -0
- package/dist/assets/chat-panel-BRoqCSCR.js +4 -0
- package/dist/assets/chat-ui-CNvkb287.js +4 -0
- package/dist/assets/circle--ysFeW9z.js +1 -0
- package/dist/assets/column-preview-D6cIOnWQ.js +1 -0
- package/dist/assets/command-palette-DtZy0Am-.js +1 -0
- package/dist/assets/common-BGLbVHCS.js +1 -0
- package/dist/assets/components-BAvTsivu.js +1 -0
- package/dist/assets/config-DcB2swfZ.js +1 -0
- package/dist/assets/context-Do8M9TDF.js +1 -0
- package/dist/assets/{datasource-C0_eJO-0.js → datasource-BrSYgi6C.js} +2 -2
- package/dist/assets/dependency-graph-panel-C13q-nga.js +5 -0
- package/dist/assets/{documentation-panel-CxjnCPq6.js → documentation-panel-CtQqNEQw.js} +1 -1
- package/dist/assets/{download-CxzOE8FX.js → download-CIdXYNHv.js} +1 -1
- package/dist/assets/dropdown-menu-CqNabM1f.js +1 -0
- package/dist/assets/edit-page-jAyDC21z.js +9 -0
- package/dist/assets/{error-entries-DCRQWi_N.js → error-entries-DEjvzkum.js} +1 -1
- package/dist/assets/{error-panel-Bx5cPw6N.js → error-panel-BvrDwUv8.js} +1 -1
- package/dist/assets/file-explorer-panel-DYujxxK-.js +1 -0
- package/dist/assets/{file-icons-DxXY84Jn.js → file-icons-Bo1Ewf1W.js} +1 -1
- package/dist/assets/file-name-input-BDGq0-ac.js +3 -0
- package/dist/assets/{floating-outline-gjfcwmyU.js → floating-outline-CNsG18LK.js} +1 -1
- package/dist/assets/{gallery-page-BI3aTl8u.js → gallery-page-4dZlIWMe.js} +1 -1
- package/dist/assets/{glide-data-editor-CrWGp7DP.js → glide-data-editor-BQNVc0zc.js} +2 -2
- package/dist/assets/home-page-CFAldshJ.js +4 -0
- package/dist/assets/html-to-image-DMn_7CCA.js +2 -0
- package/dist/assets/index-0VrRpta0.css +2 -0
- package/dist/assets/index-BBPHmHdW.js +29 -0
- package/dist/assets/input-DeyycFtq.js +7 -0
- package/dist/assets/{layout-BQjGHHu-.js → layout-Ddsz1eHh.js} +5 -5
- package/dist/assets/{logs-panel-D1RUMXL1.js → logs-panel-BSD1GETR.js} +1 -1
- package/dist/assets/markdown-renderer-DIXiKP4m.js +2 -0
- package/dist/assets/{mermaid-DtNubw9S.js → mermaid-CwvTqKGz.js} +2 -2
- package/dist/assets/{name-cell-input-DSKoNRk3.js → name-cell-input-zqypwqoZ.js} +1 -1
- package/dist/assets/{outline-panel-UIygpaO3.js → outline-panel-C5GAyqII.js} +1 -1
- package/dist/assets/packages-panel-CYLyGihe.js +1 -0
- package/dist/assets/{pair-with-agent-modal-Pl5cv7Gv.js → pair-with-agent-modal-CBIL9_6g.js} +2 -2
- package/dist/assets/panel-accordion-state-DmV3OgfX.js +1 -0
- package/dist/assets/panels-D53vWR00.js +4 -0
- package/dist/assets/{process-output-BWDSbk5d.js → process-output-DNhz9LLW.js} +1 -1
- package/dist/assets/{readonly-code-display-Bbnq-khe.js → readonly-code-display-CKT-gv1m.js} +1 -1
- package/dist/assets/readonly-python-code-De0vvuWh.js +1 -0
- package/dist/assets/{renderShortcut-Dj3NbkoC.js → renderShortcut-D9XYVNe3.js} +1 -1
- package/dist/assets/{reveal-component-Bgmfon3f.js → reveal-component-C0L5MU2W.js} +4 -4
- package/dist/assets/run-page-DXZapRtY.js +1 -0
- package/dist/assets/scratchpad-panel-ja6doGoa.js +1 -0
- package/dist/assets/secrets-panel-B7Rcwc2I.js +1 -0
- package/dist/assets/select-BUEX2Vfl.js +1 -0
- package/dist/assets/session-panel-ftyqfsYy.js +1 -0
- package/dist/assets/{skeleton-DGs-LrlZ.js → skeleton-CxFczTPo.js} +1 -1
- package/dist/assets/snippets-panel-BZ254lxN.js +1 -0
- package/dist/assets/{state-WVnIPIsw.js → state-MlBpc9qi.js} +3 -3
- package/dist/assets/{state-N_csMEH1.js → state-llVv0lIh.js} +1 -1
- package/dist/assets/switch-CBR-ykv3.js +1 -0
- package/dist/assets/tabs-c6mpccpH.js +1 -0
- package/dist/assets/{terminal-aLrVlOf6.js → terminal-OC_FnEas.js} +3 -3
- package/dist/assets/tracing-C0FGWieO.js +1 -0
- package/dist/assets/tracing-panel-LeIOi1Tv.js +2 -0
- package/dist/assets/{tree-actions-Ctf5W0DR.js → tree-actions-CrWpuExP.js} +1 -1
- package/dist/assets/useAddCell-DIdbaU70.js +1 -0
- package/dist/assets/useCellActionButton-DXyUqFdJ.js +1 -0
- package/dist/assets/{useDateFormatter-CfPcCT-J.js → useDateFormatter-BiqAQ-zI.js} +1 -1
- package/dist/assets/{useDeleteCell-6wcjnAMK.js → useDeleteCell-CSCMg7Up.js} +1 -1
- package/dist/assets/useDependencyPanelTab-Bt7aBDpS.js +1 -0
- package/dist/assets/useNotebookActions-ClT4GmnB.js +10 -0
- package/dist/assets/useNumberFormatter-DHJ9s6Vk.js +1 -0
- package/dist/assets/useRunCells-DHnbXALT.js +1 -0
- package/dist/assets/{useSplitCell-oIRu7Qlj.js → useSplitCell-CxlsGCB_.js} +1 -1
- package/dist/assets/{vega-component-D4OKyVdb.js → vega-component-CzgE5HEs.js} +1 -1
- package/dist/assets/write-secret-modal-BG_B6y1P.js +2 -0
- package/dist/index.html +57 -71
- package/package.json +1 -1
- package/src/components/chat/acp/agent-panel.tsx +4 -6
- package/src/components/data-table/columns.tsx +130 -132
- package/src/components/datasources/datasources.tsx +16 -17
- package/src/components/editor/actions/useNotebookActions.tsx +50 -0
- package/src/components/editor/chrome/panels/components.tsx +1 -1
- package/src/components/editor/connections/__tests__/add-connection-button.test.tsx +129 -0
- package/src/components/editor/connections/add-connection-button.tsx +132 -0
- package/src/components/editor/connections/components.tsx +11 -0
- package/src/components/editor/connections/quick-add-data-sources.tsx +11 -11
- package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +109 -1
- package/src/components/storage/storage-inspector.tsx +15 -17
- package/src/core/codemirror/language/panel/sql.test.tsx +69 -0
- package/src/core/codemirror/language/panel/sql.tsx +81 -4
- package/src/core/codemirror/markdown/commands.ts +1 -1
- package/src/core/codemirror/markdown/completions.ts +15 -17
- package/dist/assets/ConnectedDataExplorerComponent-88Ojqyhx.js +0 -1
- package/dist/assets/DeferredRequestRegistry-C-SpvRCH.js +0 -1
- package/dist/assets/ImperativeModal-68zj0lxy.js +0 -1
- package/dist/assets/JsonOutput-pCdkYz4X.js +0 -50
- package/dist/assets/MarimoErrorOutput-b7bFhjgD.js +0 -6
- package/dist/assets/RSPContexts-D2JcYZo2.js +0 -1
- package/dist/assets/RenderHTML-BUuDW4yZ.js +0 -1
- package/dist/assets/RunButton-4MKgVYXU.js +0 -1
- package/dist/assets/SSRProvider-C_WMmqnS.js +0 -1
- package/dist/assets/add-connection-dialog-BcLKuMHt.js +0 -166
- package/dist/assets/agent-panel-BVyaNGyG.js +0 -286
- package/dist/assets/ai-model-dropdown-C_IoSmhR.js +0 -3
- package/dist/assets/app-config-button-BvpunAmJ.js +0 -1
- package/dist/assets/cell-editor-pKuytlrm.js +0 -24
- package/dist/assets/cell-link-D9d1y67_.js +0 -1
- package/dist/assets/cells-BdoPd1mK.js +0 -226
- package/dist/assets/chat-display-CbsB82vm.js +0 -1
- package/dist/assets/chat-panel-DCPKH1gP.js +0 -4
- package/dist/assets/chat-ui-BwLG6iSS.js +0 -4
- package/dist/assets/circle-plus-cm-FRu9O.js +0 -1
- package/dist/assets/column-preview-fku2dKeP.js +0 -1
- package/dist/assets/command-Cvmdzx0n.js +0 -1
- package/dist/assets/command-palette-BJFR8wKv.js +0 -1
- package/dist/assets/common-CQ9OqrFS.js +0 -1
- package/dist/assets/components-CaOG3LOY.js +0 -1
- package/dist/assets/config-xlrIxOZJ.js +0 -1
- package/dist/assets/context-Bf2wPdef.js +0 -1
- package/dist/assets/data-source-discovery-BsrDlL04.js +0 -1
- package/dist/assets/dependency-graph-panel-CxZJ5e7D.js +0 -5
- package/dist/assets/dialog-EjSc4yoH.js +0 -1
- package/dist/assets/dropdown-menu-BpI5klSq.js +0 -1
- package/dist/assets/edit-page-DwKN7vIP.js +0 -9
- package/dist/assets/field-YoG_gQAY.js +0 -1
- package/dist/assets/file-explorer-panel-CRrPqu3s.js +0 -1
- package/dist/assets/file-name-input-uFNWUWCc.js +0 -3
- package/dist/assets/focus-DgNLvj0o.js +0 -1
- package/dist/assets/form-CD2uATFu.js +0 -2
- package/dist/assets/form-GIq5QCAI.js +0 -1
- package/dist/assets/home-page-CYg3rQCB.js +0 -4
- package/dist/assets/html-to-image-Bt7Ld74W.js +0 -2
- package/dist/assets/index-CrsPwq0f.js +0 -29
- package/dist/assets/index-H2TMW127.css +0 -2
- package/dist/assets/input-BuOxQ3Wy.js +0 -1
- package/dist/assets/label-B67m1YH7.js +0 -1
- package/dist/assets/links-C7oaxPJK.js +0 -1
- package/dist/assets/markdown-renderer-C8IbrNw3.js +0 -2
- package/dist/assets/packages-panel-CIwyYf45.js +0 -1
- package/dist/assets/panel-accordion-state-DAAi8MPY.js +0 -1
- package/dist/assets/panels-DcguQKjE.js +0 -4
- package/dist/assets/popover-CgOo22ko.js +0 -1
- package/dist/assets/radio-group-DZoEgN7k.js +0 -1
- package/dist/assets/readonly-python-code-CyfabMFb.js +0 -1
- package/dist/assets/refresh-ccw-DrVG5f2L.js +0 -1
- package/dist/assets/request-registry-mNu7sXOU.js +0 -1
- package/dist/assets/run-page-Buh6gERG.js +0 -1
- package/dist/assets/scratchpad-panel-soCf5igY.js +0 -1
- package/dist/assets/secrets-panel-CCb8F6PC.js +0 -1
- package/dist/assets/select-DwqnFsyg.js +0 -1
- package/dist/assets/session-panel-BO4lOJc-.js +0 -1
- package/dist/assets/snippets-panel-CulFiQ0V.js +0 -1
- package/dist/assets/sparkles-30wHhAF5.js +0 -1
- package/dist/assets/state-B-K0k6lb.js +0 -1
- package/dist/assets/switch-VzdUoXkf.js +0 -1
- package/dist/assets/textarea-mD3C73jw.js +0 -1
- package/dist/assets/tracing-B_Qlv38-.js +0 -1
- package/dist/assets/tracing-panel--KVMPYqh.js +0 -2
- package/dist/assets/useCellActionButton-BDVM-sW-.js +0 -1
- package/dist/assets/useDependencyPanelTab-D4TnSeLa.js +0 -1
- package/dist/assets/useNotebookActions-gEpi1TfO.js +0 -10
- package/dist/assets/useNumberFormatter-BmUQe2Pf.js +0 -1
- package/dist/assets/usePress-CwOqeEX3.js +0 -7
- package/dist/assets/useRunCells-DP6cAHq0.js +0 -1
- package/dist/assets/write-secret-modal-dplU5PG8.js +0 -2
- package/dist/assets/x-BgkoSHM-.js +0 -1
|
@@ -73,6 +73,7 @@ import { useRequestClient } from "@/core/network/requests";
|
|
|
73
73
|
import { useFilename } from "@/core/saving/filename";
|
|
74
74
|
import { createShareableLink } from "@/core/wasm/share";
|
|
75
75
|
import { isWasm } from "@/core/wasm/utils";
|
|
76
|
+
import { useDetectedDataSources } from "@/hooks/useDataSourceDiscovery";
|
|
76
77
|
import { copyToClipboard } from "@/utils/copy";
|
|
77
78
|
import { Objects } from "@/utils/objects";
|
|
78
79
|
import { Strings } from "@/utils/strings";
|
|
@@ -81,6 +82,7 @@ import { useRunAllCells } from "../cell/useRunCells";
|
|
|
81
82
|
import { useChromeActions, useChromeState } from "../chrome/state";
|
|
82
83
|
import { isPanelHidden, PANELS } from "../chrome/types";
|
|
83
84
|
import { AddConnectionDialogContent } from "../connections/add-connection-dialog";
|
|
85
|
+
import { useAddDetectedDataSource } from "../connections/components";
|
|
84
86
|
import { keyboardShortcutsAtom } from "../controls/keyboard-shortcuts";
|
|
85
87
|
import { commandPaletteAtom } from "../controls/state";
|
|
86
88
|
import { displayLayoutName, getLayoutIcon } from "../renderers/layout-select";
|
|
@@ -135,6 +137,9 @@ export function useNotebookActions({
|
|
|
135
137
|
const setKeyboardShortcutsOpen = useSetAtom(keyboardShortcutsAtom);
|
|
136
138
|
const setExportOptions = useSetAtom(exportOptionsAtom);
|
|
137
139
|
const { readCode, saveCellConfig } = useRequestClient();
|
|
140
|
+
const detectedDatabaseSources = useDetectedDataSources("database");
|
|
141
|
+
const detectedStorageSources = useDetectedDataSources("storage");
|
|
142
|
+
const addDetectedDataSource = useAddDetectedDataSource();
|
|
138
143
|
|
|
139
144
|
const hasDisabledCells = useAtomValue(hasDisabledCellsAtom);
|
|
140
145
|
const canUndoDeletes = useAtomValue(canUndoDeletesAtom);
|
|
@@ -455,6 +460,27 @@ export function useNotebookActions({
|
|
|
455
460
|
handle: () => {
|
|
456
461
|
openModal(<AddConnectionDialogContent onClose={closeModal} />);
|
|
457
462
|
},
|
|
463
|
+
dropdown:
|
|
464
|
+
detectedDatabaseSources.length === 0
|
|
465
|
+
? undefined
|
|
466
|
+
: [
|
|
467
|
+
...detectedDatabaseSources.map((source) => ({
|
|
468
|
+
icon: <SparklesIcon size={14} strokeWidth={1.5} />,
|
|
469
|
+
label: `Add ${source.displayName}`,
|
|
470
|
+
description: "Detected in your environment",
|
|
471
|
+
handle: () => addDetectedDataSource(source),
|
|
472
|
+
})),
|
|
473
|
+
{
|
|
474
|
+
divider: true,
|
|
475
|
+
icon: <DatabaseIcon size={14} strokeWidth={1.5} />,
|
|
476
|
+
label: "Browse all connections",
|
|
477
|
+
handle: () => {
|
|
478
|
+
openModal(
|
|
479
|
+
<AddConnectionDialogContent onClose={closeModal} />,
|
|
480
|
+
);
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
],
|
|
458
484
|
},
|
|
459
485
|
{
|
|
460
486
|
icon: <HardDrive size={14} strokeWidth={1.5} />,
|
|
@@ -467,6 +493,30 @@ export function useNotebookActions({
|
|
|
467
493
|
/>,
|
|
468
494
|
);
|
|
469
495
|
},
|
|
496
|
+
dropdown:
|
|
497
|
+
detectedStorageSources.length === 0
|
|
498
|
+
? undefined
|
|
499
|
+
: [
|
|
500
|
+
...detectedStorageSources.map((source) => ({
|
|
501
|
+
icon: <SparklesIcon size={14} strokeWidth={1.5} />,
|
|
502
|
+
label: `Add ${source.displayName}`,
|
|
503
|
+
description: "Detected in your environment",
|
|
504
|
+
handle: () => addDetectedDataSource(source),
|
|
505
|
+
})),
|
|
506
|
+
{
|
|
507
|
+
divider: true,
|
|
508
|
+
icon: <HardDrive size={14} strokeWidth={1.5} />,
|
|
509
|
+
label: "Browse all connections",
|
|
510
|
+
handle: () => {
|
|
511
|
+
openModal(
|
|
512
|
+
<AddConnectionDialogContent
|
|
513
|
+
defaultTab="storage"
|
|
514
|
+
onClose={closeModal}
|
|
515
|
+
/>,
|
|
516
|
+
);
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
],
|
|
470
520
|
},
|
|
471
521
|
{
|
|
472
522
|
icon: <Undo2Icon size={14} strokeWidth={1.5} />,
|
|
@@ -105,7 +105,7 @@ const DiscoveredSourcesBadge = ({
|
|
|
105
105
|
return (
|
|
106
106
|
<Tooltip content={content}>
|
|
107
107
|
<span>
|
|
108
|
-
<PanelBadge className="gap-0.5 border-(--blue-6) bg-(--blue-3) text-(--blue-11)">
|
|
108
|
+
<PanelBadge className="gap-0.5 border-(--blue-6) bg-(--blue-3) text-(--blue-11) hover:bg-(--blue-4) dark:border-(--blue-8) dark:bg-(--blue-3) dark:hover:bg-(--blue-4)">
|
|
109
109
|
<SparklesIcon className="h-2.5 w-2.5" />
|
|
110
110
|
{count}
|
|
111
111
|
</PanelBadge>
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { Provider } from "jotai";
|
|
5
|
+
import type React from "react";
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
7
|
+
import { ModalProvider } from "@/components/modal/ImperativeModal";
|
|
8
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
9
|
+
import type { DetectedDataSource } from "@/core/datasets/data-source-discovery";
|
|
10
|
+
import { store } from "@/core/state/jotai";
|
|
11
|
+
import { AddConnectionButton } from "../add-connection-button";
|
|
12
|
+
|
|
13
|
+
const mocks = vi.hoisted(() => ({
|
|
14
|
+
addDetectedDataSource: vi.fn(),
|
|
15
|
+
sources: [] as DetectedDataSource[],
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const MYSQL_SOURCE: DetectedDataSource = {
|
|
19
|
+
id: "mysql-environment",
|
|
20
|
+
integration: "mysql",
|
|
21
|
+
category: "database",
|
|
22
|
+
displayName: "MySQL",
|
|
23
|
+
confidence: "high",
|
|
24
|
+
origins: [{ type: "environment", label: "Kernel environment" }],
|
|
25
|
+
configuration: [],
|
|
26
|
+
code: "engine = create_engine()",
|
|
27
|
+
hidesWhen: { kind: "dialect", substrings: ["mysql"] },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
vi.mock("@/hooks/useDataSourceDiscovery", () => ({
|
|
31
|
+
useDetectedDataSources: () => mocks.sources,
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
vi.mock("../components", async (importOriginal) => ({
|
|
35
|
+
...(await importOriginal<typeof import("../components")>()),
|
|
36
|
+
useAddDetectedDataSource: () => mocks.addDetectedDataSource,
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
function wrapper({ children }: { children: React.ReactNode }) {
|
|
40
|
+
return (
|
|
41
|
+
<Provider store={store}>
|
|
42
|
+
<TooltipProvider>
|
|
43
|
+
<ModalProvider>{children}</ModalProvider>
|
|
44
|
+
</TooltipProvider>
|
|
45
|
+
</Provider>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe("AddConnectionButton", () => {
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
mocks.sources = [];
|
|
52
|
+
mocks.addDetectedDataSource.mockClear();
|
|
53
|
+
vi.stubGlobal("PointerEvent", MouseEvent);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
57
|
+
|
|
58
|
+
it("opens the connection dialog directly without suggestions", () => {
|
|
59
|
+
render(
|
|
60
|
+
<AddConnectionButton
|
|
61
|
+
group="database"
|
|
62
|
+
label="Add database or catalog"
|
|
63
|
+
variant="outline"
|
|
64
|
+
/>,
|
|
65
|
+
{ wrapper },
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
fireEvent.click(
|
|
69
|
+
screen.getByRole("button", { name: "Add database or catalog" }),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(
|
|
73
|
+
screen.getByRole("dialog", { name: "Add Connection" }),
|
|
74
|
+
).toBeVisible();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("opens an ordered suggestion menu when sources are detected", async () => {
|
|
78
|
+
mocks.sources = [MYSQL_SOURCE];
|
|
79
|
+
render(
|
|
80
|
+
<AddConnectionButton
|
|
81
|
+
group="database"
|
|
82
|
+
label="Add database or catalog"
|
|
83
|
+
variant="outline"
|
|
84
|
+
/>,
|
|
85
|
+
{ wrapper },
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
fireEvent.pointerDown(
|
|
89
|
+
screen.getByRole("button", {
|
|
90
|
+
name: "View detected database connections",
|
|
91
|
+
}),
|
|
92
|
+
{ button: 0, ctrlKey: false },
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const suggestion = await screen.findByRole("menuitem", {
|
|
96
|
+
name: "Add MySQL",
|
|
97
|
+
});
|
|
98
|
+
const browseAll = screen.getByRole("menuitem", {
|
|
99
|
+
name: "Browse all connections",
|
|
100
|
+
});
|
|
101
|
+
expect(
|
|
102
|
+
suggestion.compareDocumentPosition(browseAll) &
|
|
103
|
+
Node.DOCUMENT_POSITION_FOLLOWING,
|
|
104
|
+
).toBeTruthy();
|
|
105
|
+
|
|
106
|
+
fireEvent.click(suggestion);
|
|
107
|
+
expect(mocks.addDetectedDataSource).toHaveBeenCalledWith(MYSQL_SOURCE);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("keeps the primary action direct when sources are detected", () => {
|
|
111
|
+
mocks.sources = [MYSQL_SOURCE];
|
|
112
|
+
render(
|
|
113
|
+
<AddConnectionButton
|
|
114
|
+
group="database"
|
|
115
|
+
label="Add database or catalog"
|
|
116
|
+
variant="outline"
|
|
117
|
+
/>,
|
|
118
|
+
{ wrapper },
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
fireEvent.click(
|
|
122
|
+
screen.getByRole("button", { name: "Add database or catalog" }),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
expect(
|
|
126
|
+
screen.getByRole("dialog", { name: "Add Connection" }),
|
|
127
|
+
).toBeVisible();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DatabaseIcon,
|
|
5
|
+
HardDriveIcon,
|
|
6
|
+
PlusIcon,
|
|
7
|
+
SparklesIcon,
|
|
8
|
+
} from "lucide-react";
|
|
9
|
+
import type React from "react";
|
|
10
|
+
import { useImperativeModal } from "@/components/modal/ImperativeModal";
|
|
11
|
+
import { Button } from "@/components/ui/button";
|
|
12
|
+
import {
|
|
13
|
+
DropdownMenu,
|
|
14
|
+
DropdownMenuContent,
|
|
15
|
+
DropdownMenuItem,
|
|
16
|
+
DropdownMenuSeparator,
|
|
17
|
+
DropdownMenuTrigger,
|
|
18
|
+
} from "@/components/ui/dropdown-menu";
|
|
19
|
+
import { Tooltip } from "@/components/ui/tooltip";
|
|
20
|
+
import type { DataSourceDiscoveryGroup } from "@/core/datasets/data-source-discovery";
|
|
21
|
+
import { useDetectedDataSources } from "@/hooks/useDataSourceDiscovery";
|
|
22
|
+
import { cn } from "@/utils/cn";
|
|
23
|
+
import {
|
|
24
|
+
AddConnectionDialog,
|
|
25
|
+
AddConnectionDialogContent,
|
|
26
|
+
} from "./add-connection-dialog";
|
|
27
|
+
import { useAddDetectedDataSource } from "./components";
|
|
28
|
+
|
|
29
|
+
type AddConnectionButtonProps = Omit<
|
|
30
|
+
React.ComponentProps<typeof Button>,
|
|
31
|
+
"children"
|
|
32
|
+
> & {
|
|
33
|
+
group: DataSourceDiscoveryGroup;
|
|
34
|
+
label: string;
|
|
35
|
+
compact?: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Opens the connection dialog directly and, when useful suggestions exist,
|
|
40
|
+
* adds a secondary menu for detected sources.
|
|
41
|
+
*/
|
|
42
|
+
export const AddConnectionButton: React.FC<AddConnectionButtonProps> = ({
|
|
43
|
+
group,
|
|
44
|
+
label,
|
|
45
|
+
compact = false,
|
|
46
|
+
className,
|
|
47
|
+
...buttonProps
|
|
48
|
+
}) => {
|
|
49
|
+
const sources = useDetectedDataSources(group);
|
|
50
|
+
const addDetectedDataSource = useAddDetectedDataSource();
|
|
51
|
+
const { openModal, closeModal } = useImperativeModal();
|
|
52
|
+
const hasSuggestions = sources.length > 0;
|
|
53
|
+
const defaultTab = group === "storage" ? "storage" : "databases";
|
|
54
|
+
|
|
55
|
+
const primaryButton = (
|
|
56
|
+
<Button
|
|
57
|
+
{...buttonProps}
|
|
58
|
+
className={cn(className, hasSuggestions && !compact && "rounded-r-none")}
|
|
59
|
+
aria-label={compact ? label : buttonProps["aria-label"]}
|
|
60
|
+
>
|
|
61
|
+
{!compact && label}
|
|
62
|
+
<PlusIcon className={cn("h-4 w-4", !compact && "ml-2")} />
|
|
63
|
+
</Button>
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const primaryAction = (
|
|
67
|
+
<AddConnectionDialog defaultTab={defaultTab}>
|
|
68
|
+
{primaryButton}
|
|
69
|
+
</AddConnectionDialog>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (!hasSuggestions) {
|
|
73
|
+
return primaryAction;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const BrowseIcon = group === "storage" ? HardDriveIcon : DatabaseIcon;
|
|
77
|
+
const suggestionsLabel =
|
|
78
|
+
group === "storage"
|
|
79
|
+
? "View detected remote storage connections"
|
|
80
|
+
: "View detected database connections";
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div className="inline-flex">
|
|
84
|
+
{primaryAction}
|
|
85
|
+
<DropdownMenu>
|
|
86
|
+
<Tooltip content={suggestionsLabel}>
|
|
87
|
+
<span className="inline-flex">
|
|
88
|
+
<DropdownMenuTrigger asChild={true}>
|
|
89
|
+
<Button
|
|
90
|
+
variant={buttonProps.variant}
|
|
91
|
+
size={buttonProps.size}
|
|
92
|
+
disabled={buttonProps.disabled}
|
|
93
|
+
className={cn(
|
|
94
|
+
"rounded-l-none border-l-0 px-2 text-(--blue-10) hover:bg-(--blue-3)",
|
|
95
|
+
compact && "rounded-none",
|
|
96
|
+
)}
|
|
97
|
+
aria-label={suggestionsLabel}
|
|
98
|
+
>
|
|
99
|
+
<SparklesIcon className="h-4 w-4" />
|
|
100
|
+
</Button>
|
|
101
|
+
</DropdownMenuTrigger>
|
|
102
|
+
</span>
|
|
103
|
+
</Tooltip>
|
|
104
|
+
<DropdownMenuContent align="end">
|
|
105
|
+
{sources.map((source) => (
|
|
106
|
+
<DropdownMenuItem
|
|
107
|
+
key={source.id}
|
|
108
|
+
onSelect={() => addDetectedDataSource(source)}
|
|
109
|
+
>
|
|
110
|
+
<SparklesIcon className="mr-2 h-4 w-4 text-(--blue-9)" />
|
|
111
|
+
Add {source.displayName}
|
|
112
|
+
</DropdownMenuItem>
|
|
113
|
+
))}
|
|
114
|
+
<DropdownMenuSeparator />
|
|
115
|
+
<DropdownMenuItem
|
|
116
|
+
onSelect={() =>
|
|
117
|
+
openModal(
|
|
118
|
+
<AddConnectionDialogContent
|
|
119
|
+
defaultTab={defaultTab}
|
|
120
|
+
onClose={closeModal}
|
|
121
|
+
/>,
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
>
|
|
125
|
+
<BrowseIcon className="mr-2 h-4 w-4" />
|
|
126
|
+
Browse all connections
|
|
127
|
+
</DropdownMenuItem>
|
|
128
|
+
</DropdownMenuContent>
|
|
129
|
+
</DropdownMenu>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
@@ -21,6 +21,7 @@ import { maybeAddMarimoImport } from "@/core/cells/add-missing-import";
|
|
|
21
21
|
import { useCellActions } from "@/core/cells/cells";
|
|
22
22
|
import { useLastFocusedCellId } from "@/core/cells/focus";
|
|
23
23
|
import { autoInstantiateAtom } from "@/core/config/config";
|
|
24
|
+
import type { DetectedDataSource } from "@/core/datasets/data-source-discovery";
|
|
24
25
|
import {
|
|
25
26
|
ENV_RENDERER,
|
|
26
27
|
SECRET_TEXTAREA_RENDERER,
|
|
@@ -134,6 +135,16 @@ export function useInsertCode() {
|
|
|
134
135
|
};
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
/** Insert the configured cell supplied by a detected data source. */
|
|
139
|
+
export function useAddDetectedDataSource(onAdd?: () => void) {
|
|
140
|
+
const insertCode = useInsertCode();
|
|
141
|
+
|
|
142
|
+
return (source: DetectedDataSource) => {
|
|
143
|
+
insertCode(source.code);
|
|
144
|
+
onAdd?.();
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
137
148
|
/**
|
|
138
149
|
* Generic connection form: Zod-driven form with secrets support, a library
|
|
139
150
|
* picker, and Back/Add buttons. Used by both database and storage forms.
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { PlusIcon, SparklesIcon } from "lucide-react";
|
|
4
|
+
import { useId } from "react";
|
|
4
5
|
import { Tooltip, TooltipProvider } from "@/components/ui/tooltip";
|
|
5
6
|
import type {
|
|
6
7
|
DataSourceDiscoveryGroup,
|
|
7
8
|
DetectedDataSource,
|
|
8
9
|
} from "@/core/datasets/data-source-discovery";
|
|
9
10
|
import { useDetectedDataSources } from "@/hooks/useDataSourceDiscovery";
|
|
10
|
-
import {
|
|
11
|
+
import { useAddDetectedDataSource } from "./components";
|
|
11
12
|
import { cn } from "@/utils/cn";
|
|
12
13
|
|
|
13
14
|
export const QuickAddDataSources: React.FC<{
|
|
@@ -15,22 +16,24 @@ export const QuickAddDataSources: React.FC<{
|
|
|
15
16
|
sources: DetectedDataSource[];
|
|
16
17
|
onAdd: (source: DetectedDataSource) => void;
|
|
17
18
|
}> = ({ className, sources, onAdd }) => {
|
|
19
|
+
const titleId = useId();
|
|
20
|
+
|
|
18
21
|
if (sources.length === 0) {
|
|
19
22
|
return null;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
return (
|
|
23
26
|
<section
|
|
24
|
-
aria-labelledby=
|
|
27
|
+
aria-labelledby={titleId}
|
|
25
28
|
className={cn(
|
|
26
|
-
"rounded-full bg-[linear-gradient(135deg,var(--blue-2),var(--purple-3))] px-3 py-2",
|
|
29
|
+
"rounded-full bg-[linear-gradient(135deg,var(--blue-2),var(--purple-3))] px-3 py-2 dark:bg-[linear-gradient(135deg,var(--blue-2),var(--purple-2))]",
|
|
27
30
|
className,
|
|
28
31
|
)}
|
|
29
32
|
>
|
|
30
33
|
<div className="flex flex-wrap items-center gap-2">
|
|
31
34
|
<div className="mr-1 flex items-center gap-1.5">
|
|
32
|
-
<SparklesIcon className="h-3.5 w-3.5 text-(--blue-9)" />
|
|
33
|
-
<h3 id=
|
|
35
|
+
<SparklesIcon className="h-3.5 w-3.5 text-(--blue-9) dark:text-(--blue-10)" />
|
|
36
|
+
<h3 id={titleId} className="text-sm">
|
|
34
37
|
Quick add
|
|
35
38
|
</h3>
|
|
36
39
|
</div>
|
|
@@ -44,7 +47,7 @@ export const QuickAddDataSources: React.FC<{
|
|
|
44
47
|
<button
|
|
45
48
|
type="button"
|
|
46
49
|
aria-label={`Add ${source.displayName} connection`}
|
|
47
|
-
className="inline-flex items-center gap-1 rounded-full border border-border/60 bg-background px-2.5 py-1 text-xs font-medium text-foreground/90 transition-colors hover:border-(--blue-7) focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
|
50
|
+
className="inline-flex items-center gap-1 rounded-full border border-border/60 bg-background px-2.5 py-1 text-xs font-medium text-foreground/90 transition-colors hover:border-(--blue-7) focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 dark:border-border dark:bg-background/60 dark:hover:bg-accent/80"
|
|
48
51
|
onClick={() => onAdd(source)}
|
|
49
52
|
>
|
|
50
53
|
<PlusIcon className="h-3 w-3 text-muted-foreground" />
|
|
@@ -101,17 +104,14 @@ export const AutoDiscoveredDataSources: React.FC<{
|
|
|
101
104
|
className?: string;
|
|
102
105
|
group?: DataSourceDiscoveryGroup;
|
|
103
106
|
}> = ({ onSubmit, className, group }) => {
|
|
104
|
-
const
|
|
107
|
+
const addDetectedDataSource = useAddDetectedDataSource(onSubmit);
|
|
105
108
|
const sources = useDetectedDataSources(group);
|
|
106
109
|
|
|
107
110
|
return (
|
|
108
111
|
<QuickAddDataSources
|
|
109
112
|
className={className}
|
|
110
113
|
sources={sources}
|
|
111
|
-
onAdd={
|
|
112
|
-
insertCode(source.code);
|
|
113
|
-
onSubmit?.();
|
|
114
|
-
}}
|
|
114
|
+
onAdd={addDetectedDataSource}
|
|
115
115
|
/>
|
|
116
116
|
);
|
|
117
117
|
};
|
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
act,
|
|
5
|
+
fireEvent,
|
|
6
|
+
render,
|
|
7
|
+
screen,
|
|
8
|
+
waitFor,
|
|
9
|
+
} from "@testing-library/react";
|
|
4
10
|
import { Provider } from "jotai";
|
|
5
11
|
import type React from "react";
|
|
6
12
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
7
13
|
import { MockRequestClient } from "@/__mocks__/requests";
|
|
8
14
|
import { ModalProvider } from "@/components/modal/ImperativeModal";
|
|
9
15
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
16
|
+
import {
|
|
17
|
+
type DetectedDataSource,
|
|
18
|
+
invalidateDataSourceDiscovery,
|
|
19
|
+
} from "@/core/datasets/data-source-discovery";
|
|
20
|
+
import { DiscoverDataSources } from "@/core/datasets/request-registry";
|
|
10
21
|
import { layoutStateAtom } from "@/core/layout/layout";
|
|
11
22
|
import { kioskModeAtom, viewStateAtom } from "@/core/mode";
|
|
23
|
+
import { connectionAtom } from "@/core/network/connection";
|
|
12
24
|
import { requestClientAtom } from "@/core/network/requests";
|
|
13
25
|
import { filenameAtom } from "@/core/saving/file-state";
|
|
14
26
|
import { store } from "@/core/state/jotai";
|
|
15
27
|
import { isWasm } from "@/core/wasm/utils";
|
|
28
|
+
import { WebSocketState } from "@/core/websocket/types";
|
|
16
29
|
import {
|
|
17
30
|
DEFAULT_EXPORT_OPTIONS,
|
|
18
31
|
exportOptionsAtom,
|
|
@@ -20,6 +33,34 @@ import {
|
|
|
20
33
|
} from "../../actions/export-dialog/state";
|
|
21
34
|
import { NotebookMenuDropdown } from "../notebook-menu-dropdown";
|
|
22
35
|
|
|
36
|
+
const POSTGRES_SOURCE: DetectedDataSource = {
|
|
37
|
+
id: "postgres-libpq-environment",
|
|
38
|
+
integration: "postgres",
|
|
39
|
+
category: "database",
|
|
40
|
+
displayName: "PostgreSQL",
|
|
41
|
+
confidence: "high",
|
|
42
|
+
origins: [{ type: "environment", label: "Kernel environment" }],
|
|
43
|
+
configuration: [],
|
|
44
|
+
code: "engine = create_engine()",
|
|
45
|
+
hidesWhen: { kind: "dialect", substrings: ["postgres"] },
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const S3_SOURCE: DetectedDataSource = {
|
|
49
|
+
id: "aws-s3-environment",
|
|
50
|
+
integration: "aws",
|
|
51
|
+
category: "object-storage",
|
|
52
|
+
displayName: "AWS S3",
|
|
53
|
+
confidence: "high",
|
|
54
|
+
origins: [{ type: "environment", label: "Kernel environment" }],
|
|
55
|
+
configuration: [],
|
|
56
|
+
code: "storage = create_s3_storage()",
|
|
57
|
+
hidesWhen: {
|
|
58
|
+
kind: "storage",
|
|
59
|
+
protocols: ["s3"],
|
|
60
|
+
backendTypes: ["s3"],
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
23
64
|
vi.mock("@/core/wasm/utils", async (importOriginal) => {
|
|
24
65
|
const actual = await importOriginal<typeof import("@/core/wasm/utils")>();
|
|
25
66
|
return { ...actual, isWasm: vi.fn(() => false) };
|
|
@@ -59,6 +100,7 @@ describe("NotebookMenuDropdown", () => {
|
|
|
59
100
|
store.set(requestClientAtom, MockRequestClient.create());
|
|
60
101
|
store.set(filenameAtom, "/project/notebook.py");
|
|
61
102
|
store.set(viewStateAtom, { mode: "edit", cellAnchor: null });
|
|
103
|
+
store.set(connectionAtom, { state: WebSocketState.OPEN });
|
|
62
104
|
store.set(kioskModeAtom, false);
|
|
63
105
|
store.set(layoutStateAtom, {
|
|
64
106
|
selectedLayout: "vertical",
|
|
@@ -73,11 +115,77 @@ describe("NotebookMenuDropdown", () => {
|
|
|
73
115
|
addEventListener: vi.fn(),
|
|
74
116
|
removeEventListener: vi.fn(),
|
|
75
117
|
}));
|
|
118
|
+
invalidateDataSourceDiscovery();
|
|
119
|
+
vi.spyOn(DiscoverDataSources, "request").mockResolvedValue({
|
|
120
|
+
request_id: "request-id",
|
|
121
|
+
sources: [],
|
|
122
|
+
});
|
|
76
123
|
});
|
|
77
124
|
|
|
78
125
|
afterEach(() => {
|
|
79
126
|
document.title = "";
|
|
80
127
|
vi.unstubAllGlobals();
|
|
128
|
+
vi.restoreAllMocks();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("shows detected databases in the add connection submenu", async () => {
|
|
132
|
+
vi.mocked(DiscoverDataSources.request).mockResolvedValue({
|
|
133
|
+
request_id: "request-id",
|
|
134
|
+
sources: [POSTGRES_SOURCE],
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
render(<NotebookMenuDropdown />, { wrapper });
|
|
138
|
+
await waitFor(() =>
|
|
139
|
+
expect(DiscoverDataSources.request).toHaveBeenCalledOnce(),
|
|
140
|
+
);
|
|
141
|
+
await act(async () => Promise.resolve());
|
|
142
|
+
openNotebookMenu();
|
|
143
|
+
fireEvent.click(
|
|
144
|
+
await screen.findByRole("menuitem", { name: "Add database connection" }),
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const suggestion = await screen.findByRole("menuitem", {
|
|
148
|
+
name: "Add PostgreSQL",
|
|
149
|
+
});
|
|
150
|
+
const browseAll = screen.getByRole("menuitem", {
|
|
151
|
+
name: "Browse all connections",
|
|
152
|
+
});
|
|
153
|
+
expect(suggestion).toBeVisible();
|
|
154
|
+
expect(browseAll).toBeVisible();
|
|
155
|
+
expect(
|
|
156
|
+
suggestion.compareDocumentPosition(browseAll) &
|
|
157
|
+
Node.DOCUMENT_POSITION_FOLLOWING,
|
|
158
|
+
).toBeTruthy();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("shows detected storage in the add remote storage submenu", async () => {
|
|
162
|
+
vi.mocked(DiscoverDataSources.request).mockResolvedValue({
|
|
163
|
+
request_id: "request-id",
|
|
164
|
+
sources: [S3_SOURCE],
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
render(<NotebookMenuDropdown />, { wrapper });
|
|
168
|
+
await waitFor(() =>
|
|
169
|
+
expect(DiscoverDataSources.request).toHaveBeenCalledOnce(),
|
|
170
|
+
);
|
|
171
|
+
await act(async () => Promise.resolve());
|
|
172
|
+
openNotebookMenu();
|
|
173
|
+
fireEvent.click(
|
|
174
|
+
await screen.findByRole("menuitem", { name: "Add remote storage" }),
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const suggestion = await screen.findByRole("menuitem", {
|
|
178
|
+
name: "Add AWS S3",
|
|
179
|
+
});
|
|
180
|
+
const browseAll = screen.getByRole("menuitem", {
|
|
181
|
+
name: "Browse all connections",
|
|
182
|
+
});
|
|
183
|
+
expect(suggestion).toBeVisible();
|
|
184
|
+
expect(browseAll).toBeVisible();
|
|
185
|
+
expect(
|
|
186
|
+
suggestion.compareDocumentPosition(browseAll) &
|
|
187
|
+
Node.DOCUMENT_POSITION_FOLLOWING,
|
|
188
|
+
).toBeTruthy();
|
|
81
189
|
});
|
|
82
190
|
|
|
83
191
|
it("returns focus to the notebook menu when the dialog closes", async () => {
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
HardDriveIcon,
|
|
9
9
|
HelpCircleIcon,
|
|
10
10
|
LoaderCircle,
|
|
11
|
-
PlusIcon,
|
|
12
11
|
ViewIcon,
|
|
13
12
|
XIcon,
|
|
14
13
|
} from "lucide-react";
|
|
@@ -17,7 +16,7 @@ import { useLocale } from "react-aria";
|
|
|
17
16
|
import { EngineVariable } from "@/components/databases/engine-variable";
|
|
18
17
|
import { useAddCodeToNewCell } from "@/components/editor/cell/useAddCell";
|
|
19
18
|
import { PanelEmptyState } from "@/components/editor/chrome/panels/empty-state";
|
|
20
|
-
import {
|
|
19
|
+
import { AddConnectionButton } from "@/components/editor/connections/add-connection-button";
|
|
21
20
|
import {
|
|
22
21
|
FILE_ICON_COLOR,
|
|
23
22
|
renderFileIcon,
|
|
@@ -1011,12 +1010,12 @@ export const StorageInspector: React.FC = () => {
|
|
|
1011
1010
|
</span>
|
|
1012
1011
|
}
|
|
1013
1012
|
action={
|
|
1014
|
-
<
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1013
|
+
<AddConnectionButton
|
|
1014
|
+
group="storage"
|
|
1015
|
+
label="Add remote storage"
|
|
1016
|
+
variant="outline"
|
|
1017
|
+
size="sm"
|
|
1018
|
+
/>
|
|
1020
1019
|
}
|
|
1021
1020
|
icon={<HardDriveIcon className="h-8 w-8" />}
|
|
1022
1021
|
/>
|
|
@@ -1076,15 +1075,14 @@ export const StorageInspector: React.FC = () => {
|
|
|
1076
1075
|
>
|
|
1077
1076
|
<HelpCircleIcon className="h-3.5 w-3.5 shrink-0 cursor-help text-muted-foreground hover:text-foreground mr-2" />
|
|
1078
1077
|
</Tooltip>
|
|
1079
|
-
<
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
</AddConnectionDialog>
|
|
1078
|
+
<AddConnectionButton
|
|
1079
|
+
group="storage"
|
|
1080
|
+
label="Add remote storage"
|
|
1081
|
+
compact={true}
|
|
1082
|
+
variant="ghost"
|
|
1083
|
+
size="sm"
|
|
1084
|
+
className="px-2 border-0 border-l border-muted-background rounded-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
|
1085
|
+
/>
|
|
1088
1086
|
</div>
|
|
1089
1087
|
<CommandList className="flex flex-col">
|
|
1090
1088
|
{namespaces.map((ns) => {
|