feat(samples): add Maestro Cases coded web app (case-management)#546
feat(samples): add Maestro Cases coded web app (case-management)#546amrit-agarwal-1 wants to merge 1 commit into
Conversation
| import React, { createContext, useContext, useCallback, useEffect, useMemo, useRef, useState } from 'react'; | ||
| import type { ReactNode } from 'react'; | ||
| import { useAuth } from '@/hooks/useAuth'; | ||
| import { listCaseDefinitions } from '@/lib/sdk'; |
There was a problem hiding this comment.
The entire src/lib/ directory is missing from this PR. Every source file imports from modules that don't exist in the branch:
@/lib/sdk—listCaseDefinitions,getCaseInstance,runLifecycle,LifecycleAction,caseLabel,insightsTopElementFailed,getSlaSummaryAll, …@/lib/types—CaseDefinition,CaseInstance,ActionTask,CaseSla,CaseStage,SlaStatus, …@/lib/env—ENV(processKey, tenantName, orgName)@/lib/format—formatDate,humanize,statusTone,priorityTone@/lib/expr—buildVarContext@/lib/recent—addRecentCase@/lib/metrics—tenantMetrics,slaCounts
Without these files the sample will not compile. Please add the src/lib/ directory to the PR.
| const [loading, setLoading] = useState(true); | ||
| const [error, setError] = useState<string | null>(null); | ||
| const [tick, setTick] = useState(0); | ||
| const alive = useRef(true); |
There was a problem hiding this comment.
Using a useRef for the cancellation flag introduces a race condition when deps change while a slow fetch is in-flight.
Sequence that triggers the bug:
- Run 1 fires —
alive.current = true, starts slow fetch A. - A dep changes — React runs cleanup for run 1:
alive.current = false. - Run 2 fires —
alive.current = true, starts fast fetch B. - Fetch B completes —
alive.currentistrue→ state updated with B's data. ✓ - Fetch A (slow) completes —
alive.currentis stilltrue(set by run 2) → state is overwritten with stale data from A. ✗
The fix is to use a local let variable per effect closure instead of a shared ref. Each closure then captures its own cancellation flag:
| const alive = useRef(true); | |
| const reload = useCallback(() => setTick((t) => t + 1), []); |
And update the effect body:
useEffect(() => {
let alive = true; // ← local per-run, not a shared ref
setLoading(true);
setError(null);
fn()
.then((res) => { if (alive) setData(res); })
.catch((e: unknown) => { if (alive) setError(e instanceof Error ? e.message : String(e)); })
.finally(() => { if (alive) setLoading(false); });
return () => { alive = false; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [...deps, tick]);(The useRef(true) declaration on line 16 and the alive.current = true reset on line 21 can both be removed once the effect is updated.)
Review summaryTwo issues found: Blocking — Missing
|
842800f to
ab55df6
Compare
| useEffect(() => { | ||
| if (!isAuthenticated) return; | ||
| alive.current = true; | ||
| setLoading(true); |
There was a problem hiding this comment.
Same race condition as useAsync.ts (see that thread): alive.current = true is reset at the start of each effect run. When sdk or isAuthenticated changes while a slow listCaseDefinitions fetch is in-flight, the new run sets alive.current = true, so when the old fetch eventually resolves it finds the flag still true and overwrites allDefinitions with stale data.
Fix: use a per-closure local variable instead of the shared ref:
| setLoading(true); | |
| let alive = true; |
Then in the cleanup:
return () => { alive = false; };And remove the const alive = useRef(true) declaration on line 48 (it becomes unused). Each effect closure captures its own alive flag, so an old fetch can never clobber a newer one.
| cursors.current = [undefined]; | ||
| setPage(0); | ||
| }, [processKey]); | ||
|
|
There was a problem hiding this comment.
This effect has no per-run cancellation, so stale fetches can overwrite state when page, processKey, or pageSize changes.
Sequence that triggers the bug:
- User is on page 0 — fetch A starts,
alive.currentistrue. - User clicks "Next" —
pagebecomes 1. React re-runs this effect. Fetch B starts.alive.currentis stilltrue(the mount-only effect on line 20 only sets it once; it never resets between dep changes). - Fetch A (slow) completes —
alive.currentistrue→ state is overwritten with page-0 data while the user is looking at page 1. ✗
The mount-only effect (lines 20–25) is the wrong shape for this use case. Replace both effects with a single per-run local variable:
| useEffect(() => { | |
| let alive = true; |
Then set alive = false in the cleanup (return () => { alive = false; }) and drop the const alive = useRef(true) declaration on line 18. The mount-only useEffect on lines 20–25 can be removed entirely.
Review summaryTwo new findings (same root cause as the already-open Bug — Race condition in
|
A single-case operations console for UiPath Maestro Cases, built with the @uipath/uipath-typescript SDK and the Apollo Vertex (apollo-wind) design system with light/dark theme. Covers case selection, paginated instances, lifecycle actions (pause/resume/close/reopen), stages, execution history, variables, embedded Action Center tasks with native task-detail rendering, and Insights analytics (status timeline, tenant ranking, step performance). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ab55df6 to
9aefccf
Compare
|
| start: Date, | ||
| end: Date, | ||
| ): Promise<ElementStat[]> { | ||
| return new Cases(sdk).getElementStats(processKey, packageId, start, end, packageVersion); |
There was a problem hiding this comment.
Cases.getElementStats takes a single MaestroProcessStatsRequest object, not five positional arguments — this call passes processKey (a string) where the method expects { processKey, packageId, packageVersion, startTime, endTime }, so TypeScript will reject it at compile time and it will fail at runtime.
Also note that the function signature lists params as (sdk, processKey, packageId, packageVersion, start, end) — packageVersion is 4th — but the call site has them in a different order: (processKey, packageId, start, end, packageVersion). Even ignoring the object vs. positional mismatch, start and end are in the wrong slots.
| return new Cases(sdk).getElementStats(processKey, packageId, start, end, packageVersion); | |
| return new Cases(sdk).getElementStats({ processKey, packageId, packageVersion, startTime: start, endTime: end }); |
Review summaryOne new finding: Bug — wrong call signature for
|



A single-case operations console for UiPath Maestro Cases, built with the @uipath/uipath-typescript SDK and the Apollo Vertex (apollo-wind) design system with light/dark theme. Covers case selection, paginated instances, lifecycle actions (pause/resume/close/reopen), stages, execution history, variables, embedded Action Center tasks with native task-detail rendering, and Insights analytics (status timeline, tenant ranking, step performance).
App link: https://maestrolab.staging.uipath.host/case-management