Skip to content

Commit e20a774

Browse files
authored
[PLT-1306][PLT-1307] Get project list add details part 1 (#1770)
1 parent 6252ea9 commit e20a774

File tree

7 files changed

+258
-36
lines changed

7 files changed

+258
-36
lines changed

libs/labelbox/src/labelbox/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,6 @@
4343
from labelbox.schema.identifiable import UniqueId, GlobalKey
4444
from labelbox.schema.ontology_kind import OntologyKind
4545
from labelbox.schema.project_overview import ProjectOverview, ProjectOverviewDetailed
46-
from labelbox.schema.labeling_service import LabelingService, LabelingServiceStatus
46+
from labelbox.schema.labeling_service import LabelingService
47+
from labelbox.schema.labeling_service_dashboard import LabelingServiceDashboard
48+
from labelbox.schema.labeling_service_status import LabelingServiceStatus

libs/labelbox/src/labelbox/client.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
from labelbox.schema.label_score import LabelScore
5757
from labelbox.schema.ontology_kind import (OntologyKind, EditorTaskTypeMapper,
5858
EditorTaskType)
59+
from labelbox.schema.labeling_service_dashboard import LabelingServiceDashboard
5960

6061
logger = logging.getLogger(__name__)
6162

@@ -2405,3 +2406,21 @@ def upsert_label_feedback(self, label_id: str, feedback: str,
24052406
labelbox.LabelScore(name=x['name'], score=x['score'])
24062407
for x in scores_raw
24072408
]
2409+
2410+
def get_labeling_service_dashboards(
2411+
self,
2412+
after: Optional[str] = None,
2413+
search_query: Optional[List[Dict]] = None,
2414+
) -> PaginatedCollection:
2415+
"""
2416+
Get all labeling service dashboards for a given org.
2417+
2418+
Optional parameters:
2419+
after: The cursor to use for pagination.
2420+
where: A filter to apply to the query.
2421+
2422+
NOTE: support for after and search_query are not yet implemented.
2423+
"""
2424+
return LabelingServiceDashboard.get_all(self,
2425+
after,
2426+
search_query=search_query)

libs/labelbox/src/labelbox/schema/labeling_service.py

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from datetime import datetime
2-
from enum import Enum
32
import json
43
from typing import Any
54
from typing_extensions import Annotated
@@ -8,22 +7,12 @@
87

98
from labelbox.pydantic_compat import BaseModel, Field
109
from labelbox.utils import _CamelCaseMixin
10+
from labelbox.schema.labeling_service_dashboard import LabelingServiceDashboard
11+
from labelbox.schema.labeling_service_status import LabelingServiceStatus
1112

1213
Cuid = Annotated[str, Field(min_length=25, max_length=25)]
1314

1415

15-
class LabelingServiceStatus(Enum):
16-
""""
17-
The status of the labeling service.
18-
"""
19-
Accepted = 'ACCEPTED'
20-
Calibration = 'CALIBRATION'
21-
Complete = 'COMPLETE'
22-
Production = 'PRODUCTION'
23-
Requested = 'REQUESTED'
24-
SetUp = 'SET_UP'
25-
26-
2716
class LabelingService(BaseModel):
2817
"""
2918
Labeling service for a project. This is a service that can be requested to label data for a project.
@@ -66,6 +55,34 @@ def start(cls, client, project_id: Cuid) -> 'LabelingService':
6655
raise Exception("Failed to start labeling service")
6756
return cls.get(client, project_id)
6857

58+
@classmethod
59+
def get(cls, client, project_id: Cuid) -> 'LabelingService':
60+
"""
61+
Returns the labeling service associated with the project.
62+
63+
Raises:
64+
ResourceNotFoundError: If the project does not have a labeling service.
65+
"""
66+
query = """
67+
query GetProjectBoostWorkforcePyApi($projectId: ID!) {
68+
projectBoostWorkforce(data: { projectId: $projectId }) {
69+
id
70+
projectId
71+
createdAt
72+
updatedAt
73+
createdById
74+
status
75+
}
76+
}
77+
"""
78+
result = client.execute(query, {"projectId": project_id})
79+
if result["projectBoostWorkforce"] is None:
80+
raise ResourceNotFoundError(
81+
message="The project does not have a labeling service.")
82+
data = result["projectBoostWorkforce"]
83+
data["client"] = client
84+
return LabelingService(**data)
85+
6986
def request(self) -> 'LabelingService':
7087
"""
7188
Creates a request to labeling service to start labeling for the project.
@@ -124,30 +141,11 @@ def getOrCreate(cls, client, project_id: Cuid) -> 'LabelingService':
124141
except ResourceNotFoundError:
125142
return cls.start(client, project_id)
126143

127-
@classmethod
128-
def get(cls, client, project_id: Cuid) -> 'LabelingService':
144+
def dashboard(self) -> LabelingServiceDashboard:
129145
"""
130-
Returns the labeling service associated with the project.
146+
Returns the dashboard for the labeling service associated with the project.
131147
132148
Raises:
133149
ResourceNotFoundError: If the project does not have a labeling service.
134150
"""
135-
query = """
136-
query GetProjectBoostWorkforcePyApi($projectId: ID!) {
137-
projectBoostWorkforce(data: { projectId: $projectId }) {
138-
id
139-
projectId
140-
createdAt
141-
updatedAt
142-
createdById
143-
status
144-
}
145-
}
146-
"""
147-
result = client.execute(query, {"projectId": project_id})
148-
if result["projectBoostWorkforce"] is None:
149-
raise ResourceNotFoundError(
150-
message="The project does not have a labeling service.")
151-
data = result["projectBoostWorkforce"]
152-
data["client"] = client
153-
return LabelingService(**data)
151+
return LabelingServiceDashboard.get(self.client, self.project_id)
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
from string import Template
2+
from datetime import datetime
3+
from typing import Any, Dict, List, Optional, Union
4+
5+
from labelbox.exceptions import ResourceNotFoundError
6+
from labelbox.pagination import PaginatedCollection
7+
from labelbox.pydantic_compat import BaseModel, root_validator, Field
8+
from labelbox.utils import _CamelCaseMixin
9+
from labelbox.schema.labeling_service_status import LabelingServiceStatus
10+
11+
GRAPHQL_QUERY_SELECTIONS = """
12+
id
13+
name
14+
# serviceType
15+
# createdAt
16+
# updatedAt
17+
# createdById
18+
boostStatus
19+
dataRowsCount
20+
dataRowsInReviewCount
21+
dataRowsInReworkCount
22+
dataRowsDoneCount
23+
"""
24+
25+
26+
class LabelingServiceDashboard(BaseModel):
27+
"""
28+
Represent labeling service data for a project
29+
30+
Attributes:
31+
id (str): project id
32+
name (str): project name
33+
status (LabelingServiceStatus): status of the labeling service
34+
tasks_completed (int): number of data rows completed
35+
tasks_remaining (int): number of data rows that have not started
36+
client (Any): labelbox client
37+
"""
38+
id: str = Field(frozen=True)
39+
name: str = Field(frozen=True)
40+
service_type: Optional[str] = Field(frozen=True, default=None)
41+
created_at: Optional[datetime] = Field(frozen=True, default=None)
42+
updated_at: Optional[datetime] = Field(frozen=True, default=None)
43+
created_by_id: Optional[str] = Field(frozen=True, default=None)
44+
status: LabelingServiceStatus = Field(frozen=True,
45+
default=LabelingServiceStatus.Missing)
46+
data_rows_count: int = Field(frozen=True)
47+
data_rows_in_review_count: int = Field(frozen=True)
48+
data_rows_in_rework_count: int = Field(frozen=True)
49+
data_rows_done_count: int = Field(frozen=True)
50+
51+
client: Any # type Any to avoid circular import from client
52+
53+
def __init__(self, **kwargs):
54+
super().__init__(**kwargs)
55+
if not self.client.enable_experimental:
56+
raise RuntimeError(
57+
"Please enable experimental in client to use LabelingService")
58+
59+
@property
60+
def tasks_completed(self):
61+
return self.data_rows_done_count
62+
63+
@property
64+
def tasks_remaining(self):
65+
return self.data_rows_count - self.data_rows_done_count
66+
67+
class Config(_CamelCaseMixin.Config):
68+
...
69+
70+
@classmethod
71+
def get(cls, client, project_id: str) -> 'LabelingServiceDashboard':
72+
"""
73+
Returns the labeling service associated with the project.
74+
75+
Raises:
76+
ResourceNotFoundError: If the project does not have a labeling service.
77+
"""
78+
query = f"""
79+
query GetProjectByIdPyApi($id: ID!) {{
80+
getProjectById(input: {{id: $id}}) {{
81+
{GRAPHQL_QUERY_SELECTIONS}
82+
}}
83+
}}
84+
"""
85+
result = client.execute(query, {"id": project_id}, experimental=True)
86+
if result["getProjectById"] is None:
87+
raise ResourceNotFoundError(
88+
message="The project does not have a labeling service.")
89+
data = result["getProjectById"]
90+
data["client"] = client
91+
return cls(**data)
92+
93+
@classmethod
94+
def get_all(
95+
cls,
96+
client,
97+
after: Optional[str] = None,
98+
search_query: Optional[List[Dict]] = None,
99+
) -> PaginatedCollection:
100+
template = Template(
101+
"""query SearchProjectsPyApi($$first: Int, $$from: String) {
102+
searchProjects(input: {after: $$from, searchQuery: $search_query, size: $$first})
103+
{
104+
nodes { $labeling_dashboard_selections }
105+
pageInfo { endCursor }
106+
}
107+
}
108+
""")
109+
organization_id = client.get_organization().uid
110+
query_str = template.substitute(
111+
labeling_dashboard_selections=GRAPHQL_QUERY_SELECTIONS,
112+
search_query=
113+
f"[{{type: \"organization\", operator: \"is\", values: [\"{organization_id}\"]}}]"
114+
)
115+
116+
params: Dict[str, Union[str, int]] = {}
117+
if after:
118+
params = {"from": after}
119+
120+
def convert_to_labeling_service_dashboard(client, data):
121+
data['client'] = client
122+
return LabelingServiceDashboard(**data)
123+
124+
return PaginatedCollection(
125+
client=client,
126+
query=query_str,
127+
params=params,
128+
dereferencing=['searchProjects', 'nodes'],
129+
obj_class=convert_to_labeling_service_dashboard,
130+
cursor_path=['searchProjects', 'pageInfo', 'endCursor'],
131+
experimental=True,
132+
)
133+
134+
@root_validator(pre=True)
135+
def convert_boost_status_to_enum(cls, data):
136+
if 'boostStatus' in data:
137+
data['status'] = LabelingServiceStatus(data.pop('boostStatus'))
138+
139+
return data
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from enum import Enum
2+
3+
4+
class LabelingServiceStatus(Enum):
5+
Accepted = 'ACCEPTED'
6+
Calibration = 'CALIBRATION'
7+
Complete = 'COMPLETE'
8+
Production = 'PRODUCTION'
9+
Requested = 'REQUESTED'
10+
SetUp = 'SET_UP'
11+
Missing = None
12+
13+
@classmethod
14+
def is_supported(cls, value):
15+
return isinstance(value, cls)
16+
17+
@classmethod
18+
def _missing_(cls, value) -> 'LabelingServiceStatus':
19+
"""Handle missing null new task types
20+
Handle upper case names for compatibility with
21+
the GraphQL"""
22+
23+
if value is None:
24+
return cls.Missing
25+
26+
for name, member in cls.__members__.items():
27+
if value == name.upper():
28+
return member
29+
30+
return cls.Missing

libs/labelbox/src/labelbox/schema/project.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from urllib.parse import urlparse
1111

1212
from labelbox.schema.labeling_service import LabelingService, LabelingServiceStatus
13+
from labelbox.schema.labeling_service_dashboard import LabelingServiceDashboard
1314
import requests
1415

1516
from labelbox import parser
@@ -1941,6 +1942,15 @@ def get_labeling_service_status(self) -> LabelingServiceStatus:
19411942
"""
19421943
return self.get_labeling_service().status
19431944

1945+
@experimental
1946+
def labeling_service_dashboard(self) -> LabelingServiceDashboard:
1947+
"""Get the labeling service for this project.
1948+
1949+
Returns:
1950+
LabelingService: The labeling service for this project.
1951+
"""
1952+
return LabelingServiceDashboard.get(self.client, self.uid)
1953+
19441954

19451955
class ProjectMember(DbObject):
19461956
user = Relationship.ToOne("User", cache=True)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from labelbox.schema.labeling_service import LabelingServiceStatus
2+
3+
4+
def test_request_labeling_service_moe_offline_project(
5+
rand_gen, offline_chat_evaluation_project, chat_evaluation_ontology,
6+
offline_conversational_data_row):
7+
project = offline_chat_evaluation_project
8+
project.connect_ontology(chat_evaluation_ontology)
9+
10+
project.create_batch(
11+
rand_gen(str),
12+
[offline_conversational_data_row.uid], # sample of data row objects
13+
)
14+
labeling_service_dashboard = project.labeling_service_dashboard()
15+
assert labeling_service_dashboard.status == LabelingServiceStatus.Missing
16+
assert labeling_service_dashboard.tasks_completed == 0
17+
assert labeling_service_dashboard.tasks_remaining == 0
18+
19+
labeling_service_dashboard = [
20+
ld for ld in project.client.get_labeling_service_dashboards()
21+
][0]
22+
assert labeling_service_dashboard.status == LabelingServiceStatus.Missing
23+
assert labeling_service_dashboard.tasks_completed == 0
24+
assert labeling_service_dashboard.tasks_remaining == 0

0 commit comments

Comments
 (0)