Skip to content

Commit b435f81

Browse files
samejrTrigger.dev RepoOps
authored andcommitted
feat(webapp): add run page mocks to the storybook for the design engineer test
Add a "Design engineer test" section to the webapp storybook with three full-page mocks of the run page: a simple run, a fan out, and a run with lots of errors. Mono-RevId: b3b6c920e7d44ad3557cbfa753d5da13688a48da
1 parent 0b821d5 commit b435f81

8 files changed

Lines changed: 4093 additions & 26 deletions

File tree

‎apps/webapp/app/routes/storybook.design-engineer-test.$scenario/RunPage.tsx‎

Lines changed: 1263 additions & 0 deletions
Large diffs are not rendered by default.

‎apps/webapp/app/routes/storybook.design-engineer-test.$scenario/SpanInspector.tsx‎

Lines changed: 779 additions & 0 deletions
Large diffs are not rendered by default.

‎apps/webapp/app/routes/storybook.design-engineer-test.$scenario/mockTrace.ts‎

Lines changed: 1236 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useParams } from "@remix-run/react";
2+
import { Paragraph } from "~/components/primitives/Paragraph";
3+
import { RunPage } from "./RunPage";
4+
import { fanOut } from "./scenarios/fanOut";
5+
import { lotsOfErrors } from "./scenarios/lotsOfErrors";
6+
import { simpleRun } from "./scenarios/simpleRun";
7+
8+
// The run page for the design engineer test, mocked for three runs. RunPage.tsx and
9+
// SpanInspector.tsx are forks of the real page, free to be redesigned. Each scenario's data is a
10+
// file in ./scenarios, built with the helpers in ./mockTrace.ts.
11+
12+
const scenarios = new Map([
13+
["simple-run", simpleRun],
14+
["fan-out", fanOut],
15+
["lots-of-errors", lotsOfErrors],
16+
]);
17+
18+
export default function Story() {
19+
const { scenario: slug } = useParams();
20+
const scenario = slug ? scenarios.get(slug) : undefined;
21+
22+
if (!scenario) {
23+
return <Paragraph className="p-4">There's no scenario called “{slug}”.</Paragraph>;
24+
}
25+
26+
// Remounting per scenario starts each one with a fresh selection, filters and layout.
27+
return <RunPage key={slug} scenario={scenario} />;
28+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import {
2+
attempt,
3+
batchTriggerAndWait,
4+
buildRunPageScenario,
5+
httpRequest,
6+
log,
7+
run,
8+
runFunction,
9+
span,
10+
triggerAndWait,
11+
} from "../mockTrace";
12+
13+
// A run that fans out: it triggers a batch of child runs and waits for them. One of the children
14+
// triggers a batch of its own, so its attempts sit inside that child's attempt rather than under
15+
// the root run. Every run succeeds.
16+
17+
const syncId = "sync_2026-10-06";
18+
const productsPerBatch = 160;
19+
20+
// How long each batch spends upserting its products. The catalog queue runs five batches at a
21+
// time, so the rest wait for a slot and reuse the machines the first ones started.
22+
const upsertDurations = [1640, 2210, 1490, 1870, 1960, 1520, 2380, 1710, 1430, 2050, 1580, 1820];
23+
const batchWithImages = 3;
24+
const skusNeedingImages = ["SKU-10482", "SKU-10497", "SKU-10513", "SKU-10520"];
25+
const imageRenderDurations = [880, 1240, 760, 1030];
26+
27+
function productImages(sku: string, renderDuration: number) {
28+
return run("generate-product-images", {
29+
queuedFor: 72,
30+
machine: "medium-1x",
31+
payload: { sku, sizes: [320, 640, 1280] },
32+
output: { sku, images: 3 },
33+
tags: ["catalog-sync", sku],
34+
attempts: [
35+
attempt("cold", [
36+
runFunction([
37+
log.info(`Rendering 3 sizes for ${sku}`, { sku }),
38+
span("resize-and-upload", {
39+
duration: renderDuration,
40+
properties: { sku, sizes: [320, 640, 1280] },
41+
}),
42+
]),
43+
]),
44+
],
45+
});
46+
}
47+
48+
function productBatch(index: number, upsertDuration: number) {
49+
const offset = index * productsPerBatch;
50+
const generatesImages = index === batchWithImages;
51+
52+
return run("process-product-batch", {
53+
queue: "catalog-sync",
54+
idempotencyKey: `${syncId}-batch-${index + 1}`,
55+
payload: { syncId, offset, limit: productsPerBatch },
56+
output: {
57+
upserted: productsPerBatch,
58+
skipped: 0,
59+
...(generatesImages ? { imagesGenerated: skusNeedingImages.length } : {}),
60+
},
61+
tags: ["catalog-sync"],
62+
attempts: [
63+
attempt(index < 5 ? "cold" : "warm", [
64+
runFunction([
65+
log.info(`Processing products ${offset + 1}–${offset + productsPerBatch}`, {
66+
offset,
67+
limit: productsPerBatch,
68+
}),
69+
span("upsert-products", {
70+
duration: upsertDuration,
71+
properties: { table: "products", count: productsPerBatch },
72+
}),
73+
...(generatesImages
74+
? [
75+
batchTriggerAndWait(
76+
"generate-product-images",
77+
skusNeedingImages.map((sku, i) => productImages(sku, imageRenderDurations[i]))
78+
),
79+
log.info("Generated images for 4 new products", { skus: skusNeedingImages }),
80+
]
81+
: []),
82+
]),
83+
]),
84+
],
85+
});
86+
}
87+
88+
export const fanOut = buildRunPageScenario({
89+
seed: 2,
90+
triggeredAt: new Date("2026-10-06T03:00:00.412Z"),
91+
run: run("sync-product-catalog", {
92+
queuedFor: 142,
93+
machine: "small-2x",
94+
payload: { syncId, shop: "acme-store.myshopify.com", fullSync: true },
95+
output: { products: 1920, batches: 12, imagesGenerated: 4, searchIndexRebuilt: true },
96+
tags: ["catalog-sync"],
97+
metadata: { syncId, batchesCompleted: 12, batchesTotal: 12 },
98+
attempts: [
99+
attempt("cold", [
100+
runFunction([
101+
log.info("Syncing catalog from Shopify", { shop: "acme-store.myshopify.com" }),
102+
span("fetch-catalog", {}, [
103+
httpRequest(
104+
"GET",
105+
"https://acme-store.myshopify.com/admin/api/2025-07/products.json?limit=250",
106+
{ at: 2, duration: 612 }
107+
),
108+
log.info("Fetched 1,920 products across 8 pages", { products: 1920, pages: 8 }),
109+
]),
110+
log.info("Processing 12 batches of 160 products", { batches: 12 }),
111+
batchTriggerAndWait(
112+
"process-product-batch",
113+
upsertDurations.map((duration, index) => productBatch(index, duration)),
114+
{ concurrencyLimit: 5 }
115+
),
116+
log.info("All batches processed, rebuilding the search index"),
117+
triggerAndWait(
118+
run("rebuild-search-index", {
119+
queuedFor: 64,
120+
machine: "medium-2x",
121+
payload: { index: "products", source: syncId },
122+
output: { indexed: 1920 },
123+
attempts: [
124+
attempt("warm", [
125+
runFunction([
126+
log.info("Indexing 1,920 products", { index: "products" }),
127+
span("index-products", {
128+
duration: 2140,
129+
properties: { index: "products", documents: 1920 },
130+
}),
131+
triggerAndWait(
132+
run("purge-cdn-cache", {
133+
queuedFor: 58,
134+
payload: { paths: ["/products/*", "/collections/*"] },
135+
output: { purged: 2 },
136+
attempts: [
137+
attempt("warm", [
138+
runFunction([
139+
httpRequest("POST", "https://api.fastly.com/service/2xkR9/purge", {
140+
duration: 318,
141+
}),
142+
log.info("Purged 2 paths", {
143+
paths: ["/products/*", "/collections/*"],
144+
}),
145+
]),
146+
]),
147+
],
148+
})
149+
),
150+
]),
151+
]),
152+
],
153+
})
154+
),
155+
log.info("Catalog sync complete", { products: 1920, batches: 12 }),
156+
]),
157+
]),
158+
],
159+
}),
160+
});

0 commit comments

Comments
 (0)