fix(Notifications): Improvements for Notifications (#8671)

* fix: seen notification logic

* fix: rename upcoming events to today's events, fix padding

* fix: truncate title

* fix: don't allow self notifications

* fix: don't check for same  user for energy point notifications

* fix: codacy

* fix: truncate title client side

* fix: use data-action to mark as seen

* fix: don't send emails for auto energy points
This commit is contained in:
Prssanna Desai 2019-10-28 21:43:40 +05:30 committed by mergify[bot]
parent a21b5bde75
commit 70f49546c1
7 changed files with 126 additions and 85 deletions

View file

@ -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.desk.doctype.notification_log.notification_log import enqueue_create_notification
from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification,\
get_title, get_title_html
from frappe.utils import get_fullname
from frappe.website.render import clear_cache
from frappe.database.schema import add_column
@ -51,15 +52,13 @@ class Comment(Document):
return
sender_fullname = get_fullname(frappe.session.user)
title_field = frappe.get_meta(self.reference_doctype).get_title_field()
title = self.reference_name if title_field == "name" else \
frappe.db.get_value(self.reference_doctype, self.reference_name, title_field)
title = get_title(self.reference_doctype, self.reference_name)
recipients = [frappe.db.get_value("User", {"enabled": 1, "name": name, "user_type": "System User", "allowed_in_mentions": 1}, "email")
for name in mentions]
notification_message = _('''{0} mentioned you in a comment in {1} {2}''')\
.format(frappe.bold(sender_fullname), frappe.bold(self.reference_doctype), frappe.bold(title))
.format(frappe.bold(sender_fullname), frappe.bold(self.reference_doctype), get_title_html(title))
notification_doc = {
'type': 'Mention',

View file

@ -50,13 +50,17 @@ def make_notification_logs(doc, users):
_doc.update(doc)
_doc.for_user = user
_doc.subject = _doc.subject.replace('<div>', '').replace('</div>', '')
_doc.insert(ignore_permissions=True)
if _doc.for_user != _doc.from_user or doc.type == 'Energy Point':
_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
if doc.type == 'Energy Point' and doc.email_content is None:
return
from frappe.utils import get_url_to_form, strip_html
doc_link = get_url_to_form(doc.document_type, doc.document_name)
@ -88,10 +92,22 @@ def get_email_header(doc):
}[doc.type or 'Default']
def get_title(doctype, docname, title_field=None):
if not title_field:
title_field = frappe.get_meta(doctype).get_title_field()
title = docname if title_field == "name" else \
frappe.db.get_value(doctype, docname, title_field)
return title
def get_title_html(title):
return '<b class="subject-title">{0}</b>'.format(title)
@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)
frappe.publish_realtime('seen_notification', after_commit=True, user=frappe.session.user)
def mark_as_seen(docname):
if docname:
frappe.db.set_value('Notification Log', docname, 'seen', 1, update_modified=False)
@frappe.whitelist()
def trigger_indicator_hide():
frappe.publish_realtime('indicator_hide', user=frappe.session.user)

View file

