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

Adds support for deleting all external resources on case deletion #2403

Merged
merged 1 commit into from
Aug 11, 2022
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
30 changes: 22 additions & 8 deletions src/dispatch/case/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def case_new_create_flow(*, case_id: int, organization_slug: str, db_session=Non

if not group:
# we delete the ticket
ticket_flows.delete_ticket(ticket=ticket, project_id=case.project.id, db_session=db_session)
ticket_flows.delete_ticket(ticket=ticket, db_session=db_session)

# we delete the case
delete(db_session=db_session, case_id=case_id)
Expand All @@ -53,10 +53,10 @@ def case_new_create_flow(*, case_id: int, organization_slug: str, db_session=Non
storage = storage_flows.create_storage(obj=case, members=members, db_session=db_session)
if not storage:
# we delete the group
group_flows.delete_group(group=group, project_id=case.project.id, db_session=db_session)
group_flows.delete_group(group=group, db_session=db_session)

# we delete the ticket
ticket_flows.delete_ticket(ticket=ticket, project_id=case.project.id, db_session=db_session)
ticket_flows.delete_ticket(ticket=ticket, db_session=db_session)

# we delete the case
delete(db_session=db_session, case_id=case_id)
Expand All @@ -70,15 +70,13 @@ def case_new_create_flow(*, case_id: int, organization_slug: str, db_session=Non
)
if not document:
# we delete the storage
storage_flows.delete_storage(
storage=storage, project_id=case.project.id, db_session=db_session
)
storage_flows.delete_storage(storage=storage, db_session=db_session)

# we delete the group
group_flows.delete_group(group=group, project_id=case.project.id, db_session=db_session)
group_flows.delete_group(group=group, db_session=db_session)

# we delete the ticket
ticket_flows.delete_ticket(ticket=ticket, project_id=case.project.id, db_session=db_session)
ticket_flows.delete_ticket(ticket=ticket, db_session=db_session)

# we delete the case
delete(db_session=db_session, case_id=case_id)
Expand Down Expand Up @@ -139,6 +137,22 @@ def case_update_flow(
# we send the case updated notification


def case_delete_flow(case: Case, db_session: SessionLocal):
"""Runs the case delete flow."""
# we delete the external ticket
if case.ticket:
ticket_flows.delete_ticket(ticket=case.ticket, db_session=db_session)

# we delete the external groups
if case.groups:
for group in case.groups:
group_flows.delete_group(group=group, db_session=db_session)

# we delete the external storage
if case.storage:
storage_flows.delete_storage(storage=case.storage, db_session=db_session)


def case_new_status_flow(case: Case, db_session=None):
"""Runs the case new transition flow."""
pass
Expand Down
41 changes: 31 additions & 10 deletions src/dispatch/case/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from starlette.requests import Request
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, status

from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session

# NOTE: define permissions before enabling the code block below
Expand All @@ -26,6 +27,7 @@

from .flows import (
case_closed_create_flow,
case_delete_flow,
case_escalated_create_flow,
case_new_create_flow,
case_triage_create_flow,
Expand All @@ -41,7 +43,7 @@


def get_current_case(*, db_session: Session = Depends(get_db), request: Request) -> Case:
"""Fetches case or returns an HTTP 404."""
"""Fetches a case or returns an HTTP 404."""
case = get(db_session=db_session, case_id=request.path_params["case_id"])
if not case:
raise HTTPException(
Expand All @@ -51,13 +53,13 @@ def get_current_case(*, db_session: Session = Depends(get_db), request: Request)
return case


@router.get("", summary="Retrieve a list of all cases.")
@router.get("", summary="Retrieves all cases.")
def get_cases(
*,
common: dict = Depends(common_parameters),
include: List[str] = Query([], alias="include[]"),
):
"""Retrieve a list of all cases."""
"""Retrieves all cases."""
pagination = search_filter_sort_paginate(model="Case", **common)

if include:
Expand All @@ -74,7 +76,7 @@ def get_cases(
return json.loads(CasePagination(**pagination).json())


@router.post("", response_model=CaseRead, summary="Create a new case.")
@router.post("", response_model=CaseRead, summary="Creates a new case.")
def create_case(
*,
db_session: Session = Depends(get_db),
Expand Down Expand Up @@ -129,7 +131,7 @@ def update_case(
current_user: DispatchUser = Depends(get_current_user),
background_tasks: BackgroundTasks,
):
"""Update an existing case."""
"""Updates an existing case."""
# we store the previous state of the case in order to be able to detect changes
previous_case = CaseRead.from_orm(current_case)

Expand All @@ -151,14 +153,33 @@ def update_case(
@router.delete(
"/{case_id}",
response_model=CaseRead,
summary="Delete an case.",
summary="Deletes an existing case.",
# dependencies=[Depends(PermissionsDependency([CaseEditPermission]))],
)
def delete_case(
*,
case_id: PrimaryKey,
db_session: Session = Depends(get_db),
current_case: Case = Depends(get_current_case),
organization: OrganizationSlug,
case_id: PrimaryKey,
background_tasks: BackgroundTasks,
):
"""Delete an individual case."""
delete(db_session=db_session, case_id=current_case.id)
"""Deletes an existing case."""
# we get the internal case
case = get(db_session=db_session, case_id=case_id)

# we run the case delete flow
case_delete_flow(case=case, db_session=db_session)

# we delete the internal case
try:
delete(db_session=db_session, case_id=case_id)
except IntegrityError as e:
log.exception(e)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=[
{
"msg": f"Case {case.name} could not be deleted. Make sure the case has no relationships to other cases or incidents before deleting it."
}
],
)
15 changes: 6 additions & 9 deletions src/dispatch/group/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,18 +81,15 @@ def create_group(
return group


def delete_group(group: Group, project_id: int, db_session: SessionLocal):
def delete_group(group: Group, db_session: SessionLocal):
"""Deletes an existing group."""
# we delete the external group
plugin = plugin_service.get_active_instance(
db_session=db_session, project_id=project_id, plugin_type="participant-group"
db_session=db_session, project_id=group.case.project.id, plugin_type="participant-group"
)
if plugin:
# TODO(mvilanova): implement deleting the external group
# plugin.instance.delete()
pass
try:
plugin.instance.delete(email=group.email)
except Exception as e:
log.exception(e)
else:
log.warning("Group not deleted. No group plugin enabled.")

# we delete the internal group
delete(db_session=db_session, group_id=group.id)
4 changes: 2 additions & 2 deletions src/dispatch/plugins/dispatch_google/drive/drive.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,8 @@ def copy_file(client: Any, folder_id: str, file_id: str, new_file_name: str):
)


def delete_file(client: Any, folder_id: str, file_id: str):
"""Deletes a file from a teamdrive."""
def delete_file(client: Any, file_id: str):
"""Deletes a folder or file from a Google Drive."""
return make_call(client.files(), "delete", fileId=file_id, supportsAllDrives=True)


Expand Down
27 changes: 13 additions & 14 deletions src/dispatch/plugins/dispatch_google/drive/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@
from dispatch.plugins.bases import StoragePlugin, TaskPlugin
from dispatch.plugins.dispatch_google import drive as google_drive_plugin
from dispatch.plugins.dispatch_google.common import get_service

from dispatch.plugins.dispatch_google.config import GoogleConfiguration

from .drive import (
Roles,
add_domain_permission,
add_permission,
add_reply,
copy_file,
create_file,
delete_file,
download_google_document,
list_files,
mark_as_readonly,
move_file,
remove_permission,
add_domain_permission,
add_reply,
mark_as_readonly,
)
from .task import get_task_activity

Expand Down Expand Up @@ -72,13 +72,13 @@ def add_participant(
role: str = "owner",
user_type: str = "user",
):
"""Adds participants to existing Google Drive."""
"""Adds participants to an existing Google Drive."""
client = get_service(self.configuration, "drive", "v3", self.scopes)
for p in participants:
add_permission(client, p, team_drive_or_file_id, role, user_type)

def remove_participant(self, folder_id: str, participants: List[str]):
"""Removes participants from existing Google Drive."""
"""Removes participants from an existing Google Drive."""
client = get_service(self.configuration, "drive", "v3", self.scopes)
for p in participants:
remove_permission(client, p, folder_id)
Expand All @@ -101,35 +101,34 @@ def create_file(
role: str = Roles.writer,
file_type: str = "folder",
):
"""Creates a new file in existing Google Drive."""
"""Creates a new file in an existing Google Drive."""
client = get_service(self.configuration, "drive", "v3", self.scopes)
response = create_file(client, parent_id, name, participants, role, file_type)
response["weblink"] = response["webViewLink"]
return response

def delete_file(self, folder_id: str, file_id: str):
"""Removes a file from existing Google Drive."""
def delete_file(self, file_id: str):
"""Deletes a file or folder from an existing Google Drive."""
client = get_service(self.configuration, "drive", "v3", self.scopes)
response = delete_file(client, folder_id, file_id)
response["weblink"] = response["webViewLink"]
response = delete_file(client, file_id)
return response

def copy_file(self, folder_id: str, file_id: str, name: str):
"""Creates a copy of the given file and places it in the specified team drive."""
"""Creates a copy of the given file and places it in the specified Google Drive."""
client = get_service(self.configuration, "drive", "v3", self.scopes)
response = copy_file(client, folder_id, file_id, name)
response["weblink"] = response["webViewLink"]
return response

def move_file(self, new_folder_id: str, file_id: str):
"""Moves a file from one team drive to another."""
"""Moves a file from one Google drive to another."""
client = get_service(self.configuration, "drive", "v3", self.scopes)
response = move_file(client, new_folder_id, file_id)
response["weblink"] = response["webViewLink"]
return response

def list_files(self, folder_id: str, q: str = None):
"""Lists all files in team drive."""
"""Lists all files in a Google drive."""
client = get_service(self.configuration, "drive", "v3", self.scopes)
return list_files(client, folder_id, q)

Expand Down
6 changes: 6 additions & 0 deletions src/dispatch/plugins/dispatch_jira/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,3 +393,9 @@ def update_case_ticket(
)

return update(self.configuration, client, issue, issue_fields, status)

def delete(self, ticket_id: str):
"""Deletes a Jira issue."""
client = create_client(self.configuration)
issue = client.issue(ticket_id)
issue.delete()
7 changes: 6 additions & 1 deletion src/dispatch/static/dispatch/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,15 @@ instance.interceptors.response.use(
}

if (err.response.status == 500) {
let errorText = err.response.data.detail.map(({ msg }) => msg).join(" ")
if (errorText.length == 0) {
errorText =
"Something has gone wrong, please retry or let your admin know that you received this error."
}
store.commit(
"notification_backend/addBeNotification",
{
text: "Something has gone wrong, please retry or let your admin know that you received this error.",
text: errorText,
type: "error",
},
{ root: true }
Expand Down
15 changes: 6 additions & 9 deletions src/dispatch/storage/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,15 @@ def create_storage(obj: Any, members: List[str], db_session: SessionLocal):
return storage


def delete_storage(storage: Storage, project_id: int, db_session: SessionLocal):
def delete_storage(storage: Storage, db_session: SessionLocal):
"""Deletes an existing storage."""
# we delete the external storage
plugin = plugin_service.get_active_instance(
db_session=db_session, project_id=project_id, plugin_type="storage"
db_session=db_session, project_id=storage.case.project.id, plugin_type="storage"
)
if plugin:
# TODO(mvilanova): implement deleting the external storage
# plugin.instance.delete()
pass
try:
plugin.instance.delete_file(file_id=storage.resource_id)
except Exception as e:
log.exception(e)
else:
log.warning("Storage not deleted. No storage plugin enabled.")

# we delete the internal storage
delete(db_session=db_session, storage_id=storage.id)
15 changes: 6 additions & 9 deletions src/dispatch/ticket/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,18 +213,15 @@ def update_case_ticket(
)


def delete_ticket(ticket: Ticket, project_id: int, db_session: SessionLocal):
def delete_ticket(ticket: Ticket, db_session: SessionLocal):
"""Deletes a ticket."""
# we delete the external ticket
plugin = plugin_service.get_active_instance(
db_session=db_session, project_id=project_id, plugin_type="ticket"
db_session=db_session, project_id=ticket.case.project.id, plugin_type="ticket"
)
if plugin:
# TODO(mvilanova): implement deleting the external ticket
# plugin.instance.delete_case_ticket()
pass
try:
plugin.instance.delete(ticket_id=ticket.resource_id)
except Exception as e:
log.exception(e)
else:
log.warning("Ticket not deleted. No ticket plugin enabled.")

# we delete the internal ticket
delete(db_session=db_session, ticket_id=ticket.id)