H-6737: Add a customer order timeline view - #9168
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
PR SummaryLow Risk Overview Demo Shared plumbing includes Reviewed by Cursor Bugbot for commit 121b6bd. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Pull request overview
Adds customer-order timelines and statistics to supply-chain product pages, including aggregate and order-specific views.
Changes:
- Adds customer-order pipeline statistics, route filtering, and order lookup.
- Overlays order-arrival markers on E2E pipelines and extends percentile support.
- Updates demo data, tests, shared UI components, and documentation.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
product/shared/step-card.tsx |
Migrates box-plot tooltip to Ark UI. |
product/shared/pipeline-waterfall.tsx |
Adds percentile rows, order markers, and total-only mode. |
product/shared/pipeline-header.tsx |
Adds pipeline disclosure and simulator controls. |
product/recompute-order-timelines.ts |
Computes order pipeline statistics and route filtering. |
product/recompute-order-timelines.test.ts |
Tests order timeline calculations. |
product/recompute-batch-timelines.ts |
Adds P95 batch statistics. |
product/order-lookup.tsx |
Adds individual order selection and timeline display. |
product/order-arrival-markers.ts |
Computes E2E order-created markers. |
product/order-arrival-markers.test.ts |
Tests marker aggregation and positioning. |
product/e2e-what-if.tsx |
Integrates markers and revised simulator controls. |
product/customer-orders.tsx |
Adds the customer-order panel and statistics. |
product.tsx |
Integrates customer orders into the product page. |
shared/types.ts |
Defines order timeline and percentile contracts. |
shared/tooltip.tsx |
Removes the legacy tooltip implementation. |
shared/segment-stats.ts |
Adds shared segment statistics calculation. |
shared/segment-stats.test.ts |
Tests shared statistics behavior. |
shared/searchable-select.tsx |
Adds placeholders, labels, and clearing support. |
product-overview.tsx |
Documents customer-order pipeline behavior. |
summit-medium-roast/graph.json |
Adds Summit demo order timelines. |
meadow-espresso/graph.json |
Adds Meadow demo order timelines. |
supply-chain-demo.test.ts |
Validates demo order timeline data. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 24 changed files in this pull request and generated no new comments.
Suppressed comments (4)
apps/hash-frontend/src/pages/supply-chain/supply-chain-data-shell/product/recompute-order-timelines.ts:394
- The median of complete order durations is not the sum of segment medians. For three lines with segment pairs
(0,100),(100,0), and(100,100), this reports 200d althoughsegments.total_days.medianis 100d. Use the already-computed complete-duration mean/median here, and makePipelineWaterfallconsume those summary totals when all segments are shown; otherwise the new customer-order row does not display the requested order-to-dispatch median.
total_mean: totalMean,
total_median: totalMedian,
total_p75: segments.total_days?.p75,
total_p95: segments.total_days?.p95,
apps/hash-frontend/src/pages/supply-chain/supply-chain-data-shell/product/order-lookup.tsx:139
- The selected order is retained when
orderLineschanges. After switching route or time range so that the order is no longer inorderLinesByOrder,selectedLinesbecomes empty but the controlled select still displays the stale order ID (its label falls back tovalue) and remains clearable. Reset the selection when the map no longer contains it, or derive a valid selected value before passing it to the select.
const selectedLines = useMemo(
() => (selectedOrder ? (orderLinesByOrder.get(selectedOrder) ?? []) : []),
[orderLinesByOrder, selectedOrder],
);
apps/hash-frontend/src/pages/supply-chain/supply-chain-data-shell/product/order-arrival-markers.ts:172
OrderLineRow.routeis the delivery-specific route, while a linked batch's representative route can differ (the route-filter test demonstrates this atrecompute-order-timelines.test.ts:151-154). Grouping solely bybatch.routetherefore assigns such a direct order line to the hub marker, or omits it from the intended route. Prefer the line route when present and only fall back to the batch route for legacy rows.
if (!batch?.route || !batch.delivery_date || !summaries[batch.route]) {
apps/hash-frontend/src/pages/supply-chain/supply-chain-data-shell/product/customer-orders.tsx:301
- This hides the expand control whenever the selected window has no dispatched lines, even though
recomputeOrderTimelinesdeliberately returnsstatisticswhenopenOrderCount > 0and this panel defines open-line count/age cards. For example, eight open lines and zero recent dispatches renders only “no dispatched order lines,” making both open-order metrics unreachable. Allow expansion whenstatisticshas cards, and gate only the dispatched-only chips, waterfall, and lookup onshares; the outer mount guard inproduct.tsx:976should likewise account for open-only data.
{hasDispatchedOrders && (
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9168 +/- ##
=======================================
Coverage 59.57% 59.57%
=======================================
Files 1413 1413
Lines 138053 138053
Branches 6510 6510
=======================================
Hits 82241 82241
Misses 54770 54770
Partials 1042 1042
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 24 changed files in this pull request and generated no new comments.
Suppressed comments (4)
apps/hash-frontend/src/pages/supply-chain/supply-chain-data-shell/product.tsx:579
- The E2E summaries and batches are filtered by route-endpoint month at lines 554-570, but this passes every raw order line. For delivery-specific rows,
computeOrderArrivalMarkersusesroute_endpoint_datedirectly and never consults the filtered batches (order-arrival-markers.ts:162-183), so selecting a 3-month window still aggregates historical order arrivals whenever that route has any recent batch. Pass the active cutoff into marker computation and exclude route observations whose endpoint is outside it.
computeOrderArrivalMarkers(
graph.order_timelines?.lines ?? [],
filteredGraph.batch_timelines?.batches ?? [],
filteredGraph.pipeline_summary,
apps/hash-frontend/src/pages/supply-chain/supply-chain-data-shell/product/shared/step-card.tsx:403
- This focusable trigger is rendered inside the card's outer
<button>(step-card.tsx:572), creating a nested tab stop inside an interactive element. Tabbing to it or activating it can also invoke the parent card action, so keyboard users cannot inspect the distribution independently. Restructure the card so the drill-down button and tooltip trigger are sibling controls, or attach the tooltip description to the outer button instead.
<div
className={boxPlotTooltipTrigger}
aria-label="Show distribution statistics"
// Tooltip triggers must receive focus to expose their content to keyboard users.
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
tabIndex={0}
apps/hash-frontend/src/pages/supply-chain/supply-chain-data-shell/product/recompute-order-timelines.test.ts:40
- These “recent” fixtures are compared against
cutoffForRange, which derives its cutoff from the real current month. From June 2027 onward,2026-06-01is outside the 12-month window, making this and the subsequent recomputation cases fail without a code change. Freeze the clock for this suite, followingshared/time-range.test.ts:19-24, and restore real timers afterward.
describe("customer-order recomputation", () => {
it("filters on goods-issue month", () => {
const recent = line("2026-06-01", "from_stock");
const old = line("2024-01-01", "from_stock");
expect(filterOrderLines([recent, old], "12m")).toEqual([recent]);
apps/hash-frontend/src/pages/supply-chain/supply-chain-data-shell/product/customer-orders.tsx:39
GraphNode.normalizationis per production step (shared/types.ts:247-250), not the unit of the finished product's delivered quantity. On a graph whose first normalized upstream node is in kilograms while customer orders are in cases, this fallback labelsaverageOrderVolumeas kilograms. Do not infer the order unit from an arbitrary graph node; omit it unless thedelivered_qtycolumn supplies one (or explicitly identify the finished-product node).
for (const node of productNodes ?? []) {
const unit = node.normalization?.unit;
if (unit) {
return unit;
}
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (1)
apps/hash-frontend/src/pages/supply-chain/supply-chain-data-shell/product/shared/pipeline-waterfall.tsx:334
- In
PipelineBar, segments with very small widths render no child insideTooltip(pct > 4 && ...). Whenchildrenis empty, the DSTooltipwrapper won’t add atabIndex(it only does so when it has text content or a non-focusable element child), so those segments can’t be focused to reveal their tooltip via keyboard.
Render a non-empty child even when the value label is visually suppressed (e.g. a non‑breaking space or a visually-hidden label) so the trigger remains keyboard-focusable.
{pct > 4 && (
<span className={segValue}>
{formatNumber(value, { maximumFractionDigits: 0 })}d
</span>
)}
</Tooltip>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (1)
apps/hash-frontend/src/pages/supply-chain/supply-chain-data-shell/product/shared/step-card.tsx:312
zIndex: "tooltip !important"produces an invalidz-indexvalue in the generated CSS (thez-indexproperty can’t take a space-separated value like this), so the browser will ignore it and the tooltip may end up behind other UI. Use a concrete z-index value (or a plain token without!important) instead.
const boxPlotTooltipPositioner = css({ zIndex: "tooltip !important" });
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 46e5fd8. Configure here.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (1)
apps/hash-frontend/src/pages/supply-chain/supply-chain-data-shell/product/recompute-order-timelines.ts:316
topCustomerVolumePctis currently computed fromdelivered_qtyeven when delivered quantities can’t be meaningfully aggregated (mixed/missingdelivered_qty_uom). In that caseaverageOrderVolumecorrectly becomesnull, buttopCustomerVolumePctwill still produce a numeric percentage derived from incomparable units, which is misleading.
const totalCustomerVolume = [...customerVolumes.values()].reduce(
(sum, value) => sum + value,
0,
);
const topCustomerVolumePct =
totalCustomerVolume > 0
? (Math.max(...customerVolumes.values()) / totalCustomerVolume) * 100
: null;
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (3)
apps/hash-frontend/src/pages/supply-chain/supply-chain-data-shell/product/shared/pipeline-waterfall.tsx:333
- When a segment is too narrow to show an inline value (
pct <= 4), the Tooltip receives no child content. The DS Tooltip only adds a keyboard-focusabletabIndexto its trigger wrapper when it either contains text or wraps a non-focusable element (seelibs/@hashintel/ds-components/src/components/Tooltip/tooltip.tsx:109-119). Withchildrenempty, the segment remains hover-only, so keyboard users can’t access the per-segment timings/tooltips for small segments.
Render a (possibly empty) non-focusable child element unconditionally so the wrapper becomes focusable and the tooltip can open on focus, even when the label is visually suppressed.
{pct > 4 && (
<span className={segValue}>
{formatNumber(value, { maximumFractionDigits: 0 })}d
</span>
)}
apps/hash-frontend/src/pages/supply-chain/supply-chain-data-shell/product/recompute-order-timelines.ts:84
filterOrderLinesByRoutetreats a line as “new format” solely based on the presence of therouteproperty ("route" in line). BecauseOrderLineRow.routeis optional and nullable (apps/hash-frontend/src/pages/supply-chain/shared/types.ts:432-434), a payload can includeroute: null/undefinedand still pass this branch.
In that case the function returns false for every route (null === route), and it never falls back to the batch-derived compatibility logic, so otherwise-usable lines are dropped from all route-scoped views.
Prefer branching on a non-null route value (and fall back when it’s missing).
return lines.filter((line) => {
if ("route" in line) {
return line.route === route;
}
apps/hash-frontend/src/pages/supply-chain/supply-chain-data-shell/product/order-arrival-markers.ts:176
computeOrderArrivalMarkershas the same nullable-route edge case as the route filter: it enters the “new format” branch whenever therouteproperty exists, and thencontinues whenroute/route_endpoint_dateare missing. SinceOrderLineRow.routeandroute_endpoint_dateare optional+nullable (apps/hash-frontend/src/pages/supply-chain/shared/types.ts:432-435), lines withroute: null(or no endpoint) get skipped entirely instead of falling back to the batch-derived compatibility path.
Gate the “new format” branch on both a non-null route and a non-null endpoint date so older/partial payloads still produce markers.
for (const line of orderLines) {
if ("route" in line) {
const route = line.route;
const endpointDate = line.route_endpoint_date;
if (!route || !endpointDate || !summaries[route]) {
continue;
}

🌟 What is the purpose of this PR?
On product-specific pages in the supply chain feature, add a 'customer orders' pipeline below the E2E pipeline which shows the mean/median/P75/P95 timings from order creation to goods dispatch, as well as a few other statistics. Also allows users to select a specific order to see its timeline.
Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
📜 Does this require a change to the docs?
The changes in this PR:
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR: