diff --git a/darwin/future/core/items/assign_items.py b/darwin/future/core/items/assign_items.py new file mode 100644 index 000000000..20520a5ab --- /dev/null +++ b/darwin/future/core/items/assign_items.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +from darwin.future.core.client import ClientCore +from darwin.future.core.types.common import JSONDict, JSONType + + +def assign_items( + client: ClientCore, + team_slug: str, + dataset_ids: int | list[int], + assignee_id: int, + workflow_id: str, + filters: JSONDict, +) -> JSONType: + """ + Assign a user to all items matched by filters. + + Args: + client (ClientCore): The Darwin Core client. + team_slug (str): The team slug. + dataset_ids (int | list[int]): The dataset ids. + assignee_id (int): The user id to assign. + workflow_id (str): The workflow id that selected items have to belong to. + filters Dict[str, UnknownType]: The parameters of the filter. + + Returns: + JSONType: The response data. + """ + assert ( + filters + ), "No parameters provided, please provide at least one non-dataset id filter" + payload = { + "filters": { + "dataset_ids": dataset_ids + if isinstance(dataset_ids, list) + else [dataset_ids], + **filters, + }, + "assignee_id": assignee_id, + "workflow_id": workflow_id, + } + + return client.post(f"/v2/teams/{team_slug}/items/assign", data=payload) diff --git a/darwin/future/meta/objects/item.py b/darwin/future/meta/objects/item.py index f219d8aee..3c195c8b4 100644 --- a/darwin/future/meta/objects/item.py +++ b/darwin/future/meta/objects/item.py @@ -4,6 +4,7 @@ from uuid import UUID from darwin.future.core.items.archive_items import archive_list_of_items +from darwin.future.core.items.assign_items import assign_items from darwin.future.core.items.delete_items import delete_list_of_items from darwin.future.core.items.move_items_to_folder import move_list_of_items_to_folder from darwin.future.core.items.restore_items import restore_list_of_items @@ -129,6 +130,31 @@ def set_layout(self, layout: ItemLayout) -> None: filters = {"item_ids": [str(self.id)]} set_item_layout(self.client, team_slug, dataset_id, layout, filters) + def assign(self, assignee_id: int, workflow_id: str | None = None) -> None: + if not assignee_id: + raise ValueError("Must specify assignee to assign items to") + if not workflow_id: + # if workflow_id is not specified, get it from the meta_params + # this will be present in the case of a workflow object + if "workflow_id" in self.meta_params: + workflow_id = str(self.meta_params["workflow_id"]) + else: + raise ValueError("Must specify workflow_id to set items to") + assert isinstance(workflow_id, str) + team_slug, dataset_id = ( + self.meta_params["team_slug"], + self.meta_params["dataset_id"] + if "dataset_id" in self.meta_params + else self.meta_params["dataset_ids"], + ) + assert isinstance(team_slug, str) + + dataset_id = cast(Union[int, List[int]], dataset_id) + filters = {"item_ids": [str(self.id)]} + assign_items( + self.client, team_slug, dataset_id, assignee_id, workflow_id, filters + ) + def tag(self, tag_id: int) -> None: team_slug, dataset_id = ( self.meta_params["team_slug"], diff --git a/darwin/future/meta/queries/item.py b/darwin/future/meta/queries/item.py index 58e79c0bc..a8b2edf78 100644 --- a/darwin/future/meta/queries/item.py +++ b/darwin/future/meta/queries/item.py @@ -4,6 +4,7 @@ from typing import Dict, Protocol from darwin.future.core.items.archive_items import archive_list_of_items +from darwin.future.core.items.assign_items import assign_items from darwin.future.core.items.delete_items import delete_list_of_items from darwin.future.core.items.get import list_items from darwin.future.core.items.move_items_to_folder import move_list_of_items_to_folder @@ -249,6 +250,40 @@ def untag(self, tag_id: int) -> None: filters = {"item_ids": [str(item) for item in ids]} untag_items(self.client, team_slug, dataset_ids, tag_id, filters) + def assign(self, assignee_id: int, workflow_id: str | None = None) -> None: + if not assignee_id: + raise ValueError("Must specify assignee to assign items to") + + if "team_slug" not in self.meta_params: + raise ValueError("Must specify team_slug to query items") + if ( + "dataset_ids" not in self.meta_params + and "dataset_id" not in self.meta_params + ): + raise ValueError("Must specify dataset_ids to query items") + if not workflow_id: + # if workflow_id is not specified, get it from the meta_params + # this will be present in the case of a workflow object + if "workflow_id" in self.meta_params: + workflow_id = str(self.meta_params["workflow_id"]) + else: + raise ValueError("Must specify workflow_id to set items to") + assert isinstance(workflow_id, str) + + dataset_ids = ( + self.meta_params["dataset_ids"] + if "dataset_ids" in self.meta_params + else self.meta_params["dataset_id"] + ) + team_slug = self.meta_params["team_slug"] + self.collect_all() + ids = [item.id for item in self] + filters = {"item_ids": [str(item) for item in ids]} + + assign_items( + self.client, team_slug, dataset_ids, assignee_id, workflow_id, filters + ) + def set_stage( self, stage_or_stage_id: hasStage | str, workflow_id: str | None = None ) -> None: diff --git a/darwin/future/tests/core/items/test_assign_items.py b/darwin/future/tests/core/items/test_assign_items.py new file mode 100644 index 000000000..bc9cbe08f --- /dev/null +++ b/darwin/future/tests/core/items/test_assign_items.py @@ -0,0 +1,99 @@ +import pytest +import responses + +from darwin.future.core.client import ClientCore +from darwin.future.core.items.assign_items import assign_items +from darwin.future.exceptions import BadRequest +from darwin.future.tests.core.fixtures import * + + +@responses.activate +def test_assign_items(base_client: ClientCore) -> None: + team_slug = "test-team" + dataset_ids = [1, 2, 3] + assignee_id = 123456 + workflow_id = "123456" + item_ids = [ + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002", + ] + filters = {"item_ids": item_ids} + + responses.add( + responses.POST, + base_client.config.api_endpoint + "v2/teams/test-team/items/assign", + json={"created_commands": 1}, + status=200, + ) + + response = assign_items( + client=base_client, + team_slug=team_slug, + dataset_ids=dataset_ids, + assignee_id=assignee_id, + workflow_id=workflow_id, + filters=filters, + ) + + assert response == {"created_commands": 1} + + +@responses.activate +def test_assign_items_filters_error(base_client: ClientCore) -> None: + team_slug = "test-team" + dataset_ids = [1, 2, 3] + assignee_id = 123456 + workflow_id = "123456" + filters = {} + + responses.add( + responses.POST, + base_client.config.api_endpoint + "v2/teams/test-team/items/assign", + json={"created_commands": 1}, + status=200, + ) + + with pytest.raises(AssertionError) as excinfo: + assign_items( + client=base_client, + team_slug=team_slug, + dataset_ids=dataset_ids, + assignee_id=assignee_id, + workflow_id=workflow_id, + filters=filters, + ) + (msg,) = excinfo.value.args + assert ( + msg + == "No parameters provided, please provide at least one non-dataset id filter" + ) + + +@responses.activate +def test_assign_items_bad_request_error(base_client: ClientCore) -> None: + team_slug = "test-team" + dataset_ids = [1, 2, 3] + assignee_id = 123456 + workflow_id = "123456" + item_ids = [ + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000002", + ] + filters = {"item_ids": item_ids} + + responses.add( + responses.POST, + base_client.config.api_endpoint + "v2/teams/test-team/items/assign", + json={"error": "Bad Request"}, + status=400, + ) + + with pytest.raises(BadRequest): + assign_items( + client=base_client, + team_slug=team_slug, + dataset_ids=dataset_ids, + assignee_id=assignee_id, + workflow_id=workflow_id, + filters=filters, + ) diff --git a/darwin/future/tests/meta/objects/test_itemmeta.py b/darwin/future/tests/meta/objects/test_itemmeta.py index 74f70ebc6..946b13c16 100644 --- a/darwin/future/tests/meta/objects/test_itemmeta.py +++ b/darwin/future/tests/meta/objects/test_itemmeta.py @@ -362,3 +362,30 @@ def test_set_stage(item: Item) -> None: json={}, ) item.set_stage(stage_id, workflow_id) + + +def test_assign(item: Item) -> None: + with responses.RequestsMock() as rsps: + team_slug = item.meta_params["team_slug"] + dataset_id = item.meta_params["dataset_id"] + assignee_id = 123456 + workflow_id = "123456" + rsps.add( + rsps.POST, + item.client.config.api_endpoint + f"v2/teams/{team_slug}/items/assign", + status=200, + match=[ + json_params_matcher( + { + "filters": { + "item_ids": [str(item.id)], + "dataset_ids": [dataset_id], + }, + "assignee_id": assignee_id, + "workflow_id": workflow_id, + } + ) + ], + json={}, + ) + item.assign(assignee_id, workflow_id)