def job_closed_flow(job_id: int, command: Optional[dict] = None, db_session=None): """Runs the job closed flow.""" # we load the job instance job = job_service.get(db_session=db_session, job_id=job_id) # we set the closed time job.closed_at = datetime.utcnow() # we archive the conversation convo_plugin = plugins.get(INCIDENT_PLUGIN_CONVERSATION_SLUG) convo_plugin.archive(job.conversation.channel_id) # we update the external ticket update_external_job_ticket(job, db_session) if job.visibility == Visibility.open: if INCIDENT_STORAGE_RESTRICTED: # we unrestrict the storage unrestrict_job_storage(job, db_session) # we archive the artifacts in the storage archive_job_artifacts(job, db_session) # we delete the tactical and notification groups delete_participant_groups(job, db_session) # we delete the conference delete_conference(job, db_session) db_session.add(job) db_session.commit()
def job_assign_role_flow(assigner_email: str, job_id: int, assignee_email: str, assignee_role: str, db_session=None): """Runs the job participant role assignment flow.""" # we resolve the assigner and assignee's contact information contact_plugin = plugins.get(INCIDENT_PLUGIN_CONTACT_SLUG) assigner_contact_info = contact_plugin.get(assigner_email) assignee_contact_info = contact_plugin.get(assignee_email) # we load the job instance job = job_service.get(db_session=db_session, job_id=job_id) # we get the participant object for the assignee assignee_participant = participant_service.get_by_job_id_and_email( db_session=db_session, job_id=job.id, code=assignee_contact_info["email"]) if not assignee_participant: # The assignee is not a participant. We add them to the job job_add_or_reactivate_participant_flow(assignee_email, job.id, db_session=db_session) # we run the participant assign role flow result = participant_role_flows.assign_role_flow(job.id, assignee_contact_info, assignee_role, db_session) if result == "assignee_has_role": # NOTE: This is disabled until we can determine the source of the caller # we let the assigner know that the assignee already has this role # send_job_participant_has_role_ephemeral_message( # assigner_email, assignee_contact_info, assignee_role, job # ) return if result == "role_not_assigned": # NOTE: This is disabled until we can determine the source of the caller # we let the assigner know that we were not able to assign the role # send_job_participant_role_not_assigned_ephemeral_message( # assigner_email, assignee_contact_info, assignee_role, job # ) return if assignee_role != ParticipantRoleType.participant: # we send a notification to the job conversation send_job_new_role_assigned_notification(assigner_contact_info, assignee_contact_info, assignee_role, job) if assignee_role == ParticipantRoleType.job_commander: # we update the conversation topic set_conversation_topic(job) # we update the external ticket update_external_job_ticket(job, db_session)
def add_participant_to_conversation(participant_email: str, job_id: int, db_session: SessionLocal): """Adds a participant to the conversation.""" # we load the job instance job = job_service.get(db_session=db_session, job_id=job_id) convo_plugin = plugins.get(INCIDENT_PLUGIN_CONVERSATION_SLUG) convo_plugin.add(job.conversation.channel_id, [participant_email])
def job_active_flow(job_id: int, command: Optional[dict] = None, db_session=None): """Runs the job active flow.""" # we load the job instance job = job_service.get(db_session=db_session, job_id=job_id) # we remind the job commander to write a tactical report send_job_report_reminder(job, ReportTypes.tactical_report) # we update the status of the external ticket update_external_job_ticket(job, db_session)
def job_remove_participant_flow(user_email: str, job_id: int, event: dict = None, db_session=None): """Runs the remove participant flow.""" # we load the job instance job = job_service.get(db_session=db_session, job_id=job_id) if user_email == job.commander.code: # we add the job commander to the conversation again add_participant_to_conversation(user_email, job_id, db_session) # we send a notification to the channel send_job_commander_readded_notification(job_id, db_session) else: # we remove the participant from the job participant_flows.remove_participant(user_email, job_id, db_session)
def log( db_session, source: str, description: str, job_id: int = None, worker_id: int = None, started_at: datetime = None, ended_at: datetime = None, details: dict = None, ) -> Event: """ Logs an event """ uuid = uuid4() if not started_at: started_at = datetime.datetime.utcnow() if not ended_at: ended_at = started_at event_in = EventCreate( uuid=uuid, started_at=started_at, ended_at=ended_at, source=source, description=description, details=details, ) event = create(db_session=db_session, event_in=event_in) if job_id: job = job_service.get(db_session=db_session, job_id=job_id) job.events.append(event) db_session.add(job) if worker_id: worker = worker_service.get(db_session=db_session, worker_id=worker_id) worker.events.append(event) db_session.add(worker) db_session.commit() logger.info(f"{source}: {description}") return event
def job_engage_oncall_flow(user_id: str, user_email: str, job_id: int, action: dict, db_session=None): """Runs the job engage oncall flow.""" oncall_service_id = action["submission"]["oncall_service_id"] page = action["submission"]["page"] # we load the job instance job = job_service.get(db_session=db_session, job_id=job_id) # we resolve the oncall service oncall_service = service_service.get_by_external_id( db_session=db_session, external_id=oncall_service_id) oncall_plugin = plugins.get(oncall_service.type) oncall_email = oncall_plugin.get(service_id=oncall_service_id) # we add the oncall to the job job_add_or_reactivate_participant_flow(oncall_email, job.id, db_session=db_session) event_service.log( db_session=db_session, source=oncall_plugin.job_code, description= f"{user_email} engages oncall service {oncall_service.name}", job_id=job.id, ) if page == "Yes": # we page the oncall oncall_plugin.page(oncall_service_id, job.name, job.job_code, job.description) event_service.log( db_session=db_session, source=oncall_plugin.job_code, description=f"{oncall_service.name} on-call paged", job_id=job.id, )
def job_update_flow(user_email: str, job_id: int, previous_job: JobRead, notify=True, db_session=None): """Runs the job update flow.""" # we load the job instance job = job_service.get(db_session=db_session, job_id=job_id) # we load the worker worker = worker_service.get_by_code(db_session=db_session, code=user_email) conversation_topic_change = False if previous_job.job_code != job.job_code: event_service.log( db_session=db_session, source="Job Participant", description= f'{worker.name} changed the job title to "{job.job_code}"', job_id=job.id, worker_id=worker.id, ) if previous_job.description != job.description: event_service.log( db_session=db_session, source="Job Participant", description=f"{worker.name} changed the job description", details={"description": job.description}, job_id=job.id, worker_id=worker.id, ) if previous_job.job_type.name != job.job_type.name: conversation_topic_change = True event_service.log( db_session=db_session, source="Job Participant", description= f"{worker.name} changed the job type to {job.job_type.name}", job_id=job.id, worker_id=worker.id, ) if previous_job.job_priority.name != job.job_priority.name: conversation_topic_change = True event_service.log( db_session=db_session, source="Job Participant", description= f"{worker.name} changed the job priority to {job.job_priority.name}", job_id=job.id, worker_id=worker.id, ) if previous_job.planning_status.value != job.planning_status: conversation_topic_change = True event_service.log( db_session=db_session, source="Job Participant", description= f"{worker.name} marked the job as {job.planning_status}", job_id=job.id, worker_id=worker.id, )
def job_stable_flow(job_id: int, command: Optional[dict] = None, db_session=None): """Runs the job stable flow.""" # we load the job instance job = job_service.get(db_session=db_session, job_id=job_id) # we set the stable time job.stable_at = datetime.utcnow() # we remind the job commander to write a tactical report send_job_report_reminder(job, ReportTypes.tactical_report) # we update the external ticket update_external_job_ticket(job, db_session) if not job.job_review_document: storage_plugin = plugins.get(INCIDENT_PLUGIN_STORAGE_SLUG) # we create a copy of the job review document template and we move it to the job storage job_review_document_name = f"{job.name} - Post Job Review Document" job_review_document = storage_plugin.copy_file( team_drive_id=job.storage.resource_id, file_id=INCIDENT_STORAGE_INCIDENT_REVIEW_FILE_ID, name=job_review_document_name, ) job_review_document.update({ "name": job_review_document_name, "resource_type": INCIDENT_RESOURCE_INCIDENT_REVIEW_DOCUMENT, }) storage_plugin.move_file(new_team_drive_id=job.storage.resource_id, file_id=job_review_document["id"]) event_service.log( db_session=db_session, source=storage_plugin.job_code, description="Job review document added to storage", job_id=job.id, ) document_in = DocumentCreate( name=job_review_document["name"], resource_id=job_review_document["id"], resource_type=job_review_document["resource_type"], weblink=job_review_document["weblink"], ) job.documents.append( document_service.create(db_session=db_session, document_in=document_in)) event_service.log( db_session=db_session, source="Dispatch Core App", description="Job review document added to job", job_id=job.id, ) # we update the job review document update_document( job_review_document["id"], job.name, job.job_priority.name, job.planning_status, job.job_type.name, job.job_code, job.description, job.commander.name, job.conversation.weblink, job.job_document.weblink, job.storage.weblink, job.ticket.weblink, ) # we send a notification about the job review document to the conversation send_job_review_document_notification(job.conversation.channel_id, job_review_document["weblink"]) db_session.add(job) db_session.commit()
def job_create_flow(*, job_id: int, checkpoint: str = None, db_session=None): """Creates all resources required for new jobs.""" job = job_service.get(db_session=db_session, job_id=job_id) # get the job participants based on job type and priority worker_participants, team_participants = get_job_participants( job, db_session) # add workers to job for worker in worker_participants: participant_flows.add_participant(user_code=worker.code, job_id=job.id, db_session=db_session) event_service.log( db_session=db_session, source="Dispatch Core App", description="Job participants added to job", job_id=job.id, ) # create the job ticket ticket = create_job_ticket(job, db_session) job.ticket = ticket_service.create(db_session=db_session, ticket_in=TicketCreate(**ticket)) event_service.log( db_session=db_session, source="Dispatch Core App", description="External ticket added to job", job_id=job.id, ) # we set the job name name = ticket["resource_id"] job.name = name # we create the participant groups (tactical and notification) try: worker_participants = [x.worker for x in job.participants] tactical_group, notification_group = create_participant_groups( job, worker_participants, team_participants, db_session) for g in [tactical_group, notification_group]: group_in = GroupCreate( name=g["name"], code=g["email"], resource_type=g["resource_type"], resource_id=g["resource_id"], weblink=g["weblink"], ) job.groups.append( group_service.create(db_session=db_session, group_in=group_in)) event_service.log( db_session=db_session, source="Dispatch Core App", description="Tactical and notification groups added to job", job_id=job.id, ) # we create storage resource storage = create_job_storage( job, [tactical_group["email"], notification_group["email"]], db_session) job.storage = storage_service.create( db_session=db_session, resource_id=storage["resource_id"], resource_type=storage["resource_type"], weblink=storage["weblink"], ) event_service.log( db_session=db_session, source="Dispatch Core App", description="Storage added to job", job_id=job.id, ) # we create the job documents job_document, job_sheet = create_collaboration_documents( job, db_session) faq_document = { "name": "Job FAQ", "resource_id": INCIDENT_FAQ_DOCUMENT_ID, "weblink": f"https://docs.google.com/document/d/{INCIDENT_FAQ_DOCUMENT_ID}", "resource_type": INCIDENT_RESOURCE_FAQ_DOCUMENT, } conversation_commands_reference_document = { "name": "Job Conversation Commands Reference Document", "resource_id": INCIDENT_CONVERSATION_COMMANDS_REFERENCE_DOCUMENT_ID, "weblink": f"https://docs.google.com/document/d/{INCIDENT_CONVERSATION_COMMANDS_REFERENCE_DOCUMENT_ID}", "resource_type": INCIDENT_RESOURCE_CONVERSATION_COMMANDS_REFERENCE_DOCUMENT, } for d in [ job_document, job_sheet, faq_document, conversation_commands_reference_document, ]: document_in = DocumentCreate( name=d["name"], resource_id=d["resource_id"], resource_type=d["resource_type"], weblink=d["weblink"], ) job.documents.append( document_service.create(db_session=db_session, document_in=document_in)) event_service.log( db_session=db_session, source="Dispatch Core App", description="Documents added to job", job_id=job.id, ) conference = create_conference(job, [tactical_group["email"]], db_session) conference_in = ConferenceCreate( resource_id=conference["resource_id"], resource_type=conference["resource_type"], weblink=conference["weblink"], conference_id=conference["id"], conference_challenge=conference["challenge"], ) job.conference = conference_service.create(db_session=db_session, conference_in=conference_in) event_service.log( db_session=db_session, source="Dispatch Core App", description="Conference added to job", job_id=job.id, ) # we create the conversation for real-time communications participant_emails = [x.worker.code for x in job.participants] conversation = create_conversation(job, participant_emails, db_session) conversation_in = ConversationCreate( resource_id=conversation["resource_id"], resource_type=conversation["resource_type"], weblink=conversation["weblink"], channel_id=conversation["id"], ) job.conversation = conversation_service.create( db_session=db_session, conversation_in=conversation_in) event_service.log( db_session=db_session, source="Dispatch Core App", description="Conversation added to job", job_id=job.id, ) except Exception as e: log.warn("failed to create ... , job_id ={}".format(job_id), e) db_session.add(job) db_session.commit() try: # we set the conversation topic set_conversation_topic(job) # we update the job ticket update_external_job_ticket(job, db_session) # we update the investigation document update_document( job_document["id"], job.name, job.job_priority.name, job.planning_status, job.job_type.name, job.job_code, job.description, job.commander.name, job.conversation.weblink, job_document["weblink"], job.storage.weblink, job.ticket.weblink, job.conference.weblink, job.conference.conference_challenge, ) for participant in job.participants: # we announce the participant in the conversation send_job_participant_announcement_message(participant.worker.code, job.id, db_session) # we send the welcome messages to the participant send_job_welcome_participant_messages(participant.worker.code, job.id, db_session) send_job_suggested_reading_messages(participant.worker.code, job.id, db_session) event_service.log( db_session=db_session, source="Dispatch Core App", description="Participants announced and welcome messages sent", job_id=job.id, ) if job.visibility == Visibility.open: send_job_notifications(job, db_session) event_service.log( db_session=db_session, source="Dispatch Core App", description="Job notifications sent", job_id=job.id, ) except Exception as e: log.warn("failed to create more ... , job_id ={}".format(job_id), e)