From 2cfe3622d4fa34d289c095bafd9f4cd4278fcc23 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 30 Mar 2023 11:34:11 +0200 Subject: [PATCH 01/19] fix: role perms on Google Contacts --- .../doctype/google_contacts/google_contacts.json | 12 ++++++------ .../doctype/google_contacts/test_google_contacts.py | 9 +++++++++ 2 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 frappe/integrations/doctype/google_contacts/test_google_contacts.py diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.json b/frappe/integrations/doctype/google_contacts/google_contacts.json index 76781fe47f..4a72651e67 100644 --- a/frappe/integrations/doctype/google_contacts/google_contacts.json +++ b/frappe/integrations/doctype/google_contacts/google_contacts.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "format:GC-{email_id}", "creation": "2019-06-14 00:09:39.441961", "doctype": "DocType", @@ -97,10 +98,12 @@ "label": "Push to Google Contacts" } ], - "modified": "2020-09-18 17:26:09.703215", + "links": [], + "modified": "2023-03-30 11:25:48.832384", "modified_by": "Administrator", "module": "Integrations", "name": "Google Contacts", + "naming_rule": "Expression", "owner": "Administrator", "permissions": [ { @@ -116,17 +119,14 @@ { "create": 1, "delete": 1, - "email": 1, - "export": 1, - "print": 1, + "if_owner": 1, "read": 1, - "report": 1, "role": "All", - "share": 1, "write": 1 } ], "sort_field": "modified", "sort_order": "ASC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/frappe/integrations/doctype/google_contacts/test_google_contacts.py b/frappe/integrations/doctype/google_contacts/test_google_contacts.py new file mode 100644 index 0000000000..d7ca08a082 --- /dev/null +++ b/frappe/integrations/doctype/google_contacts/test_google_contacts.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestGoogleContacts(FrappeTestCase): + pass From fcb6fa82339dec1af53b5a812902f2d919b509fb Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 30 Mar 2023 11:47:52 +0200 Subject: [PATCH 02/19] refactor(Google Contact): authorize_access --- .../doctype/google_contacts/google_contacts.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.py b/frappe/integrations/doctype/google_contacts/google_contacts.py index 9a20d5e905..7c845da330 100644 --- a/frappe/integrations/doctype/google_contacts/google_contacts.py +++ b/frappe/integrations/doctype/google_contacts/google_contacts.py @@ -36,10 +36,10 @@ def authorize_access(g_contact, reauthorize=False, code=None): If no Authorization code get it from Google and then request for Refresh Token. Google Contact Name is set to flags to set_value after Authorization Code is obtained. """ + contact = frappe.get_doc("Google Contacts", g_contact) + contact.check_permission("write") - oauth_code = ( - frappe.db.get_value("Google Contacts", g_contact, "authorization_code") if not code else code - ) + oauth_code = code or contact.get_password("authorization_code") oauth_obj = GoogleOAuth("contacts") if not oauth_code or reauthorize: @@ -51,11 +51,9 @@ def authorize_access(g_contact, reauthorize=False, code=None): ) r = oauth_obj.authorize(oauth_code) - frappe.db.set_value( - "Google Contacts", - g_contact, - {"authorization_code": oauth_code, "refresh_token": r.get("refresh_token")}, - ) + contact.authorization_code = oauth_code + contact.refresh_token = r.get("refresh_token") + contact.save() def get_google_contacts_object(g_contact): From 54ec1a764c87deff38a1b7acb30d2312b3766892 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 30 Mar 2023 11:55:09 +0200 Subject: [PATCH 03/19] fix(Google Calendar): authorize_access --- frappe/integrations/doctype/google_calendar/google_calendar.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 65a4a2bccd..d1cdd0d9e7 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -111,6 +111,7 @@ def authorize_access(g_calendar, reauthorize=None): """ google_settings = frappe.get_doc("Google Settings") google_calendar = frappe.get_doc("Google Calendar", g_calendar) + google_calendar.check_permission("write") redirect_uri = ( get_request_site_address(True) From 838919eb428ba2f439f4d7b80b543fc2cd2d652b Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 21 Apr 2023 17:43:41 +0530 Subject: [PATCH 04/19] fix: update deprecated deep selector syntax (#20807) `>>>` to `:deep` --- frappe/public/js/print_format_builder/Preview.vue | 2 +- frappe/public/js/print_format_builder/PrintFormatControls.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/print_format_builder/Preview.vue b/frappe/public/js/print_format_builder/Preview.vue index 1603711846..377ec92b6d 100644 --- a/frappe/public/js/print_format_builder/Preview.vue +++ b/frappe/public/js/print_format_builder/Preview.vue @@ -130,7 +130,7 @@ onMounted(() => { margin-top: auto; margin-bottom: 1.2rem; } -.preview-control >>> .form-control { +.preview-control :deep(.form-control) { background: var(--control-bg-on-gray); } diff --git a/frappe/public/js/print_format_builder/PrintFormatControls.vue b/frappe/public/js/print_format_builder/PrintFormatControls.vue index 7d38148d5e..eac5fbe9f6 100644 --- a/frappe/public/js/print_format_builder/PrintFormatControls.vue +++ b/frappe/public/js/print_format_builder/PrintFormatControls.vue @@ -332,7 +332,7 @@ watch(print_format, () => (store.dirty.value = true), { deep: true }); margin-bottom: 0; } -.control-font >>> .frappe-control[data-fieldname="font"] label { +.control-font :deep(.frappe-control[data-fieldname="font"] label) { display: none; } From 6b57a2d35280a07b9b068503020e24ae67931089 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 24 Apr 2023 06:56:43 +0200 Subject: [PATCH 05/19] fix: endless reload in User when timezone is unset (#20815) --- frappe/core/doctype/user/user.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index 918a9ee37c..e389a73cbb 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -114,9 +114,9 @@ frappe.ui.form.on("User", { return; } - function hasChanged(doc_attr, boot_attr) { - return (doc_attr || boot_attr) && doc_attr !== boot_attr; - } + const hasChanged = (doc_attr, boot_attr) => { + return doc_attr && boot_attr && doc_attr !== boot_attr; + }; if ( doc.name === frappe.session.user && From 89186b80570843fbfada945ac15bf7d8b3c14e80 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 24 Apr 2023 11:30:35 +0530 Subject: [PATCH 06/19] fix: multiple assignments to the same person --- frappe/desk/form/assign_to.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 2b17d38371..ce8bb444a1 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -147,35 +147,36 @@ def add_multiple(args=None): def close_all_assignments(doctype, name): assignments = frappe.get_all( "ToDo", - fields=["allocated_to"], + fields=["allocated_to", "name"], filters=dict(reference_type=doctype, reference_name=name, status=("!=", "Cancelled")), ) if not assignments: return False for assign_to in assignments: - set_status(doctype, name, assign_to.allocated_to, status="Closed") + set_status(doctype, name, todo=assign_to.name, assign_to=assign_to.allocated_to, status="Closed") return True @frappe.whitelist() def remove(doctype, name, assign_to): - return set_status(doctype, name, assign_to, status="Cancelled") + return set_status(doctype, name, "", assign_to, status="Cancelled") -def set_status(doctype, name, assign_to, status="Cancelled"): +def set_status(doctype, name, todo=None, assign_to=None, status="Cancelled"): """remove from todo""" try: - todo = frappe.db.get_value( - "ToDo", - { - "reference_type": doctype, - "reference_name": name, - "allocated_to": assign_to, - "status": ("!=", status), - }, - ) + if not todo: + todo = frappe.db.get_value( + "ToDo", + { + "reference_type": doctype, + "reference_name": name, + "allocated_to": assign_to, + "status": ("!=", status), + }, + ) if todo: todo = frappe.get_doc("ToDo", todo) todo.status = status @@ -197,13 +198,17 @@ def clear(doctype, name): Clears assignments, return False if not assigned. """ assignments = frappe.get_all( - "ToDo", fields=["allocated_to"], filters=dict(reference_type=doctype, reference_name=name) + "ToDo", + fields=["allocated_to", "name"], + filters=dict(reference_type=doctype, reference_name=name), ) if not assignments: return False for assign_to in assignments: - set_status(doctype, name, assign_to.allocated_to, "Cancelled") + set_status( + doctype, name, todo=assign_to.name, assign_to=assign_to.allocated_to, status="Cancelled" + ) return True From 7ae60ff268febdd74b9ed5da381f380e4c923c20 Mon Sep 17 00:00:00 2001 From: phot0n Date: Mon, 24 Apr 2023 13:06:51 +0530 Subject: [PATCH 07/19] refactor(minor): remove update counter dialog and make counter editable by default --- .../document_naming_rule.js | 42 ------------------- .../document_naming_rule.json | 17 +++++--- .../document_naming_rule.py | 6 --- 3 files changed, 12 insertions(+), 53 deletions(-) diff --git a/frappe/core/doctype/document_naming_rule/document_naming_rule.js b/frappe/core/doctype/document_naming_rule/document_naming_rule.js index 70d95673e6..f91b2983da 100644 --- a/frappe/core/doctype/document_naming_rule/document_naming_rule.js +++ b/frappe/core/doctype/document_naming_rule/document_naming_rule.js @@ -4,7 +4,6 @@ frappe.ui.form.on("Document Naming Rule", { refresh: function (frm) { frm.trigger("document_type"); - if (!frm.doc.__islocal) frm.trigger("add_update_counter_button"); }, document_type: (frm) => { // update the select field options with fieldnames @@ -26,45 +25,4 @@ frappe.ui.form.on("Document Naming Rule", { }); } }, - add_update_counter_button: (frm) => { - frm.add_custom_button(__("Update Counter"), function () { - const fields = [ - { - fieldtype: "Data", - fieldname: "new_counter", - label: __("New Counter"), - default: frm.doc.counter, - reqd: 1, - description: __( - "Warning: Updating counter may lead to document name conflicts if not done properly" - ), - }, - ]; - - let primary_action_label = __("Save"); - - let primary_action = (fields) => { - frappe.call({ - method: "frappe.core.doctype.document_naming_rule.document_naming_rule.update_current", - args: { - name: frm.doc.name, - new_counter: fields.new_counter, - }, - callback: function () { - frm.set_value("counter", fields.new_counter); - dialog.hide(); - }, - }); - }; - - const dialog = new frappe.ui.Dialog({ - title: __("Update Counter Value for Prefix: {0}", [frm.doc.prefix]), - fields, - primary_action_label, - primary_action, - }); - - dialog.show(); - }); - }, }); diff --git a/frappe/core/doctype/document_naming_rule/document_naming_rule.json b/frappe/core/doctype/document_naming_rule/document_naming_rule.json index 4e6f3f3fd1..24cef95570 100644 --- a/frappe/core/doctype/document_naming_rule/document_naming_rule.json +++ b/frappe/core/doctype/document_naming_rule/document_naming_rule.json @@ -12,8 +12,9 @@ "conditions", "naming_section", "prefix", - "prefix_digits", - "counter" + "counter", + "column_break_xfqa", + "prefix_digits" ], "fields": [ { @@ -38,11 +39,12 @@ "reqd": 1 }, { + "default": "0", + "description": "Warning: Updating counter may lead to document name conflicts if not done properly", "fieldname": "counter", "fieldtype": "Int", "label": "Counter", - "no_copy": 1, - "read_only": 1 + "no_copy": 1 }, { "default": "5", @@ -76,11 +78,15 @@ "fieldname": "priority", "fieldtype": "Int", "label": "Priority" + }, + { + "fieldname": "column_break_xfqa", + "fieldtype": "Column Break" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-09-13 20:07:47.617615", + "modified": "2023-04-24 13:06:20.992011", "modified_by": "Administrator", "module": "Core", "name": "Document Naming Rule", @@ -102,6 +108,7 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "document_type", "track_changes": 1 } \ No newline at end of file diff --git a/frappe/core/doctype/document_naming_rule/document_naming_rule.py b/frappe/core/doctype/document_naming_rule/document_naming_rule.py index 598de98dbb..46bc1c5ee2 100644 --- a/frappe/core/doctype/document_naming_rule/document_naming_rule.py +++ b/frappe/core/doctype/document_naming_rule/document_naming_rule.py @@ -47,9 +47,3 @@ class DocumentNamingRule(Document): doc.name = naming_series + ("%0" + str(self.prefix_digits) + "d") % (counter + 1) frappe.db.set_value(self.doctype, self.name, "counter", counter + 1) - - -@frappe.whitelist() -def update_current(name, new_counter): - frappe.only_for("System Manager") - frappe.db.set_value("Document Naming Rule", name, "counter", new_counter) From eeda161e98a73f67c0787f4fd92bc17e0d6d9356 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 24 Apr 2023 17:34:24 +0530 Subject: [PATCH 08/19] fix: handle `SMTPRecipientsRefused` retries (#20819) * fix: remove trailing and leading quotes from email * chore: typo * fix: dont retry `SMTPRecipientsRefused` If refused once it's unlikely to work again just by retrying. There's no mechanism to prevent infinite retry. This will still attempt for MAX_RETRY_COUNT. * Revert "fix: remove trailing and leading quotes from email" This reverts commit 2676ac2c7fe76c34049da05a209554fee6b3d911. refer https://github.com/frappe/frappe/pull/20819#discussion_r1175166987 --- frappe/email/doctype/email_queue/email_queue.py | 3 +-- .../doctype/email_queue_recipient/email_queue_recipient.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py index c10494b0d9..d254c87a0a 100644 --- a/frappe/email/doctype/email_queue/email_queue.py +++ b/frappe/email/doctype/email_queue/email_queue.py @@ -203,7 +203,7 @@ class SendMailContext: # Note: smtp session will have to be manually closed self.retain_smtp_session = bool(smtp_server_instance) - self.sent_to = [rec.recipient for rec in self.queue_doc.recipients if rec.is_main_sent()] + self.sent_to = [rec.recipient for rec in self.queue_doc.recipients if rec.is_mail_sent()] def __enter__(self): self.queue_doc.update_status(status="Sending", commit=True) @@ -213,7 +213,6 @@ class SendMailContext: exceptions = [ smtplib.SMTPServerDisconnected, smtplib.SMTPAuthenticationError, - smtplib.SMTPRecipientsRefused, smtplib.SMTPConnectError, smtplib.SMTPHeloError, JobTimeoutException, diff --git a/frappe/email/doctype/email_queue_recipient/email_queue_recipient.py b/frappe/email/doctype/email_queue_recipient/email_queue_recipient.py index bcb8d9b05d..705075a862 100644 --- a/frappe/email/doctype/email_queue_recipient/email_queue_recipient.py +++ b/frappe/email/doctype/email_queue_recipient/email_queue_recipient.py @@ -11,7 +11,7 @@ class EmailQueueRecipient(Document): def is_mail_to_be_sent(self): return self.status == "Not Sent" - def is_main_sent(self): + def is_mail_sent(self): return self.status == "Sent" def update_db(self, commit=False, **kwargs): From fc103250749575854609a4d959e05a82de416e02 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 24 Apr 2023 15:21:42 +0200 Subject: [PATCH 09/19] fix: improve delete_contact_and_address (#20381) --- frappe/contacts/address_and_contact.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/frappe/contacts/address_and_contact.py b/frappe/contacts/address_and_contact.py index 4df32c6705..1d3a5d644c 100644 --- a/frappe/contacts/address_and_contact.py +++ b/frappe/contacts/address_and_contact.py @@ -149,18 +149,26 @@ def get_permitted_and_not_permitted_links(doctype): return {"permitted_links": permitted_links, "not_permitted_links": not_permitted_links} -def delete_contact_and_address(doctype, docname): +def delete_contact_and_address(doctype: str, docname: str) -> None: for parenttype in ("Contact", "Address"): - items = frappe.db.sql_list( - """select parent from `tabDynamic Link` - where parenttype=%s and link_doctype=%s and link_name=%s""", - (parenttype, doctype, docname), - ) - - for name in items: + for name in frappe.get_all( + "Dynamic Link", + filters={ + "parenttype": parenttype, + "link_doctype": doctype, + "link_name": docname, + }, + pluck="parent", + ): doc = frappe.get_doc(parenttype, name) if len(doc.links) == 1: doc.delete() + else: + for link in doc.links: + if link.link_doctype == doctype and link.link_name == docname: + doc.remove(link) + doc.save() + break @frappe.whitelist() From e384d94fcc39d37a1a1287d4fc3128dd10352e8b Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 24 Apr 2023 15:28:22 +0200 Subject: [PATCH 10/19] fix: make role info translatable (#20813) --- frappe/public/js/frappe/roles_editor.js | 14 ++++++++------ frappe/translations/de.csv | 5 +++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/frappe/public/js/frappe/roles_editor.js b/frappe/public/js/frappe/roles_editor.js index efdba7d4d8..312a90a82c 100644 --- a/frappe/public/js/frappe/roles_editor.js +++ b/frappe/public/js/frappe/roles_editor.js @@ -59,7 +59,7 @@ frappe.RoleEditor = class { const $body = $(this.perm_dialog.body); if (!permissions.length) { $body.append(`
- ${__("{0} role does not have permission on any doctype", [role])} + ${__("{0} role does not have permission on any doctype", [__(role)])}
`); } else { $body.append(` @@ -68,7 +68,7 @@ frappe.RoleEditor = class { ${__("Document Type")} ${__("Level")} - ${frappe.perm.rights.map((p) => ` ${frappe.unscrub(p)}`).join("")} + ${frappe.perm.rights.map((p) => ` ${__(frappe.unscrub(p))}`).join("")} @@ -77,7 +77,7 @@ frappe.RoleEditor = class { permissions.forEach((perm) => { $body.find("tbody").append(` - ${perm.parent} + ${__(perm.parent)} ${perm.permlevel} ${frappe.perm.rights .map( @@ -91,7 +91,7 @@ frappe.RoleEditor = class { `); }); } - this.perm_dialog.set_title(role); + this.perm_dialog.set_title(__(role)); this.perm_dialog.show(); }); } @@ -102,8 +102,10 @@ frappe.RoleEditor = class { this.perm_dialog.$wrapper .find(".modal-dialog") - .css("width", "1200px") - .css("max-width", "80vw"); + .css("width", "auto") + .css("max-width", "1200px"); + + this.perm_dialog.$wrapper.find(".modal-body").css("overflow", "overlay"); } show() { this.reset(); diff --git a/frappe/translations/de.csv b/frappe/translations/de.csv index 834281bbab..dae676ee72 100644 --- a/frappe/translations/de.csv +++ b/frappe/translations/de.csv @@ -2290,8 +2290,8 @@ Setup Auto Email,Einstellungen Auto E-Mail, Setup Complete,Einrichtung abgeschlossen, Setup Notifications based on various criteria.,Setup Benachrichtigungen basierend auf verschiedenen Kriterien., Setup Reports to be emailed at regular intervals,Berichte regelmäßig per E-Mail senden, -"Setup of top navigation bar, footer and logo.","Einrichten der oberen Navigationsleiste, der Fußzeile und des Logos", -Share,Aktie, +"Setup of top navigation bar, footer and logo.","Einrichten der oberen Navigationsleiste, der Fußzeile und des Logos", +Share,Freigeben, Share URL,URL teilen, Share With,Freigeben für, Share this document with,Dieses Dokument teilen mit, @@ -4840,3 +4840,4 @@ Non-numeric,Nicht-numerische, Minimal,Minimal, This value is fetched from {0}'s {1} field,Dieser Wert ergibt sich aus dem Feld {1} von {0}, This form is not editable due to a Workflow.,Dieses Formular kann in diesem Workflow-Status nicht bearbeitet werden., +{0} role does not have permission on any doctype,Die Rolle {0} hat auf keinen DocType Zugriff, From f7b2b59f9a3752641c6cae9ff5a5ba57ebd78197 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 24 Apr 2023 15:46:11 +0200 Subject: [PATCH 11/19] fix: don't translate before unscrubbing (#20814) --- frappe/public/js/frappe/model/model.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index 6c6f617275..7e5eccf26d 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -348,11 +348,9 @@ $.extend(frappe.model, { }, unscrub: function (txt) { - return __(txt || "") - .replace(/-|_/g, " ") - .replace(/\w*/g, function (keywords) { - return keywords.charAt(0).toUpperCase() + keywords.substr(1).toLowerCase(); - }); + return (txt || "").replace(/-|_/g, " ").replace(/\w*/g, function (keywords) { + return keywords.charAt(0).toUpperCase() + keywords.substr(1).toLowerCase(); + }); }, can_create: function (doctype) { From 71f45eba68e058d254c4e5c7cfe07b9b14352509 Mon Sep 17 00:00:00 2001 From: phot0n Date: Mon, 24 Apr 2023 15:45:33 +0530 Subject: [PATCH 12/19] fix: add confirmation when updating counter and make document type mandatory --- .../document_naming_rule.js | 18 ++++++++++++++++++ .../document_naming_rule.json | 5 +++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/document_naming_rule/document_naming_rule.js b/frappe/core/doctype/document_naming_rule/document_naming_rule.js index f91b2983da..f6ea996107 100644 --- a/frappe/core/doctype/document_naming_rule/document_naming_rule.js +++ b/frappe/core/doctype/document_naming_rule/document_naming_rule.js @@ -4,6 +4,24 @@ frappe.ui.form.on("Document Naming Rule", { refresh: function (frm) { frm.trigger("document_type"); + frm.last_counter_value = frm.doc.counter; + frm.skip_before_save = false; + }, + before_save: function (frm) { + if (frm.is_new() || frm.skip_before_save || frm.last_counter_value === frm.doc.counter) + return; + + frappe.validated = false; + frappe.warn( + __("Are you sure?"), + __("Updating counter may lead to document name conflicts if not done properly"), + () => { + frm.skip_before_save = true; + frm.save(); + }, + __("Proceed"), + false + ); }, document_type: (frm) => { // update the select field options with fieldnames diff --git a/frappe/core/doctype/document_naming_rule/document_naming_rule.json b/frappe/core/doctype/document_naming_rule/document_naming_rule.json index 24cef95570..1e2247c250 100644 --- a/frappe/core/doctype/document_naming_rule/document_naming_rule.json +++ b/frappe/core/doctype/document_naming_rule/document_naming_rule.json @@ -23,7 +23,8 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Document Type", - "options": "DocType" + "options": "DocType", + "reqd": 1 }, { "default": "0", @@ -86,7 +87,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-04-24 13:06:20.992011", + "modified": "2023-04-24 15:14:32.054272", "modified_by": "Administrator", "module": "Core", "name": "Document Naming Rule", From 0d1c7fee4234906bcbce4fc3b1cc178fb1f1b3a2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 Apr 2023 11:12:22 +0530 Subject: [PATCH 13/19] feat: let users disable telemetry from system settings --- .../system_settings/system_settings.json | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index ba3846f8b5..0b4a0d5605 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -76,7 +76,9 @@ "max_auto_email_report_per_user", "system_updates_section", "disable_system_update_notification", - "disable_change_log_notification" + "disable_change_log_notification", + "telemetry_section", + "enable_telemetry" ], "fields": [ { @@ -535,12 +537,24 @@ "fieldname": "disable_document_sharing", "fieldtype": "Check", "label": "Disable Document Sharing" + }, + { + "collapsible": 1, + "fieldname": "telemetry_section", + "fieldtype": "Section Break", + "label": "Telemetry" + }, + { + "default": "1", + "fieldname": "enable_telemetry", + "fieldtype": "Check", + "label": "Send Data for Improving Apps" } ], "icon": "fa fa-cog", "issingle": 1, "links": [], - "modified": "2023-03-14 11:30:56.465653", + "modified": "2023-04-23 11:14:59.302851", "modified_by": "Administrator", "module": "Core", "name": "System Settings", From 87a88195ec3a159b1114a60b165189d04f6547a9 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 19 Apr 2023 13:34:51 +0530 Subject: [PATCH 14/19] feat: telemetry using posthog --- .pre-commit-config.yaml | 1 + frappe/hooks.py | 5 ++++ frappe/public/js/lib/posthog.js | 1 + frappe/public/js/telemetry.bundle.js | 1 + frappe/public/js/telemetry/index.js | 34 ++++++++++++++++++++++++++++ frappe/utils/telemetry.py | 10 ++++++++ 6 files changed, 52 insertions(+) create mode 100644 frappe/public/js/lib/posthog.js create mode 100644 frappe/public/js/telemetry.bundle.js create mode 100644 frappe/public/js/telemetry/index.js create mode 100644 frappe/utils/telemetry.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0c6bbe8ec9..b079cf05c5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,6 +44,7 @@ repos: .*boilerplate.*| frappe/www/website_script.js| frappe/templates/includes/.*| + .*telemetry.bundle.js| frappe/public/js/lib/.* )$ diff --git a/frappe/hooks.py b/frappe/hooks.py index a3355d5455..5967486824 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -29,6 +29,7 @@ app_include_js = [ "form.bundle.js", "controls.bundle.js", "report.bundle.js", + "telemetry.bundle.js", ] app_include_css = [ "desk.bundle.css", @@ -419,3 +420,7 @@ after_job = [ "frappe.monitor.stop", "frappe.utils.file_lock.release_document_locks", ] + +extend_bootinfo = [ + "frappe.utils.telemetry.add_bootinfo", +] diff --git a/frappe/public/js/lib/posthog.js b/frappe/public/js/lib/posthog.js new file mode 100644 index 0000000000..34ecee9799 --- /dev/null +++ b/frappe/public/js/lib/posthog.js @@ -0,0 +1 @@ +!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags".split(" "),n=0;n Date: Mon, 24 Apr 2023 15:27:58 +0530 Subject: [PATCH 15/19] feat: setup wizard progress telemetry --- .pre-commit-config.yaml | 1 - frappe/desk/page/setup_wizard/setup_wizard.js | 11 ++++++++ frappe/public/js/telemetry/index.js | 25 +++++++++++-------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b079cf05c5..0c6bbe8ec9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,7 +44,6 @@ repos: .*boilerplate.*| frappe/www/website_script.js| frappe/templates/includes/.*| - .*telemetry.bundle.js| frappe/public/js/lib/.* )$ diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js index 969aedb882..48225f01cd 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.js +++ b/frappe/desk/page/setup_wizard/setup_wizard.js @@ -178,6 +178,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { } action_on_complete() { + frappe.telemetry.log("completed", "setup"); if (!this.current_slide.set_values()) return; this.update_values(); this.show_working_state(); @@ -325,6 +326,7 @@ frappe.setup.SetupWizardSlide = class SetupWizardSlide extends frappe.ui.Slide { make() { super.make(); this.set_init_values(); + this.setup_telemetry_events(); this.reset_action_button_state(); } @@ -340,6 +342,15 @@ frappe.setup.SetupWizardSlide = class SetupWizardSlide extends frappe.ui.Slide { }); } } + + setup_telemetry_events() { + let me = this; + this.fields.filter(frappe.model.is_value_type).forEach((field) => { + me.get_input(field.fieldname).on("change", function () { + frappe.telemetry.log(`${field.fieldname}_set`, "setup"); + }); + }); + } }; // Frappe slides settings diff --git a/frappe/public/js/telemetry/index.js b/frappe/public/js/telemetry/index.js index 17af2e8498..9c0248b73a 100644 --- a/frappe/public/js/telemetry/index.js +++ b/frappe/public/js/telemetry/index.js @@ -14,19 +14,24 @@ class TelemetryManager { initialize() { if (!this.enabled) return; - posthog.init(this.project_id, { - api_host: this.telemetry_host, - autocapture: false, - capture_pageview: false, - capture_pageleave: false, - }); - - // posthog.identify("site", "") + try { + posthog.init(this.project_id, { + api_host: this.telemetry_host, + autocapture: false, + capture_pageview: false, + capture_pageleave: false, + advanced_disable_decide: true, + }); + posthog.identify(frappe.boot.sitename); + } catch (e) { + console.trace("Failed to initialize telemetry", e); + this.enabled = false; + } } - log_event(event, app) { + log(event, app) { if (!this.enabled) return; - posthog.capture(`${event}_${app}`); + posthog.capture(`${app}_${event}`); } } From f63c420798ebfc0ea696f0b354901b11e6dfd067 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 25 Apr 2023 12:01:24 +0530 Subject: [PATCH 16/19] feat: add python posthog library --- frappe/desk/page/setup_wizard/setup_wizard.js | 4 +-- frappe/desk/page/setup_wizard/setup_wizard.py | 4 +++ frappe/public/js/telemetry/index.js | 2 +- frappe/utils/telemetry.py | 34 +++++++++++++++++++ pyproject.toml | 1 + 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js index 48225f01cd..ff7e4e82f6 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.js +++ b/frappe/desk/page/setup_wizard/setup_wizard.js @@ -178,7 +178,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { } action_on_complete() { - frappe.telemetry.log("completed", "setup"); + frappe.telemetry.capture("initated_client_side", "setup"); if (!this.current_slide.set_values()) return; this.update_values(); this.show_working_state(); @@ -347,7 +347,7 @@ frappe.setup.SetupWizardSlide = class SetupWizardSlide extends frappe.ui.Slide { let me = this; this.fields.filter(frappe.model.is_value_type).forEach((field) => { me.get_input(field.fieldname).on("change", function () { - frappe.telemetry.log(`${field.fieldname}_set`, "setup"); + frappe.telemetry.capture(`${field.fieldname}_set`, "setup"); }); }); } diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index cdb25b81ba..1b8a293356 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -64,6 +64,9 @@ def setup_complete(args): @frappe.task() def process_setup_stages(stages, user_input, is_background_task=False): + from frappe.utils.telemetry import capture + + capture("initated_server_side", "setup") try: frappe.flags.in_setup_wizard = True current_task = None @@ -88,6 +91,7 @@ def process_setup_stages(stages, user_input, is_background_task=False): ) else: run_setup_success(user_input) + capture("completed_server_side", "setup") if not is_background_task: return {"status": "ok"} frappe.publish_realtime("setup_task", {"status": "ok"}, user=frappe.session.user) diff --git a/frappe/public/js/telemetry/index.js b/frappe/public/js/telemetry/index.js index 9c0248b73a..c27495755f 100644 --- a/frappe/public/js/telemetry/index.js +++ b/frappe/public/js/telemetry/index.js @@ -29,7 +29,7 @@ class TelemetryManager { } } - log(event, app) { + capture(event, app) { if (!this.enabled) return; posthog.capture(`${app}_${event}`); } diff --git a/frappe/utils/telemetry.py b/frappe/utils/telemetry.py index 0e44f1f44b..6dcdf97da9 100644 --- a/frappe/utils/telemetry.py +++ b/frappe/utils/telemetry.py @@ -1,3 +1,12 @@ +""" Basic telemetry for improving apps. + +WARNING: Everything in this file should be treated "internal" and is subjected to change or get +removed without any warning. +""" +from contextlib import suppress + +from posthog import Posthog + import frappe @@ -8,3 +17,28 @@ def add_bootinfo(bootinfo): bootinfo.posthog_host = frappe.conf.posthog_host bootinfo.posthog_project_id = frappe.conf.posthog_project_id bootinfo.enable_telemetry = True + + +def init_telemetry(): + """Init posthog for server side telemetry.""" + if hasattr(frappe.local, "posthog"): + return + + if not frappe.get_system_settings("enable_telemetry"): + return + + posthog_host = frappe.conf.posthog_host + posthog_project_id = frappe.conf.posthog_project_id + + if not posthog_host or not posthog_project_id: + return + + with suppress(Exception): + frappe.local.posthog = Posthog(posthog_project_id, host=posthog_host) + + +def capture(event, app): + init_telemetry() + ph: Posthog = getattr(frappe.local, "posthog", None) + with suppress(Exception): + ph and ph.capture(frappe.local.site, f"{app}_{event}") diff --git a/pyproject.toml b/pyproject.toml index 5429682a33..f2688e97ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,6 +79,7 @@ dependencies = [ "google-api-python-client~=2.2.0", "google-auth-oauthlib~=0.4.4", "google-auth~=1.29.0", + "posthog~=3.0.1", ] [build-system] From 93c326b97172edff4320d304b5b43d58c5242c2f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 25 Apr 2023 14:21:24 +0530 Subject: [PATCH 17/19] feat: let user opt out of telemetry from setup --- .../doctype/system_settings/system_settings.json | 2 +- frappe/desk/page/setup_wizard/setup_wizard.js | 12 ++++++++++++ frappe/desk/page/setup_wizard/setup_wizard.py | 1 + frappe/public/js/telemetry/index.js | 5 +++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index 0b4a0d5605..4b6438fded 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -548,7 +548,7 @@ "default": "1", "fieldname": "enable_telemetry", "fieldtype": "Check", - "label": "Send Data for Improving Apps" + "label": "Allow Sending Usage Data for Improving applications" } ], "icon": "fa fa-cog", diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js index ff7e4e82f6..865619c9ff 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.js +++ b/frappe/desk/page/setup_wizard/setup_wizard.js @@ -348,6 +348,9 @@ frappe.setup.SetupWizardSlide = class SetupWizardSlide extends frappe.ui.Slide { this.fields.filter(frappe.model.is_value_type).forEach((field) => { me.get_input(field.fieldname).on("change", function () { frappe.telemetry.capture(`${field.fieldname}_set`, "setup"); + if (field.fieldname == "enable_telemetry" && !me.get_value("enable_telemetry")) { + frappe.telemetry.disable(); + } }); }); } @@ -395,6 +398,15 @@ frappe.setup.slides_settings = [ fieldtype: "Select", reqd: 1, }, + { + fieldtype: "Section Break", + }, + { + fieldname: "enable_telemetry", + label: __("Allow Sending Usage Data for Improving applications"), + fieldtype: "Check", + default: 1, + }, ], onload: function (slide) { diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index 1b8a293356..cb869fb5fc 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -175,6 +175,7 @@ def update_system_settings(args): "number_format": number_format, "enable_scheduler": 1 if not frappe.flags.in_test else 0, "backup_limit": 3, # Default for downloadable backups + "enable_telemetry": cint(args.get("enable_telemetry")), } ) system_settings.save() diff --git a/frappe/public/js/telemetry/index.js b/frappe/public/js/telemetry/index.js index c27495755f..409584ee50 100644 --- a/frappe/public/js/telemetry/index.js +++ b/frappe/public/js/telemetry/index.js @@ -33,6 +33,11 @@ class TelemetryManager { if (!this.enabled) return; posthog.capture(`${app}_${event}`); } + + disable() { + this.enabled = false; + posthog.opt_out_capturing(); + } } frappe.telemetry = new TelemetryManager(); From db4cd1a84b7acbdca1c6a97537ebd719703e9c6b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 25 Apr 2023 15:03:11 +0530 Subject: [PATCH 18/19] refactor: reduce duplication --- frappe/utils/telemetry.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/frappe/utils/telemetry.py b/frappe/utils/telemetry.py index 6dcdf97da9..4042c0b65e 100644 --- a/frappe/utils/telemetry.py +++ b/frappe/utils/telemetry.py @@ -9,13 +9,16 @@ from posthog import Posthog import frappe +POSTHOG_PROJECT_FIELD = "posthog_project_id" +POSTHOG_HOST_FIELD = "posthog_host" + def add_bootinfo(bootinfo): if not frappe.get_system_settings("enable_telemetry"): return - bootinfo.posthog_host = frappe.conf.posthog_host - bootinfo.posthog_project_id = frappe.conf.posthog_project_id + bootinfo.posthog_host = frappe.conf.get(POSTHOG_HOST_FIELD) + bootinfo.posthog_project_id = frappe.conf.get(POSTHOG_PROJECT_FIELD) bootinfo.enable_telemetry = True @@ -27,8 +30,8 @@ def init_telemetry(): if not frappe.get_system_settings("enable_telemetry"): return - posthog_host = frappe.conf.posthog_host - posthog_project_id = frappe.conf.posthog_project_id + posthog_host = frappe.conf.get(POSTHOG_HOST_FIELD) + posthog_project_id = frappe.conf.get(POSTHOG_PROJECT_FIELD) if not posthog_host or not posthog_project_id: return From 109a549a230e4e9aacb0d724faefc266017319b0 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 25 Apr 2023 18:31:14 +0530 Subject: [PATCH 19/19] fix: pass reference_doctype to search query methods (#20842) frappe.call ensures that it's only passed to functions which can accept it, so nothing to worry about ~ backward compatible change. --- frappe/desk/search.py | 10 +++++++++- frappe/tests/test_search.py | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/frappe/desk/search.py b/frappe/desk/search.py index ee63f67423..67695e4e73 100644 --- a/frappe/desk/search.py +++ b/frappe/desk/search.py @@ -81,7 +81,15 @@ def search_widget( try: is_whitelisted(frappe.get_attr(query)) frappe.response["values"] = frappe.call( - query, doctype, txt, searchfield, start, page_length, filters, as_dict=as_dict + query, + doctype, + txt, + searchfield, + start, + page_length, + filters, + as_dict=as_dict, + reference_doctype=reference_doctype, ) except frappe.exceptions.PermissionError as e: if frappe.local.conf.developer_mode: diff --git a/frappe/tests/test_search.py b/frappe/tests/test_search.py index 4f8e35301a..116e741ad7 100644 --- a/frappe/tests/test_search.py +++ b/frappe/tests/test_search.py @@ -130,12 +130,32 @@ class TestSearch(FrappeTestCase): search_link("User", "user@random", searchfield="name") self.assertListEqual(frappe.response["results"], []) + def test_reference_doctype(self): + """search query methods should get reference_doctype if they want""" + search_link( + doctype="User", + txt="", + filters=None, + page_length=20, + reference_doctype="ToDo", + query="frappe.tests.test_search.query_with_reference_doctype", + ) + self.assertListEqual(frappe.response["results"], []) + @frappe.validate_and_sanitize_search_inputs def get_data(doctype, txt, searchfield, start, page_len, filters): return [doctype, txt, searchfield, start, page_len, filters] +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def query_with_reference_doctype( + doctype, txt, searchfield, start, page_len, filters, reference_doctype=None +): + return [] + + def setup_test_link_field_order(TestCase): TestCase.tree_doctype_name = "Test Tree Order" TestCase.child_doctype_list = []