@marimo-team/islands 0.24.1-dev3 → 0.24.1-dev31

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.
Files changed (72) hide show
  1. package/dist/{ConnectedDataExplorerComponent-BUC1MrsF.js → ConnectedDataExplorerComponent-yEsovh7C.js} +4 -4
  2. package/dist/arrays-Ct6XviUd.js +59 -0
  3. package/dist/{capabilities-BilXeZ7p.js → capabilities-DNKeBoT5.js} +1 -1
  4. package/dist/{chat-ui-ChBRy1iT.js → chat-ui-ChbH2I73.js} +3 -3
  5. package/dist/{common-BvTQu-LJ.js → common-ZoATHjIZ.js} +17 -11
  6. package/dist/{formats-Dz0QNUEa.js → formats-BZtMpeQe.js} +1 -1
  7. package/dist/{glide-data-editor-BI2i_IEE.js → glide-data-editor-aimC_hok.js} +3 -1
  8. package/dist/{html-to-image-BtNr4zrO.js → html-to-image-BzT-fb14.js} +2357 -2325
  9. package/dist/{input-BRE2a7Ay.js → input-CqV2ZPgL.js} +1 -1
  10. package/dist/{loader-DTnbNSVZ.js → loader-Chm6nOqy.js} +1 -1
  11. package/dist/main.js +1196 -1088
  12. package/dist/{once-Boutk_9X.js → once-D1_qYl2e.js} +1 -1
  13. package/dist/{process-output-Dch8KCi8.js → process-output-C8Q8G6-g.js} +1 -1
  14. package/dist/{react-vega-zyfTF9SY.js → react-vega-BE2Uv1cq.js} +1 -1
  15. package/dist/{reveal-component-qNkzuPp3.js → reveal-component-CBpmFHpU.js} +5 -5
  16. package/dist/{spec-BL1GSUUF.js → spec-DR1CyTOo.js} +2 -0
  17. package/dist/style.css +1 -1
  18. package/dist/{swiper-component-Cx0cRWLk.js → swiper-component-UYicacis.js} +1 -1
  19. package/dist/{useIframeCapabilities-CxuXXwjj.js → useIframeCapabilities-BbnoLKxG.js} +1 -1
  20. package/dist/{vega-component-CioKuDut.js → vega-component-xrHju6oo.js} +5 -5
  21. package/dist/{vega-embed-container-BRjjqRGz.js → vega-embed-container-B1tc-DoN.js} +2 -2
  22. package/dist/{vega-loader.browser-CGb-2Kwi.js → vega-loader.browser-VFQfHaoV.js} +1 -1
  23. package/package.json +1 -1
  24. package/src/components/app-config/optional-features.test.ts +78 -0
  25. package/src/components/app-config/optional-features.tsx +33 -6
  26. package/src/components/data-table/__tests__/column_formatting.test.ts +7 -0
  27. package/src/components/data-table/charts/__tests__/schemas.test.ts +17 -2
  28. package/src/components/data-table/charts/chart-spec/types.ts +2 -0
  29. package/src/components/data-table/column-formatting/feature.ts +1 -0
  30. package/src/components/data-table/column-formatting/types.ts +1 -0
  31. package/src/components/data-table/column-summary/chart-spec-model.tsx +1 -0
  32. package/src/components/data-table/column-summary/column-summary.tsx +1 -0
  33. package/src/components/data-table/columns.tsx +4 -3
  34. package/src/components/datasets/__tests__/icons.test.ts +10 -2
  35. package/src/components/datasets/icons.tsx +4 -0
  36. package/src/components/datasources/__tests__/components.test.tsx +14 -1
  37. package/src/components/datasources/datasources.tsx +2 -40
  38. package/src/components/editor/chrome/panels/components.tsx +35 -0
  39. package/src/components/editor/chrome/panels/file-explorer-panel.tsx +10 -0
  40. package/src/components/editor/chrome/panels/session-panel.tsx +12 -4
  41. package/src/components/editor/connections/__tests__/quick-add-data-sources.test.tsx +2 -0
  42. package/src/components/editor/connections/add-connection-dialog.tsx +5 -1
  43. package/src/components/editor/connections/quick-add-data-sources.tsx +11 -7
  44. package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +43 -0
  45. package/src/components/editor/connections/storage/__tests__/as-code.test.ts +127 -0
  46. package/src/components/editor/connections/storage/add-storage-form.tsx +11 -1
  47. package/src/components/editor/connections/storage/as-code.ts +30 -1
  48. package/src/components/editor/connections/storage/schemas.ts +63 -0
  49. package/src/core/codemirror/code-lens/__tests__/popover.test.ts +13 -0
  50. package/src/core/codemirror/code-lens/popover.tsx +11 -5
  51. package/src/core/codemirror/language/languages/sql/__tests__/renderers.test.tsx +15 -4
  52. package/src/core/codemirror/language/languages/sql/renderers.tsx +1 -0
  53. package/src/core/datasets/__tests__/data-source-discovery.test.ts +277 -0
  54. package/src/core/datasets/data-source-connections.ts +36 -0
  55. package/src/core/datasets/data-source-discovery.ts +92 -0
  56. package/src/core/kernel/messages.ts +1 -0
  57. package/src/core/websocket/useMarimoKernelConnection.tsx +11 -3
  58. package/src/hooks/useDataSourceDiscovery.ts +50 -9
  59. package/src/plugins/impl/MatrixPlugin.tsx +256 -35
  60. package/src/plugins/impl/__tests__/DataEditorPlugin.test.ts +13 -1
  61. package/src/plugins/impl/__tests__/MatrixPlugin.test.tsx +540 -3
  62. package/src/plugins/impl/data-editor/glide-data-editor.tsx +1 -0
  63. package/src/plugins/impl/data-editor/glide-utils.ts +1 -0
  64. package/src/plugins/impl/data-frames/__tests__/schema.test.ts +9 -1
  65. package/src/plugins/impl/data-frames/utils/__tests__/operators.test.ts +8 -0
  66. package/src/plugins/impl/data-frames/utils/operators.ts +1 -1
  67. package/src/plugins/impl/matplotlib/__tests__/matplotlib-renderer.test.ts +184 -24
  68. package/src/plugins/impl/matplotlib/matplotlib-renderer.ts +40 -7
  69. package/src/plugins/impl/vega/__tests__/vega.test.ts +22 -4
  70. package/src/plugins/impl/vega/utils.ts +3 -0
  71. package/dist/arrays-C56grfjL.js +0 -55
  72. package/src/hooks/__tests__/useDataSourceDiscovery.test.ts +0 -80
