Skip to content

Commit ebc3f3c

Browse files
Sihem TchabiSihem Tchabi
authored andcommitted
fix(LAB-4295): add differents methods to handle review and labeling steps
1 parent 5aa9fa1 commit ebc3f3c

File tree

7 files changed

+506
-4
lines changed

7 files changed

+506
-4
lines changed

recipes/frame_dicom_data.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@
318318
"cell_type": "markdown",
319319
"metadata": {},
320320
"source": [
321-
"Finally, let's import the volumes using `appendManyToDataset` (see [link](https://staging.cloud.kili-technology.com/docs/python-graphql-api/python-api/#append_many_to_dataset)). The key argument is `json_content_array`, which is a list of list of strings. Each element is the list of urls or paths pointing to images of the volume considered."
321+
"Finally, let's import the volumes using `appendManyToDataset`. The key argument is `json_content_array`, which is a list of list of strings. Each element is the list of urls or paths pointing to images of the volume considered."
322322
]
323323
},
324324
{

src/kili/adapters/kili_api_gateway/project_workflow/mappers.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22

33
from kili.domain.project import WorkflowStepCreate, WorkflowStepUpdate
44

5-
from .types import ProjectWorkflowDataKiliAPIGatewayInput
5+
from .types import (
6+
AddReviewStepInput,
7+
DeleteStepInput,
8+
ProjectWorkflowDataKiliAPIGatewayInput,
9+
RenameStepInput,
10+
UpdateLabelingStepPropertiesInput,
11+
UpdateReviewStepPropertiesInput,
12+
)
613

714

815
def project_input_mapper(data: ProjectWorkflowDataKiliAPIGatewayInput) -> dict:
@@ -46,3 +53,66 @@ def update_step_mapper(
4653
for gql_key, py_key in mapping.items()
4754
if py_key in data and (data[py_key] is not None or gql_key in null_fields)
4855
}
56+
57+
58+
def add_review_step_input_mapper(data: AddReviewStepInput) -> dict:
59+
"""Build the GraphQL AddReviewStepInput variable."""
60+
result: dict = {
61+
"projectId": data.project_id,
62+
"name": data.name,
63+
"assignees": data.assignees,
64+
}
65+
if data.step_coverage is not None:
66+
result["stepCoverage"] = data.step_coverage
67+
if data.use_honeypot is not None:
68+
result["useHoneypot"] = data.use_honeypot
69+
if data.send_back_to_step is not None:
70+
result["sendBackStepId"] = data.send_back_to_step
71+
return result
72+
73+
74+
def update_labeling_step_properties_input_mapper(data: UpdateLabelingStepPropertiesInput) -> dict:
75+
"""Build the GraphQL UpdateLabelingStepPropertiesInput variable."""
76+
result: dict = {
77+
"projectId": data.project_id,
78+
"stepId": data.step_id,
79+
}
80+
if data.consensus_coverage is not None:
81+
result["consensusCoverage"] = data.consensus_coverage
82+
if data.number_of_expected_labels_for_consensus is not None:
83+
result["numberOfExpectedLabelsForConsensus"] = data.number_of_expected_labels_for_consensus
84+
if data.use_honeypot is not None:
85+
result["useHoneypot"] = data.use_honeypot
86+
return result
87+
88+
89+
def update_review_step_properties_input_mapper(data: UpdateReviewStepPropertiesInput) -> dict:
90+
"""Build the GraphQL UpdateReviewStepPropertiesInput variable."""
91+
result: dict = {
92+
"projectId": data.project_id,
93+
"stepId": data.step_id,
94+
}
95+
if data.step_coverage is not None:
96+
result["stepCoverage"] = data.step_coverage
97+
if data.send_back_to_step is not None:
98+
result["sendBackStepId"] = data.send_back_to_step
99+
if data.use_honeypot is not None:
100+
result["useHoneypot"] = data.use_honeypot
101+
return result
102+
103+
104+
def delete_step_input_mapper(data: DeleteStepInput) -> dict:
105+
"""Build the GraphQL DeleteStepInput variable."""
106+
return {
107+
"projectId": data.project_id,
108+
"stepId": data.step_id,
109+
}
110+
111+
112+
def rename_step_input_mapper(data: RenameStepInput) -> dict:
113+
"""Build the GraphQL RenameStepInput variable."""
114+
return {
115+
"projectId": data.project_id,
116+
"stepId": data.step_id,
117+
"newName": data.new_name,
118+
}

src/kili/adapters/kili_api_gateway/project_workflow/operations.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,58 @@ def get_steps_query(fragment: str) -> str:
2121
}}
2222
}}
2323
"""
24+
25+
26+
def get_add_review_step_mutation() -> str:
27+
"""Return the GraphQL addReviewStep mutation."""
28+
return """
29+
mutation addReviewStep($input: AddReviewStepInput!) {
30+
data: addReviewStep(input: $input) {
31+
steps{id,name}
32+
}
33+
}
34+
"""
35+
36+
37+
def get_update_labeling_step_properties_mutation() -> str:
38+
"""Return the GraphQL updateLabelingStepProperties mutation."""
39+
return """
40+
mutation updateLabelingStepProperties($input: UpdateLabelingStepPropertiesInput!) {
41+
data: updateLabelingStepProperties(input: $input) {
42+
steps{id,name}
43+
}
44+
}
45+
"""
46+
47+
48+
def get_update_review_step_properties_mutation() -> str:
49+
"""Return the GraphQL updateReviewStepProperties mutation."""
50+
return """
51+
mutation updateReviewStepProperties($input: UpdateReviewStepPropertiesInput!) {
52+
data: updateReviewStepProperties(input: $input) {
53+
steps{id,name}
54+
}
55+
}
56+
"""
57+
58+
59+
def get_delete_step_mutation() -> str:
60+
"""Return the GraphQL deleteStep mutation."""
61+
return """
62+
mutation deleteStep($input: DeleteStepInput!) {
63+
data: deleteStep(input: $input) {
64+
steps{id,name}
65+
}
66+
}
67+
"""
68+
69+
70+
def get_rename_step_mutation() -> str:
71+
"""Return the GraphQL renameStep mutation."""
72+
return """
73+
mutation renameStep($input: RenameStepInput!) {
74+
data: renameStep(input: $input) {
75+
steps{id,name}
76+
}
77+
}
78+
"""

src/kili/adapters/kili_api_gateway/project_workflow/operations_mixin.py

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,31 @@
1212
from kili.domain.types import ListOrTuple
1313
from kili.exceptions import NotFound
1414

15-
from .mappers import project_input_mapper
15+
from .mappers import (
16+
add_review_step_input_mapper,
17+
delete_step_input_mapper,
18+
project_input_mapper,
19+
rename_step_input_mapper,
20+
update_labeling_step_properties_input_mapper,
21+
update_review_step_properties_input_mapper,
22+
)
1623
from .operations import (
24+
get_add_review_step_mutation,
25+
get_delete_step_mutation,
26+
get_rename_step_mutation,
1727
get_steps_query,
28+
get_update_labeling_step_properties_mutation,
1829
get_update_project_workflow_mutation,
30+
get_update_review_step_properties_mutation,
31+
)
32+
from .types import (
33+
AddReviewStepInput,
34+
DeleteStepInput,
35+
ProjectWorkflowDataKiliAPIGatewayInput,
36+
RenameStepInput,
37+
UpdateLabelingStepPropertiesInput,
38+
UpdateReviewStepPropertiesInput,
1939
)
20-
from .types import ProjectWorkflowDataKiliAPIGatewayInput
2140

2241

2342
class ProjectWorkflowOperationMixin(BaseOperationMixin):
@@ -177,3 +196,65 @@ def remove_reviewers_from_step(
177196
)
178197

179198
return removed_emails
199+
200+
def add_review_step(self, data: AddReviewStepInput) -> dict:
201+
"""Add a review step to a project workflow."""
202+
existing_members = ProjectUserQuery(self.graphql_client, self.http_client)(
203+
where=ProjectUserWhere(project_id=data.project_id, status="ACTIVATED", deleted=False),
204+
fields=["role", "user.email", "user.id", "activated"],
205+
options=QueryOptions(None),
206+
)
207+
members_by_email = {m["user"]["email"]: m for m in (existing_members or [])}
208+
assignees_to_add = []
209+
assignees_added = []
210+
assignees_not_added = []
211+
for email in data.assignees:
212+
member = members_by_email.get(email)
213+
if member and member.get("role") != "LABELER":
214+
user_id = member["user"]["id"]
215+
assignees_to_add.append(user_id)
216+
assignees_added.append(email)
217+
else:
218+
assignees_not_added.append(email)
219+
if assignees_not_added:
220+
warnings.warn(
221+
"These emails were not added (not found or can not review): "
222+
+ ", ".join(assignees_not_added)
223+
)
224+
data.assignees = assignees_to_add
225+
variables = {"input": add_review_step_input_mapper(data)}
226+
mutation = get_add_review_step_mutation()
227+
result = self.graphql_client.execute(mutation, variables)
228+
steps = result.get("data", {}).get("steps", [])
229+
return next(step for step in steps if step.get("name") == data.name)
230+
231+
def update_labeling_step_properties(self, data: UpdateLabelingStepPropertiesInput) -> dict:
232+
"""Update properties of a labeling step."""
233+
variables = {"input": update_labeling_step_properties_input_mapper(data)}
234+
mutation = get_update_labeling_step_properties_mutation()
235+
result = self.graphql_client.execute(mutation, variables)
236+
steps = result.get("data", {}).get("steps", [])
237+
return next(step for step in steps if step.get("id") == data.step_id)
238+
239+
def update_review_step_properties(self, data: UpdateReviewStepPropertiesInput) -> dict:
240+
"""Update properties of a review step."""
241+
variables = {"input": update_review_step_properties_input_mapper(data)}
242+
mutation = get_update_review_step_properties_mutation()
243+
result = self.graphql_client.execute(mutation, variables)
244+
steps = result.get("data", {}).get("steps", [])
245+
return next(step for step in steps if step.get("id") == data.step_id)
246+
247+
def delete_step(self, data: DeleteStepInput) -> dict:
248+
"""Delete a step from a project workflow."""
249+
variables = {"input": delete_step_input_mapper(data)}
250+
mutation = get_delete_step_mutation()
251+
result = self.graphql_client.execute(mutation, variables)
252+
return result["data"]
253+
254+
def rename_step(self, data: RenameStepInput) -> dict:
255+
"""Rename a step in a project workflow."""
256+
variables = {"input": rename_step_input_mapper(data)}
257+
mutation = get_rename_step_mutation()
258+
result = self.graphql_client.execute(mutation, variables)
259+
steps = result.get("data", {}).get("steps", [])
260+
return next(step for step in steps if step.get("id") == data.step_id)

src/kili/adapters/kili_api_gateway/project_workflow/types.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,56 @@ class ProjectWorkflowDataKiliAPIGatewayInput:
1515
update_steps: Optional[list[WorkflowStepUpdate]]
1616
delete_steps: Optional[list[str]]
1717
null_fields: frozenset[str] = frozenset()
18+
19+
20+
@dataclass
21+
class AddReviewStepInput:
22+
"""Input data for adding a review step to a project workflow."""
23+
24+
project_id: str
25+
name: str
26+
assignees: list[str]
27+
step_coverage: int | None = None
28+
use_honeypot: bool | None = None
29+
send_back_to_step: str | None = None
30+
31+
32+
@dataclass
33+
class UpdateLabelingStepPropertiesInput:
34+
"""Input data for updating labeling step properties."""
35+
36+
project_id: str
37+
step_id: str
38+
assignees: list[str] | None = None
39+
consensus_coverage: int | None = None
40+
number_of_expected_labels_for_consensus: int | None = None
41+
use_honeypot: bool | None = None
42+
43+
44+
@dataclass
45+
class UpdateReviewStepPropertiesInput:
46+
"""Input data for updating review step properties."""
47+
48+
project_id: str
49+
step_id: str
50+
assignees: list[str] | None = None
51+
step_coverage: int | None = None
52+
send_back_to_step: str | None = None
53+
use_honeypot: bool | None = None
54+
55+
56+
@dataclass
57+
class DeleteStepInput:
58+
"""Input data for deleting a step from a project workflow."""
59+
60+
project_id: str
61+
step_id: str
62+
63+
64+
@dataclass
65+
class RenameStepInput:
66+
"""Input data for renaming a step in a project workflow."""
67+
68+
project_id: str
69+
step_id: str
70+
new_name: str

0 commit comments

Comments
 (0)