Skip to content

Commit 4d6c363

Browse files
authored
feat(lab-3717): handle update of workflow steps (#1909)
1 parent 95befa2 commit 4d6c363

12 files changed

Lines changed: 144 additions & 11 deletions

File tree

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
load_project_json_fields,
1515
)
1616
from kili.adapters.kili_api_gateway.project.operations import get_projects_query
17+
from kili.adapters.kili_api_gateway.project_workflow.mappers import create_project_workflow_mapper
1718
from kili.core.enums import DemoProjectType, ProjectType
18-
from kili.domain.project import ComplianceTag, InputType, ProjectFilters, ProjectId
19+
from kili.domain.project import ComplianceTag, InputType, ProjectFilters, ProjectId, Workflow
1920
from kili.domain.types import ListOrTuple
2021

2122
from .common import get_project
@@ -46,6 +47,7 @@ def create_project(
4647
project_type: Optional[ProjectType],
4748
compliance_tags: Optional[ListOrTuple[ComplianceTag]],
4849
from_demo_project: Optional[DemoProjectType],
50+
workflow: Optional[Workflow] = None,
4951
) -> ProjectId:
5052
"""Create a project."""
5153
variables = {
@@ -56,6 +58,7 @@ def create_project(
5658
"jsonInterface": json.dumps(json_interface),
5759
"projectType": project_type,
5860
"title": title,
61+
"workflow": create_project_workflow_mapper(workflow) if workflow else None,
5962
}
6063
}
6164
# compliance tags are only available for Kili app > 2.138

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

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""GraphQL payload data mappers for project operations."""
22

3-
from typing import Dict
3+
from typing import Dict, Union
4+
5+
from kili.domain.project import Workflow, WorkflowStepCreate, WorkflowStepUpdate
46

57
from .types import ProjectWorkflowDataKiliAPIGatewayInput
68

@@ -9,7 +11,41 @@ def project_input_mapper(data: ProjectWorkflowDataKiliAPIGatewayInput) -> Dict:
911
"""Build the GraphQL ProjectWorfklowData variable to be sent in an operation."""
1012
return {
1113
"enforceStepSeparation": data.enforce_step_separation,
14+
"steps": {
15+
"creates": [update_step_mapper(step) for step in data.create_steps]
16+
if data.create_steps
17+
else [],
18+
"updates": [update_step_mapper(step) for step in data.update_steps]
19+
if data.update_steps
20+
else [],
21+
"deletes": data.delete_steps if data.delete_steps else [],
22+
},
23+
}
24+
25+
26+
def create_project_workflow_mapper(data: Workflow) -> Dict:
27+
"""Build the GraphQL Workflow variable to be sent in an operation."""
28+
return {
29+
"enforceStepSeparation": data["enforce_step_separation"],
30+
"steps": [update_step_mapper(step) for step in data["steps"]] if data["steps"] else [],
31+
}
32+
33+
34+
def update_step_mapper(data: Union[WorkflowStepCreate, WorkflowStepUpdate]) -> Dict:
35+
"""Build the GraphQL create StepData variable to be sent in an operation."""
36+
step = {
37+
"id": data["id"] if "id" in data else None,
38+
"name": data["name"] if "name" in data else None,
39+
"consensusCoverage": data["consensus_coverage"] if "consensus_coverage" in data else None,
40+
"numberOfExpectedLabelsForConsensus": data["number_of_expected_labels_for_consensus"]
41+
if "number_of_expected_labels_for_consensus" in data
42+
else None,
43+
"order": data["order"] if "order" in data else None,
44+
"stepCoverage": data["step_coverage"] if "step_coverage" in data else None,
45+
"type": data["type"] if "type" in data else None,
46+
"assignees": data["assignees"] if "assignees" in data else None,
1247
}
48+
return {k: v for k, v in step.items() if v is not None}
1349

1450

1551
def step_data_mapper(data: Dict) -> Dict:

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ def update_project_workflow(
2828
"""Update properties in a project workflow."""
2929
project_workflow_input = project_input_mapper(data=project_workflow_data)
3030

31-
fields = tuple(name for name, val in project_workflow_input.items() if val is not None)
31+
fields = tuple(
32+
name
33+
for name, val in project_workflow_input.items()
34+
if val is not None and name != "steps"
35+
)
3236
fragment = fragment_builder(fields)
3337
mutation = get_update_project_workflow_mutation(fragment)
3438

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
"""Types for the ProjectWorkflow-related Kili API gateway functions."""
22

33
from dataclasses import dataclass
4-
from typing import Optional
4+
from typing import List, Optional
5+
6+
from kili.domain.project import WorkflowStepCreate, WorkflowStepUpdate
57

68

79
@dataclass
810
class ProjectWorkflowDataKiliAPIGatewayInput:
911
"""ProjectWorkflow input data for Kili API Gateway."""
1012

1113
enforce_step_separation: Optional[bool]
14+
create_steps: Optional[List[WorkflowStepCreate]]
15+
update_steps: Optional[List[WorkflowStepUpdate]]
16+
delete_steps: Optional[List[str]]

src/kili/domain/project.py

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

33
from dataclasses import dataclass
44
from enum import Enum
5-
from typing import TYPE_CHECKING, Literal, NewType, Optional, TypedDict
5+
from typing import TYPE_CHECKING, List, Literal, NewType, Optional, TypedDict
6+
7+
from typing_extensions import Required
68

79
from .types import ListOrTuple
810

@@ -23,6 +25,39 @@ class ProjectStep(TypedDict, total=True):
2325
name: str
2426

2527

28+
class WorkflowStepCreate(TypedDict, total=False):
29+
"""Project workflow step."""
30+
31+
name: Required[str]
32+
consensus_coverage: Optional[int]
33+
number_of_expected_labels_for_consensus: Optional[int]
34+
order: Required[int]
35+
step_coverage: Optional[int]
36+
type: Required[Literal["DEFAULT", "REVIEW"]]
37+
assignees: Required[List[str]]
38+
39+
40+
class WorkflowStepUpdate(TypedDict, total=False):
41+
"""Project workflow step."""
42+
43+
id: Required[str]
44+
name: Optional[str]
45+
consensus_coverage: Optional[int]
46+
number_of_expected_labels_for_consensus: Optional[int]
47+
order: Optional[int]
48+
step_coverage: Optional[int]
49+
type: Optional[Literal["DEFAULT", "REVIEW"]]
50+
assignees: Optional[List[str]]
51+
52+
53+
@dataclass(frozen=True)
54+
class Workflow(TypedDict, total=True):
55+
"""Workflow step type."""
56+
57+
enforce_step_separation: bool
58+
steps: List[WorkflowStepCreate]
59+
60+
2661
class InputTypeEnum(str, Enum):
2762
"""Input type enum."""
2863

src/kili/presentation/client/project.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from kili.adapters.kili_api_gateway.helpers.queries import QueryOptions
1818
from kili.core.enums import DemoProjectType, ProjectType
19-
from kili.domain.project import ComplianceTag, InputType, ProjectFilters, ProjectId
19+
from kili.domain.project import ComplianceTag, InputType, ProjectFilters, ProjectId, Workflow
2020
from kili.domain.tag import TagId
2121
from kili.domain.types import ListOrTuple
2222
from kili.exceptions import IncompatibleArgumentsError
@@ -47,6 +47,7 @@ def create_project(
4747
tags: Optional[ListOrTuple[str]] = None,
4848
compliance_tags: Optional[ListOrTuple[ComplianceTag]] = None,
4949
from_demo_project: Optional[DemoProjectType] = None,
50+
workflow: Optional[Workflow] = None,
5051
) -> Dict[Literal["id"], str]:
5152
"""Create a project.
5253
@@ -81,6 +82,7 @@ def create_project(
8182
- `DEMO_LLM`
8283
- `DEMO_LLM_INSTR_FOLLOWING`
8384
- `DEMO_SEGMENTATION`
85+
workflow: The workflow settings to use for the project.
8486
8587
Returns:
8688
A dict with the id of the created project.
@@ -113,6 +115,7 @@ def create_project(
113115
project_type=project_type,
114116
compliance_tags=compliance_tags,
115117
from_demo_project=from_demo_project,
118+
workflow=workflow,
116119
)
117120

118121
if tags is not None:

src/kili/presentation/client/project_workflow.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from typeguard import typechecked
66

7-
from kili.domain.project import ProjectId
7+
from kili.domain.project import ProjectId, WorkflowStepCreate, WorkflowStepUpdate
88
from kili.use_cases.project_workflow import ProjectWorkflowUseCases
99

1010
from .base import BaseClientMethods
@@ -18,6 +18,9 @@ def update_project_workflow(
1818
self,
1919
project_id: str,
2020
enforce_step_separation: Optional[bool] = None,
21+
create_steps: Optional[List[WorkflowStepCreate]] = None,
22+
update_steps: Optional[List[WorkflowStepUpdate]] = None,
23+
delete_steps: Optional[List[str]] = None,
2124
) -> Dict[str, Any]:
2225
"""Update properties of a project workflow.
2326
@@ -26,6 +29,9 @@ def update_project_workflow(
2629
enforce_step_separation: Prevents the same user from being assigned to
2730
multiple steps in the workflow for a same asset,
2831
ensuring independent review and labeling processes
32+
create_steps: List of steps to create in the project workflow.
33+
update_steps: List of steps to update in the project workflow.
34+
delete_steps: List of step IDs to delete from the project workflow.
2935
3036
Returns:
3137
A dict with the changed properties which indicates if the mutation was successful,
@@ -34,6 +40,9 @@ def update_project_workflow(
3440
return ProjectWorkflowUseCases(self.kili_api_gateway).update_project_workflow(
3541
project_id=ProjectId(project_id),
3642
enforce_step_separation=enforce_step_separation,
43+
create_steps=create_steps,
44+
update_steps=update_steps,
45+
delete_steps=delete_steps,
3746
)
3847

3948
@typechecked

src/kili/use_cases/project/project.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,14 @@
1212
from kili.adapters.kili_api_gateway.project.mappers import project_data_mapper
1313
from kili.adapters.kili_api_gateway.project.types import ProjectDataKiliAPIGatewayInput
1414
from kili.core.enums import DemoProjectType, ProjectType
15-
from kili.domain.project import ComplianceTag, InputType, ProjectFilters, ProjectId, ProjectStep
15+
from kili.domain.project import (
16+
ComplianceTag,
17+
InputType,
18+
ProjectFilters,
19+
ProjectId,
20+
ProjectStep,
21+
Workflow,
22+
)
1623
from kili.domain.types import ListOrTuple
1724
from kili.exceptions import NotFound
1825
from kili.use_cases.base import BaseUseCases
@@ -32,6 +39,7 @@ def create_project(
3239
project_id: Optional[ProjectId] = None,
3340
input_type: Optional[InputType] = None,
3441
json_interface: Optional[Dict] = None,
42+
workflow: Optional[Workflow] = None,
3543
) -> ProjectId:
3644
"""Create or copy a project if project_id is set."""
3745
if project_id is not None:
@@ -49,6 +57,7 @@ def create_project(
4957
project_type=project_type,
5058
compliance_tags=compliance_tags,
5159
from_demo_project=from_demo_project,
60+
workflow=workflow,
5261
)
5362
if project_copied["instructions"]:
5463
self.update_properties_in_project(
@@ -78,6 +87,7 @@ def create_project(
7887
project_type=project_type,
7988
compliance_tags=compliance_tags,
8089
from_demo_project=from_demo_project,
90+
workflow=workflow,
8191
)
8292

8393
# The project is not immediately available after creation

src/kili/use_cases/project_workflow/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from kili.adapters.kili_api_gateway.project_workflow.types import (
66
ProjectWorkflowDataKiliAPIGatewayInput,
77
)
8-
from kili.domain.project import ProjectId
8+
from kili.domain.project import ProjectId, WorkflowStepCreate, WorkflowStepUpdate
99
from kili.use_cases.base import BaseUseCases
1010

1111

@@ -16,10 +16,16 @@ def update_project_workflow(
1616
self,
1717
project_id: ProjectId,
1818
enforce_step_separation: Optional[bool] = None,
19+
create_steps: Optional[List[WorkflowStepCreate]] = None,
20+
update_steps: Optional[List[WorkflowStepUpdate]] = None,
21+
delete_steps: Optional[List[str]] = None,
1922
) -> Dict[str, object]:
2023
"""Update properties in a project workflow."""
2124
project_workflow_data = ProjectWorkflowDataKiliAPIGatewayInput(
2225
enforce_step_separation=enforce_step_separation,
26+
create_steps=create_steps,
27+
update_steps=update_steps,
28+
delete_steps=delete_steps,
2329
)
2430

2531
return self._kili_api_gateway.update_project_workflow(project_id, project_workflow_data)

tests/integration/presentation/test_project.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def test_when_creating_project_then_it_returns_project_id(mocker: pytest_mock.Mo
2929
"projectType": None,
3030
"title": "fake_title",
3131
"fromDemoProject": None,
32+
"workflow": None,
3233
}
3334
},
3435
)

0 commit comments

Comments
 (0)