From 7345b6b078354c576d800b19e7bc51fb3b58eb20 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 12 Jun 2025 21:17:58 +0530 Subject: [PATCH] perf: Limit get_open_count to 1s for each count query (#32920) If left unoptimized this wreaks havoc on database servers and has little real value. If it's valuable people will complaint about "?" and it will get fixed. This constriant on runtime should always be present. --- frappe/desk/notifications.py | 33 +++++++++++++++-------- frappe/public/js/frappe/form/dashboard.js | 20 +++++++------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index 995c16154c..5aa9f0a40e 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -2,6 +2,7 @@ # License: MIT. See LICENSE import json +from typing import Literal from bs4 import BeautifulSoup @@ -251,6 +252,18 @@ def get_open_count(doctype: str, name: str, items=None): if frappe.flags.in_migrate or frappe.flags.in_install: return {"count": []} + # None of the count queries should take more than 1s individually + frappe.db.set_execution_timeout(1) + + try: + return _get_linked_document_counts(doctype, name, items) + except Exception as e: + if frappe.db.is_statement_timeout(e): + return {"count": []} + raise + + +def _get_linked_document_counts(doctype: str, name: str, items=None): doc = frappe.get_lazy_doc(doctype, name) doc.check_permission() meta = doc.meta @@ -342,18 +355,16 @@ def get_external_links(doctype, name, links): return {"doctype": doctype, "count": total_count, "open_count": open_count} -def get_doc_count(doctype, filters): - return len( - frappe.get_all( - doctype, - fields="name", - filters=filters, - limit=100, - distinct=True, - ignore_ifnull=True, - order_by=None, +def get_doc_count(doctype, filters) -> int | Literal["?"]: + try: + docs = frappe.get_all( + doctype, filters=filters, limit=100, distinct=True, ignore_ifnull=True, order_by=None ) - ) + return len(docs) + except Exception as e: + if frappe.db.is_statement_timeout(e): # Skip fetching correct count if it's too slow + return "?" + raise def get_dynamic_link_filters(doctype, links, fieldname): diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js index f381543231..ddb8ba2e02 100644 --- a/frappe/public/js/frappe/form/dashboard.js +++ b/frappe/public/js/frappe/form/dashboard.js @@ -457,18 +457,14 @@ frappe.ui.form.Dashboard = class FormDashboard { $.each(count.internal_links_found, function (i, d) { me.frm.dashboard.set_badge_count_for_internal_link( d.doctype, - cint(d.open_count), - cint(d.count), + d.open_count, + d.count, d.names ); }); $.each(count.external_links_found, function (i, d) { - me.frm.dashboard.set_badge_count_for_external_link( - d.doctype, - cint(d.open_count), - cint(d.count) - ); + me.frm.dashboard.set_badge_count_for_external_link(d.doctype, d.open_count, d.count); }); } @@ -499,14 +495,20 @@ frappe.ui.form.Dashboard = class FormDashboard { $link .find(".open-notification") .removeClass("hidden") - .html(open_count > 99 ? "99+" : open_count); + .html(cint(open_count) > 99 ? "99+" : open_count); } if (count) { $link .find(".count") .removeClass("hidden") - .text(count > 99 ? "99+" : count); + .text(cint(count) > 99 ? "99+" : count) + .attr( + "title", + count != "?" + ? __("Count of linked documents") + : __("Accurate count can not be fetched, click here to view all documents") + ); } }