diff --git a/rplugin/python3/mrpy/clickup.py b/rplugin/python3/mrpy/clickup.py index 16de73b..faf4f13 100644 --- a/rplugin/python3/mrpy/clickup.py +++ b/rplugin/python3/mrpy/clickup.py @@ -6,10 +6,20 @@ from typing import Any, Callable, Self, TypedDict from urllib.error import HTTPError from .hints import JSONDataMap -from .requests import get, put +from .requests import delete, get, put, post from .env import EnvVar +class TaskRespCheckItemDict(TypedDict): + name: str + resolved: int + + +class TaskRespCheckDict(TypedDict): + name: str + items: list[TaskRespCheckItemDict] + + class TaskRespDict(TypedDict): name: str markdown_description: str @@ -20,7 +30,7 @@ class TaskRespDict(TypedDict): tags: list[dict[str, str]] parent: str | None locations: list[dict[str, str]] - checklists: dict[str, bool] + checklists: list[TaskRespCheckDict] list: dict[str, str] @@ -29,24 +39,25 @@ class ClickupTask: """fields marked with ``repr=False`` will not be pushed for updates""" name: str - markdown_description: str status: str - id: str = field(repr=False) - assignees: list[str] = field(repr=False) - tags: list[str] = field(repr=False) + id: str | None = field(default=None, repr=False) + assignees: list[str] | None = field(default=None, repr=False) + tags: list[str] | None = field(default=None, repr=False) parent_list: str = field(repr=False) - locations: list[str] = field(repr=False) - checklists: dict[str, bool] = field(repr=False) + locations: list[str] | None = field(default=None, repr=False) + checklists: dict[str, dict[str, bool]] | None = field(default=None, repr=False) parent: str | None = field(default=None, repr=False) + markdown_content: str + @classmethod def from_resp_data(cls, resp_data_raw: dict[str, Any]) -> Self: resp_data = TaskRespDict(**resp_data_raw) return cls( name=resp_data["name"], - markdown_description=resp_data["markdown_description"], + markdown_content=resp_data["markdown_description"], status=resp_data["status"]["status"], id=resp_data["id"], assignees=cls.convert_assignees(resp_data["assignees"]), @@ -54,7 +65,10 @@ class ClickupTask: parent=resp_data.get("parent"), parent_list=resp_data["list"]["id"], locations=cls.convert_simple_list_with_names(resp_data["locations"]), - checklists=resp_data["checklists"], + checklists={ + tlist["name"]: {t["name"]: bool(t["resolved"]) for t in tlist["items"]} + for tlist in resp_data["checklists"] + }, ) @classmethod @@ -78,16 +92,14 @@ class ClickupTask: @property def updateables(self) -> dict[str, Any]: - return { - f.name: getattr(self, f.name, None) for f in fields(type(self)) if f.repr - } + return {f.name: getattr(self, f.name, None) for f in fields(type(self)) if f.repr} @property def showables(self) -> dict[str, Any]: return { - f.name: getattr(self, f.name, None) - for f in fields(type(self)) - if f.name != "markdown_description" + dcf.name: getattr(self, dcf.name, ...) + for dcf in fields(self) + if getattr(self, dcf.name, ...) is not None } @property @@ -97,7 +109,12 @@ class ClickupTask: if self.parent: ret += " \033[32m 󰙅 " - ret += f"{self.name} (#{self.id})" + ret += self.name + + if self.tags: + ret += "".join(f" #{t}" for t in self.tags) + + ret += f" (#{self.id})" return ret @@ -151,9 +168,38 @@ class ClickupSession: ) return loads(raw_data) - def get_tasks(self) -> list[ClickupTask]: + def _post(self, endpoint: str, **body_params: str) -> JSONDataMap: + raw_data = post( + self.base_url + endpoint, + body_params, + headers={ + "accept": "application/json", + "content-type": "application/json", + "Authorization": self.auth_key, + }, + ) + return loads(raw_data) + + def _delete(self, endpoint: str) -> JSONDataMap: + raw_data = delete( + self.base_url + endpoint, + headers={ + "accept": "application/json", + "content-type": "application/json", + "Authorization": self.auth_key, + }, + ) + return loads(raw_data) + + def get_tasks(self, **filters: str) -> list[ClickupTask]: data = self._get( - f"/team/{self.workspace_id}/task?subtasks=true&include_markdown_description=true&assignees[]={self.user_id}" + f"/team/{self.workspace_id}/task", + **{ + "subtask": "true", + "include_markdown_description": "true", + "assignees[]": self.user_id, + } + | filters, ).get("tasks", []) if isinstance(data, list): return [ClickupTask.from_resp_data(t) for t in data if isinstance(t, dict)] @@ -169,4 +215,16 @@ class ClickupSession: ) def update(self, data: ClickupTask) -> None: - _ = self._put(f"/task/{data.id}", **data.updateables) + ret_task = self._put(f"/task/{data.id}", **data.updateables) + current_tags: set[str] = set(ret_task["tags"]) + new_tags = set(data.tags) + + for del_tag in current_tags - new_tags: + self._delete(f"/task/{data.id}/tag/{del_tag}") + + for add_tag in new_tags - current_tags: + self._post(f"/task/{data.id}/tag/{add_tag}") + + def create(self, data: ClickupTask) -> str: + ret_task = self._post(f"/list/{data.parent_list}/task", **data.updateables) + return str(ret_task["id"])