From bb5ef6f3ea6be80d00118c807326c2ff53f8938b Mon Sep 17 00:00:00 2001 From: Dipesh Mittal Date: Sat, 27 Apr 2024 16:24:48 +0530 Subject: [PATCH] Added boilerplate for test notification from workflow --- executor/workflows/tasks.py | 54 ++++++++++++++++++- executor/workflows/urls.py | 3 ++ executor/workflows/views.py | 29 +++++++++- .../Workflows/create/CreateWorkflow.jsx | 14 +++++ web/src/constants/api/workflows.ts | 1 + web/src/store/features/workflow/api/index.ts | 1 + .../api/testWorkflowNotificationApi.ts | 17 ++++++ 7 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 web/src/store/features/workflow/api/testWorkflowNotificationApi.ts diff --git a/executor/workflows/tasks.py b/executor/workflows/tasks.py index ee0a75d7f..f4a7c32a6 100644 --- a/executor/workflows/tasks.py +++ b/executor/workflows/tasks.py @@ -1,11 +1,16 @@ import logging from datetime import timedelta, datetime +import time + from celery import shared_task from django.conf import settings from accounts.models import Account from executor.crud.playbook_execution_crud import create_playbook_execution, get_db_playbook_execution +from executor.crud.playbooks_crud import get_db_playbook_step, get_db_playbook_task_definitions +from executor.models import PlayBook +from executor.task_executor import execute_task from executor.tasks import execute_playbook from executor.workflows.action.action_executor import action_executor from executor.workflows.crud.workflow_execution_crud import get_db_workflow_executions, \ @@ -21,7 +26,7 @@ from playbooks.utils.utils import current_datetime from protos.base_pb2 import TimeRange from protos.playbooks.intelligence_layer.interpreter_pb2 import InterpreterType, Interpretation as InterpretationProto -from protos.playbooks.playbook_pb2 import PlaybookExecution as PlaybookExecutionProto +from protos.playbooks.playbook_pb2 import PlaybookExecution as PlaybookExecutionProto, PlaybookExecutionLog, PlaybookTaskDefinition, PlaybookTaskExecutionResult from protos.playbooks.workflow_pb2 import WorkflowExecutionStatusType, Workflow as WorkflowProto, \ WorkflowAction as WorkflowActionProto, WorkflowActionNotificationConfig, WorkflowActionSlackNotificationConfig from utils.proto_utils import dict_to_proto, proto_to_dict @@ -195,3 +200,50 @@ def workflow_action_execution(account_id, workflow_id, workflow_execution_id, pl workflow_action_execution_prerun_notifier = publish_pre_run_task(workflow_action_execution) workflow_action_execution_failure_notifier = publish_task_failure(workflow_action_execution) workflow_action_execution_postrun_notifier = publish_post_run_task(workflow_action_execution) + + +# @shared_task() +def test_workflow_notification(account_id, workflow, message_type): + if message_type == WorkflowActionSlackNotificationConfig.MessageType.MESSAGE: + pe_logs = [] + account = Account.objects.get(id=account_id) + time_range = { "time_lt": str(int(time.time())), "time_geq": str(int(time.time()) - 3600) } + tr: TimeRange = dict_to_proto(time_range, TimeRange) + + playbook_id = workflow.playbooks[0].id.value + playbook = PlayBook.objects.get(id=playbook_id) + p_proto = playbook.proto + + playbook_steps = get_db_playbook_step(account, playbook_id, is_active=True) + try: + all_step_executions = {} + for step in list(playbook_steps): + playbook_task_definitions = get_db_playbook_task_definitions(account, playbook_id, step.id, is_active=True) + playbook_task_definitions = playbook_task_definitions.order_by('created_at') + all_task_executions = [] + for task in playbook_task_definitions: + task_proto = task.proto + task_result = execute_task(account_id, tr, task_proto) + all_task_executions.append({ + 'task': task, + 'task_result': proto_to_dict(task_result), + 'task_result_proto': task_result, + }) + all_step_executions[step] = all_task_executions + + for step, all_task_results in all_step_executions.items(): + for result in all_task_results: + playbook_execution_log = PlaybookExecutionLog( + playbook=playbook, + playbook_step=step, + playbook_task_definition=result['task'].proto, + playbook_task_result=result['task_result_proto'], + ) + pe_logs.append(playbook_execution_log) + + except Exception as exc: + logger.error(f"Error occurred while running playbook: {exc}") + + execution_output: [InterpretationProto] = playbook_execution_result_interpret(InterpreterType.BASIC_I, p_proto, + pe_logs) + action_executor(account, workflow.actions[0], execution_output) \ No newline at end of file diff --git a/executor/workflows/urls.py b/executor/workflows/urls.py index c8e298e93..7ed6a3178 100644 --- a/executor/workflows/urls.py +++ b/executor/workflows/urls.py @@ -9,6 +9,9 @@ path('create', workflow_views.workflows_create), path('update', workflow_views.workflows_update), + # Test Notification + path('test_notification', workflow_views.test_workflows_notification), + # Workflow Execution APIs path('execute', workflow_views.workflows_execute), path('executions/get', workflow_views.workflows_execution_get), diff --git a/executor/workflows/views.py b/executor/workflows/views.py index 1bd5971f5..69eac5bc1 100644 --- a/executor/workflows/views.py +++ b/executor/workflows/views.py @@ -11,6 +11,7 @@ from executor.workflows.crud.workflow_execution_utils import create_workflow_execution_util from executor.workflows.crud.workflows_crud import create_db_workflow from executor.workflows.crud.workflows_update_processor import workflows_update_processor +from executor.workflows.tasks import test_workflow_notification from playbooks.utils.decorators import web_api from playbooks.utils.meta import get_meta from playbooks.utils.queryset import filter_page @@ -20,7 +21,7 @@ CreateWorkflowResponse, UpdateWorkflowRequest, UpdateWorkflowResponse, ExecuteWorkflowRequest, \ ExecuteWorkflowResponse, ExecutionWorkflowGetRequest, ExecutionWorkflowGetResponse, ExecutionsWorkflowsListResponse, \ ExecutionsWorkflowsListRequest -from protos.playbooks.workflow_pb2 import Workflow as WorkflowProto, WorkflowSchedule as WorkflowScheduleProto +from protos.playbooks.workflow_pb2 import Workflow as WorkflowProto, WorkflowActionSlackNotificationConfig, WorkflowSchedule as WorkflowScheduleProto from utils.proto_utils import dict_to_proto logger = logging.getLogger(__name__) @@ -94,6 +95,32 @@ def workflows_update(request_message: UpdateWorkflowRequest) -> Union[UpdateWork return UpdateWorkflowResponse(success=BoolValue(value=True)) +@web_api(CreateWorkflowRequest) +def test_workflows_notification(request_message: CreateWorkflowRequest) -> Union[CreateWorkflowResponse, HttpResponse]: + account: Account = get_request_account() + user = get_request_user() + workflow: WorkflowProto = request_message.workflow + + if not workflow.playbooks or workflow.playbooks == []: + return CreateWorkflowResponse(success=BoolValue(value=False), + message=Message(title="Invalid Request", description="Select a playbook")) + + if not workflow.entry_points or workflow.entry_points == []: + return CreateWorkflowResponse(success=BoolValue(value=False), + message=Message(title="Invalid Request", description="Select the trigger type")) + + if not workflow.entry_points[0].alert_config.slack_channel_alert_config.slack_channel_id: + return CreateWorkflowResponse(success=BoolValue(value=False), + message=Message(title="Invalid Request", description="Select a slack channel")) + + if not workflow.actions or workflow.actions == []: + return CreateWorkflowResponse(success=BoolValue(value=False), + message=Message(title="Invalid Request", description="Select a notification type")) + + test_workflow_notification(account.id, workflow, workflow.actions[0].notification_config.slack_config.message_type) + return CreateWorkflowResponse(success=BoolValue(value=True)) + + @web_api(ExecuteWorkflowRequest) def workflows_execute(request_message: ExecuteWorkflowRequest) -> Union[ExecuteWorkflowResponse, HttpResponse]: account: Account = get_request_account() diff --git a/web/src/components/Workflows/create/CreateWorkflow.jsx b/web/src/components/Workflows/create/CreateWorkflow.jsx index 4c93b5a3c..0c48c2d23 100644 --- a/web/src/components/Workflows/create/CreateWorkflow.jsx +++ b/web/src/components/Workflows/create/CreateWorkflow.jsx @@ -16,6 +16,7 @@ import { showSnackbar } from "../../../store/features/snackbar/snackbarSlice.ts" import { useLazyGetWorkflowQuery } from "../../../store/features/workflow/api/getWorkflowApi.ts"; import Loading from "../../common/Loading/index.tsx"; import { useUpdateWorkflowMutation } from "../../../store/features/workflow/api/updateWorkflowApi.ts"; +import { useLazyTestWorkflowNotificationQuery } from "../../../store/features/workflow/api/testWorkflowNotificationApi.ts"; import { stateToWorkflow } from "../../../utils/parser/workflow/stateToWorkflow.ts"; import { validate } from "./utils/validation.ts"; @@ -28,6 +29,8 @@ function CreateTrigger() { useUpdateWorkflowMutation(); const [triggerGetWorkflow, { isLoading: workflowLoading }] = useLazyGetWorkflowQuery(); + const [triggerTestWorkflowNotification] = + useLazyTestWorkflowNotificationQuery(); const currentWorkflow = useSelector(currentWorkflowSelector); const handleSave = async () => { @@ -51,6 +54,11 @@ function CreateTrigger() { } }; + const handleTestNotification = () => { + triggerTestWorkflowNotification(); + dispatch(showSnackbar("Test Notification Sent")); + }; + useEffect(() => { return () => { dispatch(resetWorkflowState()); @@ -89,6 +97,12 @@ function CreateTrigger() { type="submit"> {workflowId ? "Update" : "Save"} + {/* */} {(isLoading || updateLoading) && } diff --git a/web/src/constants/api/workflows.ts b/web/src/constants/api/workflows.ts index 8e554bf12..92f953049 100644 --- a/web/src/constants/api/workflows.ts +++ b/web/src/constants/api/workflows.ts @@ -2,6 +2,7 @@ export const GET_WORKFLOWS = "/executor/workflows/get"; export const CREATE_WORKFLOW = "/executor/workflows/create"; +export const TEST_WORKFLOW_NOTIFICATION = "/executor/workflows/test_notification"; export const UPDATE_WORKFLOW = "/executor/workflows/update"; export const DELETE_WORKFLOW = "/executor/workflows/update"; export const GET_WORKFLOW_EXECUTIONS = "/executor/workflows/executions/list"; diff --git a/web/src/store/features/workflow/api/index.ts b/web/src/store/features/workflow/api/index.ts index f0e16c024..3f33031e5 100644 --- a/web/src/store/features/workflow/api/index.ts +++ b/web/src/store/features/workflow/api/index.ts @@ -1,2 +1,3 @@ export * from "./getWorkflowsApi.ts"; export * from "./createWorkflowApi.ts"; +export * from "./testWorkflowNotificationApi.ts"; diff --git a/web/src/store/features/workflow/api/testWorkflowNotificationApi.ts b/web/src/store/features/workflow/api/testWorkflowNotificationApi.ts new file mode 100644 index 000000000..3609d5543 --- /dev/null +++ b/web/src/store/features/workflow/api/testWorkflowNotificationApi.ts @@ -0,0 +1,17 @@ +import { TEST_WORKFLOW_NOTIFICATION } from "../../../../constants/index.ts"; +import { stateToWorkflow } from "../../../../utils/parser/workflow/stateToWorkflow.ts"; +import { apiSlice } from "../../../app/apiSlice.ts"; + +export const testWorkflowNotificationApi = apiSlice.injectEndpoints({ + endpoints: (builder) => ({ + testWorkflowNotification: builder.query({ + query: () => ({ + url: TEST_WORKFLOW_NOTIFICATION, + body: stateToWorkflow(), + method: "POST", + }) + }), + }), +}); + +export const { useLazyTestWorkflowNotificationQuery } = testWorkflowNotificationApi;