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 @@
@@ -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")