Skip to content

Commit d224c82

Browse files
committed
Merge branch 'develop' into 'fb-leap-2009/granularity'
Workflow run: https://github.com/HumanSignal/label-studio/actions/runs/14635186966
2 parents 39d3406 + 36eaffd commit d224c82

File tree

19 files changed

+92
-24
lines changed

19 files changed

+92
-24
lines changed

docs/source/guide/start.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ The following command line arguments are optional and must be specified with `la
5555
| `--initial-project-description` | `LABEL_STUDIO_PROJECT_DESC` | `''` | Specify a project description for a Label Studio project. See [Set up your labeling project](setup.html). |
5656
| `--password` | `LABEL_STUDIO_PASSWORD` | `None` | Password to use for the default user. See [Set up user accounts](signup.html). |
5757
| `--username` | `LABEL_STUDIO_USERNAME` | `default_user@localhost` | Username to use for the default user. See [Set up user accounts](signup.html). |
58-
| `--user-token` | `LABEL_STUDIO_USER_TOKEN` | Automatically generated. | Authentication token for a user to use for the API. Must be set with a username, otherwise automatically generated. See [Set up user accounts](signup.html).
58+
| `--user-token` | `LABEL_STUDIO_USER_TOKEN` | Automatically generated. | Authentication token for a user to use for the API. Must be set with a username, otherwise automatically generated. See [Set up user accounts](signup.html). |
5959
| `--agree-fix-sqlite` | N/A | `False` | Automatically agree to let Label Studio fix SQLite issues when using Python 3.6–3.8 on Windows operating systems. |
60+
| `--enable-legacy-api-token` | `LABEL_STUDIO_ENABLE_LEGACY_API_TOKEN` | `False` | Enable legacy API token authentication. Useful for running with a pre-existing token via `--user-token`. |
6061
| N/A | `LABEL_STUDIO_LOCAL_FILES_SERVING_ENABLED` | `False` | Allow Label Studio to access local file directories to import storage. See [Run Label Studio on Docker and use local storage](start.html#Run_Label_Studio_on_Docker_and_use_local_storage). |
6162
| N/A | `LABEL_STUDIO_LOCAL_FILES_DOCUMENT_ROOT` | `/` | Specify the root directory for Label Studio to use when accessing local file directories. See [Run Label Studio on Docker and use local storage](start.html#Run_Label_Studio_on_Docker_and_use_local_storage). |
6263

label_studio/core/argparser.py

+6
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ def project_name(raw_name):
100100
action='store_true',
101101
help='Agree to fix SQLite issues on python 3.6-3.8 on Windows automatically',
102102
)
103+
root_parser.add_argument(
104+
'--enable-legacy-api-token',
105+
dest='enable_legacy_api_token',
106+
action='store_true',
107+
help='Enable legacy API token authentication',
108+
)
103109

104110
parser = argparse.ArgumentParser(description='Label studio', parents=[root_parser])
105111

label_studio/core/settings/base.py

+2
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,8 @@ def collect_versions_dummy(**kwargs):
821821

822822
LOGOUT_REDIRECT_URL = get_env('LOGOUT_REDIRECT_URL', None)
823823

824+
# Enable legacy tokens (useful for running with a pre-existing token via `LABEL_STUDIO_USER_TOKEN`)
825+
LABEL_STUDIO_ENABLE_LEGACY_API_TOKEN = get_bool_env('LABEL_STUDIO_ENABLE_LEGACY_API_TOKEN', False)
824826
RESOLVER_PROXY_BUFFER_SIZE = int(get_env('RESOLVER_PROXY_BUFFER_SIZE', 512 * 1024))
825827
RESOLVER_PROXY_TIMEOUT = int(get_env('RESOLVER_PROXY_TIMEOUT', 20))
826828
RESOLVER_PROXY_MAX_RANGE_SIZE = int(get_env('RESOLVER_PROXY_MAX_RANGE_SIZE', 8 * 1024 * 1024))

label_studio/data_export/mixins.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,13 @@ def _get_filtered_annotations_queryset(self, annotation_filter_options=None):
110110
return queryset
111111

112112
q = reduce(lambda x, y: x | y, q_list)
113-
return queryset.filter(q)
113+
annotations_qs = queryset.filter(q)
114+
115+
# prefetch reviews in LSE
116+
if hasattr(annotations_qs.model, 'reviews'):
117+
annotations_qs = annotations_qs.prefetch_related('reviews')
118+
119+
return annotations_qs
114120

115121
@staticmethod
116122
def _get_export_serializer_option(serialization_options):
@@ -144,6 +150,7 @@ def _get_export_serializer_option(serialization_options):
144150

145151
def get_task_queryset(self, ids, annotation_filter_options):
146152
annotations_qs = self._get_filtered_annotations_queryset(annotation_filter_options=annotation_filter_options)
153+
147154
return (
148155
Task.objects.filter(id__in=ids)
149156
.prefetch_related(
@@ -152,7 +159,7 @@ def get_task_queryset(self, ids, annotation_filter_options):
152159
queryset=annotations_qs,
153160
)
154161
)
155-
.prefetch_related('predictions', 'drafts')
162+
.prefetch_related('predictions', 'drafts', 'comment_authors', 'file_upload')
156163
)
157164

158165
def get_export_data(self, task_filter_options=None, annotation_filter_options=None, serialization_options=None):
@@ -272,10 +279,10 @@ def export_to_file(self, task_filter_options=None, annotation_filter_options=Non
272279
self.status = self.Status.COMPLETED
273280
self.save(update_fields=['status'])
274281

275-
except Exception:
282+
except Exception as e:
276283
self.status = self.Status.FAILED
277284
self.save(update_fields=['status'])
278-
logger.exception('Export was failed')
285+
logger.exception('Export was failed: %s', e)
279286
finally:
280287
self.finished_at = datetime.now()
281288
self.save(update_fields=['finished_at'])

label_studio/organizations/functions.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from core.utils.common import temporary_disconnect_all_signals
2+
from django.conf import settings
23
from django.db import transaction
34
from organizations.models import Organization, OrganizationMember
45
from projects.models import Project
56

67

7-
def create_organization(title, created_by, **kwargs):
8+
def create_organization(title, created_by, legacy_api_tokens_enabled=False, **kwargs):
89
from core.feature_flags import flag_set
910

1011
JWT_ACCESS_TOKEN_ENABLED = flag_set('fflag__feature_develop__prompts__dia_1829_jwt_token_auth')
@@ -13,9 +14,11 @@ def create_organization(title, created_by, **kwargs):
1314
org = Organization.objects.create(title=title, created_by=created_by, **kwargs)
1415
OrganizationMember.objects.create(user=created_by, organization=org)
1516
if JWT_ACCESS_TOKEN_ENABLED:
16-
# set auth tokens to new system for new users
17+
# set auth tokens to new system for new users, unless specified otherwise
1718
org.jwt.api_tokens_enabled = True
18-
org.jwt.legacy_api_tokens_enabled = False
19+
org.jwt.legacy_api_tokens_enabled = (
20+
legacy_api_tokens_enabled or settings.LABEL_STUDIO_ENABLE_LEGACY_API_TOKEN
21+
)
1922
org.jwt.save()
2023
return org
2124

label_studio/server.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,9 @@ def _create_user(input_args, config):
171171
user = User.objects.get(email=username)
172172
org = Organization.objects.first()
173173
if not org:
174-
org = Organization.create_organization(created_by=user, title='Label Studio')
174+
org = Organization.create_organization(
175+
created_by=user, title='Label Studio', legacy_api_tokens_enabled=input_args.enable_legacy_api_token
176+
)
175177
else:
176178
org.add_user(user)
177179
user.active_organization = org

web/dist/apps/labelstudio/main.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/dist/apps/labelstudio/main.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/dist/apps/labelstudio/vendor.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/dist/apps/labelstudio/vendor.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"message": "feat: OPTIC-1733: Standardize Dropdown Components Using LSE selector (#7257)",
3-
"commit": "ddb598a3e2f0aedb7d58055aa80b1937ee7f901f",
4-
"date": "2025-04-22T18:37:23.000Z",
2+
"message": "fix: LEAP-1947: Fix interactive view all FF in image tag case (#7387)",
3+
"commit": "98dfd898c23e94a2f094df3798ad233ba8a83a91",
4+
"date": "2025-04-23T16:14:37.000Z",
55
"branch": "develop"
66
}

web/dist/libs/editor/version.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"message": "feat: OPTIC-1733: Standardize Dropdown Components Using LSE selector (#7257)",
3-
"commit": "ddb598a3e2f0aedb7d58055aa80b1937ee7f901f",
4-
"date": "2025-04-22T18:37:23.000Z",
2+
"message": "fix: LEAP-1947: Fix interactive view all FF in image tag case (#7387)",
3+
"commit": "98dfd898c23e94a2f094df3798ad233ba8a83a91",
4+
"date": "2025-04-23T16:14:37.000Z",
55
"branch": "develop"
66
}

web/libs/editor/src/core/Tree.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,8 @@ function renderItem(ref: IAnyStateTreeNode, annotation: IAnnotation, includeKey
221221
if (isFF(FF_DEV_3391)) {
222222
if (!annotation) return null;
223223

224-
el = annotation.ids.get(cleanUpId(ref.id ?? ref.name));
224+
// The part `|| el` is a hack to allow it to work with Image regions. For some reason, it uses this function for rendering
225+
el = annotation.ids.get(cleanUpId(ref.id ?? ref.name)) || el;
225226
}
226227

227228
if (!el) {
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"ff_front_DEV_1713_audio_ui_150222_short": false,
33
"ff_front_dev_2715_audio_3_280722_short": true,
4-
"fflag_fix_front_dev_3391_interactive_view_all": false,
54
"fflag_feat_front_dev_3873_labeling_ui_improvements_short": true,
65
"fflag_fix_front_lsdv_4620_memory_leaks_100723_short": false
76
}

web/libs/editor/src/mixins/Tool.js

+19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { ff } from "@humansignal/core";
12
import { getEnv, getRoot, types } from "mobx-state-tree";
23
import { cloneNode } from "../core/Helpers";
4+
import { FF_DEV_3391 } from "../utils/feature-flags";
35
import { AnnotationMixin } from "./AnnotationMixin";
46

57
const ToolMixin = types
@@ -10,6 +12,13 @@ const ToolMixin = types
1012
})
1113
.views((self) => ({
1214
get obj() {
15+
if (ff.isActive(FF_DEV_3391)) {
16+
// It's a temporal solution (see root description)
17+
const root = self.manager?.root;
18+
if (root?.annotationStore.selected) {
19+
return root.annotationStore.selected.names.get(self.manager?.name);
20+
}
21+
}
1322
return self.manager?.obj ?? getEnv(self).object;
1423
},
1524

@@ -18,6 +27,16 @@ const ToolMixin = types
1827
},
1928

2029
get control() {
30+
if (ff.isActive(FF_DEV_3391)) {
31+
// It's a temporal solution (see root description)
32+
const control = getEnv(self).control;
33+
const { name } = control;
34+
const root = self.manager?.root;
35+
if (root?.annotationStore.selected) {
36+
return root.annotationStore.selected.names.get(name);
37+
}
38+
return control;
39+
}
2140
return getEnv(self).control;
2241
},
2342

web/libs/editor/src/mixins/ToolManagerMixin.js

+5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
import { ff } from "@humansignal/core";
12
import { types } from "mobx-state-tree";
23
import ToolsManager from "../tools/Manager";
34
import * as Tools from "../tools";
5+
import { FF_DEV_3391 } from "../utils/feature-flags";
46

57
export const ToolManagerMixin = types.model().actions((self) => {
68
return {
79
afterAttach() {
10+
if (ff.isActive(FF_DEV_3391) && !self.annotation) {
11+
return;
12+
}
813
const toolNames = self.toolNames ?? [];
914
const manager = ToolsManager.getInstance({ name: self.toname });
1015
const env = { manager, control: self };

web/libs/editor/src/stores/Annotation/Annotation.js

+2
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,10 @@ const _Annotation = types
174174

175175
const updateIds = (item) => {
176176
const children = item.children?.map(updateIds);
177+
const imageEntities = item.imageEntities?.map(updateIds);
177178

178179
if (children) item = { ...item, children };
180+
if (imageEntities) item = { ...item, imageEntities };
179181
if (item.id) item = { ...item, id: `${item.name ?? item.id}@${sn.id}` };
180182
// @todo fallback for tags with name as id:
181183
// if (item.name) item = { ...item, name: item.name + "@" + sn.id };

web/libs/editor/src/tags/object/Image/Image.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ff } from "@humansignal/core";
12
import { inject } from "mobx-react";
23
import { destroy, getRoot, getType, types } from "mobx-state-tree";
34

@@ -16,6 +17,7 @@ import ToolsManager from "../../../tools/Manager";
1617
import { parseValue } from "../../../utils/data";
1718
import {
1819
FF_DEV_3377,
20+
FF_DEV_3391,
1921
FF_DEV_3793,
2022
FF_LSDV_4583,
2123
FF_LSDV_4583_6,
@@ -567,7 +569,9 @@ const Model = types
567569
};
568570
},
569571
}))
570-
572+
.volatile((self) => ({
573+
manager: null,
574+
}))
571575
// actions for the tools
572576
.actions((self) => {
573577
const manager = ToolsManager.getInstance({ name: self.name });
@@ -577,18 +581,19 @@ const Model = types
577581
if (!self.store.task) return;
578582

579583
const parsedValue = self.multiImage ? self.parsedValueList : self.parsedValue;
584+
const idPostfix = self.annotation ? `@${self.annotation.id}` : "";
580585

581586
if (Array.isArray(parsedValue)) {
582587
parsedValue.forEach((src, index) => {
583588
self.imageEntities.push({
584-
id: `${self.name}#${index}`,
589+
id: `${self.name}#${index}${idPostfix}`,
585590
src,
586591
index,
587592
});
588593
});
589594
} else {
590595
self.imageEntities.push({
591-
id: `${self.name}#0`,
596+
id: `${self.name}#0${idPostfix}`,
592597
src: parsedValue,
593598
index: 0,
594599
});
@@ -598,6 +603,9 @@ const Model = types
598603
}
599604

600605
function afterAttach() {
606+
if (ff.isActive(FF_DEV_3391) && !self.annotation) {
607+
return;
608+
}
601609
if (self.selectioncontrol) manager.addTool("MoveTool", Tools.Selection.create({}, env));
602610

603611
if (self.zoomcontrol) manager.addTool("ZoomPanTool", Tools.Zoom.create({}, env));

web/libs/editor/src/tools/Manager.js

+13
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,19 @@ class ToolsManager {
4242
get preservedTool() {
4343
return window.localStorage.getItem(`selected-tool:${this.name}`);
4444
}
45+
/**
46+
There are some problems with working with ToolManager with interactive view all flag switched on.
47+
For now, tool manager is hidden in view_all,
48+
so it allows us to use root and selected annotation
49+
while we are looking for the object or the control from the tool.
50+
At the same time, we can use `annotation_id`
51+
as an additional key to be able to get the right annotation in that view_all mode.
52+
But in that case,
53+
there will be a problem with the inconsistent state of tool manager for 2 different annotations in the context of the same task.
54+
*/
55+
get root() {
56+
return root;
57+
}
4558

4659
get obj() {
4760
return root.annotationStore.names.get(this.name);

0 commit comments

Comments
 (0)