mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-31 14:16:39 +01:00
Implements the five §3.x spec-completion items flagged in PAP-10735: 1. Runs row subtitle (§3.6): rebuild RunsSection on <EntityRow> with a subtitle showing inline variable values for successful runs and the failure reason for failed runs; de-dupe the redundant trigger label when label === kind (new ui/src/lib/routine-run-display.ts). 2. Runs filter chips (§3.6): Source/Status/Date selects + <FilterBar> active pills + Clear, with a filtered-empty state. 3. Delivery next-5-fires preview (§3.5): new tz-aware ui/src/lib/cron-fires.ts plus a pure previewFirePolicies() that annotates each fire with how the chosen concurrency policy treats it (queued/coalesced/skipped). 4. Secrets (§3.4): extend EnvVarEditor with recentlyUsedSecrets quick-bind chips and replace the native <select>s with shadcn <Select> for parity. 5. Triggers (§3.2): convert the always-open add form into a + New trigger expand-on-click drawer. Tests: new cron-fires + routine-run-display unit suites (30 cases); existing routine/EnvVarEditor-consumer tests stay green; tsc clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
55 lines
1.9 KiB
TypeScript
55 lines
1.9 KiB
TypeScript
import type { RoutineRunSummary, RoutineVariable } from "@paperclipai/shared";
|
|
|
|
/**
|
|
* Format a single resolved variable value for the runs-row subtitle (§3.6).
|
|
* Strings are quoted (`customer="Acme"`); numbers/booleans rendered bare.
|
|
*/
|
|
function formatVariableValue(value: unknown): string {
|
|
if (typeof value === "string") return `"${value}"`;
|
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
if (value == null) return "—";
|
|
try {
|
|
return JSON.stringify(value);
|
|
} catch {
|
|
return String(value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The de-duped trigger label for a run. The kind chip already states the kind,
|
|
* so when a trigger has no custom label (`label` falls back to `kind`) we drop
|
|
* the redundant text rather than re-stating it.
|
|
*/
|
|
export function dedupedTriggerLabel(
|
|
trigger: Pick<RoutineRunSummary["trigger"] & object, "kind" | "label"> | null | undefined,
|
|
): string | null {
|
|
if (!trigger) return null;
|
|
const label = trigger.label?.trim();
|
|
if (!label) return null;
|
|
if (label === trigger.kind) return null;
|
|
return label;
|
|
}
|
|
|
|
/**
|
|
* Subtitle line for a run row (§3.6):
|
|
* - failed runs show the failure reason ("why" without clicking through);
|
|
* - other runs show the inline resolved variable values (e.g. `customer="Acme"`).
|
|
* Returns an empty string when there is nothing meaningful to show.
|
|
*/
|
|
export function runRowSubtitle(
|
|
run: Pick<RoutineRunSummary, "status" | "failureReason" | "triggerPayload">,
|
|
variables: readonly RoutineVariable[] | null | undefined,
|
|
): string {
|
|
if (run.status === "failed") {
|
|
return run.failureReason?.trim() || "Run failed";
|
|
}
|
|
const payload = run.triggerPayload;
|
|
if (!payload || typeof payload !== "object") return "";
|
|
const parts: string[] = [];
|
|
for (const variable of variables ?? []) {
|
|
if (!(variable.name in payload)) continue;
|
|
parts.push(`${variable.name}=${formatVariableValue((payload as Record<string, unknown>)[variable.name])}`);
|
|
}
|
|
return parts.join(", ");
|
|
}
|