@ -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.desk.doctype.notification_log.notification_log import enqueue_create_notification
from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification,\
get_title, get_title_html
import frappe.utils
import frappe.share
@ -160,17 +161,15 @@ def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE',
# Search for email address in description -- i.e. assignee
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)
title = get_title(doc_type, doc_name)
description_html = "<div>{0}</div>".format(description) if description else None
if action=='CLOSE':
subject = _('Your assignment on {0} {1} has been removed').format(frappe.bold(doc_type), frappe.bold(title))
subject = _('Your assignment on {0} {1} has been removed').format(frappe.bold(doc_type), get_title_html(title))
else:
user_name = frappe.bold(user_name)
document_type = frappe.bold(doc_type)
title = frappe.bold(title)
title = get_title_html(title)
subject = _('{0} assigned a new task {1} {2} to you').format(user_name, document_type, title)
notification_doc = {

View file

@ -24,8 +24,8 @@ frappe.ui.Notifications = class 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"]'
this.$today_events = this.$dropdown_list.find(
'.category-list[data-category="Todays Events"]'
);
frappe.utils.bind_actions_with_object(this.$dropdown_list, this);
@ -44,18 +44,13 @@ frappe.ui.Notifications = class Notifications {
});
}
render_upcoming_events(e, $target) {
render_todays_events(e, $target) {
let hide = $target.next().hasClass('in');
if (!hide) {
let today = frappe.datetime.get_today();
let tomorrow = frappe.datetime.add_days(today, 1);
frappe.db.get_list('Event', {
fields: ['name', 'subject', 'starts_on'],
filters: [
{'starts_on': ['between', today, tomorrow]},
{'ends_on': ['>=', frappe.datetime.now_datetime()]},
{'owner': frappe.session.user}
]
frappe.xcall('frappe.desk.doctype.event.event.get_events', {
start: today,
end: today
}).then(event_list => {
this.render_events_html(event_list);
});
@ -75,11 +70,11 @@ frappe.ui.Notifications = class Notifications {
html = event_list.map(get_event_html).join('');
} else {
html = `<li class="recent-item text-center">
<span class="text-muted">${__('No Upcoming Events')}</span>
<span class="text-muted">${__('No Events Today')}</span>
</li>`;
}
this.$upcoming_events.html(html);
this.$today_events.html(html);
}
get_open_document_config(e) {
@ -222,23 +217,34 @@ frappe.ui.Notifications = class Notifications {
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"
`<a class="recent-item text-center text-muted"
href="#List/Notification Log">
${__('View Full Log')}
<div class="full-log-btn">${__('View Full Log')}</div>
</a>`
);
}
}
mark_as_seen() {
let unseen_docnames = this.dropdown_items
.filter(item => item.seen === 0)
.map(d => d.name);
if (!unseen_docnames.length) return;
set_field_as_seen(docname, $el) {
frappe.call(
'frappe.desk.doctype.notification_log.notification_log.mark_as_seen',
{ docnames: unseen_docnames }
);
{ docname: docname }
).then(()=> {
$el.removeClass('unseen');
});
}
explicitly_mark_as_seen(e, $target) {
e.preventDefault();
e.stopImmediatePropagation();
let docname = $target.parents('.unseen').attr('data-name');
this.set_field_as_seen(docname, $target.parents('.unseen'));
}
mark_as_seen(e, $target) {
let docname = $target.attr('data-name');
let df = this.dropdown_items.filter(f => docname.includes(f.name))[0];
this.set_field_as_seen(df.name, $target);
}
get_notifications_list(limit) {
@ -265,9 +271,9 @@ frappe.ui.Notifications = class Notifications {
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"
view_full_log_html = `<a class="recent-item text-center text-muted"
href="#List/Notification Log">
${__('View Full Log')}
<div class="full-log-btn">${__('View Full Log')}</div>
</a>`;
} else {
body_html += `<li class="recent-item text-center activity-status">
@ -287,18 +293,29 @@ frappe.ui.Notifications = class Notifications {
field.document_name
);
let seen_class = field.seen ? '' : 'unseen';
let mark_seen_action = field.seen ? '': 'data-action="mark_as_seen"';
let message = field.subject;
let title = message.match(/<b class="subject-title">(.*?)<\/b>/);
message = title ? message.replace(title[1], frappe.ellipsis(title[1], 100)): message;
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}">
let item_html =
`<a class="recent-item ${seen_class}"
href="${doc_link}"
data-name="${field.name}"
${mark_seen_action}
>
${user_avatar}
${message_html}
<div class="notification-timestamp text-muted">
${timestamp}
</div>
</a>`;
<span class="mark-read text-muted" data-action="explicitly_mark_as_seen">
${__('Mark as Read')}
</span>
</a>`;
return item_html;
}
@ -306,18 +323,18 @@ frappe.ui.Notifications = class Notifications {
render_dropdown_headers() {
this.categories = [
{
label: __('Notifications'),
value: 'Notifications'
label: __("Notifications"),
value: "Notifications"
},
{
label: __('Upcoming Events'),
value: 'Upcoming Events',
action: 'render_upcoming_events'
label: __("Today's Events"),
value: "Todays Events",
action: "render_todays_events"
},
{
label: __('Open Documents'),
value: 'Open Documents',
action: 'get_open_document_config'
label: __("Open Documents"),
value: "Open Documents",
action: "get_open_document_config"
}
];
@ -377,8 +394,8 @@ frappe.ui.Notifications = class Notifications {
}
bind_events() {
this.setup_notification_listeners();
this.setup_dropdown_events();
this.setup_notification_listeners();
this.$dropdown_list.on('click', '.recent-item', () => {
this.$dropdown.removeClass('open');
@ -399,7 +416,7 @@ frappe.ui.Notifications = class Notifications {
this.update_dropdown();
});
frappe.realtime.on('seen_notification', () => {
frappe.realtime.on('indicator_hide', () => {
this.$dropdown.find('.notifications-indicator').hide();
});
}
@ -407,7 +424,7 @@ frappe.ui.Notifications = class Notifications {
setup_dropdown_events() {
this.$dropdown_list
.find(
'[data-category="Notifications"], [data-category="Upcoming Events"], [data-category="Open Documents"]'
'[data-category="Notifications"], [data-category="Todays Events"], [data-category="Open Documents"]'
)
.collapse({
toggle: false
@ -420,17 +437,20 @@ frappe.ui.Notifications = class Notifications {
.collapse('show');
this.$dropdown_list
.find(
'[data-category="Upcoming Events"], [data-category="Open Documents"]'
'[data-category="Todays 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();
if (this.$notification_indicator.is(':visible')) {
frappe.call(
'frappe.desk.doctype.notification_log.notification_log.trigger_indicator_hide'
);
}
});
this.$dropdown.on('click', e => {

View file

@ -52,14 +52,30 @@
display: flow-root;
}
.category-list[data-category="Notifications"] .recent-item {
padding: 15px 14px 0 14px;
}
.full-log-btn {
padding-bottom: 10px;
}
a.recent-item:hover {
background-color: #f0f4f7;
}
.dropdown-energy-points .energy-points-icon {
height: 40px;
font-size: 14px;
text-align: center;
a.unseen:hover .mark-read {
display: inline-block;
}
.mark-read {
display: none;
margin-left: 10px;
font-size: 11px;
}
.mark-read:hover {
text-decoration: underline;
}
.notifications-list {
@ -68,15 +84,6 @@ a.recent-item:hover {
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;
@ -89,6 +96,8 @@ a.recent-item:hover {
.notification-timestamp {
margin-top: 5px;
font-size: 11px;
display: inline-block;
margin-bottom: 10px;
}
.user-avatar {

View file

@ -5,7 +5,8 @@ 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.desk.doctype.notification_log.notification_log import enqueue_create_notification,\
get_title, get_title_html
from frappe.utils import cint
@frappe.whitelist()
@ -152,13 +153,11 @@ def notify_assignment(shared_by, doctype, doc_name, everyone):
from frappe.utils import get_fullname
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)
title = get_title(doctype, doc_name)
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))
frappe.bold(reference_user), frappe.bold(doctype), get_title_html(title))
notification_doc = {
'type': 'Share',

View file

@ -7,7 +7,8 @@ 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.desk.doctype.notification_log.notification_log import enqueue_create_notification,\
get_title, get_title_html
from frappe.utils import cint, get_fullname, getdate, get_link_to_form
class EnergyPointLog(Document):
@ -38,7 +39,7 @@ class EnergyPointLog(Document):
'document_name': self.reference_name,
'subject': get_notification_message(self),
'from_user': reference_user,
'email_content': '<div>{}</div>'.format(self.reason)
'email_content': '<div>{}</div>'.format(self.reason) if self.reason else None
}
enqueue_create_notification(self.user, notification_doc)
@ -46,9 +47,7 @@ class EnergyPointLog(Document):
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)
title = get_title(doc.reference_doctype, doc.reference_name)
if doc.type == 'Auto':
owner_name = frappe.bold('You')
@ -56,26 +55,26 @@ def get_notification_message(doc):
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))
message = message.format(owner_name, frappe.bold(points), doc.rule, get_title_html(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))
message = message.format(frappe.bold(owner_name), get_title_html(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))
message = message.format(frappe.bold(owner_name), get_title_html(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))
message = message.format(frappe.bold(owner_name), get_title_html(title))
return message