@marimo-team/islands 0.23.16-dev6 → 0.23.16-dev62
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-CJaUI-S-.js → ConnectedDataExplorerComponent-f9vLjQiF.js} +13 -13
- package/dist/{ErrorBoundary-DXlxsrTF.js → ErrorBoundary-DhLqwSWA.js} +3 -3
- package/dist/{any-language-editor-P9tjkj4n.js → any-language-editor-DifE-fl7.js} +5 -5
- package/dist/{button-BcSml68y.js → button-BSm8IXRU.js} +2 -4
- package/dist/{capabilities-BYLFYTYv.js → capabilities-C6OQ5YBO.js} +1 -1
- package/dist/{chat-ui-BdSDiVjk.js → chat-ui-DsZJj75A.js} +3117 -3120
- package/dist/{check-Cs-KV5XY.js → check-DP7U0H0G.js} +1 -1
- package/dist/{code-visibility-CfBftiVM.js → common-Bpnmu1Yz.js} +1085 -1053
- package/dist/{copy-Cs3yt6Vt.js → copy-DNq8q3zw.js} +2 -2
- package/dist/{dist-B96tMAst.js → dist-DIlxLFXG.js} +2 -2
- package/dist/{error-banner-DR7o3CbX.js → error-banner-B_GQfxxD.js} +2 -2
- package/dist/{esm-k8PIvuZW.js → esm-DzZVaNH4.js} +1 -1
- package/dist/{extends-Cq2JYHKy.js → extends-w2_S44bV.js} +4 -4
- package/dist/{formats-BFhkZJwd.js → formats-Dwq1THhr.js} +3 -3
- package/dist/{glide-data-editor--CaN-f27.js → glide-data-editor-B9K6-MJe.js} +9 -9
- package/dist/{html-to-image-DDENewl2.js → html-to-image-CZ1kLKkq.js} +7350 -6356
- package/dist/{input-DStQIuqD.js → input-C5K4VBvB.js} +7 -7
- package/dist/{label-DIk0QoFe.js → label-DjoYwqmj.js} +2 -2
- package/dist/{loader-CTHCI-qe.js → loader-DX-IDS7n.js} +1 -1
- package/dist/main.js +259 -194
- package/dist/{mermaid-CdSvixW2.js → mermaid-hv6IckVw.js} +5 -5
- package/dist/{node-sql-parser-DLl_zNw0.js → node-sql-parser-v1Vi-Fft.js} +16867 -16665
- package/dist/{process-output-2QPcgBQg.js → process-output-bVfbcx5_.js} +32 -28
- package/dist/{reveal-component-BclLkYnV.js → reveal-component-JIt3oSeU.js} +642 -634
- package/dist/{spec-CnaxwDqy.js → spec-CQKL_D3i.js} +4 -4
- package/dist/{strings-BYAhZnQi.js → strings-DIy9NAmg.js} +4 -4
- package/dist/style.css +1 -1
- package/dist/{swiper-component-DE94711u.js → swiper-component-CEdAYjb7.js} +2 -2
- package/dist/toDate-CqhBWjvO.js +713 -0
- package/dist/{tooltip-B-qh7UPB.js → tooltip-f-qR0iUx.js} +3 -3
- package/dist/{types-DQl7jVYK.js → types-DicH9Oi0.js} +1 -1
- package/dist/{useAsyncData-CvO8WUy3.js → useAsyncData-CYOQb1EW.js} +1 -1
- package/dist/{useDateFormatter-BSoGim-G.js → useDateFormatter-Bt2qr_XS.js} +2 -2
- package/dist/{useDeepCompareMemoize-J3MKRdA4.js → useDeepCompareMemoize-CSHQeQEI.js} +1 -1
- package/dist/{useIframeCapabilities-POcFXHbw.js → useIframeCapabilities-kA09ykNq.js} +1 -1
- package/dist/{useLifecycle-DqPxHgAN.js → useLifecycle-CMJGqBaY.js} +3 -3
- package/dist/{useTheme-XMq9mNf_.js → useTheme-DYfby65G.js} +2 -2
- package/dist/{vega-component-CrFoZT1S.js → vega-component-C7nO5zcf.js} +10 -10
- package/dist/{zod-D0yOqoGe.js → zod-BUcyXSdR.js} +1 -1
- package/package.json +3 -4
- package/src/__mocks__/requests.ts +56 -4
- package/src/components/data-table/__tests__/__snapshots__/chart-spec-model.test.ts.snap +174 -21
- package/src/components/data-table/__tests__/chart-spec-model.test.ts +102 -1
- package/src/components/data-table/__tests__/columns.test.tsx +9 -1
- package/src/components/data-table/column-summary/chart-spec-model.tsx +163 -103
- package/src/components/data-table/column-summary/legacy-chart-spec.ts +15 -7
- package/src/components/data-table/column-summary/utils.ts +26 -7
- package/src/components/data-table/columns.tsx +11 -1
- package/src/components/data-table/types.ts +1 -1
- package/src/components/editor/__tests__/viewer-banner.test.tsx +14 -0
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +99 -18
- package/src/components/editor/actions/__tests__/pdf-export.test.ts +32 -13
- package/src/components/editor/actions/pair-with-agent-commands.ts +21 -10
- package/src/components/editor/actions/pair-with-agent-modal.tsx +2 -2
- package/src/components/editor/actions/pdf-export.ts +11 -14
- package/src/components/editor/actions/useNotebookActions.tsx +28 -19
- package/src/components/editor/cell/code/__tests__/cell-editor-activity-lifecycle.test.ts +250 -0
- package/src/components/editor/cell/code/cell-editor.tsx +13 -7
- package/src/components/editor/chrome/__tests__/state.test.ts +7 -1
- package/src/components/editor/chrome/panels/__tests__/logs-panel.test.tsx +25 -0
- package/src/components/editor/chrome/panels/__tests__/panel-accordion-state.test.ts +42 -0
- package/src/components/editor/chrome/panels/file-explorer-panel.tsx +6 -17
- package/src/components/editor/chrome/panels/logs-panel.tsx +12 -2
- package/src/components/editor/chrome/panels/panel-accordion-state.ts +51 -0
- package/src/components/editor/chrome/panels/session-panel.tsx +5 -16
- package/src/components/editor/chrome/panels/write-secret-modal.tsx +40 -12
- package/src/components/editor/chrome/state.ts +10 -0
- package/src/components/editor/connections/__tests__/json-credentials.test.ts +114 -0
- package/src/components/editor/connections/__tests__/paths.test.ts +39 -0
- package/src/components/editor/connections/__tests__/quick-add-data-sources.test.tsx +113 -0
- package/src/components/editor/connections/add-connection-dialog.tsx +2 -0
- package/src/components/editor/connections/components.tsx +6 -2
- package/src/components/editor/connections/database/__tests__/__snapshots__/as-code.test.ts.snap +27 -5
- package/src/components/editor/connections/database/__tests__/as-code.test.ts +33 -0
- package/src/components/editor/connections/database/as-code.ts +37 -13
- package/src/components/editor/connections/database/schemas.ts +6 -5
- package/src/components/editor/connections/form-renderers.tsx +99 -1
- package/src/components/editor/connections/json-credentials.ts +57 -0
- package/src/components/editor/connections/paths.ts +21 -0
- package/src/components/editor/connections/quick-add-data-sources.tsx +106 -0
- package/src/components/editor/connections/secret-combobox.tsx +41 -20
- package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +88 -2
- package/src/components/editor/connections/storage/__tests__/as-code.test.ts +128 -8
- package/src/components/editor/connections/storage/as-code.ts +79 -36
- package/src/components/editor/connections/storage/schemas.ts +85 -44
- package/src/components/editor/file-tree/__tests__/renderers.test.ts +31 -0
- package/src/components/editor/file-tree/file-viewer.tsx +11 -7
- package/src/components/editor/file-tree/renderers.tsx +21 -0
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +2 -2
- package/src/components/editor/viewer-banner.tsx +6 -1
- package/src/components/forms/form.tsx +59 -24
- package/src/components/forms/options.ts +2 -1
- package/src/components/slides/__tests__/cell-output-block.test.tsx +40 -0
- package/src/components/slides/__tests__/slide-cell-view.test.tsx +33 -0
- package/src/components/slides/reveal-component.tsx +22 -17
- package/src/components/slides/slide-cell-view.tsx +1 -1
- package/src/components/slides/slides.css +4 -2
- package/src/components/static-html/share-modal.tsx +1 -1
- package/src/components/ui/input.tsx +1 -1
- package/src/core/ai/context/providers/file.ts +8 -29
- package/src/core/cells/__tests__/cells.test.ts +12 -2
- package/src/core/cells/__tests__/logs.test.ts +5 -5
- package/src/core/cells/cells.ts +17 -4
- package/src/core/cells/document-changes.ts +1 -0
- package/src/core/cells/logs.ts +14 -4
- package/src/core/codemirror/cm.ts +3 -0
- package/src/core/codemirror/code-lens/__tests__/actions.test.ts +68 -0
- package/src/core/codemirror/code-lens/__tests__/analyzer.test.ts +224 -0
- package/src/core/codemirror/code-lens/__tests__/entities.test.ts +175 -0
- package/src/core/codemirror/code-lens/__tests__/extension.test.ts +325 -0
- package/src/core/codemirror/code-lens/__tests__/perf.test.ts +121 -0
- package/src/core/codemirror/code-lens/__tests__/popover.test.ts +93 -0
- package/src/core/codemirror/code-lens/actions.ts +33 -0
- package/src/core/codemirror/code-lens/analyzer.ts +284 -0
- package/src/core/codemirror/code-lens/entities.ts +123 -0
- package/src/core/codemirror/code-lens/extension.ts +298 -0
- package/src/core/codemirror/code-lens/icons.ts +36 -0
- package/src/core/codemirror/code-lens/popover.tsx +240 -0
- package/src/core/codemirror/copilot/__tests__/language-server.test.ts +396 -100
- package/src/core/codemirror/copilot/__tests__/transport.test.ts +128 -540
- package/src/core/codemirror/copilot/language-server.ts +182 -70
- package/src/core/codemirror/copilot/transport.ts +15 -207
- package/src/core/codemirror/copilot/types.ts +9 -2
- package/src/core/codemirror/find-replace/__tests__/navigate.test.ts +42 -30
- package/src/core/codemirror/find-replace/navigate.ts +2 -0
- package/src/core/codemirror/go-to-definition/__tests__/commands.test.ts +24 -3
- package/src/core/codemirror/go-to-definition/commands.ts +93 -90
- package/src/core/codemirror/keymaps/__tests__/keymaps.test.ts +31 -2
- package/src/core/codemirror/keymaps/keymaps.ts +12 -0
- package/src/core/codemirror/language/__tests__/sql.test.ts +627 -61
- package/src/core/codemirror/language/languages/python.ts +23 -11
- package/src/core/codemirror/language/languages/sql/completion-store.ts +5 -1
- package/src/core/codemirror/language/languages/sql/renderers.tsx +3 -3
- package/src/core/codemirror/language/languages/sql/sql.ts +251 -16
- package/src/core/codemirror/lsp/__tests__/federated-lsp.test.ts +161 -0
- package/src/core/codemirror/lsp/__tests__/log-messages.test.ts +53 -0
- package/src/core/codemirror/lsp/__tests__/normalize-markdown-math.test.ts +25 -0
- package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +598 -156
- package/src/core/codemirror/lsp/__tests__/transports.test.ts +112 -0
- package/src/core/codemirror/lsp/federated-lsp.ts +76 -23
- package/src/core/codemirror/lsp/log-messages.ts +37 -0
- package/src/core/codemirror/lsp/normalize-markdown-math.ts +8 -4
- package/src/core/codemirror/lsp/notebook-lsp.ts +220 -56
- package/src/core/codemirror/lsp/transports.ts +8 -1
- package/src/core/codemirror/lsp/types.ts +68 -8
- package/src/core/codemirror/lsp/utils.ts +14 -0
- package/src/core/codemirror/python-node-names.ts +90 -0
- package/src/core/codemirror/reactive-references/analyzer.ts +52 -57
- package/src/core/codemirror/utils.ts +28 -0
- package/src/core/config/feature-flag.tsx +3 -1
- package/src/core/datasets/data-source-discovery.ts +5 -0
- package/src/core/datasets/request-registry.ts +11 -0
- package/src/core/edit-app.tsx +1 -2
- package/src/core/export/__tests__/hooks.test.ts +2 -4
- package/src/core/export/hooks.ts +4 -3
- package/src/core/hotkeys/hotkeys.ts +0 -2
- package/src/core/islands/__tests__/bridge.test.ts +107 -0
- package/src/core/islands/bootstrap.ts +1 -0
- package/src/core/islands/bridge.ts +18 -0
- package/src/core/kernel/messages.ts +2 -0
- package/src/core/lsp/__tests__/transport.test.ts +509 -337
- package/src/core/lsp/transport.ts +304 -132
- package/src/core/mode.ts +10 -0
- package/src/core/network/__tests__/api.test.ts +42 -0
- package/src/core/network/__tests__/connection.test.ts +45 -0
- package/src/core/network/__tests__/requests-lazy.test.ts +47 -1
- package/src/core/network/__tests__/requests-network.test.ts +48 -0
- package/src/core/network/api.ts +27 -0
- package/src/core/network/connection.ts +15 -0
- package/src/core/network/requests-lazy.ts +3 -0
- package/src/core/network/requests-network.ts +31 -8
- package/src/core/network/requests-static.ts +3 -0
- package/src/core/network/requests-toasting.tsx +3 -0
- package/src/core/network/types.ts +27 -6
- package/src/core/rtc/state.ts +9 -0
- package/src/core/static/download-html.ts +5 -14
- package/src/core/wasm/__tests__/bridge.test.ts +99 -5
- package/src/core/wasm/bridge.ts +42 -9
- package/src/core/wasm/worker/types.ts +10 -3
- package/src/core/wasm/worker/worker.ts +7 -8
- package/src/core/websocket/useMarimoKernelConnection.tsx +4 -0
- package/src/css/app/codemirror.css +16 -2
- package/src/hooks/__tests__/useDataSourceDiscovery.test.ts +80 -0
- package/src/hooks/useDataSourceDiscovery.ts +18 -0
- package/src/plugins/impl/DataTablePlugin.tsx +4 -1
- package/src/plugins/impl/FormPlugin.tsx +1 -1
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +158 -0
- package/src/plugins/impl/__tests__/FormPlugin.test.tsx +162 -0
- package/src/plugins/impl/anywidget/__tests__/model.test.ts +55 -0
- package/src/plugins/impl/anywidget/model.ts +20 -5
- package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +33 -21
- package/src/plugins/impl/data-frames/forms/renderers.tsx +11 -8
- package/src/stories/log-viewer.stories.tsx +12 -6
- package/src/utils/__tests__/blob.test.ts +27 -1
- package/src/utils/__tests__/download.test.tsx +44 -4
- package/src/utils/blob.ts +24 -1
- package/src/utils/download.ts +14 -15
- package/dist/toDate-B3IRfHhw.js +0 -692
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
+
import { InfoIcon } from "lucide-react";
|
|
3
4
|
import { createContext, type ReactNode, use, useMemo } from "react";
|
|
4
5
|
import { z } from "zod";
|
|
5
6
|
import type { FormRenderer } from "@/components/forms/form";
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
FormLabel,
|
|
14
15
|
FormMessage,
|
|
15
16
|
} from "@/components/ui/form";
|
|
17
|
+
import { Tooltip } from "@/components/ui/tooltip";
|
|
16
18
|
import { SECRETS_REGISTRY } from "@/core/secrets/request-registry";
|
|
17
19
|
import { useAsyncData } from "@/hooks/useAsyncData";
|
|
18
20
|
import { Functions } from "@/utils/functions";
|
|
@@ -20,8 +22,10 @@ import {
|
|
|
20
22
|
sortProviders,
|
|
21
23
|
WriteSecretModal,
|
|
22
24
|
} from "../chrome/panels/write-secret-modal";
|
|
25
|
+
import { looksLikeJson } from "./json-credentials";
|
|
26
|
+
import { isPath, prefixPath, unprefixPath } from "./paths";
|
|
23
27
|
import { partitionSecretKeys, SecretCombobox } from "./secret-combobox";
|
|
24
|
-
import { prefixSecret } from "./secrets";
|
|
28
|
+
import { isSecret, prefixSecret } from "./secrets";
|
|
25
29
|
|
|
26
30
|
interface SecretsContextType {
|
|
27
31
|
providerNames: string[];
|
|
@@ -141,3 +145,97 @@ export const ENV_RENDERER: FormRenderer<z.ZodString> = {
|
|
|
141
145
|
);
|
|
142
146
|
},
|
|
143
147
|
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* For credentials that shouldn't be baked into the generated notebook code
|
|
151
|
+
* (e.g. a service-account JSON key).
|
|
152
|
+
*/
|
|
153
|
+
export const SECRET_TEXTAREA_RENDERER: FormRenderer<z.ZodString> = {
|
|
154
|
+
isMatch: (schema: z.ZodType): schema is z.ZodString => {
|
|
155
|
+
if (schema instanceof z.ZodString) {
|
|
156
|
+
const { special } = FieldOptions.parse(schema.description || "");
|
|
157
|
+
return special === "secret_textarea";
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
},
|
|
161
|
+
Component: ({ schema, form, path }) => {
|
|
162
|
+
const { secretKeys, providerNames, refreshSecrets } = useSecrets();
|
|
163
|
+
const { openModal, closeModal } = useImperativeModal();
|
|
164
|
+
const { label, description, placeholder } = FieldOptions.parse(
|
|
165
|
+
schema.description || "",
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<FormField
|
|
170
|
+
control={form.control}
|
|
171
|
+
name={path}
|
|
172
|
+
render={({ field }) => {
|
|
173
|
+
const value = field.value ? String(field.value) : "";
|
|
174
|
+
// Non-secret values are file paths; display them unprefixed.
|
|
175
|
+
const displayValue = isPath(value) ? unprefixPath(value) : value;
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<FormItem>
|
|
179
|
+
<FormLabel className="flex items-center gap-1">
|
|
180
|
+
{label}
|
|
181
|
+
<Tooltip
|
|
182
|
+
content="Enter a path to a file on disk, or create a secret to paste JSON directly."
|
|
183
|
+
delayDuration={300}
|
|
184
|
+
>
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
className="inline-flex"
|
|
188
|
+
aria-label="Credential input help"
|
|
189
|
+
>
|
|
190
|
+
<InfoIcon className="h-3.5 w-3.5" />
|
|
191
|
+
</button>
|
|
192
|
+
</Tooltip>
|
|
193
|
+
</FormLabel>
|
|
194
|
+
<FormDescription>{description}</FormDescription>
|
|
195
|
+
<FormControl>
|
|
196
|
+
<SecretCombobox
|
|
197
|
+
value={displayValue}
|
|
198
|
+
onChange={(next) => {
|
|
199
|
+
field.onChange(
|
|
200
|
+
!next || isSecret(next) || isPath(next)
|
|
201
|
+
? next
|
|
202
|
+
: prefixPath(next),
|
|
203
|
+
);
|
|
204
|
+
}}
|
|
205
|
+
placeholder={placeholder}
|
|
206
|
+
searchPlaceholder="Type a file path or paste JSON"
|
|
207
|
+
formatCustomValueLabel={(custom) =>
|
|
208
|
+
`Use "${custom}" as a file path`
|
|
209
|
+
}
|
|
210
|
+
createSecretLabel="Paste JSON credentials"
|
|
211
|
+
allowCustomValue={(search) => !looksLikeJson(search)}
|
|
212
|
+
allowCreateSecret={(search) =>
|
|
213
|
+
!search || looksLikeJson(search)
|
|
214
|
+
}
|
|
215
|
+
recommendedKeys={[]}
|
|
216
|
+
otherKeys={secretKeys}
|
|
217
|
+
onCreateSecret={(suggestedValue) => {
|
|
218
|
+
openModal(
|
|
219
|
+
<WriteSecretModal
|
|
220
|
+
providerNames={providerNames}
|
|
221
|
+
initialValue={suggestedValue}
|
|
222
|
+
multiline={true}
|
|
223
|
+
onSuccess={(secretKey) => {
|
|
224
|
+
refreshSecrets();
|
|
225
|
+
field.onChange(prefixSecret(secretKey));
|
|
226
|
+
closeModal();
|
|
227
|
+
}}
|
|
228
|
+
onClose={closeModal}
|
|
229
|
+
/>,
|
|
230
|
+
);
|
|
231
|
+
}}
|
|
232
|
+
/>
|
|
233
|
+
</FormControl>
|
|
234
|
+
<FormMessage />
|
|
235
|
+
</FormItem>
|
|
236
|
+
);
|
|
237
|
+
}}
|
|
238
|
+
/>
|
|
239
|
+
);
|
|
240
|
+
},
|
|
241
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { isPath, unprefixPath } from "./paths";
|
|
3
|
+
import { isSecret } from "./secrets";
|
|
4
|
+
|
|
5
|
+
export type JsonCredential =
|
|
6
|
+
| { kind: "path"; path: string }
|
|
7
|
+
| { kind: "json"; expr: string };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolves a `secret_textarea` field's value — a file path, a secret
|
|
11
|
+
* reference, or a raw literal — into either a path to hand to a
|
|
12
|
+
* provider's path-based auth kwarg, or a `json.loads(...)` expression.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveJsonCredential(
|
|
15
|
+
value: string,
|
|
16
|
+
printSecret: (value: string) => string,
|
|
17
|
+
): JsonCredential {
|
|
18
|
+
if (isPath(value)) {
|
|
19
|
+
return { kind: "path", path: unprefixPath(value) };
|
|
20
|
+
}
|
|
21
|
+
const expr = isSecret(value)
|
|
22
|
+
? `json.loads(${printSecret(value)})`
|
|
23
|
+
: // Use raw string to avoid interpreting \n escapes as newlines.
|
|
24
|
+
`json.loads(r"""${value}""")`;
|
|
25
|
+
return { kind: "json", expr };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function looksLikeJson(value: string): boolean {
|
|
29
|
+
const trimmed = value.trimStart();
|
|
30
|
+
return trimmed.startsWith("{") || trimmed.startsWith("[");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Collapse a credential value to a single dotenv-safe line: minify JSON
|
|
35
|
+
* when parseable, otherwise strip newlines.
|
|
36
|
+
*/
|
|
37
|
+
export function flattenSecretValue(value: string): string {
|
|
38
|
+
const trimmed = value.trim();
|
|
39
|
+
if (!trimmed) {
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
return JSON.stringify(JSON.parse(trimmed));
|
|
44
|
+
} catch {
|
|
45
|
+
return value.replaceAll(/\r?\n/g, "");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Escape a filesystem path for embedding in a Python double-quoted string. */
|
|
50
|
+
export function escapePythonString(value: string): string {
|
|
51
|
+
return value
|
|
52
|
+
.replaceAll("\\", "\\\\")
|
|
53
|
+
.replaceAll('"', '\\"')
|
|
54
|
+
.replaceAll("\n", "\\n")
|
|
55
|
+
.replaceAll("\r", "\\r")
|
|
56
|
+
.replaceAll("\t", "\\t");
|
|
57
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import type { TypedString } from "@/utils/typed";
|
|
3
|
+
|
|
4
|
+
const PREFIX = "path:";
|
|
5
|
+
|
|
6
|
+
export type PathPlaceholder = TypedString<"path">;
|
|
7
|
+
|
|
8
|
+
export function isPath(value: unknown): value is PathPlaceholder {
|
|
9
|
+
if (typeof value !== "string") {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
return value.startsWith(PREFIX);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function prefixPath(value: string): PathPlaceholder {
|
|
16
|
+
return `${PREFIX}${value}` as PathPlaceholder;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function unprefixPath(value: PathPlaceholder): string {
|
|
20
|
+
return value.slice(PREFIX.length);
|
|
21
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { PlusIcon, SparklesIcon } from "lucide-react";
|
|
4
|
+
import { Tooltip, TooltipProvider } from "@/components/ui/tooltip";
|
|
5
|
+
import type { DetectedDataSource } from "@/core/datasets/data-source-discovery";
|
|
6
|
+
import { useDataSourceDiscovery } from "@/hooks/useDataSourceDiscovery";
|
|
7
|
+
import { useInsertCode } from "./components";
|
|
8
|
+
|
|
9
|
+
export const QuickAddDataSources: React.FC<{
|
|
10
|
+
sources: DetectedDataSource[];
|
|
11
|
+
onAdd: (source: DetectedDataSource) => void;
|
|
12
|
+
}> = ({ sources, onAdd }) => {
|
|
13
|
+
if (sources.length === 0) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<section
|
|
19
|
+
aria-labelledby="quick-add-data-sources-title"
|
|
20
|
+
className="rounded-md border bg-muted/30 px-3 py-2"
|
|
21
|
+
>
|
|
22
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
23
|
+
<div className="mr-1 flex items-center gap-1.5">
|
|
24
|
+
<SparklesIcon className="h-3.5 w-3.5 text-muted-foreground" />
|
|
25
|
+
<h3 id="quick-add-data-sources-title" className="text-sm font-medium">
|
|
26
|
+
Quick add
|
|
27
|
+
</h3>
|
|
28
|
+
</div>
|
|
29
|
+
<TooltipProvider delayDuration={200}>
|
|
30
|
+
{sources.map((source) => (
|
|
31
|
+
<Tooltip
|
|
32
|
+
key={source.id}
|
|
33
|
+
side="bottom"
|
|
34
|
+
content={<DetectedDataSourceDetails source={source} />}
|
|
35
|
+
>
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
aria-label={`Add ${source.displayName} connection`}
|
|
39
|
+
className="inline-flex items-center gap-1 rounded-full border border-(--blue-8) bg-(--blue-2) px-2.5 py-1 text-xs font-semibold text-(--blue-11) transition-colors hover:bg-(--blue-3) focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
|
40
|
+
onClick={() => onAdd(source)}
|
|
41
|
+
>
|
|
42
|
+
<PlusIcon className="h-3 w-3" />
|
|
43
|
+
{source.displayName}
|
|
44
|
+
</button>
|
|
45
|
+
</Tooltip>
|
|
46
|
+
))}
|
|
47
|
+
</TooltipProvider>
|
|
48
|
+
</div>
|
|
49
|
+
</section>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const DetectedDataSourceDetails: React.FC<{
|
|
54
|
+
source: DetectedDataSource;
|
|
55
|
+
}> = ({ source }) => (
|
|
56
|
+
<div className="min-w-64 space-y-2 py-1">
|
|
57
|
+
<div>
|
|
58
|
+
<div className="font-medium">{source.displayName}</div>
|
|
59
|
+
<div className="text-xs text-muted-foreground">
|
|
60
|
+
Detected from{" "}
|
|
61
|
+
{source.origins.map((origin) => origin.label).join(" and ")}
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<dl className="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 text-xs">
|
|
65
|
+
{source.configuration.map((item) => (
|
|
66
|
+
<div
|
|
67
|
+
className="contents"
|
|
68
|
+
key={
|
|
69
|
+
item.value.kind === "environment-variable"
|
|
70
|
+
? item.value.name
|
|
71
|
+
: `${item.field}:${item.value.value}`
|
|
72
|
+
}
|
|
73
|
+
>
|
|
74
|
+
<dt className="text-muted-foreground">{item.field}</dt>
|
|
75
|
+
<dd>
|
|
76
|
+
<code>
|
|
77
|
+
{item.value.kind === "environment-variable"
|
|
78
|
+
? `os.environ["${item.value.name}"]`
|
|
79
|
+
: item.value.value}
|
|
80
|
+
</code>
|
|
81
|
+
</dd>
|
|
82
|
+
</div>
|
|
83
|
+
))}
|
|
84
|
+
</dl>
|
|
85
|
+
<div className="text-xs text-muted-foreground">
|
|
86
|
+
Click to add a configured cell.
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
export const AutoDiscoveredDataSources: React.FC<{
|
|
92
|
+
onSubmit: () => void;
|
|
93
|
+
}> = ({ onSubmit }) => {
|
|
94
|
+
const insertCode = useInsertCode();
|
|
95
|
+
const { data } = useDataSourceDiscovery();
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<QuickAddDataSources
|
|
99
|
+
sources={data ?? []}
|
|
100
|
+
onAdd={(source) => {
|
|
101
|
+
insertCode(source.code);
|
|
102
|
+
onSubmit();
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
@@ -56,6 +56,16 @@ interface SecretComboboxProps {
|
|
|
56
56
|
/** Opens the create-secret flow; `suggestedValue` is the current search text when present. */
|
|
57
57
|
onCreateSecret: (suggestedValue?: string) => void;
|
|
58
58
|
className?: string;
|
|
59
|
+
/** Placeholder for the search input inside the popover. */
|
|
60
|
+
searchPlaceholder?: string;
|
|
61
|
+
/** Label for the "commit a free-text literal" item, e.g. `Use "${value}" as a path`. */
|
|
62
|
+
formatCustomValueLabel?: (value: string) => string;
|
|
63
|
+
/** Label for the "create a new secret" item. */
|
|
64
|
+
createSecretLabel?: string;
|
|
65
|
+
/** When false for the current search, hide the free-text / path option. */
|
|
66
|
+
allowCustomValue?: (search: string) => boolean;
|
|
67
|
+
/** When false for the current search, hide the "create a new secret" option. */
|
|
68
|
+
allowCreateSecret?: (search: string) => boolean;
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
/**
|
|
@@ -73,6 +83,11 @@ export const SecretCombobox: React.FC<SecretComboboxProps> = ({
|
|
|
73
83
|
otherKeys,
|
|
74
84
|
onCreateSecret,
|
|
75
85
|
className,
|
|
86
|
+
searchPlaceholder,
|
|
87
|
+
formatCustomValueLabel = (custom) => `Use "${custom}"`,
|
|
88
|
+
createSecretLabel = "Create a new secret",
|
|
89
|
+
allowCustomValue,
|
|
90
|
+
allowCreateSecret,
|
|
76
91
|
}) => {
|
|
77
92
|
const [open, setOpen] = useState(false);
|
|
78
93
|
const [search, setSearch] = useState("");
|
|
@@ -81,7 +96,11 @@ export const SecretCombobox: React.FC<SecretComboboxProps> = ({
|
|
|
81
96
|
// Non-secret fields may commit a literal even when it collides with an
|
|
82
97
|
// existing secret key, so we intentionally don't filter out matches here.
|
|
83
98
|
const showCustomValue =
|
|
84
|
-
!secretsOnly &&
|
|
99
|
+
!secretsOnly &&
|
|
100
|
+
trimmedSearch.length > 0 &&
|
|
101
|
+
trimmedSearch !== value &&
|
|
102
|
+
(allowCustomValue?.(trimmedSearch) ?? true);
|
|
103
|
+
const showCreateSecret = allowCreateSecret?.(trimmedSearch) ?? true;
|
|
85
104
|
|
|
86
105
|
const displayValue = (() => {
|
|
87
106
|
if (!value) {
|
|
@@ -158,9 +177,10 @@ export const SecretCombobox: React.FC<SecretComboboxProps> = ({
|
|
|
158
177
|
<Command>
|
|
159
178
|
<CommandInput
|
|
160
179
|
placeholder={
|
|
161
|
-
|
|
180
|
+
searchPlaceholder ??
|
|
181
|
+
(secretsOnly
|
|
162
182
|
? "Search secrets..."
|
|
163
|
-
: "Type a value or search secrets..."
|
|
183
|
+
: "Type a value or search secrets...")
|
|
164
184
|
}
|
|
165
185
|
rootClassName="px-1 h-8"
|
|
166
186
|
autoFocus={true}
|
|
@@ -174,26 +194,27 @@ export const SecretCombobox: React.FC<SecretComboboxProps> = ({
|
|
|
174
194
|
value={`use custom value ${trimmedSearch}`}
|
|
175
195
|
onSelect={() => selectCustom(trimmedSearch)}
|
|
176
196
|
>
|
|
177
|
-
|
|
197
|
+
{formatCustomValueLabel(trimmedSearch)}
|
|
198
|
+
</CommandItem>
|
|
199
|
+
</CommandGroup>
|
|
200
|
+
)}
|
|
201
|
+
{showCreateSecret && (
|
|
202
|
+
<CommandGroup className="mt-0">
|
|
203
|
+
<CommandItem
|
|
204
|
+
// Include search so this stays visible while filtering
|
|
205
|
+
value={`create new secret ${trimmedSearch}`}
|
|
206
|
+
onSelect={() => {
|
|
207
|
+
const suggestedValue = trimmedSearch || undefined;
|
|
208
|
+
setOpen(false);
|
|
209
|
+
setSearch("");
|
|
210
|
+
onCreateSecret(suggestedValue);
|
|
211
|
+
}}
|
|
212
|
+
>
|
|
213
|
+
<PlusCircleIcon className="mr-2 h-3.5 w-3.5" />
|
|
214
|
+
{createSecretLabel}
|
|
178
215
|
</CommandItem>
|
|
179
216
|
</CommandGroup>
|
|
180
217
|
)}
|
|
181
|
-
{showCustomValue && <CommandSeparator />}
|
|
182
|
-
<CommandGroup className="mt-0">
|
|
183
|
-
<CommandItem
|
|
184
|
-
// Include search so this stays visible while filtering
|
|
185
|
-
value={`create new secret ${trimmedSearch}`}
|
|
186
|
-
onSelect={() => {
|
|
187
|
-
const suggestedValue = trimmedSearch || undefined;
|
|
188
|
-
setOpen(false);
|
|
189
|
-
setSearch("");
|
|
190
|
-
onCreateSecret(suggestedValue);
|
|
191
|
-
}}
|
|
192
|
-
>
|
|
193
|
-
<PlusCircleIcon className="mr-2 h-3.5 w-3.5" />
|
|
194
|
-
Create a new secret
|
|
195
|
-
</CommandItem>
|
|
196
|
-
</CommandGroup>
|
|
197
218
|
{recommendedKeys.length > 0 && (
|
|
198
219
|
<>
|
|
199
220
|
<CommandSeparator className="mt-0" />
|
package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap
CHANGED
|
@@ -51,6 +51,21 @@ store = S3Store("operator-bucket",
|
|
|
51
51
|
)"
|
|
52
52
|
`;
|
|
53
53
|
|
|
54
|
+
exports[`generateStorageCode > CoreWeave > with container credentials 1`] = `
|
|
55
|
+
"from obstore.store import S3Store
|
|
56
|
+
import os
|
|
57
|
+
|
|
58
|
+
_container_credentials_full_uri = os.environ.get("AWS_CONTAINER_CREDENTIALS_FULL_URI")
|
|
59
|
+
_container_authorization_token_file = os.environ.get("AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE")
|
|
60
|
+
store = S3Store("operator-bucket",
|
|
61
|
+
region="US-EAST-04A",
|
|
62
|
+
container_credentials_full_uri=_container_credentials_full_uri,
|
|
63
|
+
container_authorization_token_file=_container_authorization_token_file,
|
|
64
|
+
endpoint="https://operator-bucket.cwobject.com",
|
|
65
|
+
virtual_hosted_style_request=True,
|
|
66
|
+
)"
|
|
67
|
+
`;
|
|
68
|
+
|
|
54
69
|
exports[`generateStorageCode > CoreWeave > with secrets 1`] = `
|
|
55
70
|
"from obstore.store import S3Store
|
|
56
71
|
import os
|
|
@@ -70,7 +85,27 @@ exports[`generateStorageCode > GCS > with service account key 1`] = `
|
|
|
70
85
|
"from obstore.store import GCSStore
|
|
71
86
|
import json
|
|
72
87
|
|
|
73
|
-
_credentials = json.loads("""{"type": "service_account", "project_id": "test"}""")
|
|
88
|
+
_credentials = json.loads(r"""{"type": "service_account", "project_id": "test"}""")
|
|
89
|
+
store = GCSStore("my-bucket",
|
|
90
|
+
service_account_key=_credentials,
|
|
91
|
+
)"
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
exports[`generateStorageCode > GCS > with service account key from a file path 1`] = `
|
|
95
|
+
"from obstore.store import GCSStore
|
|
96
|
+
|
|
97
|
+
store = GCSStore("my-bucket",
|
|
98
|
+
service_account_path="/etc/secrets/gcs.json",
|
|
99
|
+
)"
|
|
100
|
+
`;
|
|
101
|
+
|
|
102
|
+
exports[`generateStorageCode > GCS > with service account key from secrets 1`] = `
|
|
103
|
+
"from obstore.store import GCSStore
|
|
104
|
+
import json
|
|
105
|
+
import os
|
|
106
|
+
|
|
107
|
+
_service_account_key = os.environ.get("GCS_SERVICE_ACCOUNT_KEY")
|
|
108
|
+
_credentials = json.loads(_service_account_key)
|
|
74
109
|
store = GCSStore("my-bucket",
|
|
75
110
|
service_account_key=_credentials,
|
|
76
111
|
)"
|
|
@@ -99,7 +134,23 @@ exports[`generateStorageCode > Google Drive > with service account credentials 1
|
|
|
99
134
|
"from gdrive_fsspec import GoogleDriveFileSystem
|
|
100
135
|
import json
|
|
101
136
|
|
|
102
|
-
_creds = json.loads("""{"type": "service_account", "client_email": "test@test.iam.gserviceaccount.com"}""")
|
|
137
|
+
_creds = json.loads(r"""{"type": "service_account", "client_email": "test@test.iam.gserviceaccount.com"}""")
|
|
138
|
+
fs = GoogleDriveFileSystem(creds=_creds, token="service_account", use_listings_cache=False, skip_instance_cache=True)"
|
|
139
|
+
`;
|
|
140
|
+
|
|
141
|
+
exports[`generateStorageCode > Google Drive > with service account credentials from a file path 1`] = `
|
|
142
|
+
"from gdrive_fsspec import GoogleDriveFileSystem
|
|
143
|
+
|
|
144
|
+
fs = GoogleDriveFileSystem(creds="/etc/secrets/gdrive.json", token="service_account", use_listings_cache=False, skip_instance_cache=True)"
|
|
145
|
+
`;
|
|
146
|
+
|
|
147
|
+
exports[`generateStorageCode > Google Drive > with service account credentials from secrets 1`] = `
|
|
148
|
+
"from gdrive_fsspec import GoogleDriveFileSystem
|
|
149
|
+
import json
|
|
150
|
+
import os
|
|
151
|
+
|
|
152
|
+
_credentials_json = os.environ.get("GDRIVE_CREDENTIALS_JSON")
|
|
153
|
+
_creds = json.loads(_credentials_json)
|
|
103
154
|
fs = GoogleDriveFileSystem(creds=_creds, token="service_account", use_listings_cache=False, skip_instance_cache=True)"
|
|
104
155
|
`;
|
|
105
156
|
|
|
@@ -119,6 +170,29 @@ exports[`generateStorageCode > S3 > minimal connection (bucket only) 1`] = `
|
|
|
119
170
|
store = S3Store("my-bucket",)"
|
|
120
171
|
`;
|
|
121
172
|
|
|
173
|
+
exports[`generateStorageCode > S3 > with container credentials 1`] = `
|
|
174
|
+
"from obstore.store import S3Store
|
|
175
|
+
|
|
176
|
+
store = S3Store("my-bucket",
|
|
177
|
+
region="us-east-1",
|
|
178
|
+
container_credentials_full_uri="http://169.254.170.23/v1/credentials",
|
|
179
|
+
container_authorization_token_file="/var/run/secrets/token",
|
|
180
|
+
)"
|
|
181
|
+
`;
|
|
182
|
+
|
|
183
|
+
exports[`generateStorageCode > S3 > with container credentials from secrets 1`] = `
|
|
184
|
+
"from obstore.store import S3Store
|
|
185
|
+
import os
|
|
186
|
+
|
|
187
|
+
_container_credentials_full_uri = os.environ.get("AWS_CONTAINER_CREDENTIALS_FULL_URI")
|
|
188
|
+
_container_authorization_token_file = os.environ.get("AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE")
|
|
189
|
+
store = S3Store("my-bucket",
|
|
190
|
+
region="us-east-1",
|
|
191
|
+
container_credentials_full_uri=_container_credentials_full_uri,
|
|
192
|
+
container_authorization_token_file=_container_authorization_token_file,
|
|
193
|
+
)"
|
|
194
|
+
`;
|
|
195
|
+
|
|
122
196
|
exports[`generateStorageCode > S3 > with custom endpoint 1`] = `
|
|
123
197
|
"from obstore.store import S3Store
|
|
124
198
|
|
|
@@ -130,6 +204,18 @@ store = S3Store("my-bucket",
|
|
|
130
204
|
)"
|
|
131
205
|
`;
|
|
132
206
|
|
|
207
|
+
exports[`generateStorageCode > S3 > with plain-http endpoint 1`] = `
|
|
208
|
+
"from obstore.store import S3Store
|
|
209
|
+
|
|
210
|
+
store = S3Store("my-bucket",
|
|
211
|
+
region="us-east-1",
|
|
212
|
+
access_key_id="AKIAIOSFODNN7EXAMPLE",
|
|
213
|
+
secret_access_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
214
|
+
endpoint_url="http://cwlota.com",
|
|
215
|
+
allow_http=True,
|
|
216
|
+
)"
|
|
217
|
+
`;
|
|
218
|
+
|
|
133
219
|
exports[`generateStorageCode > S3 > with secrets 1`] = `
|
|
134
220
|
"from obstore.store import S3Store
|
|
135
221
|
import os
|