@marimo-team/islands 0.24.3-dev82 → 0.24.3-dev87
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/{ErrorBoundary-CegtSjfd.js → ErrorBoundary-Bc52FkKN.js} +1 -1
- package/dist/{cells-BIfOnGBK.js → cells-DYIkSxs6.js} +4 -0
- package/dist/{chat-ui-LWkjhObe.js → chat-ui-BXj3uYjy.js} +4 -4
- package/dist/{code-visibility-Blq7oGrC.js → code-visibility-CI3ySb6F.js} +2 -2
- package/dist/{common-BQZDWy8O.js → common-BOJoCp6P.js} +3 -3
- package/dist/{data-grid-overlay-editor-DsU0Zpgt.js → data-grid-overlay-editor-BsT4_auq.js} +1 -1
- package/dist/{error-entries-CR0oClSL.js → error-entries-C0NsUqXl.js} +1 -1
- package/dist/{feedback-button-pGaDcCJS.js → feedback-button-B7KS_3RP.js} +4 -4
- package/dist/{glide-data-editor-DtJQCRsf.js → glide-data-editor-DtckYC_c.js} +3 -3
- package/dist/{globals-DBAC4Ihd.js → globals-CruHzAUQ.js} +1 -1
- package/dist/{html-to-image-_i03yR4q.js → html-to-image-CeYYvQNb.js} +1 -1
- package/dist/main.js +25 -14
- package/dist/{number-overlay-editor-9M7FnzxL.js → number-overlay-editor-CV1RN5Cg.js} +1 -1
- package/dist/{process-output-D3zQyTAS.js → process-output-BCTjlyuH.js} +2 -2
- package/dist/{reveal-component-ovaRyk0R.js → reveal-component-C47L1uNq.js} +3 -3
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/__tests__/fixtures/pair-preview.ts +1 -1
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +6 -6
- package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +1 -1
- package/src/components/editor/alerts/__tests__/connection-notice.test.tsx +186 -0
- package/src/components/editor/alerts/__tests__/startup-progress.test.tsx +185 -0
- package/src/components/editor/alerts/connection-notice.tsx +100 -54
- package/src/components/editor/alerts/startup-output.tsx +173 -0
- package/src/components/editor/alerts/startup-progress.tsx +204 -0
- package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +53 -4
- package/src/components/editor/chrome/panels/__tests__/sandbox-details.test.tsx +358 -0
- package/src/components/editor/chrome/panels/__tests__/sandbox-recovery.test.tsx +5 -2
- package/src/components/editor/chrome/panels/packages-panel.tsx +32 -21
- package/src/components/editor/chrome/panels/sandbox-details-state.ts +38 -0
- package/src/components/editor/chrome/panels/sandbox-panel.tsx +188 -131
- package/src/components/editor/chrome/panels/sandbox-toggle.tsx +52 -0
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +7 -0
- package/src/components/editor/package-alert.tsx +112 -66
- package/src/core/alerts/__tests__/environment.test.ts +183 -0
- package/src/core/alerts/__tests__/state.test.tsx +240 -0
- package/src/core/alerts/environment.ts +57 -0
- package/src/core/alerts/state.ts +155 -99
- package/src/core/islands/bootstrap.ts +2 -1
- package/src/core/kernel/messages.ts +2 -2
- package/src/core/network/connection-notice.ts +45 -12
- package/src/core/network/connection.ts +19 -0
- package/src/core/network/useConnectionNotice.ts +53 -0
- package/src/core/packages/__tests__/useSandboxController.test.tsx +86 -5
- package/src/core/packages/sandbox-state.ts +56 -4
- package/src/core/packages/useSandboxController.ts +27 -7
- package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +199 -8
- package/src/core/websocket/types.ts +1 -1
- package/src/core/websocket/useMarimoKernelConnection.tsx +31 -19
- package/src/plugins/impl/FileBrowserPlugin.tsx +10 -4
- package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +50 -1
- package/src/stories/sandbox.stories.tsx +230 -50
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
XIcon,
|
|
15
15
|
} from "lucide-react";
|
|
16
16
|
import type React from "react";
|
|
17
|
-
import { useState } from "react";
|
|
17
|
+
import { useEffect, useState } from "react";
|
|
18
18
|
import { useForm } from "react-hook-form";
|
|
19
19
|
import type { z } from "zod";
|
|
20
20
|
import {
|
|
@@ -25,19 +25,20 @@ import {
|
|
|
25
25
|
FormMessage,
|
|
26
26
|
} from "@/components/ui/form";
|
|
27
27
|
import {
|
|
28
|
-
|
|
28
|
+
type EnvironmentOperationAlert,
|
|
29
|
+
isEnvironmentOperationAlert,
|
|
29
30
|
isMissingPackageAlert,
|
|
30
31
|
useAlertActions,
|
|
31
32
|
useAlerts,
|
|
32
33
|
} from "@/core/alerts/state";
|
|
33
34
|
import { useResolvedMarimoConfig } from "@/core/config/config";
|
|
34
|
-
import type {
|
|
35
|
+
import type { PackageOperationStatus } from "@/core/kernel/messages";
|
|
35
36
|
import { useRequestClient } from "@/core/network/requests";
|
|
36
37
|
import { RESTART_REQUIRED_DESCRIPTION } from "@/core/packages/toast-components";
|
|
37
38
|
import { isWasm } from "@/core/wasm/utils";
|
|
38
39
|
import { usePackageMetadata } from "@/hooks/usePackageMetadata";
|
|
39
40
|
import { Banner } from "@/plugins/impl/common/error-banner";
|
|
40
|
-
import { logNever } from "@/utils/assertNever";
|
|
41
|
+
import { assertNever, logNever } from "@/utils/assertNever";
|
|
41
42
|
import { cn } from "@/utils/cn";
|
|
42
43
|
import { Logger } from "@/utils/Logger";
|
|
43
44
|
import {
|
|
@@ -100,7 +101,7 @@ const SourceBadge: React.FC<{ source?: "kernel" | "server" }> = ({
|
|
|
100
101
|
};
|
|
101
102
|
|
|
102
103
|
export const PackageAlert: React.FC = () => {
|
|
103
|
-
const { packageAlert
|
|
104
|
+
const { packageAlert } = useAlerts();
|
|
104
105
|
const { clearPackageAlert } = useAlertActions();
|
|
105
106
|
const [userConfig] = useResolvedMarimoConfig();
|
|
106
107
|
const [desiredPackageVersions, setDesiredPackageVersions] = useState<
|
|
@@ -110,6 +111,23 @@ export const PackageAlert: React.FC = () => {
|
|
|
110
111
|
Record<string, string[]>
|
|
111
112
|
>({});
|
|
112
113
|
|
|
114
|
+
const completedAlertId =
|
|
115
|
+
packageAlert?.kind === "environment" &&
|
|
116
|
+
packageAlert.status.kind === "succeeded" &&
|
|
117
|
+
!packageAlert.restartRequired
|
|
118
|
+
? packageAlert.id
|
|
119
|
+
: null;
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (completedAlertId === null) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const timeout = setTimeout(
|
|
125
|
+
() => clearPackageAlert(completedAlertId),
|
|
126
|
+
10_000,
|
|
127
|
+
);
|
|
128
|
+
return () => clearTimeout(timeout);
|
|
129
|
+
}, [completedAlertId, clearPackageAlert]);
|
|
130
|
+
|
|
113
131
|
if (packageAlert === null) {
|
|
114
132
|
return null;
|
|
115
133
|
}
|
|
@@ -224,12 +242,9 @@ export const PackageAlert: React.FC = () => {
|
|
|
224
242
|
);
|
|
225
243
|
}
|
|
226
244
|
|
|
227
|
-
if (
|
|
245
|
+
if (isEnvironmentOperationAlert(packageAlert)) {
|
|
228
246
|
const { status, title, titleIcon, description } =
|
|
229
|
-
|
|
230
|
-
if (status === "installed") {
|
|
231
|
-
setTimeout(() => clearPackageAlert(packageAlert.id), 10_000);
|
|
232
|
-
}
|
|
247
|
+
getOperationStatusElements(packageAlert);
|
|
233
248
|
|
|
234
249
|
return (
|
|
235
250
|
<div className="flex flex-col gap-4 mb-5 fixed top-5 left-12 min-w-[400px] z-200 opacity-95 max-w-[600px] pointer-events-none">
|
|
@@ -255,23 +270,27 @@ export const PackageAlert: React.FC = () => {
|
|
|
255
270
|
<div
|
|
256
271
|
className={cn(
|
|
257
272
|
"flex flex-col gap-4 justify-between items-start text-muted-foreground text-base",
|
|
258
|
-
status === "
|
|
273
|
+
status === "succeeded" && "text-accent-foreground",
|
|
259
274
|
)}
|
|
260
275
|
>
|
|
261
276
|
<p>{description}</p>
|
|
262
|
-
{status !== "
|
|
263
|
-
|
|
264
|
-
"restart-required"
|
|
265
|
-
|
|
277
|
+
{status !== "running" && packageAlert.restartRequired && (
|
|
278
|
+
<>
|
|
279
|
+
{status !== "restart-required" && (
|
|
280
|
+
<p>{RESTART_REQUIRED_DESCRIPTION}</p>
|
|
281
|
+
)}
|
|
282
|
+
<RestartKernelButton />
|
|
283
|
+
</>
|
|
284
|
+
)}
|
|
266
285
|
<ul className="list-disc ml-2 mt-1">
|
|
267
286
|
{Object.entries(packageAlert.packages).map(([pkg, st], index) => (
|
|
268
287
|
<li
|
|
269
288
|
className={cn(
|
|
270
289
|
"flex items-center gap-1 font-mono text-sm",
|
|
271
|
-
st === "
|
|
290
|
+
st === "running" && "font-semibold",
|
|
272
291
|
st === "failed" && "text-destructive",
|
|
273
|
-
st === "
|
|
274
|
-
st === "
|
|
292
|
+
st === "succeeded" && "text-accent-foreground",
|
|
293
|
+
st === "succeeded" &&
|
|
275
294
|
status === "failed" &&
|
|
276
295
|
"text-muted-foreground",
|
|
277
296
|
)}
|
|
@@ -282,8 +301,11 @@ export const PackageAlert: React.FC = () => {
|
|
|
282
301
|
</li>
|
|
283
302
|
))}
|
|
284
303
|
</ul>
|
|
285
|
-
{Object.keys(
|
|
286
|
-
<StreamingLogsViewer
|
|
304
|
+
{Object.keys(packageAlert.logs).length > 0 && (
|
|
305
|
+
<StreamingLogsViewer
|
|
306
|
+
key={packageAlert.id}
|
|
307
|
+
packageLogs={packageAlert.logs}
|
|
308
|
+
/>
|
|
287
309
|
)}
|
|
288
310
|
</div>
|
|
289
311
|
</Banner>
|
|
@@ -295,60 +317,84 @@ export const PackageAlert: React.FC = () => {
|
|
|
295
317
|
return null;
|
|
296
318
|
};
|
|
297
319
|
|
|
298
|
-
function
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
320
|
+
function getOperationStatusElements({
|
|
321
|
+
action,
|
|
322
|
+
status: outcome,
|
|
323
|
+
restartRequired,
|
|
324
|
+
}: EnvironmentOperationAlert) {
|
|
325
|
+
const status: EnvironmentOperationAlert["status"] =
|
|
326
|
+
outcome.kind === "succeeded" && restartRequired
|
|
327
|
+
? { kind: "restart-required", reason: RESTART_REQUIRED_DESCRIPTION }
|
|
328
|
+
: outcome;
|
|
329
|
+
const titles = {
|
|
330
|
+
install: {
|
|
331
|
+
running: "Installing packages",
|
|
332
|
+
succeeded: "All packages installed!",
|
|
333
|
+
failed: "Some packages failed to install",
|
|
334
|
+
cancelled: "Package installation cancelled",
|
|
335
|
+
},
|
|
336
|
+
remove: {
|
|
337
|
+
running: "Removing packages",
|
|
338
|
+
succeeded: "Packages removed",
|
|
339
|
+
failed: "Some packages failed to remove",
|
|
340
|
+
cancelled: "Package removal cancelled",
|
|
341
|
+
},
|
|
342
|
+
}[action];
|
|
343
|
+
switch (status.kind) {
|
|
344
|
+
case "running":
|
|
345
|
+
return {
|
|
346
|
+
status: "running",
|
|
347
|
+
title: titles.running,
|
|
348
|
+
titleIcon: <DownloadCloudIcon className="w-5 h-5 inline-block mr-2" />,
|
|
349
|
+
description:
|
|
350
|
+
action === "install" ? "Installing packages:" : "Removing packages:",
|
|
351
|
+
};
|
|
352
|
+
case "restart-required":
|
|
353
|
+
return {
|
|
354
|
+
status: "restart-required",
|
|
355
|
+
title: "Changes saved — restart required",
|
|
356
|
+
titleIcon: <RotateCwIcon className="w-5 h-5 inline-block mr-2" />,
|
|
357
|
+
description: status.reason,
|
|
358
|
+
};
|
|
359
|
+
case "succeeded":
|
|
360
|
+
return {
|
|
361
|
+
status: "succeeded",
|
|
362
|
+
title: titles.succeeded,
|
|
363
|
+
titleIcon: <PackageCheckIcon className="w-5 h-5 inline-block mr-2" />,
|
|
364
|
+
description:
|
|
365
|
+
action === "install" ? "Installed packages:" : "Removed packages:",
|
|
366
|
+
};
|
|
367
|
+
case "failed":
|
|
368
|
+
return {
|
|
369
|
+
status: "failed",
|
|
370
|
+
title: titles.failed,
|
|
371
|
+
titleIcon: <PackageXIcon className="w-5 h-5 inline-block mr-2" />,
|
|
372
|
+
description: status.error,
|
|
373
|
+
};
|
|
374
|
+
case "cancelled":
|
|
375
|
+
return {
|
|
376
|
+
status: "cancelled",
|
|
377
|
+
title: titles.cancelled,
|
|
378
|
+
titleIcon: <XIcon className="w-5 h-5 inline-block mr-2" />,
|
|
379
|
+
description:
|
|
380
|
+
"The operation was interrupted. Some packages may have changed.",
|
|
381
|
+
};
|
|
382
|
+
default:
|
|
383
|
+
return assertNever(status);
|
|
332
384
|
}
|
|
333
|
-
return {
|
|
334
|
-
status: "failed",
|
|
335
|
-
title: "Some packages failed to install",
|
|
336
|
-
titleIcon: <PackageXIcon className="w-5 h-5 inline-block mr-2" />,
|
|
337
|
-
description: "See error logs.",
|
|
338
|
-
};
|
|
339
385
|
}
|
|
340
386
|
|
|
341
387
|
const ProgressIcon = ({
|
|
342
388
|
status,
|
|
343
389
|
}: {
|
|
344
|
-
status:
|
|
390
|
+
status: PackageOperationStatus[string];
|
|
345
391
|
}) => {
|
|
346
392
|
switch (status) {
|
|
347
393
|
case "queued":
|
|
348
394
|
return <BoxIcon size="1rem" />;
|
|
349
|
-
case "
|
|
395
|
+
case "running":
|
|
350
396
|
return <DownloadCloudIcon size="1rem" />;
|
|
351
|
-
case "
|
|
397
|
+
case "succeeded":
|
|
352
398
|
return <CheckIcon size="1rem" />;
|
|
353
399
|
case "failed":
|
|
354
400
|
return <XIcon size="1rem" />;
|
|
@@ -672,7 +718,7 @@ const StreamingLogsViewer: React.FC<StreamingLogsViewerProps> = ({
|
|
|
672
718
|
}
|
|
673
719
|
|
|
674
720
|
return (
|
|
675
|
-
<div className="mt-4 border-t border-border pt-4 w-full">
|
|
721
|
+
<div className="mt-4 border-t border-border pt-4 w-full contain-inline-size">
|
|
676
722
|
<button
|
|
677
723
|
onClick={() => setIsExpanded(!isExpanded)}
|
|
678
724
|
className="flex items-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
|
|
@@ -683,7 +729,7 @@ const StreamingLogsViewer: React.FC<StreamingLogsViewerProps> = ({
|
|
|
683
729
|
) : (
|
|
684
730
|
<ChevronRightIcon className="w-4 h-4" />
|
|
685
731
|
)}
|
|
686
|
-
|
|
732
|
+
Logs
|
|
687
733
|
</button>
|
|
688
734
|
|
|
689
735
|
{isExpanded && (
|
|
@@ -694,7 +740,7 @@ const StreamingLogsViewer: React.FC<StreamingLogsViewerProps> = ({
|
|
|
694
740
|
{packageName}
|
|
695
741
|
</h4>
|
|
696
742
|
<div className="border border-border rounded w-full">
|
|
697
|
-
<pre className="p-3 text-xs font-mono bg-background max-h-64 overflow-
|
|
743
|
+
<pre className="p-3 text-xs font-mono bg-background max-h-64 overflow-auto text-muted-foreground whitespace-pre scrollbar-thin">
|
|
698
744
|
{logs || "No logs available"}
|
|
699
745
|
</pre>
|
|
700
746
|
</div>
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import type { NotificationMessageData } from "@/core/kernel/messages";
|
|
5
|
+
import { emptyEnvironmentState, reduceEnvironmentState } from "../environment";
|
|
6
|
+
|
|
7
|
+
function progress(
|
|
8
|
+
operation_id: string,
|
|
9
|
+
update: Partial<NotificationMessageData<"environment-operation">> = {},
|
|
10
|
+
): NotificationMessageData<"environment-operation"> {
|
|
11
|
+
return {
|
|
12
|
+
operation_id,
|
|
13
|
+
action: "install",
|
|
14
|
+
source: "kernel",
|
|
15
|
+
status: { kind: "running" },
|
|
16
|
+
packages: { numpy: "running" },
|
|
17
|
+
logs: { numpy: "Downloading\n" },
|
|
18
|
+
log_mode: "append",
|
|
19
|
+
...update,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("environment progress", () => {
|
|
24
|
+
it("continues snapshot logs without mutating or duplicating them", () => {
|
|
25
|
+
const snapshot = reduceEnvironmentState(
|
|
26
|
+
emptyEnvironmentState(),
|
|
27
|
+
progress("one"),
|
|
28
|
+
);
|
|
29
|
+
const result = reduceEnvironmentState(
|
|
30
|
+
snapshot,
|
|
31
|
+
progress("one", {
|
|
32
|
+
status: { kind: "succeeded" },
|
|
33
|
+
packages: { numpy: "succeeded" },
|
|
34
|
+
logs: { numpy: "Installed\n" },
|
|
35
|
+
log_mode: "append",
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
expect(result).toEqual({
|
|
39
|
+
restart_required: false,
|
|
40
|
+
operations: [
|
|
41
|
+
{
|
|
42
|
+
operation_id: "one",
|
|
43
|
+
action: "install",
|
|
44
|
+
source: "kernel",
|
|
45
|
+
status: { kind: "succeeded" },
|
|
46
|
+
packages: { numpy: "succeeded" },
|
|
47
|
+
logs: { numpy: "Downloading\nInstalled\n" },
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
expect(snapshot.operations[0].logs).toEqual({ numpy: "Downloading\n" });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("isolates overlapping operations and starts retries with fresh logs", () => {
|
|
55
|
+
let state = reduceEnvironmentState(
|
|
56
|
+
emptyEnvironmentState(),
|
|
57
|
+
progress("one"),
|
|
58
|
+
);
|
|
59
|
+
state = reduceEnvironmentState(
|
|
60
|
+
state,
|
|
61
|
+
progress("two", { logs: { numpy: "Other\n" } }),
|
|
62
|
+
);
|
|
63
|
+
state = reduceEnvironmentState(
|
|
64
|
+
state,
|
|
65
|
+
progress("one", {
|
|
66
|
+
status: { kind: "failed", error: "Network unavailable" },
|
|
67
|
+
logs: {},
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
expect(state.operations.map((op) => [op.operation_id, op.logs])).toEqual([
|
|
71
|
+
["one", { numpy: "Downloading\n" }],
|
|
72
|
+
["two", { numpy: "Other\n" }],
|
|
73
|
+
]);
|
|
74
|
+
state = reduceEnvironmentState(state, progress("retry", { logs: {} }));
|
|
75
|
+
expect(state.operations.map((op) => [op.operation_id, op.logs])).toEqual([
|
|
76
|
+
["two", { numpy: "Other\n" }],
|
|
77
|
+
["retry", {}],
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("replaces named logs independently of the package map", () => {
|
|
82
|
+
const state = reduceEnvironmentState(
|
|
83
|
+
emptyEnvironmentState(),
|
|
84
|
+
progress("one", {
|
|
85
|
+
packages: { numpy: "succeeded", pandas: "running" },
|
|
86
|
+
logs: { numpy: "Old\n", pandas: "Old\n" },
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
const result = reduceEnvironmentState(
|
|
90
|
+
state,
|
|
91
|
+
progress("one", {
|
|
92
|
+
log_mode: "replace",
|
|
93
|
+
logs: { numpy: "", pandas: "Ignored\n" },
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
expect(result.operations[0].logs).toEqual({
|
|
97
|
+
numpy: "",
|
|
98
|
+
pandas: "Ignored\n",
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("keeps a restart requirement after a later operation succeeds", () => {
|
|
103
|
+
let state = reduceEnvironmentState(
|
|
104
|
+
emptyEnvironmentState(),
|
|
105
|
+
progress("one", {
|
|
106
|
+
status: { kind: "restart-required", reason: "Python changed" },
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
state = reduceEnvironmentState(
|
|
110
|
+
state,
|
|
111
|
+
progress("two", {
|
|
112
|
+
status: { kind: "succeeded" },
|
|
113
|
+
packages: { numpy: "succeeded" },
|
|
114
|
+
logs: {},
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
expect(state).toEqual({
|
|
118
|
+
restart_required: true,
|
|
119
|
+
operations: [
|
|
120
|
+
{
|
|
121
|
+
operation_id: "two",
|
|
122
|
+
action: "install",
|
|
123
|
+
source: "kernel",
|
|
124
|
+
status: { kind: "succeeded" },
|
|
125
|
+
packages: { numpy: "succeeded" },
|
|
126
|
+
logs: {},
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("retains preparation through later package changes and replaces it on a new attempt", () => {
|
|
134
|
+
const preparation = progress("prepare", {
|
|
135
|
+
action: "prepare",
|
|
136
|
+
status: { kind: "succeeded" },
|
|
137
|
+
packages: {},
|
|
138
|
+
logs: { environment: "Prepared environment\n" },
|
|
139
|
+
});
|
|
140
|
+
let state = reduceEnvironmentState(emptyEnvironmentState(), preparation);
|
|
141
|
+
const retained = state.operations[0];
|
|
142
|
+
for (const action of ["install", "sync", "remove"] as const) {
|
|
143
|
+
state = reduceEnvironmentState(state, progress(action, { action }));
|
|
144
|
+
state = reduceEnvironmentState(
|
|
145
|
+
state,
|
|
146
|
+
progress(action, {
|
|
147
|
+
action,
|
|
148
|
+
status: { kind: "succeeded" },
|
|
149
|
+
packages: { numpy: "succeeded" },
|
|
150
|
+
logs: {},
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
expect(state.operations).toEqual([
|
|
154
|
+
retained,
|
|
155
|
+
{
|
|
156
|
+
operation_id: action,
|
|
157
|
+
action,
|
|
158
|
+
source: "kernel",
|
|
159
|
+
status: { kind: "succeeded" },
|
|
160
|
+
packages: { numpy: "succeeded" },
|
|
161
|
+
logs: { numpy: "Downloading\n" },
|
|
162
|
+
},
|
|
163
|
+
]);
|
|
164
|
+
}
|
|
165
|
+
state = reduceEnvironmentState(
|
|
166
|
+
state,
|
|
167
|
+
progress("prepare-again", {
|
|
168
|
+
action: "prepare",
|
|
169
|
+
packages: {},
|
|
170
|
+
logs: {},
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
173
|
+
expect(state.operations).toEqual([
|
|
174
|
+
{
|
|
175
|
+
operation_id: "prepare-again",
|
|
176
|
+
action: "prepare",
|
|
177
|
+
source: "kernel",
|
|
178
|
+
status: { kind: "running" },
|
|
179
|
+
packages: {},
|
|
180
|
+
logs: {},
|
|
181
|
+
},
|
|
182
|
+
]);
|
|
183
|
+
});
|