Skip to content

Commit cb8fa5a

Browse files
authored
[PLT-1398] Vb/project dashboard extra data plt 1398 (#1772)
1 parent bf30f08 commit cb8fa5a

File tree

6 files changed

+114
-21
lines changed

6 files changed

+114
-21
lines changed

libs/labelbox/src/labelbox/schema/labeling_service_dashboard.py

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,24 @@
77
from labelbox.pydantic_compat import BaseModel, root_validator, Field
88
from labelbox.schema.search_filters import SearchFilter, build_search_filter
99
from labelbox.utils import _CamelCaseMixin
10+
from .ontology_kind import EditorTaskType
11+
from labelbox.schema.media_type import MediaType
1012
from labelbox.schema.labeling_service_status import LabelingServiceStatus
13+
from labelbox.utils import _CamelCaseMixin, sentence_case
1114

1215
GRAPHQL_QUERY_SELECTIONS = """
1316
id
1417
name
15-
# serviceType
16-
# createdAt
17-
# updatedAt
18-
# createdById
18+
boostRequestedAt
19+
boostUpdatedAt
20+
boostRequestedBy
1921
boostStatus
2022
dataRowsCount
2123
dataRowsInReviewCount
2224
dataRowsInReworkCount
2325
dataRowsDoneCount
26+
mediaType
27+
editorTaskType
2428
"""
2529

2630

@@ -38,16 +42,16 @@ class LabelingServiceDashboard(BaseModel):
3842
"""
3943
id: str = Field(frozen=True)
4044
name: str = Field(frozen=True)
41-
service_type: Optional[str] = Field(frozen=True, default=None)
4245
created_at: Optional[datetime] = Field(frozen=True, default=None)
4346
updated_at: Optional[datetime] = Field(frozen=True, default=None)
4447
created_by_id: Optional[str] = Field(frozen=True, default=None)
45-
status: LabelingServiceStatus = Field(frozen=True,
46-
default=LabelingServiceStatus.Missing)
48+
status: LabelingServiceStatus = Field(frozen=True, default=None)
4749
data_rows_count: int = Field(frozen=True)
4850
data_rows_in_review_count: int = Field(frozen=True)
4951
data_rows_in_rework_count: int = Field(frozen=True)
5052
data_rows_done_count: int = Field(frozen=True)
53+
media_type: Optional[MediaType] = Field(frozen=True, default=None)
54+
editor_task_type: EditorTaskType = Field(frozen=True, default=None)
5155

5256
client: Any # type Any to avoid circular import from client
5357

@@ -59,12 +63,43 @@ def __init__(self, **kwargs):
5963

6064
@property
6165
def tasks_completed(self):
66+
"""
67+
Count how many data rows have been completed (i.e. in the Done queue)
68+
"""
6269
return self.data_rows_done_count
6370

6471
@property
6572
def tasks_remaining(self):
73+
"""
74+
Count how many data rows have not been completed
75+
"""
6676
return self.data_rows_count - self.data_rows_done_count
6777

78+
@property
79+
def service_type(self):
80+
"""
81+
Descriptive labeling service definition by media type and editor task type
82+
"""
83+
if self.media_type is None:
84+
return None
85+
86+
if self.editor_task_type is None:
87+
return sentence_case(self.media_type.value)
88+
89+
if self.editor_task_type == EditorTaskType.OfflineModelChatEvaluation and self.media_type == MediaType.Conversational:
90+
return "Offline chat evaluation"
91+
92+
if self.editor_task_type == EditorTaskType.ModelChatEvaluation and self.media_type == MediaType.Conversational:
93+
return "Live chat evaluation"
94+
95+
if self.editor_task_type == EditorTaskType.ResponseCreation and self.media_type == MediaType.Text:
96+
return "Response creation"
97+
98+
if self.media_type == MediaType.LLMPromptCreation or self.media_type == MediaType.LLMPromptResponseCreation:
99+
return "Prompt response creation"
100+
101+
return sentence_case(self.media_type.value)
102+
68103
class Config(_CamelCaseMixin.Config):
69104
...
70105

@@ -141,8 +176,17 @@ def convert_to_labeling_service_dashboard(client, data):
141176
)
142177

143178
@root_validator(pre=True)
144-
def convert_boost_status_to_enum(cls, data):
179+
def convert_boost_data(cls, data):
145180
if 'boostStatus' in data:
146181
data['status'] = LabelingServiceStatus(data.pop('boostStatus'))
147182

183+
if 'boostRequestedAt' in data:
184+
data['created_at'] = data.pop('boostRequestedAt')
185+
186+
if 'boostUpdatedAt' in data:
187+
data['updated_at'] = data.pop('boostUpdatedAt')
188+
189+
if 'boostRequestedBy' in data:
190+
data['created_by_id'] = data.pop('boostRequestedBy')
191+
148192
return data

libs/labelbox/src/labelbox/schema/media_type.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from enum import Enum
22

3+
from labelbox.utils import camel_case
4+
35

46
class MediaType(Enum):
57
Audio = "AUDIO"
@@ -23,17 +25,33 @@ class MediaType(Enum):
2325
LLM = "LLM"
2426

2527
@classmethod
26-
def _missing_(cls, name):
28+
def _missing_(cls, value):
2729
"""Handle missing null data types for projects
2830
created without setting allowedMediaType
2931
Handle upper case names for compatibility with
3032
the GraphQL"""
3133

32-
if name is None:
34+
if value is None:
3335
return cls.Unknown
3436

35-
for member in cls.__members__:
36-
if member.name == name.upper():
37+
def matches(value, name):
38+
"""
39+
This will convert string values (from api) to match enum values
40+
Some string values come as snake case (i.e. llm-prompt-creation)
41+
Some string values come as camel case (i.e. llmPromptCreation)
42+
etc depending on which api returns the value
43+
"""
44+
value_upper = value.upper()
45+
name_upper = name.upper()
46+
value_underscore = value.replace("-", "_")
47+
camel_case_value = camel_case(value_underscore)
48+
49+
return (value_upper == name_upper or
50+
value_underscore.upper() == name_upper or
51+
camel_case_value.upper() == name_upper)
52+
53+
for name, member in cls.__members__.items():
54+
if matches(value, name):
3755
return member
3856

3957
@classmethod

libs/labelbox/src/labelbox/schema/ontology_kind.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,30 @@ def get_ontology_kind_validation_error(cls, ontology_kind):
2121
return TypeError(f"{ontology_kind}: is not a valid ontology kind. Use"
2222
f" any of {OntologyKind.__members__.items()}"
2323
" from OntologyKind.")
24-
24+
2525
@staticmethod
26-
def evaluate_ontology_kind_with_media_type(ontology_kind,
27-
media_type: Optional[MediaType]) -> Union[MediaType, None]:
28-
26+
def evaluate_ontology_kind_with_media_type(
27+
ontology_kind,
28+
media_type: Optional[MediaType]) -> Union[MediaType, None]:
29+
2930
ontology_to_media = {
30-
OntologyKind.ModelEvaluation: (MediaType.Conversational, "For chat evaluation, media_type must be Conversational."),
31-
OntologyKind.ResponseCreation: (MediaType.Text, "For response creation, media_type must be Text.")
31+
OntologyKind.ModelEvaluation:
32+
(MediaType.Conversational,
33+
"For chat evaluation, media_type must be Conversational."),
34+
OntologyKind.ResponseCreation:
35+
(MediaType.Text,
36+
"For response creation, media_type must be Text.")
3237
}
3338

3439
if ontology_kind in ontology_to_media:
35-
expected_media_type, error_message = ontology_to_media[ontology_kind]
40+
expected_media_type, error_message = ontology_to_media[
41+
ontology_kind]
3642

3743
if media_type is None or media_type == expected_media_type:
3844
media_type = expected_media_type
3945
else:
4046
raise ValueError(error_message)
41-
47+
4248
return media_type
4349

4450

libs/labelbox/src/labelbox/utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ def snake_case(s):
3939
return _convert(s, "_", lambda i: False)
4040

4141

42+
def sentence_case(s: str) -> str:
43+
""" Converts a string in [snake|camel|title]case to Sentence case. """
44+
# Replace underscores with spaces and convert to lower case
45+
sentence_str = s.replace("_", " ").lower()
46+
# Capitalize the first letter of each word
47+
sentence_str = sentence_str.capitalize()
48+
return sentence_str
49+
50+
4251
def is_exactly_one_set(*args):
4352
return sum([bool(arg) for arg in args]) == 1
4453

libs/labelbox/tests/integration/test_labeling_dashboard.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from datetime import datetime, timedelta
22
from labelbox.schema.labeling_service import LabelingServiceStatus
33
from labelbox.schema.search_filters import DateOperator, DateRange, DateRangeOperator, DateRangeValue, DateValue, IdOperator, OperationType, OrganizationFilter, WorkforceRequestedDateFilter, WorkforceRequestedDateRangeFilter, WorkspaceFilter
4+
from labelbox.schema.ontology_kind import EditorTaskType
5+
from labelbox.schema.media_type import MediaType
46

57

68
def test_request_labeling_service_dashboard(rand_gen,
@@ -18,6 +20,9 @@ def test_request_labeling_service_dashboard(rand_gen,
1820
assert labeling_service_dashboard.status == LabelingServiceStatus.Missing
1921
assert labeling_service_dashboard.tasks_completed == 0
2022
assert labeling_service_dashboard.tasks_remaining == 0
23+
assert labeling_service_dashboard.media_type == MediaType.Conversational
24+
assert labeling_service_dashboard.editor_task_type == EditorTaskType.OfflineModelChatEvaluation
25+
assert labeling_service_dashboard.service_type == "Offline chat evaluation"
2126

2227
labeling_service_dashboard = [
2328
ld for ld in project.client.get_labeling_service_dashboards()
@@ -82,3 +87,6 @@ def test_request_labeling_service_dashboard_filters(requested_labeling_service):
8287
]
8388
assert len(labeling_service_dashboard) == 0
8489
assert labeling_service_dashboard == []
90+
labeling_service_dashboard = project.client.get_labeling_service_dashboards(
91+
).get_one()
92+
assert labeling_service_dashboard

libs/labelbox/tests/unit/test_utils.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import pytest
2-
from labelbox.utils import format_iso_datetime, format_iso_from_string
2+
from labelbox.utils import format_iso_datetime, format_iso_from_string, sentence_case
33

44

55
@pytest.mark.parametrize('datetime_str, expected_datetime_str',
@@ -11,3 +11,11 @@ def test_datetime_parsing(datetime_str, expected_datetime_str):
1111
# NOTE I would normally not take 'expected' using another function from sdk code, but in this case this is exactly the usage in _validate_parse_datetime
1212
assert format_iso_datetime(
1313
format_iso_from_string(datetime_str)) == expected_datetime_str
14+
15+
16+
@pytest.mark.parametrize(
17+
'str, expected_str',
18+
[('AUDIO', 'Audio'),
19+
('LLM_PROMPT_RESPONSE_CREATION', 'Llm prompt response creation')])
20+
def test_sentence_case(str, expected_str):
21+
assert sentence_case(str) == expected_str

0 commit comments

Comments
 (0)