Merge pull request #35007 from barredterra/cache-empty-search_link

perf: cache empty search links
This commit is contained in:
Ankush Menat 2025-12-17 12:35:38 +05:30 committed by GitHub
commit 722634a112
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 69 additions and 39 deletions

View file

@ -58,13 +58,12 @@ context("Control Link", () => {
true
);
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
cy.wait("@search_link");
cy.wait(500);
// Wait for dropdown to appear (request might be cached)
cy.get("@input").parent().findByRole("listbox").should("be.visible");
cy.wait(200);
cy.get("@input").type("todo for link", { delay: 100 });
cy.wait("@search_link");
// Wait for dropdown to update with search results
cy.wait(500);
cy.get("@input").parent().findByRole("listbox").should("be.visible");
cy.get("@input").type("{enter}");
@ -81,10 +80,10 @@ context("Control Link", () => {
get_dialog_with_link().as("dialog");
cy.intercept("/api/method/frappe.client.validate_link*").as("validate_link");
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
cy.wait("@search_link");
cy.wait(500);
// Wait for dropdown to appear (request might be cached)
cy.get("@input").parent().findByRole("listbox").should("be.visible");
cy.wait(200);
cy.get("@input").type("invalid value", { delay: 100 }).blur();
cy.wait("@validate_link");
cy.get("@input").should("have.value", "");
@ -94,11 +93,11 @@ context("Control Link", () => {
get_dialog_with_link().as("dialog");
cy.intercept("/api/method/frappe.client.validate_link*").as("validate_link");
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
cy.wait("@search_link");
cy.wait(500);
// Wait for dropdown to appear (request might be cached)
cy.get("@input").parent().findByRole("listbox").should("be.visible");
cy.wait(200);
cy.get("@input").type(" ", { delay: 100 }).blur();
cy.wait("@validate_link");
cy.get("@input").should("have.value", "");
@ -112,12 +111,11 @@ context("Control Link", () => {
it("should show open link button", () => {
get_dialog_with_link().as("dialog");
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.get("@todos").then((todos) => {
cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
cy.wait("@search_link");
cy.wait(500);
// Wait for dropdown to appear (request might be cached)
cy.get("@input").parent().findByRole("listbox").should("be.visible");
cy.wait(200);
cy.get("@input").type(todos[0], { delay: 100 }).blur();
// not waiting for validate_link because it will not get called
cy.get("@input").trigger("mouseover");
@ -156,13 +154,12 @@ context("Control Link", () => {
}
});
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
cy.wait("@search_link");
cy.wait(500);
// Wait for dropdown to appear (request might be cached)
cy.get("@input").parent().findByRole("listbox").should("be.visible");
cy.wait(200);
cy.get("@input").type("todo for link", { delay: 100 });
cy.wait("@search_link");
// Wait for dropdown to update with search results
cy.wait(500);
cy.get(".frappe-control[data-fieldname=link] ul").should("be.visible");
cy.get("@input").type("{enter}");
@ -182,7 +179,6 @@ context("Control Link", () => {
it("should update dependant fields (via fetch_from)", () => {
cy.get("@todos").then((todos) => {
cy.visit(`/desk/todo/${todos[0]}`);
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.intercept("/api/method/frappe.client.validate_link*").as("validate_link");
cy.fill_field("assigned_by", cy.config("testUser"), "Link");
@ -211,7 +207,9 @@ context("Control Link", () => {
// set valid value again
cy.get("@input").clear().focus();
cy.wait("@search_link");
// Wait for dropdown to appear (request might be cached)
cy.get("@input").parent().findByRole("listbox").should("be.visible");
cy.wait(200);
cy.get("@input").type(cy.config("testUser"), { delay: 100 }).blur();
cy.wait("@validate_link");
@ -280,12 +278,13 @@ context("Control Link", () => {
cy.wait(500);
get_dialog_with_gender_link().as("dialog");
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
cy.wait("@search_link");
// Wait for dropdown to appear (request might be cached)
cy.get("@input").parent().findByRole("listbox").should("be.visible");
cy.wait(200);
cy.get("@input").type("Sonstiges", { delay: 100 });
cy.wait("@search_link");
// Wait for dropdown to update with search results
cy.wait(500);
cy.get(".frappe-control[data-fieldname=link] ul").should("be.visible");
cy.get(".frappe-control[data-fieldname=link] input").type("{enter}");
@ -312,12 +311,13 @@ context("Control Link", () => {
cy.wait(1000);
get_dialog_with_gender_link().as("dialog");
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
cy.wait("@search_link");
// Wait for dropdown to appear (request might be cached)
cy.get("@input").parent().findByRole("listbox").should("be.visible");
cy.wait(200);
cy.get("@input").type("Non-Conforming", { delay: 100 });
cy.wait("@search_link");
// Wait for dropdown to update with search results
cy.wait(500);
cy.get(".frappe-control[data-fieldname=link] ul").should("be.visible");
cy.get(".frappe-control[data-fieldname=link] input").type("{enter}");

View file

@ -50,14 +50,14 @@ context("Form Builder", () => {
cy.get(".modal-body .filter-action-buttons .add-filter").click();
cy.wait(100);
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.get(".modal-body .filter-box .list_filter .filter-field .link-field input")
.focus()
.as("input");
cy.wait("@search_link");
cy.wait(500);
// Wait for dropdown to appear (request might be cached)
cy.get("@input").parent().findByRole("listbox").should("be.visible");
cy.wait(200);
cy.get("@input").type("Male", { delay: 100 });
cy.wait("@search_link");
// Wait for dropdown to update with search results
cy.wait(500);
cy.get("@input").type("{enter}", { delay: 100 });
cy.get("@input").blur();
@ -97,8 +97,6 @@ context("Form Builder", () => {
});
it("Add Table field and check if columns are rendered", () => {
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.visit(`/app/doctype/${doctype_name}`);
cy.findByRole("tab", { name: "Form" }).click();
@ -127,7 +125,8 @@ context("Form Builder", () => {
.click()
.as("input");
cy.get("@input").clear({ force: true }).type("Web Form Field", { delay: 200 });
cy.wait("@search_link");
// Wait for dropdown to appear and selection to complete
cy.wait(500);
cy.get(last_field).click({ force: true });

View file

@ -172,13 +172,12 @@ Cypress.Commands.add("fill_field", (fieldname, value, fieldtype = "Data") => {
}
if (["Link", "Dynamic Link"].includes(fieldtype)) {
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
cy.get("@input").clear().focus();
cy.wait("@search_link");
// Wait for dropdown to appear (request might be cached, so don't wait for network)
cy.get("@input").parent().findByRole("listbox").as("dropdown");
cy.get("@dropdown").should("be.visible");
cy.get("@input").type(value, { delay: 100 });
cy.wait("@search_link");
// Wait for dropdown to update with search results
cy.get("@dropdown")
.should("be.visible")
.find("div[role='option']")

View file

@ -15,6 +15,7 @@ from frappe.database.schema import SPECIAL_CHAR_PATTERN
from frappe.model.db_query import get_order_by
from frappe.permissions import has_permission
from frappe.utils import cint, cstr, escape_html, unique
from frappe.utils.caching import http_cache
from frappe.utils.data import make_filter_tuple
@ -34,6 +35,7 @@ class LinkSearchResults(TypedDict):
# this is called by the Link Field
@frappe.whitelist()
@http_cache(max_age=60 * 5, stale_while_revalidate=60 * 5)
def search_link(
doctype: str,
txt: str,

View file

@ -340,6 +340,34 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
});
}
/**
* Determine if we should use GET (enables HTTP caching) or POST.
* Use GET for empty searches with filters that fit in URL.
* Use POST for searches with text or large filters.
*/
should_use_post_for_search(txt, filters, max_get_size = 2000) {
// Always use POST if there's search text
if (txt) return true;
// If no filters, use GET
if (!filters) return false;
// Check size of filters when stringified
let filters_str = filters;
if (typeof filters !== "string") {
try {
filters_str = JSON.stringify(filters);
} catch (e) {
// If stringification fails, use POST
return true;
}
}
// URL-encoded params add ~30% overhead on average
const estimated_size = filters_str.length * 1.3;
return estimated_size > max_get_size;
}
on_input(e) {
var doctype = this.get_options();
if (!doctype) return;
@ -364,10 +392,12 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
this.set_custom_query(args);
const use_get = !this.should_use_post_for_search(term, args.filters);
frappe.call({
type: "POST",
type: use_get ? "GET" : "POST",
method: "frappe.desk.search.search_link",
no_spinner: true,
cache: use_get,
args: args,
callback: (r) => {
if (!window.Cypress && !this.$input.is(":focus")) {