diff --git a/recipes/frame_dicom_data.ipynb b/recipes/frame_dicom_data.ipynb index 6ce7f1bfd..6ab1a65a3 100644 --- a/recipes/frame_dicom_data.ipynb +++ b/recipes/frame_dicom_data.ipynb @@ -318,7 +318,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "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." + "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." ] }, { diff --git a/src/kili/adapters/kili_api_gateway/project_workflow/common.py b/src/kili/adapters/kili_api_gateway/project_workflow/common.py new file mode 100644 index 000000000..c7de60200 --- /dev/null +++ b/src/kili/adapters/kili_api_gateway/project_workflow/common.py @@ -0,0 +1,25 @@ +"""Project Workflow gateway common.""" + +import warnings + + +def get_assignees_to_add_ids(existing_members: list[dict], assignees: list[str]) -> list: + """Get list of assignees ids to add.""" + members_by_email = {m["user"]["email"]: m for m in (existing_members or [])} + assignees_to_add = [] + assignees_added = [] + assignees_not_added = [] + for email in assignees: + member = members_by_email.get(email) + if member and member.get("role") != "LABELER": + user_id = member["user"]["id"] + assignees_to_add.append(user_id) + assignees_added.append(email) + else: + assignees_not_added.append(email) + if assignees_not_added: + warnings.warn( + "These emails were not added (not found or can not review): " + + ", ".join(assignees_not_added) + ) + return assignees_to_add diff --git a/src/kili/adapters/kili_api_gateway/project_workflow/mappers.py b/src/kili/adapters/kili_api_gateway/project_workflow/mappers.py index c557b75c4..46997f444 100644 --- a/src/kili/adapters/kili_api_gateway/project_workflow/mappers.py +++ b/src/kili/adapters/kili_api_gateway/project_workflow/mappers.py @@ -2,7 +2,14 @@ from kili.domain.project import WorkflowStepCreate, WorkflowStepUpdate -from .types import ProjectWorkflowDataKiliAPIGatewayInput +from .types import ( + AddReviewStepInput, + DeleteStepInput, + ProjectWorkflowDataKiliAPIGatewayInput, + RenameStepInput, + UpdateLabelingStepPropertiesInput, + UpdateReviewStepPropertiesInput, +) def project_input_mapper(data: ProjectWorkflowDataKiliAPIGatewayInput) -> dict: @@ -46,3 +53,68 @@ def update_step_mapper( for gql_key, py_key in mapping.items() if py_key in data and (data[py_key] is not None or gql_key in null_fields) } + + +def add_review_step_input_mapper(data: AddReviewStepInput) -> dict: + """Build the GraphQL AddReviewStepInput variable.""" + result: dict = { + "projectId": data.project_id, + "name": data.step_name, + "assignees": data.assignees, + } + if data.step_coverage is not None: + result["stepCoverage"] = data.step_coverage + if data.use_honeypot is not None: + result["useHoneypot"] = data.use_honeypot + if data.send_back_to_step is not None: + result["sendBackStepId"] = data.send_back_to_step + return result + + +def update_labeling_step_properties_input_mapper(data: UpdateLabelingStepPropertiesInput) -> dict: + """Build the GraphQL UpdateLabelingStepPropertiesInput variable.""" + result: dict = { + "projectId": data.project_id, + "stepId": data.step_id, + } + if data.consensus_coverage is not None: + result["consensusCoverage"] = data.consensus_coverage + if data.number_of_expected_labels_for_consensus is not None: + result["numberOfExpectedLabelsForConsensus"] = data.number_of_expected_labels_for_consensus + if data.use_honeypot is not None: + result["useHoneypot"] = data.use_honeypot + return result + + +def update_review_step_properties_input_mapper(data: UpdateReviewStepPropertiesInput) -> dict: + """Build the GraphQL UpdateReviewStepPropertiesInput variable.""" + result: dict = { + "projectId": data.project_id, + "stepId": data.step_id, + } + if data.assignees is not None: + result["assignees"] = data.assignees + if data.step_coverage is not None: + result["stepCoverage"] = data.step_coverage + if data.send_back_to_step is not None: + result["sendBackStepId"] = data.send_back_to_step + if data.use_honeypot is not None: + result["useHoneypot"] = data.use_honeypot + return result + + +def delete_step_input_mapper(data: DeleteStepInput) -> dict: + """Build the GraphQL DeleteStepInput variable.""" + return { + "projectId": data.project_id, + "stepId": data.step_id, + } + + +def rename_step_input_mapper(data: RenameStepInput) -> dict: + """Build the GraphQL RenameStepInput variable.""" + return { + "projectId": data.project_id, + "stepId": data.step_id, + "newName": data.new_name, + } diff --git a/src/kili/adapters/kili_api_gateway/project_workflow/operations.py b/src/kili/adapters/kili_api_gateway/project_workflow/operations.py index 8eb5bee91..a86bb8275 100644 --- a/src/kili/adapters/kili_api_gateway/project_workflow/operations.py +++ b/src/kili/adapters/kili_api_gateway/project_workflow/operations.py @@ -21,3 +21,58 @@ def get_steps_query(fragment: str) -> str: }} }} """ + + +def get_add_review_step_mutation() -> str: + """Return the GraphQL addReviewStep mutation.""" + return """ + mutation addReviewStep($input: AddReviewStepInput!) { + data: addReviewStep(input: $input) { + steps{id,name} + } + } + """ + + +def get_update_labeling_step_properties_mutation() -> str: + """Return the GraphQL updateLabelingStepProperties mutation.""" + return """ + mutation updateLabelingStepProperties($input: UpdateLabelingStepPropertiesInput!) { + data: updateLabelingStepProperties(input: $input) { + steps{id,name} + } + } + """ + + +def get_update_review_step_properties_mutation() -> str: + """Return the GraphQL updateReviewStepProperties mutation.""" + return """ + mutation updateReviewStepProperties($input: UpdateReviewStepPropertiesInput!) { + data: updateReviewStepProperties(input: $input) { + steps{id,name} + } + } + """ + + +def get_delete_step_mutation() -> str: + """Return the GraphQL deleteStep mutation.""" + return """ + mutation deleteStep($input: DeleteStepInput!) { + data: deleteStep(input: $input) { + steps{id,name} + } + } + """ + + +def get_rename_step_mutation() -> str: + """Return the GraphQL renameStep mutation.""" + return """ + mutation renameStep($input: RenameStepInput!) { + data: renameStep(input: $input) { + steps{id,name} + } + } + """ diff --git a/src/kili/adapters/kili_api_gateway/project_workflow/operations_mixin.py b/src/kili/adapters/kili_api_gateway/project_workflow/operations_mixin.py index 24d54c38c..dfbf336bf 100644 --- a/src/kili/adapters/kili_api_gateway/project_workflow/operations_mixin.py +++ b/src/kili/adapters/kili_api_gateway/project_workflow/operations_mixin.py @@ -12,12 +12,32 @@ from kili.domain.types import ListOrTuple from kili.exceptions import NotFound -from .mappers import project_input_mapper +from .common import get_assignees_to_add_ids +from .mappers import ( + add_review_step_input_mapper, + delete_step_input_mapper, + project_input_mapper, + rename_step_input_mapper, + update_labeling_step_properties_input_mapper, + update_review_step_properties_input_mapper, +) from .operations import ( + get_add_review_step_mutation, + get_delete_step_mutation, + get_rename_step_mutation, get_steps_query, + get_update_labeling_step_properties_mutation, get_update_project_workflow_mutation, + get_update_review_step_properties_mutation, +) +from .types import ( + AddReviewStepInput, + DeleteStepInput, + ProjectWorkflowDataKiliAPIGatewayInput, + RenameStepInput, + UpdateLabelingStepPropertiesInput, + UpdateReviewStepPropertiesInput, ) -from .types import ProjectWorkflowDataKiliAPIGatewayInput class ProjectWorkflowOperationMixin(BaseOperationMixin): @@ -72,7 +92,7 @@ def list_activated_project_users(self, project_id: str) -> list[dict]: return list( ProjectUserQuery(self.graphql_client, self.http_client)( where=where, - fields=["role", "user.id"], + fields=["role", "user.id", "user.email"], options=QueryOptions(disable_tqdm=True), ) ) @@ -177,3 +197,61 @@ def remove_reviewers_from_step( ) return removed_emails + + def add_review_step(self, data: AddReviewStepInput) -> dict: + """Add a review step to a project workflow.""" + existing_members = self.list_activated_project_users(data.project_id) + assignees_to_add = get_assignees_to_add_ids(existing_members, data.assignees) + data.assignees = assignees_to_add + variables = {"input": add_review_step_input_mapper(data)} + mutation = get_add_review_step_mutation() + result = self.graphql_client.execute(mutation, variables) + steps = result.get("data", {}).get("steps", []) + step = next((step for step in steps if step.get("name") == data.step_name), None) + if not step: + raise NotFound(f"Could not find the stepId of the step {data.step_name}.") + return step + + def update_labeling_step_properties(self, data: UpdateLabelingStepPropertiesInput) -> dict: + """Update properties of a labeling step.""" + variables = {"input": update_labeling_step_properties_input_mapper(data)} + mutation = get_update_labeling_step_properties_mutation() + result = self.graphql_client.execute(mutation, variables) + steps = result.get("data", {}).get("steps", []) + step = next((step for step in steps if step.get("id") == data.step_id), None) + if not step: + raise NotFound(f"Could not find the step with id {data.step_id}.") + return step + + def update_review_step_properties(self, data: UpdateReviewStepPropertiesInput) -> dict: + """Update properties of a review step.""" + if data.assignees is not None: + existing_members = self.list_activated_project_users(data.project_id) + assignees_to_add = get_assignees_to_add_ids(existing_members, data.assignees) + data.assignees = assignees_to_add + variables = {"input": update_review_step_properties_input_mapper(data)} + mutation = get_update_review_step_properties_mutation() + result = self.graphql_client.execute(mutation, variables) + steps = result.get("data", {}).get("steps", []) + step = next((step for step in steps if step.get("id") == data.step_id), None) + if not step: + raise NotFound(f"Could not find the step with id {data.step_id}.") + return step + + def delete_step(self, data: DeleteStepInput) -> dict: + """Delete a step from a project workflow.""" + variables = {"input": delete_step_input_mapper(data)} + mutation = get_delete_step_mutation() + result = self.graphql_client.execute(mutation, variables) + return result["data"] + + def rename_step(self, data: RenameStepInput) -> dict: + """Rename a step in a project workflow.""" + variables = {"input": rename_step_input_mapper(data)} + mutation = get_rename_step_mutation() + result = self.graphql_client.execute(mutation, variables) + steps = result.get("data", {}).get("steps", []) + step = next((step for step in steps if step.get("id") == data.step_id), None) + if not step: + raise NotFound(f"Could not find the step with id {data.step_id}.") + return step diff --git a/src/kili/adapters/kili_api_gateway/project_workflow/types.py b/src/kili/adapters/kili_api_gateway/project_workflow/types.py index d396dde2e..7c684e6f0 100644 --- a/src/kili/adapters/kili_api_gateway/project_workflow/types.py +++ b/src/kili/adapters/kili_api_gateway/project_workflow/types.py @@ -15,3 +15,55 @@ class ProjectWorkflowDataKiliAPIGatewayInput: update_steps: Optional[list[WorkflowStepUpdate]] delete_steps: Optional[list[str]] null_fields: frozenset[str] = frozenset() + + +@dataclass +class AddReviewStepInput: + """Input data for adding a review step to a project workflow.""" + + project_id: str + step_name: str + assignees: list[str] + step_coverage: int | None = None + use_honeypot: bool | None = None + send_back_to_step: str | None = None + + +@dataclass +class UpdateLabelingStepPropertiesInput: + """Input data for updating labeling step properties.""" + + project_id: str + step_id: str + consensus_coverage: int | None = None + number_of_expected_labels_for_consensus: int | None = None + use_honeypot: bool | None = None + + +@dataclass +class UpdateReviewStepPropertiesInput: + """Input data for updating review step properties.""" + + project_id: str + step_id: str + assignees: list[str] | None = None + step_coverage: int | None = None + send_back_to_step: str | None = None + use_honeypot: bool | None = None + + +@dataclass +class DeleteStepInput: + """Input data for deleting a step from a project workflow.""" + + project_id: str + step_id: str + + +@dataclass +class RenameStepInput: + """Input data for renaming a step in a project workflow.""" + + project_id: str + step_id: str + new_name: str diff --git a/src/kili/presentation/client/project_workflow.py b/src/kili/presentation/client/project_workflow.py index ab13b9136..3a1cc1baa 100644 --- a/src/kili/presentation/client/project_workflow.py +++ b/src/kili/presentation/client/project_workflow.py @@ -144,3 +144,136 @@ def copy_workflow_from_project( source_project_id=ProjectId(source_project_id), destination_project_id=ProjectId(destination_project_id), ) + + @typechecked + def add_review_step( + self, + project_id: str, + step_name: str, + assignees: list[str], + step_coverage: int | None = None, + use_honeypot: bool | None = None, + send_back_to_step: str | None = None, + ) -> dict[str, Any]: + """Add a review step to a project workflow. + + Args: + project_id: Id of the project. + step_name: Name of the new review step. + assignees: List of user emails to assign as reviewers. + step_coverage: Percentage of assets to be reviewed in this step (0-100). + use_honeypot: Whether to use honeypot on this step. + send_back_to_step: Id of the step to send assets back to. + + Returns: + A dict with the created step data (id, name). + """ + return ProjectWorkflowUseCases(self.kili_api_gateway).add_review_step( + project_id=project_id, + step_name=step_name, + assignees=assignees, + step_coverage=step_coverage, + use_honeypot=use_honeypot, + send_back_to_step=send_back_to_step, + ) + + @typechecked + def update_labeling_step_properties( + self, + project_id: str, + step_name: str, + consensus_coverage: int | None = None, + number_of_expected_labels_for_consensus: int | None = None, + use_honeypot: bool | None = None, + ) -> dict[str, Any]: + """Update properties of a labeling step. + + Args: + project_id: Id of the project. + step_name: Name of the labeling step to update. + consensus_coverage: Percentage of assets to be labeled for consensus (0-100). + number_of_expected_labels_for_consensus: Number of expected labels for consensus. + use_honeypot: Whether to use honeypot on this step. + + Returns: + A dict with the updated step data (id, name). + """ + return ProjectWorkflowUseCases(self.kili_api_gateway).update_labeling_step_properties( + project_id=project_id, + step_name=step_name, + consensus_coverage=consensus_coverage, + number_of_expected_labels_for_consensus=number_of_expected_labels_for_consensus, + use_honeypot=use_honeypot, + ) + + @typechecked + def update_review_step_properties( + self, + project_id: str, + step_name: str, + assignees: list[str] | None = None, + step_coverage: int | None = None, + send_back_to_step: str | None = None, + use_honeypot: bool | None = None, + ) -> dict[str, Any]: + """Update properties of a review step. + + Args: + project_id: Id of the project. + step_name: Name of the review step to update. + assignees: List of emails to assign to the step. + step_coverage: Percentage of assets to be reviewed in this step (0-100). + send_back_to_step: Id of the step to send assets back to when rejected. + use_honeypot: Whether to use honeypot on this step. + + Returns: + A dict with the updated step data (id, name). + """ + return ProjectWorkflowUseCases(self.kili_api_gateway).update_review_step_properties( + project_id=project_id, + step_name=step_name, + assignees=assignees, + step_coverage=step_coverage, + send_back_to_step=send_back_to_step, + use_honeypot=use_honeypot, + ) + + @typechecked + def delete_last_step( + self, + project_id: str, + ) -> dict[str, Any]: + """Delete the last review step from a project workflow. + + Args: + project_id: Id of the project. + + Returns: + A dict with the remaining steps. + """ + return ProjectWorkflowUseCases(self.kili_api_gateway).delete_last_step( + project_id=project_id, + ) + + @typechecked + def rename_step( + self, + project_id: str, + step_name: str, + new_name: str, + ) -> dict[str, Any]: + """Rename a step in a project workflow. + + Args: + project_id: Id of the project. + step_name: Name of the step. + new_name: New name for the step. + + Returns: + A dict with the renamed step data (id, name). + """ + return ProjectWorkflowUseCases(self.kili_api_gateway).rename_step( + project_id=project_id, + step_name=step_name, + new_name=new_name, + ) diff --git a/src/kili/use_cases/project_workflow/__init__.py b/src/kili/use_cases/project_workflow/__init__.py index 2e14644d7..2046b4b84 100644 --- a/src/kili/use_cases/project_workflow/__init__.py +++ b/src/kili/use_cases/project_workflow/__init__.py @@ -4,7 +4,12 @@ from typing import Literal, Optional, cast from kili.adapters.kili_api_gateway.project_workflow.types import ( + AddReviewStepInput, + DeleteStepInput, ProjectWorkflowDataKiliAPIGatewayInput, + RenameStepInput, + UpdateLabelingStepPropertiesInput, + UpdateReviewStepPropertiesInput, ) from kili.domain.label import LabelFilters from kili.domain.project import ProjectId, WorkflowStepCreate, WorkflowStepUpdate @@ -289,6 +294,116 @@ def _remap_send_back_step_ids( ) return {} + def add_review_step( + self, + project_id: str, + step_name: str, + assignees: list[str], + step_coverage: int | None = None, + use_honeypot: bool | None = None, + send_back_to_step: str | None = None, + ) -> dict[str, object]: + """Add a review step to a project workflow.""" + if step_coverage and not 0 <= step_coverage <= 100: + raise ValueError("The parameter step_coverage must be between 0 and 100 (included).") + data = AddReviewStepInput( + project_id=project_id, + step_name=step_name, + assignees=assignees, + step_coverage=step_coverage, + use_honeypot=use_honeypot, + send_back_to_step=send_back_to_step, + ) + return self._kili_api_gateway.add_review_step(data) + + def update_labeling_step_properties( + self, + project_id: str, + step_name: str, + consensus_coverage: int | None = None, + number_of_expected_labels_for_consensus: int | None = None, + use_honeypot: bool | None = None, + ) -> dict[str, object]: + """Update properties of a labeling step.""" + if consensus_coverage and not 0 <= consensus_coverage <= 100: + raise ValueError( + "The parameter consensus_coverage must be between 0 and 100 (included)." + ) + steps = self._kili_api_gateway.get_steps( + project_id=ProjectId(project_id), fields=["steps.id", "steps.name"] + ) + step_id = next((step["id"] for step in steps if step["name"] == step_name), None) + if step_id is None: + raise ValueError(f"Step '{step_name}' not found") + data = UpdateLabelingStepPropertiesInput( + project_id=project_id, + step_id=str(step_id), + consensus_coverage=consensus_coverage, + number_of_expected_labels_for_consensus=number_of_expected_labels_for_consensus, + use_honeypot=use_honeypot, + ) + return self._kili_api_gateway.update_labeling_step_properties(data) + + def update_review_step_properties( + self, + project_id: str, + step_name: str, + assignees: list[str] | None = None, + step_coverage: int | None = None, + send_back_to_step: str | None = None, + use_honeypot: bool | None = None, + ) -> dict[str, object]: + """Update properties of a review step.""" + if step_coverage and not 0 <= step_coverage <= 100: + raise ValueError("The parameter step_coverage must be between 0 and 100 (included).") + steps = self._kili_api_gateway.get_steps( + project_id=ProjectId(project_id), fields=["steps.id", "steps.name"] + ) + step_id = next((step["id"] for step in steps if step["name"] == step_name), None) + if step_id is None: + raise ValueError(f"Step '{step_name}' not found") + data = UpdateReviewStepPropertiesInput( + project_id=project_id, + step_id=str(step_id), + assignees=assignees, + step_coverage=step_coverage, + send_back_to_step=send_back_to_step, + use_honeypot=use_honeypot, + ) + return self._kili_api_gateway.update_review_step_properties(data) + + def delete_last_step( + self, + project_id: str, + ) -> dict[str, object]: + """Delete the last review step from a project workflow.""" + steps = self._kili_api_gateway.get_steps( + project_id=ProjectId(project_id), fields=["steps.id", "steps.name"] + ) + if len(steps) <= 2: + raise ValueError( + "Cannot delete the last review step if only one review step is remaining." + ) + step = steps[-1] + data = DeleteStepInput(project_id=project_id, step_id=str(step["id"])) + return self._kili_api_gateway.delete_step(data) + + def rename_step( + self, + project_id: str, + step_name: str, + new_name: str, + ) -> dict[str, object]: + """Rename a step in a project workflow.""" + steps = self._kili_api_gateway.get_steps( + project_id=ProjectId(project_id), fields=["steps.id", "steps.name"] + ) + step_id = next((step["id"] for step in steps if step["name"] == step_name), None) + if step_id is None: + raise ValueError(f"Step '{step_name}' not found") + data = RenameStepInput(project_id=project_id, step_id=str(step_id), new_name=new_name) + return self._kili_api_gateway.rename_step(data) + def _make_create_step(step: dict[str, object], assignees: list[str]) -> WorkflowStepCreate: """Build a WorkflowStepCreate from a source step with destination project assignees. diff --git a/tests/integration/use_cases/test_project_workflow.py b/tests/integration/use_cases/test_project_workflow.py index 758be91ff..a6632ddf4 100644 --- a/tests/integration/use_cases/test_project_workflow.py +++ b/tests/integration/use_cases/test_project_workflow.py @@ -42,3 +42,181 @@ def mocked_update_project_workflow( "updates": [], }, } + + +def test_add_review_step(kili_api_gateway: KiliAPIGateway): + # Given + def mocked_add_review_step(data): + return { + "steps": [ + { + "id": "fake_id", + "name": data.step_name, + }, + ], + } + + kili_api_gateway.add_review_step.side_effect = mocked_add_review_step + + # When + project = ProjectWorkflowUseCases(kili_api_gateway).add_review_step( + project_id=ProjectId("fake_proj_id"), + step_name="test", + assignees=["test+fake@kili-technology.com"], + step_coverage=100, + ) + + # Then + assert project == { + "steps": [ + { + "id": "fake_id", + "name": "test", + }, + ], + } + + +def test_rename_step(kili_api_gateway: KiliAPIGateway): + # Given + kili_api_gateway.get_steps.return_value = [ + {"id": "step_1", "name": "Label"}, + {"id": "step_2", "name": "Old review"}, + ] + + def mocked_rename_step(data): + return { + "steps": [ + {"id": "step_1", "name": "Label"}, + {"id": data.step_id, "name": data.new_name}, + ] + } + + kili_api_gateway.rename_step.side_effect = mocked_rename_step + + # When + result = ProjectWorkflowUseCases(kili_api_gateway).rename_step( + project_id="fake_proj_id", + step_name="Old review", + new_name="New review", + ) + + # Then + assert result == { + "steps": [{"id": "step_1", "name": "Label"}, {"id": "step_2", "name": "New review"}] + } + + +def test_delete_last_step(kili_api_gateway: KiliAPIGateway): + # Given + kili_api_gateway.get_steps.return_value = [ + {"id": "step_1", "name": "Label"}, + {"id": "step_2", "name": "Review 1"}, + {"id": "step_3", "name": "Review 2"}, + ] + + def mocked_delete_step(data): + assert str(data.project_id) == "fake_proj_id" + assert data.step_id == "step_3" + + return {"steps": [{"id": "step_1", "name": "Label"}, {"id": "step_2", "name": "Review 1"}]} + + kili_api_gateway.delete_step.side_effect = mocked_delete_step + + # When + result = ProjectWorkflowUseCases(kili_api_gateway).delete_last_step( + project_id="fake_proj_id", + ) + + # Then + assert result == { + "steps": [{"id": "step_1", "name": "Label"}, {"id": "step_2", "name": "Review 1"}] + } + + +def test_update_labeling_step_properties(kili_api_gateway: KiliAPIGateway): + # Given + kili_api_gateway.get_steps.return_value = [ + {"id": "step_1", "name": "Label"}, + {"id": "step_2", "name": "Review"}, + ] + + def mocked_update_labeling_step_properties(data): + assert str(data.project_id) == "fake_proj_id" + assert data.step_id == "step_1" + assert data.consensus_coverage == 80 + assert data.number_of_expected_labels_for_consensus == 3 + assert data.use_honeypot is True + + return { + "steps": [ + {"id": "step_1", "name": "Label"}, + {"id": "step_2", "name": "Review"}, + ] + } + + kili_api_gateway.update_labeling_step_properties.side_effect = ( + mocked_update_labeling_step_properties + ) + + # When + result = ProjectWorkflowUseCases(kili_api_gateway).update_labeling_step_properties( + project_id="fake_proj_id", + step_name="Label", + consensus_coverage=80, + number_of_expected_labels_for_consensus=3, + use_honeypot=True, + ) + + # Then + assert result == { + "steps": [ + {"id": "step_1", "name": "Label"}, + {"id": "step_2", "name": "Review"}, + ] + } + + +def test_update_review_step_properties(kili_api_gateway: KiliAPIGateway): + # Given + kili_api_gateway.get_steps.return_value = [ + {"id": "step_1", "name": "Label"}, + {"id": "step_2", "name": "Review"}, + ] + + def mocked_update_review_step_properties(data): + assert str(data.project_id) == "fake_proj_id" + assert data.step_id == "step_2" + assert data.assignees == ["test+fake@kili-technology.com"] + assert data.step_coverage == 100 + assert data.send_back_to_step == "Label" + assert data.use_honeypot is False + + return { + "steps": [ + {"id": "step_1", "name": "Label"}, + {"id": "step_2", "name": "Review"}, + ] + } + + kili_api_gateway.update_review_step_properties.side_effect = ( + mocked_update_review_step_properties + ) + + # When + result = ProjectWorkflowUseCases(kili_api_gateway).update_review_step_properties( + project_id="fake_proj_id", + step_name="Review", + assignees=["test+fake@kili-technology.com"], + step_coverage=100, + send_back_to_step="Label", + use_honeypot=False, + ) + + # Then + assert result == { + "steps": [ + {"id": "step_1", "name": "Label"}, + {"id": "step_2", "name": "Review"}, + ] + }