Skip to content

feat(samples): add Maestro Cases coded web app (case-management)#546

Open
amrit-agarwal-1 wants to merge 1 commit into
mainfrom
feat/samples-case-management-app
Open

feat(samples): add Maestro Cases coded web app (case-management)#546
amrit-agarwal-1 wants to merge 1 commit into
mainfrom
feat/samples-case-management-app

Conversation

@amrit-agarwal-1

Copy link
Copy Markdown
Contributor

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

@amrit-agarwal-1 amrit-agarwal-1 requested a review from a team June 22, 2026 14:06
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';

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.

The entire src/lib/ directory is missing from this PR. Every source file imports from modules that don't exist in the branch:

  • @/lib/sdklistCaseDefinitions, getCaseInstance, runLifecycle, LifecycleAction, caseLabel, insightsTopElementFailed, getSlaSummaryAll, …
  • @/lib/typesCaseDefinition, CaseInstance, ActionTask, CaseSla, CaseStage, SlaStatus, …
  • @/lib/envENV (processKey, tenantName, orgName)
  • @/lib/formatformatDate, humanize, statusTone, priorityTone
  • @/lib/exprbuildVarContext
  • @/lib/recentaddRecentCase
  • @/lib/metricstenantMetrics, 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);

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.

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:

  1. Run 1 fires — alive.current = true, starts slow fetch A.
  2. A dep changes — React runs cleanup for run 1: alive.current = false.
  3. Run 2 fires — alive.current = true, starts fast fetch B.
  4. Fetch B completes — alive.current is true → state updated with B's data. ✓
  5. Fetch A (slow) completes — alive.current is still true (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:

Suggested change
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.)

@claude

claude Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review summary

Two issues found:

Blocking — Missing src/lib/ directory

src/hooks/useCases.tsx:4 — The entire src/lib/ directory (sdk, types, env, format, expr, recent, metrics) is absent from the PR branch. The sample will not compile without it.

Bug — Race condition in useAsync

src/hooks/useAsync.ts:16 — The shared useRef cancellation flag is reset to true at the start of each effect run, so a slow in-flight fetch from a prior run can overwrite state set by a faster subsequent run. Fix: replace the useRef with a local let alive = true variable captured per-closure.

@amrit-agarwal-1 amrit-agarwal-1 force-pushed the feat/samples-case-management-app branch from 842800f to ab55df6 Compare June 23, 2026 03:55
useEffect(() => {
if (!isAuthenticated) return;
alive.current = true;
setLoading(true);

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.

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:

Suggested change
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]);

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.

This effect has no per-run cancellation, so stale fetches can overwrite state when page, processKey, or pageSize changes.

Sequence that triggers the bug:

  1. User is on page 0 — fetch A starts, alive.current is true.
  2. User clicks "Next" — page becomes 1. React re-runs this effect. Fetch B starts. alive.current is still true (the mount-only effect on line 20 only sets it once; it never resets between dep changes).
  3. Fetch A (slow) completes — alive.current is true → 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:

Suggested change
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.

@claude

claude Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review summary

Two new findings (same root cause as the already-open useAsync.ts race-condition thread):

Bug — Race condition in useCases.tsx

src/hooks/useCases.tsx:63alive.current = true is reset at the start of each effect run, so a slow listCaseDefinitions fetch from a prior run can overwrite allDefinitions with stale data when sdk or isAuthenticated changes mid-flight. Fix: replace the shared useRef with a per-closure let alive = true variable.

Bug — Race condition in useInstancePages.ts

src/hooks/useInstancePages.ts:33 — The fetch effect has no per-run cancellation. The mount-only effect that manages alive.current only fires once, so when page or processKey changes the old in-flight fetch always sees alive.current === true and overwrites state with stale page data. Same fix: drop the separate mount effect and use let alive = true captured per closure with a cleanup return () => { alive = false; }.

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>
@amrit-agarwal-1 amrit-agarwal-1 force-pushed the feat/samples-case-management-app branch from ab55df6 to 9aefccf Compare June 23, 2026 04:28
@sonarqubecloud

Copy link
Copy Markdown

start: Date,
end: Date,
): Promise<ElementStat[]> {
return new Cases(sdk).getElementStats(processKey, packageId, start, end, packageVersion);

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.

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.

Suggested change
return new Cases(sdk).getElementStats(processKey, packageId, start, end, packageVersion);
return new Cases(sdk).getElementStats({ processKey, packageId, packageVersion, startTime: start, endTime: end });

@claude

claude Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review summary

One new finding:

Bug — wrong call signature for Cases.getElementStats

samples/case-management/src/lib/sdk.ts:365Cases.getElementStats takes a single MaestroProcessStatsRequest object, not five positional args. The call passes processKey (a string) where the SDK expects { processKey, packageId, packageVersion, startTime, endTime }. TypeScript rejects this at compile time and it fails at runtime. The parameter order in the wrapper signature vs. the call site also disagrees (packageVersion is 4th in the wrapper params but passed last in the call). Fix: new Cases(sdk).getElementStats({ processKey, packageId, packageVersion, startTime: start, endTime: end }).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant