From 60b4380a3438db40a8db583570b434c4bc0348e3 Mon Sep 17 00:00:00 2001 From: hakatt Date: Tue, 24 Feb 2026 14:39:43 +0100 Subject: [PATCH 1/3] fix(deps): sync package-lock.json with package.json (Node 20) (#5676) Co-authored-by: Emil Lind Co-authored-by: Claude Sonnet 4.6 --- keep-ui/package-lock.json | 111 +++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 61 deletions(-) diff --git a/keep-ui/package-lock.json b/keep-ui/package-lock.json index ea6f246f36..0874564fb6 100644 --- a/keep-ui/package-lock.json +++ b/keep-ui/package-lock.json @@ -339,7 +339,6 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.0", @@ -2062,6 +2061,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@browserbasehq/sdk/-/sdk-2.3.0.tgz", "integrity": "sha512-H2nu46C6ydWgHY+7yqaP8qpfRJMJFVGxVIgsuHe1cx9HkfJHqzkuIqaK/k8mU4ZeavQgV5ZrJa0UX6MDGYiT4w==", + "peer": true, "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", @@ -2076,6 +2076,7 @@ "version": "18.19.76", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.76.tgz", "integrity": "sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==", + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -3991,7 +3992,6 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", - "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -4121,7 +4121,6 @@ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -4799,6 +4798,7 @@ "resolved": "https://registry.npmjs.org/@ibm-cloud/watsonx-ai/-/watsonx-ai-1.6.8.tgz", "integrity": "sha512-Ip5bLDM40rQRYauRmmIIpxLO57wI3+F59Njmp0hexnVr+uKroV+O9+eAGQkdE2c9d17R16Q77ueAGheZrzqgWA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@types/node": "^18.0.0", "extend": "3.0.2", @@ -4812,6 +4812,7 @@ "version": "18.19.76", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.76.tgz", "integrity": "sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==", + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -6407,7 +6408,6 @@ "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.80.tgz", "integrity": "sha512-vcJDV2vk1AlCwSh3aBm/urQ1ZrlXFFBocv11bz/NBUfLWD5/UDNMzwPdaAd2dKvNmTWa9FM2lirLU3+JCf4cRA==", "license": "MIT", - "peer": true, "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", @@ -7021,7 +7021,6 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -7043,7 +7042,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz", "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=14" }, @@ -7080,7 +7078,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/api-logs": "0.57.2", "@types/shimmer": "^1.2.0", @@ -7528,7 +7525,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/resources": "1.30.1", @@ -7555,7 +7551,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.30.0.tgz", "integrity": "sha512-4VlGgo32k2EQ2wcCY3vEU28A0O13aOtHz3Xt2/2U5FAh9EfhD6t6DqL5Z6yAnRCntbTFDU4YfbpyzSlHNWycPw==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=14" } @@ -10567,7 +10562,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", - "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -10893,7 +10887,8 @@ "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "peer": true }, "node_modules/@tootallnate/once": { "version": "2.0.0", @@ -10952,7 +10947,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/kossnocorp" @@ -11348,8 +11342,7 @@ "version": "20.2.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.1.tgz", "integrity": "sha512-DqJociPbZP1lbZ5SQPk4oag6W7AyaGMO6gSfRwq3PWl4PXTwJpRQJhDq4W0kzrg3w6tJ1SwlvGZ5uKFHY13LIg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/node-fetch": { "version": "2.6.12", @@ -11403,7 +11396,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -11451,7 +11443,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -11566,7 +11557,6 @@ "integrity": "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.26.1", "@typescript-eslint/types": "8.26.1", @@ -12301,7 +12291,6 @@ "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -12366,7 +12355,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -12811,7 +12799,6 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "license": "MIT", - "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -13169,7 +13156,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -13445,7 +13431,6 @@ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz", "integrity": "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==", "license": "MIT", - "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -13518,7 +13503,6 @@ "version": "0.14.1", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", - "peer": true, "dependencies": { "@types/validator": "^13.11.8", "libphonenumber-js": "^1.10.53", @@ -13804,12 +13788,25 @@ "react": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/cmdk/node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, "node_modules/cmdk/node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/cmdk/node_modules/react-remove-scroll": { "version": "2.5.4", @@ -14337,7 +14334,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -14584,7 +14580,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -15070,7 +15065,6 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=12" }, @@ -15447,7 +15441,6 @@ "integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -16576,6 +16569,7 @@ "version": "16.5.4", "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "peer": true, "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", @@ -17173,7 +17167,6 @@ "version": "8.9.0", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", "integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==", - "peer": true, "dependencies": { "arrify": "^2.0.0", "base64-js": "^1.3.0", @@ -17246,7 +17239,6 @@ "version": "16.9.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", - "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -17266,7 +17258,6 @@ "version": "1.23.0", "resolved": "https://registry.npmjs.org/graphql-scalars/-/graphql-scalars-1.23.0.tgz", "integrity": "sha512-YTRNcwitkn8CqYcleKOx9IvedA8JIERn8BRq21nlKgOr4NEcTaWEG0sT+H92eF3ALTFbPgsqfft4cw+MGgv0Gg==", - "peer": true, "dependencies": { "tslib": "^2.5.0" }, @@ -17281,7 +17272,6 @@ "version": "5.10.2", "resolved": "https://registry.npmjs.org/graphql-yoga/-/graphql-yoga-5.10.2.tgz", "integrity": "sha512-LcbNUFCsCsv3enjGnXCUQNSKxM49iB4uF9H2Vb3WChBOSQjzqI1d83mvgMTgMVtrZYlKjgM/magMQZV211N2LA==", - "peer": true, "dependencies": { "@envelop/core": "^5.0.1", "@graphql-tools/executor": "^1.3.0", @@ -17855,6 +17845,7 @@ "resolved": "https://registry.npmjs.org/ibm-cloud-sdk-core/-/ibm-cloud-sdk-core-5.4.0.tgz", "integrity": "sha512-c4cwOuUDbMiFROYM/Ti1aC+Umi1v3TdvC2DO5zR7w44FYY/3xrs79+3DVPXt/nRhJeaMHN2L9XwlXsPSoVDHJA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@types/debug": "^4.1.12", "@types/node": "^18.19.80", @@ -17881,6 +17872,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz", "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -17889,6 +17881,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -17946,7 +17939,6 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "devOptional": true, - "peer": true, "engines": { "node": ">= 4" } @@ -17955,7 +17947,6 @@ "version": "10.1.1", "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -18675,7 +18666,8 @@ "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "peer": true }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -19932,7 +19924,6 @@ "version": "1.21.6", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -20051,6 +20042,7 @@ "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "peer": true, "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", @@ -20072,6 +20064,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "peer": true, "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -20082,6 +20075,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "peer": true, "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" @@ -20463,12 +20457,14 @@ "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "peer": true }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "peer": true }, "node_modules/lodash.isequal": { "version": "4.5.0", @@ -20480,12 +20476,14 @@ "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "peer": true }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "peer": true }, "node_modules/lodash.isplainobject": { "version": "4.0.6", @@ -20495,7 +20493,8 @@ "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "peer": true }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -20505,7 +20504,8 @@ "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "peer": true }, "node_modules/loglevel": { "version": "1.9.2", @@ -22033,8 +22033,7 @@ "version": "0.52.2", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/monaco-languageserver-types": { "version": "0.4.0", @@ -22235,7 +22234,6 @@ "resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz", "integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==", "license": "MIT", - "peer": true, "dependencies": { "@next/env": "15.5.9", "@swc/helpers": "0.5.15", @@ -23097,7 +23095,6 @@ "resolved": "https://registry.npmjs.org/openai/-/openai-4.86.2.tgz", "integrity": "sha512-nvYeFjmjdSu6/msld+22JoUlCICNk/lUFpSMmc6KNhpeNLpqL70TqbD/8Vura/tFmYqHKW0trcjgPwUpKSPwaA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", @@ -23439,6 +23436,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "peer": true, "engines": { "node": ">=8" }, @@ -23657,6 +23655,7 @@ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", "license": "Apache-2.0", + "peer": true, "bin": { "playwright-core": "cli.js" }, @@ -23673,6 +23672,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -23716,7 +23716,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -23982,7 +23981,6 @@ "version": "10.24.3", "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -24224,7 +24222,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -24623,7 +24620,6 @@ "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz", "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==", "license": "MIT", - "peer": true, "dependencies": { "fast-diff": "^1.3.0", "lodash.clonedeep": "^4.5.0", @@ -24684,7 +24680,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.0.1.tgz", "integrity": "sha512-nVRaZCuEyvu69sWrkdwjP6QY57C+lY+uMNNMyWUFJb9Z/JlaBOQus7mSMfGYsblv7R691u6SSJA/dX9IRnyyLQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -24799,7 +24794,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.1.tgz", "integrity": "sha512-3TJg51HSbJiLVYCS6vWwWsyqoS36aGEOCmtLLHxROlSZZ5Bk10xpxHFbrCu4DdqgR85DDc9Vucxqhai3g2xjtA==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.25.0" }, @@ -25197,6 +25191,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", + "peer": true, "dependencies": { "readable-stream": "^4.7.0" }, @@ -25856,6 +25851,7 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-2.6.0.tgz", "integrity": "sha512-pOLi+Gdll3JekwuFjXO3fTq+L9lzMQGcSq7M5gIjExcl3Gu1hd4XXuf5o3+LuSBsaULQH7DiNbsqPd1chVpQGQ==", + "peer": true, "engines": { "node": ">=10.7.0" }, @@ -25940,7 +25936,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.9.tgz", "integrity": "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.6" }, @@ -26108,7 +26103,6 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.85.1.tgz", "integrity": "sha512-Uk8WpxM5v+0cMR0XjX9KfRIacmSG86RH4DCCZjLU2rFh5tyutt9siAXJ7G+YfxQ99Q6wrRMbMlVl6KqUms71ag==", "license": "MIT", - "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -26166,7 +26160,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -26570,7 +26563,6 @@ "version": "0.94.1", "resolved": "https://registry.npmjs.org/slate/-/slate-0.94.1.tgz", "integrity": "sha512-GH/yizXr1ceBoZ9P9uebIaHe3dC/g6Plpf9nlUwnvoyf6V1UOYrRwkabtOCd3ZfIGxomY4P7lfgLr7FPH8/BKA==", - "peer": true, "dependencies": { "immer": "^9.0.6", "is-plain-object": "^5.0.0", @@ -27031,6 +27023,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "peer": true, "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" @@ -27327,7 +27320,6 @@ "version": "3.4.15", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.15.tgz", "integrity": "sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==", - "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -27671,6 +27663,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "peer": true, "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" @@ -27747,7 +27740,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -27972,7 +27964,6 @@ "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -28652,7 +28643,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz", "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -29267,7 +29257,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From 2580e9475730f19c2c29ea33bf87463d3df8752a Mon Sep 17 00:00:00 2001 From: hakatt Date: Tue, 24 Feb 2026 16:47:30 +0100 Subject: [PATCH 2/3] fix(ui): guard fingerprint modal against stale-empty alerts flash (#5672) Co-authored-by: Emil Lind Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Shahar Glazner --- .../ui/__tests__/alerts-fingerprint.test.tsx | 364 ++++++++++++++++++ keep-ui/app/(keep)/alerts/[id]/ui/alerts.tsx | 15 +- 2 files changed, 375 insertions(+), 4 deletions(-) create mode 100644 keep-ui/app/(keep)/alerts/[id]/ui/__tests__/alerts-fingerprint.test.tsx diff --git a/keep-ui/app/(keep)/alerts/[id]/ui/__tests__/alerts-fingerprint.test.tsx b/keep-ui/app/(keep)/alerts/[id]/ui/__tests__/alerts-fingerprint.test.tsx new file mode 100644 index 0000000000..be6b579423 --- /dev/null +++ b/keep-ui/app/(keep)/alerts/[id]/ui/__tests__/alerts-fingerprint.test.tsx @@ -0,0 +1,364 @@ +/** + * Tests for the alerts.tsx fingerprint-modal fix. + * + * Bug: when alerts re-fetched (polling / WebSocket), the useEffect that opens + * the ViewAlertModal or EnrichAlertSidePanel was re-evaluated. If the alert + * list was momentarily empty (during the refetch) the component would fire a + * false "Alert fingerprint not found" toast and close the modal. + * + * Fix: `resolvedFingerprintRef` stores the fingerprint once it has been + * matched so that subsequent re-evaluations of the same fingerprint (with an + * empty or partial alerts list) do not trigger the error path. + */ +import React from "react"; +import { render, act, waitFor } from "@testing-library/react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useProviders } from "@/utils/hooks/useProviders"; +import { usePresets } from "@/entities/presets/model"; +import { useAlertsTableData } from "@/widgets/alerts-table/ui/useAlertsTableData"; +import { showErrorToast } from "@/shared/ui"; +import Alerts from "../alerts"; + +// ─── Mock navigation ───────────────────────────────────────────────────────── + +jest.mock("next/navigation", () => ({ + useRouter: jest.fn(), + useSearchParams: jest.fn(), +})); + +// ─── Mock data hooks ───────────────────────────────────────────────────────── + +jest.mock("@/utils/hooks/useProviders", () => ({ + useProviders: jest.fn(), +})); + +jest.mock("@/entities/presets/model", () => ({ + usePresets: jest.fn(), +})); + +jest.mock("@/widgets/alerts-table/ui/useAlertsTableData", () => ({ + useAlertsTableData: jest.fn(), +})); + +// ─── Mock UI utilities ─────────────────────────────────────────────────────── + +jest.mock("@/shared/ui", () => ({ + showErrorToast: jest.fn(), + KeepLoader: () => null, +})); + +// ─── Mock all heavy child components ──────────────────────────────────────── +// Only ViewAlertModal and EnrichAlertSidePanel render observable testid +// attributes so we can assert that the fix works. + +jest.mock("../alert-table-tab-panel-server-side", () => ({ + __esModule: true, + default: () =>
, +})); + +jest.mock("@/features/alerts/alert-history", () => ({ + AlertHistoryModal: () => null, +})); + +jest.mock("@/features/alerts/alert-assign-ticket", () => ({ + AlertAssignTicketModal: () => null, +})); + +jest.mock("@/features/alerts/alert-note", () => ({ + AlertNoteModal: () => null, +})); + +jest.mock("@/features/alerts/alert-call-provider-method", () => ({ + AlertMethodModal: () => null, +})); + +jest.mock("@/features/workflows/manual-run-workflow", () => ({ + ManualRunWorkflowModal: () => null, +})); + +jest.mock("@/features/alerts/dismiss-alert", () => ({ + AlertDismissModal: () => null, +})); + +jest.mock("@/features/alerts/view-raw-alert", () => ({ + // Renders a testid only when an alert is supplied so tests can assert on it. + ViewAlertModal: ({ alert }: any) => + alert ?
: null, +})); + +jest.mock("@/features/alerts/alert-change-status", () => ({ + AlertChangeStatusModal: () => null, +})); + +jest.mock("@/features/alerts/enrich-alert", () => ({ + EnrichAlertSidePanel: ({ isOpen }: any) => + isOpen ?
: null, +})); + +jest.mock("@/app/(keep)/not-found", () => ({ + __esModule: true, + default: () =>
Not Found
, +})); + +// ─── Helpers ───────────────────────────────────────────────────────────────── + +const makeAlert = (fingerprint: string) => ({ + id: fingerprint, + fingerprint, + name: `Alert ${fingerprint}`, + description: "", + severity: "critical", + status: "firing", + source: ["prometheus"], + lastReceived: new Date(), + environment: "production", + pushed: false, + deleted: false, + dismissed: false, + enriched_fields: [], +}); + +const baseAlertsData = { + alerts: [] as ReturnType[], + alertsLoading: false, + mutateAlerts: jest.fn(), + alertsError: null, + totalCount: 0, + facetsCel: null, + facetsPanelRefreshToken: null, +}; + +const mockReplace = jest.fn(); +const mockSearchParamsGet = jest.fn(); + +// ─── Global setup ──────────────────────────────────────────────────────────── + +beforeEach(() => { + jest.clearAllMocks(); + + (useRouter as jest.Mock).mockReturnValue({ + replace: mockReplace, + push: jest.fn(), + back: jest.fn(), + }); + + // Return an object with a controllable .get() so each test can set params. + (useSearchParams as jest.Mock).mockReturnValue({ + get: mockSearchParamsGet, + }); + // Default: no query params. + mockSearchParamsGet.mockReturnValue(null); + + (useProviders as jest.Mock).mockReturnValue({ + data: { installed_providers: [] }, + }); + + // Return empty saved presets; "feed" comes from defaultPresets inside the + // component so selectedPreset will be found without any extra setup. + (usePresets as jest.Mock).mockReturnValue({ + dynamicPresets: [], + isLoading: false, + }); + + (useAlertsTableData as jest.Mock).mockReturnValue(baseAlertsData); +}); + +// ─── Tests ─────────────────────────────────────────────────────────────────── + +describe("Alerts — fingerprint modal fix (dataSettled guard)", () => { + it("does NOT fire error when alerts is briefly empty but totalCount > 0 (stale-empty SWR flash)", async () => { + // Regression test for the 3-render cascade in useLastAlerts: + // SWR marks isLoading=false before the React state carrying the real results + // has been flushed. For one render, alerts=[] while totalCount is already + // the real count (>0). The fix: only act when alerts.length>0 OR totalCount===0. + + const alert = makeAlert("fp-stale"); + + mockSearchParamsGet.mockImplementation((key: string) => + key === "alertPayloadFingerprint" ? "fp-stale" : null + ); + + // Phase 1 — stale-empty flash: alerts=[], alertsLoading=false, totalCount=5 + (useAlertsTableData as jest.Mock).mockReturnValue({ + ...baseAlertsData, + alerts: [], + alertsLoading: false, + totalCount: 5, + }); + + const { rerender } = render( + + ); + + // No error should fire during the stale-empty phase. + await waitFor(() => { + expect(showErrorToast).not.toHaveBeenCalled(); + }); + + // Phase 2 — real data arrives: alerts=[alert], totalCount=1 + (useAlertsTableData as jest.Mock).mockReturnValue({ + ...baseAlertsData, + alerts: [alert], + alertsLoading: false, + totalCount: 1, + }); + + await act(async () => { + rerender(); + }); + + // Modal should open and still no error. + expect(showErrorToast).not.toHaveBeenCalled(); + expect(mockReplace).not.toHaveBeenCalled(); + }); +}); + +describe("Alerts — fingerprint modal fix (resolvedFingerprintRef)", () => { + it("opens view modal when fingerprint is found and shows no error", async () => { + const alert = makeAlert("fp-abc"); + + mockSearchParamsGet.mockImplementation((key: string) => + key === "alertPayloadFingerprint" ? "fp-abc" : null + ); + + (useAlertsTableData as jest.Mock).mockReturnValue({ + ...baseAlertsData, + alerts: [alert], + }); + + const { findByTestId } = render( + + ); + + // Modal should appear. + await findByTestId("view-alert-modal"); + expect(showErrorToast).not.toHaveBeenCalled(); + }); + + it("shows error toast when fingerprint is not in the alerts list", async () => { + mockSearchParamsGet.mockImplementation((key: string) => + key === "alertPayloadFingerprint" ? "fp-missing" : null + ); + + // Alerts list present but does not contain the requested fingerprint. + (useAlertsTableData as jest.Mock).mockReturnValue({ + ...baseAlertsData, + alerts: [makeAlert("fp-other")], + }); + + render(); + + await waitFor(() => { + expect(showErrorToast).toHaveBeenCalledWith( + null, + "Alert fingerprint not found" + ); + }); + + // URL should have been cleared. + expect(mockReplace).toHaveBeenCalled(); + }); + + it("does NOT show error toast on background re-fetch after fingerprint was resolved", async () => { + // Core regression test: after a successful modal open, the alerts list + // briefly empties (due to a polling re-fetch), then repopulates. + // Without the fix, the empty-list evaluation fires the error toast. + + const alert = makeAlert("fp-abc"); + + mockSearchParamsGet.mockImplementation((key: string) => + key === "alertPayloadFingerprint" ? "fp-abc" : null + ); + + // Step 1 — alert is present; modal opens and ref is stored. + (useAlertsTableData as jest.Mock).mockReturnValue({ + ...baseAlertsData, + alerts: [alert], + }); + + const { rerender } = render( + + ); + + await waitFor(() => { + expect(showErrorToast).not.toHaveBeenCalled(); + }); + + // Step 2 — alerts list empties mid-refetch. + (useAlertsTableData as jest.Mock).mockReturnValue({ + ...baseAlertsData, + alerts: [], + }); + + await act(async () => { + rerender(); + }); + + // The fix: resolvedFingerprintRef is still "fp-abc" so the error path is + // skipped. + expect(showErrorToast).not.toHaveBeenCalled(); + expect(mockReplace).not.toHaveBeenCalled(); + }); + + it("opens enrich sidebar when both fingerprint and enrich params are present", async () => { + const alert = makeAlert("fp-enrich"); + + mockSearchParamsGet.mockImplementation((key: string) => { + if (key === "alertPayloadFingerprint") return "fp-enrich"; + if (key === "enrich") return "true"; + return null; + }); + + (useAlertsTableData as jest.Mock).mockReturnValue({ + ...baseAlertsData, + alerts: [alert], + }); + + const { findByTestId } = render( + + ); + + await findByTestId("enrich-sidebar"); + expect(showErrorToast).not.toHaveBeenCalled(); + }); + + it("resets the ref and opens modal correctly when navigating to a different fingerprint", async () => { + // Ensure that navigating from fp-1 to fp-2 does NOT inherit fp-1's ref + // and still opens fp-2's modal without errors. + + const alert1 = makeAlert("fp-1"); + const alert2 = makeAlert("fp-2"); + + mockSearchParamsGet.mockImplementation((key: string) => + key === "alertPayloadFingerprint" ? "fp-1" : null + ); + + (useAlertsTableData as jest.Mock).mockReturnValue({ + ...baseAlertsData, + alerts: [alert1, alert2], + }); + + const { rerender } = render( + + ); + + // First fingerprint resolved — no errors. + await waitFor(() => { + expect(showErrorToast).not.toHaveBeenCalled(); + }); + + // Navigate to a different fingerprint. + mockSearchParamsGet.mockImplementation((key: string) => + key === "alertPayloadFingerprint" ? "fp-2" : null + ); + + (showErrorToast as jest.Mock).mockClear(); + + await act(async () => { + rerender(); + }); + + // fp-2 is present in the list, so the modal should open without error. + expect(showErrorToast).not.toHaveBeenCalled(); + }); +}); diff --git a/keep-ui/app/(keep)/alerts/[id]/ui/alerts.tsx b/keep-ui/app/(keep)/alerts/[id]/ui/alerts.tsx index 464f5f3c8e..ed70b60635 100644 --- a/keep-ui/app/(keep)/alerts/[id]/ui/alerts.tsx +++ b/keep-ui/app/(keep)/alerts/[id]/ui/alerts.tsx @@ -110,7 +110,14 @@ export default function Alerts({ presetName, initialFacets }: AlertsProps) { resolvedFingerprintRef.current = null; } - if (fingerprint && enrich && alerts) { + // Only act once data is actually settled: either we have alerts to search + // through, or the backend confirmed there are zero results (totalCount === 0). + // This guards against a 3-render cascade in useLastAlerts where `alerts` + // briefly equals [] while `isLoading` is already false but the React state + // carrying the actual results hasn't been flushed yet. + const dataSettled = alerts && !alertsLoading && (alerts.length > 0 || totalCount === 0); + + if (fingerprint && enrich && dataSettled) { const alert = alerts?.find((alert) => alert.fingerprint === fingerprint); if (alert) { resolvedFingerprintRef.current = fingerprint; @@ -120,7 +127,7 @@ export default function Alerts({ presetName, initialFacets }: AlertsProps) { showErrorToast(null, "Alert fingerprint not found"); resetUrlAfterModal(); } - } else if (fingerprint && alerts) { + } else if (fingerprint && dataSettled) { const alert = alerts?.find((alert) => alert.fingerprint === fingerprint); if (alert) { resolvedFingerprintRef.current = fingerprint; @@ -129,13 +136,13 @@ export default function Alerts({ presetName, initialFacets }: AlertsProps) { showErrorToast(null, "Alert fingerprint not found"); resetUrlAfterModal(); } - } else if (alerts) { + } else if (alerts && !alertsLoading) { resolvedFingerprintRef.current = null; setViewAlertModal(null); setEnrichAlertModal(null); setIsEnrichSidebarOpen(false); } - }, [searchParams, alerts]); + }, [searchParams, alerts, alertsLoading, totalCount]); const alertsQueryStateRef = useRef(alertsQueryState); From 088213f442286616aaac9e63243608ea48ed7952 Mon Sep 17 00:00:00 2001 From: Shahar Glazner Date: Tue, 24 Feb 2026 18:00:19 +0200 Subject: [PATCH 3/3] chore: Bump version from 0.49.0 to 0.49.1 (#5680) Co-authored-by: Cursor --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b79738829a..9cd082b0fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "keep" -version = "0.49.0" +version = "0.49.1" description = "Alerting. for developers, by developers." authors = ["Keep Alerting LTD"] packages = [{include = "keep"}]