@marimo-team/islands 0.20.5-dev0 → 0.20.5-dev3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.20.5-dev0",
3
+ "version": "0.20.5-dev3",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -3,6 +3,7 @@
3
3
  import type { Table } from "@tanstack/react-table";
4
4
  import { render, screen } from "@testing-library/react";
5
5
  import { useEffect } from "react";
6
+ import { I18nProvider } from "react-aria";
6
7
  import { describe, expect, it } from "vitest";
7
8
  import { SELECT_COLUMN_ID } from "../../types";
8
9
  import { useCellSelectionReducerActions } from "../atoms";
@@ -21,7 +22,11 @@ const TestHarness = ({
21
22
  useEffect(() => {
22
23
  actions.setSelectedCells(selectedCellIds);
23
24
  }, [actions, selectedCellIds]);
24
- return <CellSelectionStats table={table} />;
25
+ return (
26
+ <I18nProvider locale="en-US">
27
+ <CellSelectionStats table={table} />
28
+ </I18nProvider>
29
+ );
25
30
  };
26
31
 
27
32
  describe("CellSelectionStats", () => {
@@ -107,7 +112,7 @@ describe("CellSelectionStats", () => {
107
112
  expect(screen.queryByText(/Average:/)).not.toBeInTheDocument();
108
113
  });
109
114
 
110
- it("should round sum and average to 8 decimal places", () => {
115
+ it("should round sum and average to 3 decimal places", () => {
111
116
  const row = createMockRow("0", [
112
117
  createMockCell("0_0", 0.112_233_441_1),
113
118
  createMockCell("0_1", 0.112_233_441_1),
@@ -120,14 +125,14 @@ describe("CellSelectionStats", () => {
120
125
  </CellSelectionProvider>,
121
126
  );
122
127
 
123
- expect(screen.getByText("Sum: 0.22446688")).toBeInTheDocument(); // Round 0.2244668866 to 8 decimal places
124
- expect(screen.getByText("Average: 0.11223344")).toBeInTheDocument(); // Round 0.1122334411 to 8 decimal places
128
+ expect(screen.getByText("Sum: 0.224")).toBeInTheDocument();
129
+ expect(screen.getByText("Average: 0.112")).toBeInTheDocument();
125
130
  });
126
131
 
127
- it("should correctly round sum and average to 8 decimal places", () => {
132
+ it("should correctly round sum and average to 3 decimal places", () => {
128
133
  const row = createMockRow("0", [
129
- createMockCell("0_0", 0.112_233_443_3),
130
- createMockCell("0_1", 0.112_233_443_3),
134
+ createMockCell("0_0", 0.112_833_443_3),
135
+ createMockCell("0_1", 0.112_833_443_3),
131
136
  ]);
132
137
  const table = createMockTable([row], []);
133
138
 
@@ -137,8 +142,8 @@ describe("CellSelectionStats", () => {
137
142
  </CellSelectionProvider>,
138
143
  );
139
144
 
140
- expect(screen.getByText("Sum: 0.22446689")).toBeInTheDocument(); // Round 0.2244668866 to 8 decimal places
141
- expect(screen.getByText("Average: 0.11223344")).toBeInTheDocument(); // Round 0.1122334433 to 8 decimal places
145
+ expect(screen.getByText("Sum: 0.226")).toBeInTheDocument();
146
+ expect(screen.getByText("Average: 0.113")).toBeInTheDocument();
142
147
  });
143
148
 
144
149
  it("should not add extra decimal places to sum and average", () => {
@@ -2,6 +2,7 @@
2
2
 
3
3
  import type { Table } from "@tanstack/react-table";
4
4
  import { useAtomValue } from "jotai";
5
+ import { useLocale } from "react-aria";
5
6
  import { cn } from "@/utils/cn";
6
7
  import { selectedCellsAtom } from "./atoms";
7
8
  import {
@@ -9,6 +10,9 @@ import {
9
10
  getNumericValuesFromSelectedCells,
10
11
  } from "./utils";
11
12
 
13
+ // Offers a good default for most use cases.
14
+ const MAX_FRACTION_DIGITS = 3;
15
+
12
16
  /**
13
17
  * Displays summary stats (Count, Sum, Average) for the current cell selection.
14
18
  * Renders only when 2+ data cells are selected (checkbox column excluded).
@@ -22,6 +26,7 @@ export const CellSelectionStats = <TData,>({
22
26
  table: Table<TData>;
23
27
  className?: string;
24
28
  }) => {
29
+ const { locale } = useLocale();
25
30
  const selectedCells = useAtomValue(selectedCellsAtom);
26
31
  const dataCellCount = countDataCellsInSelection(selectedCells);
27
32
 
@@ -38,42 +43,68 @@ export const CellSelectionStats = <TData,>({
38
43
  className,
39
44
  )}
40
45
  >
41
- <CountStat count={dataCellCount} />
42
- <SumStat numericValues={numericValues} />
43
- <AverageStat numericValues={numericValues} />
46
+ <CountStat count={dataCellCount} locale={locale} />
47
+ <SumStat numericValues={numericValues} locale={locale} />
48
+ <AverageStat numericValues={numericValues} locale={locale} />
44
49
  </div>
45
50
  );
46
51
  };
47
52
 
48
- const StatSpan = (statName: string, statValue: number) => {
53
+ const formatNumber = (value: number, locale: string): string => {
54
+ return value.toLocaleString(locale, {
55
+ maximumFractionDigits: MAX_FRACTION_DIGITS,
56
+ });
57
+ };
58
+
59
+ const StatSpan = ({
60
+ name,
61
+ value,
62
+ locale,
63
+ }: {
64
+ name: string;
65
+ value: number;
66
+ locale: string;
67
+ }) => {
49
68
  return (
50
69
  <span>
51
- {statName}: {statValue}
70
+ {name}: {formatNumber(value, locale)}
52
71
  </span>
53
72
  );
54
73
  };
55
74
 
56
- const CountStat = ({ count }: { count: number }) => {
57
- return StatSpan("Count", count);
75
+ const CountStat = ({ count, locale }: { count: number; locale: string }) => {
76
+ return <StatSpan name="Count" value={count} locale={locale} />;
58
77
  };
59
78
 
60
- const SumStat = ({ numericValues }: { numericValues: number[] }) => {
79
+ const SumStat = ({
80
+ numericValues,
81
+ locale,
82
+ }: {
83
+ numericValues: number[];
84
+ locale: string;
85
+ }) => {
61
86
  if (numericValues.length === 0) {
62
87
  return null;
63
88
  }
64
89
 
65
90
  const sum = numericValues.reduce((acc, n) => acc + n, 0);
66
- const sumRounded = Number(sum.toFixed(8));
67
- return StatSpan("Sum", sumRounded);
91
+ const sumRounded = Number(sum.toFixed(MAX_FRACTION_DIGITS));
92
+ return <StatSpan name="Sum" value={sumRounded} locale={locale} />;
68
93
  };
69
94
 
70
- const AverageStat = ({ numericValues }: { numericValues: number[] }) => {
95
+ const AverageStat = ({
96
+ numericValues,
97
+ locale,
98
+ }: {
99
+ numericValues: number[];
100
+ locale: string;
101
+ }) => {
71
102
  if (numericValues.length === 0) {
72
103
  return null;
73
104
  }
74
105
 
75
106
  const average =
76
107
  numericValues.reduce((acc, n) => acc + n, 0) / numericValues.length;
77
- const averageRounded = Number(average.toFixed(8));
78
- return StatSpan("Average", averageRounded);
108
+ const averageRounded = Number(average.toFixed(MAX_FRACTION_DIGITS));
109
+ return <StatSpan name="Average" value={averageRounded} locale={locale} />;
79
110
  };
@@ -29,6 +29,43 @@ store = AzureStore("my-container",
29
29
  )"
30
30
  `;
31
31
 
32
+ exports[`generateStorageCode > CoreWeave > basic connection with all fields 1`] = `
33
+ "from obstore.store import S3Store
34
+
35
+ store = S3Store("operator-bucket",
36
+ region="US-EAST-04A",
37
+ access_key_id="AKIAIOSFODNN7EXAMPLE",
38
+ secret_access_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
39
+ endpoint="https://operator-bucket.cwobject.com",
40
+ virtual_hosted_style_request=True,
41
+ )"
42
+ `;
43
+
44
+ exports[`generateStorageCode > CoreWeave > minimal connection (bucket and region only) 1`] = `
45
+ "from obstore.store import S3Store
46
+
47
+ store = S3Store("operator-bucket",
48
+ region="US-EAST-04A",
49
+ endpoint="https://operator-bucket.cwobject.com",
50
+ virtual_hosted_style_request=True,
51
+ )"
52
+ `;
53
+
54
+ exports[`generateStorageCode > CoreWeave > with secrets 1`] = `
55
+ "from obstore.store import S3Store
56
+ import os
57
+
58
+ _access_key_id = os.environ.get("COREWEAVE_OBJECT_STORAGE_KEY")
59
+ _secret_access_key = os.environ.get("COREWEAVE_OBJECT_STORAGE_SECRET")
60
+ store = S3Store("operator-bucket",
61
+ region="US-EAST-04A",
62
+ access_key_id=_access_key_id,
63
+ secret_access_key=_secret_access_key,
64
+ endpoint="https://operator-bucket.cwobject.com",
65
+ virtual_hosted_style_request=True,
66
+ )"
67
+ `;
68
+
32
69
  exports[`generateStorageCode > GCS > with service account key 1`] = `
33
70
  "from obstore.store import GCSStore
34
71
  import json
@@ -27,6 +27,14 @@ describe("generateStorageCode", () => {
27
27
  account_key: "base64accountkey==",
28
28
  };
29
29
 
30
+ const baseCoreWeave: StorageConnection = {
31
+ type: "coreweave",
32
+ bucket: "operator-bucket",
33
+ region: "US-EAST-04A",
34
+ access_key_id: "AKIAIOSFODNN7EXAMPLE",
35
+ secret_access_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
36
+ };
37
+
30
38
  const baseGDrive: StorageConnection = {
31
39
  type: "gdrive",
32
40
  credentials_json:
@@ -105,6 +113,32 @@ describe("generateStorageCode", () => {
105
113
  });
106
114
  });
107
115
 
116
+ describe("CoreWeave", () => {
117
+ it("basic connection with all fields", () => {
118
+ expect(generateStorageCode(baseCoreWeave, "obstore")).toMatchSnapshot();
119
+ });
120
+
121
+ it("minimal connection (bucket and region only)", () => {
122
+ const conn: StorageConnection = {
123
+ type: "coreweave",
124
+ bucket: "operator-bucket",
125
+ region: "US-EAST-04A",
126
+ };
127
+ expect(generateStorageCode(conn, "obstore")).toMatchSnapshot();
128
+ });
129
+
130
+ it("with secrets", () => {
131
+ const conn: StorageConnection = {
132
+ type: "coreweave",
133
+ bucket: "operator-bucket",
134
+ region: "US-EAST-04A",
135
+ access_key_id: prefixSecret("COREWEAVE_OBJECT_STORAGE_KEY"),
136
+ secret_access_key: prefixSecret("COREWEAVE_OBJECT_STORAGE_SECRET"),
137
+ };
138
+ expect(generateStorageCode(conn, "obstore")).toMatchSnapshot();
139
+ });
140
+ });
141
+
108
142
  describe("Google Drive", () => {
109
143
  it("with service account credentials", () => {
110
144
  expect(generateStorageCode(baseGDrive, "fsspec")).toMatchSnapshot();
@@ -162,5 +196,31 @@ describe("generateStorageCode", () => {
162
196
  ),
163
197
  ).toThrow();
164
198
  });
199
+
200
+ it("throws for empty CoreWeave bucket", () => {
201
+ expect(() =>
202
+ generateStorageCode(
203
+ {
204
+ type: "coreweave",
205
+ bucket: "",
206
+ region: "US-EAST-04A",
207
+ } as StorageConnection,
208
+ "obstore",
209
+ ),
210
+ ).toThrow();
211
+ });
212
+
213
+ it("throws for empty CoreWeave region", () => {
214
+ expect(() =>
215
+ generateStorageCode(
216
+ {
217
+ type: "coreweave",
218
+ bucket: "operator-bucket",
219
+ region: "",
220
+ } as StorageConnection,
221
+ "obstore",
222
+ ),
223
+ ).toThrow();
224
+ });
165
225
  });
166
226
  });
@@ -4,6 +4,7 @@ import { useState } from "react";
4
4
  import type { FieldValues } from "react-hook-form";
5
5
  import type { z } from "zod";
6
6
  import { ProtocolIcon } from "@/components/storage/components";
7
+ import type { KnownStorageProtocol } from "@/core/storage/types";
7
8
  import { ConnectionForm, SelectorButton, SelectorGrid } from "../components";
8
9
  import {
9
10
  generateStorageCode,
@@ -12,6 +13,7 @@ import {
12
13
  } from "./as-code";
13
14
  import {
14
15
  AzureStorageSchema,
16
+ CoreWeaveStorageSchema,
15
17
  GCSStorageSchema,
16
18
  GoogleDriveStorageSchema,
17
19
  S3StorageSchema,
@@ -21,7 +23,6 @@ import {
21
23
  interface StorageProviderSchema {
22
24
  name: string;
23
25
  schema: z.ZodType<StorageConnection, FieldValues>;
24
- color: string;
25
26
  protocol: string;
26
27
  storageLibraries: {
27
28
  libraries: StorageLibrary[];
@@ -29,11 +30,12 @@ interface StorageProviderSchema {
29
30
  };
30
31
  }
31
32
 
33
+ const BACKGROUND_COLOR = "#232F3E";
34
+
32
35
  const STORAGE_PROVIDERS = [
33
36
  {
34
37
  name: "Amazon S3",
35
38
  schema: S3StorageSchema,
36
- color: "#232F3E",
37
39
  protocol: "s3",
38
40
  storageLibraries: {
39
41
  libraries: ["obstore"],
@@ -43,7 +45,6 @@ const STORAGE_PROVIDERS = [
43
45
  {
44
46
  name: "Google Cloud Storage",
45
47
  schema: GCSStorageSchema,
46
- color: "#4285F4",
47
48
  protocol: "gcs",
48
49
  storageLibraries: {
49
50
  libraries: ["obstore"],
@@ -53,17 +54,24 @@ const STORAGE_PROVIDERS = [
53
54
  {
54
55
  name: "Azure Blob Storage",
55
56
  schema: AzureStorageSchema,
56
- color: "#0062AD",
57
57
  protocol: "azure",
58
58
  storageLibraries: {
59
59
  libraries: ["obstore"],
60
60
  preferred: "obstore",
61
61
  },
62
62
  },
63
+ {
64
+ name: "CoreWeave",
65
+ schema: CoreWeaveStorageSchema,
66
+ protocol: "coreweave",
67
+ storageLibraries: {
68
+ libraries: ["obstore"],
69
+ preferred: "obstore",
70
+ },
71
+ },
63
72
  {
64
73
  name: "Google Drive",
65
74
  schema: GoogleDriveStorageSchema,
66
- color: "#177834",
67
75
  protocol: "gdrive",
68
76
  storageLibraries: {
69
77
  libraries: ["fsspec"],
@@ -77,18 +85,17 @@ const StorageProviderSelector: React.FC<{
77
85
  }> = ({ onSelect }) => {
78
86
  return (
79
87
  <SelectorGrid>
80
- {STORAGE_PROVIDERS.map(({ name, schema, color, protocol }) => (
88
+ {STORAGE_PROVIDERS.map(({ name, schema, protocol }) => (
81
89
  <SelectorButton
82
90
  key={name}
83
91
  name={name}
84
- color={color}
92
+ color={BACKGROUND_COLOR}
85
93
  icon={
86
- <span className="w-8 h-8 flex items-center justify-center">
87
- <ProtocolIcon
88
- protocol={protocol}
89
- className="w-7 h-7 brightness-0 invert"
90
- />
91
- </span>
94
+ <ProtocolIcon
95
+ protocol={protocol as KnownStorageProtocol}
96
+ forceDark={true}
97
+ className="w-7.5 h-7.5"
98
+ />
92
99
  }
93
100
  onSelect={() => onSelect(schema)}
94
101
  />
@@ -125,6 +125,40 @@ function generateAzureCode(
125
125
  return { imports, code };
126
126
  }
127
127
 
128
+ function generateCoreWeaveCode(
129
+ connection: Extract<StorageConnection, { type: "coreweave" }>,
130
+ secrets: SecretContainer,
131
+ ): { imports: Set<string>; code: string } {
132
+ const bucket = secrets.print("bucket", connection.bucket);
133
+ const imports = new Set(["from obstore.store import S3Store"]);
134
+ const params: string[] = [
135
+ ` region=${secrets.print("region", connection.region)},`,
136
+ ];
137
+
138
+ if (connection.access_key_id) {
139
+ params.push(
140
+ ` access_key_id=${secrets.print("access_key_id", connection.access_key_id)},`,
141
+ );
142
+ }
143
+ if (connection.secret_access_key) {
144
+ params.push(
145
+ ` secret_access_key=${secrets.print("secret_access_key", connection.secret_access_key)},`,
146
+ );
147
+ }
148
+
149
+ params.push(
150
+ ` endpoint="https://${connection.bucket}.cwobject.com",`,
151
+ " virtual_hosted_style_request=True,",
152
+ );
153
+
154
+ const paramsStr = `\n${params.join("\n")}\n`;
155
+
156
+ const code = dedent(`
157
+ store = S3Store(${bucket},${paramsStr})
158
+ `);
159
+ return { imports, code };
160
+ }
161
+
128
162
  function generateGDriveCode(
129
163
  connection: Extract<StorageConnection, { type: "gdrive" }>,
130
164
  secrets: SecretContainer,
@@ -169,6 +203,9 @@ export function generateStorageCode(
169
203
  case "azure":
170
204
  result = generateAzureCode(connection, secrets);
171
205
  break;
206
+ case "coreweave":
207
+ result = generateCoreWeaveCode(connection, secrets);
208
+ break;
172
209
  case "gdrive":
173
210
  result = generateGDriveCode(connection, secrets);
174
211
  break;
@@ -21,7 +21,6 @@ export const S3StorageSchema = z
21
21
  FieldOptions.of({
22
22
  label: "Region",
23
23
  placeholder: "us-east-1",
24
- optionRegex: ".*region.*",
25
24
  }),
26
25
  ),
27
26
  access_key_id: z
@@ -115,6 +114,50 @@ export const AzureStorageSchema = z
115
114
  })
116
115
  .describe(FieldOptions.of({ direction: "two-columns" }));
117
116
 
117
+ export const CoreWeaveStorageSchema = z
118
+ .object({
119
+ type: z.literal("coreweave"),
120
+ bucket: z
121
+ .string()
122
+ .nonempty()
123
+ .describe(
124
+ FieldOptions.of({
125
+ label: "Bucket",
126
+ placeholder: "bucket-name",
127
+ }),
128
+ ),
129
+ region: z
130
+ .string()
131
+ .nonempty()
132
+ .describe(
133
+ FieldOptions.of({
134
+ label: "Region",
135
+ placeholder: "US-EAST-04A",
136
+ }),
137
+ ),
138
+ access_key_id: z
139
+ .string()
140
+ .optional()
141
+ .describe(
142
+ FieldOptions.of({
143
+ label: "Access Key ID",
144
+ inputType: "password",
145
+ optionRegex: ".*object_storage_key.*",
146
+ }),
147
+ ),
148
+ secret_access_key: z
149
+ .string()
150
+ .optional()
151
+ .describe(
152
+ FieldOptions.of({
153
+ label: "Secret Access Key",
154
+ inputType: "password",
155
+ optionRegex: ".*object_storage_secret.*",
156
+ }),
157
+ ),
158
+ })
159
+ .describe(FieldOptions.of({ direction: "two-columns" }));
160
+
118
161
  export const GoogleDriveStorageSchema = z
119
162
  .object({
120
163
  type: z.literal("gdrive"),
@@ -135,6 +178,7 @@ export const StorageConnectionSchema = z.discriminatedUnion("type", [
135
178
  S3StorageSchema,
136
179
  GCSStorageSchema,
137
180
  AzureStorageSchema,
181
+ CoreWeaveStorageSchema,
138
182
  GoogleDriveStorageSchema,
139
183
  ]);
140
184
 
@@ -75,15 +75,19 @@ const PROTOCOL_ICONS: Record<KnownStorageProtocol, IconEntry> = {
75
75
 
76
76
  export const ProtocolIcon: React.FC<{
77
77
  protocol: KnownStorageProtocol | (string & {});
78
+ forceDark?: boolean;
78
79
  className?: string;
79
- }> = ({ protocol, className }) => {
80
+ }> = ({ protocol, forceDark, className }) => {
80
81
  const { theme } = useTheme();
81
82
  const entry =
82
83
  PROTOCOL_ICONS[protocol.toLowerCase() as KnownStorageProtocol] ??
83
84
  HardDriveIcon;
84
85
 
85
86
  if ("src" in entry) {
86
- const src = theme === "dark" && entry.dark ? entry.dark : entry.src;
87
+ let src = entry.src;
88
+ if (entry.dark && (forceDark || theme === "dark")) {
89
+ src = entry.dark;
90
+ }
87
91
  return (
88
92
  <img src={src} alt={protocol} className={cn("h-3.5 w-3.5", className)} />
89
93
  );