Skip to content

Commit

Permalink
Merge pull request #44 from moneymeets/feature/add-issue-work-items
Browse files Browse the repository at this point in the history
feat(sdk): add issue work items
  • Loading branch information
catcombo authored Mar 14, 2024
2 parents 0ce8f2b + cd818b1 commit 67890f1
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 0 deletions.
38 changes: 38 additions & 0 deletions tests/responses/issue_work_items.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[
{
"duration": {
"id": "100",
"minutes": 100,
"presentation": "1h 40m",
"$type": "DurationValue"
},
"date": 1709424000000,
"created": 1710330927000,
"updated": null,
"type": {
"name": "Development",
"id": "1-0",
"$type": "WorkItemType"
},
"text": "Working hard",
"creator": {
"login": "mary.jane",
"ringId": "26677773-c425-4f47-b62c-dbfb2ad21e8f",
"email": "[email protected]",
"name": "Mary Jane",
"id": "1-52",
"$type": "User"
},
"author": {
"login": "paul.lawson",
"ringId": "d53ece48-4c60-4b88-b93f-68392b975087",
"email": "",
"name": "Paul Lawson",
"id": "1-64",
"$type": "User"
},
"textPreview": "<div class=\"wiki text common-markdown\"><p>Working hard</p>\n</div>",
"id": "12-14",
"$type": "IssueWorkItem"
}
]
17 changes: 17 additions & 0 deletions tests/responses/work_item_types.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[
{
"name": "Development",
"id": "1-0",
"$type": "WorkItemType"
},
{
"name": "Testing",
"id": "1-1",
"$type": "WorkItemType"
},
{
"name": "Documentation",
"id": "1-2",
"$type": "WorkItemType"
}
]
73 changes: 73 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@
from youtrack_sdk.entities import (
Agile,
AgileRef,
DurationValue,
Issue,
IssueAttachment,
IssueComment,
IssueLink,
IssueLinkType,
IssueWorkItem,
Project,
Sprint,
SprintRef,
Tag,
User,
WorkItemType,
)

from .test_definitions import (
Expand Down Expand Up @@ -168,6 +171,50 @@ def test_get_issue_comments(self):
self.client.get_issue_comments(issue_id="1"),
)

@mock_response(url="https://server/api/issues/1/timeTracking/workItems", response_name="issue_work_items")
def test_get_issue_work_items(self):
self.assertEqual(
(
IssueWorkItem.model_construct(
type="IssueWorkItem",
id="12-14",
author=User.model_construct(
type="User",
id="1-64",
name="Paul Lawson",
ring_id="d53ece48-4c60-4b88-b93f-68392b975087",
login="paul.lawson",
email="",
),
creator=User.model_construct(
type="User",
id="1-52",
name="Mary Jane",
ring_id="26677773-c425-4f47-b62c-dbfb2ad21e8f",
login="mary.jane",
email="[email protected]",
),
text="Working hard",
text_preview='<div class="wiki text common-markdown"><p>Working hard</p>\n</div>',
work_item_type=WorkItemType(
type="WorkItemType",
id="1-0",
name="Development",
),
created=datetime(2024, 3, 13, 11, 55, 27, tzinfo=UTC),
updated=None,
duration=DurationValue(
type="DurationValue",
id="100",
minutes=100,
presentation="1h 40m",
),
date=datetime(2024, 3, 3, 0, 0, tzinfo=UTC),
),
),
self.client.get_issue_work_items(issue_id="1"),
)

@mock_response(url="https://server/api/admin/projects", response_name="projects")
def test_get_projects(self):
self.assertEqual(
Expand All @@ -188,6 +235,32 @@ def test_get_projects(self):
self.client.get_projects(),
)

@mock_response(
url="https://server/api/admin/projects/DEMO/timeTrackingSettings/workItemTypes",
response_name="work_item_types",
)
def test_get_project_work_item_types(self):
self.assertEqual(
(
WorkItemType.model_construct(
type="WorkItemType",
id="1-0",
name="Development",
),
WorkItemType.model_construct(
type="WorkItemType",
id="1-1",
name="Testing",
),
WorkItemType.model_construct(
type="WorkItemType",
id="1-2",
name="Documentation",
),
),
self.client.get_project_work_item_types(project_id="DEMO"),
)

