diff --git a/docs/workflows/syntax/basic-syntax.mdx b/docs/workflows/syntax/basic-syntax.mdx index 9d2c7be95..143dff685 100644 --- a/docs/workflows/syntax/basic-syntax.mdx +++ b/docs/workflows/syntax/basic-syntax.mdx @@ -41,6 +41,7 @@ workflow: workflow: id: raw-sql-query description: Monitor that time difference is no more than 1 hour + disabled: Optionally prevent this workflow from running steps: - actions: diff --git a/keep-ui/app/workflows/builder/builder-validators.tsx b/keep-ui/app/workflows/builder/builder-validators.tsx index fe03e776f..b9742a2ed 100644 --- a/keep-ui/app/workflows/builder/builder-validators.tsx +++ b/keep-ui/app/workflows/builder/builder-validators.tsx @@ -18,7 +18,7 @@ export function globalValidatorV2( if ( !!definition?.properties && - !definition.properties['manual'] && + !definition.properties['manual'] && !definition.properties['interval'] && !definition.properties['alert'] ) { diff --git a/keep-ui/app/workflows/builder/builder.tsx b/keep-ui/app/workflows/builder/builder.tsx index b2c7565e6..3b33d8470 100644 --- a/keep-ui/app/workflows/builder/builder.tsx +++ b/keep-ui/app/workflows/builder/builder.tsx @@ -186,7 +186,7 @@ function Builder({ triggers = { alert: { source: alertSource, name: alertName } }; } setDefinition( - wrapDefinitionV2({...generateWorkflow(alertUuid, "", "", [], [], triggers), isValid: true}) + wrapDefinitionV2({...generateWorkflow(alertUuid, "", "", false,[], [], triggers), isValid: true}) ); } else { setDefinition(wrapDefinitionV2({...parseWorkflow(loadedAlertFile!, providers), isValid:true})); diff --git a/keep-ui/app/workflows/builder/editors.tsx b/keep-ui/app/workflows/builder/editors.tsx index c7a44771f..8ebcc7d76 100644 --- a/keep-ui/app/workflows/builder/editors.tsx +++ b/keep-ui/app/workflows/builder/editors.tsx @@ -292,81 +292,118 @@ function WorkflowEditorV2({ const isTrigger = ["manual", "alert", 'interval'].includes(key) ; renderDivider = isTrigger && key === selectedNode ? !renderDivider : false; return ( -
- { renderDivider && } - {((key === selectedNode)||(!isTrigger)) && {key}} - {key === "manual" ? ( - selectedNode === 'manual' &&
- - setProperties({ - ...properties, - [key]: e.target.checked ? "true" : "false", - }) - } - disabled={true} - /> +
+ {renderDivider && } + {((key === selectedNode) || (!isTrigger)) && {key}} + + {(() => { + switch (key) { + case "manual": + return ( + selectedNode === "manual" && ( +
+ + setProperties({ + ...properties, + [key]: e.target.checked ? "true" : "false", + }) + } + disabled={true} + /> +
+ ) + ); + + case "alert": + return ( + selectedNode === "alert" && ( + <> +
+ +
+ {properties.alert && + Object.keys(properties.alert as {}).map((filter) => { + return ( + <> + {filter} +
+ + updateAlertFilter(filter, e.target.value) + } + value={(properties.alert as any)[filter] as string} + /> + deleteFilter(filter)} + /> +
+ + ); + })} + + ) + ); + + case "interval": + return ( + selectedNode === "interval" && ( + + setProperties({ ...properties, [key]: e.target.value }) + } + value={properties[key] as string} + /> + ) + ); + case "disabled": + return ( +
+ + setProperties({ + ...properties, + [key]: e.target.checked ? "true" : "false", + }) + } + /> +
+ ); + default: + return ( + + setProperties({ ...properties, [key]: e.target.value }) + } + value={properties[key] as string} + /> + ); + } + })()}
- ) : key === "alert" ? ( - selectedNode === 'alert' && <> -
- -
- {properties.alert && - Object.keys(properties.alert as {}).map((filter) => { - return ( - <> - {filter} -
- - updateAlertFilter(filter, e.target.value) - } - value={(properties.alert as any)[filter] as string} - /> - deleteFilter(filter)} - /> -
- - ); - })} - - ) : key === "interval" ? ( - selectedNode === 'interval' && - setProperties({ ...properties, [key]: e.target.value }) - } - value={properties[key] as string} - /> - ): - setProperties({ ...properties, [key]: e.target.value }) - } - value={properties[key] as string} - />} - -
- ); + ); + })} ); diff --git a/keep-ui/app/workflows/builder/utils.tsx b/keep-ui/app/workflows/builder/utils.tsx index d89ef574e..4ac183c58 100644 --- a/keep-ui/app/workflows/builder/utils.tsx +++ b/keep-ui/app/workflows/builder/utils.tsx @@ -211,6 +211,7 @@ export function generateWorkflow( workflowId: string, name: string, description: string, + disabled: boolean, steps: V2Step[], conditions: V2Step[], triggers: { [key: string]: { [key: string]: string } } = {} @@ -225,6 +226,7 @@ export function generateWorkflow( id: workflowId, name: name, description: description, + disabled:disabled, isLocked: true, ...triggers, }, @@ -305,6 +307,7 @@ export function parseWorkflow( workflow.id, workflow.name, workflow.description, + workflow.disabled, steps, conditions, triggers @@ -384,6 +387,7 @@ export function buildAlert(definition: Definition): Alert { const alertId = alert.properties.id as string; const name = (alert.properties.name as string) ?? ""; const description = (alert.properties.description as string) ?? ""; + const disabled = (alert.properties.disabled) ?? false const owners = (alert.properties.owners as string[]) ?? []; const services = (alert.properties.services as string[]) ?? []; // Steps (move to func?) @@ -510,6 +514,7 @@ export function buildAlert(definition: Definition): Alert { name: name, triggers: triggers, description: description, + disabled : Boolean(disabled), owners: owners, services: services, steps: steps, diff --git a/keep-ui/app/workflows/models.tsx b/keep-ui/app/workflows/models.tsx index 78110f86b..7b548a518 100644 --- a/keep-ui/app/workflows/models.tsx +++ b/keep-ui/app/workflows/models.tsx @@ -33,6 +33,7 @@ export type Workflow = { interval: string; providers: Provider[]; triggers: Trigger[]; + disabled:boolean, last_execution_time: string; last_execution_status: string; last_updated: string; diff --git a/keep-ui/app/workflows/workflow-menu.tsx b/keep-ui/app/workflows/workflow-menu.tsx index b15651a81..732ff3d68 100644 --- a/keep-ui/app/workflows/workflow-menu.tsx +++ b/keep-ui/app/workflows/workflow-menu.tsx @@ -3,7 +3,7 @@ import { Fragment } from "react"; import { EllipsisHorizontalIcon } from "@heroicons/react/20/solid"; import { Icon } from "@tremor/react"; import { EyeIcon, PencilIcon, PlayIcon, TrashIcon, WrenchIcon } from "@heroicons/react/24/outline"; -import { DownloadIcon } from "@radix-ui/react-icons"; +import {DownloadIcon, LockClosedIcon, LockOpen1Icon} from "@radix-ui/react-icons"; interface WorkflowMenuProps { onDelete?: () => Promise; @@ -14,6 +14,7 @@ interface WorkflowMenuProps { allProvidersInstalled: boolean; hasManualTrigger: boolean; hasAlertTrigger: boolean; + isWorkflowDisabled:boolean } @@ -25,18 +26,20 @@ export default function WorkflowMenu({ onBuilder, allProvidersInstalled, hasManualTrigger, - hasAlertTrigger + hasAlertTrigger, + isWorkflowDisabled, }: WorkflowMenuProps) { const getDisabledTooltip = () => { if (!allProvidersInstalled) return "Not all providers are installed."; if (!hasManualTrigger) return "No manual trigger available."; + if (isWorkflowDisabled) return "Workflow is disabled"; return ""; }; const stopPropagation = (e: React.MouseEvent) => { e.stopPropagation(); }; - const isRunButtonDisabled = !allProvidersInstalled || (!hasManualTrigger && !hasAlertTrigger); + const isRunButtonDisabled = !allProvidersInstalled || (!hasManualTrigger && !hasAlertTrigger) || isWorkflowDisabled; return ( diff --git a/keep-ui/app/workflows/workflow-tile.tsx b/keep-ui/app/workflows/workflow-tile.tsx index 383daf703..28307ff5d 100644 --- a/keep-ui/app/workflows/workflow-tile.tsx +++ b/keep-ui/app/workflows/workflow-tile.tsx @@ -78,6 +78,7 @@ function WorkflowMenuSection({ onView={onView} onBuilder={onBuilder} allProvidersInstalled={allProvidersInstalled} + isWorkflowDisabled={workflow.disabled} hasManualTrigger={hasManualTrigger} hasAlertTrigger={hasAlertTrigger} /> @@ -1080,6 +1081,12 @@ export function WorkflowTileOld({ workflow }: { workflow: Workflow }) { : "N/A"} + + Disabled + + {workflow?.disabled?.toString()} + + diff --git a/keep/api/core/db.py b/keep/api/core/db.py index c31353dfd..9d7d5c40b 100644 --- a/keep/api/core/db.py +++ b/keep/api/core/db.py @@ -156,6 +156,7 @@ def get_workflows_that_should_run(): workflows_with_interval = ( session.query(Workflow) .filter(Workflow.is_deleted == False) + .filter(Workflow.is_disabled == False) .filter(Workflow.interval != None) .filter(Workflow.interval > 0) .all() @@ -276,6 +277,7 @@ def add_or_update_workflow( created_by, interval, workflow_raw, + is_disabled, updated_by=None, ) -> Workflow: with Session(engine, expire_on_commit=False) as session: @@ -299,6 +301,7 @@ def add_or_update_workflow( existing_workflow.revision += 1 # Increment the revision existing_workflow.last_updated = datetime.now() # Update last_updated existing_workflow.is_deleted = False + existing_workflow.is_disabled= is_disabled else: # Create a new workflow @@ -310,6 +313,7 @@ def add_or_update_workflow( created_by=created_by, updated_by=updated_by, # Set updated_by to the provided value interval=interval, + is_disabled =is_disabled, workflow_raw=workflow_raw, ) session.add(workflow) @@ -495,7 +499,6 @@ def get_raw_workflow(tenant_id: str, workflow_id: str) -> str: return None return workflow.workflow_raw - def update_provider_last_pull_time(tenant_id: str, provider_id: str): extra = {"tenant_id": tenant_id, "provider_id": provider_id} logger.info("Updating provider last pull time", extra=extra) @@ -1333,7 +1336,7 @@ def save_workflow_results(tenant_id, workflow_execution_id, workflow_results): session.commit() -def get_workflow_id_by_name(tenant_id, workflow_name): +def get_workflow_by_name(tenant_id, workflow_name): with Session(engine) as session: workflow = session.exec( select(Workflow) @@ -1342,8 +1345,7 @@ def get_workflow_id_by_name(tenant_id, workflow_name): .where(Workflow.is_deleted == False) ).first() - if workflow: - return workflow.id + return workflow def get_previous_execution_id(tenant_id, workflow_id, workflow_execution_id): diff --git a/keep/api/models/db/migrations/versions/2024-08-30-09-34_7ed12220a0d3.py b/keep/api/models/db/migrations/versions/2024-08-30-09-34_7ed12220a0d3.py new file mode 100644 index 000000000..97c81789b --- /dev/null +++ b/keep/api/models/db/migrations/versions/2024-08-30-09-34_7ed12220a0d3.py @@ -0,0 +1,82 @@ +"""Added is_disabled to workflows + +Revision ID: 7ed12220a0d3 +Revises: 1c650a429672 +Create Date: 2024-08-30 09:34:41.782797 + +""" + +import sqlalchemy as sa +import yaml +from alembic import op + +from keep.parser.parser import Parser + +# revision identifiers, used by Alembic. +revision = "7ed12220a0d3" +down_revision = "1c650a429672" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + with op.batch_alter_table("workflow", schema=None) as batch_op: + batch_op.add_column(sa.Column("is_disabled", sa.Boolean(), nullable=False, server_default=sa.false())) + + connection = op.get_bind() + workflows = connection.execute(sa.text("SELECT id, workflow_raw FROM workflow")).fetchall() + + updates = [] + for workflow in workflows: + try: + workflow_yaml = yaml.safe_load(workflow.workflow_raw) + # If, by any chance, the existing workflow YAML's "disabled" value resolves to true, + # we need to update the database to set `is_disabled` to `True` + if Parser.parse_disabled(workflow_yaml): + updates.append({ + 'id': workflow.id, + 'is_disabled': True + }) + except Exception as e: + print(f"Failed to parse workflow_raw for workflow id {workflow.id}: {e}") + continue + + if updates: + connection.execute( + sa.text( + "UPDATE workflow SET is_disabled = :is_disabled WHERE id = :id" + ), + updates + ) + + + +def downgrade() -> None: + with op.batch_alter_table("workflow", schema=None) as batch_op: + batch_op.drop_column("is_disabled") + + connection = op.get_bind() + workflows = connection.execute(sa.text("SELECT id, workflow_raw FROM workflow")).fetchall() + + updates = [] + for workflow in workflows: + try: + workflow_yaml = yaml.safe_load(workflow.workflow_raw) + if 'disabled' in workflow_yaml: + workflow_yaml.pop('disabled', None) + updated_workflow_raw = yaml.safe_dump(workflow_yaml) + updates.append({ + 'id': workflow.id, + 'workflow_raw': updated_workflow_raw + }) + except Exception as e: + print(f"Failed to parse workflow_raw for workflow id {workflow.id}: {e}") + continue + + if updates: + connection.execute( + sa.text( + "UPDATE workflow SET workflow_raw = :workflow_raw WHERE id = :id" + ), + updates + ) diff --git a/keep/api/models/db/migrations/versions/2024-09-04-09-38_b30d2141e1cb.py b/keep/api/models/db/migrations/versions/2024-09-04-09-38_b30d2141e1cb.py new file mode 100644 index 000000000..efe165a13 --- /dev/null +++ b/keep/api/models/db/migrations/versions/2024-09-04-09-38_b30d2141e1cb.py @@ -0,0 +1,21 @@ +"""Merge migrations to resolve double-headed issue + +Revision ID: b30d2141e1cb +Revises: 7ed12220a0d3, 49e7c02579db +Create Date: 2024-09-04 09:38:33.869973 + +""" + +# revision identifiers, used by Alembic. +revision = "b30d2141e1cb" +down_revision = ("7ed12220a0d3", "49e7c02579db") +branch_labels = None +depends_on = None + + +def upgrade() -> None: + pass + + +def downgrade() -> None: + pass diff --git a/keep/api/models/db/migrations/versions/2024-09-10-17-59_710b4ff1d19e.py b/keep/api/models/db/migrations/versions/2024-09-10-17-59_710b4ff1d19e.py new file mode 100644 index 000000000..c2a93173d --- /dev/null +++ b/keep/api/models/db/migrations/versions/2024-09-10-17-59_710b4ff1d19e.py @@ -0,0 +1,21 @@ +"""empty message + +Revision ID: 710b4ff1d19e +Revises: 1aacee84447e, b30d2141e1cb +Create Date: 2024-09-10 17:59:56.210094 + +""" + +# revision identifiers, used by Alembic. +revision = "710b4ff1d19e" +down_revision = ("1aacee84447e", "b30d2141e1cb") +branch_labels = None +depends_on = None + + +def upgrade() -> None: + pass + + +def downgrade() -> None: + pass diff --git a/keep/api/models/db/workflow.py b/keep/api/models/db/workflow.py index c03f56ab7..f2c575863 100644 --- a/keep/api/models/db/workflow.py +++ b/keep/api/models/db/workflow.py @@ -16,6 +16,7 @@ class Workflow(SQLModel, table=True): interval: Optional[int] workflow_raw: str = Field(sa_column=Column(TEXT)) is_deleted: bool = Field(default=False) + is_disabled: bool = Field(default=False) revision: int = Field(default=1, nullable=False) last_updated: datetime = Field(default_factory=datetime.utcnow) diff --git a/keep/api/models/workflow.py b/keep/api/models/workflow.py index 743a2efcc..8c6b75314 100644 --- a/keep/api/models/workflow.py +++ b/keep/api/models/workflow.py @@ -29,6 +29,7 @@ class WorkflowDTO(BaseModel): creation_time: datetime triggers: List[dict] = None interval: int + disabled:bool last_execution_time: datetime = None last_execution_status: str = None providers: List[ProviderDTO] @@ -66,9 +67,10 @@ def manipulate_raw(cls, raw, values): ordered_raw["id"] = d.get("id") values["workflow_raw_id"] = d.get("id") ordered_raw["description"] = d.get("description") + ordered_raw["disabled"] = d.get("disabled") ordered_raw["triggers"] = d.get("triggers") for key, val in d.items(): - if key not in ["id", "description", "triggers", "steps", "actions"]: + if key not in ["id", "description", "disabled", "triggers", "steps", "actions"]: ordered_raw[key] = val # than steps and actions ordered_raw["steps"] = d.get("steps") diff --git a/keep/api/routes/workflows.py b/keep/api/routes/workflows.py index 8f89dbe3a..173341d85 100644 --- a/keep/api/routes/workflows.py +++ b/keep/api/routes/workflows.py @@ -27,7 +27,7 @@ get_workflow, ) from keep.api.core.db import get_workflow_executions as get_workflow_executions_db -from keep.api.core.db import get_workflow_id_by_name +from keep.api.core.db import get_workflow_by_name from keep.api.models.alert import AlertDto from keep.api.models.workflow import ( ProviderDTO, @@ -215,7 +215,7 @@ def run_workflow( # if the workflow id is the name of the workflow (e.g. the CLI has only the name) if not validators.uuid(workflow_id): logger.info("Workflow ID is not a UUID, trying to get the ID by name") - workflow_id = get_workflow_id_by_name(tenant_id, workflow_id) + workflow_id = getattr(get_workflow_by_name(tenant_id, workflow_id), 'id', None) workflowmanager = WorkflowManager.get_instance() # Finally, run it diff --git a/keep/parser/parser.py b/keep/parser/parser.py index 1398261fc..33ae67c7e 100644 --- a/keep/parser/parser.py +++ b/keep/parser/parser.py @@ -150,6 +150,7 @@ def _parse_workflow( tenant_id, context_manager, workflow, actions_file, workflow_actions ) workflow_id = self._parse_id(workflow) + workflow_disabled = self.__class__.parse_disabled(workflow) workflow_owners = self._parse_owners(workflow) workflow_tags = self._parse_tags(workflow) workflow_steps = self._parse_steps(context_manager, workflow) @@ -168,6 +169,7 @@ def _parse_workflow( workflow = Workflow( workflow_id=workflow_id, workflow_description=workflow.get("description"), + workflow_disabled=workflow_disabled, workflow_owners=workflow_owners, workflow_tags=workflow_tags, workflow_interval=workflow_interval, @@ -323,6 +325,11 @@ def parse_interval(self, workflow) -> int: workflow_interval = trigger.get("value", 0) return workflow_interval + @staticmethod + def parse_disabled(workflow_dict: dict) -> bool: + workflow_is_disabled_in_yml = workflow_dict.get("disabled") + return True if (workflow_is_disabled_in_yml == "true" or workflow_is_disabled_in_yml is True) else False + @staticmethod def parse_provider_parameters(provider_parameters: dict) -> dict: parsed_provider_parameters = {} diff --git a/keep/workflowmanager/workflow.py b/keep/workflowmanager/workflow.py index 66299ea1e..f945da2f2 100644 --- a/keep/workflowmanager/workflow.py +++ b/keep/workflowmanager/workflow.py @@ -27,6 +27,7 @@ def __init__( workflow_steps: typing.List[Step], workflow_actions: typing.List[Step], workflow_description: str = None, + workflow_disabled:bool = False, workflow_providers: typing.List[dict] = None, workflow_providers_type: typing.List[str] = [], workflow_strategy: WorkflowStrategy = WorkflowStrategy.NONPARALLEL_WITH_RETRY.value, @@ -40,6 +41,7 @@ def __init__( self.workflow_steps = workflow_steps self.workflow_actions = workflow_actions self.workflow_description = workflow_description + self.workflow_disabled = workflow_disabled self.workflow_providers = workflow_providers self.workflow_providers_type = workflow_providers_type self.workflow_strategy = workflow_strategy @@ -87,6 +89,9 @@ def run_actions(self): return actions_firing, actions_errors def run(self, workflow_execution_id): + if self.workflow_disabled: + self.logger.info(f"Skipping disabled workflow {self.workflow_id}") + return self.logger.info(f"Running workflow {self.workflow_id}") self.context_manager.set_execution_context(workflow_execution_id) try: diff --git a/keep/workflowmanager/workflowmanager.py b/keep/workflowmanager/workflowmanager.py index 8e49ad386..d50ab8939 100644 --- a/keep/workflowmanager/workflowmanager.py +++ b/keep/workflowmanager/workflowmanager.py @@ -79,6 +79,12 @@ def insert_events(self, tenant_id, events: typing.List[AlertDto]): }, ) for workflow_model in all_workflow_models: + if workflow_model.is_disabled: + self.logger.debug( + f"Skipping the workflow: id={workflow_model.id}, name={workflow_model.name}, " + f"tenant_id={workflow_model.tenant_id} - Workflow is disabled." + ) + continue try: # get the actual workflow that can be triggered self.logger.info("Getting workflow from store") diff --git a/keep/workflowmanager/workflowstore.py b/keep/workflowmanager/workflowstore.py index 231627625..7eda1943c 100644 --- a/keep/workflowmanager/workflowstore.py +++ b/keep/workflowmanager/workflowstore.py @@ -42,6 +42,7 @@ def create_workflow(self, tenant_id: str, created_by, workflow: dict): workflow["name"] = workflow_name else: workflow_name = workflow.get("name") + workflow = add_or_update_workflow( id=str(uuid.uuid4()), name=workflow_name, @@ -49,6 +50,7 @@ def create_workflow(self, tenant_id: str, created_by, workflow: dict): description=workflow.get("description"), created_by=created_by, interval=interval, + is_disabled=Parser.parse_disabled(workflow), workflow_raw=yaml.dump(workflow), ) self.logger.info(f"Workflow {workflow_id} created successfully") diff --git a/tests/test_workflow_execution.py b/tests/test_workflow_execution.py index 67b8014ed..b772391fd 100644 --- a/tests/test_workflow_execution.py +++ b/tests/test_workflow_execution.py @@ -475,3 +475,103 @@ def test_workflow_execution3( elif expected_tier == 1: assert workflow_execution.results["send-slack-message-tier-0"] == [] assert "Tier 1" in workflow_execution.results["send-slack-message-tier-1"][0] + + + +workflow_definition_for_enabled_disabled = """workflow: +id: %s +description: Handle alerts based on startedAt timestamp +triggers: +- type: alert + filters: + - key: name + value: "server-is-down" +actions: +- name: send-slack-message-tier-0 + if: keep.get_firing_time('{{ alert }}', 'minutes') > 0 and keep.get_firing_time('{{ alert }}', 'minutes') < 10 + provider: + type: console + with: + message: | + "Tier 0 Alert: {{ alert.name }} - {{ alert.description }} + Alert details: {{ alert }}" +- name: send-slack-message-tier-1 + if: "keep.get_firing_time('{{ alert }}', 'minutes') >= 10 and keep.get_firing_time('{{ alert }}', 'minutes') < 30" + provider: + type: console + with: + message: | + "Tier 1 Alert: {{ alert.name }} - {{ alert.description }} + Alert details: {{ alert }}" +""" + + +def test_workflow_execution_with_disabled_workflow( + db_session, + create_alert, + workflow_manager, +): + enabled_id = "enabled-workflow" + enabled_workflow = Workflow( + id=enabled_id, + name="enabled-workflow", + tenant_id=SINGLE_TENANT_UUID, + description="This workflow is enabled and should be executed", + created_by="test@keephq.dev", + interval=0, + is_disabled=False, + workflow_raw=workflow_definition_for_enabled_disabled % enabled_id + ) + + disabled_id = "disabled-workflow" + disabled_workflow = Workflow( + id=disabled_id, + name="disabled-workflow", + tenant_id=SINGLE_TENANT_UUID, + description="This workflow is disabled and should not be executed", + created_by="test@keephq.dev", + interval=0, + is_disabled=True, + workflow_raw=workflow_definition_for_enabled_disabled % disabled_id + ) + + db_session.add(enabled_workflow) + db_session.add(disabled_workflow) + db_session.commit() + + base_time = datetime.now(tz=pytz.utc) + + create_alert("fp1", AlertStatus.FIRING, base_time) + current_alert = AlertDto( + id="grafana-1", + source=["grafana"], + name="server-is-down", + status=AlertStatus.FIRING, + severity="critical", + fingerprint="fp1", + ) + + # Sleep one second to avoid the case where tier0 alerts are not triggered + time.sleep(1) + + workflow_manager.insert_events(SINGLE_TENANT_UUID, [current_alert]) + + enabled_workflow_execution = None + disabled_workflow_execution = None + count = 0 + + while (enabled_workflow_execution is None and disabled_workflow_execution is None) and count < 30: + enabled_workflow_execution = get_last_workflow_execution_by_workflow_id( + SINGLE_TENANT_UUID, enabled_id + ) + disabled_workflow_execution = get_last_workflow_execution_by_workflow_id( + SINGLE_TENANT_UUID, disabled_id + ) + + time.sleep(1) + count += 1 + + assert enabled_workflow_execution is not None + assert enabled_workflow_execution.status == "success" + + assert disabled_workflow_execution is None