From 5226b32f2f42c9320caa8748b267ebe8b4c09d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Oliver=20S=C3=BCnderhauf?= <46800703+bosue@users.noreply.github.com> Date: Tue, 31 Oct 2023 11:42:15 +0100 Subject: [PATCH 01/19] test: Add reliably failing UI test: Awesomebar filtering current doclist --- cypress/integration/awesome_bar.js | 49 ++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/cypress/integration/awesome_bar.js b/cypress/integration/awesome_bar.js index 03ef96783a..dff04a5693 100644 --- a/cypress/integration/awesome_bar.js +++ b/cypress/integration/awesome_bar.js @@ -2,7 +2,11 @@ context("Awesome Bar", () => { before(() => { cy.visit("/login"); cy.login(); - cy.visit("/app/website"); + cy.visit("/app/todo"); // Make sure ToDo filters are cleared. + cy.clear_filters(); + cy.visit("/app/blog-post"); // Make sure Blog Post filters are cleared. + cy.clear_filters(); + cy.visit("/app/website"); // Go to some other page. }); beforeEach(() => { @@ -11,36 +15,61 @@ context("Awesome Bar", () => { cy.get("@awesome_bar").type("{selectall}"); }); + after(() => { + cy.visit("/app/todo"); // Make sure we're not bleeding any filters to the next spec. + cy.clear_filters(); + }); + it("navigates to doctype list", () => { cy.get("@awesome_bar").type("todo"); - cy.wait(100); + cy.wait(100); // Wait a bit before hitting enter. cy.get(".awesomplete").findByRole("listbox").should("be.visible"); cy.get("@awesome_bar").type("{enter}"); cy.get(".title-text").should("contain", "To Do"); cy.location("pathname").should("eq", "/app/todo"); }); - it("find text in doctype list", () => { + it("finds text in doctype list", () => { cy.get("@awesome_bar").type("test in todo"); - cy.wait(100); + cy.wait(150); // Wait a bit before hitting enter. cy.get("@awesome_bar").type("{enter}"); cy.get(".title-text").should("contain", "To Do"); - cy.wait(200); - const name_filter = cy.get('[data-original-title="ID"] > input'); - name_filter.should("have.value", "%test%"); - cy.clear_filters(); + cy.wait(200); // Wait a bit longer before checking the filter. + cy.get('[data-original-title="ID"] > input').should("have.value", "%test%"); + }); + + it("filter preserved, now finds something else", () => { + cy.visit("/app/todo"); + cy.get(".title-text").should("contain", "To Do"); + cy.wait(200); // Wait a bit longer before checking the filter. + cy.get('[data-original-title="ID"] > input').as("filter"); + cy.get("@filter").should("have.value", "%test%"); + cy.get("@awesome_bar").type("anothertest in todo"); + cy.wait(200); // Wait a bit longer before hitting enter. + cy.get("@awesome_bar").type("{enter}"); + cy.wait(200); // Wait a bit longer before checking the filter. + cy.get("@filter").should("have.value", "%anothertest%"); + }); + + it("navigates to another doctype, filter not bleeding", () => { + cy.get("@awesome_bar").type("blog post"); + cy.wait(150); // Wait a bit before hitting enter. + cy.get("@awesome_bar").type("{enter}"); + cy.get(".title-text").should("contain", "Blog Post"); + cy.wait(200); // Wait a bit longer before checking the filter. + cy.location("search").should("be.empty"); }); it("navigates to new form", () => { cy.get("@awesome_bar").type("new blog post"); - cy.wait(100); + cy.wait(150); // Wait a bit before hitting enter cy.get("@awesome_bar").type("{enter}"); cy.get(".title-text:visible").should("have.text", "New Blog Post"); }); it("calculates math expressions", () => { cy.get("@awesome_bar").type("55 + 32"); - cy.wait(100); + cy.wait(150); // Wait a bit before hitting enter cy.get("@awesome_bar").type("{downarrow}{enter}"); cy.get(".modal-title").should("contain", "Result"); cy.get(".msgprint").should("contain", "55 + 32 = 87"); From c3adc13206ef44a0d50f2a00d7514690b2a71df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Oliver=20S=C3=BCnderhauf?= <46800703+bosue@users.noreply.github.com> Date: Tue, 31 Oct 2023 23:01:09 +0100 Subject: [PATCH 02/19] fix!: Make router always take query parameters into account. --- frappe/public/js/frappe/router.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index 2631c6374e..3b50791aa7 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -368,7 +368,11 @@ frappe.router = { window.open(sub_path, "_blank"); frappe.open_in_new_tab = false; } else { - this.push_state(sub_path); + const route_options = frappe.route_options || {}; + const query_params = Object.entries(route_options) + .map(([key, value]) => `${key}=` + encodeURIComponent(JSON.stringify(value))) + .join("&"); + this.push_state(sub_path, query_params ? `?${query_params}` : ""); } setTimeout(() => { frappe.after_ajax && @@ -469,12 +473,19 @@ frappe.router = { return "/app/" + (path_string || default_page); }, - push_state(url) { - // change the URL and call the router - if (window.location.pathname !== url) { + /** + * Changes the URL and calls the router. + * + * @param {string} path - The desired URI path to replace or push, + * without query string. Example: "/app/todo" + * @param {string} query_params - The desired query parameter string. + * @returns {void} + */ + push_state(path, query_params = "") { + if (window.location.pathname !== path || window.location.search !== query_params) { // push/replace state so the browser looks fine const method = frappe.route_flags.replace_route ? "replaceState" : "pushState"; - history[method](null, null, url); + history[method](null, null, path); // now process the route this.route(); From 9e7a0b73edb7444581863e6a5f5744c2bfa1bf69 Mon Sep 17 00:00:00 2001 From: Shadrak Gurupnor Date: Thu, 23 Nov 2023 13:18:08 +0530 Subject: [PATCH 03/19] fix: show fieldname if field label is not set --- frappe/public/js/frappe/ui/group_by/group_by.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/ui/group_by/group_by.js b/frappe/public/js/frappe/ui/group_by/group_by.js index 2f218678ac..46b85a05cb 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.js +++ b/frappe/public/js/frappe/ui/group_by/group_by.js @@ -147,10 +147,13 @@ frappe.ui.GroupBy = class { doctype_fields.forEach((field) => { // pick numeric fields for sum / avg if (frappe.model.is_numeric_field(field.fieldtype)) { + let field_label = field.label + ? field.label + : frappe.model.unscrub(field.fieldname); let option_text = doctype == this.doctype - ? field.label - : `${field.label} (${__(doctype)})`; + ? field_label + : `${field_label} (${__(doctype)})`; this.aggregate_on_html += ``; } From 43ffd175590f1d5c0072764ac292fec11e1d1096 Mon Sep 17 00:00:00 2001 From: Shadrak Gurupnor Date: Thu, 23 Nov 2023 14:26:29 +0530 Subject: [PATCH 04/19] chore: translation --- frappe/public/js/frappe/ui/group_by/group_by.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/group_by/group_by.js b/frappe/public/js/frappe/ui/group_by/group_by.js index 46b85a05cb..2f62904813 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.js +++ b/frappe/public/js/frappe/ui/group_by/group_by.js @@ -153,7 +153,7 @@ frappe.ui.GroupBy = class { let option_text = doctype == this.doctype ? field_label - : `${field_label} (${__(doctype)})`; + : `${__(field_label)} (${__(doctype)})`; this.aggregate_on_html += ``; } From 077218156b313c3b488fcfc3b2401969baa0c84d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 22 Nov 2023 16:35:18 +0530 Subject: [PATCH 05/19] fix: always publish progress for bulk action --- frappe/desk/doctype/bulk_update/bulk_update.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/desk/doctype/bulk_update/bulk_update.py b/frappe/desk/doctype/bulk_update/bulk_update.py index 526543e825..797428389c 100644 --- a/frappe/desk/doctype/bulk_update/bulk_update.py +++ b/frappe/desk/doctype/bulk_update/bulk_update.py @@ -85,5 +85,4 @@ def submit_cancel_or_update_docs(doctype, docnames, action="submit", data=None): def show_progress(docnames, message, i, description): n = len(docnames) - if n >= 10: - frappe.publish_progress(float(i) * 100 / n, title=message, description=description) + frappe.publish_progress(float(i) * 100 / n, title=message, description=description) From 866a0295e34cff6565ad345421f4456233d7e7c0 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 22 Nov 2023 17:31:13 +0530 Subject: [PATCH 06/19] fix: bulk submit/cancel/update in background jobs --- .../desk/doctype/bulk_update/bulk_update.py | 17 ++++++- .../doctype/bulk_update/test_bulk_update.py | 48 +++++++++++++++++++ frappe/tests/test_bot.py | 8 ---- 3 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 frappe/desk/doctype/bulk_update/test_bulk_update.py delete mode 100644 frappe/tests/test_bot.py diff --git a/frappe/desk/doctype/bulk_update/bulk_update.py b/frappe/desk/doctype/bulk_update/bulk_update.py index 797428389c..cb3d9c7bc6 100644 --- a/frappe/desk/doctype/bulk_update/bulk_update.py +++ b/frappe/desk/doctype/bulk_update/bulk_update.py @@ -46,8 +46,23 @@ class BulkUpdate(Document): @frappe.whitelist() def submit_cancel_or_update_docs(doctype, docnames, action="submit", data=None): - docnames = frappe.parse_json(docnames) + if isinstance(docnames, str): + docnames = frappe.parse_json(docnames) + if len(docnames) < 10: + return _bulk_action(doctype, docnames, action, data) + elif len(docnames) <= 100: + frappe.msgprint(_("Bulk operation is enqueued in background."), alert=True) + frappe.enqueue( + _bulk_action, doctype=doctype, docnames=docnames, action=action, data=data, queue="long" + ) + else: + frappe.throw( + _("Bulk operations only support up to 100 documents."), title=_("Too Many Documents") + ) + + +def _bulk_action(doctype, docnames, action, data): if data: data = frappe.parse_json(data) diff --git a/frappe/desk/doctype/bulk_update/test_bulk_update.py b/frappe/desk/doctype/bulk_update/test_bulk_update.py new file mode 100644 index 0000000000..7611141a0a --- /dev/null +++ b/frappe/desk/doctype/bulk_update/test_bulk_update.py @@ -0,0 +1,48 @@ +# Copyright (c) 2023, Frappe Technologies and Contributors +# See LICENSE + +import time + +import frappe +from frappe.core.doctype.doctype.test_doctype import new_doctype +from frappe.desk.doctype.bulk_update.bulk_update import submit_cancel_or_update_docs +from frappe.tests.utils import FrappeTestCase, timeout + + +class TestBulkUpdate(FrappeTestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.doctype = new_doctype(is_submittable=1, custom=1).insert().name + frappe.db.commit() + for _ in range(50): + frappe.new_doc(cls.doctype, some_fieldname=frappe.mock("name")).insert() + + @timeout() + def wait_for_assertion(self, assertion): + """Wait till an assertion becomes True""" + while True: + if assertion(): + break + time.sleep(0.2) + + def test_bulk_submit_in_background(self): + unsubmitted = frappe.get_all(self.doctype, {"docstatus": 0}, limit=5, pluck="name") + failed = submit_cancel_or_update_docs(self.doctype, unsubmitted, action="submit") + self.assertEqual(failed, []) + + def check_docstatus(docs, status): + frappe.db.rollback() + matching_docs = frappe.get_all( + self.doctype, {"docstatus": status, "name": ("in", docs)}, pluck="name" + ) + return set(matching_docs) == set(docs) + + unsubmitted = frappe.get_all(self.doctype, {"docstatus": 0}, limit=20, pluck="name") + submit_cancel_or_update_docs(self.doctype, unsubmitted, action="submit") + + self.wait_for_assertion(lambda: check_docstatus(unsubmitted, 1)) + + submitted = frappe.get_all(self.doctype, {"docstatus": 1}, limit=20, pluck="name") + submit_cancel_or_update_docs(self.doctype, submitted, action="cancel") + self.wait_for_assertion(lambda: check_docstatus(submitted, 2)) diff --git a/frappe/tests/test_bot.py b/frappe/tests/test_bot.py deleted file mode 100644 index aead54dd63..0000000000 --- a/frappe/tests/test_bot.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: MIT. See LICENSE - -from frappe.tests.utils import FrappeTestCase - - -class TestBot(FrappeTestCase): - pass From 6cf168a56fde7672a87ebf2090f3283310a10fb3 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 24 Nov 2023 16:27:04 +0530 Subject: [PATCH 07/19] fix: bulk workflow action in background --- .../desk/doctype/bulk_update/bulk_update.py | 14 +++++++--- frappe/model/workflow.py | 28 +++++++++++++++---- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/frappe/desk/doctype/bulk_update/bulk_update.py b/frappe/desk/doctype/bulk_update/bulk_update.py index cb3d9c7bc6..27ffb4ffb8 100644 --- a/frappe/desk/doctype/bulk_update/bulk_update.py +++ b/frappe/desk/doctype/bulk_update/bulk_update.py @@ -49,16 +49,22 @@ def submit_cancel_or_update_docs(doctype, docnames, action="submit", data=None): if isinstance(docnames, str): docnames = frappe.parse_json(docnames) - if len(docnames) < 10: + if len(docnames) < 20: return _bulk_action(doctype, docnames, action, data) - elif len(docnames) <= 100: + elif len(docnames) <= 500: frappe.msgprint(_("Bulk operation is enqueued in background."), alert=True) frappe.enqueue( - _bulk_action, doctype=doctype, docnames=docnames, action=action, data=data, queue="long" + _bulk_action, + doctype=doctype, + docnames=docnames, + action=action, + data=data, + queue="short", + timeout=1000, ) else: frappe.throw( - _("Bulk operations only support up to 100 documents."), title=_("Too Many Documents") + _("Bulk operations only support up to 500 documents."), title=_("Too Many Documents") ) diff --git a/frappe/model/workflow.py b/frappe/model/workflow.py index 0d7ce13d95..cc51a55d90 100644 --- a/frappe/model/workflow.py +++ b/frappe/model/workflow.py @@ -1,6 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE import json +from collections import defaultdict from typing import TYPE_CHECKING, Union import frappe @@ -233,17 +234,30 @@ def get_workflow_field_value(workflow_name, field): @frappe.whitelist() def bulk_workflow_approval(docnames, doctype, action): - from collections import defaultdict + docnames = json.loads(docnames) + if len(docnames) < 20: + _bulk_workflow_action(docnames, doctype, action) + elif len(docnames) <= 500: + frappe.msgprint(_("Bulk {0} is enqueued in background.").format(action), alert=True) + frappe.enqueue( + _bulk_workflow_action, + docnames=docnames, + doctype=doctype, + action=action, + queue="short", + timeout=1000, + ) + else: + frappe.throw(_("Bulk approval only support up to 500 documents."), title=_("Too Many Documents")) + + +def _bulk_workflow_action(docnames, doctype, action): # dictionaries for logging failed_transactions = defaultdict(list) successful_transactions = defaultdict(list) - # WARN: message log is cleared - print("Clearing frappe.message_log...") frappe.clear_messages() - - docnames = json.loads(docnames) for (idx, docname) in enumerate(docnames, 1): message_dict = {} try: @@ -308,7 +322,9 @@ def print_workflow_log(messages, title, doctype, indicator): html = f"
{doc}
" msg += html - frappe.msgprint(msg, title=_("Workflow Status"), indicator=indicator, is_minimizable=True) + frappe.msgprint( + msg, title=_("Workflow Status"), indicator=indicator, is_minimizable=True, realtime=True + ) @frappe.whitelist() From 838a49ebc234d2fbdbb2fc906922573539987805 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Fri, 24 Nov 2023 17:13:34 +0530 Subject: [PATCH 08/19] fix(setup_wizard): don't suppress original exception Raise it so that the user can know what went wrong Signed-off-by: Akhil Narang --- frappe/desk/page/setup_wizard/setup_wizard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index 3a2b369a23..09615e27e1 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -84,7 +84,7 @@ def process_setup_stages(stages, user_input, is_background_task=False): except Exception: handle_setup_exception(user_input) if not is_background_task: - return {"status": "fail", "fail": current_task.get("fail_msg")} + raise frappe.publish_realtime( "setup_task", {"status": "fail", "fail_msg": current_task.get("fail_msg")}, From 9aec553b89d03daee35cea6ff6ebdfe31ba5a07b Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Fri, 24 Nov 2023 17:26:34 +0530 Subject: [PATCH 09/19] fix(setup_wizard): sync password character limit with other places (#23419) Signed-off-by: Akhil Narang --- frappe/desk/page/setup_wizard/setup_wizard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js index 8d42b804cd..cf01678dd2 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.js +++ b/frappe/desk/page/setup_wizard/setup_wizard.js @@ -463,7 +463,7 @@ frappe.setup.slides_settings = [ fieldtype: "Data", options: "Email", }, - { fieldname: "password", label: __("Password"), fieldtype: "Password" }, + { fieldname: "password", label: __("Password"), fieldtype: "Password", length: 512 }, ], onload: function (slide) { From 461f479b4579793706f7d9533c646ff6b9d340f8 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Fri, 24 Nov 2023 17:43:44 +0530 Subject: [PATCH 10/19] feat(setup_wizard): store error in error log as well Signed-off-by: Akhil Narang --- frappe/desk/page/setup_wizard/setup_wizard.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index 09615e27e1..34059605e5 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -83,6 +83,7 @@ def process_setup_stages(stages, user_input, is_background_task=False): task.get("fn")(task.get("args")) except Exception: handle_setup_exception(user_input) + frappe.log_error(title=f"Setup failed: {current_task.get('fail_msg')}") if not is_background_task: raise frappe.publish_realtime( From bf509526629b734271f3d092f4d5890143f69769 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Fri, 24 Nov 2023 17:45:38 +0530 Subject: [PATCH 11/19] fix: `after_mapping` hook to run custom mapping functions --- frappe/model/mapper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/model/mapper.py b/frappe/model/mapper.py index ade401a83c..91eac003f7 100644 --- a/frappe/model/mapper.py +++ b/frappe/model/mapper.py @@ -163,6 +163,7 @@ def get_mapped_doc( if postprocess: postprocess(source_doc, target_doc) + ret_doc.run_method("after_mapping", source_doc) ret_doc.set_onload("load_after_mapping", True) if ( From ddac8af434ee5335b35383ec110a2b5991b7e6f9 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 24 Nov 2023 18:16:02 +0530 Subject: [PATCH 12/19] fix: ignore duplicate contact creation (#23423) --- frappe/core/doctype/user/user.py | 40 ++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index d7c333ac44..028af756df 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -1227,27 +1227,31 @@ def create_contact(user, ignore_links=False, ignore_mandatory=False): contact_name = get_contact_name(user.email) if not contact_name: - contact = frappe.get_doc( - { - "doctype": "Contact", - "first_name": user.first_name, - "last_name": user.last_name, - "user": user.name, - "gender": user.gender, - } - ) + try: + contact = frappe.get_doc( + { + "doctype": "Contact", + "first_name": user.first_name, + "last_name": user.last_name, + "user": user.name, + "gender": user.gender, + } + ) - if user.email: - contact.add_email(user.email, is_primary=True) + if user.email: + contact.add_email(user.email, is_primary=True) - if user.phone: - contact.add_phone(user.phone, is_primary_phone=True) + if user.phone: + contact.add_phone(user.phone, is_primary_phone=True) - if user.mobile_no: - contact.add_phone(user.mobile_no, is_primary_mobile_no=True) - contact.insert( - ignore_permissions=True, ignore_links=ignore_links, ignore_mandatory=ignore_mandatory - ) + if user.mobile_no: + contact.add_phone(user.mobile_no, is_primary_mobile_no=True) + + contact.insert( + ignore_permissions=True, ignore_links=ignore_links, ignore_mandatory=ignore_mandatory + ) + except frappe.DuplicateEntryError: + pass else: contact = frappe.get_doc("Contact", contact_name) contact.first_name = user.first_name From d48c3f5f608e2275a4391f8e3abbb071942cbac1 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Fri, 24 Nov 2023 18:25:07 +0530 Subject: [PATCH 13/19] refactor: return message in response as well Signed-off-by: Akhil Narang --- frappe/desk/page/setup_wizard/setup_wizard.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index 34059605e5..e3002e89a5 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -83,12 +83,14 @@ def process_setup_stages(stages, user_input, is_background_task=False): task.get("fn")(task.get("args")) except Exception: handle_setup_exception(user_input) - frappe.log_error(title=f"Setup failed: {current_task.get('fail_msg')}") + message = current_task.get("fail_msg") if current_task else "Failed to complete setup" + frappe.log_error(title=f"Setup failed: {message}") if not is_background_task: + frappe.response["setup_wizard_failure_message"] = message raise frappe.publish_realtime( "setup_task", - {"status": "fail", "fail_msg": current_task.get("fail_msg")}, + {"status": "fail", "fail_msg": message}, user=frappe.session.user, ) else: From db3ffaf6587a64c78b4af3ad92eeaf2ad8b58f0c Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Fri, 24 Nov 2023 18:25:31 +0530 Subject: [PATCH 14/19] refactor: check for last response error Signed-off-by: Akhil Narang --- frappe/desk/page/setup_wizard/setup_wizard.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js index 8d42b804cd..268d0a86bc 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.js +++ b/frappe/desk/page/setup_wizard/setup_wizard.js @@ -196,7 +196,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { this.abort_setup(r.message.fail); } }, - error: () => this.abort_setup("Error in setup"), + error: () => this.abort_setup(), }); } @@ -213,7 +213,11 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { abort_setup(fail_msg) { this.$working_state.find(".state-icon-container").html(""); - fail_msg = fail_msg ? fail_msg : __("Failed to complete setup"); + fail_msg = fail_msg + ? fail_msg + : frappe.last_response.setup_wizard_failure_message + ? frappe.last_response.setup_wizard_failure_message + : __("Failed to complete setup"); this.update_setup_message("Could not start up: " + fail_msg); From d67a6d168eb8f10b50d93fc567555f3f016643d4 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Mon, 27 Nov 2023 11:30:33 +0530 Subject: [PATCH 15/19] fix(commands/scheduler): typo (#23441) * fix(commands/scheduler): typo Signed-off-by: Akhil Narang * chore: Update frappe/commands/scheduler.py [skip ci] --------- Signed-off-by: Akhil Narang Co-authored-by: Ankush Menat --- frappe/commands/scheduler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/commands/scheduler.py b/frappe/commands/scheduler.py index 5d453e3568..cf760cf4f0 100755 --- a/frappe/commands/scheduler.py +++ b/frappe/commands/scheduler.py @@ -202,7 +202,7 @@ def start_scheduler(): def start_worker( queue, quiet=False, rq_username=None, rq_password=None, burst=False, strategy=None ): - """Start a backgrond worker""" + """Start a background worker""" from frappe.utils.background_jobs import start_worker start_worker( @@ -225,7 +225,7 @@ def start_worker( @click.option("--quiet", is_flag=True, default=False, help="Hide Log Outputs") @click.option("--burst", is_flag=True, default=False, help="Run Worker in Burst mode.") def start_worker_pool(queue, quiet=False, num_workers=2, burst=False): - """Start a backgrond worker""" + """Start a pool of background workers""" from frappe.utils.background_jobs import start_worker_pool start_worker_pool( From 884e980526daffc6bd9b81acffc063b0e64e03a2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 27 Nov 2023 12:04:29 +0530 Subject: [PATCH 16/19] fix: Simpler oauth token validity checks The code is currently 1. Getting token expiry time (in system tz) 2. Adding system tz to make it tz aware 3. Converting it to UTC 4. Getting current UTC time and comparing. We can just get current system tz time and compare directly. --- frappe/oauth.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/frappe/oauth.py b/frappe/oauth.py index ebd6b91ae7..bf7abeb424 100644 --- a/frappe/oauth.py +++ b/frappe/oauth.py @@ -11,7 +11,7 @@ from oauthlib.openid import RequestValidator import frappe from frappe.auth import LoginManager -from frappe.utils.data import get_system_timezone +from frappe.utils.data import get_system_timezone, now_datetime class OAuthWebRequestValidator(RequestValidator): @@ -240,13 +240,7 @@ class OAuthWebRequestValidator(RequestValidator): def validate_bearer_token(self, token, scopes, request): # Remember to check expiration and scope membership otoken = frappe.get_doc("OAuth Bearer Token", token) - token_expiration_local = otoken.expiration_time.replace( - tzinfo=pytz.timezone(get_system_timezone()) - ) - token_expiration_utc = token_expiration_local.astimezone(pytz.utc) - is_token_valid = ( - datetime.datetime.now(pytz.UTC) < token_expiration_utc - ) and otoken.status != "Revoked" + is_token_valid = (now_datetime() < otoken.expiration_time) and otoken.status != "Revoked" client_scopes = frappe.db.get_value("OAuth Client", otoken.client, "scopes").split( get_url_delimiter() ) From 4a25451f44e84e0afcb01fc51f5cf88517ed7387 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 27 Nov 2023 13:56:54 +0530 Subject: [PATCH 17/19] refactor: Add tabs to "System Settings" --- .../system_settings/system_settings.json | 127 +++++++++++------- .../system_settings/system_settings.py | 1 - 2 files changed, 76 insertions(+), 52 deletions(-) diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index 54b23094f8..042b899dd9 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -23,38 +23,22 @@ "float_precision", "currency_precision", "rounding_method", - "sec_backup_limit", - "backup_limit", - "encrypt_backup", - "background_workers", - "enable_scheduler", - "dormant_days", "permissions", "apply_strict_user_permissions", "column_break_21", - "allow_guests_to_upload_files", - "force_web_capture_mode_for_uploads", + "security_tab", "security", "session_expiry", "document_share_key_expiry", - "column_break_13", "deny_multiple_sessions", + "disable_user_pass_login", + "column_break_13", "allow_login_using_mobile_number", "allow_login_using_user_name", - "disable_user_pass_login", "login_with_email_link", "login_with_email_link_expiry", - "allow_error_traceback", "strip_exif_metadata_from_uploaded_images", "allow_older_web_view_links", - "password_settings", - "logout_on_password_reset", - "force_user_to_reset_password", - "reset_password_link_expiry_duration", - "password_reset_limit", - "column_break_31", - "enable_password_policy", - "minimum_password_score", "brute_force_security", "allow_consecutive_login_attempts", "column_break_34", @@ -66,6 +50,16 @@ "two_factor_method", "lifespan_qrcode_image", "otp_issuer_name", + "password_tab", + "password_settings", + "logout_on_password_reset", + "force_user_to_reset_password", + "reset_password_link_expiry_duration", + "password_reset_limit", + "column_break_31", + "enable_password_policy", + "minimum_password_score", + "email_tab", "email", "email_footer_address", "email_retry_limit", @@ -75,17 +69,30 @@ "attach_view_link", "welcome_email_template", "reset_password_template", - "prepared_report_section", - "max_auto_email_report_per_user", + "files_tab", + "files_section", + "max_file_size", + "allow_guests_to_upload_files", + "force_web_capture_mode_for_uploads", + "column_break_uqma", + "allowed_file_extensions", + "updates_tab", "system_updates_section", "disable_system_update_notification", "disable_change_log_notification", + "backups_tab", + "sec_backup_limit", + "backup_limit", + "encrypt_backup", + "advanced_tab", + "prepared_report_section", + "max_auto_email_report_per_user", + "background_workers", + "enable_scheduler", + "dormant_days", "telemetry_section", - "enable_telemetry", - "files_section", - "max_file_size", - "column_break_uqma", - "allowed_file_extensions" + "allow_error_traceback", + "enable_telemetry" ], "fields": [ { @@ -126,7 +133,6 @@ "read_only": 1 }, { - "collapsible": 1, "fieldname": "date_and_number_format", "fieldtype": "Section Break", "label": "Date and Number Format" @@ -171,10 +177,8 @@ "options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9" }, { - "collapsible": 1, "fieldname": "sec_backup_limit", - "fieldtype": "Section Break", - "label": "Backups" + "fieldtype": "Section Break" }, { "default": "3", @@ -184,7 +188,6 @@ "label": "Number of Backups" }, { - "collapsible": 1, "fieldname": "background_workers", "fieldtype": "Section Break", "label": "Background Workers" @@ -198,7 +201,6 @@ "label": "Enable Scheduled Jobs" }, { - "collapsible": 1, "fieldname": "permissions", "fieldtype": "Section Break", "label": "Permissions" @@ -211,10 +213,8 @@ "label": "Apply Strict User Permissions" }, { - "collapsible": 1, "fieldname": "security", - "fieldtype": "Section Break", - "label": "Security" + "fieldtype": "Section Break" }, { "default": "170:00", @@ -255,7 +255,6 @@ "label": "Show Full Error and Allow Reporting of Issues to the Developer" }, { - "collapsible": 1, "fieldname": "password_settings", "fieldtype": "Section Break", "label": "Password" @@ -286,7 +285,6 @@ "options": "2\n3\n4" }, { - "collapsible": 1, "fieldname": "brute_force_security", "fieldtype": "Section Break", "label": "Brute Force Security" @@ -309,7 +307,6 @@ "label": "Allow Login After Fail" }, { - "collapsible": 1, "fieldname": "two_factor_authentication", "fieldtype": "Section Break", "label": "Two Factor Authentication" @@ -338,6 +335,7 @@ }, { "default": "OTP App", + "depends_on": "enable_two_factor_auth", "description": "Choose authentication method to be used by all users", "fieldname": "two_factor_method", "fieldtype": "Select", @@ -345,7 +343,7 @@ "options": "OTP App\nSMS\nEmail" }, { - "depends_on": "eval:doc.two_factor_method == \"OTP App\"", + "depends_on": "eval:doc.enable_two_factor_auth && doc.two_factor_method == \"OTP App\"", "description": "Time in seconds to retain QR code image on server. Min:240", "fieldname": "lifespan_qrcode_image", "fieldtype": "Int", @@ -359,10 +357,8 @@ "label": "OTP Issuer Name" }, { - "collapsible": 1, "fieldname": "email", - "fieldtype": "Section Break", - "label": "Email" + "fieldtype": "Section Break" }, { "description": "Your organization name and address for the email footer.", @@ -430,7 +426,6 @@ "label": "Include Web View Link in Email" }, { - "collapsible": 1, "fieldname": "prepared_report_section", "fieldtype": "Section Break", "label": "Reports" @@ -456,10 +451,8 @@ "label": "Encrypt Backups" }, { - "collapsible": 1, "fieldname": "system_updates_section", - "fieldtype": "Section Break", - "label": "System Updates" + "fieldtype": "Section Break" }, { "default": "0", @@ -547,7 +540,6 @@ "label": "Disable Document Sharing" }, { - "collapsible": 1, "fieldname": "telemetry_section", "fieldtype": "Section Break", "label": "Telemetry" @@ -578,10 +570,8 @@ "label": "Force Web Capture Mode for Uploads" }, { - "collapsible": 1, "fieldname": "files_section", - "fieldtype": "Section Break", - "label": "Files" + "fieldtype": "Section Break" }, { "fieldname": "max_file_size", @@ -598,12 +588,47 @@ "fieldname": "allowed_file_extensions", "fieldtype": "Small Text", "label": "Allowed File Extensions" + }, + { + "fieldname": "security_tab", + "fieldtype": "Tab Break", + "label": "Login" + }, + { + "fieldname": "email_tab", + "fieldtype": "Tab Break", + "label": "Email" + }, + { + "fieldname": "files_tab", + "fieldtype": "Tab Break", + "label": "Files" + }, + { + "fieldname": "updates_tab", + "fieldtype": "Tab Break", + "label": "Updates" + }, + { + "fieldname": "backups_tab", + "fieldtype": "Tab Break", + "label": "Backups" + }, + { + "fieldname": "advanced_tab", + "fieldtype": "Tab Break", + "label": "Advanced" + }, + { + "fieldname": "password_tab", + "fieldtype": "Tab Break", + "label": "Password" } ], "icon": "fa fa-cog", "issingle": 1, "links": [], - "modified": "2023-10-17 16:12:28.145496", + "modified": "2023-11-27 13:09:13.849522", "modified_by": "Administrator", "module": "Core", "name": "System Settings", diff --git a/frappe/core/doctype/system_settings/system_settings.py b/frappe/core/doctype/system_settings/system_settings.py index 1c64a22e54..1a548b580b 100644 --- a/frappe/core/doctype/system_settings/system_settings.py +++ b/frappe/core/doctype/system_settings/system_settings.py @@ -93,7 +93,6 @@ class SystemSettings(Document): time_zone: DF.Literal two_factor_method: DF.Literal["OTP App", "SMS", "Email"] welcome_email_template: DF.Link | None - # end: auto-generated types def validate(self): from frappe.twofactor import toggle_two_factor_auth From 2c34c71b43f94606a913a803c1d56a51a6f865d4 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 27 Nov 2023 14:08:45 +0530 Subject: [PATCH 18/19] refactor: Cleanup Login Settings in System Settings --- .../system_settings/system_settings.json | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index 042b899dd9..45fa621cec 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -26,19 +26,20 @@ "permissions", "apply_strict_user_permissions", "column_break_21", + "allow_older_web_view_links", "security_tab", "security", "session_expiry", "document_share_key_expiry", + "column_break_txqh", "deny_multiple_sessions", "disable_user_pass_login", - "column_break_13", + "login_methods_section", "allow_login_using_mobile_number", "allow_login_using_user_name", + "column_break_uhqk", "login_with_email_link", "login_with_email_link_expiry", - "strip_exif_metadata_from_uploaded_images", - "allow_older_web_view_links", "brute_force_security", "allow_consecutive_login_attempts", "column_break_34", @@ -74,6 +75,7 @@ "max_file_size", "allow_guests_to_upload_files", "force_web_capture_mode_for_uploads", + "strip_exif_metadata_from_uploaded_images", "column_break_uqma", "allowed_file_extensions", "updates_tab", @@ -223,10 +225,6 @@ "fieldtype": "Data", "label": "Session Expiry (idle timeout)" }, - { - "fieldname": "column_break_13", - "fieldtype": "Column Break" - }, { "default": "0", "description": "Note: Multiple sessions will be allowed in case of mobile device", @@ -623,12 +621,25 @@ "fieldname": "password_tab", "fieldtype": "Tab Break", "label": "Password" + }, + { + "fieldname": "column_break_txqh", + "fieldtype": "Column Break" + }, + { + "fieldname": "login_methods_section", + "fieldtype": "Section Break", + "label": "Login Methods" + }, + { + "fieldname": "column_break_uhqk", + "fieldtype": "Column Break" } ], "icon": "fa fa-cog", "issingle": 1, "links": [], - "modified": "2023-11-27 13:09:13.849522", + "modified": "2023-11-27 14:08:01.927794", "modified_by": "Administrator", "module": "Core", "name": "System Settings", From 597a53e4c12105741f14fcc6b37cf42327bccdfa Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 27 Nov 2023 14:20:27 +0530 Subject: [PATCH 19/19] fix: allow internal routing using workspace shortcut url option --- .../doctype/workspace_shortcut/workspace_shortcut.json | 5 ++--- frappe/public/js/frappe/widgets/widget_dialog.js | 7 +++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.json b/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.json index 854305ad80..e47487eaaf 100644 --- a/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.json +++ b/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.json @@ -102,8 +102,7 @@ "fieldname": "url", "fieldtype": "Data", "in_list_view": 1, - "label": "URL", - "options": "URL" + "label": "URL" }, { "depends_on": "eval:doc.doc_view == \"Kanban\"", @@ -116,7 +115,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-07-18 16:12:53.546430", + "modified": "2023-11-27 14:13:38.489737", "modified_by": "Administrator", "module": "Desk", "name": "Workspace Shortcut", diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js index abe7352d9d..14ce39355a 100644 --- a/frappe/public/js/frappe/widgets/widget_dialog.js +++ b/frappe/public/js/frappe/widgets/widget_dialog.js @@ -419,7 +419,6 @@ class ShortcutDialog extends WidgetDialog { fieldtype: "Data", fieldname: "url", label: "URL", - options: "URL", default: "", depends_on: (s) => s.type == "URL", mandatory_depends_on: (s) => s.type == "URL", @@ -547,7 +546,11 @@ class ShortcutDialog extends WidgetDialog { data.label = data.label ? data.label : frappe.model.unscrub(data.link_to); if (data.url) { - !validate_url(data.url) && + let _url = data.url; + if (data.url.startsWith("/")) { + _url = frappe.urllib.get_base_url() + data.url; + } + !validate_url(_url) && frappe.throw({ message: __("{0} is not a valid URL", [data.url]), title: __("Invalid URL"),