-
Notifications
You must be signed in to change notification settings - Fork 846
feat(inspect): Status bar rework #3186
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature/geti-inspect
Are you sure you want to change the base?
feat(inspect): Status bar rework #3186
Conversation
Signed-off-by: Dmitry Kalinin <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR reworks the status bar to consolidate training, model export, media upload, and WebRTC connection status into a unified component with priority-based display and modern visual design.
Key Changes:
- Replaces individual status components with a centralized
StatusBarthat displays the highest-priority active operation - Implements context-based state management for status tracking across features
- Updates export and upload flows to use the new status bar instead of toast notifications
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| application/ui/src/providers.tsx | Wraps app with StatusBarProvider for global status state |
| application/ui/src/features/inspect/models/export-model-dialog.component.tsx | Integrates export operations with status bar, removes toast notifications and loading states from dialog |
| application/ui/src/features/inspect/footer/training-status-item.component.tsx | Removes old training status component (replaced by adapter pattern) |
| application/ui/src/features/inspect/footer/status-bar/status-bar.module.scss | Defines styles for unified status bar with progress indicator and connection status |
| application/ui/src/features/inspect/footer/status-bar/status-bar.interface.ts | Defines TypeScript interfaces and priority system for status management |
| application/ui/src/features/inspect/footer/status-bar/status-bar.component.tsx | Implements main status bar UI with connection indicator and active operation display |
| application/ui/src/features/inspect/footer/status-bar/status-bar-context.tsx | Provides React context for managing multiple concurrent status states with priority-based active selection |
| application/ui/src/features/inspect/footer/status-bar/index.ts | Barrel export for status bar module |
| application/ui/src/features/inspect/footer/footer.component.tsx | Replaces old progress bar with new StatusBar component and adapter components |
| application/ui/src/features/inspect/footer/adapters/use-upload-status.ts | Hook for managing upload operation status lifecycle |
| application/ui/src/features/inspect/footer/adapters/use-export-status.ts | Hook for managing export operation status lifecycle |
| application/ui/src/features/inspect/footer/adapters/training-status.adapter.tsx | Adapter component that syncs training job state to status bar |
| application/ui/src/features/inspect/footer/adapters/index.ts | Barrel export for adapter modules |
| application/ui/src/features/inspect/footer/adapters/connection-status.adapter.tsx | Adapter that maps WebRTC connection state to status bar connection display |
| application/ui/src/features/inspect/dataset/upload-images.component.tsx | Updates image upload to track progress and report to status bar instead of using toasts |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| classes[activeStatus.variant], | ||
| isIndeterminate && classes.indeterminate | ||
| )} | ||
| style={{ '--progress': `${activeStatus.progress ?? 100}%` }} |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The inline style uses a CSS custom property without proper typing. Consider using as React.CSSProperties or defining a typed interface for the style object to avoid TypeScript errors with custom CSS properties.
| style={{ '--progress': `${activeStatus.progress ?? 100}%` }} | |
| style={{ '--progress': `${activeStatus.progress ?? 100}%` } as React.CSSProperties} |
| } | ||
| }, | ||
| }); | ||
| }, [trainingJob?.id, trainingJob?.payload.model_name, trainingJob?.message, trainingJob?.progress, trainingJob?.status, setStatus, removeStatus, cancelJob]); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dependency array includes trainingJob?.status but this property is not used in the effect body. This can cause unnecessary re-renders. Remove trainingJob?.status from the dependency array since it's only used in the filtering condition outside the effect.
| }, [trainingJob?.id, trainingJob?.payload.model_name, trainingJob?.message, trainingJob?.progress, trainingJob?.status, setStatus, removeStatus, cancelJob]); | |
| }, [trainingJob?.id, trainingJob?.payload.model_name, trainingJob?.message, trainingJob?.progress, setStatus, removeStatus, cancelJob]); |
| detail: `0 / ${total}`, | ||
| progress: 0, | ||
| variant: 'info', | ||
| isCancellable: false, |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The isCancellable is set to false but an onCancel callback is provided. This inconsistency means the cancel button won't be shown but the cancel logic exists. Set isCancellable: true to make the cancel functionality accessible.
| detail, | ||
| progress: percent, | ||
| variant: progress.failed > 0 ? 'warning' : 'info', | ||
| isCancellable: false, |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The isCancellable is set to false but an onCancel callback is provided. This inconsistency means the cancel button won't be shown but the cancel logic exists. Set isCancellable: true to make the cancel functionality accessible.
Signed-off-by: Dmitry Kalinin <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }, [ | ||
| trainingJob?.id, | ||
| trainingJob?.payload.model_name, | ||
| trainingJob?.message, | ||
| trainingJob?.progress, | ||
| trainingJob?.status, | ||
| setStatus, | ||
| removeStatus, | ||
| cancelJob, | ||
| ]); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing trainingJob itself in the dependency array. If the entire trainingJob object changes but the individual properties remain the same, the effect won't re-run. Add trainingJob to the dependency array or restructure to avoid relying on nested property access.
| completeExport(true); | ||
| downloadBlob(blob, filename); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dialog closes immediately on export start (line 115), but the success callback (line 91) and download still execute. If the component unmounts before the mutation completes, this could cause issues. Consider adding cleanup logic or ensure the mutation callback handles unmounted state.
| detail, | ||
| progress: percent, | ||
| variant: progress.failed > 0 ? 'warning' : 'info', | ||
| isCancellable: false, |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue as in startUpload: isCancellable is false but an onCancel handler exists (lines 59-62). This is inconsistent and prevents users from actually canceling the upload.
| // Track progress across parallel uploads | ||
| const progressRef = useRef({ completed: 0, failed: 0, total: 0 }); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a mutable ref for tracking progress in parallel uploads creates a race condition. Multiple onSuccess/onError callbacks from captureImageMutation could update progressRef.current.completed and progressRef.current.failed simultaneously, leading to inaccurate counts. Consider using atomic state updates or a reducer pattern.
Signed-off-by: Dmitry Kalinin <[email protected]>
| progress: 100, | ||
| isCancellable: false, | ||
| }); | ||
| setTimeout(() => removeStatus('export'), 5000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need to clear then to avoid memory leaks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reworked, moved timers to the context
|
|
||
| return ( | ||
| <div className={classes.statusBar}> | ||
| {/* Progress bar */} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of leaving a comment, let’s split this into a component with a descriptive name, that way the code is easier to recognize
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
Signed-off-by: Dmitry Kalinin <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }, [ | ||
| trainingJob?.id, | ||
| trainingJob?.payload.model_name, | ||
| trainingJob?.message, | ||
| trainingJob?.progress, | ||
| trainingJob?.status, | ||
| setStatus, | ||
| removeStatus, | ||
| cancelJob, | ||
| ]); |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dependency array includes individual properties from trainingJob (like trainingJob?.id, trainingJob?.message, etc.), but the useEffect also checks if (!trainingJob) at the start. If trainingJob changes from one defined object to another with the same property values, the effect won't re-run. This could cause stale status to persist. Consider including trainingJob itself as a dependency or using a stable identifier.
| const captureImageMutation = $api.useMutation('post', '/api/projects/{project_id}/images', { | ||
| onSuccess: () => { | ||
| progressRef.current.completed++; | ||
| updateProgress({ | ||
| completed: progressRef.current.completed + progressRef.current.failed, | ||
| total: progressRef.current.total, | ||
| failed: progressRef.current.failed, | ||
| }); | ||
| }, | ||
| onError: () => { | ||
| progressRef.current.failed++; | ||
| updateProgress({ | ||
| completed: progressRef.current.completed + progressRef.current.failed, | ||
| total: progressRef.current.total, | ||
| failed: progressRef.current.failed, | ||
| }); |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mutation callbacks update progressRef.current and call updateProgress synchronously. However, since Promise.allSettled runs multiple mutations in parallel, there's a potential race condition where multiple callbacks could read and update progressRef.current.completed or progressRef.current.failed simultaneously, leading to incorrect counts. Consider using atomic updates or proper state management for concurrent operations.
| const captureImageMutation = $api.useMutation('post', '/api/projects/{project_id}/images', { | |
| onSuccess: () => { | |
| progressRef.current.completed++; | |
| updateProgress({ | |
| completed: progressRef.current.completed + progressRef.current.failed, | |
| total: progressRef.current.total, | |
| failed: progressRef.current.failed, | |
| }); | |
| }, | |
| onError: () => { | |
| progressRef.current.failed++; | |
| updateProgress({ | |
| completed: progressRef.current.completed + progressRef.current.failed, | |
| total: progressRef.current.total, | |
| failed: progressRef.current.failed, | |
| }); | |
| // Atomically increment progress counters to avoid race conditions | |
| const incrementProgress = (type: 'completed' | 'failed') => { | |
| // Use a single synchronous block to update and report | |
| progressRef.current[type]++; | |
| updateProgress({ | |
| completed: progressRef.current.completed + progressRef.current.failed, | |
| total: progressRef.current.total, | |
| failed: progressRef.current.failed, | |
| }); | |
| }; | |
| const captureImageMutation = $api.useMutation('post', '/api/projects/{project_id}/images', { | |
| onSuccess: () => { | |
| incrementProgress('completed'); | |
| }, | |
| onError: () => { | |
| incrementProgress('failed'); |
| onCancel: () => { | ||
| abortControllerRef.current?.abort(); | ||
| removeStatus('batch-upload'); | ||
| }, |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The isCancellable is set to false, but an onCancel callback is provided in the updateProgress function. This is inconsistent - either enable cancellation by setting isCancellable: true, or remove the onCancel callback.
| onCancel: () => { | |
| abortControllerRef.current?.abort(); | |
| removeStatus('batch-upload'); | |
| }, |
| exportMutation.mutate(); | ||
| close(); // Close dialog immediately, status bar will show progress |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dialog closes immediately when export starts, but if the export fails synchronously (e.g., validation error before the API call), the user won't see the error in context. Consider keeping the dialog open until the mutation actually begins executing, or handle synchronous errors before closing.
| exportMutation.mutate(); | |
| close(); // Close dialog immediately, status bar will show progress | |
| try { | |
| exportMutation.mutate(); | |
| close(); // Close dialog only if mutation started successfully | |
| } catch (error) { | |
| toast.error(error instanceof Error ? error.message : String(error)); | |
| } |
Signed-off-by: Dmitry Kalinin <[email protected]>
Signed-off-by: Dmitry Kalinin <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -1,18 +1,47 @@ | |||
| import { useRef } from 'react'; | |||
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The import statement should be grouped with other React imports. Move this line to be with the existing React imports on line 3, maintaining alphabetical ordering within React imports.
| classes[activeStatus.variant], | ||
| isIndeterminate && classes.indeterminate | ||
| )} | ||
| style={{ '--progress': `${activeStatus.progress ?? 100}%` }} |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The inline style object uses a CSS custom property without TypeScript type safety. Consider adding a proper type definition for style props that include CSS custom properties, or use as React.CSSProperties to avoid potential type issues.
| style={{ '--progress': `${activeStatus.progress ?? 100}%` }} | |
| style={{ '--progress': `${activeStatus.progress ?? 100}%` } as React.CSSProperties} |
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Signed-off-by: Dmitry Kalinin <[email protected]>
📝 Description
Reworked status bar to support model training, model export, media upload and WebRTC status. Updated designs
Screen.Recording.2025-12-03.at.11.15.38.mov
✨ Changes
Select what type of change your PR is:
✅ Checklist
Before you submit your pull request, please make sure you have completed the following steps:
For more information about code review checklists, see the Code Review Checklist.