Skip to content

Commit c932d66

Browse files
makseqluarmr
andauthored
feat: BROS-502: flag_set with organization parameter and organization.id, ff for bypassing plugin verification (#8588)
Co-authored-by: Raul Martin <raul@humansignal.com> Co-authored-by: luarmr <luarmr@users.noreply.github.com>
1 parent 16b747c commit c932d66

File tree

4 files changed

+81
-5
lines changed

4 files changed

+81
-5
lines changed

label_studio/core/feature_flags/base.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
logger = logging.getLogger(__name__)
1818

1919
get_user_repr = load_func(settings.FEATURE_FLAGS_GET_USER_REPR)
20+
get_user_repr_from_organization = load_func(settings.FEATURE_FLAGS_GET_USER_REPR_FROM_ORGANIZATION)
2021

2122

2223
def get_feature_file_path():
@@ -69,7 +70,7 @@ def get_feature_file_path():
6970
client = ldclient.get()
7071

7172

72-
def flag_set(feature_flag, user=None, override_system_default=None):
73+
def flag_set(feature_flag, user=None, override_system_default=None, organization=None):
7374
"""Use this method to check whether this flag is set ON to the current user, to split the logic on backend
7475
For example,
7576
```
@@ -97,14 +98,18 @@ def flag_set(feature_flag, user=None, override_system_default=None):
9798
if request and getattr(request, 'user', None) and request.user.is_authenticated:
9899
user = request.user
99100

101+
if organization is None:
102+
user_dict = get_user_repr(user)
103+
else:
104+
user_dict = get_user_repr_from_organization(organization)
105+
100106
env_value = get_bool_env(feature_flag, default=None)
101107
if env_value is not None:
102108
return env_value
103109
if override_system_default is not None:
104110
system_default = override_system_default
105111
else:
106112
system_default = settings.FEATURE_FLAGS_DEFAULT_VALUE
107-
user_dict = get_user_repr(user)
108113
return client.variation(feature_flag, user_dict, system_default)
109114

110115

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from label_studio.core.feature_flags.base import flag_set
2+
from label_studio.core.feature_flags.utils import get_user_repr_from_organization
3+
4+
5+
def test_get_user_repr_from_organization_owner_email_and_org_id(django_user_model):
6+
# Create a minimal organization-like object
7+
class Org:
8+
def __init__(self, id, email):
9+
self.id = id
10+
11+
class CreatedBy:
12+
def __init__(self, email):
13+
self.email = email
14+
15+
self.created_by = CreatedBy(email)
16+
17+
org = Org(123, 'owner@example.com')
18+
19+
ctx = get_user_repr_from_organization(org)
20+
21+
assert ctx['key'] == 'owner@example.com'
22+
assert ctx['custom']['organization'] == 'owner@example.com'
23+
assert ctx['custom']['organization_id'] == 123
24+
25+
26+
def test_flag_set_with_organization_context_env_override(monkeypatch, settings):
27+
# Ensure offline mode/env control for deterministic behavior
28+
settings.FEATURE_FLAGS_OFFLINE = True
29+
30+
# Use env override path for flag resolution
31+
monkeypatch.setenv('fflag_feat_test_org_targeting', 'true')
32+
33+
class Org:
34+
def __init__(self, id, email):
35+
self.id = id
36+
37+
class CreatedBy:
38+
def __init__(self, email):
39+
self.email = email
40+
41+
self.created_by = CreatedBy(email)
42+
43+
org = Org(42, 'owner@example.com')
44+
45+
assert flag_set('fflag_feat_test_org_targeting', organization=org, override_system_default=False) is True
46+
47+
# Unset env should fall back to override_system_default=False
48+
monkeypatch.delenv('fflag_feat_test_org_targeting', raising=False)
49+
assert flag_set('fflag_feat_test_org_targeting', organization=org, override_system_default=False) is False
Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,32 @@
11
def get_user_repr(user):
22
"""Turn user object into dict with required properties"""
33
if user.is_anonymous:
4-
return {'key': str(user), 'custom': {'organization': None}}
4+
return {'key': str(user), 'custom': {'organization': None, 'organization_id': None}}
55
user_data = {'email': user.email}
66
user_data['key'] = user_data['email']
77
if user.active_organization is not None:
8-
user_data['custom'] = {'organization': user.active_organization.created_by.email}
8+
user_data['custom'] = {
9+
'organization': user.active_organization.created_by.email,
10+
'organization_id': user.active_organization.id,
11+
}
912
else:
10-
user_data['custom'] = {'organization': None}
13+
user_data['custom'] = {'organization': None, 'organization_id': None}
1114
return user_data
15+
16+
17+
def get_user_repr_from_organization(organization):
18+
"""Turn organization object into its owner dict"""
19+
if organization is None:
20+
return {
21+
'key': 'none',
22+
'custom': {'organization': None, 'organization_id': None},
23+
}
24+
25+
email = organization.created_by.email if organization.created_by else None
26+
return {
27+
'key': email,
28+
'custom': {
29+
'organization': email,
30+
'organization_id': organization.id,
31+
},
32+
}

label_studio/core/settings/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,7 @@
634634
PROJECT_IMPORT_PERMISSION = 'projects.permissions.ProjectImportPermission'
635635
DELETE_TASKS_ANNOTATIONS_POSTPROCESS = None
636636
FEATURE_FLAGS_GET_USER_REPR = 'core.feature_flags.utils.get_user_repr'
637+
FEATURE_FLAGS_GET_USER_REPR_FROM_ORGANIZATION = 'core.feature_flags.utils.get_user_repr_from_organization'
637638

638639
# Test factories
639640
ORGANIZATION_FACTORY = 'organizations.tests.factories.OrganizationFactory'

0 commit comments

Comments
 (0)