diff --git a/frappe/chat/doctype/chat_message/chat_message.py b/frappe/chat/doctype/chat_message/chat_message.py index 3c58a6c07d..06127b3148 100644 --- a/frappe/chat/doctype/chat_message/chat_message.py +++ b/frappe/chat/doctype/chat_message/chat_message.py @@ -39,7 +39,7 @@ def get_message_urls(content): urls.append(text) return urls - + def get_message_mentions(content): mentions = [ ] tokens = content.split(' ') @@ -68,12 +68,12 @@ def get_message_meta(content): meta.content = content meta.urls = get_message_urls(content) meta.mentions = get_message_mentions(content) - + return meta def sanitize_message_content(content): emojis = get_emojis() - + tokens = content.split(' ') for token in tokens: if token.startswith(':') and token.endswith(':'): @@ -131,7 +131,7 @@ def get_new_chat_message(user, room, content, type = "Content"): @frappe.whitelist(allow_guest = True) def send(user, room, content, type = "Content"): mess = get_new_chat_message(user, room, content, type) - + frappe.publish_realtime('frappe.chat.message:create', mess, room = room, after_commit = True) @@ -144,7 +144,7 @@ def seen(message, user = None): room = mess.room resp = dict(message = message, data = dict(seen = json.loads(mess._seen))) - + frappe.publish_realtime('frappe.chat.message:update', resp, room = room, after_commit = True) def history(room, fields = None, limit = 10, start = None, end = None): @@ -159,7 +159,7 @@ def history(room, fields = None, limit = 10, start = None, end = None): ], order_by = 'creation' ) - + if not fields or 'seen' in fields: for m in mess: m['seen'] = json.loads(m._seen) if m._seen else [ ] @@ -168,8 +168,26 @@ def history(room, fields = None, limit = 10, start = None, end = None): for m in mess: m['content'] = json.loads(m.content) if m.type in ["File"] else m.content + frappe.enqueue('frappe.chat.doctype.chat_message.chat_message.mark_messages_as_seen', + message_names=[m.name for m in mess], user=frappe.session.user) + return mess +def mark_messages_as_seen(message_names, user): + ''' + Marks chat messages as seen, updates the _seen for each message + (should be run in background process) + ''' + for name in message_names: + seen = frappe.db.get_value('Chat Message', name, '_seen') or '[]' + seen = json.loads(seen) + seen.append(user) + seen = json.dumps(seen) + frappe.db.set_value('Chat Message', name, '_seen', seen, update_modified=False) + + frappe.db.commit() + + @frappe.whitelist() def get(name, rooms = None, fields = None): rooms, fields = safe_json_loads(rooms, fields) diff --git a/frappe/core/doctype/report/test_report.py b/frappe/core/doctype/report/test_report.py index 1689d99716..5591035c08 100644 --- a/frappe/core/doctype/report/test_report.py +++ b/frappe/core/doctype/report/test_report.py @@ -53,3 +53,18 @@ class TestReport(unittest.TestCase): report = frappe.get_doc('Report', 'Test Report') self.assertNotEquals(report.is_permitted(), True) + + # test for the `_format` method if report data doesn't have sort_by parameter + def test_format_method(self): + if frappe.db.exists('Report', 'User Activity Report Without Sort'): + frappe.delete_doc('Report', 'User Activity Report Without Sort') + with open(os.path.join(os.path.dirname(__file__), 'user_activity_report_without_sort.json'), 'r') as f: + frappe.get_doc(json.loads(f.read())).insert() + + report = frappe.get_doc('Report', 'User Activity Report Without Sort') + # this would raise an error without the fix added along with this test case + columns, data = report.get_data() + self.assertEqual(columns[0].get('label'), 'ID') + self.assertEqual(columns[1].get('label'), 'User Type') + self.assertTrue('Administrator' in [d[0] for d in data]) + frappe.delete_doc('Report', 'User Activity Report Without Sort') diff --git a/frappe/core/doctype/report/user_activity_report_without_sort.json b/frappe/core/doctype/report/user_activity_report_without_sort.json new file mode 100644 index 0000000000..bb520a25e2 --- /dev/null +++ b/frappe/core/doctype/report/user_activity_report_without_sort.json @@ -0,0 +1,17 @@ +{ + "add_total_row": 0, + "apply_user_permissions": 1, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "is_standard": "No", + "javascript": null, + "json": "{\"filters\":[],\"columns\":[[\"name\",\"User\"],[\"user_type\",\"User\"],[\"first_name\",\"User\"],[\"last_name\",\"User\"],[\"last_active\",\"User\"],[\"role\",\"Has Role\"]],\"sort_order\":\"desc\",\"sort_by_next\":null,\"sort_order_next\":\"desc\"}", + "modified": "2018-12-17 18:27:07.728890", + "module": "Core", + "name": "User Activity Report Without Sort", + "query": null, + "ref_doctype": "User", + "report_name": "User Activity Report Without Sort", + "report_type": "Report Builder" + } \ No newline at end of file diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index fa350d9d8d..b3100a43da 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -29,7 +29,7 @@ def get_notifications(): notification_count[name] = count return { - "open_count_doctype": {}, + "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), diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 9e172754bd..6d43ae5eb7 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -314,7 +314,7 @@ def add_total_row(result, columns, meta = None): if fieldtype=="Link" and options == "Currency": - total_row[i] = result[0][i] + total_row[i] = result[0].get(fieldname) if isinstance(result[0], dict) else result[0][i] for i in has_percent: total_row[i] = flt(total_row[i]) / len(result) diff --git a/frappe/hooks.py b/frappe/hooks.py index bb3458d35a..223819b013 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -12,7 +12,7 @@ source_link = "https://github.com/frappe/frappe" app_license = "MIT" develop_version = '12.x.x-develop' -staging_version = '11.0.3-beta.37' +staging_version = '11.0.3-beta.38' app_email = "info@frappe.io" diff --git a/frappe/patches.txt b/frappe/patches.txt index c057611ca9..1f134ccfa5 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -233,3 +233,4 @@ frappe.patches.v11_0.set_allow_self_approval_in_workflow execute:frappe.db.sql('ALTER table `tabSeries` ADD PRIMARY KEY IF NOT EXISTS (name)') frappe.patches.v11_0.migrate_report_settings_for_new_listview frappe.patches.v11_0.delete_all_prepared_reports +frappe.patches.v11_0.fix_order_by_in_reports_json diff --git a/frappe/patches/v11_0/fix_order_by_in_reports_json.py b/frappe/patches/v11_0/fix_order_by_in_reports_json.py new file mode 100644 index 0000000000..28558ef4fb --- /dev/null +++ b/frappe/patches/v11_0/fix_order_by_in_reports_json.py @@ -0,0 +1,22 @@ +import frappe, json + +def execute(): + reports_data = frappe.get_all('Report', + filters={'json': ['not like', '%%%"order_by": "`tab%%%'], + 'report_type': 'Report Builder', 'is_standard': 'No'}, fields=['name']) + + for d in reports_data: + doc = frappe.get_doc('Report', d.get('name')) + json_data = json.loads(doc.get('json')) + + parts = [] + if ('order_by' in json_data) and ('.' in json_data.get('order_by')): + parts = json_data.get('order_by').split('.') + + sort_by = parts[1].split(' ') + + json_data['order_by'] = '`tab{0}`.`{1}`'.format(doc.ref_doctype, sort_by[0]) + json_data['order_by'] += ' {0}'.format(sort_by[1]) if len(sort_by) > 1 else '' + + doc.json = json.dumps(json_data) + doc.save() diff --git a/frappe/public/js/frappe/chat.js b/frappe/public/js/frappe/chat.js index ea7bfd3664..0a444b08dc 100644 --- a/frappe/public/js/frappe/chat.js +++ b/frappe/public/js/frappe/chat.js @@ -1984,18 +1984,27 @@ class extends Component { } } - if ( props.last_message ) + let is_unread = false + if ( props.last_message ) { item.timestamp = frappe.chat.pretty_datetime(props.last_message.creation) + is_unread = !props.last_message.seen.includes(frappe.session.user) + } return ( h("li", null, - h("a", { class: props.active ? "active": "", onclick: () => props.click(props) }, + h("a", { class: props.active ? "active": "", onclick: () => { + props.last_message.seen.push(frappe.session.user) + props.click(props) + } }, h("div", { class: "row" }, h("div", { class: "col-xs-9" }, h(frappe.Chat.Widget.MediaProfile, { ...item }) ), h("div", { class: "col-xs-3 text-right" }, - h("div", { class: "text-muted", style: { "font-size": "9px" } }, item.timestamp) + [ + h("div", { class: "text-muted", style: { "font-size": "9px" } }, item.timestamp), + is_unread ? h("span", { class: "indicator red" }) : null + ] ), ) ) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 26f346618a..3bd8a9b83c 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -974,16 +974,16 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { toggle_nothing_to_show(flag) { - let message = __('Nothing to show'); - if(this.prepared_report) { - message = __(`This is a background report. - Please set the appropriate filters and then generate a new one.`); - } + let message = this.prepared_report + ? __('This is a background report. Please set the appropriate filters and then generate a new one.') + : __('Nothing to show') + this.toggle_message(flag, message); - if(flag){ + + if (flag && this.prepared_report) { this.prepared_report_action = "New"; + this.add_prepared_report_buttons(); } - this.add_prepared_report_buttons(); } toggle_message(flag, message) { diff --git a/frappe/public/less/chat.less b/frappe/public/less/chat.less index e9ea77d843..74fd78c01b 100644 --- a/frappe/public/less/chat.less +++ b/frappe/public/less/chat.less @@ -2,8 +2,8 @@ // http://codeguide.co - @mdo (Author of Bootstrap) -@import "common.less"; @import "flex.less"; +@import {reference} "common.less"; // Typography @font-weight-bold: 700; @@ -55,7 +55,7 @@ margin: @fab-margin; width: @fab-size; height: @fab-size; - + &.frappe-fab-lg { width: @fab-size-lg; @@ -80,10 +80,10 @@ { position: fixed; bottom: 0px; - right: 0px; + right: 0px; margin: @chat-popper-margin; z-index: @chat-popper-z-index; - + & > .frappe-chat-popper-collapse { & > .panel @@ -175,7 +175,7 @@ right: 0px; overflow: auto; border-radius: 0px; - + .panel-heading { border-radius: 0px; @@ -194,19 +194,19 @@ { font-size: @chat-form-font-size; } - + .dropdown-menu { border-radius: @chat-form-menu-border-radius; } - + // Hints .hint-list.list-group { margin: 0px; max-height: @chat-form-list-group-height; overflow-y: auto; - + .hint-list-item.list-group-item:first-child, .hint-list-item.list-group-item:last-child { border-radius: 0px !important; @@ -269,7 +269,7 @@ { margin-right: @chat-base-spacing; } - + .avatar { width: 32px; height: 32px; @@ -285,7 +285,7 @@ .chat-form { border-top: @chat-form-border; - + .input-group-btn { .btn @@ -294,7 +294,7 @@ border-radius: 0px; } } - + .form-control { line-height: 27px; // HACK: Makes input and placeholder centered within textarea. Also takes care of the input-btn @@ -305,7 +305,7 @@ padding-right: 0px; overflow: hidden; } - + .fa { font-size: @chat-base-font-size-lg; @@ -317,7 +317,7 @@ .chat-list { - height: 100%; + height: 100%; // background: @chat-list-bg-color; overflow-y: scroll; @@ -334,22 +334,22 @@ } .cursor-pointer; - + border: none !important; padding: @chat-list-item-padding; background: transparent; - + .chat-bubble { max-width: @chat-bubble-max-width; display: inline-block; padding: @chat-bubble-padding; border-radius: @chat-bubble-border-radius; - + -webkit-box-shadow: @chat-bubble-box-shadow; -moz-box-shadow: @chat-bubble-box-shadow; box-shadow: @chat-bubble-box-shadow; - + @media (max-width : 768px) { min-width: @chat-bubble-min-width; } @@ -372,7 +372,7 @@ } } } - + &.chat-bubble-r { text-align: right; @@ -386,33 +386,33 @@ } } } - + .chat-bubble-author { font-size: @chat-bubble-author-font-size; - + a { .font-bold; - + text-decoration: none !important; } } - + .chat-bubble-content { margin-bottom: @chat-bubble-content-margin-bottom; word-wrap: break-word; } - + .chat-bubble-meta { font-size: @chat-bubble-meta-font-size; - + & > .chat-bubble-check { margin-left: @chat-base-spacing; - + i { font-size: @chat-bubble-check-font-size; @@ -439,4 +439,4 @@ font-size: 10px; padding: 5px; // background-color: white; -} \ No newline at end of file +} diff --git a/frappe/templates/includes/list/list.js b/frappe/templates/includes/list/list.js index ee1006326d..6bd3a155f2 100644 --- a/frappe/templates/includes/list/list.js +++ b/frappe/templates/includes/list/list.js @@ -11,6 +11,7 @@ frappe.ready(function() { pathname: location.pathname, }); data.web_form_name = frappe.web_form_name; + data.pathname = location.pathname; btn.prop("disabled", true); return $.ajax({ url:"/api/method/frappe.www.list.get", diff --git a/frappe/templates/includes/meta_block.html b/frappe/templates/includes/meta_block.html index 6f8a6a3a73..0ab3c2a4a7 100644 --- a/frappe/templates/includes/meta_block.html +++ b/frappe/templates/includes/meta_block.html @@ -1,11 +1,11 @@ {%- if metatags -%} {%- for name in metatags %} - + {%- endfor -%} - - - + + + {%- endif -%} diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 9a8cc772f1..08d46f84c6 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -824,7 +824,8 @@ def get_filter(doctype, f): if len(f) == 3: f = (doctype, f[0], f[1], f[2]) - + elif len(f) > 4: + f = f[0:4] elif len(f) != 4: frappe.throw(frappe._("Filter must have 4 values (doctype, fieldname, operator, value): {0}").format(str(f))) diff --git a/frappe/website/doctype/web_page/templates/web_page.html b/frappe/website/doctype/web_page/templates/web_page.html index ab0ec87650..0295c9ec1c 100644 --- a/frappe/website/doctype/web_page/templates/web_page.html +++ b/frappe/website/doctype/web_page/templates/web_page.html @@ -2,6 +2,10 @@ {%- block header -%} {{ header or "" }} {%- endblock -%} +{% block meta_block %} + {% include "templates/includes/meta_block.html" %} +{% endblock %} + {% block hero %}{{ hero or "" }}{% endblock %} {% block breadcrumbs %} diff --git a/frappe/www/list.py b/frappe/www/list.py index 159983aa72..e0d8bd895a 100644 --- a/frappe/www/list.py +++ b/frappe/www/list.py @@ -24,7 +24,7 @@ def get_context(context, **dict_params): context.update(get(**frappe.local.form_dict)) @frappe.whitelist(allow_guest=True) -def get(doctype, txt=None, limit_start=0, limit=20, **kwargs): +def get(doctype, txt=None, limit_start=0, limit=20, pathname=None, **kwargs): """Returns processed HTML page for a standard listing.""" limit_start = cint(limit_start) raw_result = get_list_data(doctype, txt, limit_start, limit=limit + 1, **kwargs) @@ -54,7 +54,8 @@ def get(doctype, txt=None, limit_start=0, limit=20, **kwargs): new_context.update(new_context.doc.as_dict()) if not frappe.flags.in_test: - new_context["pathname"] = frappe.local.request.path.strip("/ ") + pathname = pathname or frappe.local.request.path + new_context["pathname"] = pathname.strip("/ ") new_context.update(list_context) set_route(new_context) rendered_row = frappe.render_template(row_template, new_context, is_path=True) diff --git a/package.json b/package.json index 153ca01ee0..778324f72d 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "awesomplete": "^1.1.2", "cookie": "^0.3.1", "express": "^4.16.2", - "frappe-datatable": "^1.7.0", + "frappe-datatable": "^1.7.1", "frappe-gantt": "^0.1.0", "fuse.js": "^3.2.0", "highlight.js": "^9.12.0", diff --git a/yarn.lock b/yarn.lock index 3d2cfae642..bd2ffe9214 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1517,10 +1517,10 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= -frappe-datatable@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.7.0.tgz#6cc950a69bdfd514fb61ab25b85fa68c3b33aee0" - integrity sha512-CcCIf36eJMqzobc4rMT75zCCFKjfghJAR9rXiAP6h2uWdDKhb0U+rDjiFPweS54lv19tpTYF7Q/1SSx0Ih+Mfg== +frappe-datatable@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.7.1.tgz#5badb1138c7019e9dce50d6cbbd81c2fdc97e391" + integrity sha512-ePD4IDaLDCZCrchqT+TsPquOdnm72oYxh/KYMPfYBxUGSzWUoi2uRgzQD4MBR+uq1WxJhFQNoXe/fD3yOqNNUQ== dependencies: hyperlist "^1.0.0-beta" lodash "^4.17.5"