Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6fcc853
feat: FIT-750: Update Agreement Selected UI
yyassi-heartex Oct 2, 2025
28a5f80
linting
yyassi-heartex Oct 2, 2025
5e3bb31
updating to the new agreement_selected structure
yyassi-heartex Oct 2, 2025
eec9be0
Merge remote-tracking branch 'origin/develop' into fb-fit-750/updated-ui
yyassi-heartex Oct 3, 2025
8a316d8
linting
yyassi-heartex Oct 7, 2025
3569ff8
Merge remote-tracking branch 'origin/develop' into fb-fit-750/updated-ui
yyassi-heartex Oct 7, 2025
6a2e408
resolving an issue with reopening the popover after closing requiring…
yyassi-heartex Oct 7, 2025
f574210
updating default value
yyassi-heartex Oct 7, 2025
7853ff6
we now have a min width on agreement selected
yyassi-heartex Oct 7, 2025
55b6d2f
lint
yyassi-heartex Oct 7, 2025
9d4c561
update migration
Oct 8, 2025
de88f2d
use service queue for migration
Oct 8, 2025
9388ffe
Merge remote-tracking branch 'origin/develop' into fb-fit-750/updated-ui
yyassi-heartex Oct 8, 2025
c524adf
we now fire an event with the invoke ok action is completed
yyassi-heartex Oct 9, 2025
f2334a2
Merge remote-tracking branch 'origin/develop' into fb-fit-750/updated-ui
yyassi-heartex Oct 9, 2025
1b80f78
Sync Follow Merge dependencies
AndrejOros Oct 13, 2025
b9d761c
Merge branch 'develop' into 'fb-fit-750/updated-ui'
AndrejOros Oct 13, 2025
f88f790
Merge branch 'develop' into fb-fit-750/updated-ui
yyassi-heartex Oct 14, 2025
15e64e3
fixed tooltip button visuals
yyassi-heartex Oct 14, 2025
628a939
Merge remote-tracking branch 'origin/develop' into fb-fit-750/updated-ui
yyassi-heartex Oct 15, 2025
5f5868b
Merge remote-tracking branch 'origin/develop' into fb-fit-750/updated-ui
yyassi-heartex Oct 15, 2025
96a2d3c
lint
yyassi-heartex Oct 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions label_studio/core/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,3 +899,7 @@ def collect_versions_dummy(**kwargs):

# Base FSM (Finite State Machine) Configuration for Label Studio
FSM_CACHE_TTL = 300 # Cache TTL in seconds (5 minutes)

# Used for async migrations. In LSE this is set to a real queue name, including here so we
# can use settings.SERVICE_QUEUE_NAME in async migrations in LSO
SERVICE_QUEUE_NAME = ''
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from django.db import migrations, connection
from copy import deepcopy
from django.apps import apps as django_apps
from django.conf import settings
from core.models import AsyncMigrationStatus
from core.redis import start_job_async_or_sync
from core.utils.iterators import iterate_queryset
import logging

migration_name = '0017_update_agreement_selected_to_nested_structure'

logger = logging.getLogger(__name__)


def forward_migration():
"""
Migrates views that have agreement_selected populated to the new structure

Old structure:
'agreement_selected': {
'annotators': List[int]
'models': List[str]
'ground_truth': bool
}

New structure:
'agreement_selected': {
'annotators': {
'all': bool
'ids': List[int]
},
'models': {
'all': bool
'ids': List[str]
},
'ground_truth': bool
}
"""
migration, created = AsyncMigrationStatus.objects.get_or_create(
name=migration_name,
defaults={'status': AsyncMigrationStatus.STATUS_STARTED}
)
if not created:
return # already in progress or done

# Look up models at runtime inside the worker process
View = django_apps.get_model('data_manager', 'View')

# Iterate using values() to avoid loading full model instances
# Fetch only the fields we need, filtering to views that have 'agreement_selected' in data
qs = (
View.objects
.filter(data__has_key='agreement_selected')
.filter(data__agreement_selected__isnull=False)
.values('id', 'data')
)

updated = 0
for row in qs:
view_id = row['id']
data = row.get('data') or {}

new_data = deepcopy(data)
# Always use the new nested structure
new_data['agreement_selected'] = {
'annotators': {'all': True, 'ids': []},
'models': {'all': True, 'ids': []},
'ground_truth': False
}

# Update only the JSON field via update(); do not load model instance or call save()
View.objects.filter(id=view_id).update(data=new_data)
logger.info(f'Updated View {view_id} agreement selected to default all annotators + all models')
updated += 1

if updated:
logger.info(f'{migration_name} Updated {updated} View rows')

migration.status = AsyncMigrationStatus.STATUS_FINISHED
migration.save(update_fields=['status'])

def forwards(apps, schema_editor):
start_job_async_or_sync(forward_migration, queue_name=settings.SERVICE_QUEUE_NAME)


def backwards(apps, schema_editor):
# Irreversible: we cannot reconstruct the previous annotator lists safely
pass


class Migration(migrations.Migration):
atomic = False

dependencies = [
('data_manager', '0016_migrate_agreement_selected_annotators_to_unique')
]

