Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
27 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
274746b
Merge branch 'develop' into 'fb-fit-750/updated-ui'
AndrejOros Oct 16, 2025
1e951dc
AgreementSelected columns should behave as a number
yyassi-heartex Oct 16, 2025
8525c17
Merge remote-tracking branch 'origin/develop' into fb-fit-750/updated-ui
yyassi-heartex Oct 16, 2025
1fd633d
Merge branch 'develop' into 'fb-fit-750/updated-ui'
AndrejOros Oct 17, 2025
081d082
Merge remote-tracking branch 'origin/develop' into fb-fit-750/updated-ui
yyassi-heartex Oct 17, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from django.db import migrations
from copy import deepcopy
from django.apps import apps as django_apps
from core.models import AsyncMigrationStatus
from core.redis import start_job_async_or_sync
import logging

migration_name = '0017_update_agreement_selected_to_nested_structure'

logger = logging.getLogger(__name__)


def forward_migration():
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')
Annotation = django_apps.get_model('tasks', 'Annotation')

# Cache unique annotators per project_id to avoid repetitive queries
project_to_unique_annotators = {}

# Iterate using values() to avoid loading full model instances
# Fetch only the fields we need
qs = View.objects.all().values('id', 'project_id', 'data')

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

agreement = data.get('agreement_selected')
if not isinstance(agreement, dict):
continue

# Handle both old flat structure and new nested structure
existing_annotators = agreement.get('annotators', None)
if existing_annotators is None:
continue

# Check if using old flat structure (list) or new nested structure (dict)
if isinstance(existing_annotators, dict):
# New structure: annotators = { all: bool, ids: [] }
existing_annotator_ids = existing_annotators.get('ids', [])
else:
# Old structure: annotators = []
existing_annotator_ids = existing_annotators

# Compute unique annotators for this project (once per project)
if project_id not in project_to_unique_annotators:
unique_ids = set(
Annotation.objects
.filter(project_id=project_id, completed_by_id__isnull=False)
.values_list('completed_by_id', flat=True)
.distinct()
)
# Normalize to unique ints
project_to_unique_annotators[project_id] = unique_ids

new_annotators = project_to_unique_annotators[project_id]

# If no change, skip update
old_set = {int(a) for a in (existing_annotator_ids or [])}
if new_annotators == old_set:
continue

new_data = deepcopy(data)
# Always use the new nested structure
new_data['agreement_selected']['annotators'] = {
'all': isinstance(existing_annotators, dict) and existing_annotators.get('all', False),
'ids': list(new_annotators)
}

# 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 annotators to {list(new_annotators)}')
logger.info(f'Old annotator length: {len(old_set)}, new annotator length: {len(new_annotators)}')
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='low')


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,12 +34,12 @@ 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;
Expand Down
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
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
1 change: 0 additions & 1 deletion web/libs/editor/src/tags/Custom.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import React from "react";
import { destroy, types, getRoot } from "mobx-state-tree";
import { observer } from "mobx-react";
import { EnterpriseBadge } from "@humansignal/ui";
import Registry from "../core/Registry";
import ControlBase from "./control/Base";
import ClassificationBase from "./control/ClassificationBase";

Expand Down
Loading