Files
vercel__workflow/packages/web-shared/test/attribute-panel.test.ts
Alex Langenfeld a799025af9 Surface a Request ID in the run sidebar attribute panel (#3293)
## Summary & Motivation

One warm compute instance serves many invocations, so the Compute Instance ID already in this panel can't distinguish steps that ran inline within a single flow-function invocation — sibling steps sharing a Request ID did. It's also the value Vercel Logs indexes by, and this panel's View Logs button is where a reader takes it next.

The key is `vercelId`, not `requestId`: that's the name world-vercel stores the SDK's request id under crossing the wire, and AnalyticsEvent's sibling `requestId` field is declared but never written. Nothing in this repository populates `vercelId` on the object the panel receives yet — only AnalyticsEvent carries it, and grouping steps by invocation needs a step-level aggregation in workflow-server first — so the row ships as forward-compatible plumbing in the slot next to Compute Instance ID.

The second commit is an independent fix: `sortByAttributeOrder` guarded `indexOf` with `|| 0`, but a miss returns -1, which is truthy, so any key absent from `attributeOrder` sorted ahead of every listed key. It can be dropped on its own.

## Test Plan

Unit tests added; the two ordering assertions fail against the unfixed comparator. The new row could not be verified by hand — no local code path populates it.
2026-08-03 13:05:09 -05:00

102 lines
3.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { describe, expect, it } from 'vitest';
import { AttributePanel } from '../src/components/sidebar/attribute-panel.js';
const render = (data: Record<string, unknown>): string =>
renderToStaticMarkup(createElement(AttributePanel, { data }));
/**
* `vercelId` is the key the analytics read contract stores a flow-function
* invocation's request id under (the SDK sends it as `requestId`). The panel
* surfaces it as "Request ID" to match what Vercel Logs calls the value, since
* this panel's View Logs button is where a reader takes it next.
*/
describe('AttributePanel request id', () => {
it('renders vercelId as a copyable "Request ID" row', () => {
const markup = render({
stepId: 'step_1',
vercelId: 'iad1::abc-123-def',
});
expect(markup).toContain('Request ID');
expect(markup).toContain('iad1::abc-123-def');
// Copy affordance, matching the other opaque ids in this section.
expect(markup).toContain('aria-label="Copy Request ID"');
});
it('places Request ID above the coarser Compute Instance ID', () => {
const markup = render({
stepId: 'step_1',
deploymentId: 'dpl_1',
computeInstanceId: 'cinst_01JQ',
vercelId: 'iad1::abc-123-def',
});
expect(markup.indexOf('Request ID')).toBeGreaterThan(
markup.indexOf('Deployment ID')
);
expect(markup.indexOf('Request ID')).toBeLessThan(
markup.indexOf('Compute Instance ID')
);
});
/**
* The analytics schemas type both provenance ids as nullable, and the panel
* only drops a row whose display fn returns `null` — so stringifying an
* absent value would render the literal text "null" beside the label.
*/
it('omits provenance rows whose analytics value is null', () => {
const markup = render({
stepId: 'step_1',
vercelId: null,
computeInstanceId: null,
});
expect(markup).not.toContain('Request ID');
expect(markup).not.toContain('Compute Instance ID');
expect(markup).toContain('step_1');
});
});
/**
* Rows are ordered by their position in the panel's explicit `attributeOrder`
* list. Keys missing from that list sorted ahead of every listed key, because
* `indexOf`'s `-1` miss is truthy and so defeated the `|| 0` fallback meant to
* catch it — which put a failed step's Error Code above its own name.
*/
describe('AttributePanel row ordering', () => {
it("keeps a failed step's Error Code below its identity rows", () => {
const markup = render({
stepId: 'step_1',
stepName: 'step//./src/workflows/order//processPayment',
status: 'failed',
errorCode: 'USER_ERROR',
});
expect(markup).toContain('Error Code');
expect(markup.indexOf('Error Code')).toBeGreaterThan(
markup.indexOf('Module')
);
expect(markup.indexOf('Error Code')).toBeGreaterThan(
markup.indexOf('Step ID')
);
});
it('keeps a hook’s classification flags below its token', () => {
const markup = render({
hookId: 'hook_1',
token: 'tok_1',
isWebhook: true,
isSystem: false,
});
expect(markup.indexOf('isWebhook')).toBeGreaterThan(
markup.indexOf('Token')
);
expect(markup.indexOf('isWebhook')).toBeGreaterThan(
markup.indexOf('Hook ID')
);
});
});