feat: Global Search with Priorities

This commit is contained in:
Himanshu Warekar 2019-09-18 18:21:13 +05:30
parent 7d2e664b74
commit 9d3b361da4
13 changed files with 223 additions and 14 deletions

View file

@ -0,0 +1,29 @@
{
"creation": "2019-09-13 21:33:55.551941",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"document_type"
],
"fields": [
{
"fieldname": "document_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Document Type",
"options": "DocType"
}
],
"istable": 1,
"modified": "2019-09-18 17:59:44.354052",
"modified_by": "Administrator",
"module": "Desk",
"name": "Global Search DocType",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class GlobalSearchDocType(Document):
pass

View file

@ -0,0 +1,19 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Global Search Settings', {
refresh: function(frm) {
frm.add_custom_button(__("Reset"), function () {
frappe.call({
method: "frappe.desk.doctype.global_search_settings.global_search_settings.reset_global_search_settings_doctypes",
callback: function() {
frappe.show_alert({
message: __("Global Search Document Types Reset."),
indicator: "green"
});
frm.refresh();
}
});
});
}
});

View file

@ -0,0 +1,39 @@
{
"creation": "2019-09-03 16:08:21.333698",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"allowed_in_global_search"
],
"fields": [
{
"fieldname": "allowed_in_global_search",
"fieldtype": "Table",
"label": "Document Types",
"options": "Global Search DocType"
}
],
"issingle": 1,
"modified": "2019-09-18 18:00:17.388486",
"modified_by": "Administrator",
"module": "Desk",
"name": "Global Search Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View file

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe import _
class GlobalSearchSettings(Document):
def validate(self):
core_dts = []
for dt in self.allowed_in_global_search:
if frappe.get_meta(dt.document_type).module == "Core":
core_dts.append(dt.document_type)
if core_dts:
core_dts = (", ".join([frappe.bold(dt) for dt in core_dts]))
frappe.throw(_("Core Modules {0} cannot be searched in Global Search.").format(core_dts))
def get_doctypes_for_global_search():
doctypes = frappe.get_list("Global Search DocType", fields=["document_type"], order_by="idx ASC")
if not doctypes:
return []
return [d.document_type for d in doctypes]
@frappe.whitelist()
def reset_global_search_settings_doctypes():
update_global_search_doctypes()
def update_global_search_doctypes():
global_search_doctypes = frappe.get_hooks("global_search_doctypes")
allowed_in_global_search = []
for dt in global_search_doctypes:
if dt.get("index"):
allowed_in_global_search.insert(dt.get("index")-1, dt.get("doctype"))
continue
allowed_in_global_search.append(dt.get("doctype"))
global_search_settings = frappe.get_single("Global Search Settings")
global_search_settings.allowed_in_global_search = []
for dt in allowed_in_global_search:
global_search_settings.append("allowed_in_global_search", {
"document_type": dt
})
global_search_settings.save(ignore_permissions=True)

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestGlobalSearchSettings(unittest.TestCase):
pass

View file

@ -4,11 +4,12 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.desk.doctype.global_search_settings.global_search_settings import update_global_search_doctypes
def install():
update_genders_and_salutations()
update_global_search_doctypes()
@frappe.whitelist()
def update_genders_and_salutations():

View file

@ -300,3 +300,18 @@ user_privacy_documents = [
},
]
global_search_doctypes = [
{"doctype": "Contact"},
{"doctype": "Address"},
{"doctype": "Event"},
{"doctype": "Note"},
{"doctype": "Google Calendar"},
{"doctype": "Auto Repeat"},
{"doctype": "ToDo"},
{"doctype": "Country"},
{"doctype": "Currency"},
{"doctype": "Newsletter"},
{"doctype": "Contact Us Settings"},
{"doctype": "Google Contacts"}
]

View file

@ -0,0 +1,7 @@
import frappe
from frappe.desk.page.setup_wizard.install_fixtures import update_global_search_doctypes
def execute():
frappe.reload_doc("desk", "doctype", "global_search_doctype")
frappe.reload_doc("desk", "doctype", "global_search_settings")
update_global_search_doctypes()

View file

@ -7,10 +7,13 @@ import frappe
from frappe.utils import global_search
from frappe.test_runner import make_test_objects
from frappe.desk.page.setup_wizard.install_fixtures import update_global_search_doctypes
import frappe.utils
class TestGlobalSearch(unittest.TestCase):
def setUp(self):
update_global_search_doctypes()
global_search.setup_global_search_table()
self.assertTrue('__global_search' in frappe.db.get_tables())
doctype = "Event"

View file

@ -419,32 +419,48 @@ def search(text, start=0, limit=20, doctype=""):
:param limit: number of results to return, default 20
:return: Array of result objects
"""
from frappe.desk.doctype.global_search_settings.global_search_settings import get_doctypes_for_global_search
results = []
texts = text.split('&')
texts = [t.strip() for t in text.split('&')]
priorities = get_doctypes_for_global_search()
allowed_doctypes = ",".join(["'{0}'".format(dt) for dt in priorities])
for text in texts:
mariadb_conditions = ''
postgres_conditions = ''
if doctype:
mariadb_conditions = postgres_conditions = '`doctype` = {} AND '.format(frappe.db.escape(doctype))
mariadb_conditions += 'MATCH(`content`) AGAINST ({} IN BOOLEAN MODE)'.format(frappe.db.escape('+' + text + '*'))
postgres_conditions += 'TO_TSVECTOR("content") @@ PLAINTO_TSQUERY({})'.format(frappe.db.escape(text))
mariadb_text = frappe.db.escape('+' + text + '*')
common_query = '''SELECT `doctype`, `name`, `content`
FROM `__global_search`
WHERE {conditions}
LIMIT {limit} OFFSET {start}'''
mariadb_fields = '`doctype`, `name`, `content`, MATCH (`content`) AGAINST ({} IN BOOLEAN MODE) AS rank'.format(mariadb_text)
postgres_fields = '`doctype`, `name`, `content`, TO_TSVECTOR("content") @@ PLAINTO_TSQUERY({}) AS rank'.format(frappe.db.escape(text))
if allowed_doctypes:
mariadb_conditions += '`doctype` IN ({})'.format(allowed_doctypes)
postgres_conditions += '`doctype` IN ({})'.format(allowed_doctypes)
common_query = """
SELECT {fields}
FROM `__global_search`
WHERE {conditions}
ORDER BY rank DESC
LIMIT {limit}
OFFSET {start}
"""
result = frappe.db.multisql({
'mariadb': common_query.format(conditions=mariadb_conditions, limit=limit, start=start),
'postgres': common_query.format(conditions=postgres_conditions, limit=limit, start=start)
'mariadb': common_query.format(fields=mariadb_fields, conditions=mariadb_conditions, limit=limit, start=start),
'postgres': common_query.format(fields=postgres_fields, conditions=postgres_conditions, limit=limit, start=start)
}, as_dict=True)
tmp_result=[]
for i in result:
if i in results or not results:
tmp_result.append(i)
results += tmp_result
if i.rank > 0.0:
if i in results or not results:
tmp_result.extend([i])
results.extend(tmp_result)
for r in results:
try:
@ -453,7 +469,17 @@ def search(text, start=0, limit=20, doctype=""):
except Exception:
frappe.clear_messages()
return results
sorted_results = []
for priority in priorities:
tmp_result = []
for r in results:
if r.doctype == priority:
tmp_result.extend([r])
sorted_results.extend(tmp_result)
return sorted_results
@frappe.whitelist(allow_guest=True)