feat(Notifications): Integrated Notifications Centre (#8431)
This commit is contained in:
commit
f67c7fac61
39 changed files with 1229 additions and 641 deletions
|
|
@ -11,7 +11,7 @@ from frappe.desk.notifications import (delete_notification_count_for,
|
|||
common_default_keys = ["__default", "__global"]
|
||||
|
||||
global_cache_keys = ("app_hooks", "installed_apps",
|
||||
"app_modules", "module_app", "notification_config", 'system_settings',
|
||||
"app_modules", "module_app", "system_settings",
|
||||
'scheduler_events', 'time_zone', 'webhooks', 'active_domains',
|
||||
'active_modules', 'assignment_rule', 'server_script_map')
|
||||
|
||||
|
|
@ -114,4 +114,4 @@ def get_doctype_map(doctype, name, filters, order_by=None):
|
|||
|
||||
def clear_doctype_map(doctype, name):
|
||||
cache_key = frappe.scrub(doctype) + '_map'
|
||||
frappe.cache().hdel(cache_key, name)
|
||||
frappe.cache().hdel(cache_key, name)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ from frappe import _
|
|||
import json
|
||||
from frappe.model.document import Document
|
||||
from frappe.core.doctype.user.user import extract_mentions
|
||||
from frappe.utils import get_fullname, get_link_to_form
|
||||
from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification
|
||||
from frappe.utils import get_fullname
|
||||
from frappe.website.render import clear_cache
|
||||
from frappe.database.schema import add_column
|
||||
from frappe.exceptions import ImplicitCommitError
|
||||
|
|
@ -54,31 +55,22 @@ class Comment(Document):
|
|||
title = self.reference_name if title_field == "name" else \
|
||||
frappe.db.get_value(self.reference_doctype, self.reference_name, title_field)
|
||||
|
||||
if title != self.reference_name:
|
||||
parent_doc_label = "{0}: {1} (#{2})".format(_(self.reference_doctype),
|
||||
title, self.reference_name)
|
||||
else:
|
||||
parent_doc_label = "{0}: {1}".format(_(self.reference_doctype),
|
||||
self.reference_name)
|
||||
|
||||
subject = _("{0} mentioned you in a comment in {1}").format(sender_fullname, parent_doc_label)
|
||||
|
||||
recipients = [frappe.db.get_value("User", {"enabled": 1, "name": name, "user_type": "System User", "allowed_in_mentions": 1}, "email")
|
||||
for name in mentions]
|
||||
link = get_link_to_form(self.reference_doctype, self.reference_name, label=parent_doc_label)
|
||||
|
||||
frappe.sendmail(
|
||||
recipients = recipients,
|
||||
sender = frappe.session.user,
|
||||
subject = subject,
|
||||
template = "mentioned_in_comment",
|
||||
args = {
|
||||
"body_content": _("{0} mentioned you in a comment in {1}").format(sender_fullname, link),
|
||||
"comment": self,
|
||||
"link": link
|
||||
},
|
||||
header = [_('New Mention'), 'orange']
|
||||
)
|
||||
notification_message = _('''{0} mentioned you in a comment in {1} {2}''')\
|
||||
.format(frappe.bold(sender_fullname), frappe.bold(self.reference_doctype), frappe.bold(title))
|
||||
|
||||
notification_doc = {
|
||||
'type': 'Mention',
|
||||
'document_type': self.reference_doctype,
|
||||
'document_name': self.reference_name,
|
||||
'subject': notification_message,
|
||||
'from_user': frappe.session.user,
|
||||
'email_content': self.content
|
||||
}
|
||||
|
||||
enqueue_create_notification(recipients, notification_doc)
|
||||
|
||||
|
||||
def on_doctype_update():
|
||||
|
|
|
|||
|
|
@ -14,13 +14,6 @@ def get_notification_config():
|
|||
"Error Snapshot": {"seen": 0, "parent_error_snapshot": None},
|
||||
"Workflow Action": {"status": 'Open'}
|
||||
},
|
||||
"for_other": {
|
||||
"Likes": "frappe.core.notifications.get_unseen_likes",
|
||||
"Email": "frappe.core.notifications.get_unread_emails",
|
||||
},
|
||||
"for_module": {
|
||||
"Social": "frappe.social.doctype.post.post.get_unseen_post_count"
|
||||
}
|
||||
}
|
||||
|
||||
def get_things_todo(as_list=False):
|
||||
|
|
|
|||
0
frappe/desk/doctype/notification_log/__init__.py
Normal file
0
frappe/desk/doctype/notification_log/__init__.py
Normal file
12
frappe/desk/doctype/notification_log/notification_log.js
Normal file
12
frappe/desk/doctype/notification_log/notification_log.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Notification Log', {
|
||||
refresh: function(frm) {
|
||||
let dt = frm.doc.document_type;
|
||||
let dn = frm.doc.document_name;
|
||||
frm.fields_dict.document_name.$input_wrapper
|
||||
.find('.control-value')
|
||||
.wrapInner(`<a href='#Form/${dt}/${dn}'></a>`);
|
||||
}
|
||||
});
|
||||
106
frappe/desk/doctype/notification_log/notification_log.json
Normal file
106
frappe/desk/doctype/notification_log/notification_log.json
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
{
|
||||
"creation": "2019-08-26 13:37:34.165254",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"subject",
|
||||
"for_user",
|
||||
"type",
|
||||
"email_content",
|
||||
"column_break_4",
|
||||
"document_type",
|
||||
"seen",
|
||||
"document_name",
|
||||
"from_user"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "subject",
|
||||
"fieldtype": "Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Subject",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "for_user",
|
||||
"fieldtype": "Link",
|
||||
"label": "For User",
|
||||
"options": "User",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Type",
|
||||
"options": "Mention\nEnergy Point\nAssignment\nShare",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "email_content",
|
||||
"fieldtype": "Text",
|
||||
"label": "Email Content",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Document Type",
|
||||
"options": "DocType",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "seen",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Seen"
|
||||
},
|
||||
{
|
||||
"fieldname": "document_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Document Name",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "from_user",
|
||||
"fieldtype": "Link",
|
||||
"label": "From User",
|
||||
"options": "User",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"modified": "2019-10-09 15:03:56.682093",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Notification Log",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "subject",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
}
|
||||
96
frappe/desk/doctype/notification_log/notification_log.py
Normal file
96
frappe/desk/doctype/notification_log/notification_log.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# -*- 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 import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.desk.doctype.notification_settings.notification_settings import (is_notifications_enabled,
|
||||
is_email_notifications_enabled, is_email_notifications_enabled_for_type)
|
||||
|
||||
class NotificationLog(Document):
|
||||
def after_insert(self):
|
||||
frappe.publish_realtime('notification', after_commit=True, user=self.for_user)
|
||||
if is_email_notifications_enabled(self.for_user):
|
||||
send_notification_email(self)
|
||||
|
||||
|
||||
def get_permission_query_conditions(for_user):
|
||||
if not for_user:
|
||||
for_user = frappe.session.user
|
||||
|
||||
if for_user == 'Administrator':
|
||||
return
|
||||
|
||||
return '''(`tabNotification Log`.for_user = '{user}')'''.format(user=for_user)
|
||||
|
||||
def enqueue_create_notification(users, doc):
|
||||
doc = frappe._dict(doc)
|
||||
|
||||
if isinstance(users, frappe.string_types):
|
||||
users = [user.strip() for user in users.split(',') if user.strip()]
|
||||
|
||||
frappe.enqueue(
|
||||
'frappe.desk.doctype.notification_log.notification_log.make_notification_logs',
|
||||
doc=doc,
|
||||
users=users,
|
||||
now=frappe.flags.in_test
|
||||
)
|
||||
|
||||
def make_notification_logs(doc, users):
|
||||
from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled
|
||||
for user in users:
|
||||
if frappe.db.exists('User', user):
|
||||
if is_notifications_enabled(user):
|
||||
if doc.type == 'Energy Point' and not is_energy_point_enabled():
|
||||
return
|
||||
else:
|
||||
_doc = frappe.new_doc('Notification Log')
|
||||
_doc.update(doc)
|
||||
_doc.for_user = user
|
||||
_doc.subject = _doc.subject.replace('<div>', '').replace('</div>', '')
|
||||
_doc.insert(ignore_permissions=True)
|
||||
|
||||
def send_notification_email(doc):
|
||||
is_type_enabled = is_email_notifications_enabled_for_type(doc.for_user, doc.type)
|
||||
if not is_type_enabled:
|
||||
return
|
||||
|
||||
from frappe.utils import get_url_to_form, strip_html
|
||||
|
||||
doc_link = get_url_to_form(doc.document_type, doc.document_name)
|
||||
header = get_email_header(doc)
|
||||
email_subject = strip_html(doc.subject)
|
||||
|
||||
frappe.sendmail(
|
||||
recipients = doc.for_user,
|
||||
subject = email_subject,
|
||||
template = "new_notification",
|
||||
args = {
|
||||
'body_content': doc.subject,
|
||||
'description': doc.email_content,
|
||||
'document_type': doc.document_type,
|
||||
'document_name': doc.document_name,
|
||||
'doc_link': doc_link
|
||||
},
|
||||
header = [header, 'orange'],
|
||||
now=frappe.flags.in_test
|
||||
)
|
||||
|
||||
def get_email_header(doc):
|
||||
return {
|
||||
'Default': _('New Notification'),
|
||||
'Mention': _('New Mention'),
|
||||
'Assignment': _('New Assignment'),
|
||||
'Share': _('New Document Shared'),
|
||||
'Energy Point': _('Energy Point Update'),
|
||||
}[doc.type or 'Default']
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def mark_as_seen(docnames):
|
||||
docnames = frappe.parse_json(docnames)
|
||||
if docnames:
|
||||
filters = {'name': ['in', docnames]}
|
||||
frappe.db.set_value('Notification Log', filters, 'seen', 1, update_modified=False)
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.desk.form.assign_to import add as assign_task
|
||||
import unittest
|
||||
|
||||
class TestNotificationLog(unittest.TestCase):
|
||||
def test_assignment(self):
|
||||
todo = get_todo()
|
||||
user = get_user()
|
||||
|
||||
assign_task({
|
||||
"assign_to": user,
|
||||
"doctype": 'ToDo',
|
||||
"name": todo.name,
|
||||
"description": todo.description
|
||||
})
|
||||
log_type = frappe.db.get_value('Notification Log', {
|
||||
'document_type': 'ToDo',
|
||||
'document_name': todo.name
|
||||
}, 'type')
|
||||
self.assertEqual(log_type, 'Assignment')
|
||||
|
||||
def test_share(self):
|
||||
todo = get_todo()
|
||||
user = get_user()
|
||||
|
||||
frappe.share.add('ToDo', todo.name, user)
|
||||
log_type = frappe.db.get_value('Notification Log', {
|
||||
'document_type': 'ToDo',
|
||||
'document_name': todo.name
|
||||
}, 'type')
|
||||
self.assertEqual(log_type, 'Share')
|
||||
|
||||
email = get_last_email_queue()
|
||||
content = 'Subject: {} shared a document ToDo'.format(frappe.utils.get_fullname(frappe.session.user))
|
||||
self.assertTrue(content in email.message)
|
||||
|
||||
|
||||
def get_last_email_queue():
|
||||
res = frappe.db.get_all('Email Queue',
|
||||
fields=['message'],
|
||||
order_by='creation desc',
|
||||
limit=1
|
||||
)
|
||||
return res[0]
|
||||
|
||||
def get_todo():
|
||||
if not frappe.get_all('ToDo'):
|
||||
return frappe.get_doc({ 'doctype': 'ToDo', 'description': 'Test for Notification' }).insert()
|
||||
|
||||
res = frappe.get_all('ToDo', limit=1)
|
||||
return frappe.get_cached_doc('ToDo', res[0].name)
|
||||
|
||||
def get_user():
|
||||
users = frappe.db.get_all('User',
|
||||
filters={'name': ('not in', ['Administrator', 'Guest'])},
|
||||
fields='name', limit=1)
|
||||
return users[0].name
|
||||
0
frappe/desk/doctype/notification_settings/__init__.py
Normal file
0
frappe/desk/doctype/notification_settings/__init__.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
{
|
||||
"autoname": "Prompt",
|
||||
"creation": "2019-09-11 22:15:44.851526",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enabled",
|
||||
"subscribed_documents",
|
||||
"column_break_3",
|
||||
"enable_email_notifications",
|
||||
"enable_email_mention",
|
||||
"enable_email_assignment",
|
||||
"enable_email_energy_point",
|
||||
"enable_email_share",
|
||||
"user"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "enabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"fieldname": "subscribed_documents",
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": "Subscribed Documents",
|
||||
"options": "Notification Subscribed Document"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Email Settings"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "enable_email_notifications",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Email Notifications"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "enable_email_notifications",
|
||||
"fieldname": "enable_email_mention",
|
||||
"fieldtype": "Check",
|
||||
"label": "Mentions"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "enable_email_notifications",
|
||||
"fieldname": "enable_email_assignment",
|
||||
"fieldtype": "Check",
|
||||
"label": "Assignments"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "enable_email_notifications",
|
||||
"fieldname": "enable_email_energy_point",
|
||||
"fieldtype": "Check",
|
||||
"label": "Energy Points"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "enable_email_notifications",
|
||||
"fieldname": "enable_email_share",
|
||||
"fieldtype": "Check",
|
||||
"label": "Document Share"
|
||||
},
|
||||
{
|
||||
"default": "__user",
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "User",
|
||||
"options": "User",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"modified": "2019-10-09 15:58:16.746610",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Notification Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# -*- 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 NotificationSettings(Document):
|
||||
def on_update(self):
|
||||
from frappe.desk.notifications import clear_notification_config
|
||||
clear_notification_config(frappe.session.user)
|
||||
|
||||
|
||||
def is_notifications_enabled(user):
|
||||
enabled = frappe.db.get_value('Notification Settings', user, 'enabled')
|
||||
if enabled is None:
|
||||
return True
|
||||
return enabled
|
||||
|
||||
def is_email_notifications_enabled(user):
|
||||
enabled = frappe.db.get_value('Notification Settings', user, 'enable_email_notifications')
|
||||
if enabled is None:
|
||||
return True
|
||||
return enabled
|
||||
|
||||
def is_email_notifications_enabled_for_type(user, notification_type):
|
||||
fieldname = 'enable_email_' + frappe.scrub(notification_type)
|
||||
enabled = frappe.db.get_value('Notification Settings', user, fieldname)
|
||||
if enabled is None:
|
||||
return True
|
||||
return enabled
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_notification_settings():
|
||||
_doc = frappe.new_doc('Notification Settings')
|
||||
_doc.name = frappe.session.user
|
||||
_doc.insert(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_subscribed_documents():
|
||||
try:
|
||||
doc = frappe.get_doc('Notification Settings', frappe.session.user)
|
||||
subscribed_documents = [item.document for item in doc.subscribed_documents]
|
||||
except frappe.DoesNotExistError:
|
||||
subscribed_documents = []
|
||||
|
||||
return subscribed_documents
|
||||
|
||||
|
||||
def get_permission_query_conditions(user):
|
||||
if not user: user = frappe.session.user
|
||||
|
||||
return '''(`tabNotification Settings`.user = '{user}')'''.format(user=user)
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"creation": "2019-10-09 15:04:39.504787",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"document"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "document",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Document",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-10-09 16:02:00.049237",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Notification Subscribed Document",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -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 NotificationSubscribedDocument(Document):
|
||||
pass
|
||||
|
|
@ -190,4 +190,4 @@
|
|||
"title_field": "description",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
from frappe import _
|
||||
from frappe.desk.form.document_follow import follow_document
|
||||
from frappe.utils import cint
|
||||
from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification
|
||||
import frappe.utils
|
||||
import frappe.share
|
||||
|
||||
class DuplicateToDoError(frappe.ValidationError): pass
|
||||
|
|
@ -80,7 +81,7 @@ def add(args=None):
|
|||
|
||||
# notify
|
||||
notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN',\
|
||||
description=args.get("description"), notify=args.get('notify'))
|
||||
description=args.get("description"))
|
||||
|
||||
return get(args)
|
||||
|
||||
|
|
@ -147,7 +148,7 @@ def clear(doctype, name):
|
|||
return True
|
||||
|
||||
def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE',
|
||||
description=None, notify=0):
|
||||
description=None):
|
||||
"""
|
||||
Notify assignee that there is a change in assignment
|
||||
"""
|
||||
|
|
@ -158,56 +159,28 @@ def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE',
|
|||
return
|
||||
|
||||
# Search for email address in description -- i.e. assignee
|
||||
from frappe.utils import get_link_to_form
|
||||
assignment = get_link_to_form(doc_type, doc_name, label="%s: %s" % (doc_type, doc_name))
|
||||
owner_name = frappe.get_cached_value('User', owner, 'full_name')
|
||||
user_name = frappe.get_cached_value('User', frappe.session.user, 'full_name')
|
||||
title_field = frappe.get_meta(doc_type).get_title_field()
|
||||
title = doc_name if title_field == "name" else \
|
||||
frappe.db.get_value(doc_type, doc_name, title_field)
|
||||
description_html = "<div>{0}</div>".format(description) if description else None
|
||||
|
||||
if action=='CLOSE':
|
||||
if owner == frappe.session.get('user'):
|
||||
arg = {
|
||||
'contact': assigned_by,
|
||||
'txt': _("The task {0}, that you assigned to {1}, has been closed.").format(assignment,
|
||||
owner_name)
|
||||
}
|
||||
else:
|
||||
arg = {
|
||||
'contact': assigned_by,
|
||||
'txt': _("The task {0}, that you assigned to {1}, has been closed by {2}.").format(assignment,
|
||||
owner_name, user_name)
|
||||
}
|
||||
subject = _('Your assignment on {0} {1} has been removed').format(frappe.bold(doc_type), frappe.bold(title))
|
||||
else:
|
||||
description_html = "<p>{0}</p>".format(description)
|
||||
arg = {
|
||||
'contact': owner,
|
||||
'txt': _("A new task, {0}, has been assigned to you by {1}. {2}").format(assignment,
|
||||
user_name, description_html),
|
||||
'notify': notify
|
||||
}
|
||||
user_name = frappe.bold(user_name)
|
||||
document_type = frappe.bold(doc_type)
|
||||
title = frappe.bold(title)
|
||||
subject = _('{0} assigned a new task {1} {2} to you').format(user_name, document_type, title)
|
||||
|
||||
if arg and cint(arg.get("notify")):
|
||||
_notify(arg)
|
||||
notification_doc = {
|
||||
'type': 'Assignment',
|
||||
'document_type': doc_type,
|
||||
'subject': subject,
|
||||
'document_name': doc_name,
|
||||
'from_user': frappe.session.user,
|
||||
'email_content': description_html
|
||||
}
|
||||
|
||||
def _notify(args):
|
||||
from frappe.utils import get_fullname, get_url
|
||||
enqueue_create_notification(owner, notification_doc)
|
||||
|
||||
args = frappe._dict(args)
|
||||
contact = args.contact
|
||||
txt = args.txt
|
||||
|
||||
try:
|
||||
if not isinstance(contact, list):
|
||||
contact = [frappe.db.get_value("User", contact, "email") or contact]
|
||||
|
||||
frappe.sendmail(\
|
||||
recipients=contact,
|
||||
sender= frappe.db.get_value("User", frappe.session.user, "email"),
|
||||
subject=_("New message from {0}").format(get_fullname(frappe.session.user)),
|
||||
template="new_message",
|
||||
args={
|
||||
"from": get_fullname(frappe.session.user),
|
||||
"message": txt,
|
||||
"link": get_url()
|
||||
},
|
||||
header=[_('New Message'), 'orange'])
|
||||
except frappe.OutgoingEmailError:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.utils import time_diff_in_seconds, now, now_datetime, DATETIME_FORMAT
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe.desk.doctype.notification_settings.notification_settings import get_subscribed_documents
|
||||
from six import string_types
|
||||
import json
|
||||
|
||||
|
|
@ -16,10 +15,7 @@ def get_notifications():
|
|||
not frappe.db.get_single_value('System Settings', 'setup_complete')):
|
||||
return {
|
||||
"open_count_doctype": {},
|
||||
"open_count_module": {},
|
||||
"open_count_other": {},
|
||||
"targets": {},
|
||||
"new_messages": []
|
||||
}
|
||||
|
||||
config = get_notification_config()
|
||||
|
|
@ -37,58 +33,9 @@ def get_notifications():
|
|||
|
||||
return {
|
||||
"open_count_doctype": get_notifications_for_doctypes(config, notification_count),
|
||||
"open_count_module": get_notifications_for_modules(config, notification_count),
|
||||
"open_count_other": get_notifications_for_other(config, notification_count),
|
||||
"targets": get_notifications_for_targets(config, notification_percent),
|
||||
"new_messages": get_new_messages()
|
||||
}
|
||||
|
||||
def get_new_messages():
|
||||
last_update = frappe.cache().hget("notifications_last_update", frappe.session.user)
|
||||
now_timestamp = now()
|
||||
frappe.cache().hset("notifications_last_update", frappe.session.user, now_timestamp)
|
||||
|
||||
if not last_update:
|
||||
return []
|
||||
|
||||
if last_update and time_diff_in_seconds(now_timestamp, last_update) > 1800:
|
||||
# no update for 30 mins, consider only the last 30 mins
|
||||
last_update = (now_datetime() - relativedelta(seconds=1800)).strftime(DATETIME_FORMAT)
|
||||
|
||||
return frappe.db.sql("""select sender_full_name, content
|
||||
from `tabCommunication`
|
||||
where communication_type in ('Chat', 'Notification')
|
||||
and reference_doctype='user'
|
||||
and reference_name = %s
|
||||
and creation > %s
|
||||
order by creation desc""", (frappe.session.user, last_update), as_dict=1)
|
||||
|
||||
def get_notifications_for_modules(config, notification_count):
|
||||
"""Notifications for modules"""
|
||||
return get_notifications_for("for_module", config, notification_count)
|
||||
|
||||
def get_notifications_for_other(config, notification_count):
|
||||
"""Notifications for other items"""
|
||||
return get_notifications_for("for_other", config, notification_count)
|
||||
|
||||
def get_notifications_for(notification_type, config, notification_count):
|
||||
open_count = {}
|
||||
notification_map = config.get(notification_type) or {}
|
||||
for m in notification_map:
|
||||
try:
|
||||
if m in notification_count:
|
||||
open_count[m] = notification_count[m]
|
||||
else:
|
||||
open_count[m] = frappe.get_attr(notification_map[m])()
|
||||
|
||||
frappe.cache().hset("notification_count:" + m, frappe.session.user, open_count[m])
|
||||
except frappe.PermissionError:
|
||||
frappe.clear_messages()
|
||||
pass
|
||||
# frappe.msgprint("Permission Error in notifications for {0}".format(m))
|
||||
|
||||
return open_count
|
||||
|
||||
def get_notifications_for_doctypes(config, notification_count):
|
||||
"""Notifications for DocTypes"""
|
||||
can_read = frappe.get_user().get_can_read()
|
||||
|
|
@ -170,12 +117,11 @@ def get_notifications_for_targets(config, notification_percent):
|
|||
def clear_notifications(user=None):
|
||||
if frappe.flags.in_install:
|
||||
return
|
||||
|
||||
cache = frappe.cache()
|
||||
config = get_notification_config()
|
||||
for_doctype = list(config.get('for_doctype')) if config.get('for_doctype') else []
|
||||
for_module = list(config.get('for_module')) if config.get('for_module') else []
|
||||
groups = for_doctype + for_module
|
||||
cache = frappe.cache()
|
||||
|
||||
for name in groups:
|
||||
if user:
|
||||
|
|
@ -185,6 +131,9 @@ def clear_notifications(user=None):
|
|||
|
||||
frappe.publish_realtime('clear_notifications')
|
||||
|
||||
def clear_notification_config(user):
|
||||
frappe.cache().hdel('notification_config', user)
|
||||
|
||||
def delete_notification_count_for(doctype):
|
||||
frappe.cache().delete_key("notification_count:" + doctype)
|
||||
frappe.publish_realtime('clear_notifications')
|
||||
|
|
@ -200,9 +149,10 @@ def clear_doctype_notifications(doc, method=None, *args, **kwargs):
|
|||
delete_notification_count_for(doctype)
|
||||
return
|
||||
|
||||
def get_notification_info_for_boot():
|
||||
out = get_notifications()
|
||||
@frappe.whitelist()
|
||||
def get_notification_info():
|
||||
config = get_notification_config()
|
||||
out = get_notifications()
|
||||
can_read = frappe.get_user().get_can_read()
|
||||
conditions = {}
|
||||
module_doctypes = {}
|
||||
|
|
@ -224,6 +174,7 @@ def get_notification_info_for_boot():
|
|||
|
||||
def get_notification_config():
|
||||
def _get():
|
||||
subscribed_documents = get_subscribed_documents()
|
||||
config = frappe._dict()
|
||||
hooks = frappe.get_hooks()
|
||||
if hooks:
|
||||
|
|
@ -231,15 +182,26 @@ def get_notification_config():
|
|||
nc = frappe.get_attr(notification_config)()
|
||||
for key in ("for_doctype", "for_module", "for_other", "targets"):
|
||||
config.setdefault(key, {})
|
||||
config[key].update(nc.get(key, {}))
|
||||
if key == "for_doctype":
|
||||
if len(subscribed_documents) > 0:
|
||||
key_config = nc.get(key, {})
|
||||
subscribed_docs_config = frappe._dict()
|
||||
for document in subscribed_documents:
|
||||
if key_config.get(document):
|
||||
subscribed_docs_config[document] = key_config.get(document)
|
||||
config[key].update(subscribed_docs_config)
|
||||
else:
|
||||
config[key].update(nc.get(key, {}))
|
||||
else:
|
||||
config[key].update(nc.get(key, {}))
|
||||
return config
|
||||
|
||||
return frappe.cache().get_value("notification_config", _get)
|
||||
return frappe.cache().hget("notification_config", frappe.session.user, _get)
|
||||
|
||||
def get_filters_for(doctype):
|
||||
'''get open filters for doctype'''
|
||||
config = get_notification_config()
|
||||
return config.get('for_doctype').get(doctype, {})
|
||||
return config.get("for_doctype").get(doctype, {})
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
|
|
@ -253,7 +215,7 @@ def get_open_count(doctype, name, items=[]):
|
|||
|
||||
if frappe.flags.in_migrate or frappe.flags.in_install:
|
||||
return {
|
||||
'count': []
|
||||
"count": []
|
||||
}
|
||||
|
||||
frappe.has_permission(doc=frappe.get_doc(doctype, name), throw=True)
|
||||
|
|
@ -264,39 +226,39 @@ def get_open_count(doctype, name, items=[]):
|
|||
# compile all items in a list
|
||||
if not items:
|
||||
for group in links.transactions:
|
||||
items.extend(group.get('items'))
|
||||
items.extend(group.get("items"))
|
||||
|
||||
if not isinstance(items, list):
|
||||
items = json.loads(items)
|
||||
|
||||
out = []
|
||||
for d in items:
|
||||
if d in links.get('internal_links', {}):
|
||||
if d in links.get("internal_links", {}):
|
||||
# internal link
|
||||
continue
|
||||
|
||||
filters = get_filters_for(d)
|
||||
fieldname = links.get('non_standard_fieldnames', {}).get(d, links.fieldname)
|
||||
data = {'name': d}
|
||||
fieldname = links.get("non_standard_fieldnames", {}).get(d, links.fieldname)
|
||||
data = {"name": d}
|
||||
if filters:
|
||||
# get the fieldname for the current document
|
||||
# we only need open documents related to the current document
|
||||
filters[fieldname] = name
|
||||
total = len(frappe.get_all(d, fields='name',
|
||||
total = len(frappe.get_all(d, fields="name",
|
||||
filters=filters, limit=100, distinct=True, ignore_ifnull=True))
|
||||
data['open_count'] = total
|
||||
data["open_count"] = total
|
||||
|
||||
total = len(frappe.get_all(d, fields='name',
|
||||
total = len(frappe.get_all(d, fields="name",
|
||||
filters={fieldname: name}, limit=100, distinct=True, ignore_ifnull=True))
|
||||
data['count'] = total
|
||||
data["count"] = total
|
||||
out.append(data)
|
||||
|
||||
out = {
|
||||
'count': out,
|
||||
"count": out,
|
||||
}
|
||||
|
||||
module = frappe.get_meta_module(doctype)
|
||||
if hasattr(module, 'get_timeline_data'):
|
||||
out['timeline_data'] = module.get_timeline_data(doctype, name)
|
||||
if hasattr(module, "get_timeline_data"):
|
||||
out["timeline_data"] = module.get_timeline_data(doctype, name)
|
||||
|
||||
return out
|
||||
|
|
|
|||
|
|
@ -88,6 +88,8 @@ permission_query_conditions = {
|
|||
"Event": "frappe.desk.doctype.event.event.get_permission_query_conditions",
|
||||
"ToDo": "frappe.desk.doctype.todo.todo.get_permission_query_conditions",
|
||||
"User": "frappe.core.doctype.user.user.get_permission_query_conditions",
|
||||
"Notification Log": "frappe.desk.doctype.notification_log.notification_log.get_permission_query_conditions",
|
||||
"Notification Settings": "frappe.desk.doctype.notification_settings.notification_settings.get_permission_query_conditions",
|
||||
"Note": "frappe.desk.doctype.note.note.get_permission_query_conditions",
|
||||
"Kanban Board": "frappe.desk.doctype.kanban_board.kanban_board.get_permission_query_conditions",
|
||||
"Contact": "frappe.contacts.address_and_contact.get_permission_query_conditions_for_contact",
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ 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", "Tag Link")
|
||||
doctypes_to_skip = ("Communication", "ToDo", "DocShare", "Email Unsubscribe", "Activity Log", "File",
|
||||
"Version", "Document Follow", "Comment" , "View Log", "Tag Link", "Notification Log")
|
||||
|
||||
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):
|
||||
|
|
@ -305,6 +306,7 @@ def delete_dynamic_links(doctype, name):
|
|||
delete_references('Comment', doctype, name)
|
||||
delete_references('View Log', doctype, name)
|
||||
delete_references('Document Follow', doctype, name, 'ref_doctype', 'ref_docname')
|
||||
delete_references('Notification Log', doctype, name, 'document_type', 'document_name')
|
||||
|
||||
# unlink communications
|
||||
clear_timeline_references(doctype, name)
|
||||
|
|
|
|||
|
|
@ -252,3 +252,5 @@ 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_tags
|
||||
execute:frappe.reload_doc('desk', 'doctype', 'notification_settings')
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@
|
|||
"public/less/indicator.less",
|
||||
"public/less/avatar.less",
|
||||
"public/less/navbar.less",
|
||||
"public/less/notifications.less",
|
||||
"public/less/sidebar.less",
|
||||
"public/less/page.less",
|
||||
"public/less/tree.less",
|
||||
|
|
@ -185,6 +186,7 @@
|
|||
|
||||
"public/js/frappe/ui/toolbar/awesome_bar.js",
|
||||
"public/js/frappe/ui/toolbar/energy_points_notifications.js",
|
||||
"public/js/frappe/ui/notifications/notifications.js",
|
||||
"public/js/frappe/ui/toolbar/search.js",
|
||||
"public/js/frappe/ui/toolbar/tag_utils.js",
|
||||
"public/js/frappe/ui/toolbar/search.html",
|
||||
|
|
|
|||
|
|
@ -74,8 +74,6 @@ frappe.Application = Class.extend({
|
|||
// trigger app startup
|
||||
$(document).trigger('startup');
|
||||
|
||||
this.start_notification_updates();
|
||||
|
||||
$(document).trigger('app_ready');
|
||||
|
||||
if (frappe.boot.messages) {
|
||||
|
|
@ -268,54 +266,6 @@ frappe.Application = Class.extend({
|
|||
}
|
||||
},
|
||||
|
||||
start_notification_updates: function() {
|
||||
var me = this;
|
||||
|
||||
// refresh_notifications will be called only once during a 1 second window
|
||||
this.refresh_notifications = frappe.utils.debounce(this.refresh_notifications.bind(this), 1000);
|
||||
|
||||
// kickoff
|
||||
this.refresh_notifications();
|
||||
|
||||
frappe.realtime.on('clear_notifications', () => {
|
||||
me.refresh_notifications();
|
||||
});
|
||||
|
||||
// first time loaded in boot
|
||||
$(document).trigger("notification-update");
|
||||
|
||||
// refresh notifications if user is back after sometime
|
||||
$(document).on("session_alive", function() {
|
||||
me.refresh_notifications();
|
||||
});
|
||||
},
|
||||
|
||||
refresh_notifications: function() {
|
||||
var me = this;
|
||||
if(frappe.session_alive && frappe.boot && frappe.boot.home_page !== 'setup-wizard') {
|
||||
if (this._refresh_notifications) {
|
||||
this._refresh_notifications.abort();
|
||||
}
|
||||
this._refresh_notifications = frappe.call({
|
||||
type: 'GET',
|
||||
method: "frappe.desk.notifications.get_notifications",
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
$.extend(frappe.boot.notification_info, r.message);
|
||||
$(document).trigger("notification-update");
|
||||
|
||||
if(frappe.get_route()[0] != "messages") {
|
||||
if(r.message.new_messages.length) {
|
||||
frappe.utils.set_title_prefix("(" + r.message.new_messages.length + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
freeze: false
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
set_globals: function() {
|
||||
frappe.session.user = frappe.boot.user.name;
|
||||
frappe.session.user_email = frappe.boot.user.email;
|
||||
|
|
|
|||
|
|
@ -140,7 +140,6 @@ frappe.ui.form.AssignToDialog = Class.extend({
|
|||
{ fieldtype: 'Section Break' },
|
||||
{ fieldtype: 'Column Break' },
|
||||
{ fieldtype: 'Date', fieldname: 'date', label: __("Complete By") },
|
||||
{ fieldtype: 'Check', fieldname: 'notify', label: __("Notify by Email"), default: 1},
|
||||
{ fieldtype: 'Column Break' },
|
||||
{ fieldtype: 'Select', fieldname: 'priority', label: __("Priority"),
|
||||
options: [
|
||||
|
|
@ -171,12 +170,10 @@ frappe.ui.form.AssignToDialog = Class.extend({
|
|||
var me = this;
|
||||
if($(myself).prop("checked")) {
|
||||
me.dialog.set_value("assign_to", frappe.session.user);
|
||||
me.dialog.set_value("notify", 0);
|
||||
me.dialog.get_field("notify").$wrapper.toggle(false);
|
||||
me.dialog.get_field("assign_to").$wrapper.toggle(false);
|
||||
} else {
|
||||
me.dialog.set_value("assign_to", "");
|
||||
me.dialog.get_field("notify").$wrapper.toggle(true);
|
||||
me.dialog.get_field("assign_to").$wrapper.toggle(true);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -141,7 +141,6 @@ frappe.ui.form.Share = Class.extend({
|
|||
read: $(d.body).find(".add-share-read").prop("checked") ? 1 : 0,
|
||||
write: $(d.body).find(".add-share-write").prop("checked") ? 1 : 0,
|
||||
share: $(d.body).find(".add-share-share").prop("checked") ? 1 : 0,
|
||||
notify: $(d.body).find(".add-share-notify").prop("checked") ? 1 : 0
|
||||
},
|
||||
btn: this,
|
||||
callback: function(r) {
|
||||
|
|
|
|||
|
|
@ -51,17 +51,5 @@
|
|||
<p>
|
||||
<button class="btn btn-primary btn-add-share">{{ __("Add") }}</button>
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col-xs-6"></div>
|
||||
<div class="col-xs-6">
|
||||
<div class="checkbox">
|
||||
<label><span class="input-area">
|
||||
<input type="checkbox" class="add-share-notify"
|
||||
name="notify"></span>
|
||||
<span class="label-area small">{{ __("Notify by email") }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
450
frappe/public/js/frappe/ui/notifications/notifications.js
Normal file
450
frappe/public/js/frappe/ui/notifications/notifications.js
Normal file
|
|
@ -0,0 +1,450 @@
|
|||
frappe.ui.Notifications = class Notifications {
|
||||
constructor() {
|
||||
frappe.model
|
||||
.with_doc('Notification Settings', frappe.session.user)
|
||||
.then(doc => {
|
||||
this.notifications_settings = doc;
|
||||
this.make();
|
||||
});
|
||||
}
|
||||
|
||||
make() {
|
||||
this.$dropdown = $('.navbar').find('.dropdown-notifications');
|
||||
this.$dropdown_list = this.$dropdown.find('.notifications-list');
|
||||
this.$notification_indicator = this.$dropdown.find(
|
||||
'.notifications-indicator'
|
||||
);
|
||||
this.user = frappe.session.user;
|
||||
this.max_length = 20;
|
||||
|
||||
this.render_dropdown_headers();
|
||||
this.$notifications = this.$dropdown_list.find(
|
||||
'.category-list[data-category="Notifications"]'
|
||||
);
|
||||
this.$open_docs = this.$dropdown_list.find(
|
||||
'.category-list[data-category="Open Documents"]'
|
||||
);
|
||||
this.$upcoming_events = this.$dropdown_list.find(
|
||||
'.category-list[data-category="Upcoming Events"]'
|
||||
);
|
||||
|
||||
frappe.utils.bind_actions_with_object(this.$dropdown_list, this);
|
||||
this.setup_notifications();
|
||||
this.bind_events();
|
||||
}
|
||||
|
||||
setup_notifications() {
|
||||
this.get_notifications_list(this.max_length).then(list => {
|
||||
this.dropdown_items = list;
|
||||
this.render_notifications_dropdown();
|
||||
|
||||
if (this.$notifications.find('.unseen').length) {
|
||||
this.$notification_indicator.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render_upcoming_events(e, $target) {
|
||||
let hide = $target.next().hasClass('in');
|
||||
if (!hide) {
|
||||
let today = frappe.datetime.now_date();
|
||||
|
||||
frappe
|
||||
.xcall('frappe.desk.doctype.event.event.get_events', {
|
||||
start: today,
|
||||
end: today
|
||||
})
|
||||
.then(event_list => {
|
||||
this.render_events_html(event_list);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render_events_html(event_list) {
|
||||
let html = '';
|
||||
if (event_list.length) {
|
||||
let get_event_html = event => {
|
||||
let time = frappe.datetime.get_time(event.starts_on);
|
||||
return `<a class="recent-item event" href="#Form/Event/${event.name}">
|
||||
<span class="event-time bold">${time}</span>
|
||||
<span class="event-subject">${event.subject}</span>
|
||||
</a>`;
|
||||
};
|
||||
html = event_list.map(get_event_html).join('');
|
||||
} else {
|
||||
html = `<li class="recent-item text-center">
|
||||
<span class="text-muted">${__('No Upcoming Events')}</span>
|
||||
</li>`;
|
||||
}
|
||||
|
||||
this.$upcoming_events.html(html);
|
||||
}
|
||||
|
||||
get_open_document_config(e) {
|
||||
this.open_docs_config = {
|
||||
ToDo: { label: __('To Do') },
|
||||
Event: { label: __('Calendar'), route: 'List/Event/Calendar' }
|
||||
};
|
||||
|
||||
let hide = $(e.currentTarget)
|
||||
.next()
|
||||
.hasClass('in');
|
||||
if (!hide) {
|
||||
frappe
|
||||
.xcall('frappe.desk.notifications.get_notification_info')
|
||||
.then(r => {
|
||||
this.open_document_list = r;
|
||||
this.render_open_document_count();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render_open_document_count() {
|
||||
this.$open_docs.html('');
|
||||
let defaults = ['ToDo'];
|
||||
this.get_counts(this.open_document_list['open_count_doctype'], 1, defaults);
|
||||
let targets = { doctypes: {} },
|
||||
map = this.open_document_list['targets'];
|
||||
|
||||
Object.keys(map).map(doctype => {
|
||||
Object.keys(map[doctype]).map(doc => {
|
||||
targets[doc] = map[doctype][doc];
|
||||
targets.doctypes[doc] = doctype;
|
||||
});
|
||||
});
|
||||
|
||||
this.get_counts(targets, 1, null, ['doctypes'], true);
|
||||
this.get_counts(
|
||||
this.open_document_list['open_count_doctype'],
|
||||
0,
|
||||
null,
|
||||
defaults
|
||||
);
|
||||
}
|
||||
|
||||
get_counts(map, divide, keys, excluded = [], target = false) {
|
||||
let empty_map = 1;
|
||||
keys = keys
|
||||
? keys
|
||||
: Object.keys(map).sort().filter(e => !excluded.includes(e));
|
||||
keys.map(key => {
|
||||
let doc_dt = map.doctypes ? map.doctypes[key] : undefined;
|
||||
if (map[key] > 0 || target) {
|
||||
this.add_open_document_html(key, map[key], doc_dt, target);
|
||||
empty_map = 0;
|
||||
}
|
||||
});
|
||||
|
||||
if (divide && !empty_map) {
|
||||
this.$open_docs.append($('<li class="divider"></li>'));
|
||||
}
|
||||
}
|
||||
|
||||
add_open_document_html(name, value, doc_dt, target = false) {
|
||||
let label = this.open_docs_config[name]
|
||||
? this.open_docs_config[name].label
|
||||
: name;
|
||||
let title = target ? `title="${__('Your Target')}"` : '';
|
||||
let $list_item = !target
|
||||
? $(`<li><a class="badge-hover" data-action="route_to_document_type" data-doctype="${name}" ${title}>
|
||||
${label}
|
||||
<span class="badge pull-right">${value}</span>
|
||||
</a></li>`)
|
||||
: $(`<li><a class="progress-small" data-action="route_to_document_type" ${title}
|
||||
data-doctype="${doc_dt}" data-docname="${name}">
|
||||
<span class="dropdown-item-label">${label}<span>
|
||||
<div class="progress-chart">
|
||||
<div class="progress">
|
||||
<div class="progress-bar" style="width: ${value}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</a></li>`);
|
||||
|
||||
this.$open_docs.append($list_item);
|
||||
if (!target) this.total += value;
|
||||
}
|
||||
|
||||
route_to_list_with_filters(doctype) {
|
||||
let filters = this.open_document_list['conditions'][doctype];
|
||||
if (filters && $.isPlainObject(filters)) {
|
||||
if (!frappe.route_options) {
|
||||
frappe.route_options = {};
|
||||
}
|
||||
$.extend(frappe.route_options, filters);
|
||||
}
|
||||
frappe.set_route('List', doctype);
|
||||
}
|
||||
|
||||
route_to_document_type(e) {
|
||||
this.$dropdown.removeClass('open');
|
||||
this.$dropdown.trigger('hide.bs.dropdown');
|
||||
let doctype = $(e.currentTarget).attr('data-doctype');
|
||||
let docname = $(e.currentTarget).attr('data-docname');
|
||||
if (!docname) {
|
||||
let config = this.open_docs_config[doctype] || {};
|
||||
if (config.route) {
|
||||
frappe.set_route(config.route);
|
||||
} else if (config.click) {
|
||||
config.click();
|
||||
} else {
|
||||
this.route_to_list_with_filters(doctype);
|
||||
}
|
||||
} else {
|
||||
frappe.set_route('Form', doctype, docname);
|
||||
}
|
||||
}
|
||||
|
||||
update_dropdown() {
|
||||
this.get_notifications_list(1).then(r => {
|
||||
let new_item = r[0];
|
||||
this.dropdown_items.unshift(new_item);
|
||||
if (this.dropdown_items.length > this.max_length) {
|
||||
this.$dropdown_list
|
||||
.find('.recent-notification')
|
||||
.last()
|
||||
.remove();
|
||||
this.dropdown_items.pop();
|
||||
}
|
||||
|
||||
this.insert_into_dropdown();
|
||||
});
|
||||
}
|
||||
|
||||
insert_into_dropdown() {
|
||||
let new_item = this.dropdown_items[0];
|
||||
let new_item_html = this.get_dropdown_item_html(new_item);
|
||||
$(new_item_html).prependTo(this.$dropdown_list.find(this.$notifications));
|
||||
this.change_activity_status();
|
||||
}
|
||||
|
||||
change_activity_status() {
|
||||
if (this.$dropdown_list.find('.activity-status')) {
|
||||
this.$dropdown_list.find('.activity-status').replaceWith(
|
||||
`<a class="recent-item text-center text-muted full-log-btn"
|
||||
href="#List/Notification Log">
|
||||
${__('View Full Log')}
|
||||
</a>`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mark_as_seen() {
|
||||
let unseen_docnames = this.dropdown_items
|
||||
.filter(item => item.seen === 0)
|
||||
.map(d => d.name);
|
||||
if (!unseen_docnames.length) return;
|
||||
frappe.call(
|
||||
'frappe.desk.doctype.notification_log.notification_log.mark_as_seen',
|
||||
{ docnames: unseen_docnames }
|
||||
);
|
||||
}
|
||||
|
||||
get_notifications_list(limit) {
|
||||
return frappe.db.get_list('Notification Log', {
|
||||
fields: ['*'],
|
||||
limit: limit,
|
||||
order_by: 'creation desc'
|
||||
});
|
||||
}
|
||||
|
||||
render_notifications_dropdown() {
|
||||
let body_html = '';
|
||||
let view_full_log_html = '';
|
||||
let dropdown_html;
|
||||
|
||||
if (this.notifications_settings && !this.notifications_settings.enabled) {
|
||||
dropdown_html = `<li class="recent-item text-center">
|
||||
<span class="text-muted">
|
||||
${__('Notifications Disabled')}
|
||||
</span></li>`;
|
||||
} else {
|
||||
if (this.dropdown_items.length) {
|
||||
this.dropdown_items.forEach(field => {
|
||||
let item_html = this.get_dropdown_item_html(field);
|
||||
if (item_html) body_html += item_html;
|
||||
});
|
||||
view_full_log_html = `<a class="recent-item text-center text-muted full-log-btn"
|
||||
href="#List/Notification Log">
|
||||
${__('View Full Log')}
|
||||
</a>`;
|
||||
} else {
|
||||
body_html += `<li class="recent-item text-center activity-status">
|
||||
<span class="text-muted">
|
||||
${__('No activity')}
|
||||
</span></li>`;
|
||||
}
|
||||
dropdown_html = body_html + view_full_log_html;
|
||||
}
|
||||
|
||||
this.$notifications.html(dropdown_html);
|
||||
}
|
||||
|
||||
get_dropdown_item_html(field) {
|
||||
let doc_link = frappe.utils.get_form_link(
|
||||
field.document_type,
|
||||
field.document_name
|
||||
);
|
||||
let seen_class = field.seen ? '' : 'unseen';
|
||||
let message = field.subject;
|
||||
let message_html = `<div class="message">${message}</div>`;
|
||||
let user = field.from_user;
|
||||
let user_avatar = frappe.avatar(user, 'avatar-small user-avatar');
|
||||
let timestamp = frappe.datetime.comment_when(field.creation, true);
|
||||
let item_html = `<a class="recent-item ${seen_class}" href = "${doc_link}">
|
||||
${user_avatar}
|
||||
${message_html}
|
||||
<div class="notification-timestamp text-muted">
|
||||
${timestamp}
|
||||
</div>
|
||||
</a>`;
|
||||
|
||||
return item_html;
|
||||
}
|
||||
|
||||
render_dropdown_headers() {
|
||||
this.categories = [
|
||||
{
|
||||
label: __('Notifications'),
|
||||
value: 'Notifications'
|
||||
},
|
||||
{
|
||||
label: __('Upcoming Events'),
|
||||
value: 'Upcoming Events',
|
||||
action: 'render_upcoming_events'
|
||||
},
|
||||
{
|
||||
label: __('Open Documents'),
|
||||
value: 'Open Documents',
|
||||
action: 'get_open_document_config'
|
||||
}
|
||||
];
|
||||
|
||||
let get_headers_html = category => {
|
||||
let category_id = frappe.dom.get_unique_id();
|
||||
let settings_html =
|
||||
category.value === 'Notifications'
|
||||
? `<span class="notification-settings pull-right" data-action="make_and_route_to_settings">
|
||||
${__('Settings')}
|
||||
</span>`
|
||||
: '';
|
||||
let html = `<li class="notifications-category">
|
||||
<li class="text-muted header"
|
||||
data-action="${category.action}"
|
||||
href="#${category_id}"
|
||||
data-toggle="collapse">
|
||||
${category.label}
|
||||
<span class="octicon octicon-chevron-down collapse-indicator"></span>
|
||||
${settings_html}
|
||||
</li>
|
||||
<div id="${category_id}" class="collapse category-list" data-category="${category.value}">
|
||||
<div class="text-center text-muted notifications-loading">
|
||||
${__('Loading...')}
|
||||
</div>
|
||||
</div>
|
||||
</li>`;
|
||||
|
||||
return html;
|
||||
};
|
||||
|
||||
let html = this.categories
|
||||
.map(get_headers_html)
|
||||
.join('<li class="divider"></li>');
|
||||
this.$dropdown_list.append(html);
|
||||
this.$dropdown_list
|
||||
.find('.category-list[data-category="Notifications"]')
|
||||
.collapse('show');
|
||||
this.toggle_collapse_indicator(
|
||||
this.$dropdown_list.find('.category-list[data-category="Notifications"]')
|
||||
);
|
||||
}
|
||||
|
||||
make_and_route_to_settings(e) {
|
||||
e.stopImmediatePropagation();
|
||||
this.$dropdown.removeClass('open');
|
||||
this.$dropdown.trigger('hide.bs.dropdown');
|
||||
let method =
|
||||
'frappe.desk.doctype.notification_settings.notification_settings.create_notification_settings';
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
if (!this.notifications_settings) return frappe.call(method);
|
||||
})
|
||||
.then(() => {
|
||||
frappe.set_route(`#Form/Notification Settings/${frappe.session.user}`);
|
||||
});
|
||||
}
|
||||
|
||||
bind_events() {
|
||||
this.setup_notification_listener();
|
||||
this.setup_dropdown_events();
|
||||
|
||||
this.$dropdown_list.on('click', '.recent-item', () => {
|
||||
this.$dropdown.removeClass('open');
|
||||
});
|
||||
|
||||
$('.category-list').on('hide.bs.collapse', e => {
|
||||
this.toggle_collapse_indicator($(e.currentTarget));
|
||||
});
|
||||
|
||||
$('.category-list').on('show.bs.collapse', e => {
|
||||
this.toggle_collapse_indicator($(e.currentTarget));
|
||||
});
|
||||
}
|
||||
|
||||
setup_notification_listener() {
|
||||
frappe.realtime.on('notification', () => {
|
||||
this.$dropdown.find('.notifications-indicator').show();
|
||||
this.update_dropdown();
|
||||
});
|
||||
}
|
||||
|
||||
setup_dropdown_events() {
|
||||
this.$dropdown_list
|
||||
.find(
|
||||
'[data-category="Notifications"], [data-category="Upcoming Events"], [data-category="Open Documents"]'
|
||||
)
|
||||
.collapse({
|
||||
toggle: false
|
||||
});
|
||||
this.$dropdown.on('hide.bs.dropdown', e => {
|
||||
this.$notification_indicator.hide();
|
||||
let hide = $(e.currentTarget).data('closable');
|
||||
if (hide) {
|
||||
this.$dropdown_list
|
||||
.find('[data-category="Notifications"]')
|
||||
.collapse('show');
|
||||
this.$dropdown_list
|
||||
.find(
|
||||
'[data-category="Upcoming Events"], [data-category="Open Documents"]'
|
||||
)
|
||||
.collapse('hide');
|
||||
}
|
||||
this.$dropdown_list.find('.unseen').removeClass('unseen');
|
||||
$(e.currentTarget).data('closable', true);
|
||||
return hide;
|
||||
});
|
||||
|
||||
this.$dropdown.on('show.bs.dropdown', () => {
|
||||
this.mark_as_seen();
|
||||
});
|
||||
|
||||
this.$dropdown.on('click', e => {
|
||||
if ($(e.target).closest('.dropdown-toggle').length) {
|
||||
$(e.currentTarget).data('closable', true);
|
||||
} else {
|
||||
$(e.currentTarget).data('closable', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toggle_collapse_indicator($el) {
|
||||
$el
|
||||
.prev()
|
||||
.find('.collapse-indicator')
|
||||
.toggleClass('octicon-chevron-down');
|
||||
$el
|
||||
.prev()
|
||||
.find('.collapse-indicator')
|
||||
.toggleClass('octicon-chevron-up');
|
||||
}
|
||||
};
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
|
||||
frappe.ui.EnergyPointsNotifications = class {
|
||||
|
||||
constructor() {
|
||||
this.$dropdown = $('.navbar').find('.dropdown-energy-points');
|
||||
this.$dropdown_list = this.$dropdown.find('.recent-points-list');
|
||||
this.$notification_indicator = this.$dropdown.find('.energy-points-notification');
|
||||
this.max_length = 20;
|
||||
this.setup_energy_points_notifications();
|
||||
}
|
||||
|
||||
setup_energy_points_notifications() {
|
||||
this.get_energy_points_list(this.max_length).then(user_points_list => {
|
||||
this.dropdown_items = user_points_list;
|
||||
this.render_energy_points_dropdown();
|
||||
this.setup_view_full_log();
|
||||
if (this.$dropdown_list.find('.unseen').length) {
|
||||
this.$notification_indicator.show();
|
||||
}
|
||||
});
|
||||
|
||||
this.bind_events();
|
||||
}
|
||||
|
||||
bind_events() {
|
||||
frappe.realtime.on('energy_points_notification', () => {
|
||||
this.$dropdown.find('.energy-points-notification').show();
|
||||
this.update_dropdown();
|
||||
});
|
||||
|
||||
this.$dropdown.on('hide.bs.dropdown', () => {
|
||||
this.$notification_indicator.hide();
|
||||
this.$dropdown_list.find('.unseen').removeClass('unseen');
|
||||
});
|
||||
|
||||
this.$dropdown.on('show.bs.dropdown', () => {
|
||||
this.check_seen();
|
||||
});
|
||||
}
|
||||
|
||||
update_dropdown() {
|
||||
this.get_energy_points_list(1).then(r => {
|
||||
let new_item = r[0];
|
||||
this.dropdown_items.unshift(new_item);
|
||||
if (this.dropdown_items.length > this.max_length) {
|
||||
this.$dropdown_list.find('.recent-points-item').last().remove();
|
||||
this.dropdown_items.pop();
|
||||
}
|
||||
this.insert_into_dropdown();
|
||||
});
|
||||
}
|
||||
|
||||
insert_into_dropdown() {
|
||||
let new_item = this.dropdown_items[0];
|
||||
let new_item_html = this.get_dropdown_item_html(new_item);
|
||||
let new_item_date_range = this.get_date_range_title(new_item.creation);
|
||||
let current_date_range = this.get_date_range_title(this.dropdown_items[1].creation);
|
||||
if (current_date_range !== new_item_date_range) {
|
||||
let $date_range = $(`<li class="points-date-range text-muted">${new_item_date_range}</li>`);
|
||||
$date_range.insertAfter(this.$dropdown_list.find('.points-updates-header'));
|
||||
$(new_item_html).insertAfter($date_range);
|
||||
} else {
|
||||
$(new_item_html).insertAfter(this.$dropdown_list.find('.points-date-range').eq(0));
|
||||
}
|
||||
}
|
||||
|
||||
check_seen() {
|
||||
let unseen_logs = this.dropdown_items.filter(item => item.seen === 0);
|
||||
frappe.call('frappe.social.doctype.energy_point_log.energy_point_log.set_notification_as_seen', {point_logs: unseen_logs});
|
||||
}
|
||||
|
||||
get_date_range_title(date) {
|
||||
let current_date = frappe.datetime.now_date();
|
||||
let prev_week = frappe.datetime.add_days(current_date, -7);
|
||||
let prev_month = frappe.datetime.add_months(frappe.datetime.now_date(), -1);
|
||||
if (date >= current_date) {
|
||||
return __('Today');
|
||||
} else if (date > prev_week) {
|
||||
return __('Last 7 days');
|
||||
} else if (date > prev_month) {
|
||||
return __('Last 30 days');
|
||||
} else {
|
||||
return __('Older');
|
||||
}
|
||||
}
|
||||
|
||||
get_energy_points_list(limit) {
|
||||
return frappe.db.get_list('Energy Point Log', {
|
||||
filters: {
|
||||
user: frappe.session.user,
|
||||
type: ['not in', ['Review']],
|
||||
},
|
||||
fields:
|
||||
['name', 'user', 'points', 'reference_doctype', 'reference_name', 'reason', 'type', 'seen', 'rule', 'owner', 'creation'],
|
||||
limit: limit,
|
||||
order_by: 'creation desc'
|
||||
}).then((energy_points_list) => {
|
||||
return energy_points_list;
|
||||
});
|
||||
}
|
||||
|
||||
render_energy_points_dropdown() {
|
||||
let header_html =
|
||||
`<li class="points-updates-header">
|
||||
<span class="points-updates-title bold text-muted">${__('Energy Points')}</span>
|
||||
<a href="#leaderboard/Energy Point Log" class="points-leaderboard text-muted hidden-xs">${__('Leaderboard')}</a>
|
||||
</li>`;
|
||||
let body_html = '';
|
||||
let view_full_log_html = '';
|
||||
|
||||
if (this.dropdown_items.length) {
|
||||
let date_range = this.get_date_range_title(this.dropdown_items[0].creation);
|
||||
body_html += `<li class="points-date-range text-muted">${date_range}</li>`;
|
||||
this.dropdown_items.forEach(field => {
|
||||
let current_field_date_range = this.get_date_range_title(field.creation);
|
||||
if (date_range !== current_field_date_range) {
|
||||
body_html += `<li class="points-date-range text-muted">${current_field_date_range}</li>`;
|
||||
date_range = current_field_date_range;
|
||||
}
|
||||
let item_html = this.get_dropdown_item_html(field);
|
||||
if (item_html) body_html += item_html;
|
||||
});
|
||||
view_full_log_html = `<li><a class="text-muted text-center full-log-btn">${__('View Full Log')}</a></li>`;
|
||||
} else {
|
||||
body_html += `<li><a href="#" onclick = "return false" class="text-muted text-center">${__('No activity')}</a></li>`;
|
||||
}
|
||||
let dropdown_html = header_html + body_html + view_full_log_html;
|
||||
this.$dropdown_list.html(dropdown_html);
|
||||
}
|
||||
|
||||
get_dropdown_item_html(field) {
|
||||
let doc_link = frappe.utils.get_form_link(field.reference_doctype, field.reference_name);
|
||||
let link_html_string = field.seen ? `<a href=${doc_link}>`: `<a href=${doc_link} class="unseen">`;
|
||||
let points_html = `<div class="points-update">${frappe.energy_points.get_points(field.points)}</div>`;
|
||||
let message_html = this.get_message_html(field);
|
||||
|
||||
let item_html = `<li class="recent-points-item">
|
||||
${link_html_string}
|
||||
${points_html}
|
||||
<div class="points-reason">
|
||||
${message_html}
|
||||
</div>
|
||||
</a>
|
||||
</li>`;
|
||||
return item_html;
|
||||
}
|
||||
|
||||
get_message_html(field) {
|
||||
let owner_name = frappe.user.full_name(field.owner).trim();
|
||||
owner_name = frappe.ellipsis(owner_name, 50);
|
||||
let message_html = '';
|
||||
let reference_doc = `
|
||||
<span class="points-reason-name text-muted">
|
||||
${field.reference_name}
|
||||
</span>
|
||||
`;
|
||||
let reason_string = `
|
||||
<span class="hidden-xs">
|
||||
- "${frappe.ellipsis(field.reason, 50)}"
|
||||
</span>
|
||||
`;
|
||||
if (field.type === 'Auto' ) {
|
||||
message_html = __('For {0} {1}',
|
||||
[field.rule, reference_doc]);
|
||||
} else {
|
||||
if (field.type === 'Appreciation') {
|
||||
message_html = __('{0} appreciated your work on {1} {2}',
|
||||
[owner_name, reference_doc, reason_string]);
|
||||
} else if (field.type === 'Criticism') {
|
||||
message_html = __('{0} criticized your work on {1} {2}',
|
||||
[owner_name, reference_doc, reason_string]);
|
||||
} else if (field.type === 'Revert') {
|
||||
message_html = __('{0} reverted your points on {1} {2}',
|
||||
[owner_name, reference_doc, reason_string]);
|
||||
}
|
||||
}
|
||||
return message_html;
|
||||
}
|
||||
|
||||
setup_view_full_log() {
|
||||
this.$dropdown_list.find('.full-log-btn').on('click', () => {
|
||||
frappe.set_route('List', 'Energy Point Log', {user: frappe.session.user});
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -68,36 +68,19 @@
|
|||
<li class="frappe-chat-dropdown"></li>
|
||||
<!-- end frappe.chat -->
|
||||
|
||||
<li class="dropdown dropdown-energy-points dropdown-mobile">
|
||||
<span class="energy-points-notification"><i class="fa fa-circle"></i></span>
|
||||
<li class="dropdown dropdown-notifications dropdown-mobile">
|
||||
<span class="notifications-indicator"><i class="fa fa-circle"></i></span>
|
||||
<a
|
||||
class="dropdown-toggle energy-points-icon"
|
||||
class="dropdown-toggle notifications-icon"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="true"
|
||||
href="#"
|
||||
onclick="return false;">
|
||||
<span><i class="fa fa-trophy"></i></span>
|
||||
<span><i class="fa fa-bell" aria-hidden="true"></i></span>
|
||||
</a>
|
||||
<ul
|
||||
class="dropdown-menu recent-points-list"
|
||||
role="menu">
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="dropdown dropdown-navbar-new-comments dropdown-mobile">
|
||||
<a
|
||||
class="btn dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="true"
|
||||
href="#"
|
||||
onclick="return false;">
|
||||
<span class="navbar-new-comments">0</span>
|
||||
</a>
|
||||
<ul
|
||||
class="dropdown-menu"
|
||||
id="dropdown-notification"
|
||||
class="dropdown-menu notifications-list"
|
||||
role="menu">
|
||||
</ul>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -1,103 +0,0 @@
|
|||
frappe.provide("frappe.ui.notifications");
|
||||
|
||||
frappe.ui.notifications = {
|
||||
config: {
|
||||
"ToDo": { label: __("To Do") },
|
||||
"Event": { label: __("Calendar"), route: "List/Event/Calendar" },
|
||||
"Email": { label: __("Email"), route: "List/Communication/Inbox" }
|
||||
},
|
||||
|
||||
update_notifications: function() {
|
||||
this.total = 0;
|
||||
this.dropdown = $("#dropdown-notification").empty();
|
||||
this.boot_info = frappe.boot.notification_info;
|
||||
let defaults = ["Comment", "ToDo", "Event"];
|
||||
|
||||
this.get_counts(this.boot_info.open_count_doctype, 1, defaults);
|
||||
this.get_counts(this.boot_info.open_count_other, 1);
|
||||
|
||||
// Target counts are stored for docs per doctype
|
||||
let targets = { doctypes : {} }, map = this.boot_info.targets;
|
||||
Object.keys(map).map(doctype => {
|
||||
Object.keys(map[doctype]).map(doc => {
|
||||
targets[doc] = map[doctype][doc];
|
||||
targets.doctypes[doc] = doctype;
|
||||
});
|
||||
});
|
||||
this.get_counts(targets, 1, null, ["doctypes"], true);
|
||||
this.get_counts(this.boot_info.open_count_doctype,
|
||||
0, null, defaults);
|
||||
|
||||
this.bind_list();
|
||||
|
||||
// switch colour on the navbar and disable if no notifications
|
||||
$(".navbar-new-comments")
|
||||
.html(this.total > 99 ? '99+' : this.total)
|
||||
.toggleClass("navbar-new-comments-true", this.total ? true : false)
|
||||
.parent().toggleClass("disabled", this.total ? false : true);
|
||||
},
|
||||
|
||||
get_counts: function(map, divide, keys, excluded = [], target = false) {
|
||||
let empty_map = 1;
|
||||
keys = keys ? keys
|
||||
: Object.keys(map).sort().filter(e => !excluded.includes(e));
|
||||
keys.map(key => {
|
||||
let doc_dt = (map.doctypes) ? map.doctypes[key] : undefined;
|
||||
if(map[key] > 0 || target) {
|
||||
this.add_notification(key, map[key], doc_dt, target);
|
||||
empty_map = 0;
|
||||
}
|
||||
});
|
||||
if(divide && !empty_map) {
|
||||
this.dropdown.append($('<li class="divider"></li>'));
|
||||
}
|
||||
},
|
||||
|
||||
add_notification: function(name, value, doc_dt, target = false) {
|
||||
let label = this.config[name] ? this.config[name].label : name;
|
||||
let title = target ? `title="Your Target"` : '';
|
||||
let $list_item = !target
|
||||
? $(`<li><a class="badge-hover" href="#" onclick="return false;" data-doctype="${name}" ${title}>${__(label)}
|
||||
<span class="badge pull-right">${value}</span>
|
||||
</a></li>`)
|
||||
: $(`<li><a class="progress-small" href="#" onclick="return false;" ${title} data-doctype="${doc_dt}"
|
||||
data-doc="${name}"><span class="dropdown-item-label">${__(label)}<span>
|
||||
<div class="progress-chart"><div class="progress">
|
||||
<div class="progress-bar" style="width: ${value}%"></div>
|
||||
</div></div>
|
||||
</a></li>`);
|
||||
this.dropdown.append($list_item);
|
||||
if(!target) this.total += value;
|
||||
},
|
||||
|
||||
bind_list: function() {
|
||||
var me = this;
|
||||
$("#dropdown-notification a").on("click", function() {
|
||||
var doctype = $(this).attr("data-doctype");
|
||||
var doc = $(this).attr("data-doc");
|
||||
if(!doc) {
|
||||
var config = me.config[doctype] || {};
|
||||
if (config.route) {
|
||||
frappe.set_route(config.route);
|
||||
} else if (config.click) {
|
||||
config.click();
|
||||
} else {
|
||||
frappe.ui.notifications.show_open_count_list(doctype);
|
||||
}
|
||||
} else {
|
||||
frappe.set_route("Form", doctype, doc);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
show_open_count_list: function(doctype) {
|
||||
let filters = this.boot_info.conditions[doctype];
|
||||
if(filters && $.isPlainObject(filters)) {
|
||||
if (!frappe.route_options) {
|
||||
frappe.route_options = {};
|
||||
}
|
||||
$.extend(frappe.route_options, filters);
|
||||
}
|
||||
frappe.set_route("List", doctype);
|
||||
},
|
||||
};
|
||||
|
|
@ -15,8 +15,7 @@ frappe.ui.toolbar.Toolbar = Class.extend({
|
|||
awesome_bar.setup("#navbar-search");
|
||||
awesome_bar.setup("#modal-search");
|
||||
|
||||
this.setup_energy_point_notifications();
|
||||
|
||||
this.setup_notifications();
|
||||
this.make();
|
||||
},
|
||||
|
||||
|
|
@ -30,10 +29,6 @@ frappe.ui.toolbar.Toolbar = Class.extend({
|
|||
},
|
||||
|
||||
bind_events: function() {
|
||||
$(document).on("notification-update", function() {
|
||||
frappe.ui.notifications.update_notifications();
|
||||
});
|
||||
|
||||
// clear all custom menus on page change
|
||||
$(document).on("page-change", function() {
|
||||
$("header .navbar .custom-menu").remove();
|
||||
|
|
@ -161,13 +156,8 @@ frappe.ui.toolbar.Toolbar = Class.extend({
|
|||
}
|
||||
},
|
||||
|
||||
setup_energy_point_notifications: function() {
|
||||
if (frappe.boot.energy_points_enabled) {
|
||||
$('.dropdown-energy-points').show();
|
||||
this.energy_points_notifications = new frappe.ui.EnergyPointsNotifications();
|
||||
} else {
|
||||
$('.dropdown-energy-points').hide();
|
||||
}
|
||||
setup_notifications: function() {
|
||||
this.notifications = new frappe.ui.Notifications();
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
:key="section.label + item.label"
|
||||
:data-youtube-id="item.type==='help' ? item.youtube_id : false"
|
||||
v-bind="item"
|
||||
:open_count="item.type==='doctype' ? frappe.boot.notification_info.open_count_doctype[item.doctype] : false"
|
||||
>
|
||||
</module-link-item>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -262,7 +262,6 @@ frappe.views.InteractionComposer = class InteractionComposer {
|
|||
doctype: doc.doctype,
|
||||
name: doc.name,
|
||||
assign_to: assignee,
|
||||
notify: 1
|
||||
},
|
||||
callback:function(r) {
|
||||
if(!r.exc) {
|
||||
|
|
|
|||
|
|
@ -276,56 +276,3 @@
|
|||
.navbar-default .navbar-brand {
|
||||
color: @text-muted;
|
||||
}
|
||||
|
||||
.dropdown-energy-points .energy-points-icon {
|
||||
height: 40px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.recent-points-list {
|
||||
width: 300px;
|
||||
max-height: 480px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.energy-points-notification {
|
||||
font-size: 7px;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 8px;
|
||||
color: @indicator-orange;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.points-update {
|
||||
float: left;
|
||||
text-align: right;
|
||||
margin-right: 15px;
|
||||
width: 7%;
|
||||
}
|
||||
|
||||
.points-reason {
|
||||
display: flow-root;
|
||||
}
|
||||
|
||||
.recent-points-list .points-updates-header .points-leaderboard {
|
||||
float: right;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.points-updates-header {
|
||||
background: @navbar-bg;
|
||||
padding: 10px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.points-date-range {
|
||||
padding: 10px 0 2px 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.unseen {
|
||||
background: @light-yellow;
|
||||
}
|
||||
126
frappe/public/less/notifications.less
Normal file
126
frappe/public/less/notifications.less
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
@import "variables.less";
|
||||
@import "mixins.less";
|
||||
|
||||
|
||||
.dropdown-notifications .header {
|
||||
margin: 7px 15px 10px 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.notification-settings {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.collapse-indicator {
|
||||
padding: 0px 5px;
|
||||
color: #d1d8dd;
|
||||
}
|
||||
|
||||
.category-list[data-category="Open Documents"] li a {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
padding: 14px;
|
||||
clear: both;
|
||||
font-weight: normal;
|
||||
line-height: 1.42857143;
|
||||
color: #333;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.category-list[data-category="Open Documents"] li a:hover {
|
||||
background-color: #f0f4f7;
|
||||
}
|
||||
|
||||
.open-doc-count {
|
||||
margin-left: 150px;
|
||||
}
|
||||
|
||||
.notifications-indicator {
|
||||
font-size: 7px;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 8px;
|
||||
color: @indicator-orange;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropdown-notifications .recent-item {
|
||||
padding: 10px 14px;
|
||||
white-space: normal;
|
||||
text-decoration: none;
|
||||
font-weight: normal;
|
||||
display: flow-root;
|
||||
}
|
||||
|
||||
a.recent-item:hover {
|
||||
background-color: #f0f4f7;
|
||||
}
|
||||
|
||||
.dropdown-energy-points .energy-points-icon {
|
||||
height: 40px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.notifications-list {
|
||||
width: 450px;
|
||||
max-height: 480px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.energy-points-notification {
|
||||
font-size: 7px;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 8px;
|
||||
color: @indicator-orange;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.date-range {
|
||||
padding: 10px 0 2px 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.unseen {
|
||||
background: @light-yellow;
|
||||
}
|
||||
|
||||
.notification-timestamp {
|
||||
margin-top: 5px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.event-time {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.notifications-loading {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
|
||||
.dropdown-notifications {
|
||||
.notifications-list {
|
||||
max-height: 100vh;
|
||||
min-width: 100vw;
|
||||
}
|
||||
|
||||
.category-list[data-category="Open Documents"] li a {
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.recent-item .user-avatar {
|
||||
margin-right: 10px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -112,9 +112,8 @@ def clear_expired_sessions():
|
|||
delete_session(sid, reason="Session Expired")
|
||||
|
||||
def get():
|
||||
|
||||
"""get session boot info"""
|
||||
from frappe.desk.notifications import \
|
||||
get_notification_info_for_boot, get_notifications
|
||||
from frappe.boot import get_bootinfo, get_unseen_notes
|
||||
|
||||
bootinfo = None
|
||||
|
|
@ -123,14 +122,12 @@ def get():
|
|||
bootinfo = frappe.cache().hget("bootinfo", frappe.session.user)
|
||||
if bootinfo:
|
||||
bootinfo['from_cache'] = 1
|
||||
bootinfo["notification_info"].update(get_notifications())
|
||||
bootinfo["user"]["recent"] = json.dumps(\
|
||||
frappe.cache().hget("user_recent", frappe.session.user))
|
||||
|
||||
if not bootinfo:
|
||||
# if not create it
|
||||
bootinfo = get_bootinfo()
|
||||
bootinfo["notification_info"] = get_notification_info_for_boot()
|
||||
frappe.cache().hset("bootinfo", frappe.session.user, bootinfo)
|
||||
try:
|
||||
frappe.cache().ping()
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
from frappe import _
|
||||
from frappe.desk.form.document_follow import follow_document
|
||||
from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification
|
||||
from frappe.utils import cint
|
||||
|
||||
@frappe.whitelist()
|
||||
def add(doctype, name, user=None, read=1, write=0, share=0, everyone=0, flags=None, notify=0):
|
||||
def add(doctype, name, user=None, read=1, write=0, share=0, everyone=0, flags=None):
|
||||
"""Share the given document with a user."""
|
||||
if not user:
|
||||
user = frappe.session.user
|
||||
|
|
@ -40,7 +41,7 @@ def add(doctype, name, user=None, read=1, write=0, share=0, everyone=0, flags=No
|
|||
})
|
||||
|
||||
doc.save(ignore_permissions=True)
|
||||
notify_assignment(user, doctype, name, description=None, notify=notify)
|
||||
notify_assignment(user, doctype, name, everyone)
|
||||
|
||||
follow_document(doctype, name, user)
|
||||
|
||||
|
|
@ -145,16 +146,26 @@ def check_share_permission(doctype, name):
|
|||
if not frappe.has_permission(doctype, ptype="share", doc=name):
|
||||
frappe.throw(_("No permission to {0} {1} {2}".format("share", doctype, name)), frappe.PermissionError)
|
||||
|
||||
def notify_assignment(shared_by, doc_type, doc_name, description=None, notify=0):
|
||||
def notify_assignment(shared_by, doctype, doc_name, everyone):
|
||||
|
||||
if not (shared_by and doc_type and doc_name): return
|
||||
if not (shared_by and doctype and doc_name) or everyone: return
|
||||
|
||||
from frappe.utils import get_link_to_form
|
||||
document = get_link_to_form(doc_type, doc_name, label="%s: %s" % (doc_type, doc_name))
|
||||
from frappe.utils import get_fullname
|
||||
|
||||
arg = {
|
||||
'contact': shared_by,
|
||||
'txt': _("A new document {0} has been shared by with you {1}.").format(document,
|
||||
shared_by),
|
||||
'notify': notify
|
||||
}
|
||||
title_field = frappe.get_meta(doctype).get_title_field()
|
||||
title = doc_name if title_field == "name" else \
|
||||
frappe.db.get_value(doctype, doc_name, title_field)
|
||||
|
||||
reference_user = get_fullname(frappe.session.user)
|
||||
notification_message = _('{0} shared a document {1} {2} with you').format(
|
||||
frappe.bold(reference_user), frappe.bold(doctype), frappe.bold(title))
|
||||
|
||||
notification_doc = {
|
||||
'type': 'Share',
|
||||
'document_type': doctype,
|
||||
'subject': notification_message,
|
||||
'document_name': doc_name,
|
||||
'from_user': frappe.session.user
|
||||
}
|
||||
|
||||
enqueue_create_notification(shared_by, notification_doc)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import frappe
|
|||
from frappe import _
|
||||
import json
|
||||
from frappe.model.document import Document
|
||||
from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification
|
||||
from frappe.utils import cint, get_fullname, getdate, get_link_to_form
|
||||
|
||||
class EnergyPointLog(Document):
|
||||
|
|
@ -25,13 +26,58 @@ class EnergyPointLog(Document):
|
|||
alert_dict = get_alert_dict(self)
|
||||
if alert_dict:
|
||||
frappe.publish_realtime('energy_point_alert', message=alert_dict, user=self.user)
|
||||
send_review_mail(self, alert_dict)
|
||||
|
||||
frappe.cache().hdel('energy_points', self.user)
|
||||
frappe.publish_realtime('update_points', after_commit=True)
|
||||
|
||||
if self.type != 'Review':
|
||||
frappe.publish_realtime('energy_points_notification', after_commit=True, user=self.user)
|
||||
reference_user = self.user if self.type == 'Auto' else self.owner
|
||||
notification_doc = {
|
||||
'type': 'Energy Point',
|
||||
'document_type': self.reference_doctype,
|
||||
'document_name': self.reference_name,
|
||||
'subject': get_notification_message(self),
|
||||
'from_user': reference_user,
|
||||
'email_content': '<div>{}</div>'.format(self.reason)
|
||||
}
|
||||
|
||||
enqueue_create_notification(self.user, notification_doc)
|
||||
|
||||
def get_notification_message(doc):
|
||||
owner_name = get_fullname(doc.owner)
|
||||
points = doc.points
|
||||
title_field = frappe.get_meta(doc.reference_doctype).get_title_field()
|
||||
title = doc.reference_name if title_field == "name" else \
|
||||
frappe.db.get_value(doc.reference_doctype, doc.reference_name, title_field)
|
||||
|
||||
if doc.type == 'Auto':
|
||||
owner_name = frappe.bold('You')
|
||||
if points == 1:
|
||||
message = _('{0} gained {1} point for {2} {3}')
|
||||
else:
|
||||
message = _('{0} gained {1} points for {2} {3}')
|
||||
message = message.format(owner_name, frappe.bold(points), doc.rule, frappe.bold(title))
|
||||
elif doc.type == 'Appreciation':
|
||||
if points == 1:
|
||||
message = _('{0} appreciated your work on {1} with {2} point')
|
||||
else:
|
||||
message = _('{0} appreciated your work on {1} with {2} points')
|
||||
message = message.format(frappe.bold(owner_name), frappe.bold(title), frappe.bold(points))
|
||||
elif doc.type == 'Criticism':
|
||||
if points == 1:
|
||||
message = _('{0} criticized your work on {1} with {2} point')
|
||||
else:
|
||||
message = _('{0} criticized your work on {1} with {2} points')
|
||||
|
||||
message = message.format(frappe.bold(owner_name), frappe.bold(title), frappe.bold(points))
|
||||
elif doc.type == 'Revert':
|
||||
if points == 1:
|
||||
message = _('{0} reverted your point on {1}')
|
||||
else:
|
||||
message = _('{0} reverted your points on {1}')
|
||||
message = message.format(frappe.bold(owner_name), frappe.bold(title))
|
||||
|
||||
return message
|
||||
|
||||
def get_alert_dict(doc):
|
||||
alert_dict = frappe._dict()
|
||||
|
|
@ -83,13 +129,6 @@ def get_alert_dict(doc):
|
|||
|
||||
return alert_dict
|
||||
|
||||
def send_review_mail(doc, message_dict):
|
||||
if doc.type in ['Appreciation', 'Criticism']:
|
||||
frappe.sendmail(recipients=doc.user,
|
||||
subject=_("You gained some energy points") if doc.points > 0 else _("You lost some energy points"),
|
||||
message=message_dict.message + '<p>{}</p>'.format(doc.reason),
|
||||
header=[_('Energy point update'), message_dict.indicator])
|
||||
|
||||
def create_energy_points_log(ref_doctype, ref_name, doc):
|
||||
doc = frappe._dict(doc)
|
||||
log_exists = frappe.db.exists('Energy Point Log', {
|
||||
|
|
@ -173,13 +212,6 @@ def get_user_energy_and_review_points(user=None, from_date=None, as_dict=True):
|
|||
dict_to_return[d.pop('user')] = d
|
||||
return dict_to_return
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_notification_as_seen(point_logs):
|
||||
point_logs = frappe.parse_json(point_logs)
|
||||
for log in point_logs:
|
||||
frappe.db.set_value('Energy Point Log', log['name'], 'seen', 1, update_modified=False)
|
||||
|
||||
@frappe.whitelist()
|
||||
def review(doc, points, to_user, reason, review_type='Appreciation'):
|
||||
current_review_points = get_energy_points(frappe.session.user).review_points
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
<p>
|
||||
{{ body_content }}
|
||||
</p>
|
||||
<blockquote
|
||||
style="border-left: 3px solid #d1d8dd; padding: 7px 15px; margin-left: 0px;">
|
||||
{{ comment.content | markdown }}
|
||||
</blockquote>
|
||||
13
frappe/templates/emails/new_notification.html
Normal file
13
frappe/templates/emails/new_notification.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<p>
|
||||
<p class="text-color">{{ body_content }}</p>
|
||||
</p>
|
||||
{% if description %}
|
||||
<blockquote
|
||||
style="border-left: 3px solid #d1d8dd; padding: 7px 15px; margin-left: 0px;">
|
||||
{{ description | markdown }}
|
||||
</blockquote>
|
||||
{% endif %}
|
||||
<div class="more-info">
|
||||
<a href="{{ doc_link }}">{{ _("Login and view in Browser") }}</a>
|
||||
</div>
|
||||
|
||||
Loading…
Add table
Reference in a new issue