diff --git a/desk/src/components/Settings/Branding.vue b/desk/src/components/Settings/Branding.vue index 3031e8257..4b11b6305 100644 --- a/desk/src/components/Settings/Branding.vue +++ b/desk/src/components/Settings/Branding.vue @@ -7,7 +7,7 @@

{{ config.title }}

@@ -51,9 +51,22 @@ import { createToast } from "@/utils"; const config = useConfigStore(); +const websiteSettings = createResource({ + url: "frappe.client.get_value", + cache: true, + params: { + doctype: "Website Settings", + fieldname: "favicon", + }, + onSuccess(data) { + state.brandFavicon = data.favicon; + }, + auto: true, +}); + const state = reactive({ brandLogo: config.brandLogo, - brandFavicon: "", + brandFavicon: websiteSettings.data?.favicon || "", }); const loadingState = reactive({ @@ -78,19 +91,6 @@ const brandingConfig = computed(() => [ }, ]); -const websiteSettings = createResource({ - url: "frappe.client.get_value", - cache: true, - params: { - doctype: "Website Settings", - fieldname: "favicon", - }, - onSuccess(data) { - state.brandFavicon = data.favicon; - }, - auto: true, -}); - const settingsResource = createResource({ url: "frappe.client.set_value", debounce: 1000, diff --git a/desk/src/pages/ticket/TicketNewArticles.vue b/desk/src/pages/ticket/TicketNewArticles.vue index 26d339d5a..9a0b78ec6 100644 --- a/desk/src/pages/ticket/TicketNewArticles.vue +++ b/desk/src/pages/ticket/TicketNewArticles.vue @@ -22,7 +22,7 @@ class="focus:ring-cyan-30 rounded-md border-2 border-hidden p-4 hover:bg-cyan-100 focus:outline-none focus:ring active:bg-cyan-50" > list[dict]: + """Return columns for the report. + + One field definition per column, just like a DocType field definition. + """ + return [ + { + "label": _("Subject"), + "fieldname": "subject", + "fieldtype": "Data", + }, + { + "label": _("Top Result"), + "fieldname": "top_res", + "fieldtype": "Text", + }, + { + "label": _("Search Score"), + "fieldname": "score", + "fieldtype": "Float", + }, + ] + + +def get_top_res(search_term: str) -> float: + """Return the search score for the top result for the search term.""" + res = search(search_term) + headings = "" + score = 0 + for item in res: + headings += item["headings"] or item["subject"] + headings += "\n" + score += item["score"] + return headings, score + + +def get_data() -> list[list]: + """Return data for the report. + + The report data is a list of rows, with each row being a list of cell values. + """ + tickets = frappe.get_all( + "HD Ticket", {"agent_group": ["like", "%FC%"]}, ["name", "subject"], limit=100 + ) + for ticket in tickets: + ticket["top_res"], ticket["score"] = get_top_res(ticket["subject"]) + return [ + [ticket["subject"], ticket["top_res"], ticket["score"]] for ticket in tickets + ] diff --git a/helpdesk/search.py b/helpdesk/search.py index d39c688fa..c21f735e1 100644 --- a/helpdesk/search.py +++ b/helpdesk/search.py @@ -12,6 +12,7 @@ import frappe from bs4 import BeautifulSoup, PageElement from frappe.utils import cstr, strip_html_tags, update_progress_bar +from frappe.utils.caching import redis_cache from frappe.utils.synchronization import filelock from redis.commands.search.field import TagField, TextField from redis.commands.search.indexDefinition import IndexDefinition @@ -62,11 +63,20 @@ "you", "me", "do", + "has", + "been", + "urgent", + "want", ] +@redis_cache(3600 * 24) +def get_stopwords(): + return STOPWORDS + frappe.get_all("HD Stopword", {"enabled": True}, pluck="name") + + class Search: - unsafe_chars = re.compile(r"[\[\]{}<>+]") + unsafe_chars = re.compile(r"[\[\]{}<>+!-]") def __init__(self, index_name, prefix, schema) -> None: self.redis = frappe.cache() @@ -95,7 +105,7 @@ def create_index(self): self.redis.ft(self.index_name).create_index( schema, definition=index_def, - stopwords=STOPWORDS, + stopwords=get_stopwords(), ) self._index_exists = True @@ -128,6 +138,7 @@ def search( query.summarize(fields=["description"]) query.scorer("DISMAX") + query.with_scores() try: result = self.redis.ft(self.index_name).search(query) @@ -147,8 +158,7 @@ def search( def clean_query(self, query): query = query.strip().replace("-*", "*") query = self.unsafe_chars.sub(" ", query) - query.strip() - return query + return query.strip().lower() def spellcheck(self, query, **kwargs): return self.redis.ft(self.index_name).spellcheck(query, **kwargs) @@ -292,7 +302,10 @@ def get_count(self, doctype): def get_records(self, doctype): records = [] - for d in frappe.db.get_all(doctype, fields=self.DOCTYPE_FIELDS[doctype]): + filters = {"status": "Published"} if doctype == "HD Article" else {} + for d in frappe.db.get_all( + doctype, filters=filters, fields=self.DOCTYPE_FIELDS[doctype] + ): d.doctype = doctype if doctype == "HD Article": for heading, section in self.get_sections(d.content): @@ -311,9 +324,9 @@ def get_records(self, doctype): def search(query, only_articles=False): search = HelpdeskSearch() query = search.clean_query(query) - query_parts = query.split(" ") + query_parts = query.split() query = " ".join( - [f"%{q}%" for q in query_parts if q not in STOPWORDS] + [f"{q}*" for q in query_parts if q not in get_stopwords()] ) # for stopwords to be ignored result = search.search(query, start=0, highlight=True) groups = {} @@ -360,6 +373,8 @@ def download_corpus(): try: data.find("taggers/averaged_perceptron_tagger_eng.zip") data.find("tokenizers/punkt_tab.zip") + data.find("corpora/brown.zip") except LookupError: download("averaged_perceptron_tagger_eng") download("punkt_tab") + download("brown")