From 271f179b0047f9c1ec85893a19e88873acd59948 Mon Sep 17 00:00:00 2001 From: Shrihari Mahabal Date: Tue, 31 Mar 2026 12:52:15 +0530 Subject: [PATCH 1/6] refactor: remove unnecessary console log --- frappe/public/js/frappe/ui/notifications/notifications.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js index a7d40e0cf9..b87dcfd8f1 100644 --- a/frappe/public/js/frappe/ui/notifications/notifications.js +++ b/frappe/public/js/frappe/ui/notifications/notifications.js @@ -34,7 +34,6 @@ frappe.ui.Notifications = class Notifications { `) .on("click", (e) => { e.stopImmediatePropagation(); - console.log("what"); frappe.set_route("Form", "Notification Settings", frappe.session.user); }) .appendTo(this.header_actions) From ecc43b95b1faffb6af4f4521d604be286126f8c8 Mon Sep 17 00:00:00 2001 From: Shrihari Mahabal Date: Tue, 31 Mar 2026 21:58:07 +0530 Subject: [PATCH 2/6] perf: load notifications and events on demand --- .../frappe/ui/notifications/notifications.js | 72 +++++++++++++------ frappe/public/js/frappe/ui/sidebar/sidebar.js | 6 +- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js index b87dcfd8f1..ec91f15291 100644 --- a/frappe/public/js/frappe/ui/notifications/notifications.js +++ b/frappe/public/js/frappe/ui/notifications/notifications.js @@ -223,15 +223,13 @@ class NotificationsView extends BaseNotificationsView { .tooltip({ delay: { show: 600, hide: 100 }, trigger: "hover" }); this.setup_notification_listeners(); - this.get_notifications_list(this.max_length).then((r) => { - if (!r.message) return; - this.dropdown_items = r.message.notification_logs; - frappe.update_user_info(r.message.user_info); - this.render_notifications_dropdown(); - if (this.settings.seen == 0 && this.dropdown_items.length > 0) { - this.toggle_notification_icon(false); - } - }); + + this.dropdown_items = []; + this.notifications_fetched = false + + if (this.settings && this.settings.seen == 0) { + this.toggle_notification_icon(false); + } } update_dropdown() { @@ -407,6 +405,21 @@ class NotificationsView extends BaseNotificationsView { }); this.parent.on("show.bs.dropdown", () => { + if (!this.notifications_fetched) { + this.container.html(`
+
+
+
+
`); + this.get_notifications_list(this.max_length).then((r) => { + if (!r.message) return; + this.dropdown_items = r.message.notification_logs; + frappe.update_user_info(r.message.user_info); + this.render_notifications_dropdown(); + this.notifications_fetched = true; + }); + } + this.toggle_seen(true); if (this.notifications_icon.find(".notifications-unseen").is(":visible")) { this.toggle_notification_icon(true); @@ -420,20 +433,33 @@ class NotificationsView extends BaseNotificationsView { class EventsView extends BaseNotificationsView { make() { - let today = frappe.datetime.get_today(); - frappe - .xcall( - "frappe.desk.doctype.event.event.get_events", - { - start: today, - end: today, - }, - "GET", - { cache: true } - ) - .then((event_list) => { - this.render_events_html(event_list); - }); + this.events_fetched = false; + + this.parent.on("show.bs.dropdown", () => { + if (this.events_fetched) return; + + this.container.html(`
+
+
+
+
`); + + let today = frappe.datetime.get_today(); + frappe + .xcall( + "frappe.desk.doctype.event.event.get_events", + { + start: today, + end: today, + }, + "GET", + { cache: true } + ) + .then((event_list) => { + this.render_events_html(event_list); + this.events_fetched = true; + }); + }); } render_events_html(event_list) { diff --git a/frappe/public/js/frappe/ui/sidebar/sidebar.js b/frappe/public/js/frappe/ui/sidebar/sidebar.js index 9b3d4c3a97..10a7f3161c 100644 --- a/frappe/public/js/frappe/ui/sidebar/sidebar.js +++ b/frappe/public/js/frappe/ui/sidebar/sidebar.js @@ -484,7 +484,11 @@ frappe.ui.Sidebar = class Sidebar { type: "Button", class: "sidebar-notification hidden", onClick: () => { - this.wrapper.find(".dropdown-notifications").toggleClass("hidden"); + const $dropdown = this.wrapper.find(".dropdown-notifications"); + $dropdown.toggleClass("hidden"); + if (!$dropdown.hasClass("hidden")) { + $dropdown.trigger("show.bs.dropdown"); + } if (frappe.is_mobile()) { this.wrapper.removeClass("expanded"); } From 73b757c201af4f5b87c9580c1d81507555b6436e Mon Sep 17 00:00:00 2001 From: Shrihari Mahabal Date: Tue, 31 Mar 2026 22:59:03 +0530 Subject: [PATCH 3/6] chore: fix formatting --- frappe/public/js/frappe/ui/notifications/notifications.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js index ec91f15291..91a6cf2807 100644 --- a/frappe/public/js/frappe/ui/notifications/notifications.js +++ b/frappe/public/js/frappe/ui/notifications/notifications.js @@ -224,8 +224,8 @@ class NotificationsView extends BaseNotificationsView { this.setup_notification_listeners(); - this.dropdown_items = []; - this.notifications_fetched = false + this.dropdown_items = []; + this.notifications_fetched = false; if (this.settings && this.settings.seen == 0) { this.toggle_notification_icon(false); From 381f5e5c0698fe4847cf2450cbfbdc6a43134921 Mon Sep 17 00:00:00 2001 From: Shrihari Mahabal Date: Mon, 6 Apr 2026 18:50:31 +0530 Subject: [PATCH 4/6] fix: set notifications list to empty when no notifications --- .../js/frappe/ui/notifications/notifications.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js index 91a6cf2807..70f61c7f8d 100644 --- a/frappe/public/js/frappe/ui/notifications/notifications.js +++ b/frappe/public/js/frappe/ui/notifications/notifications.js @@ -344,7 +344,7 @@ class NotificationsView extends BaseNotificationsView {
${__("See all Activity")}
`); } else { - this.container.append( + this.container.html( $(`
Generic Empty State @@ -412,9 +412,12 @@ class NotificationsView extends BaseNotificationsView {
`); this.get_notifications_list(this.max_length).then((r) => { - if (!r.message) return; - this.dropdown_items = r.message.notification_logs; - frappe.update_user_info(r.message.user_info); + if (r.message && r.message.notification_logs) { + this.dropdown_items = r.message.notification_logs; + frappe.update_user_info(r.message.user_info); + } else { + this.dropdown_items = []; + } this.render_notifications_dropdown(); this.notifications_fetched = true; }); From 838506a6e13e6e3d33941451b194a076cb99da9d Mon Sep 17 00:00:00 2001 From: Mikael Ylinen Date: Tue, 7 Apr 2026 23:15:41 +0300 Subject: [PATCH 5/6] fix(website): disable HTML caching for portal pages --- frappe/www/portal.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/www/portal.py b/frappe/www/portal.py index 610063cdc2..9aebe54c02 100644 --- a/frappe/www/portal.py +++ b/frappe/www/portal.py @@ -6,6 +6,8 @@ from frappe.model.document import Document from frappe.utils.data import quoted from frappe.www.list import get_list_context, get_list_data +no_cache = 1 + def get_context(context, **dict_params): frappe.local.form_dict.update(dict_params) From 51cfc8181e55fa09934363120e208fe9bbc14877 Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas <64089478+kaulith@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:57:07 +0530 Subject: [PATCH 6/6] perf(query): replace Coalesce with OR IS NULL in func_in (#38336) --- frappe/database/operator_map.py | 3 +-- frappe/tests/test_query_builder.py | 11 +++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/frappe/database/operator_map.py b/frappe/database/operator_map.py index c8cb9aa099..218983a318 100644 --- a/frappe/database/operator_map.py +++ b/frappe/database/operator_map.py @@ -8,7 +8,6 @@ import frappe from frappe.database.utils import NestedSetHierarchy from frappe.model.db_query import get_timespan_date_range from frappe.query_builder import Field -from frappe.query_builder.functions import Coalesce from frappe.utils import cstr @@ -51,7 +50,7 @@ def func_in(key: Field, value: list | tuple) -> frappe.qb: value = ["" if v is None else v for v in value] if "" in value: - return Coalesce(key, "").isin(value) + return key.isin(value) | key.isnull() return key.isin(value) diff --git a/frappe/tests/test_query_builder.py b/frappe/tests/test_query_builder.py index 920b06e4af..f0df927238 100644 --- a/frappe/tests/test_query_builder.py +++ b/frappe/tests/test_query_builder.py @@ -525,15 +525,17 @@ class TestOperatorIn(IntegrationTestCase): query = func_in(note.name, [None, "user1"]) sql_str = str(query).lower() - self.assertIn("coalesce", sql_str) + self.assertNotIn("coalesce", sql_str) + self.assertIn("is null", sql_str) self.assertIn("''", sql_str) - def test_func_in_with_empty_string_uses_coalesce(self): + def test_func_in_with_empty_string_uses_or_is_null(self): note = frappe.qb.DocType("Note") query = func_in(note.name, ["", "user1"]) sql_str = str(query).lower() - self.assertIn("coalesce", sql_str) + self.assertNotIn("coalesce", sql_str) + self.assertIn("is null", sql_str) self.assertIn("''", sql_str) def test_func_in_with_mixed_none_and_values(self): @@ -541,7 +543,8 @@ class TestOperatorIn(IntegrationTestCase): query = func_in(note.name, ["val1", None, "val2"]) sql_str = str(query).lower() - self.assertIn("coalesce", sql_str) + self.assertNotIn("coalesce", sql_str) + self.assertIn("is null", sql_str) def test_in_filter_matches_null_and_empty_columns(self): test_doctype = new_doctype(