Skip to content

H-6737: Add a customer order timeline view - #9168

Merged
CiaranMn merged 14 commits into
mainfrom
cm/customer-order-stats
Aug 7, 2026
Merged

H-6737: Add a customer order timeline view#9168
CiaranMn merged 14 commits into
mainfrom
cm/customer-order-stats

Conversation

@CiaranMn

@CiaranMn CiaranMn commented Aug 6, 2026

Copy link
Copy Markdown
Member

🌟 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 not modify any publishable blocks or libraries, or modifications do not need publishing

📜 Does this require a change to the docs?

The changes in this PR:

  • require changes to docs which are made as part of this PR

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

Copilot AI review requested due to automatic review settings August 6, 2026 09:30
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hash Ready Ready Preview Aug 7, 2026 12:15pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
hashdotdesign-tokens Ignored Ignored Preview Aug 7, 2026 12:15pm
petrinaut Skipped Skipped Aug 7, 2026 12:15pm

@cursor

cursor Bot commented Aug 6, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Mostly demo seed JSON, frontend supply-chain UI, and tests; no auth or production API behavior described in the diff.

Overview
Adds a Customer Order Pipeline on product supply-chain pages: a waterfall from order creation through delivery-document creation to goods issue, with mean/median/P75/P95, fulfilment (from_stock / awaited_production), MTO pegging, and drill-down to a single order line. This is separate from the E2E pipeline, which needs an actual customer (or hub) arrival endpoint.

Demo graph.json payloads now include an order_timelines block (sales-order lines linked to batches, segment day counts, open-order metadata, and detail_columns). supply-chain-demo.test.ts asserts that timelines stay internally consistent with batch data (dates, segments, fulfilment rules, observed_as_of).

Shared plumbing includes segmentStats (nearest-rank percentiles, optional Tukey trimming for means), extended pipeline/order types, and SearchableSelect updates (placeholder, aria-label, clear button, combobox filter fix). Product docs gain a customer-order section and clearer E2E vs dispatch wording; tooltip.tsx is removed from this folder (consumers likely moved elsewhere in the same PR branch).

Reviewed by Cursor Bugbot for commit 121b6bd. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread apps/hash-frontend/src/pages/supply-chain/shared/segment-stats.ts Outdated
Copilot AI review requested due to automatic review settings August 6, 2026 10:48
@vercel
vercel Bot temporarily deployed to Preview – petrinaut August 6, 2026 10:48 Inactive

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 although segments.total_days.median is 100d. Use the already-computed complete-duration mean/median here, and make PipelineWaterfall consume 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 orderLines changes. After switching route or time range so that the order is no longer in orderLinesByOrder, selectedLines becomes empty but the controlled select still displays the stale order ID (its label falls back to value) 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.route is the delivery-specific route, while a linked batch's representative route can differ (the route-filter test demonstrates this at recompute-order-timelines.test.ts:151-154). Grouping solely by batch.route therefore 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 recomputeOrderTimelines deliberately returns statistics when openOrderCount > 0 and 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 when statistics has cards, and gate only the dispatched-only chips, waterfall, and lookup on shares; the outer mount guard in product.tsx:976 should likewise account for open-only data.
        {hasDispatchedOrders && (

@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 59.57%. Comparing base (c072f0d) to head (10f32f0).
⚠️ Report is 2 commits behind head on main.

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           
Flag Coverage Δ
apps.hash-api 12.56% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI review requested due to automatic review settings August 6, 2026 12:21
@vercel
vercel Bot temporarily deployed to Preview – petrinaut August 6, 2026 12:21 Inactive

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, computeOrderArrivalMarkers uses route_endpoint_date directly 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-01 is outside the 12-month window, making this and the subsequent recomputation cases fail without a code change. Freeze the clock for this suite, following shared/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.normalization is 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 labels averageOrderVolume as kilograms. Do not infer the order unit from an arbitrary graph node; omit it unless the delivered_qty column supplies one (or explicitly identify the finished-product node).
  for (const node of productNodes ?? []) {
    const unit = node.normalization?.unit;
    if (unit) {
      return unit;
    }
  }

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 inside Tooltip (pct > 4 && ...). When children is empty, the DS Tooltip wrapper won’t add a tabIndex (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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 invalid z-index value in the generated CSS (the z-index property 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" });

Copilot AI review requested due to automatic review settings August 7, 2026 09:54
@vercel
vercel Bot temporarily deployed to Preview – petrinaut August 7, 2026 09:54 Inactive

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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.

@CiaranMn
CiaranMn requested a review from vilkinsons August 7, 2026 09:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • topCustomerVolumePct is currently computed from delivered_qty even when delivered quantities can’t be meaningfully aggregated (mixed/missing delivered_qty_uom). In that case averageOrderVolume correctly becomes null, but topCustomerVolumePct will 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;

Copilot AI review requested due to automatic review settings August 7, 2026 12:06
@vercel
vercel Bot temporarily deployed to Preview – petrinaut August 7, 2026 12:06 Inactive

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-focusable tabIndex to its trigger wrapper when it either contains text or wraps a non-focusable element (see libs/@hashintel/ds-components/src/components/Tooltip/tooltip.tsx:109-119). With children empty, 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

  • filterOrderLinesByRoute treats a line as “new format” solely based on the presence of the route property ("route" in line). Because OrderLineRow.route is optional and nullable (apps/hash-frontend/src/pages/supply-chain/shared/types.ts:432-434), a payload can include route: null/undefined and 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

  • computeOrderArrivalMarkers has the same nullable-route edge case as the route filter: it enters the “new format” branch whenever the route property exists, and then continues when route / route_endpoint_date are missing. Since OrderLineRow.route and route_endpoint_date are optional+nullable (apps/hash-frontend/src/pages/supply-chain/shared/types.ts:432-435), lines with route: 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;
      }

@CiaranMn
CiaranMn added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit cfa2242 Aug 7, 2026
50 checks passed
@CiaranMn
CiaranMn deleted the cm/customer-order-stats branch August 7, 2026 13:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps > hash* Affects HASH (a `hash-*` app) area/apps > hash-api Affects the HASH API (app) area/apps type/eng > backend Owned by the @backend team type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

3 participants