Repository: keephq/keep
Pull request: fix: feed counter
<!-- 
Thanks for creating this pull request 🤗

Please make sure that the pull request is limited to one type (docs, feature, etc.) and keep it as small as possible. You can open multiple prs instead of opening a huge one.
-->

<!-- If this pull request closes an issue, please mention the issue number below -->
Closes #2585 

## 📑 Description
<!-- Add a brief description of the pr -->

<!-- You can also choose to add a list of changes and if they have been completed or not by using the markdown to-do list syntax
- [ ] Not Completed
- [x] Completed
-->

## ✅ Checks
<!-- Make sure your pr passes the CI checks and do check the following fields as needed - -->
- [ ] My pull request adheres to the code style of this project
- [ ] My code requires changes to the documentation
- [ ] I have updated the documentation as required
- [ ] All the tests have passed

## ℹ Additional Information
<!-- Any additional information like breaking changes, dependencies added, screenshots, comparisons between new and old behavior, etc. -->


Problem statement:
[🐛 Bug]: Feed counter is not updating
**Describe the bug**
Feed counter is not reflecting an update without a page reload.

**To Reproduce**
Steps to reproduce the behavior:

1. Open 1 tab of keep-ui any page, (e.g. /alerts/feed) 
2. Open/settings?selectedTab=webhook
3. Click `Click to create an example Alert` button
4. See the badge in the first tab is not updated.

**Expected behavior**
Badge counter should add up 1.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Additional context**
It seems to happen because the "async-presets" WS even is not being sent for static presets and the once fetched count take precedence over updated localstorage value.

