Skip to content

Commit 6fc224c

Browse files
Narek MkhitaryanNarek Mkhitaryan
authored andcommitted
updated list_users for scoring
1 parent 4e3a54a commit 6fc224c

File tree

3 files changed

+136
-6
lines changed

3 files changed

+136
-6
lines changed

src/superannotate/lib/app/interface/sdk_interface.py

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ def list_users(
460460
**filters,
461461
):
462462
"""
463-
Search users by filtering criteria
463+
Search users, including their scores, by filtering criteria.
464464
465465
:param project: Project name or ID, if provided, results will be for project-level,
466466
otherwise results will be for team level.
@@ -470,8 +470,7 @@ def list_users(
470470
471471
Possible values are
472472
473-
- "custom_fields": Includes the custom fields assigned to each user.
474-
:type include: list of str, optional
473+
- "custom_fields": Includes custom fields and scores assigned to each user.
475474
476475
:param filters: Specifies filtering criteria, with all conditions combined using logical AND.
477476
@@ -501,18 +500,35 @@ def list_users(
501500
- email__contains: str
502501
- email__starts: str
503502
- email__ends: str
503+
504+
Following params if project is not selected::
505+
504506
- state: Literal[“Confirmed”, “Pending”]
505507
- state__in: List[Literal[“Confirmed”, “Pending”]]
506508
- role: Literal[“admin”, “contributor”]
507509
- role__in: List[Literal[“admin”, “contributor”]]
508510
509-
Custom Fields Filtering:
510-
- Custom fields must be prefixed with `custom_field__`.
511+
Scores and Custom Field Filtering:
512+
513+
- Scores and other custom fields must be prefixed with `custom_field__` .
511514
- Example: custom_field__Due_date__gte="1738281600" (filtering users whose Due date is after the given Unix timestamp).
512515
516+
- **Text** custom field only works with the following filter params: __in, __notin, __contains
517+
- **Numeric** custom field only works with the following filter params: __in, __notin, __ne, __gt, __gte, __lt, __lte
518+
- **Single-select** custom field only works with the following filter params: __in, __notin, __contains
519+
- **Multi-select** custom field only works with the following filter params: __in, __notin
520+
- **Date picker** custom field only works with the following filter params: __gt, __gte, __lt, __lte
521+
522+
**If score name has a space, please use the following format to filter them**:
523+
::
524+
525+
user_filters = {"custom_field__accuracy score 30D__lt": 90}
526+
client.list_users(include=["custom_fields"], **user_filters)
527+
528+
513529
:type filters: UserFilters, optional
514530
515-
:return: A list of team users metadata that matches the filtering criteria
531+
:return: A list of team/project users metadata that matches the filtering criteria
516532
:rtype: list of dicts
517533
518534
Request Example:
@@ -543,6 +559,82 @@ def list_users(
543559
"team_id": 44311,
544560
}
545561
]
562+
563+
Request Example:
564+
::
565+
566+
# Project level scores
567+
568+
scores = client.list_users(
569+
include=["custom_fields"],
570+
project="my_multimodal",
571+
email__contains="@superannotate.com",
572+
custom_field__speed__gte=90,
573+
custom_field__weight__lte=1,
574+
)
575+
576+
Response Example:
577+
::
578+
579+
# Project level scores
580+
581+
[
582+
{
583+
"createdAt": "2025-03-07T13:19:59.000Z",
584+
"updatedAt": "2025-03-07T13:19:59.000Z",
585+
"custom_fields": {"speed": 92, "weight": 0.8},
586+
"email": "example@superannotate.com",
587+
"id": 715121,
588+
"role": "Annotator",
589+
"state": "Confirmed",
590+
"team_id": 1234,
591+
}
592+
]
593+
594+
Request Example:
595+
::
596+
597+
# Team level scores
598+
599+
user_filters = {
600+
"custom_field__accuracy score 30D__lt": 95,
601+
"custom_field__speed score 7D__lt": 15
602+
}
603+
604+
scores = client.list_users(
605+
include=["custom_fields"],
606+
email__contains="@superannotate.com",
607+
role="Contributor",
608+
**user_filters
609+
)
610+
611+
Response Example:
612+
::
613+
614+
# Team level scores
615+
616+
[
617+
{
618+
"createdAt": "2025-03-07T13:19:59.000Z",
619+
"updatedAt": "2025-03-07T13:19:59.000Z",
620+
"custom_fields": {
621+
"Test custom field": 80,
622+
"Tag custom fields": ["Tag1", "Tag2"],
623+
"accuracy score 30D": 95,
624+
"accuracy score 14D": 47,
625+
"accuracy score 7D": 24,
626+
"speed score 30D": 33,
627+
"speed score 14D": 22,
628+
"speed score 7D": 11,
629+
},
630+
"email": "example@superannotate.com",
631+
"id": 715121,
632+
"role": "Contributor",
633+
"state": "Confirmed",
634+
"team_id": 1234,
635+
}
636+
]
637+
546638
"""
547639
if project is not None:
548640
if isinstance(project, int):

tests/integration/work_management/test_user_custom_fields.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,18 @@ def test_list_users(self):
149149
)[0]
150150
assert scapegoat["custom_fields"]["SDK_test_date_picker"] == value
151151

152+
# by date_picker with dict **filters
153+
value = round(time.time(), 3)
154+
filters = {
155+
"email": scapegoat["email"],
156+
"custom_field__SDK_test_date_picker": value,
157+
}
158+
scapegoat = sa.list_users(
159+
include=["custom_fields"],
160+
**filters,
161+
)[0]
162+
assert scapegoat["custom_fields"]["SDK_test_date_picker"] == value
163+
152164
# by date_picker None case
153165
sa.set_user_custom_field(
154166
scapegoat["email"],

tests/integration/work_management/test_user_scoring.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,32 @@ def test_set_get_scores(self):
131131
assert score["createdAt"]
132132
assert score["updatedAt"]
133133

134+
def test_list_users_with_scores(self):
135+
# list team users
136+
team_users = sa.list_users(include=["custom_fields"])
137+
for u in team_users:
138+
for s in SCORE_TEMPLATES:
139+
try:
140+
assert not u["custom_fields"][f"{s['name']} 7D"]
141+
assert not u["custom_fields"][f"{s['name']} 14D"]
142+
assert not u["custom_fields"][f"{s['name']} 30D"]
143+
except KeyError as e:
144+
raise AssertionError(str(e))
145+
146+
# filter team users by score
147+
filters = {f"custom_field__{SCORE_TEMPLATES[0]['name']} 7D": None}
148+
team_users = sa.list_users(include=["custom_fields"], **filters)
149+
assert team_users
150+
for u in team_users:
151+
try:
152+
assert not u["custom_fields"][f"{SCORE_TEMPLATES[0]['name']} 7D"]
153+
except KeyError as e:
154+
raise AssertionError(str(e))
155+
156+
filters = {f"custom_field__{SCORE_TEMPLATES[1]['name']} 30D": 5}
157+
team_users = sa.list_users(include=["custom_fields"], **filters)
158+
assert not team_users
159+
134160
def test_set_get_scores_negative_cases(self):
135161
item_name = f"test_item_{uuid.uuid4()}"
136162
self._attach_item(self.PROJECT_NAME, item_name)

0 commit comments

Comments
 (0)