diff --git a/src/dispatch/case/flows.py b/src/dispatch/case/flows.py
index 4f64d6ee717c..0db531237466 100644
--- a/src/dispatch/case/flows.py
+++ b/src/dispatch/case/flows.py
@@ -174,6 +174,74 @@ def create_conversation(case: Case, conversation_target: str, db_session: Sessio
return conversation
+def create_conversation_flow(
+ conversation_target: str,
+ case: Case,
+ individual_participants: list,
+ db_session: SessionLocal,
+):
+ conversation_plugin = plugin_service.get_active_instance(
+ db_session=db_session, project_id=case.project.id, plugin_type="conversation"
+ )
+ if conversation_plugin:
+ if not conversation_target:
+ conversation_target = case.case_type.conversation_target
+ if conversation_target:
+ try:
+ # TODO: Refactor conversation creation using conversation_flows module
+ conversation = create_conversation(case, conversation_target, db_session)
+ conversation_in = ConversationCreate(
+ resource_id=conversation["resource_id"],
+ resource_type=conversation["resource_type"],
+ weblink=conversation["weblink"],
+ thread_id=conversation["timestamp"],
+ channel_id=conversation["id"],
+ )
+ case.conversation = conversation_service.create(
+ db_session=db_session, conversation_in=conversation_in
+ )
+
+ event_service.log_case_event(
+ db_session=db_session,
+ source="Dispatch Core App",
+ description="Conversation added to case",
+ case_id=case.id,
+ )
+ # wait until all resources are created before adding suggested participants
+ individual_participants = [x.email for x, _ in individual_participants]
+
+ for email in individual_participants:
+ # we don't rely on on this flow to add folks to the conversation because in this case
+ # we want to do it in bulk
+ case_add_or_reactivate_participant_flow(
+ db_session=db_session,
+ user_email=email,
+ case_id=case.id,
+ add_to_conversation=False,
+ )
+ # explicitly add the assignee to the conversation
+ all_participants = individual_participants + [case.assignee.individual.email]
+ conversation_plugin.instance.add_to_thread(
+ case.conversation.channel_id,
+ case.conversation.thread_id,
+ all_participants,
+ )
+ event_service.log_case_event(
+ db_session=db_session,
+ source="Dispatch Core App",
+ description="Case participants added to conversation.",
+ case_id=case.id,
+ )
+ except Exception as e:
+ event_service.log_case_event(
+ db_session=db_session,
+ source="Dispatch Core App",
+ description=f"Creation of case conversation failed. Reason: {e}",
+ case_id=case.id,
+ )
+ log.exception(e)
+
+
def update_conversation(case: Case, db_session: SessionLocal):
"""Updates external communication conversation."""
plugin = plugin_service.get_active_instance(
@@ -241,66 +309,8 @@ def case_new_create_flow(
else:
log.warning("Case assignee not paged. No plugin of type oncall enabled.")
- conversation_plugin = plugin_service.get_active_instance(
- db_session=db_session, project_id=case.project.id, plugin_type="conversation"
- )
- if conversation_plugin:
- if not conversation_target:
- conversation_target = case.case_type.conversation_target
- if conversation_target:
- try:
- # TODO: Refactor conversation creation using conversation_flows module
- conversation = create_conversation(case, conversation_target, db_session)
- conversation_in = ConversationCreate(
- resource_id=conversation["resource_id"],
- resource_type=conversation["resource_type"],
- weblink=conversation["weblink"],
- thread_id=conversation["timestamp"],
- channel_id=conversation["id"],
- )
- case.conversation = conversation_service.create(
- db_session=db_session, conversation_in=conversation_in
- )
-
- event_service.log_case_event(
- db_session=db_session,
- source="Dispatch Core App",
- description="Conversation added to case",
- case_id=case.id,
- )
- # wait until all resources are created before adding suggested participants
- individual_participants = [x.email for x, _ in individual_participants]
-
- for email in individual_participants:
- # we don't rely on on this flow to add folks to the conversation because in this case
- # we want to do it in bulk
- case_add_or_reactivate_participant_flow(
- db_session=db_session,
- user_email=email,
- case_id=case.id,
- add_to_conversation=False,
- )
- # explicitly add the assignee to the conversation
- all_participants = individual_participants + [case.assignee.individual.email]
- conversation_plugin.instance.add_to_thread(
- case.conversation.channel_id,
- case.conversation.thread_id,
- all_participants,
- )
- event_service.log_case_event(
- db_session=db_session,
- source="Dispatch Core App",
- description="Case participants added to conversation.",
- case_id=case.id,
- )
- except Exception as e:
- event_service.log_case_event(
- db_session=db_session,
- source="Dispatch Core App",
- description=f"Creation of case conversation failed. Reason: {e}",
- case_id=case.id,
- )
- log.exception(e)
+ # we create the conversation and add participants to the thread
+ create_conversation_flow(conversation_target, case, individual_participants, db_session)
db_session.add(case)
db_session.commit()
@@ -684,7 +694,11 @@ def case_assign_role_flow(
def case_create_resources_flow(
- db_session: Session, case_id: int, individual_participants: list, team_participants: list
+ db_session: Session,
+ case_id: int,
+ individual_participants: list,
+ team_participants: list,
+ conversation_target: str = None,
) -> None:
"""Runs the case resource creation flow."""
case = get(db_session=db_session, case_id=case_id)
@@ -697,38 +711,44 @@ def case_create_resources_flow(
indirect_participant_emails = [t.email for t in team_participants]
- group = group_flows.create_group(
- subject=case,
- group_type=GroupType.tactical,
- group_participants=list(set(direct_participant_emails + indirect_participant_emails)),
- db_session=db_session,
- )
+ if not case.groups:
+ group_flows.create_group(
+ subject=case,
+ group_type=GroupType.tactical,
+ group_participants=list(set(direct_participant_emails + indirect_participant_emails)),
+ db_session=db_session,
+ )
# we create the storage folder
storage_members = []
- if group:
- storage_members = [group.email]
+ if case.tactical_group:
+ storage_members = [case.tactical_group.email]
# direct add members if not group exists
else:
storage_members = direct_participant_emails
- case.storage = storage_flows.create_storage(
- subject=case, storage_members=storage_members, db_session=db_session
- )
+ if not case.storage:
+ storage_flows.create_storage(
+ subject=case, storage_members=storage_members, db_session=db_session
+ )
# we create the investigation document
- document = document_flows.create_document(
- subject=case,
- document_type=DocumentResourceTypes.case,
- document_template=case.case_type.case_template_document,
- db_session=db_session,
- )
+ if not case.case_document:
+ document_flows.create_document(
+ subject=case,
+ document_type=DocumentResourceTypes.case,
+ document_template=case.case_type.case_template_document,
+ db_session=db_session,
+ )
+
+ # we create the conversation and add participants to the thread
+ create_conversation_flow(conversation_target, case, individual_participants, db_session)
# we update the ticket
ticket_flows.update_case_ticket(case=case, db_session=db_session)
# we update the case document
document_flows.update_document(
- document=document, project_id=case.project.id, db_session=db_session
+ document=case.case_document, project_id=case.project.id, db_session=db_session
)
diff --git a/src/dispatch/case/views.py b/src/dispatch/case/views.py
index 1d9e68f36e94..7e9ade924737 100644
--- a/src/dispatch/case/views.py
+++ b/src/dispatch/case/views.py
@@ -34,6 +34,8 @@
case_new_create_flow,
case_triage_create_flow,
case_update_flow,
+ case_create_resources_flow,
+ get_case_participants,
)
from .models import Case, CaseCreate, CasePagination, CaseRead, CaseUpdate, CaseExpandedPagination
from .service import create, delete, get, update
@@ -145,6 +147,30 @@ def create_case(
return case
+@router.post(
+ "/{case_id}/resources",
+ response_model=CaseRead,
+ summary="Creates resources for an existing case.",
+)
+def create_case_resources(
+ db_session: DbSession,
+ case_id: PrimaryKey,
+ current_case: CurrentCase,
+):
+ """Creates resources for an existing incident."""
+ individual_participants, team_participants = get_case_participants(
+ case=current_case, db_session=db_session
+ )
+ case_create_resources_flow(
+ db_session=db_session,
+ case_id=case_id,
+ individual_participants=individual_participants,
+ team_participants=team_participants,
+ )
+
+ return current_case
+
+
@router.put(
"/{case_id}",
response_model=CaseRead,
diff --git a/src/dispatch/static/dispatch/src/case/ResourcesTab.vue b/src/dispatch/static/dispatch/src/case/ResourcesTab.vue
index ad7403449c82..fc7ccc1d0f40 100644
--- a/src/dispatch/static/dispatch/src/case/ResourcesTab.vue
+++ b/src/dispatch/static/dispatch/src/case/ResourcesTab.vue
@@ -65,17 +65,48 @@
+
+
+
+ Recreate Missing Resources
+ Initiate a retry for creating any missing or unsuccesfully created
+ resource(s).
+
+
+
+ refresh
+
+
+
+
diff --git a/src/dispatch/static/dispatch/src/case/api.js b/src/dispatch/static/dispatch/src/case/api.js
index 8a3b0d2abb61..2fc1a01f27e3 100644
--- a/src/dispatch/static/dispatch/src/case/api.js
+++ b/src/dispatch/static/dispatch/src/case/api.js
@@ -44,4 +44,8 @@ export default {
delete(caseId) {
return API.delete(`/${resource}/${caseId}`)
},
+
+ createAllResources(caseId, payload) {
+ return API.post(`/${resource}/${caseId}/resources`, payload)
+ },
}
diff --git a/src/dispatch/static/dispatch/src/case/store.js b/src/dispatch/static/dispatch/src/case/store.js
index bbe3ec901dac..1f4624ec8941 100644
--- a/src/dispatch/static/dispatch/src/case/store.js
+++ b/src/dispatch/static/dispatch/src/case/store.js
@@ -3,6 +3,7 @@ import { debounce } from "lodash"
import SearchUtils from "@/search/utils"
import CaseApi from "@/case/api"
+import PluginApi from "@/plugin/api"
import router from "@/router"
const getDefaultSelectedState = () => {
@@ -253,6 +254,28 @@ const actions = {
commit("SET_SELECTED_LOADING", false)
})
},
+ createAllResources({ commit, dispatch }) {
+ commit("SET_SELECTED_LOADING", true)
+ return CaseApi.createAllResources(state.selected.id)
+ .then(function () {
+ dispatch("get")
+ commit(
+ "notification_backend/addBeNotification",
+ { text: "Case resource(s) created successfully.", type: "success" },
+ { root: true }
+ )
+ })
+ .catch(() => {
+ commit(
+ "notification_backend/addBeNotification",
+ { text: "No case resources created.", type: "error" },
+ { root: true }
+ )
+ })
+ .finally(() => {
+ commit("SET_SELECTED_LOADING", false)
+ })
+ },
save({ commit, dispatch }) {
commit("SET_SELECTED_LOADING", true)
if (!state.selected.id) {
@@ -330,6 +353,37 @@ const actions = {
)
})
},
+ isPluginEnabled(_, type) {
+ if (!state.selected.project) {
+ return false
+ }
+ return PluginApi.getAllInstances({
+ filter: JSON.stringify({
+ and: [
+ {
+ model: "PluginInstance",
+ field: "enabled",
+ op: "==",
+ value: "true",
+ },
+ {
+ model: "Project",
+ field: "name",
+ op: "==",
+ value: state.selected.project.name,
+ },
+ {
+ model: "Plugin",
+ field: "type",
+ op: "like",
+ value: String(type),
+ },
+ ],
+ }),
+ }).then((response) => {
+ return response.data.items.length > 0
+ })
+ },
}
const mutations = {