Skip to content

Commit ea9722c

Browse files
Merge branch 'develop' into 'fb-fit-1123'
Workflow run: https://github.com/HumanSignal/label-studio/actions/runs/20919412034
2 parents c2092de + f470537 commit ea9722c

File tree

10 files changed

+76
-89
lines changed

10 files changed

+76
-89
lines changed

label_studio/core/feature_flags/stale_feature_flags.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@
6464
'fflag_root_223_optimize_delete_predictions': True,
6565
'fflag_root_212_reduce_importstoragelink_counts': True,
6666
# Jan 8
67+
'fflag_feat_all_dia_2067_tasks_table_component': True, # Removed from code
68+
'fflag_feat_front_dia_1747_projects_list_banner': True, # Removed from code
69+
'fflag_feat_front_leap_1424_grid_preview_short': True, # Removed from code
70+
'ff_front_dev_1536_taxonomy_user_labels_150222_long': True, # Removed from code
71+
'fflag_feat_front_leap_2036_annotations_summary': True, # Removed from code
72+
'fflag_feat_front_leap_1973_adjustable_spans_090425_short': True, # Removed from code
73+
'fflag_feat_front_lsdv_5451_async_taxonomy_110823_short': True, # Removed from code
74+
'fflag_feat_all_lsdv_e_294_llm_annotations_180723_long': True, # Removed from code
75+
'fflag_fix_front_dev_3793_relative_coords_short': True, # Removed from code
76+
'fflag_feat_front_lsdv_4583_6_images_preloading_short': True, # Removed from code
77+
'fflag_feat_all_leap_1821_annotation_limit_short': True,
78+
'fflag_all_feat_dia_1777_ls_homepage_short': True,
79+
'fflag_feat_all_leap_1181_bulk_annotation_short': True,
80+
'fflag_feat_front_lsdv_4583_multi_image_segmentation_short': True,
6781
'fflag_fix_back_plt_914_projects_list_cache_sdk_09102025_short': True,
6882
'fflag_fix_back_plt_913_cache_annotator_queue_total_07102025_short': True,
6983
'fflag_fix_back_plt_913_cache_finished_task_number_06102025_short': True,

