feat: implement timeout handling and logging for team data fetching
- Added a timeout mechanism to the getBranch calls in TeamDataService to prevent hangs on Windows setups, improving reliability during team data retrieval. - Introduced performance logging in TeamDetailView and teamSlice to track the start and completion of team selection processes, enhancing debugging capabilities. - Updated error handling to provide clearer warnings during team provisioning and selection, improving user experience.
This commit is contained in:
parent
9ce6d37528
commit
ca03b14c51
3 changed files with 27 additions and 23 deletions
|
|
@ -514,9 +514,25 @@ export class TeamDataService {
|
||||||
const leadCwd = leadEntry?.cwd ?? config.projectPath;
|
const leadCwd = leadEntry?.cwd ?? config.projectPath;
|
||||||
if (!leadCwd) return;
|
if (!leadCwd) return;
|
||||||
|
|
||||||
|
const withTimeout = async <T>(p: Promise<T>, ms: number): Promise<T> => {
|
||||||
|
let timer: NodeJS.Timeout | null = null;
|
||||||
|
try {
|
||||||
|
return await Promise.race([
|
||||||
|
p,
|
||||||
|
new Promise<T>((_resolve, reject) => {
|
||||||
|
timer = setTimeout(() => reject(new Error('timeout')), ms);
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
} finally {
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let leadBranch: string | null = null;
|
let leadBranch: string | null = null;
|
||||||
try {
|
try {
|
||||||
leadBranch = await gitIdentityResolver.getBranch(leadCwd);
|
// Git can hang on some Windows setups (network drives, locked repos, credential prompts).
|
||||||
|
// Branch is best-effort; never block team:getData on it.
|
||||||
|
leadBranch = await withTimeout(gitIdentityResolver.getBranch(leadCwd), 2000);
|
||||||
} catch {
|
} catch {
|
||||||
// Lead cwd may not be a git repo — skip enrichment entirely
|
// Lead cwd may not be a git repo — skip enrichment entirely
|
||||||
return;
|
return;
|
||||||
|
|
@ -532,7 +548,7 @@ export class TeamDataService {
|
||||||
batch.map(async (member) => {
|
batch.map(async (member) => {
|
||||||
if (!member.cwd) return;
|
if (!member.cwd) return;
|
||||||
try {
|
try {
|
||||||
const branch = await gitIdentityResolver.getBranch(member.cwd);
|
const branch = await withTimeout(gitIdentityResolver.getBranch(member.cwd), 2000);
|
||||||
if (branch && branch !== leadBranch) {
|
if (branch && branch !== leadBranch) {
|
||||||
// eslint-disable-next-line no-param-reassign -- intentional in-place enrichment
|
// eslint-disable-next-line no-param-reassign -- intentional in-place enrichment
|
||||||
member.gitBranch = branch;
|
member.gitBranch = branch;
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,7 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
|
||||||
if (!teamName) {
|
if (!teamName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.warn(`[TeamDetailView] selectTeam start team=${teamName}`);
|
||||||
void selectTeam(teamName);
|
void selectTeam(teamName);
|
||||||
void fetchDeletedTasks(teamName);
|
void fetchDeletedTasks(teamName);
|
||||||
}, [teamName, selectTeam, fetchDeletedTasks]);
|
}, [teamName, selectTeam, fetchDeletedTasks]);
|
||||||
|
|
|
||||||
|
|
@ -384,26 +384,10 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
||||||
reviewActionError: null,
|
reviewActionError: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// If this team is being provisioned right now, config.json doesn't exist yet.
|
|
||||||
// Stay in loading state — the provisioning progress callback will re-call
|
|
||||||
// selectTeam once config is written.
|
|
||||||
const isProvisioningNow = Object.values(get().provisioningRuns).some(
|
|
||||||
(run) =>
|
|
||||||
run.teamName === teamName &&
|
|
||||||
!['ready', 'disconnected', 'failed', 'cancelled'].includes(run.state)
|
|
||||||
);
|
|
||||||
if (isProvisioningNow) {
|
|
||||||
set({
|
|
||||||
selectedTeamLoading: true,
|
|
||||||
selectedTeamData: null,
|
|
||||||
selectedTeamError: null,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const startedAt = Date.now();
|
const startedAt = Date.now();
|
||||||
const traceId = `${teamName}:${startedAt}`;
|
const traceId = `${teamName}:${startedAt}`;
|
||||||
logger.info(
|
// NOTE: logger.info is not shown by default (level=WARN in dev). Use warn/console.
|
||||||
|
console.warn(
|
||||||
`[selectTeam] start trace=${traceId} skipProjectAutoSelect=${opts?.skipProjectAutoSelect === true}`
|
`[selectTeam] start trace=${traceId} skipProjectAutoSelect=${opts?.skipProjectAutoSelect === true}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -424,7 +408,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
||||||
selectedTeamError: null,
|
selectedTeamError: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info(
|
console.warn(
|
||||||
`[selectTeam] done trace=${traceId} ms=${Date.now() - startedAt} tasks=${data.tasks.length} members=${data.members.length} messages=${data.messages.length}`
|
`[selectTeam] done trace=${traceId} ms=${Date.now() - startedAt} tasks=${data.tasks.length} members=${data.members.length} messages=${data.messages.length}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -479,12 +463,15 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
||||||
!['ready', 'disconnected', 'failed', 'cancelled'].includes(run.state)
|
!['ready', 'disconnected', 'failed', 'cancelled'].includes(run.state)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isProvisioning) {
|
const msg = error instanceof Error ? error.message : String(error);
|
||||||
|
// IPC can report provisioning state explicitly.
|
||||||
|
if (msg === 'TEAM_PROVISIONING' || (msg.includes('TEAM_PROVISIONING') && isProvisioning)) {
|
||||||
set({
|
set({
|
||||||
selectedTeamLoading: true,
|
selectedTeamLoading: true,
|
||||||
selectedTeamData: null,
|
selectedTeamData: null,
|
||||||
selectedTeamError: null,
|
selectedTeamError: null,
|
||||||
});
|
});
|
||||||
|
console.warn(`[selectTeam] provisioning team=${teamName} trace=${traceId}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -494,7 +481,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
||||||
: error instanceof Error
|
: error instanceof Error
|
||||||
? error.message
|
? error.message
|
||||||
: 'Failed to fetch team data';
|
: 'Failed to fetch team data';
|
||||||
logger.error(`[selectTeam] fail team=${teamName} ms=${Date.now() - startedAt} ${message}`);
|
console.warn(`[selectTeam] fail team=${teamName} ms=${Date.now() - startedAt} ${message}`);
|
||||||
set({
|
set({
|
||||||
selectedTeamLoading: false,
|
selectedTeamLoading: false,
|
||||||
selectedTeamData: null,
|
selectedTeamData: null,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue