From f6d1ce2194b871ef994c8d4bb000f9225c870724 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 24 Sep 2019 01:03:11 +0530 Subject: [PATCH 01/21] feat: Global Tags --- frappe/core/doctype/tag/tag.py | 128 ++++++++++++++- .../core/doctype/tag_category/tag_category.js | 9 -- .../doctype/tag_category/tag_category.json | 147 ------------------ .../doctype/tag_category/test_tag_category.py | 12 -- .../core/doctype/tag_doc_category/__init__.py | 0 .../tag_doc_category/tag_doc_category.json | 58 ------- .../tag_doc_category/tag_doc_category.py | 10 -- frappe/database/mariadb/database.py | 12 ++ frappe/database/postgres/database.py | 9 ++ .../doctype/tag}/__init__.py | 0 frappe/desk/doctype/tag/tag.js | 8 + frappe/desk/doctype/tag/tag.json | 56 +++++++ .../doctype/tag/tag.py} | 6 +- frappe/desk/doctype/tag/test_tag.py | 10 ++ frappe/desk/reportview.py | 7 +- frappe/installer.py | 1 + frappe/model/delete_doc.py | 3 + frappe/public/build.json | 1 + frappe/public/js/frappe/desk.js | 6 + frappe/public/js/frappe/ui/tag_editor.js | 8 +- .../js/frappe/ui/toolbar/awesome_bar.js | 11 ++ .../js/frappe/ui/toolbar/global_tags.js | 136 ++++++++++++++++ frappe/utils/global_search.py | 4 - frappe/utils/global_tags.py | 94 +++++++++++ 24 files changed, 481 insertions(+), 255 deletions(-) delete mode 100644 frappe/core/doctype/tag_category/tag_category.js delete mode 100644 frappe/core/doctype/tag_category/tag_category.json delete mode 100644 frappe/core/doctype/tag_category/test_tag_category.py delete mode 100644 frappe/core/doctype/tag_doc_category/__init__.py delete mode 100644 frappe/core/doctype/tag_doc_category/tag_doc_category.json delete mode 100644 frappe/core/doctype/tag_doc_category/tag_doc_category.py rename frappe/{core/doctype/tag_category => desk/doctype/tag}/__init__.py (100%) create mode 100644 frappe/desk/doctype/tag/tag.js create mode 100644 frappe/desk/doctype/tag/tag.json rename frappe/{core/doctype/tag_category/tag_category.py => desk/doctype/tag/tag.py} (61%) create mode 100644 frappe/desk/doctype/tag/test_tag.py create mode 100644 frappe/public/js/frappe/ui/toolbar/global_tags.js create mode 100644 frappe/utils/global_tags.py diff --git a/frappe/core/doctype/tag/tag.py b/frappe/core/doctype/tag/tag.py index cc8e17e8d2..d45a14aa35 100644 --- a/frappe/core/doctype/tag/tag.py +++ b/frappe/core/doctype/tag/tag.py @@ -1,11 +1,133 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies and contributors +# Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt from __future__ import unicode_literals import frappe +import json from frappe.model.document import Document +from frappe.utils.global_tags import update_global_tags +from frappe import _ class Tag(Document): - def validate(self): - self.tag_name = self.tag_name.title() + + def on_trash(self): + if self.count > 0: + frappe.throw(_("Cannot delete Tag {0} since it is linked to Documents.").format(frappe.bold(self.name))) + +def check_user_tags(dt): + "if the user does not have a tags column, then it creates one" + try: + frappe.db.sql("select `_user_tags` from `tab%s` limit 1" % dt) + except Exception as e: + if frappe.db.is_column_missing(e): + DocTags(dt).setup() + +@frappe.whitelist() +def add_tag(tag, dt, dn, color=None): + "adds a new tag to a record, and creates the Tag master" + DocTags(dt).add(dn, tag) + + return tag + +@frappe.whitelist() +def remove_tag(tag, dt, dn): + "removes tag from the record" + DocTags(dt).remove(dn, tag) + +@frappe.whitelist() +def get_tagged_docs(doctype, tag): + frappe.has_permission(doctype, throw=True) + + return frappe.db.sql("""SELECT name + FROM `tab{0}` + WHERE _user_tags LIKE '%{1}%'""".format(doctype, tag)) + +@frappe.whitelist() +def get_tags(doctype, txt, cat_tags): + tags = json.loads(cat_tags) + tag = frappe.get_list("Tag", filters=[["name", "like", "%{}%".format(txt)]]) + tags.extend([t.name for t in tag]) + + return sorted(filter(lambda t: t and txt.lower() in t.lower(), list(set(tags)))) + +class DocTags: + """Tags for a particular doctype""" + def __init__(self, dt): + self.dt = dt + + def get_tag_fields(self): + """returns tag_fields property""" + return frappe.db.get_value('DocType', self.dt, 'tag_fields') + + def get_tags(self, dn): + """returns tag for a particular item""" + return (frappe.db.get_value(self.dt, dn, '_user_tags', ignore=1) or '').strip() + + def add(self, dn, tag): + """add a new user tag""" + tl = self.get_tags(dn).split(',') + if not tag in tl: + tl.append(tag) + if not frappe.db.exists("Tag", tag): + frappe.get_doc({"doctype": "Tag", "name": tag, "count": 1}).insert(ignore_permissions=True) + else: + update_tag_count(tags=tag) + self.update(dn, tl) + + def remove(self, dn, tag): + """remove a user tag""" + tl = self.get_tags(dn).split(',') + update_tag_count(tags=tag, increment=False) + self.update(dn, filter(lambda x:x.lower()!=tag.lower(), tl)) + + def remove_all(self, dn): + """remove all user tags (call before delete)""" + update_tag_count(tags=tag, increment=False, dt=self.dt, dn=dn) + self.update(dn, []) + + def update(self, dn, tl): + """updates the _user_tag column in the table""" + + if not tl: + tags = '' + else: + tl = list(set(filter(lambda x: x, tl))) + tags = ',' + ','.join(tl) + try: + frappe.db.sql("update `tab%s` set _user_tags=%s where name=%s" % \ + (self.dt,'%s','%s'), (tags , dn)) + doc= frappe.get_doc(self.dt, dn) + update_global_tags(doc, tags) + except Exception as e: + if frappe.db.is_column_missing(e): + if not tags: + # no tags, nothing to do + return + + self.setup() + self.update(dn, tl) + else: raise + + def setup(self): + """adds the _user_tags column if not exists""" + from frappe.database.schema import add_column + add_column(self.dt, "_user_tags", "Data") + +def update_tag_count(tags, increment=True, dt=None, dn=None): + """ + Used to Increase or Decrease the count of documents linked with a certain tag + """ + _user_tags = [tags] + if tags == [] and dt and dn: + _user_tags = frappe.db.get_value(dt, dn, '_user_tags', ignore=1).split(",") + _user_tags = [t.strip() for t in _user_tags if t] + + for tag in _user_tags: + tag_count = frappe.db.get_value("Tag", tag, "count") + if increment: + tag_count+=1 + else: + tag_count-=1 + + frappe.db.set_value("Tag", tag, "count", tag_count) diff --git a/frappe/core/doctype/tag_category/tag_category.js b/frappe/core/doctype/tag_category/tag_category.js deleted file mode 100644 index e01dad063d..0000000000 --- a/frappe/core/doctype/tag_category/tag_category.js +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies and contributors -// For license information, please see license.txt -frappe.ui.form.on('Tag', { - tag_name:function(frm){ - for (var i = 0 ;i { if(me.initialized && !me.refreshing) { return frappe.call({ - method: 'frappe.desk.tags.add_tag', + method: "frappe.desk.doctype.tag.tag.add_tag", args: me.get_args(tag), callback: function(r) { var user_tags = me.user_tags ? me.user_tags.split(",") : []; user_tags.push(tag) me.user_tags = user_tags.join(","); me.on_change && me.on_change(me.user_tags); + frappe.global_tags.utils.set_tags(); } }); } @@ -49,13 +50,14 @@ frappe.ui.TagEditor = Class.extend({ onTagRemove: (tag) => { if(!me.refreshing) { return frappe.call({ - method: 'frappe.desk.tags.remove_tag', + method: "frappe.desk.doctype.tag.tag.remove_tag", args: me.get_args(tag), callback: function(r) { var user_tags = me.user_tags.split(","); user_tags.splice(user_tags.indexOf(tag), 1); me.user_tags = user_tags.join(","); me.on_change && me.on_change(me.user_tags); + frappe.global_tags.utils.set_tags(); } }); } @@ -82,7 +84,7 @@ frappe.ui.TagEditor = Class.extend({ $input.on("input", function(e) { var value = e.target.value; frappe.call({ - method:"frappe.desk.tags.get_tags", + method: "frappe.desk.doctype.tag.tag.get_tags", args:{ doctype: me.frm.doctype, txt: value.toLowerCase(), diff --git a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js index 412117f49e..bb5f7edbe5 100644 --- a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js +++ b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js @@ -1,6 +1,7 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // MIT License. See license.txt frappe.provide('frappe.search'); +frappe.provide('frappe.global_tags'); frappe.search.AwesomeBar = Class.extend({ setup: function(element) { @@ -140,6 +141,8 @@ frappe.search.AwesomeBar = Class.extend({ __("document type..., e.g. customer")+'\ '+__("Search in a document type")+''+ __("text in document type")+'\ + '+__("Tags")+''+ + __("tag name..., e.g. #tag")+'\ '+__("Open a module or tool")+''+ __("module name...")+'\ '+__("Calculate")+''+ @@ -177,6 +180,9 @@ frappe.search.AwesomeBar = Class.extend({ frappe.search.utils.get_recent_pages(txt || ""), frappe.search.utils.get_executables(txt) ); + if (txt.charAt(0) === "#") { + options = frappe.global_tags.utils.get_tags(txt); + } var out = this.deduplicate(options); return out.sort(function(a, b) { return b.index - a.index; @@ -215,6 +221,11 @@ frappe.search.AwesomeBar = Class.extend({ make_global_search: function(txt) { var me = this; + + if (txt.charAt(0) === "#") { + return; + } + this.options.push({ label: __("Search for '{0}'", [txt.bold()]), value: __("Search for '{0}'", [txt]), diff --git a/frappe/public/js/frappe/ui/toolbar/global_tags.js b/frappe/public/js/frappe/ui/toolbar/global_tags.js new file mode 100644 index 0000000000..05bc5d8a0e --- /dev/null +++ b/frappe/public/js/frappe/ui/toolbar/global_tags.js @@ -0,0 +1,136 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +// MIT License. See license.txt + +frappe.provide("frappe.global_tags"); +frappe.provide("locals.global_tags"); + +frappe.global_tags.GlobalTagsDialog = class GlobalTags { + constructor(opts) { + $.extend(this, opts); + this.show(); + } + + show() { + if (!this.dialog) { + this.make_dialog(); + } + + $(this.dialog.body).html( + `
+ ${__("Loading")}... +
`); + + this.dialog.show(); + } + + make_dialog() { + let title = __("Tag {0}", ["#".concat(this.tag)]); + + this.dialog = new frappe.ui.Dialog({ + hide_on_page_refresh: true, + minimizable: true, + title: title + }); + + this.dialog.on_page_show = () => { + this.get_documents_for_tag() + .then(() => this.make_html()); + }; + } + + make_html() { + const results = this.results; + let html = ''; + + const linked_doctypes = Object.keys(results); + + if (linked_doctypes.length === 0) { + html = __("Not Linked to any record"); + } else { + html = linked_doctypes.map(doctype => { + const docs = results[doctype]; + return ` +
+ ${this.make_doc_head(doctype)} + ${docs.map(doc => this.make_doc_row(doc.name, doctype, doc.title)).join('')} +
+ `; + }).join(''); + } + + $(this.dialog.body).html(html); + } + + get_documents_for_tag() { + return new Promise((resolve) => { + frappe.call({ + method: "frappe.utils.global_tags.get_documents_for_tag", + args: { + tag: this.tag + }, + callback: (r) => { + this.results = r.message; + resolve(); + } + }); + }); + } + + make_doc_head(heading) { + return ` +
+
${__(heading)}
+
+ `; + } + + make_doc_row(docname, doctype, title) { + return `
+
+ +
+

${title}

