@marimo-team/islands 0.24.1-dev42 → 0.24.1-dev44
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-4UvlnITc.js → ConnectedDataExplorerComponent-CxLgYj_n.js} +1038 -1038
- package/dist/{any-language-editor-ClDhQoll.js → any-language-editor-BrPYtXHK.js} +1 -1
- package/dist/{chat-ui-BzZL5_XE.js → chat-ui-Mjq3lBLn.js} +1335 -1335
- package/dist/{common-DiHZo6cj.js → common-BTE61CWu.js} +20955 -20975
- package/dist/{input-CIaA2N0m.js → dropdown-menu-DEu759Oe.js} +930 -927
- package/dist/{error-banner-BbxpEiD5.js → error-banner-BT4uIIQ2.js} +190 -192
- package/dist/{glide-data-editor-CgO5z9xu.js → glide-data-editor-C2RTkAgQ.js} +51 -51
- package/dist/{html-to-image-DPAZGZSi.js → html-to-image-hKcfSWj_.js} +5795 -5590
- package/dist/main.js +8696 -8703
- package/dist/{mermaid-BBTUHwCi.js → mermaid-OsTxhZOI.js} +12 -12
- package/dist/{process-output-oZcxC_tq.js → process-output-BkHK-KDc.js} +6 -6
- package/dist/{reveal-component-UCcAUoNH.js → reveal-component-B6TKxaT4.js} +11 -11
- package/dist/{strings-CoQ5sA4r.js → select-Bakm9EUs.js} +50 -53
- package/dist/{spec-6c0_fLYz.js → spec-DqUrBQpS.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{useDateFormatter-6ImHK1ln.js → useDateFormatter-DjRKOAkR.js} +1 -1
- package/dist/{vega-component-DlnGFDeY.js → vega-component-A2xO4BUa.js} +17 -17
- package/package.json +1 -1
- 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
|
@@ -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) => {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { Provider } from "jotai";
|
|
5
|
+
import { beforeAll, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
7
|
+
import type { CellId } from "@/core/cells/ids";
|
|
8
|
+
import type { DetectedDataSource } from "@/core/datasets/data-source-discovery";
|
|
9
|
+
import type { ConnectionName } from "@/core/datasets/engines";
|
|
10
|
+
import { store } from "@/core/state/jotai";
|
|
11
|
+
import { SQLEngineSelect } from "./sql";
|
|
12
|
+
|
|
13
|
+
const { addDetectedDataSource } = vi.hoisted(() => ({
|
|
14
|
+
addDetectedDataSource: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const POSTGRES_SOURCE: DetectedDataSource = {
|
|
18
|
+
id: "postgres-libpq-environment",
|
|
19
|
+
integration: "postgres",
|
|
20
|
+
category: "database",
|
|
21
|
+
displayName: "PostgreSQL",
|
|
22
|
+
confidence: "high",
|
|
23
|
+
origins: [{ type: "environment", label: "Kernel environment" }],
|
|
24
|
+
configuration: [],
|
|
25
|
+
code: "engine = create_engine()",
|
|
26
|
+
hidesWhen: { kind: "dialect", substrings: ["postgres"] },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
vi.mock("@/hooks/useDataSourceDiscovery", () => ({
|
|
30
|
+
useDetectedDataSources: () => [POSTGRES_SOURCE],
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
vi.mock("@/components/editor/connections/components", () => ({
|
|
34
|
+
useAddDetectedDataSource: () => addDetectedDataSource,
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
describe("SQLEngineSelect", () => {
|
|
38
|
+
beforeAll(() => {
|
|
39
|
+
HTMLElement.prototype.hasPointerCapture = vi.fn(() => false);
|
|
40
|
+
HTMLElement.prototype.setPointerCapture = vi.fn();
|
|
41
|
+
HTMLElement.prototype.releasePointerCapture = vi.fn();
|
|
42
|
+
HTMLElement.prototype.scrollIntoView = vi.fn();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("adds a database detected in the environment", async () => {
|
|
46
|
+
render(
|
|
47
|
+
<Provider store={store}>
|
|
48
|
+
<TooltipProvider>
|
|
49
|
+
<SQLEngineSelect
|
|
50
|
+
selectedEngine={"" as ConnectionName}
|
|
51
|
+
onChange={vi.fn()}
|
|
52
|
+
cellId={"cell-1" as CellId}
|
|
53
|
+
/>
|
|
54
|
+
</TooltipProvider>
|
|
55
|
+
</Provider>,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
fireEvent.keyDown(screen.getByRole("combobox"), { key: "ArrowDown" });
|
|
59
|
+
|
|
60
|
+
expect(
|
|
61
|
+
await screen.findByText("Detected in your environment"),
|
|
62
|
+
).toBeVisible();
|
|
63
|
+
fireEvent.click(
|
|
64
|
+
screen.getByRole("option", { name: "Add PostgreSQL connection" }),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(addDetectedDataSource).toHaveBeenCalledWith(POSTGRES_SOURCE);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -10,10 +10,12 @@ import {
|
|
|
10
10
|
CircleHelpIcon,
|
|
11
11
|
DatabaseZap,
|
|
12
12
|
SearchCheck,
|
|
13
|
+
SparklesIcon,
|
|
13
14
|
} from "lucide-react";
|
|
14
15
|
import { getCellForDomProps } from "@/components/data-table/cell-utils";
|
|
15
16
|
import { transformDisplayName } from "@/components/databases/display";
|
|
16
17
|
import { DatabaseLogo } from "@/components/databases/icon";
|
|
18
|
+
import { useAddDetectedDataSource } from "@/components/editor/connections/components";
|
|
17
19
|
import { Button } from "@/components/ui/button";
|
|
18
20
|
import {
|
|
19
21
|
Select,
|
|
@@ -31,11 +33,13 @@ import {
|
|
|
31
33
|
dataConnectionsMapAtom,
|
|
32
34
|
setLatestEngineSelected,
|
|
33
35
|
} from "@/core/datasets/data-source-connections";
|
|
36
|
+
import type { DetectedDataSource } from "@/core/datasets/data-source-discovery";
|
|
34
37
|
import {
|
|
35
38
|
type ConnectionName,
|
|
36
39
|
INTERNAL_SQL_ENGINES,
|
|
37
40
|
} from "@/core/datasets/engines";
|
|
38
41
|
import type { DataSourceConnection } from "@/core/kernel/messages";
|
|
42
|
+
import { useDetectedDataSources } from "@/hooks/useDataSourceDiscovery";
|
|
39
43
|
import { useNonce } from "@/hooks/useNonce";
|
|
40
44
|
import { clearAllSqlValidationErrors } from "../languages/sql/banner-validation-errors";
|
|
41
45
|
import { type SQLMode, useSQLMode } from "../languages/sql/sql-mode";
|
|
@@ -46,19 +50,61 @@ interface SelectProps {
|
|
|
46
50
|
cellId: CellId;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
export const SQLEngineSelect: React.FC<SelectProps> = ({
|
|
53
|
+
export const SQLEngineSelect: React.FC<SelectProps> = (props) => {
|
|
54
|
+
const detectedDataSources = useDetectedDataSources("database");
|
|
55
|
+
|
|
56
|
+
if (detectedDataSources.length === 0) {
|
|
57
|
+
return (
|
|
58
|
+
<SQLEngineSelectImpl
|
|
59
|
+
{...props}
|
|
60
|
+
detectedDataSources={detectedDataSources}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<SQLEngineSelectWithDetectedSources
|
|
67
|
+
{...props}
|
|
68
|
+
detectedDataSources={detectedDataSources}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const SQLEngineSelectWithDetectedSources: React.FC<
|
|
74
|
+
SelectProps & { detectedDataSources: DetectedDataSource[] }
|
|
75
|
+
> = ({ detectedDataSources, ...props }) => {
|
|
76
|
+
const addDetectedDataSource = useAddDetectedDataSource();
|
|
77
|
+
return (
|
|
78
|
+
<SQLEngineSelectImpl
|
|
79
|
+
{...props}
|
|
80
|
+
detectedDataSources={detectedDataSources}
|
|
81
|
+
addDetectedDataSource={addDetectedDataSource}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const SQLEngineSelectImpl: React.FC<
|
|
87
|
+
SelectProps & {
|
|
88
|
+
detectedDataSources: DetectedDataSource[];
|
|
89
|
+
addDetectedDataSource?: (source: DetectedDataSource) => void;
|
|
90
|
+
}
|
|
91
|
+
> = ({
|
|
50
92
|
selectedEngine,
|
|
51
93
|
onChange,
|
|
52
94
|
cellId,
|
|
95
|
+
detectedDataSources,
|
|
96
|
+
addDetectedDataSource,
|
|
53
97
|
}) => {
|
|
54
98
|
const connectionsMap = useAtomValue(dataConnectionsMapAtom);
|
|
55
99
|
|
|
56
100
|
const internalEngineConnections: DataSourceConnection[] = [];
|
|
57
101
|
const userDefinedConnections: DataSourceConnection[] = [];
|
|
58
102
|
for (const [connName, connection] of connectionsMap.entries()) {
|
|
59
|
-
INTERNAL_SQL_ENGINES.has(connName)
|
|
60
|
-
|
|
61
|
-
|
|
103
|
+
if (INTERNAL_SQL_ENGINES.has(connName)) {
|
|
104
|
+
internalEngineConnections.push(connection);
|
|
105
|
+
} else {
|
|
106
|
+
userDefinedConnections.push(connection);
|
|
107
|
+
}
|
|
62
108
|
}
|
|
63
109
|
|
|
64
110
|
// Use nonce to force re-render as languageAdapter.engine may not trigger change
|
|
@@ -74,6 +120,17 @@ export const SQLEngineSelect: React.FC<SelectProps> = ({
|
|
|
74
120
|
return;
|
|
75
121
|
}
|
|
76
122
|
|
|
123
|
+
if (value.startsWith(DETECTED_SOURCE_PREFIX)) {
|
|
124
|
+
const sourceId = value.slice(DETECTED_SOURCE_PREFIX.length);
|
|
125
|
+
const source = detectedDataSources.find(
|
|
126
|
+
(source) => source.id === sourceId,
|
|
127
|
+
);
|
|
128
|
+
if (source && addDetectedDataSource) {
|
|
129
|
+
addDetectedDataSource(source);
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
77
134
|
const nextEngine = connectionsMap.get(value as ConnectionName);
|
|
78
135
|
if (nextEngine) {
|
|
79
136
|
rerender();
|
|
@@ -125,6 +182,25 @@ export const SQLEngineSelect: React.FC<SelectProps> = ({
|
|
|
125
182
|
{renderConnections(userDefinedConnections)}
|
|
126
183
|
{userDefinedConnections.length > 0 && <SelectSeparator />}
|
|
127
184
|
{renderConnections(internalEngineConnections)}
|
|
185
|
+
{detectedDataSources.length > 0 && (
|
|
186
|
+
<>
|
|
187
|
+
<SelectSeparator />
|
|
188
|
+
<SelectLabel>Detected in your environment</SelectLabel>
|
|
189
|
+
{detectedDataSources.map((source) => (
|
|
190
|
+
<SelectItem
|
|
191
|
+
key={source.id}
|
|
192
|
+
value={`${DETECTED_SOURCE_PREFIX}${source.id}`}
|
|
193
|
+
>
|
|
194
|
+
<div className="flex items-center gap-1">
|
|
195
|
+
<SparklesIcon className="h-3 w-3 text-(--blue-9)" />
|
|
196
|
+
<span className="truncate ml-0.5">
|
|
197
|
+
Add {source.displayName} connection
|
|
198
|
+
</span>
|
|
199
|
+
</div>
|
|
200
|
+
</SelectItem>
|
|
201
|
+
))}
|
|
202
|
+
</>
|
|
203
|
+
)}
|
|
128
204
|
<SelectSeparator />
|
|
129
205
|
<SelectItem className="text-muted-foreground" value={HELP_KEY}>
|
|
130
206
|
<a
|
|
@@ -145,6 +221,7 @@ export const SQLEngineSelect: React.FC<SelectProps> = ({
|
|
|
145
221
|
};
|
|
146
222
|
|
|
147
223
|
const HELP_KEY = "__help__";
|
|
224
|
+
const DETECTED_SOURCE_PREFIX = "__detected_source__:";
|
|
148
225
|
const HELP_URL =
|
|
149
226
|
"http://docs.marimo.io/guides/working_with_data/sql/#connecting-to-a-custom-database";
|
|
150
227
|
|