@mock_response(url="https://server/api/tags", response_name="tags")
def test_get_tags(self):
self.assertEqual(
Expand Down
55 changes: 55 additions & 0 deletions youtrack_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
IssueCustomFieldType,
IssueLink,
IssueLinkType,
IssueWorkItem,
Project,
Sprint,
Tag,
User,
WorkItemType,
)
from .exceptions import YouTrackException, YouTrackNotFound, YouTrackUnauthorized
from .helpers import model_to_field_names, obj_to_json
Expand Down Expand Up @@ -414,6 +416,37 @@ def create_comment_attachments(
),
)

def get_issue_work_items(self, *, issue_id: str, offset: int = 0, count: int = -1) -> Sequence[IssueWorkItem]:
"""Get the list of all work items of the specific issue.
https://www.jetbrains.com/help/youtrack/devportal/resource-api-issues-issueID-timeTracking-workItems.html#get_all-IssueWorkItem-method
"""
return TypeAdapter(tuple[IssueWorkItem, ...]).validate_python(
self._get(
url=self._build_url(
path=f"/issues/{issue_id}/timeTracking/workItems",
fields=model_to_field_names(IssueWorkItem),
offset=offset,
count=count,
),
),
)

def create_issue_work_item(self, *, issue_id: str, issue_work_item: IssueWorkItem) -> IssueWorkItem:
"""Add a new work item to the issue.
https://www.jetbrains.com/help/youtrack/devportal/resource-api-issues-issueID-timeTracking-workItems.html#create-IssueWorkItem-method
"""
return IssueWorkItem.model_validate(
self._post(
url=self._build_url(
path=f"/issues/{issue_id}/timeTracking/workItems",
fields=model_to_field_names(IssueWorkItem),
),
data=issue_work_item,
),
)

def get_projects(self, offset: int = 0, count: int = -1) -> Sequence[Project]:
"""Get a list of all available projects in the system.
Expand All @@ -430,6 +463,28 @@ def get_projects(self, offset: int = 0, count: int = -1) -> Sequence[Project]:
),
)

def get_project_work_item_types(
self,
*,
project_id: str,
offset: int = 0,
count: int = -1,
) -> Sequence[WorkItemType]:
"""Get the list of all work item types that are used in the project.
https://www.jetbrains.com/help/youtrack/devportal/resource-api-admin-projects-projectID-timeTrackingSettings-workItemTypes.html#get_all-WorkItemType-method
"""
return TypeAdapter(tuple[WorkItemType, ...]).validate_python(
self._get(
url=self._build_url(
path=f"/admin/projects/{project_id}/timeTrackingSettings/workItemTypes",
fields=model_to_field_names(WorkItemType),
offset=offset,
count=count,
),
),
)

def get_tags(self, offset: int = 0, count: int = -1) -> Sequence[Tag]:
"""Get all tags that are visible to the current user.
Expand Down
27 changes: 27 additions & 0 deletions youtrack_sdk/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ class PeriodValue(BaseModel):
presentation: Optional[str] = None


class DurationValue(BaseModel):
type: Literal["DurationValue"] = Field(alias="$type", default="DurationValue")
id: Optional[str] = None
minutes: Optional[int] = None
presentation: Optional[str] = None


class BundleElement(BaseModel):
id: Optional[str] = None
name: Optional[str] = None
Expand Down Expand Up @@ -333,6 +340,26 @@ class IssueLink(BaseModel):
trimmed_issues: Optional[Sequence[Issue]] = Field(alias="trimmedIssues", default=None)


class WorkItemType(BaseModel):
type: Literal["WorkItemType"] = Field(alias="$type", default="WorkItemType")
id: Optional[str] = None
name: Optional[str] = None


class IssueWorkItem(BaseModel):
type: Literal["IssueWorkItem"] = Field(alias="$type", default="IssueWorkItem")
id: Optional[str] = None
author: Optional[User] = None
creator: Optional[User] = None
text: Optional[str] = None
text_preview: Optional[str] = Field(alias="textPreview", default=None)
work_item_type: Optional[WorkItemType] = Field(alias="type", default=None)
created: Optional[AwareDatetime] = None
updated: Optional[AwareDatetime] = None
duration: Optional[DurationValue] = None
date: Optional[AwareDatetime] = None


class AgileRef(BaseModel):
type: Literal["Agile"] = Field(alias="$type", default="Agile")
id: Optional[str] = None
Expand Down

0 comments on commit 67890f1

Please sign in to comment.