From 5ac65ca64a773d81a6aa916c2ba290f722ac6cf2 Mon Sep 17 00:00:00 2001 From: Shahar Glazner Date: Sun, 15 Sep 2024 15:23:08 +0300 Subject: [PATCH] feat: add the option to run workflow manually but with alert payload (#1925) --- .../workflows/builder/ReactFlowBuilder.tsx | 11 +++-- .../app/workflows/builder/ReactFlowEditor.tsx | 5 ++- keep-ui/app/workflows/builder/builder.tsx | 1 + keep-ui/app/workflows/builder/editors.tsx | 15 +++++-- .../workflow-run-with-alert-modal.tsx | 11 ++--- keep-ui/utils/hooks/useWorkflowRun.ts | 41 ++++++++++++------- 6 files changed, 56 insertions(+), 28 deletions(-) diff --git a/keep-ui/app/workflows/builder/ReactFlowBuilder.tsx b/keep-ui/app/workflows/builder/ReactFlowBuilder.tsx index ba303a9e2..77b7b861f 100644 --- a/keep-ui/app/workflows/builder/ReactFlowBuilder.tsx +++ b/keep-ui/app/workflows/builder/ReactFlowBuilder.tsx @@ -14,12 +14,14 @@ const nodeTypes = { custom: CustomNode as any }; const edgeTypes: EdgeTypesType = { "custom-edge": CustomEdge as React.ComponentType }; const ReactFlowBuilder = ({ + providers, installedProviders, toolboxConfiguration, definition, onDefinitionChange, validatorConfiguration }: { + providers: Provider[] | undefined | null; installedProviders: Provider[] | undefined | null; toolboxConfiguration: Record; definition: any; @@ -29,7 +31,7 @@ const ReactFlowBuilder = ({ }; onDefinitionChange:(def: Definition) => void; }) => { - + const { nodes, edges, @@ -60,13 +62,14 @@ const ReactFlowBuilder = ({ nodeTypes={nodeTypes} edgeTypes={edgeTypes} fitView - > + > )} - diff --git a/keep-ui/app/workflows/builder/ReactFlowEditor.tsx b/keep-ui/app/workflows/builder/ReactFlowEditor.tsx index 30ce85c5f..6848dd13c 100644 --- a/keep-ui/app/workflows/builder/ReactFlowEditor.tsx +++ b/keep-ui/app/workflows/builder/ReactFlowEditor.tsx @@ -9,10 +9,12 @@ import debounce from "lodash.debounce"; const ReactFlowEditor = ({ providers, + installedProviders, validatorConfiguration, onDefinitionChange }: { providers: Provider[] | undefined | null; + installedProviders: Provider[] | undefined | null; validatorConfiguration: { step: (step: V2Step, parent?: V2Step, defnition?: ReactFlowDefinition) => boolean; root: (def: Definition) => boolean; @@ -114,7 +116,7 @@ const ReactFlowEditor = ({
{!selectedNode?.includes('empty') && !isTrigger && } - {!selectedNode?.includes('empty') && !isTrigger && } + {!selectedNode?.includes('empty') && !isTrigger && }
@@ -124,4 +126,3 @@ const ReactFlowEditor = ({ }; export default ReactFlowEditor; - diff --git a/keep-ui/app/workflows/builder/builder.tsx b/keep-ui/app/workflows/builder/builder.tsx index 3b33d8470..020d13bf4 100644 --- a/keep-ui/app/workflows/builder/builder.tsx +++ b/keep-ui/app/workflows/builder/builder.tsx @@ -313,6 +313,7 @@ function Builder({
void); + providers?: Provider[] | null | undefined; installedProviders?: Provider[] | null | undefined; providerType?: string; type?: string; @@ -64,6 +65,7 @@ function KeepStepEditor({ properties, updateProperty, installedProviders, + providers, providerType, type, }: keepEditorProps) { @@ -86,6 +88,9 @@ function KeepStepEditor({ const installedProviderByType = installedProviders?.filter( (p) => p.type === providerType ); + const isThisProviderNeedsInstallation = providers?.some( + (p) => p.type === providerType && p.config && Object.keys(p.config).length > 0 + ) ?? false; const DynamicIcon = (props: any) => ( p.details?.name === providerConfig ) === undefined } errorMessage={`${ - providerConfig && + providerConfig && isThisProviderNeedsInstallation && installedProviderByType?.find( (p) => p.details?.name === providerConfig ) === undefined @@ -412,14 +418,16 @@ function WorkflowEditorV2({ export function StepEditorV2({ + providers, installedProviders, setSynced }: { + providers: Provider[] | undefined | null; installedProviders?: Provider[] | undefined | null; setSynced: (sync:boolean) => void; }) { const [formData, setFormData] = useState<{ name?: string; properties?: V2Properties, type?:string }>({}); - const { + const { selectedNode, updateSelectedNodeData, setOpneGlobalEditor, @@ -475,6 +483,7 @@ export function StepEditorV2({ ); -} \ No newline at end of file +} diff --git a/keep-ui/app/workflows/workflow-run-with-alert-modal.tsx b/keep-ui/app/workflows/workflow-run-with-alert-modal.tsx index bbf64d458..47b27321a 100644 --- a/keep-ui/app/workflows/workflow-run-with-alert-modal.tsx +++ b/keep-ui/app/workflows/workflow-run-with-alert-modal.tsx @@ -166,16 +166,17 @@ export default function AlertTriggerModal({ return (
- - Fields Defined As Workflow Filters - {Array.isArray(staticFields) && - staticFields.map((field, index) => ( + {Array.isArray(staticFields) && staticFields.length > 0 && ( + + Fields Defined As Workflow Filters + {staticFields.map((field, index) => (
))} - + + )} diff --git a/keep-ui/utils/hooks/useWorkflowRun.ts b/keep-ui/utils/hooks/useWorkflowRun.ts index a35ca86bc..3d5890cc5 100644 --- a/keep-ui/utils/hooks/useWorkflowRun.ts +++ b/keep-ui/utils/hooks/useWorkflowRun.ts @@ -2,7 +2,14 @@ import { useState } from "react"; import { useSession } from "next-auth/react"; import { getApiURL } from "utils/apiUrl"; import { useRouter } from "next/navigation"; +import { useProviders } from "./useProviders"; import { Filter, Workflow } from "app/workflows/models"; +import { Provider } from "app/providers/providers"; + +interface ProvidersData { + providers: { [key: string]: { providers: Provider[] } }; + } + export const useWorkflowRun = (workflow: Workflow) => { @@ -15,14 +22,23 @@ export const useWorkflowRun = (workflow: Workflow) => { const [alertFilters, setAlertFilters] = useState([]); const [alertDependencies, setAlertDependencies] = useState([]); + const { data: providersData = { providers: {} } as ProvidersData } = useProviders(); + const providers = providersData.providers; + + const apiUrl = getApiURL(); if (!workflow) { return {}; } - const allProvidersInstalled = workflow?.providers?.every( - (provider) => provider.installed - ); + + const notInstalledProviders = workflow?.providers?.filter((workflowProvider) => + !workflowProvider.installed && Object.values(providers || {}).some(provider => + provider.type === workflowProvider.type && (provider.config && Object.keys(provider.config).length > 0) + ) + ).map(provider => provider.type); + + const allProvidersInstalled = notInstalledProviders.length === 0; // Check if there is a manual trigger const hasManualTrigger = workflow?.triggers?.some( @@ -36,7 +52,7 @@ export const useWorkflowRun = (workflow: Workflow) => { const isWorkflowDisabled = !!workflow?.disabled const getDisabledTooltip = () => { - if (!allProvidersInstalled) return "Not all providers are installed."; + if (!allProvidersInstalled) return `Not all providers are installed: ${notInstalledProviders.join(", ")}`; if (!hasManualTrigger) return "No manual trigger available."; if(isWorkflowDisabled) { return "Workflow is Disabled"; @@ -107,28 +123,25 @@ export const useWorkflowRun = (workflow: Workflow) => { if (!workflow) { return; } - const hasAlertTrigger = workflow?.triggers?.some( - (trigger) => trigger.type === "alert" - ); + const dependencies = extractAlertDependencies(workflow?.workflow_raw); + const hasDependencies = dependencies.length > 0; - // if it needs alert payload, than open the modal - if (hasAlertTrigger) { + // if it has dependencies, open the modal + if (hasDependencies) { + setAlertDependencies(dependencies); // extract the filters // TODO: support more than one trigger - for (const trigger of workflow?.triggers) { - // at least one trigger is alert, o/w hasAlertTrigger was false + for (const trigger of workflow?.triggers || []) { if (trigger.type === "alert") { const staticAlertFilters = trigger.filters || []; setAlertFilters(staticAlertFilters); break; } } - const dependencies = extractAlertDependencies(workflow?.workflow_raw); - setAlertDependencies(dependencies); setIsAlertTriggerModalOpen(true); return; } - // else, manual trigger, just run it + // else, no dependencies, just run it else { runWorkflow({}); }