Skip to content

Commit

Permalink
feat(workflows): add the endpoint to delete a workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-herrero committed Oct 30, 2023
1 parent bb90205 commit a42b564
Show file tree
Hide file tree
Showing 19 changed files with 890 additions and 73 deletions.
8 changes: 8 additions & 0 deletions python/apps/taiga/src/taiga/stories/stories/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,11 @@ def list_stories_to_reorder(filters: StoryFilters = {}) -> list[Story]:
@sync_to_async
def list_story_assignees(story: Story) -> list[User]:
return list(story.assignees.all().order_by("-story_assignments__created_at"))


async def bulk_update_workflow_to_stories(
statuses_ids: list[UUID], old_workflow_id: UUID, new_workflow_id: UUID
) -> None:
await Story.objects.filter(status_id__in=statuses_ids, workflow_id=old_workflow_id).aupdate(
workflow_id=new_workflow_id
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class StorySummarySerializer(BaseModel):
title: str
status: WorkflowStatusNestedSerializer
version: int
assignees: list[UserNestedSerializer]
assignees: list[UserNestedSerializer] | None

class Config:
orm_mode = True
Expand Down
23 changes: 17 additions & 6 deletions python/apps/taiga/src/taiga/stories/stories/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,25 +83,36 @@ async def list_paginated_stories(
filters={"workflow_slug": workflow_slug, "project_id": project_id}
)

stories_serializer = await get_serialized_stories(project_id, workflow_slug, limit, offset)

pagination = Pagination(offset=offset, limit=limit, total=total_stories)

return pagination, stories_serializer


async def list_all_stories(project_id: UUID, workflow_slug: str) -> list[StorySummarySerializer]:
stories_serializer = await get_serialized_stories(project_id, workflow_slug)

return stories_serializer


async def get_serialized_stories(
project_id: UUID, workflow_slug: str, limit: int | None = None, offset: int | None = None
) -> list[StorySummarySerializer]:
stories = await stories_repositories.list_stories(
filters={"workflow_slug": workflow_slug, "project_id": project_id},
offset=offset,
limit=limit,
select_related=["created_by", "project", "workflow", "status"],
prefetch_related=["assignees"],
)

stories_serializer = [
return [
serializers_services.serialize_story_list(
story=story, assignees=await stories_repositories.list_story_assignees(story)
)
for story in stories
]

pagination = Pagination(offset=offset, limit=limit, total=total_stories)

return pagination, stories_serializer


##########################################################
# get story
Expand Down
45 changes: 42 additions & 3 deletions python/apps/taiga/src/taiga/workflows/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from taiga.workflows.api.validators import (
CreateWorkflowStatusValidator,
CreateWorkflowValidator,
DeleteWorkflowQuery,
DeleteWorkflowStatusQuery,
ReorderWorkflowStatusesValidator,
UpdateWorkflowStatusValidator,
Expand All @@ -32,6 +33,7 @@
CREATE_WORKFLOW = IsProjectAdmin()
LIST_WORKFLOWS = HasPerm("view_story")
GET_WORKFLOW = HasPerm("view_story")
DELETE_WORKFLOW = IsProjectAdmin()
UPDATE_WORKFLOW = IsProjectAdmin()
CREATE_WORKFLOW_STATUS = IsProjectAdmin()
UPDATE_WORKFLOW_STATUS = IsProjectAdmin()
Expand Down Expand Up @@ -119,7 +121,7 @@ async def get_workflow(
return await workflows_services.get_workflow_detail(project_id=project_id, workflow_slug=workflow_slug)


##########################################################
#########################################################
# update workflow
##########################################################

Expand Down Expand Up @@ -147,6 +149,44 @@ async def update_workflow(
return await workflows_services.update_workflow(project_id=project_id, workflow=workflow, values=values)


################################################
# delete workflow
################################################


@routes.workflows.delete(
"/projects/{project_id}/workflows/{workflow_slug}",
name="project.workflow.delete",
summary="Delete a workflow",
responses=ERROR_403 | ERROR_404 | ERROR_422,
status_code=status.HTTP_204_NO_CONTENT,
)
async def delete_workflow(
project_id: B64UUID,
workflow_slug: str,
request: AuthRequest,
query_params: DeleteWorkflowQuery = Depends(),
) -> None:
"""
Deletes a workflow in the given project, providing the option to move all the statuses and their stories to another
workflow.
Query params:
* **move_to:** the workflow's slug to which move the statuses from the workflow being deleted
- if not received, the workflow, statuses and its contained stories will be deleted
- if received, the workflow will be deleted but its statuses and stories won't (they will be appended to the
last status of the specified workflow).
"""
workflow = await get_workflow_or_404(project_id=project_id, workflow_slug=workflow_slug)
await check_permissions(permissions=DELETE_WORKFLOW, user=request.user, obj=workflow)

await workflows_services.delete_workflow(
workflow=workflow,
target_workflow_slug=query_params.move_to,
)


################################################
# misc
################################################
Expand Down Expand Up @@ -247,7 +287,7 @@ async def reorder_workflow_statuses(
await check_permissions(permissions=REORDER_WORKFLOW_STATUSES, user=request.user, obj=workflow)

return await workflows_services.reorder_workflow_statuses(
workflow=workflow,
target_workflow=workflow,
statuses=form.statuses,
reorder=form.get_reorder_dict(),
)
Expand Down Expand Up @@ -277,7 +317,6 @@ async def delete_workflow_status(
to any other existing workflow status in the same workflow.
Query params:
* **move_to:** the workflow status's slug to which move the stories from the status being deleted
- if not received, the workflow status and its contained stories will be deleted
- if received, the workflow status will be deleted but its contained stories won't (they will be first moved to
Expand Down
16 changes: 16 additions & 0 deletions python/apps/taiga/src/taiga/workflows/api/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,26 @@ class WorkflowName(ConstrainedStr):
max_length = 40


class WorkflowSlug(ConstrainedStr):
strip_whitespace = True
min_length = 1
max_length = 40


class CreateWorkflowValidator(BaseModel):
name: WorkflowName


class DeleteWorkflowQuery(BaseModel):
move_to: WorkflowSlug | None

@validator("move_to")
def check_move_to_slug(cls, v: WorkflowSlug | None) -> WorkflowSlug | None:
if v is None:
return None
return v


class CreateWorkflowStatusValidator(BaseModel):
name: WorkflowStatusName
color: conint(gt=0, lt=9) # type: ignore
Expand Down
17 changes: 16 additions & 1 deletion python/apps/taiga/src/taiga/workflows/events/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@
from taiga.workflows.events.content import (
CreateWorkflowContent,
CreateWorkflowStatusContent,
DeleteWorkflowContent,
DeleteWorkflowStatusContent,
ReorderWorkflowStatusesContent,
UpdateWorkflowContent,
UpdateWorkflowStatusContent,
)
from taiga.workflows.models import WorkflowStatus
from taiga.workflows.serializers import ReorderWorkflowStatusesSerializer, WorkflowSerializer
from taiga.workflows.serializers import DeleteWorkflowSerializer, ReorderWorkflowStatusesSerializer, WorkflowSerializer

CREATE_WORKFLOW = "workflows.create"
UPDATE_WORKFLOW = "workflows.update"
DELETE_WORKFLOW = "workflows.delete"
CREATE_WORKFLOW_STATUS = "workflowstatuses.create"
UPDATE_WORKFLOW_STATUS = "workflowstatuses.update"
REORDER_WORKFLOW_STATUS = "workflowstatuses.reorder"
Expand All @@ -44,6 +46,19 @@ async def emit_event_when_workflow_is_updated(project: Project, workflow: Workfl
)


async def emit_event_when_workflow_is_deleted(
project: Project, workflow: DeleteWorkflowSerializer, target_workflow: WorkflowSerializer | None
) -> None:
await events_manager.publish_on_project_channel(
project=project,
type=DELETE_WORKFLOW,
content=DeleteWorkflowContent(
workflow=workflow,
target_workflow=target_workflow,
),
)


async def emit_event_when_workflow_status_is_created(project: Project, workflow_status: WorkflowStatus) -> None:
await events_manager.publish_on_project_channel(
project=project,
Expand Down
12 changes: 11 additions & 1 deletion python/apps/taiga/src/taiga/workflows/events/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
# Copyright (c) 2023-present Kaleidos INC

from taiga.base.serializers import BaseModel
from taiga.workflows.serializers import ReorderWorkflowStatusesSerializer, WorkflowSerializer, WorkflowStatusSerializer
from taiga.workflows.serializers import (
DeleteWorkflowSerializer,
ReorderWorkflowStatusesSerializer,
WorkflowSerializer,
WorkflowStatusSerializer,
)


class CreateWorkflowContent(BaseModel):
Expand All @@ -17,6 +22,11 @@ class UpdateWorkflowContent(BaseModel):
workflow: WorkflowSerializer


class DeleteWorkflowContent(BaseModel):
workflow: DeleteWorkflowSerializer
target_workflow: WorkflowSerializer | None


class CreateWorkflowStatusContent(BaseModel):
workflow_status: WorkflowStatusSerializer

Expand Down
12 changes: 12 additions & 0 deletions python/apps/taiga/src/taiga/workflows/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@


class WorkflowFilters(TypedDict, total=False):
id: UUID
slug: str
project_id: UUID

Expand Down Expand Up @@ -162,6 +163,17 @@ def update_workflow(workflow: Workflow, values: dict[str, Any] = {}) -> Workflow
return workflow


##########################################################
# Workflow - delete workflow
##########################################################


async def delete_workflow(filters: WorkflowFilters = {}) -> int:
qs = _apply_filters_to_workflow_queryset(qs=DEFAULT_QUERYSET_WORKFLOW, filters=filters)
count, _ = await qs.adelete()
return count


##########################################################
# WorkflowStatus - filters and querysets
##########################################################
Expand Down
15 changes: 14 additions & 1 deletion python/apps/taiga/src/taiga/workflows/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Copyright (c) 2023-present Kaleidos INC

from taiga.base.serializers import UUIDB64, BaseModel
from taiga.stories.stories.serializers import StorySummarySerializer
from taiga.workflows.serializers.nested import WorkflowNestedSerializer, WorkflowStatusNestedSerializer


Expand All @@ -20,6 +21,18 @@ class Config:
orm_mode = True


class DeleteWorkflowSerializer(BaseModel):
id: UUIDB64
name: str
slug: str
order: int
statuses: list[WorkflowStatusNestedSerializer]
stories: list[StorySummarySerializer]

class Config:
orm_mode = True


class WorkflowStatusSerializer(BaseModel):
id: UUIDB64
name: str
Expand All @@ -42,7 +55,7 @@ class Config:
class ReorderWorkflowStatusesSerializer(BaseModel):
workflow: WorkflowNestedSerializer
statuses: list[UUIDB64]
reorder: ReorderSerializer
reorder: ReorderSerializer | None

class Config:
orm_mode = True
18 changes: 17 additions & 1 deletion python/apps/taiga/src/taiga/workflows/serializers/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
from typing import Any
from uuid import UUID

from taiga.stories.stories.serializers import StorySummarySerializer
from taiga.workflows.models import Workflow, WorkflowStatus
from taiga.workflows.serializers import ReorderWorkflowStatusesSerializer, WorkflowSerializer
from taiga.workflows.serializers import DeleteWorkflowSerializer, ReorderWorkflowStatusesSerializer, WorkflowSerializer


def serialize_workflow(workflow: Workflow, workflow_statuses: list[WorkflowStatus] = []) -> WorkflowSerializer:
Expand All @@ -23,6 +24,21 @@ def serialize_workflow(workflow: Workflow, workflow_statuses: list[WorkflowStatu
)


def serialize_delete_workflow_detail(
workflow: Workflow,
workflow_statuses: list[WorkflowStatus] = [],
workflow_stories: list[StorySummarySerializer] = [],
) -> DeleteWorkflowSerializer:
return DeleteWorkflowSerializer(
id=workflow.id,
name=workflow.name,
slug=workflow.slug,
order=workflow.order,
statuses=workflow_statuses,
stories=workflow_stories,
)


def serialize_reorder_workflow_statuses(
workflow: Workflow, statuses: list[UUID], reorder: dict[str, Any] | None = None
) -> ReorderWorkflowStatusesSerializer:
Expand Down
Loading

0 comments on commit a42b564

Please sign in to comment.