@@ -2,8 +2,11 @@
2
2
 
3
3
  import { PlusIcon, SparklesIcon } from "lucide-react";
4
4
  import { Tooltip, TooltipProvider } from "@/components/ui/tooltip";
5
- import type { DetectedDataSource } from "@/core/datasets/data-source-discovery";
6
- import { useDataSourceDiscovery } from "@/hooks/useDataSourceDiscovery";
5
+ import type {
6
+ DataSourceDiscoveryGroup,
7
+ DetectedDataSource,
8
+ } from "@/core/datasets/data-source-discovery";
9
+ import { useDetectedDataSources } from "@/hooks/useDataSourceDiscovery";
7
10
  import { useInsertCode } from "./components";
8
11
  import { cn } from "@/utils/cn";
9
12
 
@@ -94,19 +97,20 @@ const DetectedDataSourceDetails: React.FC<{
94
97
  );
95
98
 
96
99
  export const AutoDiscoveredDataSources: React.FC<{
97
- onSubmit: () => void;
100
+ onSubmit?: () => void;
98
101
  className?: string;
99
- }> = ({ onSubmit, className }) => {
102
+ group?: DataSourceDiscoveryGroup;
103
+ }> = ({ onSubmit, className, group }) => {
100
104
  const insertCode = useInsertCode();
101
- const { data } = useDataSourceDiscovery();
105
+ const sources = useDetectedDataSources(group);
102
106
 
103
107
  return (
104
108
  <QuickAddDataSources
105
109
  className={className}
106
- sources={data ?? []}
110
+ sources={sources}
107
111
  onAdd={(source) => {
108
112
  insertCode(source.code);
109
- onSubmit();
113
+ onSubmit?.();
110
114
  }}
111
115
  />
112
116
  );
@@ -117,6 +117,49 @@ exports[`generateStorageCode > GCS > without service account key (default creden
117
117
  store = GCSStore("my-bucket")"
118
118
  `;
119
119
 
120
+ exports[`generateStorageCode > GitHub > public repo 1`] = `
121
+ "from fsspec.implementations.github import GithubFileSystem
122
+
123
+ fs = GithubFileSystem(
124
+ org="marimo-team",
125
+ repo="marimo",
126
+ )"
127
+ `;
128
+
129
+ exports[`generateStorageCode > GitHub > with sha 1`] = `
130
+ "from fsspec.implementations.github import GithubFileSystem
131
+
132
+ fs = GithubFileSystem(
133
+ org="marimo-team",
134
+ repo="marimo",
135
+ sha="main",
136
+ )"
137
+ `;
138
+
139
+ exports[`generateStorageCode > GitHub > with username and token 1`] = `
140
+ "from fsspec.implementations.github import GithubFileSystem
141
+
142
+ fs = GithubFileSystem(
143
+ org="marimo-team",
144
+ repo="marimo",
145
+ username="octocat",
146
+ token="ghp_example",
147
+ )"
148
+ `;
149
+
150
+ exports[`generateStorageCode > GitHub > with username and token from secrets 1`] = `
151
+ "from fsspec.implementations.github import GithubFileSystem
152
+ import os
153
+
154
+ _token = os.environ.get("GITHUB_TOKEN")
155
+ fs = GithubFileSystem(
156
+ org="marimo-team",
157
+ repo="marimo",
158
+ username="octocat",
159
+ token=_token,
160
+ )"
161
+ `;
162
+
120
163
  exports[`generateStorageCode > Google Drive > with default auth (no credentials) 1`] = `
121
164
  "from gdrive_fsspec import GoogleDriveFileSystem
122
165
 
@@ -329,6 +329,83 @@ describe("generateStorageCode", () => {
329
329
  });
330
330
  });
331
331
 
332
+ describe("GitHub", () => {
333
+ it("public repo", () => {
334
+ expect(
335
+ generateStorageCode(
336
+ { type: "github", org: "marimo-team", repo: "marimo" },
337
+ { library: "fsspec" },
338
+ ),
339
+ ).toMatchSnapshot();
340
+ });
341
+
342
+ it("with sha", () => {
343
+ expect(
344
+ generateStorageCode(
345
+ {
346
+ type: "github",
347
+ org: "marimo-team",
348
+ repo: "marimo",
349
+ sha: "main",
350
+ },
351
+ { library: "fsspec" },
352
+ ),
353
+ ).toMatchSnapshot();
354
+ });
355
+
356
+ it("escapes quotes and backslashes in sha", () => {
357
+ expect(
358
+ generateStorageCode(
359
+ {
360
+ type: "github",
361
+ org: "marimo-team",
362
+ repo: "marimo",
363
+ sha: 'feature/\\"new"',
364
+ },
365
+ { library: "fsspec" },
366
+ ),
367
+ ).toMatchInlineSnapshot(`
368
+ "from fsspec.implementations.github import GithubFileSystem
369
+
370
+ fs = GithubFileSystem(
371
+ org="marimo-team",
372
+ repo="marimo",
373
+ sha="feature/\\\\\\"new\\"",
374
+ )"
375
+ `);
376
+ });
377
+
378
+ it("with username and token", () => {
379
+ expect(
380
+ generateStorageCode(
381
+ {
382
+ type: "github",
383
+ org: "marimo-team",
384
+ repo: "marimo",
385
+ username: "octocat",
386
+ token: "ghp_example",
387
+ },
388
+ { library: "fsspec" },
389
+ ),
390
+ ).toMatchSnapshot();
391
+ });
392
+
393
+ it("with username and token from secrets", () => {
394
+ expect(
395
+ generateStorageCode(
396
+ {
397
+ type: "github",
398
+ org: "marimo-team",
399
+ repo: "marimo",
400
+ username: "octocat",
401
+ token: prefixSecret("GITHUB_TOKEN"),
402
+ },
403
+ { library: "fsspec" },
404
+ ),
405
+ ).toMatchSnapshot();
406
+ });
407
+ });
408
+
332
409
  describe("invalid cases", () => {
333
410
  it("throws for empty S3 bucket", () => {
334
411
  expect(() =>
@@ -397,5 +474,55 @@ describe("generateStorageCode", () => {
397
474
  ),
398
475
  ).toThrow();
399
476
  });
477
+
478
+ it("throws for empty GitHub org", () => {
479
+ expect(() =>
480
+ generateStorageCode(
481
+ { type: "github", org: "", repo: "marimo" } as StorageConnection,
482
+ { library: "fsspec" },
483
+ ),
484
+ ).toThrow();
485
+ });
486
+
487
+ it("throws for empty GitHub repo", () => {
488
+ expect(() =>
489
+ generateStorageCode(
490
+ {
491
+ type: "github",
492
+ org: "marimo-team",
493
+ repo: "",
494
+ } as StorageConnection,
495
+ { library: "fsspec" },
496
+ ),
497
+ ).toThrow();
498
+ });
499
+
500
+ it("throws when GitHub username is set without token", () => {
501
+ expect(() =>
502
+ generateStorageCode(
503
+ {
504
+ type: "github",
505
+ org: "marimo-team",
506
+ repo: "marimo",
507
+ username: "octocat",
508
+ },
509
+ { library: "fsspec" },
510
+ ),
511
+ ).toThrow();
512
+ });
513
+
514
+ it("throws when GitHub token is set without username", () => {
515
+ expect(() =>
516
+ generateStorageCode(
517
+ {
518
+ type: "github",
519
+ org: "marimo-team",
520
+ repo: "marimo",
521
+ token: "ghp_example",
522
+ },
523
+ { library: "fsspec" },
524
+ ),
525
+ ).toThrow();
526
+ });
400
527
  });
401
528
  });
@@ -16,6 +16,7 @@ import {
16
16
  AzureStorageSchema,
17
17
  CoreWeaveStorageSchema,
18
18
  GCSStorageSchema,
19
+ GithubStorageSchema,
19
20
  GoogleDriveStorageSchema,
20
21
  HuggingfaceStorageSchema,
21
22
  S3StorageSchema,
@@ -89,6 +90,15 @@ const STORAGE_PROVIDERS = [
89
90
  preferred: "huggingface_hub",
90
91
  },
91
92
  },
93
+ {
94
+ name: "GitHub",
95
+ schema: GithubStorageSchema,
96
+ protocol: "github",
97
+ storageLibraries: {
98
+ libraries: ["fsspec"],
99
+ preferred: "fsspec",
100
+ },
101
+ },
92
102
  ] satisfies StorageProviderSchema[];
93
103
 
94
104
  const StorageProviderSelector: React.FC<{
@@ -105,7 +115,7 @@ const StorageProviderSelector: React.FC<{
105
115
  <ProtocolIcon
106
116
  protocol={protocol as KnownStorageProtocol}
107
117
  forceDark={true}
108
- className="w-7.5 h-7.5"
118
+ className="w-7.5 h-7.5 text-white"
109
119
  />
110
120
  }
111
121
  onSelect={() => onSelect(schema)}
@@ -43,7 +43,7 @@ class SecretContainer {
43
43
  this.secrets[privateVar] = `os.environ.get("${envVar}")`;
44
44
  return privateVar;
45
45
  }
46
- return `"${value}"`;
46
+ return `"${escapePythonString(value)}"`;
47
47
  }
48
48
 
49
49
  formatSecrets(): string {
@@ -260,6 +260,32 @@ function generateHuggingfaceCode(
260
260
  return { imports, code: `hf = HfApi(token=${token})` };
261
261
  }
262
262
 
263
+ function generateGithubCode(
264
+ connection: Extract<StorageConnection, { type: "github" }>,
265
+ secrets: SecretContainer,
266
+ ): { imports: Set<string>; code: string } {
267
+ const imports = new Set([
268
+ "from fsspec.implementations.github import GithubFileSystem",
269
+ ]);
270
+ const params: string[] = [
271
+ `org=${secrets.print("org", connection.org)},`,
272
+ `repo=${secrets.print("repo", connection.repo)},`,
273
+ ];
274
+
275
+ if (connection.sha) {
276
+ params.push(`sha=${secrets.print("sha", connection.sha)},`);
277
+ }
278
+ if (connection.username && connection.token) {
279
+ params.push(
280
+ `username=${secrets.print("username", connection.username)},`,
281
+ `token=${secrets.print("token", connection.token)},`,
282
+ );
283
+ }
284
+
285
+ const paramsStr = params.map((param) => ` ${param}`).join("\n");
286
+ return { imports, code: `fs = GithubFileSystem(\n${paramsStr}\n)` };
287
+ }
288
+
263
289
  export function generateStorageCode(
264
290
  connection: StorageConnection,
265
291
  options: StorageCodeOptions,
@@ -291,6 +317,9 @@ export function generateStorageCode(
291
317
  case "huggingface":
292
318
  result = generateHuggingfaceCode(connection, secrets);
293
319
  break;
320
+ case "github":
321
+ result = generateGithubCode(connection, secrets);
322
+ break;
294
323
  default:
295
324
  assertNever(connection);
296
325
  }
@@ -238,6 +238,68 @@ export const HuggingfaceStorageSchema = z
238
238
  })
239
239
  .describe(FieldOptions.of({ direction: "two-columns" }));
240
240
 
241
+ function isFilled(value: string | undefined): boolean {
242
+ return value != null && value !== "";
243
+ }
244
+
245
+ export const GithubStorageSchema = z
246
+ .object({
247
+ type: z.literal("github"),
248
+ org: z
249
+ .string()
250
+ .nonempty()
251
+ .describe(
252
+ FieldOptions.of({
253
+ label: "Organization or user",
254
+ }),
255
+ ),
256
+ repo: z
257
+ .string()
258
+ .nonempty()
259
+ .describe(
260
+ FieldOptions.of({
261
+ label: "Repository",
262
+ }),
263
+ ),
264
+ sha: z
265
+ .string()
266
+ .optional()
267
+ .describe(
268
+ FieldOptions.of({
269
+ label: "Branch, tag, or commit",
270
+ placeholder: "main",
271
+ description: "Leave empty to use the default branch",
272
+ }),
273
+ ),
274
+ username: z
275
+ .string()
276
+ .optional()
277
+ .describe(
278
+ FieldOptions.of({
279
+ label: "Username",
280
+ description: "Set this together with access token",
281
+ optionRegex: "(github.?username|github.?user)",
282
+ }),
283
+ ),
284
+ token: z
285
+ .string()
286
+ .optional()
287
+ .describe(
288
+ FieldOptions.of({
289
+ label: "Access Token",
290
+ description:
291
+ "Set this together with username. Required for private repos and to avoid GitHub's rate limits.",
292
+ inputType: "password",
293
+ optionRegex: "(github.?token|gh.?token|github.?pat)",
294
+ }),
295
+ ),
296
+ })
297
+ .refine((value) => isFilled(value.username) === isFilled(value.token), {
298
+ message: "Username and access token are required together",
299
+ path: ["token"],
300
+ })
301
+ .describe(FieldOptions.of({ direction: "two-columns" }));
302
+
241
303
  export const StorageConnectionSchema = z.discriminatedUnion("type", [
242
304
  S3StorageSchema,
243
305
  GCSStorageSchema,
@@ -245,6 +307,7 @@ export const StorageConnectionSchema = z.discriminatedUnion("type", [
245
307
  CoreWeaveStorageSchema,
246
308
  GoogleDriveStorageSchema,
247
309
  HuggingfaceStorageSchema,
310
+ GithubStorageSchema,
248
311
  ]);
249
312
 
250
313
  export type StorageConnection = z.infer<typeof StorageConnectionSchema>;
@@ -56,6 +56,19 @@ describe("CachePopover getCacheInfo throttling", () => {
56
56
  return () => act(() => dispose());
57
57
  }
58
58
 
59
+ it("renders content synchronously for CodeMirror's initial measurement", async () => {
60
+ const dom = document.createElement("div");
61
+
62
+ // Keep the mount outside `act()`. It would flush an asynchronous render
63
+ // and make this assertion pass without `flushSync`.
64
+ const dispose = mountLensPopover(dom, CACHE_SPEC);
65
+
66
+ expect(dom.textContent).toContain("my_cache");
67
+ await act(async () => {
68
+ dispose();
69
+ });
70
+ });
71
+
59
72
  it("does not refetch on every hover, only after the throttle interval elapses", () => {
60
73
  const disposeFirst = mount();
61
74
  expect(getCacheInfoMock).toHaveBeenCalledTimes(1);
@@ -4,6 +4,7 @@ import { Provider, useAtomValue } from "jotai";
4
4
  import { DatabaseZapIcon } from "lucide-react";
5
5
  import type React from "react";
6
6
  import { useEffect } from "react";
7
+ import { flushSync } from "react-dom";
7
8
  import { createRoot } from "react-dom/client";
8
9
  import { useLocale } from "react-aria";
9
10
  import { ProtocolIcon } from "@/components/storage/components";
@@ -39,11 +40,16 @@ export function mountLensPopover(
39
40
  spec: CodeLensSpec,
40
41
  ): () => void {
41
42
  const root = createRoot(dom);
42
- root.render(
43
- <Provider store={store}>
44
- <LensPopover spec={spec} />
45
- </Provider>,
46
- );
43
+ // CodeMirror measures and positions the tooltip as soon as this function
44
+ // returns. Render now so it measures the popover content, not an empty
45
+ // container that later grows over the hovered icon.
46
+ flushSync(() => {
47
+ root.render(
48
+ <Provider store={store}>
49
+ <LensPopover spec={spec} />
50
+ </Provider>,
51
+ );
52
+ });
47
53
  // Defer so tearing the tooltip down from inside a React event (e.g. the
48
54
  // icon's click handler) doesn't unmount mid-render
49
55
  return () => queueMicrotask(() => root.unmount());
@@ -9,13 +9,18 @@ import type {
9
9
  } from "@/core/kernel/messages";
10
10
  import { renderColumnInfo, renderTableInfo } from "../renderers";
11
11
 
12
- const geometryColumn = {
12
+ const bogusColumn = {
13
13
  name: "geom",
14
- type: "geometry" as DataType,
14
+ type: "bogus_type" as DataType,
15
15
  external_type: "geometry",
16
16
  sample_values: [],
17
17
  } as DataTableColumn;
18
18
 
19
+ const geometryColumn = {
20
+ ...bogusColumn,
21
+ type: "geometry" as DataType,
22
+ } as DataTableColumn;
23
+
19
24
  describe("SQL renderers", () => {
20
25
  it("renders table information with an unrecognized column type", () => {
21
26
  const table: DataTable = {
@@ -26,7 +31,7 @@ describe("SQL renderers", () => {
26
31
  num_rows: null,
27
32
  num_columns: 1,
28
33
  variable_name: null,
29
- columns: [geometryColumn],
34
+ columns: [bogusColumn],
30
35
  primary_keys: [],
31
36
  indexes: [],
32
37
  };
@@ -37,8 +42,14 @@ describe("SQL renderers", () => {
37
42
  });
38
43
 
39
44
  it("renders column information with an unrecognized column type", () => {
40
- const result = render(renderColumnInfo(geometryColumn));
45
+ const result = render(renderColumnInfo(bogusColumn));
41
46
 
42
47
  expect(result.container.querySelector("svg")).not.toBeNull();
43
48
  });
49
+
50
+ it("renders geometry column information with the map pin icon", () => {
51
+ const result = render(renderColumnInfo(geometryColumn));
52
+
53
+ expect(result.container.querySelector(".lucide-map-pin")).not.toBeNull();
54
+ });
44
55
  });
@@ -41,6 +41,7 @@ const DATA_TYPE_COLORS: Record<DataType, string> = {
41
41
  number: "bg-(--purple-4) text-(--purple-11)",
42
42
  integer: "bg-(--purple-4) text-(--purple-11)",
43
43
  string: "bg-(--blue-4) text-(--blue-11)",
44
+ geometry: "bg-(--cyan-4) text-(--cyan-11)",
44
45
  unknown: "bg-(--slate-4) text-(--slate-11)",
45
46
  };
46
47