From a1190a0c4a9e2f1943d30a3206420367a451340b Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 12 Apr 2021 16:35:11 +0530 Subject: [PATCH] feat: Add option to mention a group of users --- frappe/boot.py | 2 + frappe/core/doctype/user/user.py | 5 ++ frappe/core/doctype/user_group/__init__.py | 0 .../doctype/user_group/test_user_group.py | 10 ++++ frappe/core/doctype/user_group/user_group.js | 8 +++ .../core/doctype/user_group/user_group.json | 50 +++++++++++++++++++ frappe/core/doctype/user_group/user_group.py | 10 ++++ .../doctype/user_group_member/__init__.py | 0 .../test_user_group_member.py | 10 ++++ .../user_group_member/user_group_member.js | 8 +++ .../user_group_member/user_group_member.json | 32 ++++++++++++ .../user_group_member/user_group_member.py | 10 ++++ .../controls/quill-mention/blots/mention.js | 2 + .../controls/quill-mention/quill.mention.js | 2 + frappe/public/js/frappe/utils/utils.js | 9 ++++ frappe/public/scss/common/quill.scss | 6 ++- frappe/public/scss/desk/timeline.scss | 3 +- frappe/utils/html_utils.py | 2 +- 18 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 frappe/core/doctype/user_group/__init__.py create mode 100644 frappe/core/doctype/user_group/test_user_group.py create mode 100644 frappe/core/doctype/user_group/user_group.js create mode 100644 frappe/core/doctype/user_group/user_group.json create mode 100644 frappe/core/doctype/user_group/user_group.py create mode 100644 frappe/core/doctype/user_group_member/__init__.py create mode 100644 frappe/core/doctype/user_group_member/test_user_group_member.py create mode 100644 frappe/core/doctype/user_group_member/user_group_member.js create mode 100644 frappe/core/doctype/user_group_member/user_group_member.json create mode 100644 frappe/core/doctype/user_group_member/user_group_member.py diff --git a/frappe/boot.py b/frappe/boot.py index 0dfcb8d1b4..65a07b15e5 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -42,6 +42,8 @@ def get_bootinfo(): bootinfo.user_info = get_user_info() bootinfo.sid = frappe.session['sid'] + bootinfo.user_groups = frappe.get_all('User Group', pluck="name") + bootinfo.modules = {} bootinfo.module_list = [] load_desktop_data(bootinfo) diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 04d087e82a..035333cbe9 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -1018,8 +1018,13 @@ def extract_mentions(txt): soup = BeautifulSoup(txt, 'html.parser') emails = [] for mention in soup.find_all(class_='mention'): + if mention.get('data-is-group'): + user_group = frappe.get_cached_doc('User Group', mention['data-id']) + emails += [d.user for d in user_group.user_group_members] + continue email = mention['data-id'] emails.append(email) + return emails def handle_password_test_fail(result): diff --git a/frappe/core/doctype/user_group/__init__.py b/frappe/core/doctype/user_group/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/user_group/test_user_group.py b/frappe/core/doctype/user_group/test_user_group.py new file mode 100644 index 0000000000..c7e28f3d31 --- /dev/null +++ b/frappe/core/doctype/user_group/test_user_group.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestUserGroup(unittest.TestCase): + pass diff --git a/frappe/core/doctype/user_group/user_group.js b/frappe/core/doctype/user_group/user_group.js new file mode 100644 index 0000000000..2aa9b68658 --- /dev/null +++ b/frappe/core/doctype/user_group/user_group.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('User Group', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/core/doctype/user_group/user_group.json b/frappe/core/doctype/user_group/user_group.json new file mode 100644 index 0000000000..3d859dcc9a --- /dev/null +++ b/frappe/core/doctype/user_group/user_group.json @@ -0,0 +1,50 @@ +{ + "actions": [], + "autoname": "field:group_name", + "creation": "2021-04-12 15:17:24.751710", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "group_name", + "user_group_members" + ], + "fields": [ + { + "fieldname": "group_name", + "fieldtype": "Data", + "label": "Group Name", + "unique": 1 + }, + { + "fieldname": "user_group_members", + "fieldtype": "Table MultiSelect", + "label": "User Group Members", + "options": "User Group Member" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-04-12 15:17:24.751710", + "modified_by": "Administrator", + "module": "Core", + "name": "User Group", + "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/core/doctype/user_group/user_group.py b/frappe/core/doctype/user_group/user_group.py new file mode 100644 index 0000000000..6fc6db4620 --- /dev/null +++ b/frappe/core/doctype/user_group/user_group.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 UserGroup(Document): + pass diff --git a/frappe/core/doctype/user_group_member/__init__.py b/frappe/core/doctype/user_group_member/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/user_group_member/test_user_group_member.py b/frappe/core/doctype/user_group_member/test_user_group_member.py new file mode 100644 index 0000000000..38aade4608 --- /dev/null +++ b/frappe/core/doctype/user_group_member/test_user_group_member.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestUserGroupMember(unittest.TestCase): + pass diff --git a/frappe/core/doctype/user_group_member/user_group_member.js b/frappe/core/doctype/user_group_member/user_group_member.js new file mode 100644 index 0000000000..0b2dbe0d46 --- /dev/null +++ b/frappe/core/doctype/user_group_member/user_group_member.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('User Group Member', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/core/doctype/user_group_member/user_group_member.json b/frappe/core/doctype/user_group_member/user_group_member.json new file mode 100644 index 0000000000..d2ff149366 --- /dev/null +++ b/frappe/core/doctype/user_group_member/user_group_member.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "creation": "2021-04-12 15:16:29.279107", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "user" + ], + "fields": [ + { + "fieldname": "user", + "fieldtype": "Link", + "in_list_view": 1, + "label": "User", + "options": "User", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-12 15:17:18.773046", + "modified_by": "Administrator", + "module": "Core", + "name": "User Group Member", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/core/doctype/user_group_member/user_group_member.py b/frappe/core/doctype/user_group_member/user_group_member.py new file mode 100644 index 0000000000..4d0656913d --- /dev/null +++ b/frappe/core/doctype/user_group_member/user_group_member.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 UserGroupMember(Document): + pass diff --git a/frappe/public/js/frappe/form/controls/quill-mention/blots/mention.js b/frappe/public/js/frappe/form/controls/quill-mention/blots/mention.js index 1c5787f854..d6907158f9 100644 --- a/frappe/public/js/frappe/form/controls/quill-mention/blots/mention.js +++ b/frappe/public/js/frappe/form/controls/quill-mention/blots/mention.js @@ -15,6 +15,7 @@ class MentionBlot extends Embed { node.dataset.id = data.id; node.dataset.value = data.value; node.dataset.denotationChar = data.denotationChar; + node.dataset.isGroup = data.isGroup; if (data.link) { node.dataset.link = data.link; } @@ -27,6 +28,7 @@ class MentionBlot extends Embed { value: domNode.dataset.value, link: domNode.dataset.link || null, denotationChar: domNode.dataset.denotationChar, + isGroup: domNode.dataset.isGroup, }; } } diff --git a/frappe/public/js/frappe/form/controls/quill-mention/quill.mention.js b/frappe/public/js/frappe/form/controls/quill-mention/quill.mention.js index ac1b9697f0..4b5326271e 100644 --- a/frappe/public/js/frappe/form/controls/quill-mention/quill.mention.js +++ b/frappe/public/js/frappe/form/controls/quill-mention/quill.mention.js @@ -149,6 +149,7 @@ class Mention { this.mentionList.childNodes[this.itemIndex].dataset.value, link: itemLink || null, denotationChar: this.mentionList.childNodes[this.itemIndex].dataset.denotationChar, + isGroup: this.mentionList.childNodes[this.itemIndex].dataset.isGroup, }; } @@ -197,6 +198,7 @@ class Mention { li.dataset.index = i; li.dataset.id = data[i].id; li.dataset.value = data[i].value; + li.dataset.isGroup = Boolean(data[i].is_group); li.dataset.denotationChar = mentionChar; if (data[i].link) { li.dataset.link = data[i].link; diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 302ebceeda..e1e4f437cd 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1285,6 +1285,15 @@ Object.assign(frappe.utils, { value: frappe.boot.user_info[user].fullname, }; }); + + frappe.boot.user_groups && frappe.boot.user_groups.map(group => { + names_for_mentions.push({ + id: group, + value: group, + is_group: true + }); + }); + return names_for_mentions; }, print(doctype, docname, print_format, letterhead, lang_code) { diff --git a/frappe/public/scss/common/quill.scss b/frappe/public/scss/common/quill.scss index e5303be5cf..c4011e2d6b 100644 --- a/frappe/public/scss/common/quill.scss +++ b/frappe/public/scss/common/quill.scss @@ -190,4 +190,8 @@ .mention>span { margin: 0 3px; -} \ No newline at end of file +} + +.mention[data-is-group="true"] { + background-color: var(--purple-100) !important; +} diff --git a/frappe/public/scss/desk/timeline.scss b/frappe/public/scss/desk/timeline.scss index 4bb3cbec78..a7e5d3dd9c 100644 --- a/frappe/public/scss/desk/timeline.scss +++ b/frappe/public/scss/desk/timeline.scss @@ -77,6 +77,7 @@ $threshold: 34; } } .document-email-link-container { + @extend .ellipsis; position: relative; padding: var(--padding-sm); font-size: var(--text-sm); @@ -141,4 +142,4 @@ $threshold: 34; --icon-stroke: var(--text-color); } } -} \ No newline at end of file +} diff --git a/frappe/utils/html_utils.py b/frappe/utils/html_utils.py index 81e5f2de3e..f3f86dcad2 100644 --- a/frappe/utils/html_utils.py +++ b/frappe/utils/html_utils.py @@ -177,7 +177,7 @@ acceptable_attributes = [ 'data-value', 'role', 'frameborder', 'allowfullscreen', 'spellcheck', 'data-mode', 'data-gramm', 'data-placeholder', 'data-comment', 'data-id', 'data-denotation-char', 'itemprop', 'itemscope', - 'itemtype', 'itemid', 'itemref', 'datetime' + 'itemtype', 'itemid', 'itemref', 'datetime', 'data-is-group' ] mathml_attributes = [