+
+
+
`; + } +}; + +frappe.global_tags.utils = { + get_tags: function(txt) { + txt = txt.slice(1); + let out = []; + + for (let i in locals.global_tags) { + let tag = locals.global_tags[i]; + let level = frappe.search.utils.fuzzy_search(txt, tag); + if (level) { + out.push({ + type: "Tag", + label: __("#{0}", [frappe.search.utils.bolden_match_part(__(tag), txt)]), + value: __("#{0}", [__(tag)]), + index: 1 + level, + match: tag, + onclick: function() { + new frappe.global_tags.GlobalTagsDialog({"tag": tag}); + } + }); + } + } + + return out; + }, + + set_tags: function() { + frappe.call({ + method: "frappe.utils.global_tags.get_tags_list_for_awesomebar", + callback: function(r) { + if (r && r.message) { + locals.global_tags = $.extend([], r.message); + } + } + }); + } +} \ No newline at end of file diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py index d30676ad53..4d267a452b 100644 --- a/frappe/utils/global_search.py +++ b/frappe/utils/global_search.py @@ -242,10 +242,6 @@ def update_global_search(doc): if doc.get(field.fieldname) and field.fieldtype not in frappe.model.table_fields: content.append(get_formatted_value(doc.get(field.fieldname), field)) - tags = (doc.get('_user_tags') or '').strip() - if tags: - content.extend(list(filter(lambda x: x, tags.split(',')))) - # Get children for child in doc.meta.get_table_fields(): for d in doc.get(child.fieldname): diff --git a/frappe/utils/global_tags.py b/frappe/utils/global_tags.py new file mode 100644 index 0000000000..d187824efd --- /dev/null +++ b/frappe/utils/global_tags.py @@ -0,0 +1,94 @@ +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe + +def setup_global_tags_table(): + """ + Creates __global_search table + :return: + """ + frappe.db.create_global_tags_table() + +def reset(): + """ + Deletes all data in __global_tags + :return: + """ + frappe.db.sql('DELETE FROM `__global_tags`') + +def delete_tags_for_document(doc): + """ + Delete the __global_tags entry of a document that has + been deleted + :param doc: Deleted document + """ + frappe.db.sql("""DELETE + FROM `__global_search` + WHERE doctype = %s + AND name = %s""", (doc.doctype, doc.name)) + +def update_global_tags(doc, tags): + """ + Adds tags for documents + :param doc: Document to be added to global tags + """ + if frappe.local.conf.get('disable_global_tags') or not doc.get("_user_tags"): + return + + value = { + "doctype": doc.doctype, + "name": doc.name, + "title": (doc.get_title() or '')[:int(frappe.db.VARCHAR_LEN)], + "tags": tags.lower() + } + + frappe.db.multisql({ + 'mariadb': '''INSERT INTO `__global_tags` + (`doctype`, `name`, `title`, `tags`) + VALUES (%(doctype)s, %(name)s, %(title)s, %(tags)s) + ON DUPLICATE key UPDATE + `tags`=%(tags)s + ''', + 'postgres': '''INSERT INTO `__global_tags` + (`doctype`, `name`, `title`, `tags`) + VALUES (%(doctype)s, %(name)s, %(title)s, %(tags)s) + ON CONFLICT("doctype", "name") DO UPDATE SET + `tags`=%(tags)s + ''' + }, value) + +@frappe.whitelist() +def get_documents_for_tag(tag): + """ + Search for given text in __global_tags + :param tag: tag to be searched + """ + # remove hastag # from tag + results = {} + tag = frappe.db.escape('%{0}%'.format(tag.lower()), False) + + common_query = ''' + SELECT `doctype`, `name`, `title`, `tags` + FROM `__global_tags` + WHERE `tags` LIKE {tag} + ''' + + result = frappe.db.multisql({ + 'mariadb': common_query.format(tag=tag), + 'postgres': common_query.format(tag=tag) + }, as_dict=True) + + for res in result: + if res.doctype in results.keys(): + results[res.doctype].append(res) + else: + results[res.doctype] = [res] + + return results + +@frappe.whitelist() +def get_tags_list_for_awesomebar(): + return [t.name for t in frappe.get_list("Tag")] \ No newline at end of file From a7bac4ab74b753ba60d1a722a05982ad34029af1 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 24 Sep 2019 01:07:03 +0530 Subject: [PATCH 02/21] fix: delete tag from core module --- frappe/core/doctype/tag/__init__.py | 0 frappe/core/doctype/tag/tag.json | 58 ------------ frappe/core/doctype/tag/tag.py | 133 ---------------------------- frappe/desk/doctype/tag/tag.py | 127 +++++++++++++++++++++++++- 4 files changed, 125 insertions(+), 193 deletions(-) delete mode 100644 frappe/core/doctype/tag/__init__.py delete mode 100644 frappe/core/doctype/tag/tag.json delete mode 100644 frappe/core/doctype/tag/tag.py diff --git a/frappe/core/doctype/tag/__init__.py b/frappe/core/doctype/tag/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/core/doctype/tag/tag.json b/frappe/core/doctype/tag/tag.json deleted file mode 100644 index 5b206eb506..0000000000 --- a/frappe/core/doctype/tag/tag.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "creation": "2016-05-25 09:43:44.767581", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "fields": [ - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "tag_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Tags", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2016-05-31 08:29:01.773065", - "modified_by": "Administrator", - "module": "Core", - "name": "Tag", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC" -} \ No newline at end of file diff --git a/frappe/core/doctype/tag/tag.py b/frappe/core/doctype/tag/tag.py deleted file mode 100644 index d45a14aa35..0000000000 --- a/frappe/core/doctype/tag/tag.py +++ /dev/null @@ -1,133 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -import json -from frappe.model.document import Document -from frappe.utils.global_tags import update_global_tags -from frappe import _ - -class Tag(Document): - - def on_trash(self): - if self.count > 0: - frappe.throw(_("Cannot delete Tag {0} since it is linked to Documents.").format(frappe.bold(self.name))) - -def check_user_tags(dt): - "if the user does not have a tags column, then it creates one" - try: - frappe.db.sql("select `_user_tags` from `tab%s` limit 1" % dt) - except Exception as e: - if frappe.db.is_column_missing(e): - DocTags(dt).setup() - -@frappe.whitelist() -def add_tag(tag, dt, dn, color=None): - "adds a new tag to a record, and creates the Tag master" - DocTags(dt).add(dn, tag) - - return tag - -@frappe.whitelist() -def remove_tag(tag, dt, dn): - "removes tag from the record" - DocTags(dt).remove(dn, tag) - -@frappe.whitelist() -def get_tagged_docs(doctype, tag): - frappe.has_permission(doctype, throw=True) - - return frappe.db.sql("""SELECT name - FROM `tab{0}` - WHERE _user_tags LIKE '%{1}%'""".format(doctype, tag)) - -@frappe.whitelist() -def get_tags(doctype, txt, cat_tags): - tags = json.loads(cat_tags) - tag = frappe.get_list("Tag", filters=[["name", "like", "%{}%".format(txt)]]) - tags.extend([t.name for t in tag]) - - return sorted(filter(lambda t: t and txt.lower() in t.lower(), list(set(tags)))) - -class DocTags: - """Tags for a particular doctype""" - def __init__(self, dt): - self.dt = dt - - def get_tag_fields(self): - """returns tag_fields property""" - return frappe.db.get_value('DocType', self.dt, 'tag_fields') - - def get_tags(self, dn): - """returns tag for a particular item""" - return (frappe.db.get_value(self.dt, dn, '_user_tags', ignore=1) or '').strip() - - def add(self, dn, tag): - """add a new user tag""" - tl = self.get_tags(dn).split(',') - if not tag in tl: - tl.append(tag) - if not frappe.db.exists("Tag", tag): - frappe.get_doc({"doctype": "Tag", "name": tag, "count": 1}).insert(ignore_permissions=True) - else: - update_tag_count(tags=tag) - self.update(dn, tl) - - def remove(self, dn, tag): - """remove a user tag""" - tl = self.get_tags(dn).split(',') - update_tag_count(tags=tag, increment=False) - self.update(dn, filter(lambda x:x.lower()!=tag.lower(), tl)) - - def remove_all(self, dn): - """remove all user tags (call before delete)""" - update_tag_count(tags=tag, increment=False, dt=self.dt, dn=dn) - self.update(dn, []) - - def update(self, dn, tl): - """updates the _user_tag column in the table""" - - if not tl: - tags = '' - else: - tl = list(set(filter(lambda x: x, tl))) - tags = ',' + ','.join(tl) - try: - frappe.db.sql("update `tab%s` set _user_tags=%s where name=%s" % \ - (self.dt,'%s','%s'), (tags , dn)) - doc= frappe.get_doc(self.dt, dn) - update_global_tags(doc, tags) - except Exception as e: - if frappe.db.is_column_missing(e): - if not tags: - # no tags, nothing to do - return - - self.setup() - self.update(dn, tl) - else: raise - - def setup(self): - """adds the _user_tags column if not exists""" - from frappe.database.schema import add_column - add_column(self.dt, "_user_tags", "Data") - -def update_tag_count(tags, increment=True, dt=None, dn=None): - """ - Used to Increase or Decrease the count of documents linked with a certain tag - """ - _user_tags = [tags] - if tags == [] and dt and dn: - _user_tags = frappe.db.get_value(dt, dn, '_user_tags', ignore=1).split(",") - _user_tags = [t.strip() for t in _user_tags if t] - - for tag in _user_tags: - tag_count = frappe.db.get_value("Tag", tag, "count") - if increment: - tag_count+=1 - else: - tag_count-=1 - - frappe.db.set_value("Tag", tag, "count", tag_count) diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py index f156799799..d45a14aa35 100644 --- a/frappe/desk/doctype/tag/tag.py +++ b/frappe/desk/doctype/tag/tag.py @@ -3,8 +3,131 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe +import json from frappe.model.document import Document +from frappe.utils.global_tags import update_global_tags +from frappe import _ class Tag(Document): - pass + + def on_trash(self): + if self.count > 0: + frappe.throw(_("Cannot delete Tag {0} since it is linked to Documents.").format(frappe.bold(self.name))) + +def check_user_tags(dt): + "if the user does not have a tags column, then it creates one" + try: + frappe.db.sql("select `_user_tags` from `tab%s` limit 1" % dt) + except Exception as e: + if frappe.db.is_column_missing(e): + DocTags(dt).setup() + +@frappe.whitelist() +def add_tag(tag, dt, dn, color=None): + "adds a new tag to a record, and creates the Tag master" + DocTags(dt).add(dn, tag) + + return tag + +@frappe.whitelist() +def remove_tag(tag, dt, dn): + "removes tag from the record" + DocTags(dt).remove(dn, tag) + +@frappe.whitelist() +def get_tagged_docs(doctype, tag): + frappe.has_permission(doctype, throw=True) + + return frappe.db.sql("""SELECT name + FROM `tab{0}` + WHERE _user_tags LIKE '%{1}%'""".format(doctype, tag)) + +@frappe.whitelist() +def get_tags(doctype, txt, cat_tags): + tags = json.loads(cat_tags) + tag = frappe.get_list("Tag", filters=[["name", "like", "%{}%".format(txt)]]) + tags.extend([t.name for t in tag]) + + return sorted(filter(lambda t: t and txt.lower() in t.lower(), list(set(tags)))) + +class DocTags: + """Tags for a particular doctype""" + def __init__(self, dt): + self.dt = dt + + def get_tag_fields(self): + """returns tag_fields property""" + return frappe.db.get_value('DocType', self.dt, 'tag_fields') + + def get_tags(self, dn): + """returns tag for a particular item""" + return (frappe.db.get_value(self.dt, dn, '_user_tags', ignore=1) or '').strip() + + def add(self, dn, tag): + """add a new user tag""" + tl = self.get_tags(dn).split(',') + if not tag in tl: + tl.append(tag) + if not frappe.db.exists("Tag", tag): + frappe.get_doc({"doctype": "Tag", "name": tag, "count": 1}).insert(ignore_permissions=True) + else: + update_tag_count(tags=tag) + self.update(dn, tl) + + def remove(self, dn, tag): + """remove a user tag""" + tl = self.get_tags(dn).split(',') + update_tag_count(tags=tag, increment=False) + self.update(dn, filter(lambda x:x.lower()!=tag.lower(), tl)) + + def remove_all(self, dn): + """remove all user tags (call before delete)""" + update_tag_count(tags=tag, increment=False, dt=self.dt, dn=dn) + self.update(dn, []) + + def update(self, dn, tl): + """updates the _user_tag column in the table""" + + if not tl: + tags = '' + else: + tl = list(set(filter(lambda x: x, tl))) + tags = ',' + ','.join(tl) + try: + frappe.db.sql("update `tab%s` set _user_tags=%s where name=%s" % \ + (self.dt,'%s','%s'), (tags , dn)) + doc= frappe.get_doc(self.dt, dn) + update_global_tags(doc, tags) + except Exception as e: + if frappe.db.is_column_missing(e): + if not tags: + # no tags, nothing to do + return + + self.setup() + self.update(dn, tl) + else: raise + + def setup(self): + """adds the _user_tags column if not exists""" + from frappe.database.schema import add_column + add_column(self.dt, "_user_tags", "Data") + +def update_tag_count(tags, increment=True, dt=None, dn=None): + """ + Used to Increase or Decrease the count of documents linked with a certain tag + """ + _user_tags = [tags] + if tags == [] and dt and dn: + _user_tags = frappe.db.get_value(dt, dn, '_user_tags', ignore=1).split(",") + _user_tags = [t.strip() for t in _user_tags if t] + + for tag in _user_tags: + tag_count = frappe.db.get_value("Tag", tag, "count") + if increment: + tag_count+=1 + else: + tag_count-=1 + + frappe.db.set_value("Tag", tag, "count", tag_count) From b7196f124caf0fe9c563128eba57c93743634e60 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 24 Sep 2019 23:25:04 +0530 Subject: [PATCH 03/21] feat: use tag link for global tags --- frappe/database/mariadb/database.py | 12 -- frappe/database/postgres/database.py | 9 -- frappe/desk/doctype/tag/tag.py | 27 +--- frappe/desk/doctype/tag_link/__init__.py | 0 frappe/desk/doctype/tag_link/tag_link.js | 8 ++ frappe/desk/doctype/tag_link/tag_link.json | 68 ++++++++++ frappe/desk/doctype/tag_link/tag_link.py | 10 ++ frappe/desk/doctype/tag_link/tag_link_list.js | 6 + frappe/desk/doctype/tag_link/test_tag_link.py | 10 ++ frappe/desk/reportview.py | 11 +- frappe/desk/tags.py | 127 ------------------ frappe/installer.py | 1 - .../js/frappe/ui/toolbar/global_tags.js | 2 +- frappe/utils/global_tags.py | 76 ++++------- 14 files changed, 143 insertions(+), 224 deletions(-) create mode 100644 frappe/desk/doctype/tag_link/__init__.py create mode 100644 frappe/desk/doctype/tag_link/tag_link.js create mode 100644 frappe/desk/doctype/tag_link/tag_link.json create mode 100644 frappe/desk/doctype/tag_link/tag_link.py create mode 100644 frappe/desk/doctype/tag_link/tag_link_list.js create mode 100644 frappe/desk/doctype/tag_link/test_tag_link.py delete mode 100644 frappe/desk/tags.py diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index 86ee3370a9..cd053569f0 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -205,18 +205,6 @@ class MariaDBDatabase(Database): ENGINE=MyISAM CHARACTER SET=utf8mb4'''.format(self.VARCHAR_LEN)) - def create_global_tags_table(self): - if not '__global_tags' in self.get_tables(): - self.sql('''create table __global_tags( - doctype varchar(100), - name varchar({0}), - title varchar({0}), - tags varchar({0}), - unique `doctype_name` (doctype, name)) - COLLATE=utf8mb4_unicode_ci - ENGINE=MyISAM - CHARACTER SET=utf8mb4'''.format(self.VARCHAR_LEN)) - def create_user_settings_table(self): self.sql_ddl("""create table if not exists __UserSettings ( `user` VARCHAR(180) NOT NULL, diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index cd5b998d6d..abacc5ab4c 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -194,15 +194,6 @@ class PostgresDatabase(Database): published int not null default 0, unique (doctype, name))'''.format(self.VARCHAR_LEN)) - def create_global_tags_table(self): - if not '__global_tags' in self.get_tables(): - self.sql('''create table __global_tags( - doctype varchar(100), - name varchar({0}), - title varchar({0}), - tags varchar({0}), - unique (doctype, name))'''.format(self.VARCHAR_LEN)) - def create_user_settings_table(self): self.sql_ddl("""create table if not exists "__UserSettings" ( "user" VARCHAR(180) NOT NULL, diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py index d45a14aa35..1ed242eda4 100644 --- a/frappe/desk/doctype/tag/tag.py +++ b/frappe/desk/doctype/tag/tag.py @@ -12,9 +12,12 @@ from frappe import _ class Tag(Document): def on_trash(self): - if self.count > 0: + if check_if_tag_is_linked(self.name): frappe.throw(_("Cannot delete Tag {0} since it is linked to Documents.").format(frappe.bold(self.name))) +def check_if_tag_is_linked(tag): + return frappe.db.count("Tag Link", {"tags": ["like", "%{0}%".format(tag)]}) + def check_user_tags(dt): "if the user does not have a tags column, then it creates one" try: @@ -71,19 +74,15 @@ class DocTags: tl.append(tag) if not frappe.db.exists("Tag", tag): frappe.get_doc({"doctype": "Tag", "name": tag, "count": 1}).insert(ignore_permissions=True) - else: - update_tag_count(tags=tag) self.update(dn, tl) def remove(self, dn, tag): """remove a user tag""" tl = self.get_tags(dn).split(',') - update_tag_count(tags=tag, increment=False) self.update(dn, filter(lambda x:x.lower()!=tag.lower(), tl)) def remove_all(self, dn): """remove all user tags (call before delete)""" - update_tag_count(tags=tag, increment=False, dt=self.dt, dn=dn) self.update(dn, []) def update(self, dn, tl): @@ -113,21 +112,3 @@ class DocTags: """adds the _user_tags column if not exists""" from frappe.database.schema import add_column add_column(self.dt, "_user_tags", "Data") - -def update_tag_count(tags, increment=True, dt=None, dn=None): - """ - Used to Increase or Decrease the count of documents linked with a certain tag - """ - _user_tags = [tags] - if tags == [] and dt and dn: - _user_tags = frappe.db.get_value(dt, dn, '_user_tags', ignore=1).split(",") - _user_tags = [t.strip() for t in _user_tags if t] - - for tag in _user_tags: - tag_count = frappe.db.get_value("Tag", tag, "count") - if increment: - tag_count+=1 - else: - tag_count-=1 - - frappe.db.set_value("Tag", tag, "count", tag_count) diff --git a/frappe/desk/doctype/tag_link/__init__.py b/frappe/desk/doctype/tag_link/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/desk/doctype/tag_link/tag_link.js b/frappe/desk/doctype/tag_link/tag_link.js new file mode 100644 index 0000000000..d85655bb90 --- /dev/null +++ b/frappe/desk/doctype/tag_link/tag_link.js @@ -0,0 +1,8 @@ +// Copyright (c) 2019, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Tag Link', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/desk/doctype/tag_link/tag_link.json b/frappe/desk/doctype/tag_link/tag_link.json new file mode 100644 index 0000000000..733f305466 --- /dev/null +++ b/frappe/desk/doctype/tag_link/tag_link.json @@ -0,0 +1,68 @@ +{ + "creation": "2019-09-24 13:25:36.435685", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "dt", + "dn", + "tags", + "title" + ], + "fields": [ + { + "fieldname": "dt", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Document Type", + "options": "DocType", + "read_only": 1 + }, + { + "fieldname": "dn", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Document Name", + "options": "dt", + "read_only": 1 + }, + { + "fieldname": "tags", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Tag", + "read_only": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Document Title", + "read_only": 1 + } + ], + "modified": "2019-09-24 22:24:36.927919", + "modified_by": "Administrator", + "module": "Desk", + "name": "Tag Link", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/desk/doctype/tag_link/tag_link.py b/frappe/desk/doctype/tag_link/tag_link.py new file mode 100644 index 0000000000..87c8af7212 --- /dev/null +++ b/frappe/desk/doctype/tag_link/tag_link.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 TagLink(Document): + pass diff --git a/frappe/desk/doctype/tag_link/tag_link_list.js b/frappe/desk/doctype/tag_link/tag_link_list.js new file mode 100644 index 0000000000..dfa45d413e --- /dev/null +++ b/frappe/desk/doctype/tag_link/tag_link_list.js @@ -0,0 +1,6 @@ +frappe.listview_settings["Tag Link"] = { + onload: function() { + frappe.set_route(""); + // frappe.throw (__("Access Denied.")); + }, +} \ No newline at end of file diff --git a/frappe/desk/doctype/tag_link/test_tag_link.py b/frappe/desk/doctype/tag_link/test_tag_link.py new file mode 100644 index 0000000000..1c22ac18bc --- /dev/null +++ b/frappe/desk/doctype/tag_link/test_tag_link.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 TestTagLink(unittest.TestCase): + pass diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 8d5ae8c899..32099f5c01 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -262,7 +262,16 @@ def delete_bulk(doctype, items): @frappe.read_only() def get_sidebar_stats(stats, doctype, filters=[]): - return {"defined_cat": [], "stats": get_stats(stats, doctype, filters)} + if not frappe.cache().hget("tags_count", doctype): + tags = [tag.name for tag in frappe.get_list("Tag")] + _user_tags = [] + for tag in tags: + count = frappe.db.count("Tag Link", filters={"dt": doctype, "tags": ["like", "%{0}%".format(tag)]}) + if count > 0: + _user_tags.append([tag, count]) + frappe.cache().hset("tags_count", doctype, _user_tags) + + return {"defined_cat": [], "stats": {"_user_tags": frappe.cache().hget("tags_count", doctype)}} @frappe.whitelist() @frappe.read_only() diff --git a/frappe/desk/tags.py b/frappe/desk/tags.py deleted file mode 100644 index 0d130f48cf..0000000000 --- a/frappe/desk/tags.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals, print_function -import json -""" -Server side functions for tagging. - -- Tags can be added to any record (doctype, name) in the system. -- Items are filtered by tags -- Top tags are shown in the sidebar (?) -- Tags are also identified by the tag_fields property of the DocType - -Discussion: - -Tags are shown in the docbrowser and ideally where-ever items are searched. -There should also be statistics available for tags (like top tags etc) - - -Design: - -- free tags (user_tags) are stored in __user_tags -- doctype tags are set in tag_fields property of the doctype -- top tags merges the tags from both the lists (only refreshes once an hour (max)) - -""" - -import frappe -from frappe.utils.global_search import update_global_search - -def check_user_tags(dt): - "if the user does not have a tags column, then it creates one" - try: - frappe.db.sql("select `_user_tags` from `tab%s` limit 1" % dt) - except Exception as e: - if frappe.db.is_column_missing(e): - DocTags(dt).setup() - -@frappe.whitelist() -def add_tag(tag, dt, dn, color=None): - "adds a new tag to a record, and creates the Tag master" - DocTags(dt).add(dn, tag) - - return tag - -@frappe.whitelist() -def remove_tag(tag, dt, dn): - "removes tag from the record" - DocTags(dt).remove(dn, tag) - -@frappe.whitelist() -def get_tagged_docs(doctype, tag): - frappe.has_permission(doctype, throw=True) - - return frappe.db.sql("""SELECT name - FROM `tab{0}` - WHERE _user_tags LIKE '%{1}%'""".format(doctype, tag)) - -@frappe.whitelist() -def get_tags(doctype, txt, cat_tags): - tags = json.loads(cat_tags) - try: - for _user_tags in frappe.db.sql_list("""select DISTINCT `_user_tags` - from `tab{0}` - where _user_tags like {1} - limit 50""".format(doctype, frappe.db.escape('%' + txt + '%'))): - tags.extend(_user_tags[1:].split(",")) - except Exception as e: - if not frappe.db.is_column_missing(e): raise - return sorted(filter(lambda t: t and txt.lower() in t.lower(), list(set(tags)))) - -class DocTags: - """Tags for a particular doctype""" - def __init__(self, dt): - self.dt = dt - - def get_tag_fields(self): - """returns tag_fields property""" - return frappe.db.get_value('DocType', self.dt, 'tag_fields') - - def get_tags(self, dn): - """returns tag for a particular item""" - return (frappe.db.get_value(self.dt, dn, '_user_tags', ignore=1) or '').strip() - - def add(self, dn, tag): - """add a new user tag""" - tl = self.get_tags(dn).split(',') - if not tag in tl: - tl.append(tag) - self.update(dn, tl) - - def remove(self, dn, tag): - """remove a user tag""" - tl = self.get_tags(dn).split(',') - self.update(dn, filter(lambda x:x.lower()!=tag.lower(), tl)) - - def remove_all(self, dn): - """remove all user tags (call before delete)""" - self.update(dn, []) - - def update(self, dn, tl): - """updates the _user_tag column in the table""" - - if not tl: - tags = '' - else: - tl = list(set(filter(lambda x: x, tl))) - tags = ',' + ','.join(tl) - try: - frappe.db.sql("update `tab%s` set _user_tags=%s where name=%s" % \ - (self.dt,'%s','%s'), (tags , dn)) - doc= frappe.get_doc(self.dt, dn) - update_global_search(doc) - except Exception as e: - if frappe.db.is_column_missing(e): - if not tags: - # no tags, nothing to do - return - - self.setup() - self.update(dn, tl) - else: raise - - def setup(self): - """adds the _user_tags column if not exists""" - from frappe.database.schema import add_column - add_column(self.dt, "_user_tags", "Data") diff --git a/frappe/installer.py b/frappe/installer.py index 295a4c8af6..764a0b6780 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -39,7 +39,6 @@ def install_db(root_login="root", root_password=None, db_name=None, source_sql=N frappe.db.create_auth_table() frappe.db.create_global_search_table() - frappe.db.create_global_tags_table() frappe.db.create_user_settings_table() frappe.flags.in_install_db = False diff --git a/frappe/public/js/frappe/ui/toolbar/global_tags.js b/frappe/public/js/frappe/ui/toolbar/global_tags.js index 05bc5d8a0e..bf4cb2293b 100644 --- a/frappe/public/js/frappe/ui/toolbar/global_tags.js +++ b/frappe/public/js/frappe/ui/toolbar/global_tags.js @@ -52,7 +52,7 @@ frappe.global_tags.GlobalTagsDialog = class GlobalTags { return `
${this.make_doc_head(doctype)} - ${docs.map(doc => this.make_doc_row(doc.name, doctype, doc.title)).join('')} + ${docs.map(doc => this.make_doc_row(doc.dn, doctype, doc.title)).join('')}
`; }).join(''); diff --git a/frappe/utils/global_tags.py b/frappe/utils/global_tags.py index d187824efd..d13e4e2cc3 100644 --- a/frappe/utils/global_tags.py +++ b/frappe/utils/global_tags.py @@ -2,22 +2,14 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals - import frappe -def setup_global_tags_table(): - """ - Creates __global_search table - :return: - """ - frappe.db.create_global_tags_table() - def reset(): """ - Deletes all data in __global_tags + Deletes all data in `tabTag Link` :return: """ - frappe.db.sql('DELETE FROM `__global_tags`') + frappe.db.sql('DELETE FROM `tabTag Link`') def delete_tags_for_document(doc): """ @@ -26,9 +18,9 @@ def delete_tags_for_document(doc): :param doc: Deleted document """ frappe.db.sql("""DELETE - FROM `__global_search` - WHERE doctype = %s - AND name = %s""", (doc.doctype, doc.name)) + FROM `tabTag Link` + WHERE dt = %s + AND dn = %s""", (doc.doctype, doc.name)) def update_global_tags(doc, tags): """ @@ -38,54 +30,38 @@ def update_global_tags(doc, tags): if frappe.local.conf.get('disable_global_tags') or not doc.get("_user_tags"): return - value = { - "doctype": doc.doctype, - "name": doc.name, - "title": (doc.get_title() or '')[:int(frappe.db.VARCHAR_LEN)], - "tags": tags.lower() - } - - frappe.db.multisql({ - 'mariadb': '''INSERT INTO `__global_tags` - (`doctype`, `name`, `title`, `tags`) - VALUES (%(doctype)s, %(name)s, %(title)s, %(tags)s) - ON DUPLICATE key UPDATE - `tags`=%(tags)s - ''', - 'postgres': '''INSERT INTO `__global_tags` - (`doctype`, `name`, `title`, `tags`) - VALUES (%(doctype)s, %(name)s, %(title)s, %(tags)s) - ON CONFLICT("doctype", "name") DO UPDATE SET - `tags`=%(tags)s - ''' - }, value) + if not frappe.db.exists("Tag Link", {"dt": doc.doctype, "dn": doc.name}): + frappe.get_doc({ + "doctype": "Tag Link", + "dt": doc.doctype, + "dn": doc.name, + "title": doc.get_title() or '', + "tags": tags + }).insert(ignore_permissions=True) + else: + frappe.db.set_value("Tag Link", {"dt": doc.doctype, "dn": doc.name}, "tags", tags) @frappe.whitelist() def get_documents_for_tag(tag): """ - Search for given text in __global_tags - :param tag: tag to be searched + Search for given text in __global_tags + :param tag: tag to be searched """ - # remove hastag # from tag + # remove hastag `#` from tag results = {} tag = frappe.db.escape('%{0}%'.format(tag.lower()), False) - common_query = ''' - SELECT `doctype`, `name`, `title`, `tags` - FROM `__global_tags` - WHERE `tags` LIKE {tag} - ''' - - result = frappe.db.multisql({ - 'mariadb': common_query.format(tag=tag), - 'postgres': common_query.format(tag=tag) - }, as_dict=True) + result = frappe.db.sql(''' + SELECT `dt`, `dn`, `title`, `tags` + FROM `tabTag Link` + WHERE `tags` LIKE {0} + '''.format(tag), as_dict=True) for res in result: - if res.doctype in results.keys(): - results[res.doctype].append(res) + if res.dt in results.keys(): + results[res.dt].append(res) else: - results[res.doctype] = [res] + results[res.dt] = [res] return results From abb477a1c0c2510db3eb7ec2fee4511864f2aa08 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 25 Sep 2019 17:48:36 +0530 Subject: [PATCH 04/21] feat: use search bar for tags too --- frappe/desk/doctype/tag/tag.json | 11 +- .../js/frappe/ui/toolbar/global_tags.js | 228 ++++++++++-------- frappe/public/js/frappe/ui/toolbar/search.js | 35 ++- .../js/frappe/ui/toolbar/search_header.html | 15 +- frappe/utils/global_tags.py | 14 +- 5 files changed, 181 insertions(+), 122 deletions(-) diff --git a/frappe/desk/doctype/tag/tag.json b/frappe/desk/doctype/tag/tag.json index 7522d9471a..895516594e 100644 --- a/frappe/desk/doctype/tag/tag.json +++ b/frappe/desk/doctype/tag/tag.json @@ -4,8 +4,7 @@ "doctype": "DocType", "engine": "InnoDB", "field_order": [ - "description", - "count" + "description" ], "fields": [ { @@ -13,15 +12,9 @@ "fieldtype": "Small Text", "in_list_view": 1, "label": "Description" - }, - { - "fieldname": "count", - "fieldtype": "Int", - "label": "Count", - "read_only": 1 } ], - "modified": "2019-09-24 00:53:13.488147", + "modified": "2019-09-25 17:47:41.712237", "modified_by": "Administrator", "module": "Desk", "name": "Tag", diff --git a/frappe/public/js/frappe/ui/toolbar/global_tags.js b/frappe/public/js/frappe/ui/toolbar/global_tags.js index bf4cb2293b..f8fbb39c8b 100644 --- a/frappe/public/js/frappe/ui/toolbar/global_tags.js +++ b/frappe/public/js/frappe/ui/toolbar/global_tags.js @@ -2,109 +2,14 @@ // MIT License. See license.txt frappe.provide("frappe.global_tags"); -frappe.provide("locals.global_tags"); - -frappe.global_tags.GlobalTagsDialog = class GlobalTags { - constructor(opts) { - $.extend(this, opts); - this.show(); - } - - show() { - if (!this.dialog) { - this.make_dialog(); - } - - $(this.dialog.body).html( - `
- ${__("Loading")}... -
`); - - this.dialog.show(); - } - - make_dialog() { - let title = __("Tag {0}", ["#".concat(this.tag)]); - - this.dialog = new frappe.ui.Dialog({ - hide_on_page_refresh: true, - minimizable: true, - title: title - }); - - this.dialog.on_page_show = () => { - this.get_documents_for_tag() - .then(() => this.make_html()); - }; - } - - make_html() { - const results = this.results; - let html = ''; - - const linked_doctypes = Object.keys(results); - - if (linked_doctypes.length === 0) { - html = __("Not Linked to any record"); - } else { - html = linked_doctypes.map(doctype => { - const docs = results[doctype]; - return ` -
- ${this.make_doc_head(doctype)} - ${docs.map(doc => this.make_doc_row(doc.dn, doctype, doc.title)).join('')} -
- `; - }).join(''); - } - - $(this.dialog.body).html(html); - } - - get_documents_for_tag() { - return new Promise((resolve) => { - frappe.call({ - method: "frappe.utils.global_tags.get_documents_for_tag", - args: { - tag: this.tag - }, - callback: (r) => { - this.results = r.message; - resolve(); - } - }); - }); - } - - make_doc_head(heading) { - return ` -
-
${__(heading)}
-
- `; - } - - make_doc_row(docname, doctype, title) { - return `
-
- -
-

