Skip to content

Commit 0549e43

Browse files
yyassi-heartexhakan458AndrejOros
authored andcommitted
feat: FIT-750: Update Agreement Selected UI (#8575)
Co-authored-by: hakan458 <[email protected]> Co-authored-by: AndrejOros <[email protected]>
1 parent 8010b81 commit 0549e43

File tree

9 files changed

+167
-22
lines changed

9 files changed

+167
-22
lines changed

label_studio/core/settings/base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,3 +898,7 @@ def collect_versions_dummy(**kwargs):
898898

899899
# Base FSM (Finite State Machine) Configuration for Label Studio
900900
FSM_CACHE_TTL = 300 # Cache TTL in seconds (5 minutes)
901+
902+
# Used for async migrations. In LSE this is set to a real queue name, including here so we
903+
# can use settings.SERVICE_QUEUE_NAME in async migrations in LSO
904+
SERVICE_QUEUE_NAME = ''
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from django.db import migrations, connection
2+
from copy import deepcopy
3+
from django.apps import apps as django_apps
4+
from django.conf import settings
5+
from core.models import AsyncMigrationStatus
6+
from core.redis import start_job_async_or_sync
7+
from core.utils.iterators import iterate_queryset
8+
import logging
9+
10+
migration_name = '0017_update_agreement_selected_to_nested_structure'
11+
12+
logger = logging.getLogger(__name__)
13+
14+
15+
def forward_migration():
16+
"""
17+
Migrates views that have agreement_selected populated to the new structure
18+
19+
Old structure:
20+
'agreement_selected': {
21+
'annotators': List[int]
22+
'models': List[str]
23+
'ground_truth': bool
24+
}
25+
26+
New structure:
27+
'agreement_selected': {
28+
'annotators': {
29+
'all': bool
30+
'ids': List[int]
31+
},
32+
'models': {
33+
'all': bool
34+
'ids': List[str]
35+
},
36+
'ground_truth': bool
37+
}
38+
"""
39+
migration, created = AsyncMigrationStatus.objects.get_or_create(
40+
name=migration_name,
41+
defaults={'status': AsyncMigrationStatus.STATUS_STARTED}
42+
)
43+
if not created:
44+
return # already in progress or done
45+
46+
# Look up models at runtime inside the worker process
47+
View = django_apps.get_model('data_manager', 'View')
48+
49+
# Iterate using values() to avoid loading full model instances
50+
# Fetch only the fields we need, filtering to views that have 'agreement_selected' in data
51+
qs = (
52+
View.objects
53+
.filter(data__has_key='agreement_selected')
54+
.filter(data__agreement_selected__isnull=False)
55+
.values('id', 'data')
56+
)
57+
58+
updated = 0
59+
for row in qs:
60+
view_id = row['id']
61+
data = row.get('data') or {}
62+
63+
new_data = deepcopy(data)
64+
# Always use the new nested structure
65+
new_data['agreement_selected'] = {
66+
'annotators': {'all': True, 'ids': []},
67+
'models': {'all': True, 'ids': []},
68+
'ground_truth': False
69+
}
70+
71+
# Update only the JSON field via update(); do not load model instance or call save()
72+
View.objects.filter(id=view_id).update(data=new_data)
73+
logger.info(f'Updated View {view_id} agreement selected to default all annotators + all models')
74+
updated += 1
75+
76+
if updated:
77+
logger.info(f'{migration_name} Updated {updated} View rows')
78+
79+
migration.status = AsyncMigrationStatus.STATUS_FINISHED
80+
migration.save(update_fields=['status'])
81+
82+
def forwards(apps, schema_editor):
83+
start_job_async_or_sync(forward_migration, queue_name=settings.SERVICE_QUEUE_NAME)
84+
85+
86+
def backwards(apps, schema_editor):
87+
# Irreversible: we cannot reconstruct the previous annotator lists safely
88+
pass
89+
90+
91+
class Migration(migrations.Migration):
92+
atomic = False
93+
94+
dependencies = [
95+
('data_manager', '0016_migrate_agreement_selected_annotators_to_unique')
96+
]
97+
98+
operations = [
99+
migrations.RunPython(forwards, backwards),
100+
]
101+
102+
103+

web/libs/datamanager/src/components/CellViews/AgreementSelected.jsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,17 @@ export const AgreementSelected = (cell) => {
3434

3535
AgreementSelected.userSelectable = false;
3636

37-
AgreementSelected.HeaderCell = ({ agreementFilters, onSave }) => {
37+
AgreementSelected.HeaderCell = ({ agreementFilters, onSave, onClose }) => {
3838
const sdk = useSDK();
3939
const [content, setContent] = useState(null);
4040

4141
useEffect(() => {
42-
sdk.invoke("AgreementSelectedHeaderClick", { agreementFilters, onSave }, (jsx) => setContent(jsx));
42+
sdk.invoke("AgreementSelectedHeaderClick", { agreementFilters, onSave, onClose }, (jsx) => setContent(jsx));
4343
}, []);
4444

4545
return content;
4646
};
47+
48+
AgreementSelected.style = {
49+
minWidth: 210,
50+
};

web/libs/datamanager/src/components/Common/Table/TableHead/TableHead.jsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,30 @@ const AgreementSelectedWrapper = observer(({ column, children }) => {
7373
const selectedView = root.viewsStore.selected;
7474
const agreementFilters = selectedView.agreement_selected;
7575
const [isOpen, setIsOpen] = useState(false);
76+
const ref = useRef(null);
77+
const closeHandler = () => {
78+
ref.current?.close();
79+
setIsOpen(false);
80+
};
7681
const onSave = (agreementFilters) => {
7782
selectedView.setAgreementFilters(agreementFilters);
83+
closeHandler();
7884
return selectedView.save();
7985
};
8086
const onToggle = (isOpen) => {
8187
setIsOpen(isOpen);
8288
};
8389
return (
8490
<Dropdown.Trigger
91+
ref={ref}
8592
content={
8693
isOpen ? (
87-
<AgreementSelected.HeaderCell agreementFilters={agreementFilters} onSave={onSave} align="left" />
94+
<AgreementSelected.HeaderCell
95+
agreementFilters={agreementFilters}
96+
onSave={onSave}
97+
align="left"
98+
onClose={closeHandler}
99+
/>
88100
) : (
89101
<></>
90102
)

web/libs/datamanager/src/components/Filters/types/Number.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ const valueFilter = (value) => {
88
return value;
99
}
1010
if (typeof value === "string") {
11-
return value.replace(/([^\d.,]+)/, "");
11+
const cleaned = value.replace(/([^\d.,]+)/, "");
12+
return cleaned ? Number(cleaned) : null;
1213
}
1314
return value || null;
1415
}
@@ -25,7 +26,6 @@ const RangeInput = observer(({ schema, value, onChange }) => {
2526
const max = value?.max ?? null;
2627

2728
const onValueChange = (newValue) => {
28-
console.log({ newValue });
2929
onChange(newValue);
3030
};
3131

web/libs/datamanager/src/stores/AppStore.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,8 @@ export const AppStore = types
711711

712712
invokeAction: flow(function* (actionId, options = {}) {
713713
const view = self.currentView ?? {};
714+
const viewReloaded = view;
715+
let projectFetched = self.project;
714716

715717
const needsLock = self.availableActions.findIndex((a) => a.id === actionId) >= 0;
716718

@@ -749,7 +751,13 @@ export const AppStore = types
749751
}
750752

751753
if (actionCallback instanceof Function) {
752-
return actionCallback(actionParams, view);
754+
const result = yield actionCallback(actionParams, view);
755+
self.SDK.invoke("actionDialogOkComplete", actionId, {
756+
result,
757+
view: viewReloaded,
758+
project: projectFetched,
759+
});
760+
return result;
753761
}
754762

755763
const requestParams = {
@@ -774,17 +782,28 @@ export const AppStore = types
774782

775783
if (result.reload) {
776784
self.SDK.reload();
785+
self.SDK.invoke("actionDialogOkComplete", actionId, {
786+
result,
787+
view: viewReloaded,
788+
project: projectFetched,
789+
});
777790
return;
778791
}
779792

780793
if (options.reload !== false) {
781794
yield view.reload();
782-
self.fetchProject();
795+
yield self.fetchProject();
796+
projectFetched = self.project;
783797
view.clearSelection();
784798
}
785799

786800
view?.unlock?.();
787801

802+
self.SDK.invoke("actionDialogOkComplete", actionId, {
803+
result,
804+
view: viewReloaded,
805+
project: projectFetched,
806+
});
788807
return result;
789808
}),
790809

web/libs/datamanager/src/stores/Tabs/tab.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -422,11 +422,21 @@ export const Tab = types
422422
self.save();
423423
},
424424

425-
setAgreementFilters({ ground_truth = false, annotators = [], models = [] }) {
425+
setAgreementFilters({
426+
ground_truth = false,
427+
annotators = { all: true, ids: [] },
428+
models = { all: true, ids: [] },
429+
}) {
426430
self.agreement_selected = {
427431
ground_truth,
428-
annotators,
429-
models,
432+
annotators: {
433+
all: annotators.all,
434+
ids: annotators.ids,
435+
},
436+
models: {
437+
all: models.all,
438+
ids: models.ids,
439+
},
430440
};
431441
},
432442

web/libs/ui/src/lib/toast/toast.module.scss

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@
8989
.toast {
9090
--text-color: var(--color-neutral-inverted-content);
9191
--background-color: var(--color-neutral-inverted-surface);
92-
--border-color: var(--color-neutral-inverted-border);
92+
--border-color: var(--color-surface-border);
9393
--hover-color: var(--color-neutral-inverted-surface-hover);
94-
--padding: var(--toast-spacing) calc(var(--toast-spacing) * 2);
94+
--padding: var(--toast-spacing);
9595

9696
display: flex;
9797
align-items: center;
@@ -160,7 +160,7 @@
160160
// Always maintain a dark background for info toasts
161161
--text-color: var(--color-sand-100);
162162
--background-color: var(--color-sand-900);
163-
--border-color: var(--color-neutral-border);
163+
--border-color: var(--color-sand-700);
164164
--hover-color: var(--color-sand-800);
165165
}
166166

web/libs/ui/src/lib/toast/toast.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import * as ToastPrimitive from "@radix-ui/react-toast";
33
import styles from "./toast.module.scss";
44
import clsx from "clsx";
55
import { IconCross } from "../../assets/icons";
6-
import { Button } from "../button/button";
76
import { cn } from "@humansignal/shad/utils";
87

98
export type ToastViewportProps = ToastPrimitive.ToastViewportProps & any;
@@ -87,15 +86,9 @@ export interface ToastActionProps extends ToastPrimitive.ToastActionProps {
8786
}
8887
export const ToastAction: FC<ToastActionProps> = ({ children, onClose, altText, ...props }) => (
8988
<ToastPrimitive.Action altText={altText} asChild className="pointer-events-none">
90-
<Button
91-
look="string"
92-
size="small"
93-
className={cn(styles.toast__action, "pointer-events-all")}
94-
onClick={onClose}
95-
{...props}
96-
>
89+
<button className={cn(styles.toast__action, "pointer-events-all")} onClick={onClose} {...props}>
9790
{children}
98-
</Button>
91+
</button>
9992
</ToastPrimitive.Action>
10093
);
10194
export type ToastShowArgs = {

0 commit comments

Comments
 (0)