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 = {