From 8b3bc47c8e9a32265ce4c10f13c9e7509f6571b6 Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Tue, 24 Jun 2025 16:20:32 +0400 Subject: [PATCH 01/17] feat: generate and upload json schema --- .github/workflows/release-workflow-schema.yml | 114 ++++++++++++++++++ .gitignore | 1 + .../lib/generateWorkflowYamlJsonSchema.ts | 46 +++++++ .../workflows/lib/useWorkflowJsonSchema.ts | 51 +------- keep-ui/package.json | 1 + .../generate-workflow-yaml-json-schema.ts | 23 ++++ scripts/workflow_yaml_generate_json_schema.sh | 7 ++ 7 files changed, 197 insertions(+), 46 deletions(-) create mode 100644 .github/workflows/release-workflow-schema.yml create mode 100644 keep-ui/entities/workflows/lib/generateWorkflowYamlJsonSchema.ts create mode 100644 keep-ui/scripts/generate-workflow-yaml-json-schema.ts create mode 100644 scripts/workflow_yaml_generate_json_schema.sh diff --git a/.github/workflows/release-workflow-schema.yml b/.github/workflows/release-workflow-schema.yml new file mode 100644 index 0000000000..b843ee7812 --- /dev/null +++ b/.github/workflows/release-workflow-schema.yml @@ -0,0 +1,114 @@ +name: Release JSON Schema + +on: + push: + branches: + - main + paths: + - "pyproject.toml" + - "keep/providers/**" + - "keep-ui/entities/workflows/model/yaml.schema.ts" + +env: + PYTHON_VERSION: 3.11 + STORAGE_MANAGER_DIRECTORY: /tmp/storage-manager + +jobs: + release-schema: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract version from pyproject.toml + id: get_version + run: | + VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + virtualenvs-create: true + virtualenvs-in-project: true + + - name: Cache dependencies + id: cache-deps + uses: actions/cache@v4.2.0 + with: + path: .venv + key: pydeps-${{ hashFiles('**/poetry.lock') }} + + - name: Install dependencies using poetry + run: poetry install --no-interaction --no-root --with dev + + - name: Save providers list + run: | + PYTHONPATH="${{ github.workspace }}" poetry run python ./scripts/save_providers_list.py + + - name: Set up Node.js 20 + uses: actions/setup-node@v3 + with: + node-version: 20 + cache: "npm" + cache-dependency-path: keep-ui/package-lock.json + + - name: Install Node dependencies + working-directory: keep-ui + run: npm ci + + - name: Generate JSON Schema + working-directory: keep-ui + run: npm run build:workflow-yaml-json-schema + + - name: Checkout schema repository + uses: actions/checkout@v4 + with: + repository: keephq/keep-workflow-schema + token: ${{ secrets.GITHUB_TOKEN }} + path: schema-repo + + - name: Copy schema to target repository + run: | + # Assuming the schema is generated in keep-ui/dist/ or similar + cp keep-ui/workflow-schema.json schema-repo/schema.json + + # Update schema with version info + jq --arg version "${{ steps.get_version.outputs.version }}" \ + --arg id "https://raw.githubusercontent.com/keephq/keep-workflow-schema/v${{ steps.get_version.outputs.version }}/schema.json" \ + '. + {version: $version, "$id": $id}' \ + schema-repo/schema.json > schema-repo/schema.tmp.json + + mv schema-repo/schema.tmp.json schema-repo/schema.json + + - name: Check if schema changed + id: check_changes + working-directory: schema-repo + run: | + if git diff --quiet schema.json; then + echo "changed=false" >> $GITHUB_OUTPUT + else + echo "changed=true" >> $GITHUB_OUTPUT + fi + + - name: Commit and push schema + if: steps.check_changes.outputs.changed == 'true' + working-directory: schema-repo + run: | + git config user.name "Keep Schema Bot" + git config user.email "no-reply@keephq.dev" + git add schema.json + git commit -m "Release schema v${{ steps.get_version.outputs.version }}" + git tag "v${{ steps.get_version.outputs.version }}" + git push origin main + git push origin "v${{ steps.get_version.outputs.version }}" diff --git a/.gitignore b/.gitignore index 65cfb4294a..c59aa1db94 100644 --- a/.gitignore +++ b/.gitignore @@ -217,6 +217,7 @@ scripts/keep_slack_bot.py *.db providers_cache.json providers_list.json +workflow-yaml-json-schema.json tests/provision/* !tests/provision/workflows* diff --git a/keep-ui/entities/workflows/lib/generateWorkflowYamlJsonSchema.ts b/keep-ui/entities/workflows/lib/generateWorkflowYamlJsonSchema.ts new file mode 100644 index 0000000000..c4b75f0605 --- /dev/null +++ b/keep-ui/entities/workflows/lib/generateWorkflowYamlJsonSchema.ts @@ -0,0 +1,46 @@ +import { ZodSchema } from "zod"; +import zodToJsonSchema, { PostProcessCallback } from "zod-to-json-schema"; + +const makeRequiredEitherStepsOrActions: PostProcessCallback = ( + // The original output produced by the package itself: + jsonSchema, + // The ZodSchema def used to produce the original schema: + def, + // The refs object containing the current path, passed options, etc. + refs +) => { + const path = refs.currentPath.join("/"); + if ( + jsonSchema && + path === "#/definitions/WorkflowSchema/properties/workflow" + ) { + // @ts-ignore + jsonSchema.required = jsonSchema.required.filter( + (r: string) => r !== "steps" + ); + // @ts-ignore + jsonSchema.anyOf = [ + { + required: ["steps"], + properties: { + steps: { minItems: 1 }, + }, + }, + { + required: ["actions"], + properties: { + actions: { minItems: 1 }, + }, + }, + ]; + } + return jsonSchema; +}; + +export function generateWorkflowYamlJsonSchema(zodSchema: ZodSchema) { + return zodToJsonSchema(zodSchema, { + name: "KeepWorkflowSchema", + // Make workflow valid if it has either actions or steps + postProcess: makeRequiredEitherStepsOrActions, + }); +} diff --git a/keep-ui/entities/workflows/lib/useWorkflowJsonSchema.ts b/keep-ui/entities/workflows/lib/useWorkflowJsonSchema.ts index 200c23f39b..a707083de1 100644 --- a/keep-ui/entities/workflows/lib/useWorkflowJsonSchema.ts +++ b/keep-ui/entities/workflows/lib/useWorkflowJsonSchema.ts @@ -1,58 +1,17 @@ import { useProviders } from "@/utils/hooks/useProviders"; import { getYamlWorkflowDefinitionSchema } from "../model/yaml.schema"; import { useMemo } from "react"; -import zodToJsonSchema, { PostProcessCallback } from "zod-to-json-schema"; import { YamlWorkflowDefinitionSchema } from "../model/yaml.schema"; - -const makeRequiredEitherStepsOrActions: PostProcessCallback = ( - // The original output produced by the package itself: - jsonSchema, - // The ZodSchema def used to produce the original schema: - def, - // The refs object containing the current path, passed options, etc. - refs -) => { - const path = refs.currentPath.join("/"); - if ( - jsonSchema && - path === "#/definitions/WorkflowSchema/properties/workflow" - ) { - // @ts-ignore - jsonSchema.required = jsonSchema.required.filter( - (r: string) => r !== "steps" - ); - // @ts-ignore - jsonSchema.anyOf = [ - { - required: ["steps"], - properties: { - steps: { minItems: 1 }, - }, - }, - { - required: ["actions"], - properties: { - actions: { minItems: 1 }, - }, - }, - ]; - } - return jsonSchema; -}; +import { generateWorkflowYamlJsonSchema } from "./generateWorkflowYamlJsonSchema"; export function useWorkflowJsonSchema() { const { data: { providers } = {} } = useProviders(); return useMemo(() => { if (!providers) { - return zodToJsonSchema(YamlWorkflowDefinitionSchema, { - name: "WorkflowSchema", - postProcess: makeRequiredEitherStepsOrActions, - }); + return generateWorkflowYamlJsonSchema(YamlWorkflowDefinitionSchema); } - return zodToJsonSchema(getYamlWorkflowDefinitionSchema(providers), { - name: "WorkflowSchema", - // Make workflow valid if it has either actions or steps - postProcess: makeRequiredEitherStepsOrActions, - }); + return generateWorkflowYamlJsonSchema( + getYamlWorkflowDefinitionSchema(providers) + ); }, [providers]); } diff --git a/keep-ui/package.json b/keep-ui/package.json index da424b2cee..7cb68f8b55 100644 --- a/keep-ui/package.json +++ b/keep-ui/package.json @@ -6,6 +6,7 @@ "scripts": { "build-monaco-workers": "node scripts/build-monaco-workers-turbopack.js", "build": "./next_build.sh", + "build:workflow-yaml-json-schema": "ts-node -P tsconfig.scripts.json scripts/generate-workflow-yaml-json-schema.ts", "dev": "npm run build-monaco-workers && next dev --turbopack -p 3000", "dev:webpack": "next dev -p 3000", "lint": "next lint", diff --git a/keep-ui/scripts/generate-workflow-yaml-json-schema.ts b/keep-ui/scripts/generate-workflow-yaml-json-schema.ts new file mode 100644 index 0000000000..63a53e468b --- /dev/null +++ b/keep-ui/scripts/generate-workflow-yaml-json-schema.ts @@ -0,0 +1,23 @@ +import { getYamlWorkflowDefinitionSchema } from "../entities/workflows/model/yaml.schema"; +import fs from "fs"; +import path from "path"; +import { generateWorkflowYamlJsonSchema } from "../entities/workflows/lib/generateWorkflowYamlJsonSchema"; + +function saveWorkflowYamlJsonSchema() { + console.log("Loading providers list"); + // providers_list.json should be generated with "python3 scripts/save_providers_list.py" from the root of the repo + const providers = JSON.parse( + fs.readFileSync(path.join(__dirname, "../../providers_list.json"), "utf8") + ) as any[]; + console.log(`Providers list loaded, ${providers.length} providers found`); + const zodSchema = getYamlWorkflowDefinitionSchema(providers); + console.log(`Zod schema loaded`); + const jsonSchema = generateWorkflowYamlJsonSchema(zodSchema); + fs.writeFileSync( + path.join(__dirname, "../../workflow-yaml-json-schema.json"), + JSON.stringify(jsonSchema, null, 2) + ); + console.log("JSON schema generated"); +} + +saveWorkflowYamlJsonSchema(); diff --git a/scripts/workflow_yaml_generate_json_schema.sh b/scripts/workflow_yaml_generate_json_schema.sh new file mode 100644 index 0000000000..f50419e37c --- /dev/null +++ b/scripts/workflow_yaml_generate_json_schema.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Save providers list to providers_list.json +python3 ./scripts/save_providers_list.py + +# Generate JSON schema from providers list +cd keep-ui && npm run build:workflow-yaml-json-schema \ No newline at end of file From 3ef2022ed931125629bfc68c3b78f775f7d97b27 Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Tue, 24 Jun 2025 16:25:45 +0400 Subject: [PATCH 02/17] ci: allow manually release schema --- .github/workflows/release-workflow-schema.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-workflow-schema.yml b/.github/workflows/release-workflow-schema.yml index b843ee7812..d76d7046c7 100644 --- a/.github/workflows/release-workflow-schema.yml +++ b/.github/workflows/release-workflow-schema.yml @@ -8,6 +8,7 @@ on: - "pyproject.toml" - "keep/providers/**" - "keep-ui/entities/workflows/model/yaml.schema.ts" + workflow_dispatch: env: PYTHON_VERSION: 3.11 From f3e82ee6be3fe040bbe6e7e48a367dd52fd0b69a Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Tue, 24 Jun 2025 16:29:21 +0400 Subject: [PATCH 03/17] fix: update console_provider to trigger a workflow --- .../providers/console_provider/console_provider.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/keep/providers/console_provider/console_provider.py b/keep/providers/console_provider/console_provider.py index 6d12afaaee..cf53fa8c56 100644 --- a/keep/providers/console_provider/console_provider.py +++ b/keep/providers/console_provider/console_provider.py @@ -26,10 +26,18 @@ def dispose(self): # No need to dispose of anything, so just do nothing. pass - def _query(self, message: str = "", logger: bool = False, severity: str = "info"): - return self._notify(message, logger, severity) + def _query( + self, message: str = "", logger: bool = False, severity: str = "info", **kwargs + ): + return self._notify(message, logger, severity, **kwargs) - def _notify(self, message: str = "", logger: bool = False, severity: str = "info"): + def _notify( + self, + message: str = "", + logger: bool = False, + severity: str = "info", + **kwargs, # `enrich_alert` is not removed from kwargs, so we need to allow as part of kwargs + ): """ Output alert message simply using the print method. From 52d1d684aa782163b10b251e7921e2522443f569 Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Tue, 24 Jun 2025 16:34:08 +0400 Subject: [PATCH 04/17] ci: also run on pull-requests --- .github/workflows/release-workflow-schema.yml | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-workflow-schema.yml b/.github/workflows/release-workflow-schema.yml index d76d7046c7..3d2e377487 100644 --- a/.github/workflows/release-workflow-schema.yml +++ b/.github/workflows/release-workflow-schema.yml @@ -8,6 +8,11 @@ on: - "pyproject.toml" - "keep/providers/**" - "keep-ui/entities/workflows/model/yaml.schema.ts" + pull_request: + paths: + - "pyproject.toml" + - "keep/providers/**" + - "keep-ui/entities/workflows/model/yaml.schema.ts" workflow_dispatch: env: @@ -79,6 +84,25 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} path: schema-repo + - name: Set target branch variable + id: set_branch + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "branch=${{ github.head_ref }}" >> $GITHUB_OUTPUT + else + echo "branch=${{ github.ref_name }}" >> $GITHUB_OUTPUT + fi + + - name: Create or switch to target branch in schema repo + working-directory: schema-repo + run: | + git fetch origin + if git show-ref --verify --quiet refs/heads/${{ steps.set_branch.outputs.branch }}; then + git checkout ${{ steps.set_branch.outputs.branch }} + else + git checkout -b ${{ steps.set_branch.outputs.branch }} + fi + - name: Copy schema to target repository run: | # Assuming the schema is generated in keep-ui/dist/ or similar @@ -111,5 +135,5 @@ jobs: git add schema.json git commit -m "Release schema v${{ steps.get_version.outputs.version }}" git tag "v${{ steps.get_version.outputs.version }}" - git push origin main + git push origin ${{ steps.set_branch.outputs.branch }} git push origin "v${{ steps.get_version.outputs.version }}" From 27fecf97405cf6909f0ca8ab34aed8850d19abe5 Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Tue, 24 Jun 2025 18:47:32 +0400 Subject: [PATCH 05/17] ci: update the token and add release-step --- .github/workflows/release-workflow-schema.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-workflow-schema.yml b/.github/workflows/release-workflow-schema.yml index 3d2e377487..f9c72cf29b 100644 --- a/.github/workflows/release-workflow-schema.yml +++ b/.github/workflows/release-workflow-schema.yml @@ -81,7 +81,7 @@ jobs: uses: actions/checkout@v4 with: repository: keephq/keep-workflow-schema - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_SCHEMA_REPO_PAT }} path: schema-repo - name: Set target branch variable @@ -105,8 +105,7 @@ jobs: - name: Copy schema to target repository run: | - # Assuming the schema is generated in keep-ui/dist/ or similar - cp keep-ui/workflow-schema.json schema-repo/schema.json + cp workflow-yaml-json-schema.json schema-repo/schema.json # Update schema with version info jq --arg version "${{ steps.get_version.outputs.version }}" \ @@ -137,3 +136,14 @@ jobs: git tag "v${{ steps.get_version.outputs.version }}" git push origin ${{ steps.set_branch.outputs.branch }} git push origin "v${{ steps.get_version.outputs.version }}" + + - name: Create GitHub Release + if: steps.check_changes.outputs.changed == 'true' && steps.set_branch.outputs.branch == 'main' + uses: softprops/action-gh-release@v1 + with: + tag_name: v${{ steps.get_version.outputs.version }} + name: Release v${{ steps.get_version.outputs.version }} + body: | + Automated release of schema version v${{ steps.get_version.outputs.version }}. + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_SCHEMA_REPO_PAT }} From 51f4303d9261ce56ee2e10c1519c90d70f20523b Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Tue, 24 Jun 2025 21:01:28 +0400 Subject: [PATCH 06/17] Update release-workflow-schema.yml --- .github/workflows/release-workflow-schema.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-workflow-schema.yml b/.github/workflows/release-workflow-schema.yml index f9c72cf29b..2eca7f0d02 100644 --- a/.github/workflows/release-workflow-schema.yml +++ b/.github/workflows/release-workflow-schema.yml @@ -81,7 +81,7 @@ jobs: uses: actions/checkout@v4 with: repository: keephq/keep-workflow-schema - token: ${{ secrets.GITHUB_SCHEMA_REPO_PAT }} + token: ${{ secrets.SCHEMA_REPO_PAT }} path: schema-repo - name: Set target branch variable @@ -146,4 +146,4 @@ jobs: body: | Automated release of schema version v${{ steps.get_version.outputs.version }}. env: - GITHUB_TOKEN: ${{ secrets.GITHUB_SCHEMA_REPO_PAT }} + GITHUB_TOKEN: ${{ secrets.SCHEMA_REPO_PAT }} From 5ad05ae290147c255aa1fe9fc0d67b9be7a0809b Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Thu, 26 Jun 2025 12:57:56 +0400 Subject: [PATCH 07/17] fix: mock console provider notify --- tests/test_workflow_execution.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/test_workflow_execution.py b/tests/test_workflow_execution.py index 24dc8b5939..e831ff6871 100644 --- a/tests/test_workflow_execution.py +++ b/tests/test_workflow_execution.py @@ -1780,7 +1780,7 @@ def test_workflow_with_on_failure_action(db_session, workflow_manager, mocker): ) -def test_get_all_workflows_with_last_execution(db_session, workflow_manager): +def test_get_all_workflows_with_last_execution(db_session, workflow_manager, mocker): workflow = Workflow( id="log-every-alert", name="log-every-alert", @@ -1789,6 +1789,7 @@ def test_get_all_workflows_with_last_execution(db_session, workflow_manager): created_by="borat@keephq.dev", interval=0, workflow_raw=LOG_EVERY_ALERT_WORKFLOW, + last_updated=datetime.now(tz=pytz.utc), ) db_session.add(workflow) db_session.commit() @@ -1819,6 +1820,16 @@ def test_get_all_workflows_with_last_execution(db_session, workflow_manager): should_fail="true", ) + def mock_notify(*args, **kwargs): + if kwargs.get("invalid-argument-to-fail-workflow"): + raise Exception("Workflow failed") + return True + + mocker.patch( + "keep.providers.console_provider.console_provider.ConsoleProvider._notify", + mock_notify, + ) + workflow_manager.insert_events(SINGLE_TENANT_UUID, [alert1]) # Wait for the workflow to execute From 4e3631524c8864ad68654bfa97271171e53ab0cb Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Thu, 26 Jun 2025 14:26:59 +0400 Subject: [PATCH 08/17] tests: print current active element of e2e failures --- tests/e2e_tests/utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/e2e_tests/utils.py b/tests/e2e_tests/utils.py index fae8912aec..66a014a947 100644 --- a/tests/e2e_tests/utils.py +++ b/tests/e2e_tests/utils.py @@ -237,6 +237,12 @@ def save_failure_artifacts(page, log_entries=[], prefix=""): if prefix: current_test_name = prefix + "_" + current_test_name + # print current active element + print( + "current active element: ", + page.locator("body").evaluate("() => document.activeElement.outerHTML"), + ) + # Save screenshot page.screenshot(path=current_test_name + ".png") From 97f7087aa12fff93eba25159ea7c02cbe216fa26 Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Thu, 26 Jun 2025 15:09:23 +0400 Subject: [PATCH 09/17] fix: fill the input for more robust e2e test involving react-select --- tests/e2e_tests/test_end_to_end.py | 19 ++++++++++++------- tests/e2e_tests/utils.py | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/tests/e2e_tests/test_end_to_end.py b/tests/e2e_tests/test_end_to_end.py index 15e6832a13..4b6cf6ef64 100644 --- a/tests/e2e_tests/test_end_to_end.py +++ b/tests/e2e_tests/test_end_to_end.py @@ -823,10 +823,12 @@ def test_run_workflow_from_alert_and_incident( page.wait_for_timeout(200) expect(modal).to_be_visible() page.wait_for_timeout(200) - modal.get_by_test_id("manual-run-workflow-select-control").click() - modal.get_by_role( - "option", name=re.compile(r"Log every incident") - ).first.click() + input = modal.get_by_test_id("manual-run-workflow-select-control").get_by_role( + "combobox" + ) + input.click() + input.fill("Log every incident") + input.press("Enter") modal.get_by_role("button", name="Run").click() expect(page.get_by_text("Workflow started successfully")).to_be_visible() # Run workflow from alert @@ -840,9 +842,12 @@ def test_run_workflow_from_alert_and_incident( page.get_by_test_id("dropdown-menu-list").get_by_role( "button", name="Run workflow" ).click() - modal = page.get_by_test_id("manual-run-workflow-modal") - modal.get_by_test_id("manual-run-workflow-select-control").click() - modal.get_by_role("option", name=re.compile(r"Log every alert")).click() + input = modal.get_by_test_id("manual-run-workflow-select-control").get_by_role( + "combobox" + ) + input.click() + input.fill("Log every alert") + input.press("Enter") modal.get_by_role("button", name="Run").click() expect(page.get_by_text("Workflow started successfully")).to_be_visible() except Exception: diff --git a/tests/e2e_tests/utils.py b/tests/e2e_tests/utils.py index 66a014a947..2b3ee11c07 100644 --- a/tests/e2e_tests/utils.py +++ b/tests/e2e_tests/utils.py @@ -240,7 +240,7 @@ def save_failure_artifacts(page, log_entries=[], prefix=""): # print current active element print( "current active element: ", - page.locator("body").evaluate("() => document.activeElement.outerHTML"), + page.locator("body").evaluate("() => document.activeElement.outerHTML")[:200], ) # Save screenshot From 12fd0e670ffeee25f6d4e7a55ecde2b7d6622520 Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Thu, 26 Jun 2025 18:15:02 +0400 Subject: [PATCH 10/17] fix: update reference to modal --- tests/e2e_tests/test_end_to_end.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e_tests/test_end_to_end.py b/tests/e2e_tests/test_end_to_end.py index 4b6cf6ef64..26013f8fe7 100644 --- a/tests/e2e_tests/test_end_to_end.py +++ b/tests/e2e_tests/test_end_to_end.py @@ -842,6 +842,7 @@ def test_run_workflow_from_alert_and_incident( page.get_by_test_id("dropdown-menu-list").get_by_role( "button", name="Run workflow" ).click() + modal = page.get_by_test_id("manual-run-workflow-modal") input = modal.get_by_test_id("manual-run-workflow-select-control").get_by_role( "combobox" ) From f732169e801aa77ee0f477b070cd26b56c5f79c1 Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Fri, 27 Jun 2025 13:38:13 +0400 Subject: [PATCH 11/17] fix: select combobox input with retry --- tests/e2e_tests/test_end_to_end.py | 16 ++++------------ tests/e2e_tests/utils.py | 22 +++++++++++++++++++++- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/tests/e2e_tests/test_end_to_end.py b/tests/e2e_tests/test_end_to_end.py index 26013f8fe7..7ad2a0c199 100644 --- a/tests/e2e_tests/test_end_to_end.py +++ b/tests/e2e_tests/test_end_to_end.py @@ -35,6 +35,7 @@ from tests.e2e_tests.utils import ( assert_connected_provider_count, assert_scope_text_count, + choose_combobox_option_with_retry, delete_provider, init_e2e_test, install_webhook_provider, @@ -823,12 +824,8 @@ def test_run_workflow_from_alert_and_incident( page.wait_for_timeout(200) expect(modal).to_be_visible() page.wait_for_timeout(200) - input = modal.get_by_test_id("manual-run-workflow-select-control").get_by_role( - "combobox" - ) - input.click() - input.fill("Log every incident") - input.press("Enter") + select = modal.get_by_test_id("manual-run-workflow-select-control") + choose_combobox_option_with_retry(page, select, "Log every incident") modal.get_by_role("button", name="Run").click() expect(page.get_by_text("Workflow started successfully")).to_be_visible() # Run workflow from alert @@ -843,12 +840,7 @@ def test_run_workflow_from_alert_and_incident( "button", name="Run workflow" ).click() modal = page.get_by_test_id("manual-run-workflow-modal") - input = modal.get_by_test_id("manual-run-workflow-select-control").get_by_role( - "combobox" - ) - input.click() - input.fill("Log every alert") - input.press("Enter") + choose_combobox_option_with_retry(page, select, "Log every alert") modal.get_by_role("button", name="Run").click() expect(page.get_by_text("Workflow started successfully")).to_be_visible() except Exception: diff --git a/tests/e2e_tests/utils.py b/tests/e2e_tests/utils.py index 2b3ee11c07..910d499f31 100644 --- a/tests/e2e_tests/utils.py +++ b/tests/e2e_tests/utils.py @@ -1,16 +1,36 @@ import json import os +import re import sys from datetime import datetime import requests -from playwright.sync_api import Page, expect +from playwright.sync_api import Locator, Page, expect from keep.providers.providers_factory import ProvidersFactory KEEP_UI_URL = "http://localhost:3000" +def choose_combobox_option_with_retry( + page: Page, + combobox_container_locator: Locator, + option_text: str, + max_retries: int = 3, +): + for i in range(max_retries): + combobox_container_locator.click() + combobox = combobox_container_locator.get_by_role("combobox") + combobox.fill(option_text) + combobox.press("Enter") + if combobox_container_locator.get_by_text(re.compile(option_text)).is_visible(): + return + page.wait_for_timeout(100) + raise Exception( + f"Failed to choose combobox option {option_text}, current value: {combobox.input_value()}" + ) + + def trigger_alert(provider_name, tenant_id=None): provider = ProvidersFactory.get_provider_class(provider_name) token = get_token(tenant_id) From 27793f01a3e93a90fd7ad2348d70863164e4a6ea Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Fri, 27 Jun 2025 14:08:53 +0400 Subject: [PATCH 12/17] fix: update select reference --- tests/e2e_tests/test_end_to_end.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e_tests/test_end_to_end.py b/tests/e2e_tests/test_end_to_end.py index 7ad2a0c199..736908f85b 100644 --- a/tests/e2e_tests/test_end_to_end.py +++ b/tests/e2e_tests/test_end_to_end.py @@ -840,6 +840,7 @@ def test_run_workflow_from_alert_and_incident( "button", name="Run workflow" ).click() modal = page.get_by_test_id("manual-run-workflow-modal") + select = modal.get_by_test_id("manual-run-workflow-select-control") choose_combobox_option_with_retry(page, select, "Log every alert") modal.get_by_role("button", name="Run").click() expect(page.get_by_text("Workflow started successfully")).to_be_visible() From 34b9f2f6ae2a71ae287f9cecb43392c6102d896a Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Fri, 27 Jun 2025 14:19:33 +0400 Subject: [PATCH 13/17] fix: schema name --- .../lib/generateWorkflowYamlJsonSchema.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/keep-ui/entities/workflows/lib/generateWorkflowYamlJsonSchema.ts b/keep-ui/entities/workflows/lib/generateWorkflowYamlJsonSchema.ts index c4b75f0605..6e047abcee 100644 --- a/keep-ui/entities/workflows/lib/generateWorkflowYamlJsonSchema.ts +++ b/keep-ui/entities/workflows/lib/generateWorkflowYamlJsonSchema.ts @@ -1,6 +1,9 @@ import { ZodSchema } from "zod"; import zodToJsonSchema, { PostProcessCallback } from "zod-to-json-schema"; +const schemaName = "KeepWorkflowSchema"; +const rootPath = `#/definitions/${schemaName}/properties/workflow`; + const makeRequiredEitherStepsOrActions: PostProcessCallback = ( // The original output produced by the package itself: jsonSchema, @@ -10,10 +13,8 @@ const makeRequiredEitherStepsOrActions: PostProcessCallback = ( refs ) => { const path = refs.currentPath.join("/"); - if ( - jsonSchema && - path === "#/definitions/WorkflowSchema/properties/workflow" - ) { + let rootVisited = false; + if (jsonSchema && path === rootPath) { // @ts-ignore jsonSchema.required = jsonSchema.required.filter( (r: string) => r !== "steps" @@ -33,13 +34,17 @@ const makeRequiredEitherStepsOrActions: PostProcessCallback = ( }, }, ]; + rootVisited = true; + } + if (!rootVisited) { + throw new Error(`${rootPath} not found in the schema`); } return jsonSchema; }; export function generateWorkflowYamlJsonSchema(zodSchema: ZodSchema) { return zodToJsonSchema(zodSchema, { - name: "KeepWorkflowSchema", + name: schemaName, // Make workflow valid if it has either actions or steps postProcess: makeRequiredEitherStepsOrActions, }); From 5776359d133a1e03a98884a7616f640bdf751dff Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Fri, 27 Jun 2025 14:21:45 +0400 Subject: [PATCH 14/17] fix: specify repo for release --- .github/workflows/release-workflow-schema.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-workflow-schema.yml b/.github/workflows/release-workflow-schema.yml index 2eca7f0d02..48ce2c0e1f 100644 --- a/.github/workflows/release-workflow-schema.yml +++ b/.github/workflows/release-workflow-schema.yml @@ -18,6 +18,7 @@ on: env: PYTHON_VERSION: 3.11 STORAGE_MANAGER_DIRECTORY: /tmp/storage-manager + SCHEMA_REPO_NAME: keephq/keep-workflow-schema jobs: release-schema: @@ -80,7 +81,7 @@ jobs: - name: Checkout schema repository uses: actions/checkout@v4 with: - repository: keephq/keep-workflow-schema + repository: ${{ env.SCHEMA_REPO_NAME }} token: ${{ secrets.SCHEMA_REPO_PAT }} path: schema-repo @@ -109,7 +110,7 @@ jobs: # Update schema with version info jq --arg version "${{ steps.get_version.outputs.version }}" \ - --arg id "https://raw.githubusercontent.com/keephq/keep-workflow-schema/v${{ steps.get_version.outputs.version }}/schema.json" \ + --arg id "https://raw.githubusercontent.com/${{ env.SCHEMA_REPO_NAME }}/v${{ steps.get_version.outputs.version }}/schema.json" \ '. + {version: $version, "$id": $id}' \ schema-repo/schema.json > schema-repo/schema.tmp.json @@ -141,6 +142,7 @@ jobs: if: steps.check_changes.outputs.changed == 'true' && steps.set_branch.outputs.branch == 'main' uses: softprops/action-gh-release@v1 with: + repository: ${{ env.SCHEMA_REPO_NAME }} tag_name: v${{ steps.get_version.outputs.version }} name: Release v${{ steps.get_version.outputs.version }} body: | From fbe5cf771cb73b9020748b80f0db6b875484fe7d Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Fri, 27 Jun 2025 14:26:09 +0400 Subject: [PATCH 15/17] fix: only create tag for `main` --- .github/workflows/release-workflow-schema.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-workflow-schema.yml b/.github/workflows/release-workflow-schema.yml index 48ce2c0e1f..1767a41d89 100644 --- a/.github/workflows/release-workflow-schema.yml +++ b/.github/workflows/release-workflow-schema.yml @@ -134,9 +134,11 @@ jobs: git config user.email "no-reply@keephq.dev" git add schema.json git commit -m "Release schema v${{ steps.get_version.outputs.version }}" - git tag "v${{ steps.get_version.outputs.version }}" git push origin ${{ steps.set_branch.outputs.branch }} - git push origin "v${{ steps.get_version.outputs.version }}" + if [ "${{ steps.set_branch.outputs.branch }}" = "main" ]; then + git tag "v${{ steps.get_version.outputs.version }}" + git push origin "v${{ steps.get_version.outputs.version }}" + fi - name: Create GitHub Release if: steps.check_changes.outputs.changed == 'true' && steps.set_branch.outputs.branch == 'main' From 8e9d7f7de0b7b19a7fbdbbd2766f96170dfee8b7 Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Fri, 27 Jun 2025 14:35:10 +0400 Subject: [PATCH 16/17] fix: remove root visited check, rely on const --- .../entities/workflows/lib/generateWorkflowYamlJsonSchema.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/keep-ui/entities/workflows/lib/generateWorkflowYamlJsonSchema.ts b/keep-ui/entities/workflows/lib/generateWorkflowYamlJsonSchema.ts index 6e047abcee..be84a40a00 100644 --- a/keep-ui/entities/workflows/lib/generateWorkflowYamlJsonSchema.ts +++ b/keep-ui/entities/workflows/lib/generateWorkflowYamlJsonSchema.ts @@ -13,7 +13,6 @@ const makeRequiredEitherStepsOrActions: PostProcessCallback = ( refs ) => { const path = refs.currentPath.join("/"); - let rootVisited = false; if (jsonSchema && path === rootPath) { // @ts-ignore jsonSchema.required = jsonSchema.required.filter( @@ -34,10 +33,6 @@ const makeRequiredEitherStepsOrActions: PostProcessCallback = ( }, }, ]; - rootVisited = true; - } - if (!rootVisited) { - throw new Error(`${rootPath} not found in the schema`); } return jsonSchema; }; From b9a99b0a10e827b15281881d4e1ce4663b0181b2 Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Mon, 30 Jun 2025 19:32:51 +0400 Subject: [PATCH 17/17] Revert "fix: update console_provider to trigger a workflow" This reverts commit f3e82ee6be3fe040bbe6e7e48a367dd52fd0b69a. --- .../providers/console_provider/console_provider.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/keep/providers/console_provider/console_provider.py b/keep/providers/console_provider/console_provider.py index cf53fa8c56..6d12afaaee 100644 --- a/keep/providers/console_provider/console_provider.py +++ b/keep/providers/console_provider/console_provider.py @@ -26,18 +26,10 @@ def dispose(self): # No need to dispose of anything, so just do nothing. pass - def _query( - self, message: str = "", logger: bool = False, severity: str = "info", **kwargs - ): - return self._notify(message, logger, severity, **kwargs) + def _query(self, message: str = "", logger: bool = False, severity: str = "info"): + return self._notify(message, logger, severity) - def _notify( - self, - message: str = "", - logger: bool = False, - severity: str = "info", - **kwargs, # `enrich_alert` is not removed from kwargs, so we need to allow as part of kwargs - ): + def _notify(self, message: str = "", logger: bool = False, severity: str = "info"): """ Output alert message simply using the print method.