Skip to content

Commit ea14818

Browse files
authored
feat(inspect): Status bar rework (#3186)
* Implemented new status bar Signed-off-by: Dmitry Kalinin <[email protected]> * Added tests Signed-off-by: Dmitry Kalinin <[email protected]> * Fixed timers Signed-off-by: Dmitry Kalinin <[email protected]> * Fixed linter Signed-off-by: Dmitry Kalinin <[email protected]> * Fixed linter Signed-off-by: Dmitry Kalinin <[email protected]> * Minor fixes Signed-off-by: Dmitry Kalinin <[email protected]> * Fixed comment Signed-off-by: Dmitry Kalinin <[email protected]> * Fixed comments Signed-off-by: Dmitry Kalinin <[email protected]> * Fixed linter Signed-off-by: Dmitry Kalinin <[email protected]> * Fixed comment Signed-off-by: Dmitry Kalinin <[email protected]> * Fixed comments Signed-off-by: Dmitry Kalinin <[email protected]> * Moved export hook to a separate file Signed-off-by: Dmitry Kalinin <[email protected]> --------- Signed-off-by: Dmitry Kalinin <[email protected]>
1 parent 3c79f7d commit ea14818

24 files changed

+1561
-250
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (C) 2025 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { SchemaPagination } from '../src/api/openapi-spec';
5+
6+
export const getMockedPagination = (overrides?: Partial<SchemaPagination>): SchemaPagination => {
7+
return {
8+
offset: 0,
9+
limit: 0,
10+
count: 0,
11+
total: 0,
12+
...overrides,
13+
};
14+
};

application/ui/src/features/inspect/dataset/dataset.component.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Copyright (C) 2025 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
14
import { Suspense } from 'react';
25

36
import { Flex, Heading, Loading, View } from '@geti/ui';

application/ui/src/features/inspect/dataset/upload-images.component.tsx

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
1+
// Copyright (C) 2025 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
14
import { $api } from '@geti-inspect/api';
25
import { useProjectIdentifier } from '@geti-inspect/hooks';
36
import { Button, FileTrigger, toast } from '@geti/ui';
47
import { useQueryClient } from '@tanstack/react-query';
58

9+
import { useUploadStatus } from '../footer/status-bar/adapters/use-upload-status';
610
import { TrainModelButton } from '../train-model/train-model-button.component';
711
import { REQUIRED_NUMBER_OF_NORMAL_IMAGES_TO_TRIGGER_TRAINING } from './utils';
812

913
export const UploadImages = () => {
1014
const { projectId } = useProjectIdentifier();
1115
const queryClient = useQueryClient();
16+
const { startUpload, incrementProgress, completeUpload } = useUploadStatus();
1217

13-
const captureImageMutation = $api.useMutation('post', '/api/projects/{project_id}/images');
18+
const captureImageMutation = $api.useMutation('post', '/api/projects/{project_id}/images', {
19+
onSuccess: () => incrementProgress(true),
20+
onError: () => incrementProgress(false),
21+
});
1422

1523
const handleAddMediaItem = async (files: File[]) => {
24+
startUpload(files.length);
25+
1626
const uploadPromises = files.map((file) => {
1727
const formData = new FormData();
1828
formData.append('file', file);
@@ -24,10 +34,9 @@ export const UploadImages = () => {
2434
});
2535
});
2636

27-
const promises = await Promise.allSettled(uploadPromises);
37+
await Promise.allSettled(uploadPromises);
2838

29-
const succeeded = promises.filter((result) => result.status === 'fulfilled').length;
30-
const failed = promises.filter((result) => result.status === 'rejected').length;
39+
completeUpload();
3140

3241
const imagesOptions = $api.queryOptions('get', '/api/projects/{project_id}/images', {
3342
params: { path: { project_id: projectId } },
@@ -44,18 +53,6 @@ export const UploadImages = () => {
4453
actionButtons: [<TrainModelButton key='train' />],
4554
position: 'bottom-left',
4655
});
47-
return;
48-
}
49-
50-
if (failed === 0) {
51-
toast({ type: 'success', message: `Uploaded ${succeeded} item(s)` });
52-
} else if (succeeded === 0) {
53-
toast({ type: 'error', message: `Failed to upload ${failed} item(s)` });
54-
} else {
55-
toast({
56-
type: 'warning',
57-
message: `Uploaded ${succeeded} item(s), ${failed} failed`,
58-
});
5956
}
6057
};
6158

application/ui/src/features/inspect/footer/footer.component.tsx

Lines changed: 7 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,14 @@
33

44
import { Suspense, useEffect } from 'react';
55

6-
import { $api } from '@geti-inspect/api';
7-
import { SchemaJob as Job } from '@geti-inspect/api/spec';
86
import { usePatchPipeline, usePipeline, useProjectIdentifier } from '@geti-inspect/hooks';
9-
import { Flex, Loading, Text, View } from '@geti/ui';
10-
import { WaitingIcon } from '@geti/ui/icons';
7+
import { Loading, View } from '@geti/ui';
118
import { isEmpty } from 'lodash-es';
129

1310
import { useCompletedModels } from '../../../hooks/use-completed-models.hook';
14-
import { TrainingStatusItem } from './training-status-item.component';
15-
16-
const useCurrentJob = () => {
17-
const { projectId } = useProjectIdentifier();
18-
const { data: jobsData } = $api.useSuspenseQuery('get', '/api/jobs', undefined, {
19-
refetchInterval: 5000,
20-
});
21-
22-
const runningJob = jobsData.jobs.find(
23-
(job: Job) => job.project_id === projectId && (job.status === 'running' || job.status === 'pending')
24-
);
25-
26-
return runningJob;
27-
};
11+
import { ConnectionStatusAdapter } from './status-bar/adapters/connection-status.adapter';
12+
import { TrainingStatusAdapter } from './status-bar/adapters/training-status.adapter';
13+
import { StatusBar } from './status-bar/status-bar.component';
2814

2915
const useDefaultModel = () => {
3016
const models = useCompletedModels();
@@ -47,37 +33,15 @@ const useDefaultModel = () => {
4733
}, [hasNonAvailableModels, hasSelectedModel, models, patchPipeline, projectId]);
4834
};
4935

