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