label_studio/feature_flags.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2229,6 +2229,33 @@
22292229
"version": 2,
22302230
"deleted": false
22312231
},
2232+
"fflag_feat_fit_1084_fsm_analytics_kpis": {
2233+
"key": "fflag_feat_fit_1084_fsm_analytics_kpis",
2234+
"on": true,
2235+
"prerequisites": [],
2236+
"targets": [],
2237+
"contextTargets": [],
2238+
"rules": [],
2239+
"fallthrough": {
2240+
"variation": 0
2241+
},
2242+
"offVariation": 1,
2243+
"variations": [
2244+
true,
2245+
false
2246+
],
2247+
"clientSideAvailability": {
2248+
"usingMobileKey": false,
2249+
"usingEnvironmentId": false
2250+
},
2251+
"clientSide": false,
2252+
"salt": "fea6fb38190a4ad6a102f56ea210a706",
2253+
"trackEvents": false,
2254+
"trackEventsFallthrough": false,
2255+
"debugEventsUntilDate": null,
2256+
"version": 3,
2257+
"deleted": false
2258+
},
22322259
"fflag_feat_fit_449_datamanager_filter_members_short": {
22332260
"key": "fflag_feat_fit_449_datamanager_filter_members_short",
22342261
"on": false,

label_studio/io_storages/base_models.py

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -398,34 +398,16 @@ def resolve_uri(self, uri, task=None):
398398
logger.debug(f'No storage info found for URI={uri}')
399399
return
400400

401-
if flag_set('fflag_optic_all_optic_1938_storage_proxy', user=self.project.organization.created_by):
402-
if task is None:
403-
logger.error(f'Task is required to resolve URI={uri}', exc_info=True)
404-
raise ValueError(f'Task is required to resolve URI={uri}')
405-
406-
proxy_url = urljoin(
407-
settings.HOSTNAME,
408-
reverse('storages:task-storage-data-resolve', kwargs={'task_id': task.id})
409-
+ f'?fileuri={base64.urlsafe_b64encode(extracted_uri.encode()).decode()}',
410-
)
411-
return uri.replace(extracted_uri, proxy_url)
412-
413-
# ff off: old logic without proxy
414-
else:
415-
if self.presign and task is not None:
416-
proxy_url = urljoin(
417-
settings.HOSTNAME,
418-
reverse('storages:task-storage-data-presign', kwargs={'task_id': task.id})
419-
+ f'?fileuri={base64.urlsafe_b64encode(extracted_uri.encode()).decode()}',
420-
)
421-
return uri.replace(extracted_uri, proxy_url)
422-
else:
423-
# this branch is our old approach:
424-
# it generates presigned URLs if storage.presign=True;
425-
# or it inserts base64 media into task data if storage.presign=False
426-
http_url = self.generate_http_url(extracted_uri)
427-
428-
return uri.replace(extracted_uri, http_url)
401+
if task is None:
402+
logger.error(f'Task is required to resolve URI={uri}', exc_info=True)
403+
raise ValueError(f'Task is required to resolve URI={uri}')
404+
405+
proxy_url = urljoin(
406+
settings.HOSTNAME,
407+
reverse('storages:task-storage-data-resolve', kwargs={'task_id': task.id})
408+
+ f'?fileuri={base64.urlsafe_b64encode(extracted_uri.encode()).decode()}',
409+
)
410+
return uri.replace(extracted_uri, proxy_url)
429411
except Exception:
430412
logger.info(f"Can't resolve URI={uri}", exc_info=True)
431413

label_studio/io_storages/proxy_api.py

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from typing import Union
55
from urllib.parse import unquote
66

7-
from core.feature_flags import flag_set
87
from core.utils.exceptions import extract_message
98
from django.conf import settings
109
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, StreamingHttpResponse
@@ -40,21 +39,17 @@ def resolve(self, request: HttpRequest, fileuri: str, instance: Union[Task, Proj
4039
fileuri = unquote(fileuri)
4140

4241
# Try to find storage by URL
43-
project = None
44-
if flag_set('fflag_optic_all_optic_1938_storage_proxy', user='auto'):
45-
project = instance if isinstance(instance, Project) else instance.project
46-
storage_objects = project.get_all_import_storage_objects
47-
storage = get_storage_by_url(fileuri, storage_objects)
48-
if not storage:
49-
logger.error(f'Could not find storage for URI {fileuri}')
50-
return Response(status=status.HTTP_404_NOT_FOUND)
51-
# Not all storages support presigned URLs
52-
if not hasattr(storage, 'presign'):
53-
logger.error(f'Storage {storage} does not support presign URLs')
54-
return Response(status=status.HTTP_404_NOT_FOUND)
55-
presign = storage.presign
56-
else:
57-
presign = True
42+
project = instance if isinstance(instance, Project) else instance.project
43+
storage_objects = project.get_all_import_storage_objects
44+
storage = get_storage_by_url(fileuri, storage_objects)
45+
if not storage:
46+
logger.error(f'Could not find storage for URI {fileuri}')
47+
return Response(status=status.HTTP_404_NOT_FOUND)
48+
# Not all storages support presigned URLs
49+
if not hasattr(storage, 'presign'):
50+
logger.error(f'Storage {storage} does not support presign URLs')
51+
return Response(status=status.HTTP_404_NOT_FOUND)
52+
presign = storage.presign
5853

5954
# Check if storage should use presigned URLs;
6055
# It's important to have this check here, because it increases security:

label_studio/io_storages/tests/test_proxy_api.py

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,8 @@ def test_resolve_with_permission_denied(self):
3737
result = self.mixin.resolve(self.request, 'test_fileuri', self.task)
3838
assert result.status_code == status.HTTP_403_FORBIDDEN
3939

40-
@patch('io_storages.proxy_api.flag_set')
4140
@patch('io_storages.proxy_api.get_storage_by_url')
42-
def test_resolve_with_base64_decoding(self, mock_get_storage, mock_flag_set):
43-
mock_flag_set.return_value = True
41+
def test_resolve_with_base64_decoding(self, mock_get_storage):
4442
mock_get_storage.return_value = self.storage
4543
fileuri = base64.urlsafe_b64encode(b'test_uri').decode()
4644

@@ -49,10 +47,8 @@ def test_resolve_with_base64_decoding(self, mock_get_storage, mock_flag_set):
4947
self.mixin.resolve(self.request, fileuri, self.task)
5048
mock_redirect.assert_called_once_with('test_uri', self.task, 'Task')
5149

52-
@patch('io_storages.proxy_api.flag_set')
5350
@patch('io_storages.proxy_api.get_storage_by_url')
54-
def test_resolve_with_url_unquote_fallback(self, mock_get_storage, mock_flag_set):
55-
mock_flag_set.return_value = True
51+
def test_resolve_with_url_unquote_fallback(self, mock_get_storage):
5652
mock_get_storage.return_value = self.storage
5753

5854
with patch.object(self.mixin, 'redirect_to_presign_url') as mock_redirect:
@@ -61,28 +57,22 @@ def test_resolve_with_url_unquote_fallback(self, mock_get_storage, mock_flag_set
6157
self.mixin.resolve(self.request, 's3://bucket/file.jpg', self.task)
6258
mock_redirect.assert_called_once_with('s3://bucket/file.jpg', self.task, 'Task')
6359

64-
@patch('io_storages.proxy_api.flag_set')
6560
@patch('io_storages.proxy_api.get_storage_by_url')
66-
def test_resolve_storage_not_found(self, mock_get_storage, mock_flag_set):
67-
mock_flag_set.return_value = True
61+
def test_resolve_storage_not_found(self, mock_get_storage):
6862
mock_get_storage.return_value = None
6963
result = self.mixin.resolve(self.request, 'fileuri', self.task)
7064
assert result.status_code == status.HTTP_404_NOT_FOUND
7165

72-
@patch('io_storages.proxy_api.flag_set')
7366
@patch('io_storages.proxy_api.get_storage_by_url')
74-
def test_resolve_storage_no_presign_support(self, mock_get_storage, mock_flag_set):
75-
mock_flag_set.return_value = True
67+
def test_resolve_storage_no_presign_support(self, mock_get_storage):
7668
mock_storage = MagicMock()
7769
delattr(mock_storage, 'presign')
7870
mock_get_storage.return_value = mock_storage
7971
result = self.mixin.resolve(self.request, 'fileuri', self.task)
8072
assert result.status_code == status.HTTP_404_NOT_FOUND
8173

82-
@patch('io_storages.proxy_api.flag_set')
8374
@patch('io_storages.proxy_api.get_storage_by_url')
84-
def test_resolve_with_presign_true(self, mock_get_storage, mock_flag_set):
85-
mock_flag_set.return_value = True
75+
def test_resolve_with_presign_true(self, mock_get_storage):
8676
mock_storage = MagicMock()
8777
mock_storage.presign = True
8878
mock_get_storage.return_value = mock_storage
@@ -92,10 +82,8 @@ def test_resolve_with_presign_true(self, mock_get_storage, mock_flag_set):
9282
self.mixin.resolve(self.request, 'fileuri', self.task)
9383
mock_redirect.assert_called_once()
9484

95-
@patch('io_storages.proxy_api.flag_set')
9685
@patch('io_storages.proxy_api.get_storage_by_url')
97-
def test_resolve_with_presign_false(self, mock_get_storage, mock_flag_set):
98-
mock_flag_set.return_value = True
86+
def test_resolve_with_presign_false(self, mock_get_storage):
9987
mock_storage = MagicMock()
10088
mock_storage.presign = False
10189
mock_get_storage.return_value = mock_storage

label_studio/tasks/api.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import logging
44

5-
from core.feature_flags import flag_set
65
from core.mixins import GetParentObjectMixin
76
from core.permissions import ViewClassPermission, all_permissions
87
from core.utils.common import is_community
@@ -343,13 +342,10 @@ def get_queryset(self):
343342
if review:
344343
kwargs = {'fields_for_evaluation': ['annotators', 'reviewed']}
345344
else:
346-
if flag_set('fflag_fix_back_bros_182_api_task_optimizations', user=self.request.user):
347-
kwargs = {
348-
'all_fields': True,
349-
'excluded_fields_for_evaluation': self.get_excluded_fields_for_evaluation(),
350-
}
351-
else:
352-
kwargs = {'all_fields': True}
345+
kwargs = {
346+
'all_fields': True,
347+
'excluded_fields_for_evaluation': self.get_excluded_fields_for_evaluation(),
348+
}
353349
project = self.request.query_params.get('project') or self.request.data.get('project')
354350
if not project:
355351
project = task.project.id

label_studio/tests/conftest.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,6 @@ def set_feature_flag_envvar():
705705
"""
706706
Automatically set the environment variable for all tests, including Tavern tests.
707707
"""
708-
os.environ['fflag_optic_all_optic_1938_storage_proxy'] = 'true'
709708
os.environ['fflag_feat_utc_210_prediction_validation_15082025'] = 'true'
710709

711710

web/libs/editor/src/tags/object/TimeSeries.jsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import { fixMobxObserve } from "../../utils/utilities";
2828
import "./TimeSeries/MultiChannel";
2929
import "./TimeSeries/Channel";
3030
import { getChannelColor } from "./TimeSeries/palette";
31-
import { FF_TIMESERIES_SYNC, isFF } from "../../utils/feature-flags";
3231
import { ff } from "@humansignal/core";
3332
/**
3433
* The `TimeSeries` tag can be used to label time series data. Read more about Time Series Labeling on [the time series template page](../templates/time_series.html).
@@ -1022,16 +1021,13 @@ const Model = types
10221021

10231022
registerSyncHandlers() {
10241023
if (!isAlive(self)) return;
1025-
if (isFF(FF_TIMESERIES_SYNC)) {
1026-
self.syncHandlers.set("seek", self._handleSeek);
1027-
self.syncHandlers.set("play", self._handlePlay);
1028-
self.syncHandlers.set("pause", self._handlePause);
1029-
}
1024+
self.syncHandlers.set("seek", self._handleSeek);
1025+
self.syncHandlers.set("play", self._handlePlay);
1026+
self.syncHandlers.set("pause", self._handlePause);
10301027
},
10311028

10321029
emitSeekSync() {
10331030
if (!isAlive(self)) return;
1034-
if (!isFF(FF_TIMESERIES_SYNC)) return;
10351031
if (self.suppressSync) return;
10361032

10371033
const centerTime = self.centerTime; // centerTime is in NATIVE units (ms if isDate, else seconds/indices)
@@ -1056,7 +1052,7 @@ const Model = types
10561052
},
10571053

10581054
plotClickHandler(timeClicked) {
1059-
if (!isAlive(self) || !isFF(FF_TIMESERIES_SYNC) || !self.sync) return;
1055+
if (!isAlive(self) || !self.sync) return;
10601056
if (self.isNotReady) return;
10611057

10621058
const [minKey, maxKey] = self.keysRange;

web/libs/editor/src/tags/object/__tests__/TimeSeries.test.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import * as d3 from "d3";
33
import { types } from "mobx-state-tree";
44
import { mockFF } from "../../../../__mocks__/global";
5-
import { FF_TIMESERIES_SYNC } from "../../../utils/feature-flags";
65
import { TimeSeriesModel } from "../TimeSeries";
76

87
const ff = mockFF();
@@ -36,7 +35,6 @@ const MockStore = types
3635

3736
// Set up feature flags
3837
ff.setup();
39-
ff.set(FF_TIMESERIES_SYNC, true);
4038

4139
describe("TimeSeries brush range calculation", () => {
4240
// creating models can be a long one, so all tests will share one model

web/libs/editor/src/utils/feature-flags.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,6 @@ export const FF_IMAGE_MEMORY_USAGE = "fflag_feat_front_optic_1479_improve_image_
169169

170170
export const FF_VIDEO_FRAME_SEEK_PRECISION = "fflag_fix_front_optic_1608_improve_video_frame_seek_precision_short";
171171

172-
/**
173-
* Allows the time series component to participate in synchronized playback with other media components (like audio and video)
174-
* when the feature flag is enabled, while maintaining independent operation when the flag is disabled.
175-
*
176-
* @link https://app.launchdarkly.com/projects/default/flags/fflag_feat_optic_2125_timeseries_sync
177-
*/
178-
export const FF_TIMESERIES_SYNC = "fflag_feat_optic_2125_timeseries_sync";
179-
180172
Object.assign(window, {
181173
APP_SETTINGS: {
182174
...(window.APP_SETTINGS ?? {}),

0 commit comments

Comments
 (0)