Skip to content

Commit 2c55cc5

Browse files
authored
Friday hotfixes (#4776)
1 parent 52151da commit 2c55cc5

File tree

11 files changed

+84
-39
lines changed

11 files changed

+84
-39
lines changed

application/ui/src/features/annotator/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export interface Polygon {
3030

3131
export type Shape = Rect | Polygon;
3232

33-
export type Label = { id: string; name: string; color: string; isPrediction: boolean; score?: number };
33+
export type Label = { id: string; name: string; color: string; isPrediction?: boolean; score?: number };
3434

3535
export type Annotation = {
3636
id: string;

application/ui/src/features/project/create/create-project-form.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { v4 as uuid } from 'uuid';
99

1010
import { $api } from '../../../api/client';
1111
import { paths } from '../../../router';
12-
import { LabelItemProps } from '../label-selection/interface';
12+
import { Label } from '../../annotator/types';
1313
import { LabelSelection } from '../label-selection/label-selection.component';
1414
import { TaskType } from '../task-selection/interface';
1515
import { TaskSelection } from '../task-selection/task-selection.component';
@@ -19,9 +19,7 @@ import classes from './create-project-form.module.scss';
1919

2020
export const CreateProjectForm = () => {
2121
const [selectedTask, setSelectedTask] = useState<TaskType>('detection');
22-
const [labels, setLabels] = useState<Omit<LabelItemProps, 'onDelete'>[]>([
23-
{ id: uuid(), colorValue: '#F20004', nameValue: 'Car' },
24-
]);
22+
const [labels, setLabels] = useState<Label[]>([{ id: uuid(), color: '#F20004', name: 'Car' }]);
2523
const [name, setName] = useState<string>('Project #1');
2624

2725
const navigate = useNavigate();
@@ -39,7 +37,7 @@ export const CreateProjectForm = () => {
3937
task: {
4038
task_type: selectedTask,
4139
exclusive_labels: selectedTask === 'classification',
42-
labels: labels.map((label) => ({ name: label.nameValue })),
40+
labels: labels.map((label) => ({ name: label.name })),
4341
},
4442
name,
4543
},

application/ui/src/features/project/details/project-details.component.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ describe('ProjectDetails', () => {
7979

8080
// Pipeline section headers
8181
expect(await screen.findByRole('heading', { name: 'Pipeline' })).toBeInTheDocument();
82-
expect(screen.getByRole('heading', { name: 'Source(s)' })).toBeInTheDocument();
83-
expect(screen.getByRole('heading', { name: 'Model(s)' })).toBeInTheDocument();
84-
expect(screen.getByRole('heading', { name: 'Sink(s)' })).toBeInTheDocument();
82+
expect(screen.getByRole('heading', { name: 'Source' })).toBeInTheDocument();
83+
expect(screen.getByRole('heading', { name: 'Model' })).toBeInTheDocument();
84+
expect(screen.getByRole('heading', { name: 'Sink' })).toBeInTheDocument();
8585

8686
// Pipeline content
8787
expect(screen.getByText('video_file')).toBeInTheDocument();

application/ui/src/features/project/details/project-details.component.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export const ProjectDetails = () => {
9999
<Grid columns={repeat(3, '1fr')} rows={repeat(5, 'auto')} gap='size-300' flex='1'>
100100
<View>
101101
<Heading level={2} marginBottom={'size-300'}>
102-
Source(s)
102+
Source
103103
</Heading>
104104
<Flex direction={'column'} gap={'size-300'}>
105105
{Object.entries(pipeline.data.source || {}).map(
@@ -114,7 +114,7 @@ export const ProjectDetails = () => {
114114
</View>
115115
<View>
116116
<Heading level={2} marginBottom={'size-300'}>
117-
Model(s)
117+
Model
118118
</Heading>
119119
<Flex direction={'column'} gap={'size-300'}>
120120
{Object.entries(pipeline.data.model || {}).map(
@@ -136,7 +136,7 @@ export const ProjectDetails = () => {
136136
</View>
137137
<View>
138138
<Heading level={2} marginBottom={'size-300'}>
139-
Sink(s)
139+
Sink
140140
</Heading>
141141
<Flex direction={'column'} gap={'size-300'}>
142142
{Object.entries(pipeline.data.sink || {}).map(
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
// Copyright (C) 2025 Intel Corporation
22
// SPDX-License-Identifier: Apache-2.0
33

4-
export type LabelItemProps = { id: string; colorValue: string; nameValue: string; onDelete: (id: string) => void };
4+
import { Label } from '../../annotator/types';
5+
6+
export type LabelItemProps = {
7+
label: Label;
8+
onDelete: (id: string) => void;
9+
onUpdate: (label: Label) => void;
10+
};

application/ui/src/features/project/label-selection/label-selection.component.test.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
import { Toast } from '@geti/ui';
55
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
66

7+
import { getMockedLabel } from '../../../test-utils/mocked-labels';
78
import { LabelSelection } from './label-selection.component';
89

9-
const mockLabels = [{ id: 'id-1', colorValue: '#F20004', nameValue: 'Car' }];
10+
const mockLabels = [getMockedLabel({ id: 'id-1', name: 'Car' })];
1011

1112
const App = ({ labels = mockLabels, setLabels = vi.fn() }) => {
1213
return (
@@ -33,15 +34,15 @@ describe('LabelSelection', () => {
3334

3435
expect(screen.getByLabelText('Label input for Car')).toBeInTheDocument();
3536
expect(mockSetLabels).toHaveBeenCalledWith(
36-
expect.arrayContaining([mockLabels[0], expect.objectContaining({ nameValue: 'Object' })])
37+
expect.arrayContaining([mockLabels[0], expect.objectContaining({ name: 'Object' })])
3738
);
3839
});
3940

4041
it('deletes a label item when delete is clicked', () => {
4142
const mockSetLabels = vi.fn();
4243
render(
4344
<App
44-
labels={[mockLabels[0], { id: 'id-2', colorValue: '#F20004', nameValue: 'People' }]}
45+
labels={[mockLabels[0], getMockedLabel({ id: 'id-2', color: '#F20004', name: 'People' })]}
4546
setLabels={mockSetLabels}
4647
/>
4748
);
@@ -67,11 +68,20 @@ describe('LabelSelection', () => {
6768
});
6869

6970
it('can edit the label name', () => {
70-
render(<App />);
71+
const mockSetLabels = vi.fn();
72+
render(<App setLabels={mockSetLabels} />);
7173

7274
const input = screen.getByLabelText('Label input for Car');
7375
fireEvent.change(input, { target: { value: 'Truck' } });
7476

75-
expect(screen.getByLabelText('Label input for Truck')).toBeInTheDocument();
77+
expect(mockSetLabels).toHaveBeenCalledWith(
78+
expect.arrayContaining([
79+
expect.objectContaining({
80+
id: 'id-1',
81+
name: 'Truck',
82+
color: expect.any(String),
83+
}),
84+
])
85+
);
7686
});
7787
});

application/ui/src/features/project/label-selection/label-selection.component.tsx

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (C) 2025 Intel Corporation
22
// SPDX-License-Identifier: Apache-2.0
33

4-
import { Dispatch, SetStateAction, useState } from 'react';
4+
import { Dispatch, SetStateAction } from 'react';
55

66
import {
77
ActionButton,
@@ -19,6 +19,7 @@ import {
1919
import { Add, Delete } from '@geti/ui/icons';
2020
import { v4 as uuid } from 'uuid';
2121

22+
import { Label } from '../../annotator/types';
2223
import { LabelItemProps } from './interface';
2324

2425
import classes from './label-selection.module.scss';
@@ -55,22 +56,21 @@ const LabelInput = ({ value, onChange }: { value: string; onChange: (newValue: s
5556
);
5657
};
5758

58-
const LabelItem = ({ id, colorValue, nameValue, onDelete }: LabelItemProps) => {
59-
const [color, setColor] = useState<string>(colorValue);
60-
const [name, setName] = useState<string>(nameValue);
59+
const LabelItem = ({ label, onDelete, onUpdate }: LabelItemProps) => {
60+
const { id, name, color } = label;
6161

6262
return (
6363
<Grid columns={['size-400', '1fr', 'size-400']} gap={'size-50'} maxWidth={'640px'} width={'100%'} id={id}>
6464
<ColorPicker
6565
onChange={(newColor) => {
66-
setColor(newColor.toString());
66+
onUpdate({ ...label, color: newColor.toString() });
6767
}}
6868
value={color ?? undefined}
6969
/>
7070
<LabelInput
7171
value={name}
72-
onChange={(newValue) => {
73-
setName(newValue);
72+
onChange={(newName) => {
73+
onUpdate({ ...label, name: newName });
7474
}}
7575
/>
7676
<Flex justifyContent={'center'} alignItems={'center'}>
@@ -87,8 +87,8 @@ const LabelItem = ({ id, colorValue, nameValue, onDelete }: LabelItemProps) => {
8787
};
8888

8989
type LabelSelectionProps = {
90-
labels: Omit<LabelItemProps, 'onDelete'>[];
91-
setLabels: Dispatch<SetStateAction<Omit<LabelItemProps, 'onDelete'>[]>>;
90+
labels: Label[];
91+
setLabels: Dispatch<SetStateAction<Label[]>>;
9292
};
9393
export const LabelSelection = ({ labels, setLabels }: LabelSelectionProps) => {
9494
const handleDeleteItem = (id: string) => {
@@ -104,12 +104,18 @@ export const LabelSelection = ({ labels, setLabels }: LabelSelectionProps) => {
104104
...labels,
105105
{
106106
id: uuid(),
107-
colorValue: '',
108-
nameValue: 'Object',
107+
color: '',
108+
name: 'Object',
109109
},
110110
]);
111111
};
112112

113+
const handleUpdateItem = (updatedLabel: Label) => {
114+
const updatedLabels = labels.map((label) => (label.id === updatedLabel.id ? updatedLabel : label));
115+
116+
setLabels(updatedLabels);
117+
};
118+
113119
return (
114120
<Flex
115121
direction={'column'}
@@ -121,7 +127,14 @@ export const LabelSelection = ({ labels, setLabels }: LabelSelectionProps) => {
121127
>
122128
<Flex direction={'column'} alignItems={'center'} gap={'size-100'} width={'100%'}>
123129
{labels.map((label) => {
124-
return <LabelItem key={label.id} {...label} onDelete={handleDeleteItem} />;
130+
return (
131+
<LabelItem
132+
key={label.id}
133+
label={label}
134+
onDelete={handleDeleteItem}
135+
onUpdate={handleUpdateItem}
136+
/>
137+
);
125138
})}
126139
</Flex>
127140

application/ui/src/features/project/list/menu-actions.component.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { paths } from '../../../router';
1111
export const MenuActions = ({ projectId }: { projectId: string }) => {
1212
const navigate = useNavigate();
1313

14+
const enablePipelineMutation = $api.useMutation('post', '/api/projects/{project_id}/pipeline:enable');
15+
1416
const deleteMutation = $api.useMutation('delete', '/api/projects/{project_id}', {
1517
onSuccess: () => {
1618
toast({ type: 'success', message: 'Project deleted successfully' });
@@ -22,8 +24,19 @@ export const MenuActions = ({ projectId }: { projectId: string }) => {
2224

2325
const handleMenuAction = (key: Key) => {
2426
switch (key) {
25-
case 'export':
26-
// Handle export action
27+
case 'activate':
28+
enablePipelineMutation.mutate(
29+
{ params: { path: { project_id: projectId } } },
30+
{
31+
onSuccess: () => {
32+
toast({ type: 'success', message: 'Project enabled successfully' });
33+
},
34+
onError: () => {
35+
toast({ type: 'error', message: 'Failed to enable project' });
36+
},
37+
}
38+
);
39+
2740
break;
2841
case 'edit':
2942
navigate(paths.project.details({ projectId }));
@@ -42,8 +55,7 @@ export const MenuActions = ({ projectId }: { projectId: string }) => {
4255
<MoreMenu />
4356
</ActionButton>
4457
<Menu onAction={handleMenuAction}>
45-
{/* TODO: unsupported for now. Uncomment if we ever support this */}
46-
{/* <Item key={'export'}>Export</Item> */}
58+
<Item key={'activate'}>Activate</Item>
4759
<Item key={'edit'}>Edit</Item>
4860
<Item key={'delete'}>Delete</Item>
4961
</Menu>

application/ui/src/features/project/list/project-card.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Flex, Heading, Tag, Text, View } from '@geti/ui';
55
import { clsx } from 'clsx';
66
import { NavLink } from 'react-router-dom';
77

8+
import { $api } from '../../../api/client';
89
import { SchemaProjectInput } from '../../../api/openapi-spec';
910
import thumbnailUrl from '../../../assets/mocked-project-thumbnail.png';
1011
import { paths } from '../../../router';
@@ -14,10 +15,15 @@ import classes from './project-list.module.scss';
1415

1516
type ProjectCardProps = {
1617
item: SchemaProjectInput;
17-
isActive: boolean;
1818
};
1919

20-
export const ProjectCard = ({ item, isActive }: ProjectCardProps) => {
20+
export const ProjectCard = ({ item }: ProjectCardProps) => {
21+
const pipeline = $api.useSuspenseQuery('get', '/api/projects/{project_id}/pipeline', {
22+
params: { path: { project_id: item.id || '' } },
23+
});
24+
25+
const isActive = pipeline.data?.status === 'running';
26+
2127
return (
2228
// TODO: remove this empty string check once
2329
// https://github.com/open-edge-platform/training_extensions/issues/4721 is done

application/ui/src/features/project/list/project-list.component.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ export const ProjectList = () => {
5858
>
5959
<NewProjectLink />
6060

61-
{projects.data.map((item, index) => (
62-
<ProjectCard key={item.id} item={item} isActive={index === 0} />
61+
{projects.data.map((item) => (
62+
<ProjectCard key={item.id} item={item} />
6363
))}
6464
</Grid>
6565
</Content>

0 commit comments

Comments
 (0)