From 9d3b361da4ea812b412ca01a06334c0d6a34f3dc Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 18 Sep 2019 18:21:13 +0530 Subject: [PATCH] feat: Global Search with Priorities --- .../doctype/global_search_doctype/__init__.py | 0 .../global_search_doctype.json | 29 +++++++++++ .../global_search_doctype.py | 10 ++++ .../global_search_settings/__init__.py | 0 .../global_search_settings.js | 19 +++++++ .../global_search_settings.json | 39 ++++++++++++++ .../global_search_settings.py | 50 ++++++++++++++++++ .../test_global_search_settings.py | 10 ++++ .../page/setup_wizard/install_fixtures.py | 3 +- frappe/hooks.py | 15 ++++++ frappe/patches/v12_0/update_global_search.py | 7 +++ frappe/tests/test_global_search.py | 3 ++ frappe/utils/global_search.py | 52 ++++++++++++++----- 13 files changed, 223 insertions(+), 14 deletions(-) create mode 100644 frappe/desk/doctype/global_search_doctype/__init__.py create mode 100644 frappe/desk/doctype/global_search_doctype/global_search_doctype.json create mode 100644 frappe/desk/doctype/global_search_doctype/global_search_doctype.py create mode 100644 frappe/desk/doctype/global_search_settings/__init__.py create mode 100644 frappe/desk/doctype/global_search_settings/global_search_settings.js create mode 100644 frappe/desk/doctype/global_search_settings/global_search_settings.json create mode 100644 frappe/desk/doctype/global_search_settings/global_search_settings.py create mode 100644 frappe/desk/doctype/global_search_settings/test_global_search_settings.py create mode 100644 frappe/patches/v12_0/update_global_search.py diff --git a/frappe/desk/doctype/global_search_doctype/__init__.py b/frappe/desk/doctype/global_search_doctype/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/desk/doctype/global_search_doctype/global_search_doctype.json b/frappe/desk/doctype/global_search_doctype/global_search_doctype.json new file mode 100644 index 0000000000..648e8f1824 --- /dev/null +++ b/frappe/desk/doctype/global_search_doctype/global_search_doctype.json @@ -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 +} \ No newline at end of file diff --git a/frappe/desk/doctype/global_search_doctype/global_search_doctype.py b/frappe/desk/doctype/global_search_doctype/global_search_doctype.py new file mode 100644 index 0000000000..4c9a948278 --- /dev/null +++ b/frappe/desk/doctype/global_search_doctype/global_search_doctype.py @@ -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 diff --git a/frappe/desk/doctype/global_search_settings/__init__.py b/frappe/desk/doctype/global_search_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/desk/doctype/global_search_settings/global_search_settings.js b/frappe/desk/doctype/global_search_settings/global_search_settings.js new file mode 100644 index 0000000000..97feb951d6 --- /dev/null +++ b/frappe/desk/doctype/global_search_settings/global_search_settings.js @@ -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(); + } + }); + }); + } +}); diff --git a/frappe/desk/doctype/global_search_settings/global_search_settings.json b/frappe/desk/doctype/global_search_settings/global_search_settings.json new file mode 100644 index 0000000000..e0b4b11d05 --- /dev/null +++ b/frappe/desk/doctype/global_search_settings/global_search_settings.json @@ -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 +} \ No newline at end of file diff --git a/frappe/desk/doctype/global_search_settings/global_search_settings.py b/frappe/desk/doctype/global_search_settings/global_search_settings.py new file mode 100644 index 0000000000..c8dc86fe9e --- /dev/null +++ b/frappe/desk/doctype/global_search_settings/global_search_settings.py @@ -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) \ No newline at end of file diff --git a/frappe/desk/doctype/global_search_settings/test_global_search_settings.py b/frappe/desk/doctype/global_search_settings/test_global_search_settings.py new file mode 100644 index 0000000000..a378695448 --- /dev/null +++ b/frappe/desk/doctype/global_search_settings/test_global_search_settings.py @@ -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 diff --git a/frappe/desk/page/setup_wizard/install_fixtures.py b/frappe/desk/page/setup_wizard/install_fixtures.py index 5da25932ca..bb598ab180 100644 --- a/frappe/desk/page/setup_wizard/install_fixtures.py +++ b/frappe/desk/page/setup_wizard/install_fixtures.py @@ -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(): diff --git a/frappe/hooks.py b/frappe/hooks.py index d2e8570644..21d753aca4 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -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"} +] \ No newline at end of file diff --git a/frappe/patches/v12_0/update_global_search.py b/frappe/patches/v12_0/update_global_search.py new file mode 100644 index 0000000000..8042a2ee68 --- /dev/null +++ b/frappe/patches/v12_0/update_global_search.py @@ -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() \ No newline at end of file diff --git a/frappe/tests/test_global_search.py b/frappe/tests/test_global_search.py index 940a424966..01067c85dd 100644 --- a/frappe/tests/test_global_search.py +++ b/frappe/tests/test_global_search.py @@ -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" diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py index d30676ad53..b8e0bfa2a7 100644 --- a/frappe/utils/global_search.py +++ b/frappe/utils/global_search.py @@ -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)