Hints from issue discussion:
<!-- Answer -->
I found a similar open issue titled "[Feed preset counter is not updating](https://github.com/keephq/keep/issues/1164)" which was last updated on October 02, 2024 <sup>[[1]](https://github.com/keephq/keep/issues/1164)</sup>.

The issue with the feed counter not updating without a page reload could indeed be related to the "async-presets" WebSocket event not being sent. The `usePresets` hook in the keep-ui application uses a WebSocket to listen for the "async-presets" event to update local presets. If this event is not being sent, the local presets will not be updated in real-time, resulting in the feed counter not updating until a page reload occurs <sup>[[2]](https://github.com/keephq/keep/blob/main/keep-ui/utils/hooks/usePresets.ts)</sup><sup>[[3]](https://github.com/keephq/keep/blob/main/keep/api/tasks/process_event_task.py)</sup>.

Ensure that the Pusher client is correctly configured and that there are no exceptions being logged when attempting to trigger the "async-presets" event. The Pusher client configuration involves several environment variables, such as `PUSHER_DISABLED`, `PUSHER_HOST`, `PUSHER_PORT`, and `PUSHER_APP_KEY`. If `PUSHER_DISABLED` is set to "True", it may prevent the WebSocket event from being sent <sup>[[4]](https://github.com/keephq/keep/blob/main/docs/deployment/configuration.mdx)</sup><sup>[[5]](https://github.com/keephq/keep/blob/main/keep/api/routes/pusher.py)</sup>.


<!-- Dosu Comment Footer -->
*To continue talking to [Dosu](https://dosu.dev), mention @dosu.*

Commit message: wip: revalidate presets which are updated via `async-presets`

(cherry picked from commit f817864033d7e57698cf5d4e1e632a2e14587772)

Patch to review:
diff --git a/keep-ui/utils/hooks/usePresets.ts b/keep-ui/utils/hooks/usePresets.ts
index 6cc10873d5..bfad2b3e01 100644
--- a/keep-ui/utils/hooks/usePresets.ts
+++ b/keep-ui/utils/hooks/usePresets.ts
@@ -9,6 +9,7 @@ import { useConfig } from "./useConfig";
 import useSWRSubscription from "swr/subscription";
 import { useWebsocket } from "./usePusher";
 import { useSearchParams } from "next/navigation";
+import { useRevalidateMultiple } from "../state";
 
 export const usePresets = (type?: string, useFilters?: boolean) => {
   const { data: session } = useSession();
@@ -31,6 +32,7 @@ export const usePresets = (type?: string, useFilters?: boolean) => {
   const presetsOrderRef = useRef(presetsOrderFromLS);
   const staticPresetsOrderRef = useRef(staticPresetsOrderFromLS);
   const { bind, unbind } = useWebsocket();
+  const revalidateMultiple = useRevalidateMultiple();
 
   useEffect(() => {
     presetsOrderRef.current = presetsOrderFromLS;
@@ -88,6 +90,8 @@ export const usePresets = (type?: string, useFilters?: boolean) => {
     (_, { next }) => {
       const newPresets = (newPresets: Preset[]) => {
         updateLocalPresets(newPresets);
+        const presetKeys = newPresets.map((preset) => `preset/${preset.id}`);
+        revalidateMultiple(presetKeys);
         next(null, {
           presets: newPresets,
           isAsyncLoading: false,
diff --git a/keep/api/core/db.py b/keep/api/core/db.py
index f374117eed..844b88e1ee 100644
--- a/keep/api/core/db.py
+++ b/keep/api/core/db.py
@@ -40,6 +40,7 @@
 from sqlalchemy.sql import exists, expression
 from sqlmodel import Session, SQLModel, col, or_, select, text
 
+from keep.api.consts import STATIC_PRESETS
 from keep.api.core.db_utils import create_db_engine, get_json_extract_field
 
 # This import is required to create the tables
@@ -2624,7 +2625,6 @@ def get_preset_by_name(tenant_id: str, preset_name: str) -> Preset:
         ).first()
     return preset
 
-
 def get_all_presets(tenant_id: str) -> List[Preset]:
     with Session(engine) as session:
         presets = (
@@ -2634,6 +2634,11 @@ def get_all_presets(tenant_id: str) -> List[Preset]:
         )
     return presets
 
+# 
+def get_db_and_static_presets_dtos(tenant_id: str) -> List[PresetDto]:
+    presets = get_all_presets(tenant_id)
+    static_presets_dtos = list(STATIC_PRESETS.values())
+    return [PresetDto(**preset.to_dict()) for preset in presets] + static_presets_dtos
 
 def get_dashboards(tenant_id: str, email=None) -> List[Dict[str, Any]]:
     with Session(engine) as session:
diff --git a/keep/api/tasks/process_event_task.py b/keep/api/tasks/process_event_task.py
index a4bec5e8c2..c78ae6a473 100644
--- a/keep/api/tasks/process_event_task.py
+++ b/keep/api/tasks/process_event_task.py
@@ -21,6 +21,7 @@
     bulk_upsert_alert_fields,
     get_alerts_by_fingerprint,
     get_all_presets,
+    get_db_and_static_presets_dtos,
     get_enrichment_with_session,
     get_session_sync,
 )
@@ -443,12 +444,11 @@ def __handle_formatted_events(
         return
     
     try:
-        presets = get_all_presets(tenant_id)
+        presets = get_db_and_static_presets_dtos(tenant_id)
         rules_engine = RulesEngine(tenant_id=tenant_id)
         presets_do_update = []
-        for preset in presets:
+        for preset_dto in presets:
             # filter the alerts based on the search query
-            preset_dto = PresetDto(**preset.to_dict())
             filtered_alerts = rules_engine.filter_alerts(
                 enriched_formatted_events, preset_dto.cel_query
             )
@@ -458,7 +458,7 @@ def __handle_formatted_events(
             presets_do_update.append(preset_dto)
             preset_dto.alerts_count = len(filtered_alerts)
             # update noisy
-            if preset.is_noisy:
+            if preset_dto.is_noisy:
                 firing_filtered_alerts = list(
                     filter(
                         lambda alert: alert.status == AlertStatus.FIRING.value,


Code review comments:
diff_hunk: @@ -2634,6 +2634,11 @@ def get_all_presets(tenant_id: str) -> List[Preset]:
         )
     return presets
 
+# 
+def get_db_and_static_presets_dtos(tenant_id: str) -> List[PresetDto]:
path: keep/api/core/db.py
text: @user1:
It's confusing that we have "get_all" and "get_db_and_static_presets_dtos" = "get all + smth". Shouldn't it be "get_db_only_presets" and "get_all"? :)

diff_hunk: @@ -2634,6 +2634,11 @@ def get_all_presets(tenant_id: str) -> List[Preset]:
         )
     return presets
 
+# 
path: keep/api/core/db.py
text: @user1:
Leftover