50-
export const ProgressBarItem = () => {
51-
const trainingJob = useCurrentJob();
52-
53-
if (trainingJob !== undefined) {
54-
return <TrainingStatusItem trainingJob={trainingJob} />;
55-
}
56-
57-
return (
58-
<Flex
59-
gap='size-100'
60-
height='100%'
61-
width='size-3000'
62-
alignItems='center'
63-
justifyContent='start'
64-
UNSAFE_style={{ padding: '0 var(--spectrum-global-dimension-size-200)' }}
65-
>
66-
<WaitingIcon height='14px' width='14px' stroke='var(--spectrum-global-color-gray-600)' />
67-
<Text marginStart={'5px'} UNSAFE_style={{ color: 'var(--spectrum-global-color-gray-600)' }}>
68-
Idle
69-
</Text>
70-
</Flex>
71-
);
72-
};
73-
7436
export const Footer = () => {
7537
useDefaultModel();
7638

7739
return (
7840
<View gridArea={'footer'} backgroundColor={'gray-100'} width={'100%'} height={'size-400'} overflow={'hidden'}>
7941
<Suspense fallback={<Loading mode={'inline'} size='S' />}>
80-
<ProgressBarItem />
42+
<ConnectionStatusAdapter />
43+
<TrainingStatusAdapter />
44+
<StatusBar />
8145
</Suspense>
8246
</View>
8347
);
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (C) 2025 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { ReactNode } from 'react';
5+
6+
import { renderHook } from '@testing-library/react';
7+
8+
import { useWebRTCConnection } from '../../../../../components/stream/web-rtc-connection-provider';
9+
import { StatusBarProvider, useStatusBar } from '../status-bar-context';
10+
import { ConnectionStatusAdapter } from './connection-status.adapter';
11+
12+
vi.mock('../../../../../components/stream/web-rtc-connection-provider', () => ({
13+
useWebRTCConnection: vi.fn(),
14+
}));
15+
16+
const wrapper = ({ children }: { children: ReactNode }) => (
17+
<StatusBarProvider>
18+
<ConnectionStatusAdapter />
19+
{children}
20+
</StatusBarProvider>
21+
);
22+
23+
describe('ConnectionStatusAdapter', () => {
24+
beforeEach(() => {
25+
vi.clearAllMocks();
26+
});
27+
28+
it('maps connected status', () => {
29+
vi.mocked(useWebRTCConnection).mockReturnValue({
30+
status: 'connected',
31+
start: vi.fn(),
32+
stop: vi.fn(),
33+
webRTCConnectionRef: { current: null },
34+
});
35+
36+
const { result } = renderHook(() => useStatusBar(), { wrapper });
37+
38+
expect(result.current.connection).toBe('connected');
39+
});
40+
41+
it('maps idle to disconnected', () => {
42+
vi.mocked(useWebRTCConnection).mockReturnValue({
43+
status: 'idle',
44+
start: vi.fn(),
45+
stop: vi.fn(),
46+
webRTCConnectionRef: { current: null },
47+
});
48+
49+
const { result } = renderHook(() => useStatusBar(), { wrapper });
50+
51+
expect(result.current.connection).toBe('disconnected');
52+
});
53+
54+
it('maps failed status', () => {
55+
vi.mocked(useWebRTCConnection).mockReturnValue({
56+
status: 'failed',
57+
start: vi.fn(),
58+
stop: vi.fn(),
59+
webRTCConnectionRef: { current: null },
60+
});
61+
62+
const { result } = renderHook(() => useStatusBar(), { wrapper });
63+
64+
expect(result.current.connection).toBe('failed');
65+
});
66+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (C) 2025 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { useEffect } from 'react';
5+
6+
import { useWebRTCConnection } from '../../../../../components/stream/web-rtc-connection-provider';
7+
import { useStatusBar } from '../status-bar-context';
8+
import type { ConnectionStatus } from '../status-bar.interface';
9+
10+
const CONNECTION_STATUS_MAP: Record<string, ConnectionStatus> = {
11+
connected: 'connected',
12+
connecting: 'connecting',
13+
failed: 'failed',
14+
idle: 'disconnected',
15+
disconnected: 'disconnected',
16+
};
17+
18+
export const ConnectionStatusAdapter = () => {
19+
const { setConnection } = useStatusBar();
20+
const { status } = useWebRTCConnection();
21+
22+
useEffect(() => {
23+
const connectionStatus = CONNECTION_STATUS_MAP[status] || 'disconnected';
24+
25+
setConnection(connectionStatus);
26+
}, [status, setConnection]);
27+
28+
return null;
29+
};

0 commit comments

Comments
 (0)