@marimo-team/islands 0.20.3-dev94 → 0.20.3-dev96
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/main.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/databases/icons/google-drive.svg +8 -0
- package/src/components/datasources/datasources.tsx +5 -5
- package/src/components/editor/actions/useNotebookActions.tsx +15 -2
- package/src/components/editor/connections/add-connection-dialog.tsx +83 -0
- package/src/components/editor/connections/components.tsx +177 -0
- package/src/components/editor/{database → connections/database}/__tests__/as-code.test.ts +1 -1
- package/src/components/editor/connections/database/add-database-form.tsx +303 -0
- package/src/components/editor/{database → connections/database}/as-code.ts +1 -1
- package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +100 -0
- package/src/components/editor/connections/storage/__tests__/as-code.test.ts +166 -0
- package/src/components/editor/connections/storage/add-storage-form.tsx +135 -0
- package/src/components/editor/connections/storage/as-code.ts +188 -0
- package/src/components/editor/connections/storage/schemas.ts +141 -0
- package/src/components/storage/components.tsx +9 -3
- package/src/components/storage/storage-inspector.tsx +20 -1
- package/src/core/codemirror/__tests__/format.test.ts +9 -1
- package/src/core/storage/types.ts +1 -0
- package/src/components/editor/database/add-database-form.tsx +0 -420
- /package/src/components/editor/{database → connections}/__tests__/secrets.test.ts +0 -0
- /package/src/components/editor/{database → connections/database}/__tests__/__snapshots__/as-code.test.ts.snap +0 -0
- /package/src/components/editor/{database → connections/database}/schemas.ts +0 -0
- /package/src/components/editor/{database → connections}/form-renderers.tsx +0 -0
- /package/src/components/editor/{database → connections}/secrets.ts +0 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { FieldOptions } from "@/components/forms/options";
|
|
4
|
+
|
|
5
|
+
export const S3StorageSchema = z
|
|
6
|
+
.object({
|
|
7
|
+
type: z.literal("s3"),
|
|
8
|
+
bucket: z
|
|
9
|
+
.string()
|
|
10
|
+
.nonempty()
|
|
11
|
+
.describe(
|
|
12
|
+
FieldOptions.of({
|
|
13
|
+
label: "Bucket",
|
|
14
|
+
placeholder: "my-bucket",
|
|
15
|
+
}),
|
|
16
|
+
),
|
|
17
|
+
region: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe(
|
|
21
|
+
FieldOptions.of({
|
|
22
|
+
label: "Region",
|
|
23
|
+
placeholder: "us-east-1",
|
|
24
|
+
optionRegex: ".*region.*",
|
|
25
|
+
}),
|
|
26
|
+
),
|
|
27
|
+
access_key_id: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe(
|
|
31
|
+
FieldOptions.of({
|
|
32
|
+
label: "Access Key ID",
|
|
33
|
+
inputType: "password",
|
|
34
|
+
optionRegex: ".*access_key.*",
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
37
|
+
secret_access_key: z
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe(
|
|
41
|
+
FieldOptions.of({
|
|
42
|
+
label: "Secret Access Key",
|
|
43
|
+
inputType: "password",
|
|
44
|
+
optionRegex: ".*secret.*access.*",
|
|
45
|
+
}),
|
|
46
|
+
),
|
|
47
|
+
endpoint_url: z
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe(
|
|
51
|
+
FieldOptions.of({
|
|
52
|
+
label: "Endpoint URL",
|
|
53
|
+
placeholder: "https://s3.amazonaws.com",
|
|
54
|
+
}),
|
|
55
|
+
),
|
|
56
|
+
})
|
|
57
|
+
.describe(FieldOptions.of({ direction: "two-columns" }));
|
|
58
|
+
|
|
59
|
+
export const GCSStorageSchema = z
|
|
60
|
+
.object({
|
|
61
|
+
type: z.literal("gcs"),
|
|
62
|
+
bucket: z
|
|
63
|
+
.string()
|
|
64
|
+
.nonempty()
|
|
65
|
+
.describe(
|
|
66
|
+
FieldOptions.of({
|
|
67
|
+
label: "Bucket",
|
|
68
|
+
placeholder: "my-bucket",
|
|
69
|
+
}),
|
|
70
|
+
),
|
|
71
|
+
service_account_key: z
|
|
72
|
+
.string()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe(
|
|
75
|
+
FieldOptions.of({
|
|
76
|
+
label: "Service Account Key (JSON)",
|
|
77
|
+
inputType: "textarea",
|
|
78
|
+
}),
|
|
79
|
+
),
|
|
80
|
+
})
|
|
81
|
+
.describe(FieldOptions.of({ direction: "two-columns" }));
|
|
82
|
+
|
|
83
|
+
export const AzureStorageSchema = z
|
|
84
|
+
.object({
|
|
85
|
+
type: z.literal("azure"),
|
|
86
|
+
container: z
|
|
87
|
+
.string()
|
|
88
|
+
.nonempty()
|
|
89
|
+
.describe(
|
|
90
|
+
FieldOptions.of({
|
|
91
|
+
label: "Container",
|
|
92
|
+
placeholder: "my-container",
|
|
93
|
+
}),
|
|
94
|
+
),
|
|
95
|
+
account_name: z
|
|
96
|
+
.string()
|
|
97
|
+
.nonempty()
|
|
98
|
+
.describe(
|
|
99
|
+
FieldOptions.of({
|
|
100
|
+
label: "Account Name",
|
|
101
|
+
placeholder: "storageaccount",
|
|
102
|
+
optionRegex: ".*account.*",
|
|
103
|
+
}),
|
|
104
|
+
),
|
|
105
|
+
account_key: z
|
|
106
|
+
.string()
|
|
107
|
+
.optional()
|
|
108
|
+
.describe(
|
|
109
|
+
FieldOptions.of({
|
|
110
|
+
label: "Account Key",
|
|
111
|
+
inputType: "password",
|
|
112
|
+
optionRegex: ".*azure.*key.*",
|
|
113
|
+
}),
|
|
114
|
+
),
|
|
115
|
+
})
|
|
116
|
+
.describe(FieldOptions.of({ direction: "two-columns" }));
|
|
117
|
+
|
|
118
|
+
export const GoogleDriveStorageSchema = z
|
|
119
|
+
.object({
|
|
120
|
+
type: z.literal("gdrive"),
|
|
121
|
+
credentials_json: z
|
|
122
|
+
.string()
|
|
123
|
+
.optional()
|
|
124
|
+
.describe(
|
|
125
|
+
FieldOptions.of({
|
|
126
|
+
label: "Service Account JSON",
|
|
127
|
+
description: "Leave empty to use browser-based authentication",
|
|
128
|
+
inputType: "textarea",
|
|
129
|
+
}),
|
|
130
|
+
),
|
|
131
|
+
})
|
|
132
|
+
.describe(FieldOptions.of({ direction: "two-columns" }));
|
|
133
|
+
|
|
134
|
+
export const StorageConnectionSchema = z.discriminatedUnion("type", [
|
|
135
|
+
S3StorageSchema,
|
|
136
|
+
GCSStorageSchema,
|
|
137
|
+
AzureStorageSchema,
|
|
138
|
+
GoogleDriveStorageSchema,
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
export type StorageConnection = z.infer<typeof StorageConnectionSchema>;
|
|
@@ -19,8 +19,10 @@ import {
|
|
|
19
19
|
ImageIcon,
|
|
20
20
|
} from "lucide-react";
|
|
21
21
|
import GoogleCloudIcon from "@/components/databases/icons/google-cloud-storage.svg?inline";
|
|
22
|
+
import GoogleDriveIcon from "@/components/databases/icons/google-drive.svg?inline";
|
|
22
23
|
import type { KnownStorageProtocol } from "@/core/storage/types";
|
|
23
24
|
import { useTheme } from "@/theme/useTheme";
|
|
25
|
+
import { cn } from "@/utils/cn";
|
|
24
26
|
|
|
25
27
|
export function renderFileIcon(name: string): React.ReactNode {
|
|
26
28
|
const ext = name.split(".").pop()?.toLowerCase();
|
|
@@ -67,12 +69,14 @@ const PROTOCOL_ICONS: Record<KnownStorageProtocol, IconEntry> = {
|
|
|
67
69
|
http: GlobeIcon,
|
|
68
70
|
file: HardDriveIcon,
|
|
69
71
|
"in-memory": DatabaseZapIcon,
|
|
72
|
+
gdrive: { src: GoogleDriveIcon },
|
|
70
73
|
github: GithubIcon,
|
|
71
74
|
};
|
|
72
75
|
|
|
73
76
|
export const ProtocolIcon: React.FC<{
|
|
74
77
|
protocol: KnownStorageProtocol | (string & {});
|
|
75
|
-
|
|
78
|
+
className?: string;
|
|
79
|
+
}> = ({ protocol, className }) => {
|
|
76
80
|
const { theme } = useTheme();
|
|
77
81
|
const entry =
|
|
78
82
|
PROTOCOL_ICONS[protocol.toLowerCase() as KnownStorageProtocol] ??
|
|
@@ -80,9 +84,11 @@ export const ProtocolIcon: React.FC<{
|
|
|
80
84
|
|
|
81
85
|
if ("src" in entry) {
|
|
82
86
|
const src = theme === "dark" && entry.dark ? entry.dark : entry.src;
|
|
83
|
-
return
|
|
87
|
+
return (
|
|
88
|
+
<img src={src} alt={protocol} className={cn("h-3.5 w-3.5", className)} />
|
|
89
|
+
);
|
|
84
90
|
}
|
|
85
91
|
|
|
86
92
|
const Icon = entry;
|
|
87
|
-
return <Icon className="h-3.5 w-3.5" />;
|
|
93
|
+
return <Icon className={cn("h-3.5 w-3.5", className)} />;
|
|
88
94
|
};
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
HelpCircleIcon,
|
|
11
11
|
LoaderCircle,
|
|
12
12
|
MoreVerticalIcon,
|
|
13
|
+
PlusIcon,
|
|
13
14
|
RefreshCwIcon,
|
|
14
15
|
ViewIcon,
|
|
15
16
|
XIcon,
|
|
@@ -18,6 +19,7 @@ import React, { useCallback, useState } from "react";
|
|
|
18
19
|
import { useLocale } from "react-aria";
|
|
19
20
|
import { EngineVariable } from "@/components/databases/engine-variable";
|
|
20
21
|
import { PanelEmptyState } from "@/components/editor/chrome/panels/empty-state";
|
|
22
|
+
import { AddConnectionDialog } from "@/components/editor/connections/add-connection-dialog";
|
|
21
23
|
import { Command, CommandInput, CommandItem } from "@/components/ui/command";
|
|
22
24
|
import {
|
|
23
25
|
DropdownMenu,
|
|
@@ -550,6 +552,14 @@ export const StorageInspector: React.FC = () => {
|
|
|
550
552
|
.
|
|
551
553
|
</span>
|
|
552
554
|
}
|
|
555
|
+
action={
|
|
556
|
+
<AddConnectionDialog defaultTab="storage">
|
|
557
|
+
<Button variant="outline" size="sm">
|
|
558
|
+
Add remote storage
|
|
559
|
+
<PlusIcon className="h-4 w-4 ml-2" />
|
|
560
|
+
</Button>
|
|
561
|
+
</AddConnectionDialog>
|
|
562
|
+
}
|
|
553
563
|
icon={<HardDriveIcon className="h-8 w-8" />}
|
|
554
564
|
/>
|
|
555
565
|
);
|
|
@@ -595,8 +605,17 @@ export const StorageInspector: React.FC = () => {
|
|
|
595
605
|
content="Filters loaded entries only. Expand directories to include their contents in the search."
|
|
596
606
|
delayDuration={200}
|
|
597
607
|
>
|
|
598
|
-
<HelpCircleIcon className="h-3.5 w-3.5
|
|
608
|
+
<HelpCircleIcon className="h-3.5 w-3.5 shrink-0 cursor-help text-muted-foreground hover:text-foreground mr-2" />
|
|
599
609
|
</Tooltip>
|
|
610
|
+
<AddConnectionDialog defaultTab="storage">
|
|
611
|
+
<Button
|
|
612
|
+
variant="ghost"
|
|
613
|
+
size="sm"
|
|
614
|
+
className="px-2 border-0 border-l border-muted-background rounded-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
|
615
|
+
>
|
|
616
|
+
<PlusIcon className="h-4 w-4" />
|
|
617
|
+
</Button>
|
|
618
|
+
</AddConnectionDialog>
|
|
600
619
|
</div>
|
|
601
620
|
<CommandList className="flex flex-col">
|
|
602
621
|
{namespaces.map((ns) => (
|
|
@@ -4,7 +4,7 @@ import { python } from "@codemirror/lang-python";
|
|
|
4
4
|
import { EditorState } from "@codemirror/state";
|
|
5
5
|
import { EditorView } from "@codemirror/view";
|
|
6
6
|
import { atom } from "jotai";
|
|
7
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
8
8
|
import { MockRequestClient } from "@/__mocks__/requests";
|
|
9
9
|
import type { NotebookState } from "@/core/cells/cells";
|
|
10
10
|
import { getNotebook } from "@/core/cells/cells";
|
|
@@ -44,6 +44,7 @@ vi.mock("@/core/config/config", () => ({
|
|
|
44
44
|
}));
|
|
45
45
|
|
|
46
46
|
const updateCellCode = vi.fn();
|
|
47
|
+
const createdViews: EditorView[] = [];
|
|
47
48
|
|
|
48
49
|
function createEditor(content: string, cellId: CellId) {
|
|
49
50
|
const state = EditorState.create({
|
|
@@ -74,6 +75,7 @@ function createEditor(content: string, cellId: CellId) {
|
|
|
74
75
|
parent: document.body,
|
|
75
76
|
});
|
|
76
77
|
|
|
78
|
+
createdViews.push(view);
|
|
77
79
|
return view;
|
|
78
80
|
}
|
|
79
81
|
|
|
@@ -88,6 +90,12 @@ beforeEach(() => {
|
|
|
88
90
|
store.set(requestClientAtom, mockRequestClient);
|
|
89
91
|
});
|
|
90
92
|
|
|
93
|
+
afterEach(() => {
|
|
94
|
+
for (const view of createdViews) {
|
|
95
|
+
view.destroy();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
91
99
|
describe("format", () => {
|
|
92
100
|
describe("formatEditorViews", () => {
|
|
93
101
|
it("should format code in editor views", async () => {
|
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
|
|
3
|
-
import { zodResolver } from "@hookform/resolvers/zod";
|
|
4
|
-
import { useState } from "react";
|
|
5
|
-
import { useForm } from "react-hook-form";
|
|
6
|
-
import type { z } from "zod";
|
|
7
|
-
import { DatabaseLogo, type DBLogoName } from "@/components/databases/icon";
|
|
8
|
-
import { type FormRenderer, ZodForm } from "@/components/forms/form";
|
|
9
|
-
import { getDefaults } from "@/components/forms/form-utils";
|
|
10
|
-
import { Button } from "@/components/ui/button";
|
|
11
|
-
import {
|
|
12
|
-
Dialog,
|
|
13
|
-
DialogContent,
|
|
14
|
-
DialogDescription,
|
|
15
|
-
DialogHeader,
|
|
16
|
-
DialogTitle,
|
|
17
|
-
DialogTrigger,
|
|
18
|
-
} from "@/components/ui/dialog";
|
|
19
|
-
import { FormErrorsBanner } from "@/components/ui/form";
|
|
20
|
-
import { ExternalLink } from "@/components/ui/links";
|
|
21
|
-
import {
|
|
22
|
-
Select,
|
|
23
|
-
SelectContent,
|
|
24
|
-
SelectGroup,
|
|
25
|
-
SelectItem,
|
|
26
|
-
SelectTrigger,
|
|
27
|
-
SelectValue,
|
|
28
|
-
} from "@/components/ui/select";
|
|
29
|
-
import { useCellActions } from "@/core/cells/cells";
|
|
30
|
-
import { useLastFocusedCellId } from "@/core/cells/focus";
|
|
31
|
-
import {
|
|
32
|
-
ConnectionDisplayNames,
|
|
33
|
-
type ConnectionLibrary,
|
|
34
|
-
generateDatabaseCode,
|
|
35
|
-
} from "./as-code";
|
|
36
|
-
import { ENV_RENDERER, SecretsProvider } from "./form-renderers";
|
|
37
|
-
import {
|
|
38
|
-
BigQueryConnectionSchema,
|
|
39
|
-
ChdbConnectionSchema,
|
|
40
|
-
ClickhouseConnectionSchema,
|
|
41
|
-
type DatabaseConnection,
|
|
42
|
-
DatabricksConnectionSchema,
|
|
43
|
-
DataFusionConnectionSchema,
|
|
44
|
-
DuckDBConnectionSchema,
|
|
45
|
-
IcebergConnectionSchema,
|
|
46
|
-
MotherDuckConnectionSchema,
|
|
47
|
-
MySQLConnectionSchema,
|
|
48
|
-
PostgresConnectionSchema,
|
|
49
|
-
PySparkConnectionSchema,
|
|
50
|
-
RedshiftConnectionSchema,
|
|
51
|
-
SnowflakeConnectionSchema,
|
|
52
|
-
SQLiteConnectionSchema,
|
|
53
|
-
SupabaseConnectionSchema,
|
|
54
|
-
TimeplusConnectionSchema,
|
|
55
|
-
TrinoConnectionSchema,
|
|
56
|
-
} from "./schemas";
|
|
57
|
-
|
|
58
|
-
interface Props {
|
|
59
|
-
onSubmit: () => void;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
interface ConnectionSchema {
|
|
63
|
-
name: string;
|
|
64
|
-
schema: z.ZodType<DatabaseConnection>;
|
|
65
|
-
color: string;
|
|
66
|
-
logo: DBLogoName;
|
|
67
|
-
connectionLibraries: {
|
|
68
|
-
libraries: ConnectionLibrary[];
|
|
69
|
-
preferred: ConnectionLibrary;
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// default to sqlalchemy because it has fewer dependencies
|
|
74
|
-
const DATABASES = [
|
|
75
|
-
{
|
|
76
|
-
name: "PostgreSQL",
|
|
77
|
-
schema: PostgresConnectionSchema,
|
|
78
|
-
color: "#336791",
|
|
79
|
-
logo: "postgres",
|
|
80
|
-
connectionLibraries: {
|
|
81
|
-
libraries: ["sqlalchemy", "sqlmodel"],
|
|
82
|
-
preferred: "sqlalchemy",
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: "MySQL",
|
|
87
|
-
schema: MySQLConnectionSchema,
|
|
88
|
-
color: "#00758F",
|
|
89
|
-
logo: "mysql",
|
|
90
|
-
connectionLibraries: {
|
|
91
|
-
libraries: ["sqlalchemy", "sqlmodel"],
|
|
92
|
-
preferred: "sqlalchemy",
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
name: "SQLite",
|
|
97
|
-
schema: SQLiteConnectionSchema,
|
|
98
|
-
color: "#003B57",
|
|
99
|
-
logo: "sqlite",
|
|
100
|
-
connectionLibraries: {
|
|
101
|
-
libraries: ["sqlalchemy", "sqlmodel"],
|
|
102
|
-
preferred: "sqlalchemy",
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
name: "DuckDB",
|
|
107
|
-
schema: DuckDBConnectionSchema,
|
|
108
|
-
color: "#FFD700",
|
|
109
|
-
logo: "duckdb",
|
|
110
|
-
connectionLibraries: {
|
|
111
|
-
libraries: ["duckdb"],
|
|
112
|
-
preferred: "duckdb",
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
name: "MotherDuck",
|
|
117
|
-
schema: MotherDuckConnectionSchema,
|
|
118
|
-
color: "#ff9538",
|
|
119
|
-
logo: "motherduck",
|
|
120
|
-
connectionLibraries: {
|
|
121
|
-
libraries: ["duckdb"],
|
|
122
|
-
preferred: "duckdb",
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
name: "Snowflake",
|
|
127
|
-
schema: SnowflakeConnectionSchema,
|
|
128
|
-
color: "#29B5E8",
|
|
129
|
-
logo: "snowflake",
|
|
130
|
-
connectionLibraries: {
|
|
131
|
-
libraries: ["sqlalchemy", "sqlmodel"],
|
|
132
|
-
preferred: "sqlalchemy",
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
name: "ClickHouse",
|
|
137
|
-
schema: ClickhouseConnectionSchema,
|
|
138
|
-
color: "#2C2C1D",
|
|
139
|
-
logo: "clickhouse",
|
|
140
|
-
connectionLibraries: {
|
|
141
|
-
libraries: ["clickhouse_connect"],
|
|
142
|
-
preferred: "clickhouse_connect",
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
name: "Timeplus",
|
|
147
|
-
schema: TimeplusConnectionSchema,
|
|
148
|
-
color: "#B83280",
|
|
149
|
-
logo: "timeplus",
|
|
150
|
-
connectionLibraries: {
|
|
151
|
-
libraries: ["sqlalchemy", "sqlmodel"],
|
|
152
|
-
preferred: "sqlalchemy",
|
|
153
|
-
},
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
name: "BigQuery",
|
|
157
|
-
schema: BigQueryConnectionSchema,
|
|
158
|
-
color: "#4285F4",
|
|
159
|
-
logo: "bigquery",
|
|
160
|
-
connectionLibraries: {
|
|
161
|
-
libraries: ["sqlalchemy", "sqlmodel"],
|
|
162
|
-
preferred: "sqlalchemy",
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
name: "ClickHouse Embedded",
|
|
167
|
-
schema: ChdbConnectionSchema,
|
|
168
|
-
color: "#f2b611",
|
|
169
|
-
logo: "clickhouse",
|
|
170
|
-
connectionLibraries: {
|
|
171
|
-
libraries: ["chdb"],
|
|
172
|
-
preferred: "chdb",
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
name: "Trino",
|
|
177
|
-
schema: TrinoConnectionSchema,
|
|
178
|
-
color: "#d466b6",
|
|
179
|
-
logo: "trino",
|
|
180
|
-
connectionLibraries: {
|
|
181
|
-
libraries: ["sqlalchemy", "sqlmodel"],
|
|
182
|
-
preferred: "sqlalchemy",
|
|
183
|
-
},
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
name: "DataFusion",
|
|
187
|
-
schema: DataFusionConnectionSchema,
|
|
188
|
-
color: "#202A37",
|
|
189
|
-
logo: "datafusion",
|
|
190
|
-
connectionLibraries: {
|
|
191
|
-
libraries: ["ibis"],
|
|
192
|
-
preferred: "ibis",
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
{
|
|
196
|
-
name: "PySpark",
|
|
197
|
-
schema: PySparkConnectionSchema,
|
|
198
|
-
color: "#1C5162",
|
|
199
|
-
logo: "pyspark",
|
|
200
|
-
connectionLibraries: {
|
|
201
|
-
libraries: ["ibis"],
|
|
202
|
-
preferred: "ibis",
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
name: "Redshift",
|
|
207
|
-
schema: RedshiftConnectionSchema,
|
|
208
|
-
color: "#522BAE",
|
|
209
|
-
logo: "redshift",
|
|
210
|
-
connectionLibraries: {
|
|
211
|
-
libraries: ["redshift"],
|
|
212
|
-
preferred: "redshift",
|
|
213
|
-
},
|
|
214
|
-
},
|
|
215
|
-
{
|
|
216
|
-
name: "Databricks",
|
|
217
|
-
schema: DatabricksConnectionSchema,
|
|
218
|
-
color: "#c41e0c",
|
|
219
|
-
logo: "databricks",
|
|
220
|
-
connectionLibraries: {
|
|
221
|
-
libraries: ["sqlalchemy", "sqlmodel", "ibis"],
|
|
222
|
-
preferred: "sqlalchemy",
|
|
223
|
-
},
|
|
224
|
-
},
|
|
225
|
-
{
|
|
226
|
-
name: "Supabase",
|
|
227
|
-
schema: SupabaseConnectionSchema,
|
|
228
|
-
color: "#238F5F",
|
|
229
|
-
logo: "supabase",
|
|
230
|
-
connectionLibraries: {
|
|
231
|
-
libraries: ["sqlalchemy", "sqlmodel"],
|
|
232
|
-
preferred: "sqlalchemy",
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
] satisfies ConnectionSchema[];
|
|
236
|
-
|
|
237
|
-
const DATA_CATALOGS = [
|
|
238
|
-
{
|
|
239
|
-
name: "Iceberg",
|
|
240
|
-
schema: IcebergConnectionSchema,
|
|
241
|
-
color: "#000000",
|
|
242
|
-
logo: "iceberg",
|
|
243
|
-
connectionLibraries: {
|
|
244
|
-
libraries: ["pyiceberg"],
|
|
245
|
-
preferred: "pyiceberg",
|
|
246
|
-
},
|
|
247
|
-
},
|
|
248
|
-
] satisfies ConnectionSchema[];
|
|
249
|
-
|
|
250
|
-
const DatabaseSchemaSelector: React.FC<{
|
|
251
|
-
onSelect: (schema: z.ZodType<DatabaseConnection>) => void;
|
|
252
|
-
}> = ({ onSelect }) => {
|
|
253
|
-
const renderItem = ({ name, schema, color, logo }: ConnectionSchema) => {
|
|
254
|
-
return (
|
|
255
|
-
<button
|
|
256
|
-
type="button"
|
|
257
|
-
key={name}
|
|
258
|
-
className="py-3 flex flex-col items-center justify-center gap-1 transition-all hover:scale-105 hover:brightness-110 rounded shadow-sm-solid hover:shadow-md-solid"
|
|
259
|
-
style={{ backgroundColor: color }}
|
|
260
|
-
onClick={() => onSelect(schema)}
|
|
261
|
-
>
|
|
262
|
-
<DatabaseLogo
|
|
263
|
-
name={logo}
|
|
264
|
-
className="w-8 h-8 text-white brightness-0 invert dark:invert"
|
|
265
|
-
/>
|
|
266
|
-
<span className="text-white font-medium text-lg">{name}</span>
|
|
267
|
-
</button>
|
|
268
|
-
);
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
return (
|
|
272
|
-
<>
|
|
273
|
-
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
|
274
|
-
{DATABASES.map(renderItem)}
|
|
275
|
-
</div>
|
|
276
|
-
<h4 className="font-semibold text-muted-foreground text-lg flex items-center gap-4">
|
|
277
|
-
Data Catalogs
|
|
278
|
-
<hr className="flex-1" />
|
|
279
|
-
</h4>
|
|
280
|
-
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
|
281
|
-
{DATA_CATALOGS.map(renderItem)}
|
|
282
|
-
</div>
|
|
283
|
-
</>
|
|
284
|
-
);
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
const RENDERERS: FormRenderer[] = [ENV_RENDERER];
|
|
288
|
-
|
|
289
|
-
const DatabaseForm: React.FC<{
|
|
290
|
-
schema: z.ZodType<DatabaseConnection>;
|
|
291
|
-
onSubmit: () => void;
|
|
292
|
-
onBack: () => void;
|
|
293
|
-
}> = ({ schema, onSubmit, onBack }) => {
|
|
294
|
-
const form = useForm<DatabaseConnection>({
|
|
295
|
-
defaultValues: getDefaults(schema),
|
|
296
|
-
resolver: zodResolver(
|
|
297
|
-
schema as unknown as z.ZodType<unknown, DatabaseConnection>,
|
|
298
|
-
),
|
|
299
|
-
reValidateMode: "onChange",
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
const connectionLibraries = [...DATABASES, ...DATA_CATALOGS].find(
|
|
303
|
-
(s) => s.schema === schema,
|
|
304
|
-
)?.connectionLibraries;
|
|
305
|
-
const [preferredConnection, setPreferredConnection] =
|
|
306
|
-
useState<ConnectionLibrary>(connectionLibraries?.preferred ?? "sqlalchemy");
|
|
307
|
-
|
|
308
|
-
const { createNewCell } = useCellActions();
|
|
309
|
-
const lastFocusedCellId = useLastFocusedCellId();
|
|
310
|
-
|
|
311
|
-
const handleInsertCode = (code: string) => {
|
|
312
|
-
createNewCell({
|
|
313
|
-
code,
|
|
314
|
-
before: false,
|
|
315
|
-
cellId: lastFocusedCellId ?? "__end__",
|
|
316
|
-
skipIfCodeExists: true,
|
|
317
|
-
});
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
const handleSubmit = (values: DatabaseConnection) => {
|
|
321
|
-
handleInsertCode(generateDatabaseCode(values, preferredConnection));
|
|
322
|
-
onSubmit();
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
return (
|
|
326
|
-
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
|
|
327
|
-
<SecretsProvider>
|
|
328
|
-
<ZodForm schema={schema} form={form} renderers={RENDERERS}>
|
|
329
|
-
<FormErrorsBanner />
|
|
330
|
-
</ZodForm>
|
|
331
|
-
</SecretsProvider>
|
|
332
|
-
<div className="flex gap-2 justify-between">
|
|
333
|
-
<div className="flex gap-2">
|
|
334
|
-
<Button type="button" variant="outline" onClick={onBack}>
|
|
335
|
-
Back
|
|
336
|
-
</Button>
|
|
337
|
-
<Button type="submit" disabled={!form.formState.isValid}>
|
|
338
|
-
Add
|
|
339
|
-
</Button>
|
|
340
|
-
</div>
|
|
341
|
-
<div>
|
|
342
|
-
<Select
|
|
343
|
-
value={preferredConnection}
|
|
344
|
-
onValueChange={(value) =>
|
|
345
|
-
setPreferredConnection(value as ConnectionLibrary)
|
|
346
|
-
}
|
|
347
|
-
>
|
|
348
|
-
<div className="flex flex-col gap-1 items-end">
|
|
349
|
-
<SelectTrigger>
|
|
350
|
-
<SelectValue placeholder="Select a library" />
|
|
351
|
-
</SelectTrigger>
|
|
352
|
-
<span className="text-xs text-muted-foreground">
|
|
353
|
-
Preferred connection library
|
|
354
|
-
</span>
|
|
355
|
-
</div>
|
|
356
|
-
<SelectContent>
|
|
357
|
-
<SelectGroup>
|
|
358
|
-
{connectionLibraries?.libraries.map((library) => (
|
|
359
|
-
<SelectItem key={library} value={library}>
|
|
360
|
-
{ConnectionDisplayNames[library]}
|
|
361
|
-
</SelectItem>
|
|
362
|
-
))}
|
|
363
|
-
</SelectGroup>
|
|
364
|
-
</SelectContent>
|
|
365
|
-
</Select>
|
|
366
|
-
</div>
|
|
367
|
-
</div>
|
|
368
|
-
</form>
|
|
369
|
-
);
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
const AddDatabaseForm: React.FC<Props> = ({ onSubmit }) => {
|
|
373
|
-
const [selectedSchema, setSelectedSchema] =
|
|
374
|
-
useState<z.ZodType<DatabaseConnection> | null>(null);
|
|
375
|
-
|
|
376
|
-
if (!selectedSchema) {
|
|
377
|
-
return <DatabaseSchemaSelector onSelect={setSelectedSchema} />;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return (
|
|
381
|
-
<DatabaseForm
|
|
382
|
-
schema={selectedSchema}
|
|
383
|
-
onSubmit={onSubmit}
|
|
384
|
-
onBack={() => setSelectedSchema(null)}
|
|
385
|
-
/>
|
|
386
|
-
);
|
|
387
|
-
};
|
|
388
|
-
|
|
389
|
-
export const AddDatabaseDialog: React.FC<{
|
|
390
|
-
children: React.ReactNode;
|
|
391
|
-
}> = ({ children }) => {
|
|
392
|
-
const [open, setOpen] = useState(false);
|
|
393
|
-
|
|
394
|
-
return (
|
|
395
|
-
<Dialog open={open} onOpenChange={setOpen}>
|
|
396
|
-
<DialogTrigger asChild={true}>{children}</DialogTrigger>
|
|
397
|
-
<AddDatabaseDialogContent onClose={() => setOpen(false)} />
|
|
398
|
-
</Dialog>
|
|
399
|
-
);
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
export const AddDatabaseDialogContent: React.FC<{
|
|
403
|
-
onClose: () => void;
|
|
404
|
-
}> = ({ onClose }) => {
|
|
405
|
-
return (
|
|
406
|
-
<DialogContent className="max-h-[75vh] overflow-y-auto">
|
|
407
|
-
<DialogHeader className="mb-4">
|
|
408
|
-
<DialogTitle>Add Connection</DialogTitle>
|
|
409
|
-
<DialogDescription>
|
|
410
|
-
Connect to your database or data catalog to query data directly from
|
|
411
|
-
your notebook. Learn more about how to connect to your database in our{" "}
|
|
412
|
-
<ExternalLink href="https://docs.marimo.io/guides/working_with_data/sql/#connecting-to-a-custom-database">
|
|
413
|
-
docs.
|
|
414
|
-
</ExternalLink>
|
|
415
|
-
</DialogDescription>
|
|
416
|
-
</DialogHeader>
|
|
417
|
-
<AddDatabaseForm onSubmit={() => onClose()} />
|
|
418
|
-
</DialogContent>
|
|
419
|
-
);
|
|
420
|
-
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|