operations = [
migrations.RunPython(forwards, backwards),
]



Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@ export const AgreementSelected = (cell) => {

AgreementSelected.userSelectable = false;

AgreementSelected.HeaderCell = ({ agreementFilters, onSave }) => {
AgreementSelected.HeaderCell = ({ agreementFilters, onSave, onClose }) => {
const sdk = useSDK();
const [content, setContent] = useState(null);

useEffect(() => {
sdk.invoke("AgreementSelectedHeaderClick", { agreementFilters, onSave }, (jsx) => setContent(jsx));
sdk.invoke("AgreementSelectedHeaderClick", { agreementFilters, onSave, onClose }, (jsx) => setContent(jsx));
}, []);

return content;
};

AgreementSelected.style = {
minWidth: 210,
};
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,30 @@ const AgreementSelectedWrapper = observer(({ column, children }) => {
const selectedView = root.viewsStore.selected;
const agreementFilters = selectedView.agreement_selected;
const [isOpen, setIsOpen] = useState(false);
const ref = useRef(null);
const closeHandler = () => {
ref.current?.close();
setIsOpen(false);
};
const onSave = (agreementFilters) => {
selectedView.setAgreementFilters(agreementFilters);
closeHandler();
return selectedView.save();
};
const onToggle = (isOpen) => {
setIsOpen(isOpen);
};
return (
<Dropdown.Trigger
ref={ref}
content={
isOpen ? (
<AgreementSelected.HeaderCell agreementFilters={agreementFilters} onSave={onSave} align="left" />
<AgreementSelected.HeaderCell
agreementFilters={agreementFilters}
onSave={onSave}
align="left"
onClose={closeHandler}
/>
) : (
<></>
)
Expand Down
23 changes: 21 additions & 2 deletions web/libs/datamanager/src/stores/AppStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,8 @@ export const AppStore = types

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

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

Expand Down Expand Up @@ -749,7 +751,13 @@ export const AppStore = types
}

if (actionCallback instanceof Function) {
return actionCallback(actionParams, view);
const result = yield actionCallback(actionParams, view);
self.SDK.invoke("actionDialogOkComplete", actionId, {
result,
view: viewReloaded,
project: projectFetched,
});
return result;
}

const requestParams = {
Expand All @@ -774,17 +782,28 @@ export const AppStore = types

if (result.reload) {
self.SDK.reload();
self.SDK.invoke("actionDialogOkComplete", actionId, {
result,
view: viewReloaded,
project: projectFetched,
});
return;
}

if (options.reload !== false) {
yield view.reload();
self.fetchProject();
yield self.fetchProject();
projectFetched = self.project;
view.clearSelection();
}

view?.unlock?.();

self.SDK.invoke("actionDialogOkComplete", actionId, {
result,
view: viewReloaded,
project: projectFetched,
});
return result;
}),

Expand Down
16 changes: 13 additions & 3 deletions web/libs/datamanager/src/stores/Tabs/tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -422,11 +422,21 @@ export const Tab = types
self.save();
},

setAgreementFilters({ ground_truth = false, annotators = [], models = [] }) {
setAgreementFilters({
ground_truth = false,
annotators = { all: true, ids: [] },
models = { all: true, ids: [] },
}) {
self.agreement_selected = {
ground_truth,
annotators,
models,
annotators: {
all: annotators.all,
ids: annotators.ids,
},
models: {
all: models.all,
ids: models.ids,
},
};
},

Expand Down
6 changes: 3 additions & 3 deletions web/libs/ui/src/lib/toast/toast.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@
.toast {
--text-color: var(--color-neutral-inverted-content);
--background-color: var(--color-neutral-inverted-surface);
--border-color: var(--color-neutral-inverted-border);
--border-color: var(--color-surface-border);
--hover-color: var(--color-neutral-inverted-surface-hover);
--padding: var(--toast-spacing) calc(var(--toast-spacing) * 2);
--padding: var(--toast-spacing);

display: flex;
align-items: center;
Expand Down Expand Up @@ -160,7 +160,7 @@
// Always maintain a dark background for info toasts
--text-color: var(--color-sand-100);
--background-color: var(--color-sand-900);
--border-color: var(--color-neutral-border);
--border-color: var(--color-sand-700);
--hover-color: var(--color-sand-800);
}

Expand Down
11 changes: 2 additions & 9 deletions web/libs/ui/src/lib/toast/toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import * as ToastPrimitive from "@radix-ui/react-toast";
import styles from "./toast.module.scss";
import clsx from "clsx";
import { IconCross } from "../../assets/icons";
import { Button } from "../button/button";
import { cn } from "@humansignal/shad/utils";

export type ToastViewportProps = ToastPrimitive.ToastViewportProps & any;
Expand Down Expand Up @@ -87,15 +86,9 @@ export interface ToastActionProps extends ToastPrimitive.ToastActionProps {
}
export const ToastAction: FC<ToastActionProps> = ({ children, onClose, altText, ...props }) => (
<ToastPrimitive.Action altText={altText} asChild className="pointer-events-none">
<Button
look="string"
size="small"
className={cn(styles.toast__action, "pointer-events-all")}
onClick={onClose}
{...props}
>
<button className={cn(styles.toast__action, "pointer-events-all")} onClick={onClose} {...props}>
{children}
</Button>
</button>
</ToastPrimitive.Action>
);
export type ToastShowArgs = {
Expand Down
Loading