Skip to content

Commit cf45ed4

Browse files
authored
Labels reordering - buttons (#375)
1 parent 46548b8 commit cf45ed4

File tree

18 files changed

+342
-207
lines changed

18 files changed

+342
-207
lines changed

web_ui/packages/core/src/feature-flags/services/feature-flag-service.interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const DEV_FEATURE_FLAGS = {
3535
FEATURE_FLAG_MANAGE_USERS_ROLES: false,
3636
FEATURE_FLAG_REQ_ACCESS: false,
3737
FEATURE_FLAG_NEW_CONFIGURABLE_PARAMETERS: false,
38+
FEATURE_FLAG_LABELS_REORDERING: false,
3839
FEATURE_FLAG_TELEMETRY_STACK: true,
3940
FEATURE_FLAG_ANNOTATION_HOLE: false,
4041
// Only used for unit testing

web_ui/src/core/labels/label-tree-view.interface.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,21 @@ interface SetLabelsValidation {
6262

6363
export type SetValidationProps = SetTreeValidation | SetLabelsValidation;
6464

65+
export type ReorderType = 'up' | 'down';
66+
67+
export interface TreeItemActions {
68+
save: (editedLabel?: LabelTreeItem, oldId?: string) => void;
69+
deleteItem: (deletedLabel: LabelTreeItem) => void;
70+
addChild: (parentId: string | null, groupName: string, type: LabelItemType) => void;
71+
reorder: (item: LabelTreeItem, type: ReorderType) => void;
72+
}
73+
6574
export interface EditableTreeViewProps {
6675
labelsTree: LabelTreeItem[];
6776
projectLabels?: LabelTreeItem[];
6877
isHierarchicalMode: boolean;
6978
isInEditMode: boolean;
70-
save: (editedLabel?: LabelTreeItem, oldId?: string) => void;
71-
deleteItem: (deletedLabel: LabelTreeItem) => void;
72-
addChild: (parentId: string, groupName: string, type: LabelItemType) => void;
79+
actions: TreeItemActions;
7380
domains: DOMAIN[];
7481
type: Readonly.NO;
7582
options?: LabelTreeViewOptions;
@@ -80,9 +87,7 @@ interface ReadonlyTreeView {
8087
labelsTree: LabelTreeItem[];
8188
projectLabels?: LabelTreeItem[];
8289
isHierarchicalMode: boolean;
83-
save?: never;
84-
deleteItem?: never;
85-
addChild?: never;
90+
actions: never;
8691
domains?: never;
8792
type: Readonly.YES;
8893
options?: LabelTreeViewOptions;
@@ -91,11 +96,9 @@ interface ReadonlyTreeView {
9196
export type LabelsTreeViewProps = EditableTreeViewProps | ReadonlyTreeView;
9297

9398
export interface CommonTreeViewProps {
94-
labels: LabelTreeItem[];
99+
treeItems: LabelTreeItem[];
95100
isEditable: boolean;
96-
save: (editedLabel?: LabelTreeItem, oldId?: string) => void;
97-
addChild: (parentId: string | null, groupName: string, type: LabelItemType) => void;
98-
deleteItem: (deletedItem: LabelTreeItem) => void;
101+
actions: TreeItemActions;
99102
projectLabels: LabelTreeItem[];
100103
domains: DOMAIN[];
101104
treeValidationErrors: Record<string, Record<string, string>>;

web_ui/src/pages/create-project/components/project-labels-management/task-labels-management/task-labels-creation-tree/task-labels-creation-tree.component.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ type TaskLabelsCreationTreeProps = Omit<EditableTreeViewProps, 'type' | 'isInEdi
88

99
export const TaskLabelsCreationTree = ({
1010
labelsTree,
11-
deleteItem,
12-
addChild,
13-
save,
11+
actions,
1412
domains,
1513
isHierarchicalMode,
1614
projectLabels,
@@ -22,12 +20,10 @@ export const TaskLabelsCreationTree = ({
2220
projectLabels={projectLabels}
2321
type={Readonly.NO}
2422
isHierarchicalMode={isHierarchicalMode}
25-
addChild={addChild}
26-
deleteItem={deleteItem}
23+
actions={actions}
2724
domains={domains}
2825
isInEditMode={true}
2926
options={{ newTree: true }}
30-
save={save}
3127
setValidationError={setValidationError}
3228
/>
3329
);

web_ui/src/pages/create-project/components/project-labels-management/task-labels-management/task-labels-management.component.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
LabelItemType,
1111
LabelTreeItem,
1212
LabelTreeLabelProps,
13+
ReorderType,
1314
SetValidationProps,
1415
ValidationErrorType,
1516
} from '../../../../../core/labels/label-tree-view.interface';
@@ -20,6 +21,7 @@ import {
2021
getLabelsWithAddedChild,
2122
getLabelWithoutDeleted,
2223
getNextColor,
24+
getReorderedTree,
2325
getTreeWithUpdatedItem,
2426
} from '../../../../../shared/components/label-tree-view/utils';
2527
import { isYupValidationError } from '../../../../user-management/profile-page/utils';
@@ -61,13 +63,13 @@ export const TaskLabelsManagement = ({
6163
setValidationError({ type: ValidationErrorType.TREE, validationError: message });
6264
};
6365

64-
const addChild = (parentId: string, groupName: string, childType: LabelItemType) => {
66+
const addChild = (parentId: string | null, groupName: string, childType: LabelItemType) => {
6567
setLabels(getLabelsWithAddedChild(labels, labels, parentId, groupName, childType));
6668
};
6769

6870
const addLabel = (label: LabelTreeItem, shouldGoNext = false) => {
6971
const newLabel = { ...label, state: LabelItemEditionState.NEW };
70-
const newLabels: LabelTreeItem[] = type === LABEL_TREE_TYPE.SINGLE ? [newLabel] : [...labels, newLabel];
72+
const newLabels: LabelTreeItem[] = type === LABEL_TREE_TYPE.SINGLE ? [newLabel] : [newLabel, ...labels];
7173

7274
setLabels(newLabels);
7375

@@ -88,6 +90,12 @@ export const TaskLabelsManagement = ({
8890
setLabels(updated);
8991
};
9092

93+
const reorder = (item: LabelTreeItem, mode: ReorderType) => {
94+
//TODO: there is also reordering in annotations! - maybe worth to share some code!
95+
const updated = getReorderedTree(labels, item, mode);
96+
setLabels(updated);
97+
};
98+
9199
useEffect(() => {
92100
try {
93101
const validated = validateLabelsSchema(domain).validateSync({ labels }, { abortEarly: false });
@@ -143,11 +151,9 @@ export const TaskLabelsManagement = ({
143151
<TaskLabelsCreationTree
144152
isHierarchicalMode={relation === LabelsRelationType.MIXED}
145153
labelsTree={labels}
146-
addChild={addChild}
154+
actions={{ addChild, deleteItem, save: saveHandler, reorder }}
147155
projectLabels={projectLabels}
148156
domains={domains}
149-
deleteItem={deleteItem}
150-
save={saveHandler}
151157
setValidationError={setValidationError}
152158
/>
153159
</Flex>

web_ui/src/pages/create-project/components/project-labels-management/task-labels-management/task-labels-management.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,8 @@ describe('Task label management', () => {
271271
rerender(<Labels />);
272272
addLabel('test 2');
273273

274-
expect(allLabels[0].name).toBe(labelName);
275-
expect(allLabels[1].name).toBe('test 2');
274+
expect(allLabels[0].name).toBe('test 2');
275+
expect(allLabels[1].name).toBe(labelName);
276276
expect((allLabels[0] as LabelTreeLabelProps).group).toBe(
277277
`${DOMAIN.DETECTION} labels${GROUP_SEPARATOR}${allLabels[0].name}`
278278
);

web_ui/src/pages/create-project/new-project-dialog-task-chain.test.tsx

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,6 @@ describe('New project dialog - Task chain', () => {
197197
});
198198

199199
describe('NewProjectDialog - Check data to save', () => {
200-
const addChildLabel = async (labelName: string) => {
201-
fireEvent.click(screen.getByRole('button', { name: 'add child label button' }));
202-
203-
await userEvent.keyboard(labelName);
204-
await userEvent.keyboard('{enter}');
205-
};
206-
207200
it('Task chain - check if enter properly saves first task label', async () => {
208201
await typeIntoTextbox('task chain - Detection Classification', 'Project name');
209202
clickNextButton();
@@ -217,12 +210,10 @@ describe('New project dialog - Task chain', () => {
217210
await userEvent.keyboard('{enter}');
218211

219212
const groupNameInput = screen.getByRole('textbox', { name: 'Label group name' });
220-
clearInput(groupNameInput);
221213
fireEvent.change(groupNameInput, { target: { value: 'test' } });
222214
await userEvent.keyboard('{enter}');
223215

224-
await addChildLabel('child1');
225-
216+
fireEvent.click(screen.getByRole('button', { name: 'add child label button' }));
226217
fireEvent.click(screen.getByRole('button', { name: 'Create' }));
227218

228219
await waitFor(() => {
@@ -242,14 +233,6 @@ describe('New project dialog - Task chain', () => {
242233
}),
243234
expect.objectContaining({
244235
domain: 'Classification',
245-
labels: [
246-
expect.objectContaining({
247-
name: 'test',
248-
}),
249-
expect.objectContaining({
250-
name: 'child1',
251-
}),
252-
],
253236
relation: 'Mixed',
254237
}),
255238
],

web_ui/src/pages/project-details/components/project-labels/project-labels-management/project-labels-management.component.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
LabelTreeItem,
1010
LabelTreeLabelProps,
1111
Readonly,
12+
ReorderType,
1213
SetValidationProps,
1314
ValidationErrorType,
1415
} from '../../../../../core/labels/label-tree-view.interface';
@@ -19,6 +20,7 @@ import { LabelTreeView } from '../../../../../shared/components/label-tree-view/
1920
import {
2021
getLabelsWithAddedChild,
2122
getLabelWithoutDeleted,
23+
getReorderedTree,
2224
getTreeWithUpdatedItem,
2325
} from '../../../../../shared/components/label-tree-view/utils';
2426
import { LABEL_TREE_TYPE } from '../../../../create-project/components/project-labels-management/label-tree-type.enum';
@@ -60,10 +62,10 @@ export const ProjectLabelsManagement = ({
6062
};
6163

6264
const addItemHandler = (item?: LabelTreeItem) => {
63-
item && setTaskLabelsHandler([...labelsTree, { ...item, state: LabelItemEditionState.NEW }]);
65+
item && setTaskLabelsHandler([{ ...item, state: LabelItemEditionState.NEW }, ...labelsTree]);
6466
};
6567

66-
const addChild = (parentId: string, groupName: string, type: LabelItemType) => {
68+
const addChild = (parentId: string | null, groupName: string, type: LabelItemType) => {
6769
setTaskLabelsHandler(getLabelsWithAddedChild(labelsTree, labelsTree, parentId, groupName, type));
6870
};
6971

@@ -73,6 +75,12 @@ export const ProjectLabelsManagement = ({
7375
setTaskLabelsHandler(updated);
7476
};
7577

78+
const reorder = (item: LabelTreeItem, mode: ReorderType) => {
79+
//TODO: there is also reordering in annotations! - maybe worth to share some code!
80+
const updated = getReorderedTree(labelsTree, item, mode);
81+
setTaskLabelsHandler(updated);
82+
};
83+
7684
const setTaskLabelsHandler = (updatedLabels: LabelTreeItem[]): void => {
7785
setLabelsTree(updatedLabels);
7886
setIsDirty(true);
@@ -133,11 +141,14 @@ export const ProjectLabelsManagement = ({
133141
>
134142
<LabelTreeView
135143
labelsTree={labelsTree}
136-
save={saveHandler}
137-
deleteItem={deleteItem}
144+
actions={{
145+
save: saveHandler,
146+
deleteItem,
147+
addChild,
148+
reorder,
149+
}}
138150
isInEditMode={isInEdition}
139151
isHierarchicalMode={isHierarchicalMode}
140-
addChild={addChild}
141152
domains={domains}
142153
setValidationError={setValidationError}
143154
type={Readonly.NO}

web_ui/src/shared/components/label-tree-view/flat-tree-view.component.tsx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,31 @@ import { FlatLabelTreeViewProps, LabelItemType, LabelTreeItem } from '../../../c
55
import { LabelTreeViewItem } from './label-tree-view-item/label-tree-view-item.component';
66

77
export const FlatTreeView = ({
8-
labels,
8+
treeItems,
99
isEditable,
10-
save,
11-
addChild,
12-
deleteItem,
10+
actions,
1311
projectLabels,
1412
width,
1513
isCreationInNewProject = false,
1614
domains,
1715
treeValidationErrors,
1816
setValidationError,
1917
}: FlatLabelTreeViewProps): JSX.Element => {
20-
const onlyLabels = labels.length > 0 ? (labels[0].type === LabelItemType.GROUP ? labels[0].children : labels) : [];
21-
22-
const reversedLabelList = [...onlyLabels].reverse();
18+
const labels =
19+
treeItems.length > 0 ? (treeItems[0].type === LabelItemType.GROUP ? treeItems[0].children : treeItems) : [];
2320

2421
return (
2522
<ul
2623
className={`spectrum-TreeView ${isEditable ? 'spectrum-TreeView--thumbnail' : ''}`}
2724
style={{ margin: 0, maxWidth: width || '100%' }}
2825
>
29-
{reversedLabelList.map((label: LabelTreeItem) => {
26+
{labels.map((label: LabelTreeItem) => {
3027
return (
3128
<LabelTreeViewItem
3229
key={label.id}
3330
item={label}
34-
save={save}
35-
addChild={addChild}
36-
deleteItem={deleteItem}
31+
siblings={labels}
32+
actions={actions}
3733
projectLabels={projectLabels}
3834
isCreationInNewProject={isCreationInNewProject}
3935
domains={domains}

web_ui/src/shared/components/label-tree-view/flat-tree-view.test.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@ import { providersRender as render } from '../../../test-utils/required-provider
99
import { FlatTreeView } from './flat-tree-view.component';
1010

1111
describe('FlatTreeView', () => {
12-
it('Check if new labels are shown on the top - reversed order', async () => {
12+
const actions = {
13+
save: jest.fn(),
14+
addChild: jest.fn(),
15+
deleteItem: jest.fn(),
16+
reorder: jest.fn(),
17+
};
18+
19+
it('Check if new labels are shown on the top', async () => {
1320
const labels = [
1421
getMockedTreeLabel({ name: 'Label 1' }),
1522
getMockedTreeLabel({ name: 'Label 2' }),
@@ -18,10 +25,8 @@ describe('FlatTreeView', () => {
1825

1926
render(
2027
<FlatTreeView
21-
labels={labels}
22-
save={jest.fn()}
23-
addChild={jest.fn()}
24-
deleteItem={jest.fn()}
28+
treeItems={labels}
29+
actions={actions}
2530
isEditable={false}
2631
domains={[DOMAIN.CLASSIFICATION]}
2732
projectLabels={labels}
@@ -32,8 +37,8 @@ describe('FlatTreeView', () => {
3237

3338
const treeItems = screen.getAllByRole('listitem');
3439

35-
expect(treeItems[0]).toHaveTextContent('Label 3');
40+
expect(treeItems[0]).toHaveTextContent('Label 1');
3641
expect(treeItems[1]).toHaveTextContent('Label 2');
37-
expect(treeItems[2]).toHaveTextContent('Label 1');
42+
expect(treeItems[2]).toHaveTextContent('Label 3');
3843
});
3944
});

web_ui/src/shared/components/label-tree-view/hierarchical-tree-view.component.tsx

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,16 @@ import { LabelTreeViewItem, LabelTreeViewItemProps } from './label-tree-view-ite
99
import classes from './hierarchical-tree-view.module.scss';
1010

1111
export const HierarchicalTreeView = ({
12-
labels,
12+
treeItems,
1313
isEditable,
14-
save,
15-
addChild,
16-
deleteItem,
14+
actions,
1715
projectLabels,
1816
level = 0,
1917
options,
2018
domains,
2119
treeValidationErrors,
2220
setValidationError,
2321
}: HierarchicalLabelTreeViewProps): JSX.Element => {
24-
const reversedLabelList = [...labels].reverse();
25-
2622
return (
2723
<ul
2824
className={`spectrum-TreeView ${isEditable ? 'spectrum-TreeView--thumbnail' : ''} ${classes.item}`}
@@ -31,13 +27,12 @@ export const HierarchicalTreeView = ({
3127
paddingLeft: level && 'var(--spectrum-global-dimension-size-500)',
3228
}}
3329
>
34-
{reversedLabelList.map((item: LabelTreeItem) => {
30+
{treeItems.map((item: LabelTreeItem) => {
3531
const labelTreeViewItemProps: LabelTreeViewItemProps = {
36-
save,
37-
addChild,
38-
deleteItem,
32+
actions,
3933
setValidationError,
4034
item,
35+
siblings: treeItems,
4136
domains,
4237
isEditable,
4338
projectLabels,
@@ -53,12 +48,10 @@ export const HierarchicalTreeView = ({
5348
return (
5449
<LabelTreeViewItem key={item.id} {...labelTreeViewItemProps}>
5550
<HierarchicalTreeView
56-
labels={item.children}
51+
treeItems={item.children}
5752
isEditable={isEditable}
5853
options={options}
59-
save={save}
60-
addChild={addChild}
61-
deleteItem={deleteItem}
54+
actions={actions}
6255
projectLabels={projectLabels}
6356
level={++level}
6457
domains={domains}

0 commit comments

Comments
 (0)