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:
parent
a21b5bde75
commit
70f49546c1
7 changed files with 126 additions and 85 deletions
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue