Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(story): u#3725 t#4044 update story workflow #507

Merged
merged 1 commit into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class UpdateStoryValidator(BaseModel):
title: Title | None
description: str | None
status: B64UUID | None
workflow: str | None


class ReorderValidator(BaseModel):
Expand Down
36 changes: 32 additions & 4 deletions python/apps/taiga/src/taiga/stories/stories/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ async def list_paginated_stories(
async def get_story(project_id: UUID, ref: int) -> Story | None:
return await stories_repositories.get_story(
filters={"ref": ref, "project_id": project_id},
select_related=["project", "workspace"],
select_related=["project", "workspace", "workflow"],
)


Expand Down Expand Up @@ -181,10 +181,30 @@ async def _validate_and_process_values_to_update(story: Story, values: dict[str,
output["status"] = status

# Calculate new order
latest_story = await stories_repositories.list_stories(
filters={"status_id": status.id}, order_by=["-order"], offset=0, limit=1
output["order"] = await _calculate_next_order(status_id=status.id)

elif workflow_slug := output.pop("workflow", None):
workflow = await workflows_repositories.get_workflow(
filters={"project_id": story.project_id, "slug": workflow_slug}, prefetch_related=["statuses"]
)

if not workflow:
raise ex.InvalidWorkflowError("The provided workflow is not valid.")
elif workflow.slug != story.workflow.slug:
output["workflow"] = workflow

# Set first status
first_status = await workflows_repositories.list_workflow_statuses(
filters={"workflow_id": workflow.id}, order_by=["order"], offset=0, limit=1
)
output["order"] = DEFAULT_ORDER_OFFSET + (latest_story[0].order if latest_story else 0)

if not first_status:
raise ex.WorkflowHasNotStatusesError("The provided workflow hasn't any statuses.")

output["status"] = first_status[0]
tdelatorre marked this conversation as resolved.
Show resolved Hide resolved

# Calculate new order
output["order"] = await _calculate_next_order(status_id=first_status[0].id)

return output

Expand Down Expand Up @@ -304,6 +324,14 @@ async def reorder_stories(
return reorder_story_serializer


async def _calculate_next_order(status_id: UUID) -> Decimal:
latest_story = await stories_repositories.list_stories(
filters={"status_id": status_id}, order_by=["-order"], offset=0, limit=1
)

return DEFAULT_ORDER_OFFSET + (latest_story[0].order if latest_story else 0)


##########################################################
# delete story
##########################################################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ class InvalidStatusError(TaigaServiceException):
...


class InvalidWorkflowError(TaigaServiceException):
...


class WorkflowHasNotStatusesError(TaigaServiceException):
...


class InvalidStoryRefError(TaigaServiceException):
...

Expand Down
2 changes: 1 addition & 1 deletion python/apps/taiga/src/taiga/workflows/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def get_workflow(


##########################################################
# update workflow
# Workflow - update workflow
##########################################################


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ async def test_get_story_422_unprocessable_story_ref(client):
##########################################################


async def test_update_story_200_ok_unprotected_attribute_ok(client):
async def test_update_story_200_ok_unprotected_attribute_status_ok(client):
project = await f.create_project()
workflow = await project.workflows.afirst()
status1 = await workflow.statuses.afirst()
Expand All @@ -304,6 +304,19 @@ async def test_update_story_200_ok_unprotected_attribute_ok(client):
assert response.status_code == status.HTTP_200_OK, response.text


async def test_update_story_200_ok_unprotected_attribute_workflow_ok(client):
project = await f.create_project()
workflow1 = await project.workflows.afirst()
status1 = await workflow1.statuses.afirst()
workflow2 = await f.create_workflow(project=project)
story = await f.create_story(project=project, workflow=workflow1, status=status1)

data = {"version": story.version, "workflow": workflow2.slug}
client.login(project.created_by)
response = client.patch(f"/projects/{project.b64id}/stories/{story.ref}", json=data)
assert response.status_code == status.HTTP_200_OK, response.text


async def test_update_story_200_ok_protected_attribute_ok(client):
project = await f.create_project()
workflow = await project.workflows.afirst()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,90 @@ async def test_validate_and_process_values_to_update_error_wrong_status():
fake_stories_repo.list_stories.assert_not_awaited()


async def test_validate_and_process_values_to_update_ok_with_workflow():
project = f.build_project()
workflow1 = f.build_workflow(project=project)
status1 = f.build_workflow_status(workflow=workflow1)
story1 = f.build_story(project=project, workflow=workflow1, status=status1)
workflow2 = f.build_workflow(project=project)
status2 = f.build_workflow_status(workflow=workflow2)
status3 = f.build_workflow_status(workflow=workflow2)
story2 = f.build_story(project=project, workflow=workflow2, status=status2)
values = {"version": story1.version, "workflow": workflow2.slug}

with (
patch("taiga.stories.stories.services.stories_repositories", autospec=True) as fake_stories_repo,
patch("taiga.stories.stories.services.workflows_repositories", autospec=True) as fake_workflows_repo,
):
fake_workflows_repo.get_workflow.return_value = workflow2
fake_workflows_repo.list_workflow_statuses.return_value = [status2]
fake_stories_repo.list_stories.return_value = [story2, status3]

valid_values = await services._validate_and_process_values_to_update(story=story1, values=values)

fake_workflows_repo.get_workflow.assert_awaited_once_with(
filters={"project_id": story1.project_id, "slug": workflow2.slug}, prefetch_related=["statuses"]
)
fake_workflows_repo.list_workflow_statuses.assert_awaited_once_with(
filters={"workflow_id": workflow2.id}, order_by=["order"], offset=0, limit=1
)
fake_stories_repo.list_stories.assert_awaited_once_with(
filters={"status_id": status2.id},
order_by=["-order"],
offset=0,
limit=1,
)

assert valid_values["workflow"] == workflow2
assert valid_values["order"] == services.DEFAULT_ORDER_OFFSET + story2.order


async def test_validate_and_process_values_to_update_error_wrong_workflow():
story = f.build_story()
values = {"version": story.version, "workflow": "wrong_workflow"}

with (
patch("taiga.stories.stories.services.stories_repositories", autospec=True) as fake_stories_repo,
patch("taiga.stories.stories.services.workflows_repositories", autospec=True) as fake_workflows_repo,
):
fake_workflows_repo.get_workflow.return_value = None

with pytest.raises(ex.InvalidWorkflowError):
await services._validate_and_process_values_to_update(story=story, values=values)

fake_workflows_repo.get_workflow.assert_awaited_once_with(
filters={"project_id": story.project_id, "slug": "wrong_workflow"}, prefetch_related=["statuses"]
)
fake_stories_repo.list_stories.assert_not_awaited()


async def test_validate_and_process_values_to_update_error_workflow_without_statuses():
project = f.build_project()
workflow1 = f.build_workflow(project=project)
status1 = f.build_workflow_status(workflow=workflow1)
story = f.build_story(project=project, workflow=workflow1, status=status1)
workflow2 = f.build_workflow(project=project, statuses=None)
values = {"version": story.version, "workflow": workflow2.slug}

with (
patch("taiga.stories.stories.services.stories_repositories", autospec=True) as fake_stories_repo,
patch("taiga.stories.stories.services.workflows_repositories", autospec=True) as fake_workflows_repo,
):
fake_workflows_repo.get_workflow.return_value = workflow2
fake_workflows_repo.list_workflow_statuses.return_value = []

with pytest.raises(ex.WorkflowHasNotStatusesError):
await services._validate_and_process_values_to_update(story=story, values=values)

fake_workflows_repo.get_workflow.assert_awaited_once_with(
filters={"project_id": story.project_id, "slug": workflow2.slug}, prefetch_related=["statuses"]
)
fake_workflows_repo.list_workflow_statuses.assert_awaited_once_with(
filters={"workflow_id": workflow2.id}, order_by=["order"], offset=0, limit=1
)
fake_stories_repo.list_stories.assert_not_awaited()


#######################################################
# _calculate_offset
#######################################################
Expand Down
4 changes: 2 additions & 2 deletions python/docs/postman/taiga.postman_collection.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"info": {
"_postman_id": "a9dc834f-1ba0-4cc9-907f-f5eac02b8f85",
"_postman_id": "b95931e2-27bf-4229-bdcb-363116c53bea",
"name": "taiga-next",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "9835734"
Expand Down Expand Up @@ -3007,7 +3007,7 @@
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"title\": \"New title\",\n \"description\": \"New description\",\n \"version\": {{story_version}}\n}",
"raw": "{\n \"title\": \"New title\",\n \"description\": \"New description\",\n \"version\": {{story_version}},\n \"workflow\": \"new-workflow\"\n}",
"options": {
"raw": {
"language": "json"
Expand Down
Loading