${title}

-
-
-
`; - } -}; frappe.global_tags.utils = { get_tags: function(txt) { txt = txt.slice(1); let out = []; - for (let i in locals.global_tags) { - let tag = locals.global_tags[i]; + for (let i in frappe.global_tags.tags) { + let tag = frappe.global_tags.tags[i]; let level = frappe.search.utils.fuzzy_search(txt, tag); if (level) { out.push({ @@ -113,8 +18,9 @@ frappe.global_tags.utils = { value: __("#{0}", [__(tag)]), index: 1 + level, match: tag, - onclick: function() { - new frappe.global_tags.GlobalTagsDialog({"tag": tag}); + onclick() { + // Use Global Search Dialog for tag search too. + frappe.searchdialog.search.init_search("#".concat(tag), "global_tag") } }); } @@ -123,14 +29,130 @@ frappe.global_tags.utils = { return out; }, - set_tags: function() { + set_tags() { frappe.call({ method: "frappe.utils.global_tags.get_tags_list_for_awesomebar", callback: function(r) { if (r && r.message) { - locals.global_tags = $.extend([], r.message); + frappe.global_tags.tags = $.extend([], r.message); } } }); - } -} \ No newline at end of file + }, + + get_tag_results: function(tag) { + var me = this; + function get_results_sets(data) { + var results_sets = [], result, set; + function get_existing_set(doctype) { + return results_sets.find(function(set) { + return set.title === doctype; + }); + } + + function make_description(content, doc_name) { + var parts = content.split(" ||| "); + var result_max_length = 300; + var field_length = 120; + var fields = []; + var result_current_length = 0; + var field_text = ""; + for(var i = 0; i < parts.length; i++) { + var part = parts[i]; + if(part.toLowerCase().indexOf(tag) !== -1) { + // If the field contains the keyword + if(part.indexOf(' &&& ') !== -1) { + var colon_index = part.indexOf(' &&& '); + var field_value = part.slice(colon_index + 5); + } else { + var colon_index = part.indexOf(' : '); + var field_value = part.slice(colon_index + 3); + } + if(field_value.length > field_length) { + // If field value exceeds field_length, find the keyword in it + // and trim field value by half the field_length at both sides + // ellipsify if necessary + var field_data = ""; + var index = field_value.indexOf(tag); + field_data += index < field_length/2 ? field_value.slice(0, index) + : '...' + field_value.slice(index - field_length/2, index); + field_data += field_value.slice(index, index + field_length/2); + field_data += index + field_length/2 < field_value.length ? "..." : ""; + field_value = field_data; + } + var field_name = part.slice(0, colon_index); + + // Find remaining result_length and add field length to result_current_length + var remaining_length = result_max_length - result_current_length; + result_current_length += field_name.length + field_value.length + 2; + if(result_current_length < result_max_length) { + // We have room, push the entire field + field_text = '' + + me.bolden_match_part(field_name, tag) + ': ' + + me.bolden_match_part(field_value, tag); + if(fields.indexOf(field_text) === -1 && doc_name !== field_value) { + fields.push(field_text); + } + } else { + // Not enough room + if(field_name.length < remaining_length){ + // Ellipsify (trim at word end) and push + remaining_length -= field_name.length; + field_text = '' + + me.bolden_match_part(field_name, tag) + ': '; + field_value = field_value.slice(0, remaining_length); + field_value = field_value.slice(0, field_value.lastIndexOf(' ')) + ' ...'; + field_text += me.bolden_match_part(field_value, tag); + fields.push(field_text); + } else { + // No room for even the field name, skip + fields.push('...'); + } + break; + } + } + } + return fields.join(', '); + } + + data.forEach(function(d) { + // more properties + result = { + label: d.name, + value: d.name, + description: make_description(d.content, d.name), + route: ['Form', d.doctype, d.name], + + }; + set = get_existing_set(d.doctype); + if(set) { + set.results.push(result); + } else { + set = { + title: d.doctype, + results: [result], + fetch_type: "Global" + }; + results_sets.push(set); + } + + }); + return results_sets; + } + return new Promise(function(resolve, reject) { + frappe.call({ + method: "frappe.utils.global_tags.get_documents_for_tag", + args: { + tag: tag + }, + callback: function(r) { + if(r.message) { + resolve(get_results_sets(r.message)); + } else { + resolve([]); + } + } + }); + }); + }, +} diff --git a/frappe/public/js/frappe/ui/toolbar/search.js b/frappe/public/js/frappe/ui/toolbar/search.js index e27c383ec4..ac50a35a60 100644 --- a/frappe/public/js/frappe/ui/toolbar/search.js +++ b/frappe/public/js/frappe/ui/toolbar/search.js @@ -124,6 +124,10 @@ frappe.search.SearchDialog = Class.extend({ // Help results // this.$modal_body.on('click', 'a[data-path]', frappe.help.show_results); this.bind_keyboard_events(); + + // Setup Minimizable functionality + this.search_dialog.minimizable = true; + this.search_dialog.$wrapper.find('.btn-modal-minimize').click(() => this.toggle_minimize()); }, bind_keyboard_events: function() { @@ -308,7 +312,7 @@ frappe.search.SearchDialog = Class.extend({ frappe.route_options = result.route_options; } $result.on('click', (e) => { - this.search_dialog.hide(); + this.toggle_minimize(); if(result.onclick) { result.onclick(result.match); } else { @@ -353,6 +357,19 @@ frappe.search.SearchDialog = Class.extend({ this.$modal_body.find('.more-results.last').slideDown(200, function() {}); }, + get_minimize_btn: function() { + return this.search_dialog.$wrapper.find(".modal-header .btn-modal-minimize"); + }, + + toggle_minimize: function() { + let modal = this.search_dialog.$wrapper.closest('.modal').toggleClass('modal-minimize'); + modal.attr('tabindex') ? modal.removeAttr('tabindex') : modal.attr('tabindex', -1); + this.get_minimize_btn().find('i').toggleClass('octicon-chevron-down').toggleClass('octicon-chevron-up'); + this.search_dialog.is_minimized = !this.search_dialog.is_minimized; + this.on_minimize_toggle && this.on_minimize_toggle(this.search_dialog.is_minimized); + this.search_dialog.header.find('.modal-title').toggleClass('cursor-pointer'); + }, + // Search objects searches: { global_search: { @@ -372,6 +389,22 @@ frappe.search.SearchDialog = Class.extend({ }); } }, + global_tag: { + input_placeholder: __("Global Tags"), + empty_state_text: __("Search for Tags"), + no_results_status: (keyword) => __("

No results found for '" + keyword + "' in Global Tags

"), + + get_results: function(keywords, callback) { + var results = frappe.search.utils.get_nav_results(keywords); + frappe.global_tags.utils.get_tag_results(keywords) + .then(function(global_results) { + results = results.concat(global_results); + callback(results, keywords); + }, function (err) { + console.error(err); + }); + } + }, }, }); \ No newline at end of file diff --git a/frappe/public/js/frappe/ui/toolbar/search_header.html b/frappe/public/js/frappe/ui/toolbar/search_header.html index f36e87c7ea..4b8b70ad97 100644 --- a/frappe/public/js/frappe/ui/toolbar/search_header.html +++ b/frappe/public/js/frappe/ui/toolbar/search_header.html @@ -1,6 +1,13 @@
- - -

{%= __("Searching")%} ...

- + + +

{%= __("Searching")%} ...

+ + + + +
\ No newline at end of file diff --git a/frappe/utils/global_tags.py b/frappe/utils/global_tags.py index d13e4e2cc3..3c061d057a 100644 --- a/frappe/utils/global_tags.py +++ b/frappe/utils/global_tags.py @@ -48,7 +48,10 @@ def get_documents_for_tag(tag): :param tag: tag to be searched """ # remove hastag `#` from tag - results = {} + tag = tag[1:] + + results = [] + tag = frappe.db.escape('%{0}%'.format(tag.lower()), False) result = frappe.db.sql(''' @@ -58,10 +61,11 @@ def get_documents_for_tag(tag): '''.format(tag), as_dict=True) for res in result: - if res.dt in results.keys(): - results[res.dt].append(res) - else: - results[res.dt] = [res] + results.append({ + "doctype": res.dt, + "name": res.dn, + "content": res.title + }) return results From a952a5b5c31f5de56fd3165a8a5ee5acc5a13f3e Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 25 Sep 2019 22:15:40 +0530 Subject: [PATCH 05/21] feat: unique entry for each tag --- frappe/desk/doctype/tag/tag.py | 7 +-- frappe/desk/doctype/tag_link/tag_link.json | 17 ++--- frappe/desk/doctype/tag_link/tag_link_list.js | 6 -- frappe/desk/reportview.py | 2 +- frappe/public/js/frappe/list/list_sidebar.js | 24 +------ frappe/public/js/frappe/ui/tag_editor.js | 2 - .../js/frappe/ui/toolbar/global_tags.js | 30 ++++----- frappe/public/js/frappe/ui/toolbar/search.js | 3 + frappe/utils/global_tags.py | 62 ++++++++++--------- 9 files changed, 66 insertions(+), 87 deletions(-) delete mode 100644 frappe/desk/doctype/tag_link/tag_link_list.js diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py index 1ed242eda4..f03bf153f3 100644 --- a/frappe/desk/doctype/tag/tag.py +++ b/frappe/desk/doctype/tag/tag.py @@ -16,7 +16,7 @@ class Tag(Document): frappe.throw(_("Cannot delete Tag {0} since it is linked to Documents.").format(frappe.bold(self.name))) def check_if_tag_is_linked(tag): - return frappe.db.count("Tag Link", {"tags": ["like", "%{0}%".format(tag)]}) + return frappe.db.count("Tag Link", {"tag": frappe.db.escape('%{0}%'.format(tag), False)}) def check_user_tags(dt): "if the user does not have a tags column, then it creates one" @@ -47,10 +47,9 @@ def get_tagged_docs(doctype, tag): WHERE _user_tags LIKE '%{1}%'""".format(doctype, tag)) @frappe.whitelist() -def get_tags(doctype, txt, cat_tags): - tags = json.loads(cat_tags) +def get_tags(doctype, txt): tag = frappe.get_list("Tag", filters=[["name", "like", "%{}%".format(txt)]]) - tags.extend([t.name for t in tag]) + tags = [t.name for t in tag] return sorted(filter(lambda t: t and txt.lower() in t.lower(), list(set(tags)))) diff --git a/frappe/desk/doctype/tag_link/tag_link.json b/frappe/desk/doctype/tag_link/tag_link.json index 733f305466..ce389415e5 100644 --- a/frappe/desk/doctype/tag_link/tag_link.json +++ b/frappe/desk/doctype/tag_link/tag_link.json @@ -6,7 +6,7 @@ "field_order": [ "dt", "dn", - "tags", + "tag", "title" ], "fields": [ @@ -29,21 +29,21 @@ "read_only": 1 }, { - "fieldname": "tags", + "fieldname": "title", "fieldtype": "Data", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Tag", + "label": "Document Title", "read_only": 1 }, { - "fieldname": "title", + "fieldname": "tag", "fieldtype": "Data", - "label": "Document Title", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Document Tag", "read_only": 1 } ], - "modified": "2019-09-24 22:24:36.927919", + "modified": "2019-09-25 22:10:47.671304", "modified_by": "Administrator", "module": "Desk", "name": "Tag Link", @@ -62,6 +62,7 @@ "write": 1 } ], + "read_only": 1, "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 diff --git a/frappe/desk/doctype/tag_link/tag_link_list.js b/frappe/desk/doctype/tag_link/tag_link_list.js deleted file mode 100644 index dfa45d413e..0000000000 --- a/frappe/desk/doctype/tag_link/tag_link_list.js +++ /dev/null @@ -1,6 +0,0 @@ -frappe.listview_settings["Tag Link"] = { - onload: function() { - frappe.set_route(""); - // frappe.throw (__("Access Denied.")); - }, -} \ No newline at end of file diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 32099f5c01..3273bd65d7 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -266,7 +266,7 @@ def get_sidebar_stats(stats, doctype, filters=[]): tags = [tag.name for tag in frappe.get_list("Tag")] _user_tags = [] for tag in tags: - count = frappe.db.count("Tag Link", filters={"dt": doctype, "tags": ["like", "%{0}%".format(tag)]}) + count = frappe.db.count("Tag Link", filters={"dt": doctype, "tag": tag}) if count > 0: _user_tags.append([tag, count]) frappe.cache().hset("tags_count", doctype, _user_tags) diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js index a43d92591d..d841125f87 100644 --- a/frappe/public/js/frappe/list/list_sidebar.js +++ b/frappe/public/js/frappe/list/list_sidebar.js @@ -288,29 +288,7 @@ frappe.views.ListSidebar = class ListSidebar { filters: me.default_filters || [] }, callback: function(r) { - me.defined_category = r.message; - if (r.message.defined_cat) { - me.defined_category = r.message.defined_cat; - me.cats = {}; - //structure the tag categories - for (var i in me.defined_category) { - if (me.cats[me.defined_category[i].category] === undefined) { - me.cats[me.defined_category[i].category] = [me.defined_category[i].tag]; - } else { - me.cats[me.defined_category[i].category].push(me.defined_category[i].tag); - } - me.cat_tags[i] = me.defined_category[i].tag; - } - me.tempstats = r.message.stats; - - $.each(me.cats, function(i, v) { - me.render_stat(i, (me.tempstats || {})["_user_tags"], v); - }); - me.render_stat("_user_tags", (me.tempstats || {})["_user_tags"]); - } else { - //render normal stats - me.render_stat("_user_tags", (r.message.stats || {})["_user_tags"]); - } + me.render_stat("_user_tags", (r.message.stats || {})["_user_tags"]); let stats_dropdown = me.sidebar.find('.list-stats-dropdown'); me.setup_dropdown_search(stats_dropdown,'.stat-label'); } diff --git a/frappe/public/js/frappe/ui/tag_editor.js b/frappe/public/js/frappe/ui/tag_editor.js index a7b1d6a0be..00102cf808 100644 --- a/frappe/public/js/frappe/ui/tag_editor.js +++ b/frappe/public/js/frappe/ui/tag_editor.js @@ -88,8 +88,6 @@ frappe.ui.TagEditor = Class.extend({ args:{ doctype: me.frm.doctype, txt: value.toLowerCase(), - cat_tags: me.list_sidebar ? - JSON.stringify(me.list_sidebar.get_cat_tags()) : '[]' }, callback: function(r) { me.awesomplete.list = r.message; diff --git a/frappe/public/js/frappe/ui/toolbar/global_tags.js b/frappe/public/js/frappe/ui/toolbar/global_tags.js index f8fbb39c8b..3abab35124 100644 --- a/frappe/public/js/frappe/ui/toolbar/global_tags.js +++ b/frappe/public/js/frappe/ui/toolbar/global_tags.js @@ -20,7 +20,7 @@ frappe.global_tags.utils = { match: tag, onclick() { // Use Global Search Dialog for tag search too. - frappe.searchdialog.search.init_search("#".concat(tag), "global_tag") + frappe.searchdialog.search.init_search("#".concat(tag), "global_tag"); } }); } @@ -57,18 +57,20 @@ frappe.global_tags.utils = { var fields = []; var result_current_length = 0; var field_text = ""; - for(var i = 0; i < parts.length; i++) { + var colon_index = null; + var field_value = null; + for (var i = 0; i < parts.length; i++) { var part = parts[i]; if(part.toLowerCase().indexOf(tag) !== -1) { // If the field contains the keyword - if(part.indexOf(' &&& ') !== -1) { - var colon_index = part.indexOf(' &&& '); - var field_value = part.slice(colon_index + 5); + if (part.indexOf(' &&& ') !== -1) { + colon_index = part.indexOf(' &&& '); + field_value = part.slice(colon_index + 5); } else { - var colon_index = part.indexOf(' : '); - var field_value = part.slice(colon_index + 3); + colon_index = part.indexOf(' : '); + field_value = part.slice(colon_index + 3); } - if(field_value.length > field_length) { + if (field_value.length > field_length) { // If field value exceeds field_length, find the keyword in it // and trim field value by half the field_length at both sides // ellipsify if necessary @@ -85,17 +87,17 @@ frappe.global_tags.utils = { // Find remaining result_length and add field length to result_current_length var remaining_length = result_max_length - result_current_length; result_current_length += field_name.length + field_value.length + 2; - if(result_current_length < result_max_length) { + if (result_current_length < result_max_length) { // We have room, push the entire field field_text = '' + me.bolden_match_part(field_name, tag) + ': ' + me.bolden_match_part(field_value, tag); - if(fields.indexOf(field_text) === -1 && doc_name !== field_value) { + if (fields.indexOf(field_text) === -1 && doc_name !== field_value) { fields.push(field_text); } } else { // Not enough room - if(field_name.length < remaining_length){ + if (field_name.length < remaining_length){ // Ellipsify (trim at word end) and push remaining_length -= field_name.length; field_text = '' + @@ -125,7 +127,7 @@ frappe.global_tags.utils = { }; set = get_existing_set(d.doctype); - if(set) { + if (set) { set.results.push(result); } else { set = { @@ -146,7 +148,7 @@ frappe.global_tags.utils = { tag: tag }, callback: function(r) { - if(r.message) { + if (r.message) { resolve(get_results_sets(r.message)); } else { resolve([]); @@ -155,4 +157,4 @@ frappe.global_tags.utils = { }); }); }, -} +}; diff --git a/frappe/public/js/frappe/ui/toolbar/search.js b/frappe/public/js/frappe/ui/toolbar/search.js index ac50a35a60..4793c12108 100644 --- a/frappe/public/js/frappe/ui/toolbar/search.js +++ b/frappe/public/js/frappe/ui/toolbar/search.js @@ -183,6 +183,9 @@ frappe.search.SearchDialog = Class.extend({ this.$search_modal.find('.loading-state').removeClass('hide'); } this.search.get_results(keywords, this.parse_results.bind(this)); + if (this.search_dialog.is_minimized) { + this.toggle_minimize(); + } }, parse_results: function(result_sets, keyword) { diff --git a/frappe/utils/global_tags.py b/frappe/utils/global_tags.py index 3c061d057a..fd970616ab 100644 --- a/frappe/utils/global_tags.py +++ b/frappe/utils/global_tags.py @@ -4,42 +4,49 @@ from __future__ import unicode_literals import frappe -def reset(): - """ - Deletes all data in `tabTag Link` - :return: - """ - frappe.db.sql('DELETE FROM `tabTag Link`') - def delete_tags_for_document(doc): """ Delete the __global_tags entry of a document that has been deleted :param doc: Deleted document """ - frappe.db.sql("""DELETE - FROM `tabTag Link` - WHERE dt = %s - AND dn = %s""", (doc.doctype, doc.name)) + frappe.db.sql("""DELETE FROM `tabTag Link` WHERE `dt`=%s AND `dn`=%s""", (doc.doctype, doc.name)) def update_global_tags(doc, tags): """ Adds tags for documents :param doc: Document to be added to global tags """ - if frappe.local.conf.get('disable_global_tags') or not doc.get("_user_tags"): + if frappe.local.conf.get('disable_global_tags'): return - if not frappe.db.exists("Tag Link", {"dt": doc.doctype, "dn": doc.name}): - frappe.get_doc({ - "doctype": "Tag Link", - "dt": doc.doctype, - "dn": doc.name, - "title": doc.get_title() or '', - "tags": tags - }).insert(ignore_permissions=True) - else: - frappe.db.set_value("Tag Link", {"dt": doc.doctype, "dn": doc.name}, "tags", tags) + new_tags = list(set([tag.strip() for tag in tags.split(",") if tag])) + + + for tag in new_tags: + if not frappe.db.exists("Tag Link", {"dt": doc.doctype, "dn": doc.name, "tag": tag}): + frappe.get_doc({ + "doctype": "Tag Link", + "dt": doc.doctype, + "dn": doc.name, + "title": doc.get_title() or '', + "tag": tag + }).insert(ignore_permissions=True) + + existing_tags = [tag.tag for tag in frappe.get_list("Tag Link", filters={"dt": doc.doctype, "dn": doc.name}, fields=["tag"])] + + deleted_tags = get_deleted_tags(new_tags, existing_tags) + + if deleted_tags: + for tag in deleted_tags: + delete_tag_for_document(doc.doctype, doc.name, tag) + +def get_deleted_tags(new_tags, existing_tags): + print(list(set(existing_tags) - set(new_tags))) + return list(set(existing_tags) - set(new_tags)) + +def delete_tag_for_document(dt, dn, tag): + frappe.db.sql("""DELETE FROM `tabTag Link` WHERE dt=%s, dn=%s, tag=%s""", (dt, dn, tag)) @frappe.whitelist() def get_documents_for_tag(tag): @@ -49,16 +56,13 @@ def get_documents_for_tag(tag): """ # remove hastag `#` from tag tag = tag[1:] - results = [] - tag = frappe.db.escape('%{0}%'.format(tag.lower()), False) - - result = frappe.db.sql(''' - SELECT `dt`, `dn`, `title`, `tags` + result = frappe.db.sql(""" + SELECT `dt`, `dn`, `title`, `tag` FROM `tabTag Link` - WHERE `tags` LIKE {0} - '''.format(tag), as_dict=True) + WHERE `tag`=%s + """, (tag), as_dict=True) for res in result: results.append({ From 860d4b761cab77d2573d63e2ee1760b7831f599a Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 27 Sep 2019 11:30:58 +0530 Subject: [PATCH 06/21] fix: load tags info in docinfo --- frappe/desk/form/load.py | 7 ++++++- frappe/public/js/frappe/form/sidebar/form_sidebar.js | 4 ++-- frappe/public/js/frappe/ui/toolbar/search.js | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index 8c7082401a..c487335b3a 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -100,7 +100,8 @@ def get_docinfo(doc=None, doctype=None, name=None): "views": get_view_logs(doc.doctype, doc.name), "energy_point_logs": get_point_logs(doc.doctype, doc.name), "milestones": get_milestones(doc.doctype, doc.name), - "is_document_followed": is_document_followed(doc.doctype, doc.name, frappe.session.user) + "is_document_followed": is_document_followed(doc.doctype, doc.name, frappe.session.user), + "tags": get_tags(doc.doctype, doc.name) } def get_milestones(doctype, name): @@ -255,3 +256,7 @@ def get_view_logs(doctype, docname): if view_logs: logs = view_logs return logs + +def get_tags(doctype, name): + tags = [tag.tag for tag in frappe.get_list("Tag Link", filters={"dt": doctype, "dn": name}, fields=["tag"])] + return ",".join([tag for tag in tags]) \ No newline at end of file diff --git a/frappe/public/js/frappe/form/sidebar/form_sidebar.js b/frappe/public/js/frappe/form/sidebar/form_sidebar.js index 5798d3361e..5baef8c58a 100644 --- a/frappe/public/js/frappe/form/sidebar/form_sidebar.js +++ b/frappe/public/js/frappe/form/sidebar/form_sidebar.js @@ -76,7 +76,7 @@ frappe.ui.form.Sidebar = Class.extend({ this.frm.shared.refresh(); this.frm.follow.refresh(); this.frm.viewers.refresh(); - this.frm.tags && this.frm.tags.refresh(this.frm.doc._user_tags); + this.frm.tags && this.frm.tags.refresh(this.frm.get_docinfo().tags); this.sidebar.find(".modified-by").html(__("{0} edited this {1}", ["" + frappe.user.full_name(this.frm.doc.modified_by) + "", "
" + comment_when(this.frm.doc.modified)])); @@ -129,7 +129,7 @@ frappe.ui.form.Sidebar = Class.extend({ parent: this.sidebar.find(".tag-area"), frm: this.frm, on_change: function(user_tags) { - me.frm.doc._user_tags = user_tags; + this.frm.tags && this.frm.tags.refresh(user_tags); } }); }, diff --git a/frappe/public/js/frappe/ui/toolbar/search.js b/frappe/public/js/frappe/ui/toolbar/search.js index 4793c12108..14d7adc70f 100644 --- a/frappe/public/js/frappe/ui/toolbar/search.js +++ b/frappe/public/js/frappe/ui/toolbar/search.js @@ -127,6 +127,7 @@ frappe.search.SearchDialog = Class.extend({ // Setup Minimizable functionality this.search_dialog.minimizable = true; + this.search_dialog.is_minimized = false; this.search_dialog.$wrapper.find('.btn-modal-minimize').click(() => this.toggle_minimize()); }, From ac418c8392f76c4bea29a8b63842b5f1c88f5f1b Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 27 Sep 2019 12:18:05 +0530 Subject: [PATCH 07/21] feat: switch between global search and tags --- frappe/public/js/frappe/ui/toolbar/search.js | 13 ++++++++++--- .../public/js/frappe/ui/toolbar/search_header.html | 1 - 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/ui/toolbar/search.js b/frappe/public/js/frappe/ui/toolbar/search.js index 14d7adc70f..efd3be2868 100644 --- a/frappe/public/js/frappe/ui/toolbar/search.js +++ b/frappe/public/js/frappe/ui/toolbar/search.js @@ -183,6 +183,13 @@ frappe.search.SearchDialog = Class.extend({ } else { this.$search_modal.find('.loading-state').removeClass('hide'); } + + if (this.current_keyword.charAt(0) === "#") { + this.search = this.searches["global_tag"]; + } else { + this.search = this.searches["global_search"]; + } + this.search.get_results(keywords, this.parse_results.bind(this)); if (this.search_dialog.is_minimized) { this.toggle_minimize(); @@ -377,7 +384,7 @@ frappe.search.SearchDialog = Class.extend({ // Search objects searches: { global_search: { - input_placeholder: __("Global Search"), + input_placeholder: __("Search"), empty_state_text: __("Search for anything"), no_results_status: (keyword) => __("

