fix(#298): Fix async response handling in dashboard
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

Replaced setTimeout hacks with proper polling mechanism:
- Added pollForQueryResponse() function with configurable polling interval
- Polls every 500ms with 30s timeout
- Properly handles DELIVERED and FAILED message states
- Throws errors for failures and timeouts

Updated dashboard to use polling instead of arbitrary delays:
- Removed setTimeout(resolve, 1000) hacks
- Added proper async/await for query responses
- Improved response data parsing for new query format
- Better error handling via polling exceptions

This fixes race conditions and unreliable data loading.

Fixes #298

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 22:51:25 -06:00
parent d675189a77
commit 9582d9a265
22 changed files with 524 additions and 11 deletions

View File

@@ -13,7 +13,7 @@ import {
FederationConnectionStatus,
type ConnectionDetails,
} from "@/lib/api/federation";
import { sendFederatedQuery } from "@/lib/api/federation-queries";
import { sendFederatedQuery, pollForQueryResponse } from "@/lib/api/federation-queries";
import type { Task, Event } from "@mosaic/shared";
export default function AggregatedDashboardPage(): React.JSX.Element {
@@ -50,18 +50,19 @@ export default function AggregatedDashboardPage(): React.JSX.Element {
try {
// Query tasks
if (connection.remoteCapabilities.supportsQuery) {
const taskResponse = await sendFederatedQuery(connection.id, "tasks.list", {
const taskQueryMessage = await sendFederatedQuery(connection.id, "get tasks", {
limit: 10,
});
// Wait a bit for the query to be processed and response received
// In production, this would use WebSocket or polling
await new Promise((resolve) => setTimeout(resolve, 1000));
// Poll for the response instead of using setTimeout
const taskResponse = await pollForQueryResponse(taskQueryMessage.id, {
pollInterval: 500,
timeout: 30000,
});
// For MVP, we'll use mock data since query processing is async
// In production, we'd poll for the response or use WebSocket
if (taskResponse.response) {
const responseTasks = (taskResponse.response as { data?: Task[] }).data ?? [];
const responseData = taskResponse.response as { type?: string; data?: Task[] };
const responseTasks = responseData.data ?? [];
const federatedTasks = responseTasks.map((task) => ({
task,
provenance: {
@@ -76,14 +77,19 @@ export default function AggregatedDashboardPage(): React.JSX.Element {
}
// Query events
const eventResponse = await sendFederatedQuery(connection.id, "events.list", {
const eventQueryMessage = await sendFederatedQuery(connection.id, "get events", {
limit: 10,
});
await new Promise((resolve) => setTimeout(resolve, 1000));
// Poll for the response
const eventResponse = await pollForQueryResponse(eventQueryMessage.id, {
pollInterval: 500,
timeout: 30000,
});
if (eventResponse.response) {
const responseEvents = (eventResponse.response as { data?: Event[] }).data ?? [];
const responseData = eventResponse.response as { type?: string; data?: Event[] };
const responseEvents = responseData.data ?? [];
const federatedEvents = responseEvents.map((event) => ({
event,
provenance: {