No results found for '" + keyword + "' in Global Search

"), @@ -394,8 +401,8 @@ frappe.search.SearchDialog = Class.extend({ } }, global_tag: { - input_placeholder: __("Global Tags"), - empty_state_text: __("Search for Tags"), + input_placeholder: __("Search"), + empty_state_text: __("Search for anything"), no_results_status: (keyword) => __("

No results found for '" + keyword + "' in Global Tags

"), get_results: function(keywords, callback) { diff --git a/frappe/public/js/frappe/ui/toolbar/search_header.html b/frappe/public/js/frappe/ui/toolbar/search_header.html index 4b8b70ad97..86c3fa1783 100644 --- a/frappe/public/js/frappe/ui/toolbar/search_header.html +++ b/frappe/public/js/frappe/ui/toolbar/search_header.html @@ -2,7 +2,6 @@

{%= __("Searching")%} ...

- From 09b639a52763902ea6bda7ab6ea4950abd99854a Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 27 Sep 2019 22:02:22 +0530 Subject: [PATCH 08/21] fix: codacy and travis fixes --- frappe/public/js/frappe/ui/toolbar/global_tags.js | 6 +++--- frappe/utils/global_tags.py | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/ui/toolbar/global_tags.js b/frappe/public/js/frappe/ui/toolbar/global_tags.js index 3abab35124..ce565b9e02 100644 --- a/frappe/public/js/frappe/ui/toolbar/global_tags.js +++ b/frappe/public/js/frappe/ui/toolbar/global_tags.js @@ -61,7 +61,7 @@ frappe.global_tags.utils = { var field_value = null; for (var i = 0; i < parts.length; i++) { var part = parts[i]; - if(part.toLowerCase().indexOf(tag) !== -1) { + if (part.toLowerCase().indexOf(tag) !== -1) { // If the field contains the keyword if (part.indexOf(' &&& ') !== -1) { colon_index = part.indexOf(' &&& '); @@ -97,7 +97,7 @@ frappe.global_tags.utils = { } } else { // Not enough room - if (field_name.length < remaining_length){ + if (field_name.length < remaining_length) { // Ellipsify (trim at word end) and push remaining_length -= field_name.length; field_text = '' + @@ -141,7 +141,7 @@ frappe.global_tags.utils = { }); return results_sets; } - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { frappe.call({ method: "frappe.utils.global_tags.get_documents_for_tag", args: { diff --git a/frappe/utils/global_tags.py b/frappe/utils/global_tags.py index fd970616ab..11f745d9ce 100644 --- a/frappe/utils/global_tags.py +++ b/frappe/utils/global_tags.py @@ -10,6 +10,9 @@ def delete_tags_for_document(doc): been deleted :param doc: Deleted document """ + if not frappe.db.table_exists("Tag Link"): + return + frappe.db.sql("""DELETE FROM `tabTag Link` WHERE `dt`=%s AND `dn`=%s""", (doc.doctype, doc.name)) def update_global_tags(doc, tags): @@ -17,8 +20,6 @@ def update_global_tags(doc, tags): Adds tags for documents :param doc: Document to be added to global tags """ - if frappe.local.conf.get('disable_global_tags'): - return new_tags = list(set([tag.strip() for tag in tags.split(",") if tag])) @@ -42,7 +43,7 @@ def update_global_tags(doc, tags): delete_tag_for_document(doc.doctype, doc.name, tag) def get_deleted_tags(new_tags, existing_tags): - print(list(set(existing_tags) - set(new_tags))) + return list(set(existing_tags) - set(new_tags)) def delete_tag_for_document(dt, dn, tag): From 7b9c90885f711f87796a1342aa866d4e4a5f03a2 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 27 Sep 2019 22:23:38 +0530 Subject: [PATCH 09/21] fix: ignore permission for loading tags --- frappe/desk/form/load.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index c487335b3a..fc1f7e1f4f 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -258,5 +258,5 @@ def get_view_logs(doctype, docname): return logs def get_tags(doctype, name): - tags = [tag.tag for tag in frappe.get_list("Tag Link", filters={"dt": doctype, "dn": name}, fields=["tag"])] + tags = [tag.tag for tag in frappe.get_all("Tag Link", filters={"dt": doctype, "dn": name}, fields=["tag"])] return ",".join([tag for tag in tags]) \ No newline at end of file From cd16373b53ca43d2bc36904eb88f6ab57d63eea7 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 30 Sep 2019 18:12:37 +0530 Subject: [PATCH 10/21] patch: move to new tag structure --- frappe/patches.txt | 1 + frappe/patches/v12_0/global_tags.py | 46 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 frappe/patches/v12_0/global_tags.py diff --git a/frappe/patches.txt b/frappe/patches.txt index d86a8109c1..fd729dfeb9 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -252,3 +252,4 @@ frappe.patches.v12_0.move_email_and_phone_to_child_table frappe.patches.v12_0.delete_duplicate_indexes frappe.patches.v12_0.set_default_incoming_email_port frappe.patches.v12_0.update_global_search +frappe.patches.v12_0.global_tags diff --git a/frappe/patches/v12_0/global_tags.py b/frappe/patches/v12_0/global_tags.py new file mode 100644 index 0000000000..b3bd15745c --- /dev/null +++ b/frappe/patches/v12_0/global_tags.py @@ -0,0 +1,46 @@ +import frappe + +def execute(): + frappe.delete_doc_if_exists("DocType", "Tag Category") + frappe.delete_doc_if_exists("DocType", "Tag Doc Category") + + frappe.reload_doc("desk", "doctype", "tag") + + tag_list = [] + tag_links = [] + time = frappe.utils.get_datetime() + + for doctype in frappe.get_list("DocType", filters={"istable": 0, "issingle": 0}): + for dt_tags in frappe.db.sql("select `name`, `_user_tags` from `tab{0}`".format(doctype.name), as_dict=True): + tags = dt_tags.get("_user_tags").split(",") if dt_tags.get("_user_tags") else None + if not tags: + continue + + for tag in tags: + if not tag: + continue + + tag_list.append(tag.strip()) + + tag_link_name = frappe.generate_hash(dt_tags.name + tag.strip(), 10), + tag_links.append((tag_link_name, doctype.name, dt_tags.name, tag.strip(), time, time, 'Administrator')) + + + temp_list = [] + for count, value in enumerate(set(tag_list)): + temp_list.append((value, time, time, 'Administrator')) + + if count and (count%1000 == 0 or count == len(tag_list)-1): + frappe.db.sql(""" + INSERT INTO `tabTag` (`name`, `creation`, `modified`, `modified_by`) VALUES {} + """.format(", ".join(['%s'] * len(temp_list))), tuple(temp_list)) + temp_list = [] + + for count, value in enumerate(set(tag_links)): + temp_list.append(value) + + if count and (count%1000 == 0 or count == len(tag_links)-1): + frappe.db.sql(""" + INSERT INTO `tabTag Link` (`name`, `dt`, `dn`, `tag`, `creation`, `modified`, `modified_by`) VALUES {} + """.format(", ".join(['%s'] * len(temp_list))), tuple(temp_list)) + temp_list = [] \ No newline at end of file From 30595889bde957f3888c27377ad358b340008b21 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 30 Sep 2019 18:39:56 +0530 Subject: [PATCH 11/21] fix: rename patch file --- frappe/patches/v12_0/{global_tags.py => setup_global_tags.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename frappe/patches/v12_0/{global_tags.py => setup_global_tags.py} (100%) diff --git a/frappe/patches/v12_0/global_tags.py b/frappe/patches/v12_0/setup_global_tags.py similarity index 100% rename from frappe/patches/v12_0/global_tags.py rename to frappe/patches/v12_0/setup_global_tags.py From a5a48f2054f7649dea1b799da9b304c17a0a2967 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 2 Oct 2019 11:39:25 +0530 Subject: [PATCH 12/21] fix: left join Tag Link --- frappe/public/js/frappe/list/list_sidebar.js | 7 +- frappe/public/js/frappe/ui/filters/filter.js | 6 ++ .../js/frappe/ui/toolbar/global_tags.js | 73 +++---------------- frappe/utils/global_tags.py | 5 +- 4 files changed, 22 insertions(+), 69 deletions(-) diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js index d841125f87..0000a3dc73 100644 --- a/frappe/public/js/frappe/list/list_sidebar.js +++ b/frappe/public/js/frappe/list/list_sidebar.js @@ -333,13 +333,14 @@ frappe.views.ListSidebar = class ListSidebar { field: field, stat: stats, sum: sum, - label: field === '_user_tags' ? (tags ? __(label) : __("Tags")) : __(label), + label: field === '_user_tags' ? (tags ? __(label) : __("Tag")) : __(label), }; $(frappe.render_template("list_sidebar_stat", context)) .on("click", ".stat-link", function() { + var doctype = "Tag Link"; var fieldname = $(this).attr('data-field'); var label = $(this).attr('data-label'); - var condition = "like"; + var condition = "="; var existing = me.list_view.filter_area.filter_list.get_filter(fieldname); if(existing) { existing.remove(); @@ -348,7 +349,7 @@ frappe.views.ListSidebar = class ListSidebar { label = "%,%"; condition = "not like"; } - me.list_view.filter_area.filter_list.add_filter(me.list_view.doctype, fieldname, condition, label) + me.list_view.filter_area.filter_list.add_filter(doctype, fieldname, condition, label) .then(function() { me.list_view.refresh(); }); diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index fdf5266216..128ea4e872 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -61,6 +61,7 @@ frappe.ui.Filter = class { doctype: this.parent_doctype, filter_fields: this.filter_fields, select: (doctype, fieldname) => { + console.log(doctype, fieldname); this.set_field(doctype, fieldname); } }); @@ -173,6 +174,11 @@ frappe.ui.Filter = class { if(this.field) for(let k in this.field.df) cur[k] = this.field.df[k]; let original_docfield = (this.fieldselect.fields_by_name[doctype] || {})[fieldname]; + + if (doctype === "Tag Link") { + original_docfield = {fieldname: "tag", fieldtype: "Data", label: "Tags", parent: doctype}; + } + if(!original_docfield) { console.warn(`Field ${fieldname} is not selectable.`); this.remove(); diff --git a/frappe/public/js/frappe/ui/toolbar/global_tags.js b/frappe/public/js/frappe/ui/toolbar/global_tags.js index ce565b9e02..1c9f9a88f8 100644 --- a/frappe/public/js/frappe/ui/toolbar/global_tags.js +++ b/frappe/public/js/frappe/ui/toolbar/global_tags.js @@ -50,71 +50,16 @@ frappe.global_tags.utils = { }); } - function make_description(content, doc_name) { - var parts = content.split(" ||| "); - var result_max_length = 300; - var field_length = 120; - var fields = []; - var result_current_length = 0; - var field_text = ""; - var colon_index = null; + function make_description(content) { + var field_length = 110; var field_value = null; - for (var i = 0; i < parts.length; i++) { - var part = parts[i]; - if (part.toLowerCase().indexOf(tag) !== -1) { - // If the field contains the keyword - if (part.indexOf(' &&& ') !== -1) { - colon_index = part.indexOf(' &&& '); - field_value = part.slice(colon_index + 5); - } else { - colon_index = part.indexOf(' : '); - field_value = part.slice(colon_index + 3); - } - if (field_value.length > field_length) { - // If field value exceeds field_length, find the keyword in it - // and trim field value by half the field_length at both sides - // ellipsify if necessary - var field_data = ""; - var index = field_value.indexOf(tag); - field_data += index < field_length/2 ? field_value.slice(0, index) - : '...' + field_value.slice(index - field_length/2, index); - field_data += field_value.slice(index, index + field_length/2); - field_data += index + field_length/2 < field_value.length ? "..." : ""; - field_value = field_data; - } - var field_name = part.slice(0, colon_index); - - // Find remaining result_length and add field length to result_current_length - var remaining_length = result_max_length - result_current_length; - result_current_length += field_name.length + field_value.length + 2; - if (result_current_length < result_max_length) { - // We have room, push the entire field - field_text = '' + - me.bolden_match_part(field_name, tag) + ': ' + - me.bolden_match_part(field_value, tag); - if (fields.indexOf(field_text) === -1 && doc_name !== field_value) { - fields.push(field_text); - } - } else { - // Not enough room - if (field_name.length < remaining_length) { - // Ellipsify (trim at word end) and push - remaining_length -= field_name.length; - field_text = '' + - me.bolden_match_part(field_name, tag) + ': '; - field_value = field_value.slice(0, remaining_length); - field_value = field_value.slice(0, field_value.lastIndexOf(' ')) + ' ...'; - field_text += me.bolden_match_part(field_value, tag); - fields.push(field_text); - } else { - // No room for even the field name, skip - fields.push('...'); - } - break; - } - } + if (content.length > field_length) { + field_value = content.slice(0, field_length) + "..."; + } else { + var length = content.length; + field_value = content.slice(0, length) + "..."; } - return fields.join(', '); + return field_value; } data.forEach(function(d) { @@ -122,7 +67,7 @@ frappe.global_tags.utils = { result = { label: d.name, value: d.name, - description: make_description(d.content, d.name), + description: make_description(d.content), route: ['Form', d.doctype, d.name], }; diff --git a/frappe/utils/global_tags.py b/frappe/utils/global_tags.py index 11f745d9ce..93cf82a140 100644 --- a/frappe/utils/global_tags.py +++ b/frappe/utils/global_tags.py @@ -23,13 +23,14 @@ def update_global_tags(doc, tags): new_tags = list(set([tag.strip() for tag in tags.split(",") if tag])) - for tag in new_tags: - if not frappe.db.exists("Tag Link", {"dt": doc.doctype, "dn": doc.name, "tag": tag}): + if not frappe.db.exists("Tag Link", {"parenttype": doc.doctype, "parent": doc.name, "tag": tag}): frappe.get_doc({ "doctype": "Tag Link", "dt": doc.doctype, "dn": doc.name, + "parenttype": doc.doctype, + "parent": doc.name, "title": doc.get_title() or '', "tag": tag }).insert(ignore_permissions=True) From 02cebdd7d34bf3be21108df183b75c13c8ba60f9 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 3 Oct 2019 16:43:36 +0530 Subject: [PATCH 13/21] fix: use frappe.tags --- frappe/database/database.py | 20 +++++ frappe/desk/doctype/tag/tag.py | 86 +++++++++++++++++-- frappe/desk/doctype/tag_link/tag_link.json | 39 +++++---- frappe/desk/form/load.py | 6 +- frappe/desk/reportview.py | 4 +- frappe/model/delete_doc.py | 4 +- frappe/patches.txt | 2 +- frappe/patches/v12_0/setup_global_tags.py | 24 +----- frappe/public/js/frappe/desk.js | 2 +- frappe/public/js/frappe/ui/filters/filter.js | 1 - frappe/public/js/frappe/ui/tag_editor.js | 4 +- .../js/frappe/ui/toolbar/awesome_bar.js | 4 +- .../js/frappe/ui/toolbar/global_tags.js | 18 ++-- frappe/public/js/frappe/ui/toolbar/search.js | 6 +- frappe/utils/global_tags.py | 80 ----------------- 15 files changed, 150 insertions(+), 150 deletions(-) delete mode 100644 frappe/utils/global_tags.py diff --git a/frappe/database/database.py b/frappe/database/database.py index a1b8d390a9..2ff8094035 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -961,6 +961,26 @@ class Database(object): frappe.flags.touched_tables = set() frappe.flags.touched_tables.update(tables) + def bulk_insert(self, doctype, fields, values): + """ + Insert multiple records at a time + + :param doctype: Doctype name + :param fields: list of fields + :params values: list of list of values + """ + insert_list = [] + fields = ", ".join(["`"+field+"`" for field in fields]) + + for idx, value in enumerate(values): + insert_list.append(tuple(value)) + if idx and (idx%10000 == 0 or idx < len(values)-1): + self.sql("""INSERT INTO `tab{doctype}` ({fields}) VALUES {values}""".format( + doctype=doctype, + fields=fields, + values=", ".join(['%s'] * len(insert_list)) + ), tuple(insert_list)) + insert_list = [] def enqueue_jobs_after_commit(): if frappe.flags.enqueue_after_commit and len(frappe.flags.enqueue_after_commit) > 0: diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py index f03bf153f3..e48b6d4f9b 100644 --- a/frappe/desk/doctype/tag/tag.py +++ b/frappe/desk/doctype/tag/tag.py @@ -6,17 +6,10 @@ from __future__ import unicode_literals import frappe import json from frappe.model.document import Document -from frappe.utils.global_tags import update_global_tags from frappe import _ class Tag(Document): - - def on_trash(self): - if check_if_tag_is_linked(self.name): - frappe.throw(_("Cannot delete Tag {0} since it is linked to Documents.").format(frappe.bold(self.name))) - -def check_if_tag_is_linked(tag): - return frappe.db.count("Tag Link", {"tag": frappe.db.escape('%{0}%'.format(tag), False)}) + pass def check_user_tags(dt): "if the user does not have a tags column, then it creates one" @@ -72,7 +65,7 @@ class DocTags: if not tag in tl: tl.append(tag) if not frappe.db.exists("Tag", tag): - frappe.get_doc({"doctype": "Tag", "name": tag, "count": 1}).insert(ignore_permissions=True) + frappe.get_doc({"doctype": "Tag", "name": tag}).insert(ignore_permissions=True) self.update(dn, tl) def remove(self, dn, tag): @@ -111,3 +104,78 @@ class DocTags: """adds the _user_tags column if not exists""" from frappe.database.schema import add_column add_column(self.dt, "_user_tags", "Data") + +def delete_tags_for_document(doc): + """ + Delete the __global_tags entry of a document that has + been deleted + :param doc: Deleted document + """ + if not frappe.db.table_exists("Tag Link"): + return + + frappe.db.sql("""DELETE FROM `tabTag Link` WHERE `document_type`=%s AND `document_name`=%s""", (doc.doctype, doc.name)) + +def update_global_tags(doc, tags): + """ + Adds tags for documents + :param doc: Document to be added to global tags + """ + + new_tags = list(set([tag.strip() for tag in tags.split(",") if tag])) + + for tag in new_tags: + if not frappe.db.exists("Tag Link", {"parenttype": doc.doctype, "parent": doc.name, "tag": tag}): + frappe.get_doc({ + "doctype": "Tag Link", + "document_type": doc.doctype, + "document_name": doc.name, + "parenttype": doc.doctype, + "parent": doc.name, + "title": doc.get_title() or '', + "tag": tag + }).insert(ignore_permissions=True) + + existing_tags = [tag.tag for tag in frappe.get_list("Tag Link", filters={ + "document_type": doc.doctype, + "document_name": doc.name + }, fields=["tag"])] + + deleted_tags = get_deleted_tags(new_tags, existing_tags) + + if deleted_tags: + for tag in deleted_tags: + delete_tag_for_document(doc.doctype, doc.name, tag) + +def get_deleted_tags(new_tags, existing_tags): + + return list(set(existing_tags) - set(new_tags)) + +def delete_tag_for_document(dt, dn, tag): + frappe.db.sql("""DELETE FROM `tabTag Link` WHERE `document_type`=%s, `document_name`=%s, tag=%s""", (dt, dn, tag)) + +@frappe.whitelist() +def get_documents_for_tag(tag): + """ + Search for given text in Tag Link + :param tag: tag to be searched + """ + # remove hastag `#` from tag + tag = tag[1:] + results = [] + + result = frappe.get_list("Tag Link", filters={"tag": tag}, fields=["document_type", "document_name", "title", "tag"]) + + for res in result: + results.append({ + "doctype": res.document_type, + "name": res.document_name, + "content": res.title + }) + + print(results) + return results + +@frappe.whitelist() +def get_tags_list_for_awesomebar(): + return [t.name for t in frappe.get_list("Tag")] \ No newline at end of file diff --git a/frappe/desk/doctype/tag_link/tag_link.json b/frappe/desk/doctype/tag_link/tag_link.json index ce389415e5..00a7349c5c 100644 --- a/frappe/desk/doctype/tag_link/tag_link.json +++ b/frappe/desk/doctype/tag_link/tag_link.json @@ -4,46 +4,47 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "dt", - "dn", + "document_type", + "document_name", "tag", "title" ], "fields": [ { - "fieldname": "dt", - "fieldtype": "Link", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Document Type", - "options": "DocType", + "fieldname": "title", + "fieldtype": "Data", + "label": "Document Title", "read_only": 1 }, { - "fieldname": "dn", - "fieldtype": "Dynamic Link", + "fieldname": "tag", + "fieldtype": "Link", "in_list_view": 1, "in_standard_filter": 1, - "label": "Document Name", - "options": "dt", + "label": "Document Tag", + "options": "Tag", "read_only": 1 }, { - "fieldname": "title", - "fieldtype": "Data", - "label": "Document Title", + "fieldname": "document_type", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Document Type", + "options": "DocType", "read_only": 1 }, { - "fieldname": "tag", - "fieldtype": "Data", + "fieldname": "document_name", + "fieldtype": "Dynamic Link", "in_list_view": 1, "in_standard_filter": 1, - "label": "Document Tag", + "label": "Document Name", + "options": "document_type", "read_only": 1 } ], - "modified": "2019-09-25 22:10:47.671304", + "modified": "2019-10-03 16:42:35.932409", "modified_by": "Administrator", "module": "Desk", "name": "Tag Link", diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index fc1f7e1f4f..4044a3dcfc 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -258,5 +258,9 @@ def get_view_logs(doctype, docname): return logs def get_tags(doctype, name): - tags = [tag.tag for tag in frappe.get_all("Tag Link", filters={"dt": doctype, "dn": name}, fields=["tag"])] + tags = [tag.tag for tag in frappe.get_all("Tag Link", filters={ + "document_type": doctype, + "document_name": name + }, fields=["tag"])] + return ",".join([tag for tag in tags]) \ No newline at end of file diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 3273bd65d7..d5b43807a8 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -266,12 +266,12 @@ def get_sidebar_stats(stats, doctype, filters=[]): tags = [tag.name for tag in frappe.get_list("Tag")] _user_tags = [] for tag in tags: - count = frappe.db.count("Tag Link", filters={"dt": doctype, "tag": tag}) + count = frappe.db.count("Tag Link", filters={"document_type": doctype, "tag": tag}) if count > 0: _user_tags.append([tag, count]) frappe.cache().hset("tags_count", doctype, _user_tags) - return {"defined_cat": [], "stats": {"_user_tags": frappe.cache().hget("tags_count", doctype)}} + return {"stats": {"_user_tags": frappe.cache().hget("tags_count", doctype)}} @frappe.whitelist() @frappe.read_only() diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index 193dcd417e..ba1372f278 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -16,11 +16,11 @@ from frappe.core.doctype.file.file import remove_all from frappe.utils.password import delete_all_passwords_for from frappe.model.naming import revert_series_if_last from frappe.utils.global_search import delete_for_document -from frappe.utils.global_tags import delete_tags_for_document +from frappe.desk.doctype.tag.tag import delete_tags_for_document from frappe.exceptions import FileNotFoundError -doctypes_to_skip = ("Communication", "ToDo", "DocShare", "Email Unsubscribe", "Activity Log", "File", "Version", "Document Follow", "Comment" , "View Log") +doctypes_to_skip = ("Communication", "ToDo", "DocShare", "Email Unsubscribe", "Activity Log", "File", "Version", "Document Follow", "Comment" , "View Log", "Tag Link") def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False, ignore_permissions=False, flags=None, ignore_on_trash=False, ignore_missing=True): diff --git a/frappe/patches.txt b/frappe/patches.txt index 953b7f2c52..5de9831c79 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -252,4 +252,4 @@ frappe.patches.v12_0.move_email_and_phone_to_child_table frappe.patches.v12_0.delete_duplicate_indexes frappe.patches.v12_0.set_default_incoming_email_port frappe.patches.v12_0.update_global_search -frappe.patches.v12_0.global_tags +frappe.patches.v12_0.setup_global_tags diff --git a/frappe/patches/v12_0/setup_global_tags.py b/frappe/patches/v12_0/setup_global_tags.py index b3bd15745c..4f501c5e92 100644 --- a/frappe/patches/v12_0/setup_global_tags.py +++ b/frappe/patches/v12_0/setup_global_tags.py @@ -5,6 +5,7 @@ def execute(): frappe.delete_doc_if_exists("DocType", "Tag Doc Category") frappe.reload_doc("desk", "doctype", "tag") + frappe.reload_doc("desk", "doctype", "tag_link") tag_list = [] tag_links = [] @@ -20,27 +21,10 @@ def execute(): if not tag: continue - tag_list.append(tag.strip()) + tag_list.append((tag.strip(), time, time, 'Administrator')) tag_link_name = frappe.generate_hash(dt_tags.name + tag.strip(), 10), tag_links.append((tag_link_name, doctype.name, dt_tags.name, tag.strip(), time, time, 'Administrator')) - - temp_list = [] - for count, value in enumerate(set(tag_list)): - temp_list.append((value, time, time, 'Administrator')) - - if count and (count%1000 == 0 or count == len(tag_list)-1): - frappe.db.sql(""" - INSERT INTO `tabTag` (`name`, `creation`, `modified`, `modified_by`) VALUES {} - """.format(", ".join(['%s'] * len(temp_list))), tuple(temp_list)) - temp_list = [] - - for count, value in enumerate(set(tag_links)): - temp_list.append(value) - - if count and (count%1000 == 0 or count == len(tag_links)-1): - frappe.db.sql(""" - INSERT INTO `tabTag Link` (`name`, `dt`, `dn`, `tag`, `creation`, `modified`, `modified_by`) VALUES {} - """.format(", ".join(['%s'] * len(temp_list))), tuple(temp_list)) - temp_list = [] \ No newline at end of file + frappe.db.bulk_insert("Tag", fields=["name", "creation", "modified", "modified_by"], values=tag_list) + frappe.db.bulk_insert("Tag Link", fields=["name", "document_type", "document_name", "tag", "creation", "modified", "modified_by"], values=tag_links) \ No newline at end of file diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index fee6f4280a..13fd58a0ea 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -603,7 +603,7 @@ frappe.Application = Class.extend({ }, set_global_tags() { - frappe.global_tags.utils.set_tags(); + frappe.tags.utils.set_tags(); } }); diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index 128ea4e872..c953b221c4 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -61,7 +61,6 @@ frappe.ui.Filter = class { doctype: this.parent_doctype, filter_fields: this.filter_fields, select: (doctype, fieldname) => { - console.log(doctype, fieldname); this.set_field(doctype, fieldname); } }); diff --git a/frappe/public/js/frappe/ui/tag_editor.js b/frappe/public/js/frappe/ui/tag_editor.js index 00102cf808..5811714bb5 100644 --- a/frappe/public/js/frappe/ui/tag_editor.js +++ b/frappe/public/js/frappe/ui/tag_editor.js @@ -42,7 +42,7 @@ frappe.ui.TagEditor = Class.extend({ user_tags.push(tag) me.user_tags = user_tags.join(","); me.on_change && me.on_change(me.user_tags); - frappe.global_tags.utils.set_tags(); + frappe.tags.utils.set_tags(); } }); } @@ -57,7 +57,7 @@ frappe.ui.TagEditor = Class.extend({ user_tags.splice(user_tags.indexOf(tag), 1); me.user_tags = user_tags.join(","); me.on_change && me.on_change(me.user_tags); - frappe.global_tags.utils.set_tags(); + frappe.tags.utils.set_tags(); } }); } diff --git a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js index bb5f7edbe5..bfcc5a2d77 100644 --- a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js +++ b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js @@ -1,7 +1,7 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // MIT License. See license.txt frappe.provide('frappe.search'); -frappe.provide('frappe.global_tags'); +frappe.provide('frappe.tags'); frappe.search.AwesomeBar = Class.extend({ setup: function(element) { @@ -181,7 +181,7 @@ frappe.search.AwesomeBar = Class.extend({ frappe.search.utils.get_executables(txt) ); if (txt.charAt(0) === "#") { - options = frappe.global_tags.utils.get_tags(txt); + options = frappe.tags.utils.get_tags(txt); } var out = this.deduplicate(options); return out.sort(function(a, b) { diff --git a/frappe/public/js/frappe/ui/toolbar/global_tags.js b/frappe/public/js/frappe/ui/toolbar/global_tags.js index 1c9f9a88f8..6bbeb10c81 100644 --- a/frappe/public/js/frappe/ui/toolbar/global_tags.js +++ b/frappe/public/js/frappe/ui/toolbar/global_tags.js @@ -1,15 +1,15 @@ // Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors // MIT License. See license.txt -frappe.provide("frappe.global_tags"); +frappe.provide("frappe.tags"); -frappe.global_tags.utils = { +frappe.tags.utils = { get_tags: function(txt) { txt = txt.slice(1); let out = []; - for (let i in frappe.global_tags.tags) { - let tag = frappe.global_tags.tags[i]; + for (let i in frappe.tags.tags) { + let tag = frappe.tags.tags[i]; let level = frappe.search.utils.fuzzy_search(txt, tag); if (level) { out.push({ @@ -31,10 +31,10 @@ frappe.global_tags.utils = { set_tags() { frappe.call({ - method: "frappe.utils.global_tags.get_tags_list_for_awesomebar", + method: "frappe.desk.doctype.tag.tag.get_tags_list_for_awesomebar", callback: function(r) { if (r && r.message) { - frappe.global_tags.tags = $.extend([], r.message); + frappe.tags.tags = $.extend([], r.message); } } }); @@ -51,6 +51,9 @@ frappe.global_tags.utils = { } function make_description(content) { + if (!content) { + return; + } var field_length = 110; var field_value = null; if (content.length > field_length) { @@ -88,12 +91,13 @@ frappe.global_tags.utils = { } return new Promise(function(resolve) { frappe.call({ - method: "frappe.utils.global_tags.get_documents_for_tag", + method: "frappe.desk.doctype.tag.tag.get_documents_for_tag", args: { tag: tag }, callback: function(r) { if (r.message) { + console.log(r.message); resolve(get_results_sets(r.message)); } else { resolve([]); diff --git a/frappe/public/js/frappe/ui/toolbar/search.js b/frappe/public/js/frappe/ui/toolbar/search.js index efd3be2868..7e7cca0768 100644 --- a/frappe/public/js/frappe/ui/toolbar/search.js +++ b/frappe/public/js/frappe/ui/toolbar/search.js @@ -386,7 +386,7 @@ frappe.search.SearchDialog = Class.extend({ global_search: { input_placeholder: __("Search"), empty_state_text: __("Search for anything"), - no_results_status: (keyword) => __("

No results found for '" + keyword + "' in Global Search

"), + no_results_status: (keyword) => "

" + __("No results found for {0} in Global Search", [keyword]) + "

", get_results: function(keywords, callback) { var start = 0, limit = 1000; @@ -403,11 +403,11 @@ frappe.search.SearchDialog = Class.extend({ global_tag: { input_placeholder: __("Search"), empty_state_text: __("Search for anything"), - no_results_status: (keyword) => __("

No results found for '" + keyword + "' in Global Tags

"), + no_results_status: (keyword) => "

" + __("No results found for {0} in Global Tags", [keyword]) + "

", get_results: function(keywords, callback) { var results = frappe.search.utils.get_nav_results(keywords); - frappe.global_tags.utils.get_tag_results(keywords) + frappe.tags.utils.get_tag_results(keywords) .then(function(global_results) { results = results.concat(global_results); callback(results, keywords); diff --git a/frappe/utils/global_tags.py b/frappe/utils/global_tags.py deleted file mode 100644 index 93cf82a140..0000000000 --- a/frappe/utils/global_tags.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def delete_tags_for_document(doc): - """ - Delete the __global_tags entry of a document that has - been deleted - :param doc: Deleted document - """ - if not frappe.db.table_exists("Tag Link"): - return - - frappe.db.sql("""DELETE FROM `tabTag Link` WHERE `dt`=%s AND `dn`=%s""", (doc.doctype, doc.name)) - -def update_global_tags(doc, tags): - """ - Adds tags for documents - :param doc: Document to be added to global tags - """ - - new_tags = list(set([tag.strip() for tag in tags.split(",") if tag])) - - for tag in new_tags: - if not frappe.db.exists("Tag Link", {"parenttype": doc.doctype, "parent": doc.name, "tag": tag}): - frappe.get_doc({ - "doctype": "Tag Link", - "dt": doc.doctype, - "dn": doc.name, - "parenttype": doc.doctype, - "parent": doc.name, - "title": doc.get_title() or '', - "tag": tag - }).insert(ignore_permissions=True) - - existing_tags = [tag.tag for tag in frappe.get_list("Tag Link", filters={"dt": doc.doctype, "dn": doc.name}, fields=["tag"])] - - deleted_tags = get_deleted_tags(new_tags, existing_tags) - - if deleted_tags: - for tag in deleted_tags: - delete_tag_for_document(doc.doctype, doc.name, tag) - -def get_deleted_tags(new_tags, existing_tags): - - return list(set(existing_tags) - set(new_tags)) - -def delete_tag_for_document(dt, dn, tag): - frappe.db.sql("""DELETE FROM `tabTag Link` WHERE dt=%s, dn=%s, tag=%s""", (dt, dn, tag)) - -@frappe.whitelist() -def get_documents_for_tag(tag): - """ - Search for given text in __global_tags - :param tag: tag to be searched - """ - # remove hastag `#` from tag - tag = tag[1:] - results = [] - - result = frappe.db.sql(""" - SELECT `dt`, `dn`, `title`, `tag` - FROM `tabTag Link` - WHERE `tag`=%s - """, (tag), as_dict=True) - - for res in result: - results.append({ - "doctype": res.dt, - "name": res.dn, - "content": res.title - }) - - return results - -@frappe.whitelist() -def get_tags_list_for_awesomebar(): - return [t.name for t in frappe.get_list("Tag")] \ No newline at end of file From 18516788f749922fd3664b800703b45db5fe9141 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Thu, 3 Oct 2019 16:45:39 +0530 Subject: [PATCH 14/21] Update frappe/model/delete_doc.py Co-Authored-By: Faris Ansari --- frappe/model/delete_doc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index ba1372f278..2765c7acb4 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -117,7 +117,7 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa # delete global search entry delete_for_document(doc) - # delete tags from __global_tags + # delete tags from Tag Link delete_tags_for_document(doc) if doc and not for_reload: From 13321ec5f4fed1ec3b1fde88811180a41e3392da Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 5 Oct 2019 09:57:43 +0530 Subject: [PATCH 15/21] chore: rename global_tags to tags --- frappe/desk/doctype/tag/tag.py | 6 +++--- frappe/model/delete_doc.py | 2 +- frappe/patches.txt | 2 +- .../patches/v12_0/{setup_global_tags.py => setup_tags.py} | 7 ++++--- frappe/public/build.json | 2 +- frappe/public/js/frappe/desk.js | 4 ++-- frappe/public/js/frappe/ui/toolbar/search.js | 6 +++--- .../js/frappe/ui/toolbar/{global_tags.js => tags.js} | 2 +- 8 files changed, 16 insertions(+), 15 deletions(-) rename frappe/patches/v12_0/{setup_global_tags.py => setup_tags.py} (79%) rename frappe/public/js/frappe/ui/toolbar/{global_tags.js => tags.js} (96%) diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py index e48b6d4f9b..ca26789d4d 100644 --- a/frappe/desk/doctype/tag/tag.py +++ b/frappe/desk/doctype/tag/tag.py @@ -89,7 +89,7 @@ class DocTags: frappe.db.sql("update `tab%s` set _user_tags=%s where name=%s" % \ (self.dt,'%s','%s'), (tags , dn)) doc= frappe.get_doc(self.dt, dn) - update_global_tags(doc, tags) + update_tags(doc, tags) except Exception as e: if frappe.db.is_column_missing(e): if not tags: @@ -107,7 +107,7 @@ class DocTags: def delete_tags_for_document(doc): """ - Delete the __global_tags entry of a document that has + Delete the Tag Link entry of a document that has been deleted :param doc: Deleted document """ @@ -116,7 +116,7 @@ def delete_tags_for_document(doc): frappe.db.sql("""DELETE FROM `tabTag Link` WHERE `document_type`=%s AND `document_name`=%s""", (doc.doctype, doc.name)) -def update_global_tags(doc, tags): +def update_tags(doc, tags): """ Adds tags for documents :param doc: Document to be added to global tags diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index ba1372f278..33d7f8e0af 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -117,7 +117,7 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa # delete global search entry delete_for_document(doc) - # delete tags from __global_tags + # delete tag link entry delete_tags_for_document(doc) if doc and not for_reload: diff --git a/frappe/patches.txt b/frappe/patches.txt index 5de9831c79..3a1706cda1 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -252,4 +252,4 @@ frappe.patches.v12_0.move_email_and_phone_to_child_table frappe.patches.v12_0.delete_duplicate_indexes frappe.patches.v12_0.set_default_incoming_email_port frappe.patches.v12_0.update_global_search -frappe.patches.v12_0.setup_global_tags +frappe.patches.v12_0.setup_tags diff --git a/frappe/patches/v12_0/setup_global_tags.py b/frappe/patches/v12_0/setup_tags.py similarity index 79% rename from frappe/patches/v12_0/setup_global_tags.py rename to frappe/patches/v12_0/setup_tags.py index 4f501c5e92..33ea39c898 100644 --- a/frappe/patches/v12_0/setup_global_tags.py +++ b/frappe/patches/v12_0/setup_tags.py @@ -21,10 +21,11 @@ def execute(): if not tag: continue - tag_list.append((tag.strip(), time, time, 'Administrator')) + escaped_tag = frappe.db.escape(tag.strip()) + tag_list.append((escaped_tag, time, time, 'Administrator')) - tag_link_name = frappe.generate_hash(dt_tags.name + tag.strip(), 10), - tag_links.append((tag_link_name, doctype.name, dt_tags.name, tag.strip(), time, time, 'Administrator')) + tag_link_name = frappe.generate_hash(dt_tags.name + escaped_tag, 10), + tag_links.append((tag_link_name, doctype.name, dt_tags.name, escaped_tag, time, time, 'Administrator')) frappe.db.bulk_insert("Tag", fields=["name", "creation", "modified", "modified_by"], values=tag_list) frappe.db.bulk_insert("Tag Link", fields=["name", "document_type", "document_name", "tag", "creation", "modified", "modified_by"], values=tag_links) \ No newline at end of file diff --git a/frappe/public/build.json b/frappe/public/build.json index 13583f01eb..343acde05d 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -186,7 +186,7 @@ "public/js/frappe/ui/toolbar/awesome_bar.js", "public/js/frappe/ui/toolbar/energy_points_notifications.js", "public/js/frappe/ui/toolbar/search.js", - "public/js/frappe/ui/toolbar/global_tags.js", + "public/js/frappe/ui/toolbar/tags.js", "public/js/frappe/ui/toolbar/search.html", "public/js/frappe/ui/toolbar/search_header.html", "public/js/frappe/ui/toolbar/search_utils.js", diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 13fd58a0ea..dbbdb268b3 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -148,7 +148,7 @@ frappe.Application = Class.extend({ }, 300000); // check every 5 minutes } - this.set_global_tags(); + this.setup_tags(); }, setup_frappe_vue() { @@ -602,7 +602,7 @@ frappe.Application = Class.extend({ }); }, - set_global_tags() { + setup_tags() { frappe.tags.utils.set_tags(); } }); diff --git a/frappe/public/js/frappe/ui/toolbar/search.js b/frappe/public/js/frappe/ui/toolbar/search.js index 7e7cca0768..42898c73f2 100644 --- a/frappe/public/js/frappe/ui/toolbar/search.js +++ b/frappe/public/js/frappe/ui/toolbar/search.js @@ -185,7 +185,7 @@ frappe.search.SearchDialog = Class.extend({ } if (this.current_keyword.charAt(0) === "#") { - this.search = this.searches["global_tag"]; + this.search = this.searches["tags"]; } else { this.search = this.searches["global_search"]; } @@ -400,10 +400,10 @@ frappe.search.SearchDialog = Class.extend({ }); } }, - global_tag: { + tags: { input_placeholder: __("Search"), empty_state_text: __("Search for anything"), - no_results_status: (keyword) => "

" + __("No results found for {0} in Global Tags", [keyword]) + "

", + no_results_status: (keyword) => "

" + __("No results found for {0} in Tags", [keyword]) + "

", get_results: function(keywords, callback) { var results = frappe.search.utils.get_nav_results(keywords); diff --git a/frappe/public/js/frappe/ui/toolbar/global_tags.js b/frappe/public/js/frappe/ui/toolbar/tags.js similarity index 96% rename from frappe/public/js/frappe/ui/toolbar/global_tags.js rename to frappe/public/js/frappe/ui/toolbar/tags.js index 6bbeb10c81..c8b4d629a4 100644 --- a/frappe/public/js/frappe/ui/toolbar/global_tags.js +++ b/frappe/public/js/frappe/ui/toolbar/tags.js @@ -20,7 +20,7 @@ frappe.tags.utils = { match: tag, onclick() { // Use Global Search Dialog for tag search too. - frappe.searchdialog.search.init_search("#".concat(tag), "global_tag"); + frappe.searchdialog.search.init_search("#".concat(tag), "tags"); } }); } From b100b59ee01081d1d1fe333a6654cd94c84e7220 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 5 Oct 2019 18:59:01 +0530 Subject: [PATCH 16/21] chore: rename file --- frappe/public/js/frappe/desk.js | 4 ++-- frappe/public/js/frappe/ui/toolbar/search.js | 2 +- frappe/public/js/frappe/ui/toolbar/{tags.js => tag_utils.js} | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) rename frappe/public/js/frappe/ui/toolbar/{tags.js => tag_utils.js} (98%) diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index dbbdb268b3..d75b50e05c 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -148,7 +148,7 @@ frappe.Application = Class.extend({ }, 300000); // check every 5 minutes } - this.setup_tags(); + this.fetch_tags(); }, setup_frappe_vue() { @@ -602,7 +602,7 @@ frappe.Application = Class.extend({ }); }, - setup_tags() { + fetch_tags() { frappe.tags.utils.set_tags(); } }); diff --git a/frappe/public/js/frappe/ui/toolbar/search.js b/frappe/public/js/frappe/ui/toolbar/search.js index 42898c73f2..acc623fa20 100644 --- a/frappe/public/js/frappe/ui/toolbar/search.js +++ b/frappe/public/js/frappe/ui/toolbar/search.js @@ -403,7 +403,7 @@ frappe.search.SearchDialog = Class.extend({ tags: { input_placeholder: __("Search"), empty_state_text: __("Search for anything"), - no_results_status: (keyword) => "

" + __("No results found for {0} in Tags", [keyword]) + "

", + no_results_status: (keyword) => "

" + __("No documents found tagged with {0}", [keyword]) + "

", get_results: function(keywords, callback) { var results = frappe.search.utils.get_nav_results(keywords); diff --git a/frappe/public/js/frappe/ui/toolbar/tags.js b/frappe/public/js/frappe/ui/toolbar/tag_utils.js similarity index 98% rename from frappe/public/js/frappe/ui/toolbar/tags.js rename to frappe/public/js/frappe/ui/toolbar/tag_utils.js index c8b4d629a4..aab94775d6 100644 --- a/frappe/public/js/frappe/ui/toolbar/tags.js +++ b/frappe/public/js/frappe/ui/toolbar/tag_utils.js @@ -97,7 +97,6 @@ frappe.tags.utils = { }, callback: function(r) { if (r.message) { - console.log(r.message); resolve(get_results_sets(r.message)); } else { resolve([]); From 014c0360dda5eea684ee3adfdf0282c243a4b691 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sun, 6 Oct 2019 16:09:30 +0530 Subject: [PATCH 17/21] fix: list filters for tags --- frappe/desk/doctype/tag/tag.py | 2 +- frappe/public/build.json | 2 +- frappe/public/js/frappe/desk.js | 2 +- frappe/public/js/frappe/list/list_view.js | 2 +- frappe/public/js/frappe/ui/filters/filter.js | 6 ++++-- frappe/public/js/frappe/ui/tag_editor.js | 4 ++-- frappe/public/js/frappe/ui/toolbar/tag_utils.js | 2 +- 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py index ca26789d4d..8711b190f3 100644 --- a/frappe/desk/doctype/tag/tag.py +++ b/frappe/desk/doctype/tag/tag.py @@ -152,7 +152,7 @@ def get_deleted_tags(new_tags, existing_tags): return list(set(existing_tags) - set(new_tags)) def delete_tag_for_document(dt, dn, tag): - frappe.db.sql("""DELETE FROM `tabTag Link` WHERE `document_type`=%s, `document_name`=%s, tag=%s""", (dt, dn, tag)) + frappe.db.sql("""DELETE FROM `tabTag Link` WHERE `document_type`=%s AND `document_name`=%s AND tag=%s""", (dt, dn, tag)) @frappe.whitelist() def get_documents_for_tag(tag): diff --git a/frappe/public/build.json b/frappe/public/build.json index 343acde05d..c59df8034c 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -186,7 +186,7 @@ "public/js/frappe/ui/toolbar/awesome_bar.js", "public/js/frappe/ui/toolbar/energy_points_notifications.js", "public/js/frappe/ui/toolbar/search.js", - "public/js/frappe/ui/toolbar/tags.js", + "public/js/frappe/ui/toolbar/tag_utils.js", "public/js/frappe/ui/toolbar/search.html", "public/js/frappe/ui/toolbar/search_header.html", "public/js/frappe/ui/toolbar/search_utils.js", diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index d75b50e05c..88a3ba9803 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -603,7 +603,7 @@ frappe.Application = Class.extend({ }, fetch_tags() { - frappe.tags.utils.set_tags(); + frappe.tags.utils.fetch_tags(); } }); diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 19079e1c24..ac6511fcd6 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -432,7 +432,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { tag_editor.wrapper.on('click', '.tagit-label', (e) => { const $this = $(e.currentTarget); - this.filter_area.add(this.doctype, '_user_tags', '=', $this.text()); + this.filter_area.add('Tag Link', 'tag', '=', $this.text()); }); }); } diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index c953b221c4..2a99253b11 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -174,8 +174,10 @@ frappe.ui.Filter = class { let original_docfield = (this.fieldselect.fields_by_name[doctype] || {})[fieldname]; - if (doctype === "Tag Link") { - original_docfield = {fieldname: "tag", fieldtype: "Data", label: "Tags", parent: doctype}; + if (doctype === "Tag Link" || fieldname === "_user_tags") { + original_docfield = {fieldname: "tag", fieldtype: "Data", label: "Tags", parent: "Tag Link"}; + doctype = "Tag Link"; + condition = "="; } if(!original_docfield) { diff --git a/frappe/public/js/frappe/ui/tag_editor.js b/frappe/public/js/frappe/ui/tag_editor.js index 5811714bb5..bf5844e790 100644 --- a/frappe/public/js/frappe/ui/tag_editor.js +++ b/frappe/public/js/frappe/ui/tag_editor.js @@ -42,7 +42,7 @@ frappe.ui.TagEditor = Class.extend({ user_tags.push(tag) me.user_tags = user_tags.join(","); me.on_change && me.on_change(me.user_tags); - frappe.tags.utils.set_tags(); + frappe.tags.utils.fetch_tags(); } }); } @@ -57,7 +57,7 @@ frappe.ui.TagEditor = Class.extend({ user_tags.splice(user_tags.indexOf(tag), 1); me.user_tags = user_tags.join(","); me.on_change && me.on_change(me.user_tags); - frappe.tags.utils.set_tags(); + frappe.tags.utils.fetch_tags(); } }); } diff --git a/frappe/public/js/frappe/ui/toolbar/tag_utils.js b/frappe/public/js/frappe/ui/toolbar/tag_utils.js index aab94775d6..9ba0b4d59e 100644 --- a/frappe/public/js/frappe/ui/toolbar/tag_utils.js +++ b/frappe/public/js/frappe/ui/toolbar/tag_utils.js @@ -29,7 +29,7 @@ frappe.tags.utils = { return out; }, - set_tags() { + fetch_tags() { frappe.call({ method: "frappe.desk.doctype.tag.tag.get_tags_list_for_awesomebar", callback: function(r) { From 1553426807a290889f98722855e913618857c44f Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sun, 6 Oct 2019 16:30:16 +0530 Subject: [PATCH 18/21] fix: do not capitalize tag name --- frappe/public/js/frappe/ui/tags.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/tags.js b/frappe/public/js/frappe/ui/tags.js index 1508e3651e..90c53beae7 100644 --- a/frappe/public/js/frappe/ui/tags.js +++ b/frappe/public/js/frappe/ui/tags.js @@ -67,7 +67,6 @@ frappe.ui.Tags = class { } addTag(label) { - label = toTitle(label); if(label && label!== '' && !this.tagsList.includes(label)) { let $tag = this.getTag(label); this.getListElement($tag).insertBefore(this.$inputWrapper); From b4fcadca2fcf06c32fcab6362ddf1aa7e791bb18 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sun, 6 Oct 2019 21:58:48 +0530 Subject: [PATCH 19/21] fix: codacy fixes --- frappe/desk/doctype/tag/tag.py | 2 -- frappe/public/js/frappe/form/sidebar/form_sidebar.js | 1 - frappe/public/js/frappe/ui/toolbar/tag_utils.js | 1 - 3 files changed, 4 deletions(-) diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py index 8711b190f3..0e2afbb35c 100644 --- a/frappe/desk/doctype/tag/tag.py +++ b/frappe/desk/doctype/tag/tag.py @@ -4,9 +4,7 @@ from __future__ import unicode_literals import frappe -import json from frappe.model.document import Document -from frappe import _ class Tag(Document): pass diff --git a/frappe/public/js/frappe/form/sidebar/form_sidebar.js b/frappe/public/js/frappe/form/sidebar/form_sidebar.js index 87d6c21614..5034b8969c 100644 --- a/frappe/public/js/frappe/form/sidebar/form_sidebar.js +++ b/frappe/public/js/frappe/form/sidebar/form_sidebar.js @@ -119,7 +119,6 @@ frappe.ui.form.Sidebar = Class.extend({ }, make_tags: function() { - var me = this; if (this.frm.meta.issingle) { this.sidebar.find(".form-tags").toggle(false); return; diff --git a/frappe/public/js/frappe/ui/toolbar/tag_utils.js b/frappe/public/js/frappe/ui/toolbar/tag_utils.js index 9ba0b4d59e..c6466d2000 100644 --- a/frappe/public/js/frappe/ui/toolbar/tag_utils.js +++ b/frappe/public/js/frappe/ui/toolbar/tag_utils.js @@ -41,7 +41,6 @@ frappe.tags.utils = { }, get_tag_results: function(tag) { - var me = this; function get_results_sets(data) { var results_sets = [], result, set; function get_existing_set(doctype) { From 254ce39cc53329e9cc7ba8832c9bda724bd708c9 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 7 Oct 2019 09:32:51 +0530 Subject: [PATCH 20/21] chore: return empty string --- frappe/public/js/frappe/ui/toolbar/tag_utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/toolbar/tag_utils.js b/frappe/public/js/frappe/ui/toolbar/tag_utils.js index c6466d2000..12ede1cfcb 100644 --- a/frappe/public/js/frappe/ui/toolbar/tag_utils.js +++ b/frappe/public/js/frappe/ui/toolbar/tag_utils.js @@ -51,7 +51,7 @@ frappe.tags.utils = { function make_description(content) { if (!content) { - return; + return ""; } var field_length = 110; var field_value = null; From 6fc5cb584ff97b36ca42ed4d839db2787a6b3f43 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 7 Oct 2019 17:18:11 +0530 Subject: [PATCH 21/21] fix: codacy --- frappe/public/js/frappe/ui/toolbar/tag_utils.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/ui/toolbar/tag_utils.js b/frappe/public/js/frappe/ui/toolbar/tag_utils.js index 12ede1cfcb..0982260624 100644 --- a/frappe/public/js/frappe/ui/toolbar/tag_utils.js +++ b/frappe/public/js/frappe/ui/toolbar/tag_utils.js @@ -50,9 +50,6 @@ frappe.tags.utils = { } function make_description(content) { - if (!content) { - return ""; - } var field_length = 110; var field_value = null; if (content.length > field_length) { @@ -66,10 +63,14 @@ frappe.tags.utils = { data.forEach(function(d) { // more properties + var description = ""; + if (d.content) { + description = make_description(d.content); + } result = { label: d.name, value: d.name, - description: make_description(d.content), + description: description, route: ['Form', d.doctype, d.name], };