From b2d145f065626d89c00eb714fb89f2b2ed5245cf Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 30 Aug 2022 16:31:19 +0530 Subject: [PATCH 01/19] perf: short-circuit guest connection and basic perf tests (#17988) (#17991) * perf: reorder condition to avoid redis call * test: basic perf tests (cherry picked from commit f5b8e5f015204ddb15e8507f4d169e93de4a30dd) Co-authored-by: Ankush Menat --- frappe/app.py | 2 +- frappe/auth.py | 6 ++-- frappe/tests/test_perf.py | 64 +++++++++++++++++++++++++++++++++++++++ frappe/tests/utils.py | 18 +++++++++++ 4 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 frappe/tests/test_perf.py diff --git a/frappe/app.py b/frappe/app.py index 298d94b06c..3cf1bf555a 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -46,7 +46,7 @@ class RequestContext: @local_manager.middleware @Request.application -def application(request): +def application(request: Request): response = None try: diff --git a/frappe/auth.py b/frappe/auth.py index fec7ade839..7b64ecc083 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -226,14 +226,16 @@ class LoginManager: def clear_active_sessions(self): """Clear other sessions of the current user if `deny_multiple_sessions` is not set""" + if frappe.session.user == "Guest": + return + if not ( cint(frappe.conf.get("deny_multiple_sessions")) or cint(frappe.db.get_system_setting("deny_multiple_sessions")) ): return - if frappe.session.user != "Guest": - clear_sessions(frappe.session.user, keep_current=True) + clear_sessions(frappe.session.user, keep_current=True) def authenticate(self, user: str = None, pwd: str = None): from frappe.core.doctype.user.user import User diff --git a/frappe/tests/test_perf.py b/frappe/tests/test_perf.py new file mode 100644 index 0000000000..6e23fb9856 --- /dev/null +++ b/frappe/tests/test_perf.py @@ -0,0 +1,64 @@ +""" +This file contains multiple primitive tests for avoiding performance regressions. + +- Time bound tests: Benchmarks are done on GHA before adding numbers +- Query count tests: More than expected # of queries for any action is frequent source of + performance issues. This guards against such problems. + + +E.g. We know get_controller is supposed to be cached and hence shouldn't make query post first +query. This test can be written like this. + +>>> def test_controller_caching(self): +>>> +>>> get_controller("User") # <- "warm up code" +>>> with self.assertQueryCount(0): +>>> get_controller("User") + +""" +import unittest + +import frappe +from frappe.model.base_document import get_controller +from frappe.tests.utils import FrappeTestCase +from frappe.website.path_resolver import PathResolver + + +class TestPerformance(FrappeTestCase): + def reset_request_specific_caches(self): + # To simulate close to request level of handling + frappe.destroy() # releases everything on frappe.local + frappe.init(site=self.TEST_SITE) + frappe.connect() + frappe.clear_cache() + + def setUp(self) -> None: + self.reset_request_specific_caches() + + def test_meta_caching(self): + frappe.get_meta("User") + + with self.assertQueryCount(0): + frappe.get_meta("User") + + def test_controller_caching(self): + + get_controller("User") + with self.assertQueryCount(0): + get_controller("User") + + def test_db_value_cache(self): + """Link validation if repeated should just use db.value_cache, hence no extra queries""" + doc = frappe.get_last_doc("User") + doc.get_invalid_links() + + with self.assertQueryCount(0): + doc.get_invalid_links() + + @unittest.skip("Not implemented") + def test_homepage_resolver(self): + paths = ["/", "/app"] + for path in paths: + PathResolver(path).resolve() + with self.assertQueryCount(1): + PathResolver(path).resolve() diff --git a/frappe/tests/utils.py b/frappe/tests/utils.py index 79dfd76238..aa27a5eb01 100644 --- a/frappe/tests/utils.py +++ b/frappe/tests/utils.py @@ -19,10 +19,13 @@ class FrappeTestCase(unittest.TestCase): otherwise this class will become ineffective. """ + TEST_SITE = "test_site" + SHOW_TRANSACTION_COMMIT_WARNINGS = False @classmethod def setUpClass(cls) -> None: + cls.TEST_SITE = getattr(frappe.local, "site", None) or cls.TEST_SITE # flush changes done so far to avoid flake frappe.db.commit() frappe.db.begin() @@ -64,6 +67,21 @@ class FrappeTestCase(unittest.TestCase): else: self.assertEqual(expected, actual, msg=msg) + @contextmanager + def assertQueryCount(self, count): + def _sql_with_count(*args, **kwargs): + frappe.db.sql_query_count += 1 + return orig_sql(*args, **kwargs) + + try: + orig_sql = frappe.db.sql + frappe.db.sql_query_count = 0 + frappe.db.sql = _sql_with_count + yield + self.assertLessEqual(frappe.db.sql_query_count, count) + finally: + frappe.db.sql = orig_sql + def _commit_watcher(): import traceback From 40040ebd19a8ec01fc8d3d2af00b0642d61b96a7 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 30 Aug 2022 17:34:18 +0530 Subject: [PATCH 02/19] chore: add route history to default log clean up --- frappe/core/doctype/log_settings/log_settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/core/doctype/log_settings/log_settings.py b/frappe/core/doctype/log_settings/log_settings.py index 8009324e70..4a519dcaf4 100644 --- a/frappe/core/doctype/log_settings/log_settings.py +++ b/frappe/core/doctype/log_settings/log_settings.py @@ -16,6 +16,7 @@ DEFAULT_LOGTYPES_RETENTION = { "Email Queue": 30, "Error Snapshot": 30, "Scheduled Job Log": 90, + "Route History": 90, } From a67abbe5b29c5df8a1cdd0084d2a311d74f50091 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 30 Aug 2022 18:06:51 +0530 Subject: [PATCH 03/19] fix(patch): only run patch if webform has is_multi_step_form field (#17993) (#17994) (cherry picked from commit 6a24748f9a7d200876fa1bdad825a9f8895688fa) Co-authored-by: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> --- frappe/patches.txt | 2 +- frappe/patches/v14_0/update_multistep_webforms.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/patches.txt b/frappe/patches.txt index e4facb7e3d..b90d02e7bc 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -210,4 +210,4 @@ frappe.patches.v14_0.set_document_expiry_default frappe.patches.v14_0.delete_data_migration_tool frappe.patches.v14_0.set_suspend_email_queue_default frappe.patches.v14_0.different_encryption_key -frappe.patches.v14_0.update_multistep_webforms +frappe.patches.v14_0.update_multistep_webforms #30-08-2022 diff --git a/frappe/patches/v14_0/update_multistep_webforms.py b/frappe/patches/v14_0/update_multistep_webforms.py index a4a2885c4a..c87663c254 100644 --- a/frappe/patches/v14_0/update_multistep_webforms.py +++ b/frappe/patches/v14_0/update_multistep_webforms.py @@ -4,6 +4,9 @@ import frappe def execute(): frappe.reload_doctype("Web Form") + if not frappe.db.has_column("Web Form", "is_multi_step_form"): + return + for web_form in frappe.get_all("Web Form", filters={"is_multi_step_form": 1}): web_form_fields = frappe.get_doc("Web Form", web_form.name).web_form_fields for web_form_field in web_form_fields: From 9299df339bfe6f37e9330aa349f5bc2da15e5b44 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 30 Aug 2022 18:20:45 +0530 Subject: [PATCH 04/19] chore: dont rerun patch Rerunning this isn't required and might cause unexepcted behaviour [skip ci] --- frappe/patches.txt | 2 +- frappe/patches/v14_0/update_multistep_webforms.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/frappe/patches.txt b/frappe/patches.txt index b90d02e7bc..e4facb7e3d 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -210,4 +210,4 @@ frappe.patches.v14_0.set_document_expiry_default frappe.patches.v14_0.delete_data_migration_tool frappe.patches.v14_0.set_suspend_email_queue_default frappe.patches.v14_0.different_encryption_key -frappe.patches.v14_0.update_multistep_webforms #30-08-2022 +frappe.patches.v14_0.update_multistep_webforms diff --git a/frappe/patches/v14_0/update_multistep_webforms.py b/frappe/patches/v14_0/update_multistep_webforms.py index c87663c254..9919ef6e15 100644 --- a/frappe/patches/v14_0/update_multistep_webforms.py +++ b/frappe/patches/v14_0/update_multistep_webforms.py @@ -2,8 +2,6 @@ import frappe def execute(): - frappe.reload_doctype("Web Form") - if not frappe.db.has_column("Web Form", "is_multi_step_form"): return @@ -12,4 +10,3 @@ def execute(): for web_form_field in web_form_fields: if web_form_field.fieldtype == "Section Break" and web_form_field.idx != 1: frappe.db.set_value("Web Form Field", web_form_field.name, "fieldtype", "Page Break") - frappe.db.commit() From cfdc57add14f5f730b44f6cb86944968643407d3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 31 Aug 2022 11:58:46 +0530 Subject: [PATCH 05/19] feat: add button to collapse all in tree view (#17992) (#18003) * Added button to collapse tree root node * Removed unneccessary space * Collapse all expanded nodes * style: prettier * refactor: load first level root nodes by default Co-authored-by: Ankush Menat (cherry picked from commit e8b0b991ab3bedc6e3be926053b852ec156a2f9d) Co-authored-by: Nikhil Kothari --- frappe/public/js/frappe/views/treeview.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/public/js/frappe/views/treeview.js b/frappe/public/js/frappe/views/treeview.js index 2c8c5749d8..96e3bf7227 100644 --- a/frappe/public/js/frappe/views/treeview.js +++ b/frappe/public/js/frappe/views/treeview.js @@ -94,6 +94,10 @@ frappe.views.TreeView = class TreeView { this.page.main.addClass("frappe-card"); if (this.opts.show_expand_all) { + this.page.add_inner_button(__("Collapse All"), function () { + me.tree.load_children(me.tree.root_node, false); + }); + this.page.add_inner_button(__("Expand All"), function () { me.tree.load_children(me.tree.root_node, true); }); From d182a6c40c09f423416a7615ebf4db452813ca97 Mon Sep 17 00:00:00 2001 From: Ritwik Puri Date: Wed, 31 Aug 2022 13:51:23 +0530 Subject: [PATCH 06/19] chore: add depreciation warning for event streaming (#17987) inlieu of event_streaming app --- frappe/patches.txt | 1 + .../patches/v14_0/event_streaming_deprecation_warning.py | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 frappe/patches/v14_0/event_streaming_deprecation_warning.py diff --git a/frappe/patches.txt b/frappe/patches.txt index e4facb7e3d..3a902cb35e 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -196,6 +196,7 @@ frappe.patches.v14_0.log_settings_migration frappe.patches.v14_0.setup_likes_from_feedback frappe.patches.v14_0.update_webforms frappe.patches.v14_0.delete_payment_gateways +frappe.patches.v14_0.event_streaming_deprecation_warning [post_model_sync] frappe.patches.v14_0.drop_data_import_legacy diff --git a/frappe/patches/v14_0/event_streaming_deprecation_warning.py b/frappe/patches/v14_0/event_streaming_deprecation_warning.py new file mode 100644 index 0000000000..b709c9c1d3 --- /dev/null +++ b/frappe/patches/v14_0/event_streaming_deprecation_warning.py @@ -0,0 +1,9 @@ +import click + + +def execute(): + click.secho( + "Event Streaming is moved to a separate app in version 15.\n" + "When upgrading to Frappe version-15, Please install the 'Event Streaming' app to continue using them: https://github.com/frappe/event_streaming", + fg="yellow", + ) From ac9cfe7f9fb12e1eb9c7e5d85536b55539dc7840 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 1 Sep 2022 15:27:00 +0530 Subject: [PATCH 07/19] ci: disable parallel tests (#18008) Not required on stable branches. --- .github/workflows/server-mariadb-tests.yml | 7 +------ .github/workflows/server-postgres-tests.yml | 7 +------ .github/workflows/ui-tests.yml | 2 +- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/.github/workflows/server-mariadb-tests.yml b/.github/workflows/server-mariadb-tests.yml index 6f2d9dc7ef..bf547f1f03 100644 --- a/.github/workflows/server-mariadb-tests.yml +++ b/.github/workflows/server-mariadb-tests.yml @@ -22,8 +22,6 @@ jobs: strategy: fail-fast: false - matrix: - container: [1, 2] services: mariadb: @@ -122,7 +120,4 @@ jobs: - name: Run Tests if: ${{ steps.check-build.outputs.build == 'strawberry' }} - run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --use-orchestrator - env: - CI_BUILD_ID: ${{ github.run_id }} - ORCHESTRATOR_URL: http://test-orchestrator.frappe.io + run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests diff --git a/.github/workflows/server-postgres-tests.yml b/.github/workflows/server-postgres-tests.yml index 659fd00472..4ec193d8ba 100644 --- a/.github/workflows/server-postgres-tests.yml +++ b/.github/workflows/server-postgres-tests.yml @@ -21,8 +21,6 @@ jobs: strategy: fail-fast: false - matrix: - container: [1, 2] services: postgres: @@ -125,7 +123,4 @@ jobs: - name: Run Tests if: ${{ steps.check-build.outputs.build == 'strawberry' }} - run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --use-orchestrator - env: - CI_BUILD_ID: ${{ github.run_id }} - ORCHESTRATOR_URL: http://test-orchestrator.frappe.io + run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index f6293db18f..7a955f53d2 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false matrix: - containers: [1, 2, 3] + containers: [1, 2] name: UI Tests (Cypress) From 0293c113c6842ac3630c26fc3d0436356752771f Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 1 Sep 2022 16:53:20 +0200 Subject: [PATCH 08/19] feat: add lang to safe globals (cherry picked from commit 836fd6ef78b20b200210af7440188a0a46f8477f) --- frappe/utils/safe_exec.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 6fdfba19e3..f2807dfc68 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -181,6 +181,7 @@ def get_safe_globals(): run_script=run_script, is_job_queued=is_job_queued, get_visible_columns=get_visible_columns, + lang=getattr(frappe.local, "lang", "en"), ) add_module_properties( From 17cbf4a8b06caa883cc6573dc24a2d04e5163abf Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 1 Sep 2022 17:12:58 +0200 Subject: [PATCH 09/19] refactor: move lang into frappe namespace (cherry picked from commit d0b753a25d99ec67af316a2f0ef3fd58aa2e098b) --- frappe/utils/safe_exec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index f2807dfc68..65967a47ba 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -167,6 +167,7 @@ def get_safe_globals(): rollback=frappe.db.rollback, add_index=frappe.db.add_index, ), + lang=getattr(frappe.local, "lang", "en"), ), FrappeClient=FrappeClient, style=frappe._dict(border_color="#d1d8dd"), @@ -181,7 +182,6 @@ def get_safe_globals(): run_script=run_script, is_job_queued=is_job_queued, get_visible_columns=get_visible_columns, - lang=getattr(frappe.local, "lang", "en"), ) add_module_properties( From 13015c2dc802d5725272b332cd1c51ba01cdac15 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 2 Sep 2022 11:13:26 +0530 Subject: [PATCH 10/19] fix: comma looks like a decimal in report summary widget (#18014) (#18017) [skip ci] (cherry picked from commit 7b646ccec53fadddf25ed7f62e17af4ac1b84452) Co-authored-by: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> --- frappe/public/scss/desk/report.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/scss/desk/report.scss b/frappe/public/scss/desk/report.scss index 8ed0fb740c..a2c940dc58 100644 --- a/frappe/public/scss/desk/report.scss +++ b/frappe/public/scss/desk/report.scss @@ -196,8 +196,8 @@ &.blue { color: var(--blue-500); } // SIZE & SPACING - margin-top: 12px; - margin-bottom: 5px; + padding-top: 12px; + padding-bottom: 5px; // LAYOUT text-align: center; From eaaae9814cdaeec70d279e52349e727bbcf1fb75 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 2 Sep 2022 11:22:20 +0530 Subject: [PATCH 11/19] feat: review Custom and Build workspace, move background Job to Build Workspace (#17981) (#18018) * feat: review link for Custom, build and NavBar * feat: review link for Custom, build and NavBar * feat: review link for Custom, build and NavBar * feat: review link for Custom, build and NavBar * feat: review link for Custom, build and NavBar * feat: review link for Custom, build and NavBar * chore: linter json * fix: replace Block Module By Module Profile * chore: cypress test * chore: reset package.json to develop * refactor: workspace link, system logs section - removed workspace listview link, visiting list view isn't required for practically all the tasks - Frappe Logs -> System Logs [skip ci] (cherry picked from commit df9e8c62dd0505b41d606159c78802c3921bf895) Co-authored-by: HENRY Florian --- cypress/integration/workspace_blocks.js | 1 + frappe/core/workspace/build/build.json | 140 ++++++++++++------ frappe/core/workspace/settings/settings.json | 73 ++++----- .../customization/customization.json | 17 ++- frappe/utils/install.py | 6 +- 5 files changed, 139 insertions(+), 98 deletions(-) diff --git a/cypress/integration/workspace_blocks.js b/cypress/integration/workspace_blocks.js index 5b3167b3ac..774595b6b8 100644 --- a/cypress/integration/workspace_blocks.js +++ b/cypress/integration/workspace_blocks.js @@ -71,6 +71,7 @@ context("Workspace Blocks", () => { url: "api/method/frappe.desk.form.load.getdoctype", }).as("get_doctype"); + cy.visit("/app/tools"); cy.get(".codex-editor__redactor .ce-block"); cy.get(".standard-actions .btn-secondary[data-label=Edit]").click(); diff --git a/frappe/core/workspace/build/build.json b/frappe/core/workspace/build/build.json index c1c506ae3a..9282c50e67 100644 --- a/frappe/core/workspace/build/build.json +++ b/frappe/core/workspace/build/build.json @@ -1,6 +1,6 @@ { "charts": [], - "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"DocType\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Workspace\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Report\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Elements\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Modules\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Models\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Views\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Scripting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Packages\",\"col\":4}}]", + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"DocType\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Workspace\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Report\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Elements\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Modules\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Models\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Views\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Scripting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Packages\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"System Logs\",\"col\":4}}]", "creation": "2021-01-02 10:51:16.579957", "docstatus": 0, "doctype": "Workspace", @@ -13,7 +13,7 @@ { "hidden": 0, "is_query_report": 0, - "label": "Modules", + "label": "Models", "link_count": 0, "link_type": "DocType", "onboard": 0, @@ -23,9 +23,9 @@ { "hidden": 0, "is_query_report": 0, - "label": "Module Def", + "label": "DocType", "link_count": 0, - "link_to": "Module Def", + "link_to": "DocType", "link_type": "DocType", "onboard": 0, "only_for": "", @@ -34,9 +34,9 @@ { "hidden": 0, "is_query_report": 0, - "label": "Workspace", + "label": "Workflow", "link_count": 0, - "link_to": "Workspace", + "link_to": "Workflow", "link_type": "DocType", "onboard": 0, "only_for": "", @@ -45,9 +45,19 @@ { "hidden": 0, "is_query_report": 0, - "label": "Module Onboarding", + "label": "Scripting", "link_count": 0, - "link_to": "Module Onboarding", + "link_type": "DocType", + "onboard": 0, + "only_for": "", + "type": "Card Break" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Server Script", + "link_count": 0, + "link_to": "Server Script", "link_type": "DocType", "onboard": 0, "only_for": "", @@ -56,9 +66,9 @@ { "hidden": 0, "is_query_report": 0, - "label": "Block Module", + "label": "Client Script", "link_count": 0, - "link_to": "Block Module", + "link_to": "Client Script", "link_type": "DocType", "onboard": 0, "only_for": "", @@ -67,30 +77,56 @@ { "hidden": 0, "is_query_report": 0, - "label": "Models", + "label": "Scheduled Job Type", "link_count": 0, + "link_to": "Scheduled Job Type", "link_type": "DocType", "onboard": 0, "only_for": "", + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Packages", + "link_count": 2, + "onboard": 0, "type": "Card Break" }, { "hidden": 0, "is_query_report": 0, - "label": "DocType", + "label": "Package", "link_count": 0, - "link_to": "DocType", + "link_to": "Package", "link_type": "DocType", "onboard": 0, - "only_for": "", "type": "Link" }, { "hidden": 0, "is_query_report": 0, - "label": "Workflow", + "label": "Package Import", "link_count": 0, - "link_to": "Workflow", + "link_to": "Package Import", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Modules", + "link_count": 3, + "onboard": 0, + "type": "Card Break" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Module Def", + "link_count": 0, + "link_to": "Module Def", "link_type": "DocType", "onboard": 0, "only_for": "", @@ -99,11 +135,30 @@ { "hidden": 0, "is_query_report": 0, - "label": "Views", + "label": "Module Onboarding", "link_count": 0, + "link_to": "Module Onboarding", "link_type": "DocType", "onboard": 0, "only_for": "", + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Module Profile", + "link_count": 0, + "link_to": "Module Profile", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Views", + "link_count": 4, + "onboard": 0, "type": "Card Break" }, { @@ -131,9 +186,9 @@ { "hidden": 0, "is_query_report": 0, - "label": "Workspace", + "label": "Dashboard", "link_count": 0, - "link_to": "Workspace", + "link_to": "Dashboard", "link_type": "DocType", "onboard": 0, "only_for": "", @@ -142,71 +197,67 @@ { "hidden": 0, "is_query_report": 0, - "label": "Dashboard", + "label": "Workspace", "link_count": 0, - "link_to": "Dashboard", + "link_to": "Workspace", "link_type": "DocType", "onboard": 0, - "only_for": "", "type": "Link" }, { "hidden": 0, "is_query_report": 0, - "label": "Scripting", - "link_count": 0, - "link_type": "DocType", + "label": "System Logs", + "link_count": 6, "onboard": 0, - "only_for": "", "type": "Card Break" }, { "hidden": 0, "is_query_report": 0, - "label": "Server Script", + "label": "Background Jobs", "link_count": 0, - "link_to": "Server Script", - "link_type": "DocType", + "link_to": "background_jobs", + "link_type": "Page", "onboard": 0, - "only_for": "", "type": "Link" }, { "hidden": 0, "is_query_report": 0, - "label": "Client Script", + "label": "Scheduled Jobs Logs", "link_count": 0, - "link_to": "Client Script", + "link_to": "Scheduled Job Log", "link_type": "DocType", "onboard": 0, - "only_for": "", "type": "Link" }, { "hidden": 0, "is_query_report": 0, - "label": "Scheduled Job Type", + "label": "Error Logs", "link_count": 0, - "link_to": "Scheduled Job Type", + "link_to": "Error Log", "link_type": "DocType", "onboard": 0, - "only_for": "", "type": "Link" }, { "hidden": 0, "is_query_report": 0, - "label": "Packages", - "link_count": 2, + "label": "Error Snapshot", + "link_count": 0, + "link_to": "Error Snapshot", + "link_type": "DocType", "onboard": 0, - "type": "Card Break" + "type": "Link" }, { "hidden": 0, "is_query_report": 0, - "label": "Package", + "label": "Communication Logs", "link_count": 0, - "link_to": "Package", + "link_to": "Communication", "link_type": "DocType", "onboard": 0, "type": "Link" @@ -214,21 +265,22 @@ { "hidden": 0, "is_query_report": 0, - "label": "Package Import", + "label": "Activity Log", "link_count": 0, - "link_to": "Package Import", + "link_to": "Activity Log", "link_type": "DocType", "onboard": 0, "type": "Link" } ], - "modified": "2022-01-13 17:26:02.736366", + "modified": "2022-09-02 01:48:28.029135", "modified_by": "Administrator", "module": "Core", "name": "Build", "owner": "Administrator", "parent_page": "", "public": 1, + "quick_lists": [], "restrict_to_domain": "", "roles": [], "sequence_id": 5.0, diff --git a/frappe/core/workspace/settings/settings.json b/frappe/core/workspace/settings/settings.json index 5aadbc42d5..1469892bd8 100644 --- a/frappe/core/workspace/settings/settings.json +++ b/frappe/core/workspace/settings/settings.json @@ -1,6 +1,6 @@ { "charts": [], - "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Settings\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"System Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Print Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Website Settings\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Email / Notifications\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Website\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Core\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Printing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Workflow\",\"col\":4}}]", + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Settings\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"System Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Print Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Website Settings\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Email / Notifications\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Website\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Core\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Printing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Workflow\",\"col\":4}}]", "creation": "2020-03-02 15:09:40.527211", "docstatus": 0, "doctype": "Workspace", @@ -224,7 +224,7 @@ { "hidden": 0, "is_query_report": 0, - "label": "Core", + "label": "Printing", "link_count": 0, "onboard": 0, "type": "Card Break" @@ -233,10 +233,10 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "System Settings", + "label": "Print Format Builder", "link_count": 0, - "link_to": "System Settings", - "link_type": "DocType", + "link_to": "print-format-builder", + "link_type": "Page", "onboard": 0, "type": "Link" }, @@ -244,9 +244,9 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Error Log", + "label": "Print Settings", "link_count": 0, - "link_to": "Error Log", + "link_to": "Print Settings", "link_type": "DocType", "onboard": 0, "type": "Link" @@ -255,9 +255,9 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Error Snapshot", + "label": "Print Format", "link_count": 0, - "link_to": "Error Snapshot", + "link_to": "Print Format", "link_type": "DocType", "onboard": 0, "type": "Link" @@ -266,9 +266,9 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Domain Settings", + "label": "Print Style", "link_count": 0, - "link_to": "Domain Settings", + "link_to": "Print Style", "link_type": "DocType", "onboard": 0, "type": "Link" @@ -276,7 +276,7 @@ { "hidden": 0, "is_query_report": 0, - "label": "Printing", + "label": "Workflow", "link_count": 0, "onboard": 0, "type": "Card Break" @@ -285,20 +285,9 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Print Format Builder", - "link_count": 0, - "link_to": "print-format-builder", - "link_type": "Page", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Print Settings", + "label": "Workflow", "link_count": 0, - "link_to": "Print Settings", + "link_to": "Workflow", "link_type": "DocType", "onboard": 0, "type": "Link" @@ -307,9 +296,9 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Print Format", + "label": "Workflow State", "link_count": 0, - "link_to": "Print Format", + "link_to": "Workflow State", "link_type": "DocType", "onboard": 0, "type": "Link" @@ -318,9 +307,9 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Print Style", + "label": "Workflow Action", "link_count": 0, - "link_to": "Print Style", + "link_to": "Workflow Action", "link_type": "DocType", "onboard": 0, "type": "Link" @@ -328,8 +317,8 @@ { "hidden": 0, "is_query_report": 0, - "label": "Workflow", - "link_count": 0, + "label": "Core", + "link_count": 2, "onboard": 0, "type": "Card Break" }, @@ -337,20 +326,9 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Workflow", - "link_count": 0, - "link_to": "Workflow", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Workflow State", + "label": "System Settings", "link_count": 0, - "link_to": "Workflow State", + "link_to": "System Settings", "link_type": "DocType", "onboard": 0, "type": "Link" @@ -359,21 +337,22 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Workflow Action", + "label": "Domain Settings", "link_count": 0, - "link_to": "Workflow Action", + "link_to": "Domain Settings", "link_type": "DocType", "onboard": 0, "type": "Link" } ], - "modified": "2022-01-13 17:49:59.586909", + "modified": "2022-08-28 21:41:28.065190", "modified_by": "Administrator", "module": "Core", "name": "Settings", "owner": "Administrator", "parent_page": "", "public": 1, + "quick_lists": [], "restrict_to_domain": "", "roles": [], "sequence_id": 29.0, diff --git a/frappe/custom/workspace/customization/customization.json b/frappe/custom/workspace/customization/customization.json index 1756abcb1d..8985bf54ed 100644 --- a/frappe/custom/workspace/customization/customization.json +++ b/frappe/custom/workspace/customization/customization.json @@ -1,6 +1,6 @@ { "charts": [], - "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Customization\",\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customize Form\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Custom Role\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Client Script\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Server Script\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Dashboards\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Form Customization\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other\",\"col\":4}}]", + "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Customization\",\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customize Form\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Custom Role\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Client Script\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Server Script\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Dashboards\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Form Customization\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other\",\"col\":4}}]", "creation": "2020-03-02 15:15:03.839594", "docstatus": 0, "doctype": "Workspace", @@ -107,7 +107,7 @@ "hidden": 0, "is_query_report": 0, "label": "Other", - "link_count": 0, + "link_count": 2, "onboard": 0, "type": "Card Break" }, @@ -121,15 +121,26 @@ "link_type": "DocType", "onboard": 0, "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Navbar Settings", + "link_count": 0, + "link_to": "Navbar Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" } ], - "modified": "2022-01-13 17:28:08.345794", + "modified": "2022-08-28 20:56:24.980719", "modified_by": "Administrator", "module": "Custom", "name": "Customization", "owner": "Administrator", "parent_page": "", "public": 1, + "quick_lists": [], "restrict_to_domain": "", "roles": [], "sequence_id": 8.0, diff --git a/frappe/utils/install.py b/frappe/utils/install.py index fcf8f9d436..a5f46dc555 100644 --- a/frappe/utils/install.py +++ b/frappe/utils/install.py @@ -289,12 +289,10 @@ def add_standard_navbar_items(): "is_standard": 1, }, { - "item_label": "Background Jobs", - "item_type": "Route", - "route": "/app/background_jobs", + "item_type": "Separator", "is_standard": 1, + "item_label": "", }, - {"item_type": "Separator", "is_standard": 1}, { "item_label": "Log out", "item_type": "Action", From a1191f670e43de274ec554266f14e6d0eea708b1 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 2 Sep 2022 14:09:54 +0530 Subject: [PATCH 12/19] refactor: dont fetch whole doc for 1 value. (cherry picked from commit 8541fa4cd30f8b0d8e860d62185a9761e149b956) --- frappe/core/doctype/comment/comment.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py index 02a5d2d58e..985dde8ce2 100644 --- a/frappe/core/doctype/comment/comment.py +++ b/frappe/core/doctype/comment/comment.py @@ -161,12 +161,13 @@ def update_comments_in_parent(reference_doctype, reference_name, _comments): else: raise ImplicitCommitError - else: - if not frappe.flags.in_patch: - reference_doc = frappe.get_doc(reference_doctype, reference_name) - if getattr(reference_doc, "route", None): - clear_cache(reference_doc.route) + if frappe.flags.in_patch: + return + + # Clear route cache + if route := frappe.get_cached_value(reference_doctype, reference_name, "route"): + clear_cache(route) def update_comments_in_parent_after_request(): From 1e81a2a2b53af2c5d4f1650780395dcb3c5471bc Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 31 Aug 2022 19:39:12 +0530 Subject: [PATCH 13/19] ci: separate cache for UI jobs and selective test This is reusing normal cache which doesn't contain half the things (cherry picked from commit 236ab8dbed2cb9e7a09e20b4fc42be18e50a4faa) --- .github/helper/roulette.py | 41 ++++++++++++++++++++++++++++------ .github/workflows/ui-tests.yml | 11 ++++----- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/.github/helper/roulette.py b/.github/helper/roulette.py index 554f4ae5f5..f5b7176e9c 100644 --- a/.github/helper/roulette.py +++ b/.github/helper/roulette.py @@ -17,39 +17,57 @@ def fetch_pr_data(pr_number, repo, endpoint=""): req = urllib.request.Request(api_url) res = urllib.request.urlopen(req) - return json.loads(res.read().decode('utf8')) + return json.loads(res.read().decode("utf8")) + def get_files_list(pr_number, repo="frappe/frappe"): return [change["filename"] for change in fetch_pr_data(pr_number, repo, "files")] + def get_output(command, shell=True): print(command) command = shlex.split(command) return subprocess.check_output(command, shell=shell, encoding="utf8").strip() + def has_skip_ci_label(pr_number, repo="frappe/frappe"): return has_label(pr_number, "Skip CI", repo) + def has_run_server_tests_label(pr_number, repo="frappe/frappe"): return has_label(pr_number, "Run Server Tests", repo) + def has_run_ui_tests_label(pr_number, repo="frappe/frappe"): return has_label(pr_number, "Run UI Tests", repo) + def has_label(pr_number, label, repo="frappe/frappe"): - return any([fetched_label["name"] for fetched_label in fetch_pr_data(pr_number, repo)["labels"] if fetched_label["name"] == label]) + return any( + [ + fetched_label["name"] + for fetched_label in fetch_pr_data(pr_number, repo)["labels"] + if fetched_label["name"] == label + ] + ) + def is_py(file): return file.endswith("py") + def is_ci(file): return ".github" in file + def is_frontend_code(file): - return file.lower().endswith((".css", ".scss", ".less", ".sass", ".styl", ".js", ".ts", ".vue", ".html")) + return file.lower().endswith( + (".css", ".scss", ".less", ".sass", ".styl", ".js", ".ts", ".vue", ".html") + ) + def is_docs(file): - regex = re.compile(r'\.(md|png|jpg|jpeg|csv|svg)$|^.github|LICENSE') + regex = re.compile(r"\.(md|png|jpg|jpeg|csv|svg)$|^.github|LICENSE") return bool(regex.search(file)) @@ -78,8 +96,13 @@ if __name__ == "__main__": only_py_changed = updated_py_file_count == len(files_list) if has_skip_ci_label(pr_number, repo): - print("Found `Skip CI` label on pr, stopping build process.") - sys.exit(0) + if build_type == "ui" and has_run_ui_tests_label(pr_number, repo): + print("Running UI tests only.") + elif build_type == "server" and has_run_server_tests_label(pr_number, repo): + print("Running server tests only.") + else: + print("Found `Skip CI` label on pr, stopping build process.") + sys.exit(0) elif ci_files_changed: print("CI related files were updated, running all build processes.") @@ -88,7 +111,11 @@ if __name__ == "__main__": print("Only docs were updated, stopping build process.") sys.exit(0) - elif only_frontend_code_changed and build_type == "server" and not has_run_server_tests_label(pr_number, repo): + elif ( + only_frontend_code_changed + and build_type == "server" + and not has_run_server_tests_label(pr_number, repo) + ): print("Only Frontend code was updated; Stopping Python build process.") sys.exit(0) diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 7a955f53d2..db215cbc7b 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -105,19 +105,16 @@ jobs: id: yarn-cache with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + key: ${{ runner.os }}-yarn-ui-${{ hashFiles('**/yarn.lock') }} restore-keys: | - ${{ runner.os }}-yarn- + ${{ runner.os }}-yarn-ui- - name: Cache cypress binary if: ${{ steps.check-build.outputs.build == 'strawberry' }} uses: actions/cache@v3 with: - path: ~/.cache - key: ${{ runner.os }}-cypress- - restore-keys: | - ${{ runner.os }}-cypress- - ${{ runner.os }}- + path: ~/.cache/Cypress + key: ${{ runner.os }}-cypress - name: Install Dependencies if: ${{ steps.check-build.outputs.build == 'strawberry' }} From de203c3a72543432d23f0ce1102eea502e67ddc7 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 31 Aug 2022 19:23:28 +0530 Subject: [PATCH 14/19] test: upgrade cypress from v6 to v10 chore: run cypress migrator fix: specPattern should be an array test: correct intercept URL This is breaking change in cypress 7.0 test: make list view test rerunnable test: redo undo tests and add compare doc test: scroll to action button chore: drop flaky tests fix: remove scroll behaviour fix: clear filters before running count test test: input delay causes flake (cherry picked from commit e397b27d29c904de9b8798d0a3982227cb2de208) --- cypress.config.js | 23 ++++++ cypress.json | 15 ---- cypress/integration/control_float.js | 1 + cypress/integration/form.js | 14 ++-- cypress/integration/kanban.js | 13 ---- cypress/integration/list_view.js | 9 ++- cypress/integration/list_view_settings.js | 2 + cypress/integration/timeline_email.js | 93 ----------------------- cypress/integration/workspace_blocks.js | 2 +- cypress/support/commands.js | 29 ++++++- cypress/support/{index.js => e2e.js} | 0 frappe/commands/utils.py | 2 +- frappe/tests/ui_test_helpers.py | 3 +- 13 files changed, 66 insertions(+), 140 deletions(-) create mode 100644 cypress.config.js delete mode 100644 cypress.json delete mode 100644 cypress/integration/timeline_email.js rename cypress/support/{index.js => e2e.js} (100%) diff --git a/cypress.config.js b/cypress.config.js new file mode 100644 index 0000000000..f86354a06d --- /dev/null +++ b/cypress.config.js @@ -0,0 +1,23 @@ +const { defineConfig } = require("cypress"); + +module.exports = defineConfig({ + projectId: "92odwv", + adminPassword: "admin", + defaultCommandTimeout: 20000, + pageLoadTimeout: 15000, + video: true, + videoUploadOnPasses: false, + retries: { + runMode: 2, + openMode: 2, + }, + e2e: { + // We've imported your old cypress plugins here. + // You may want to clean this up later by importing these. + setupNodeEvents(on, config) { + return require("./cypress/plugins/index.js")(on, config); + }, + baseUrl: "http://test_site_ui:8000", + specPattern: ["./cypress/integration/*.js", "**/ui_test_*.js"], + }, +}); diff --git a/cypress.json b/cypress.json deleted file mode 100644 index 15f8f230fa..0000000000 --- a/cypress.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "baseUrl": "http://test_site_ui:8000", - "projectId": "92odwv", - "adminPassword": "admin", - "defaultCommandTimeout": 20000, - "pageLoadTimeout": 15000, - "video": true, - "videoUploadOnPasses": false, - "retries": { - "runMode": 2, - "openMode": 2 - }, - "integrationFolder": ".", - "testFiles": ["cypress/integration/*.js", "**/ui_test_*.js"] -} diff --git a/cypress/integration/control_float.js b/cypress/integration/control_float.js index c8261ad043..65aa21ed69 100644 --- a/cypress/integration/control_float.js +++ b/cypress/integration/control_float.js @@ -29,6 +29,7 @@ context("Control Float", () => { }); x.values.forEach((d) => { cy.get_field("float_number", "Float").clear(); + cy.wait(200); cy.fill_field("float_number", d.input, "Float").blur(); cy.get_field("float_number", "Float").should("have.value", d.blur_expected); diff --git a/cypress/integration/form.js b/cypress/integration/form.js index 43ab5350b7..6f9460588d 100644 --- a/cypress/integration/form.js +++ b/cypress/integration/form.js @@ -132,9 +132,6 @@ context("Form", () => { jump_to_field("Username"); type_value("admin42"); - jump_to_field("Birth Date"); - type_value("12-31-01"); - jump_to_field("Send Welcome Email"); cy.focused().uncheck(); @@ -155,16 +152,15 @@ context("Form", () => { undo(); undo(); undo(); - undo(); - redo(); redo(); redo(); redo(); redo(); - cy.get_field("username").should("have.value", "admin24"); - cy.get_field("email").should("have.value", "admin@example.com"); - cy.get_field("birth_date").should("have.value", "12-31-2001"); // parsed value - cy.get_field("send_welcome_email").should("not.be.checked"); + cy.compare_document({ + username: "admin24", + email: "admin@example.com", + send_welcome_email: 0, + }); }); }); diff --git a/cypress/integration/kanban.js b/cypress/integration/kanban.js index 7296a12666..1b7e45ac55 100644 --- a/cypress/integration/kanban.js +++ b/cypress/integration/kanban.js @@ -96,17 +96,4 @@ context("Kanban Board", () => { .first() .should("not.contain", "ID:"); }); - - // it('Drag todo', () => { - // cy.intercept({ - // method: 'POST', - // url: 'api/method/frappe.desk.doctype.kanban_board.kanban_board.update_order_for_single_card' - // }).as('drag-completed'); - - // cy.get('.kanban-card-body') - // .contains('Test Kanban ToDo').first() - // .drag('[data-column-value="Closed"] .kanban-cards', { force: true }); - - // cy.wait('@drag-completed'); - // }); }); diff --git a/cypress/integration/list_view.js b/cypress/integration/list_view.js index 7fb0ef445c..1fed62d678 100644 --- a/cypress/integration/list_view.js +++ b/cypress/integration/list_view.js @@ -10,10 +10,13 @@ context("List View", () => { }); }); - it("Keep checkbox checked after Refresh", () => { + it("Keep checkbox checked after Refresh", { scrollBehavior: false }, () => { cy.go_to_list("ToDo"); cy.clear_filters(); - cy.get(".list-row-container .list-row-checkbox").click({ multiple: true, force: true }); + cy.get(".list-row-container .list-row-checkbox").click({ + multiple: true, + force: true, + }); cy.get(".actions-btn-group button").contains("Actions").should("be.visible"); cy.intercept("/api/method/frappe.desk.reportview.get").as("list-refresh"); cy.wait(3000); // wait before you hit another refresh @@ -22,7 +25,7 @@ context("List View", () => { cy.get(".list-row-container .list-row-checkbox:checked").should("be.visible"); }); - it('enables "Actions" button', () => { + it('enables "Actions" button', { scrollBehavior: false }, () => { const actions = [ "Approve", "Reject", diff --git a/cypress/integration/list_view_settings.js b/cypress/integration/list_view_settings.js index 5e66ee43f5..898fe1dec4 100644 --- a/cypress/integration/list_view_settings.js +++ b/cypress/integration/list_view_settings.js @@ -5,12 +5,14 @@ context("List View Settings", () => { }); it("Default settings", () => { cy.visit("/app/List/DocType/List"); + cy.clear_filters(); cy.get(".list-count").should("contain", "20 of"); cy.get(".list-stats").should("contain", "Tags"); }); it("disable count and sidebar stats then verify", () => { cy.wait(300); cy.visit("/app/List/DocType/List"); + cy.clear_filters(); cy.wait(300); cy.get(".list-count").should("contain", "20 of"); cy.get(".menu-btn-group button").click(); diff --git a/cypress/integration/timeline_email.js b/cypress/integration/timeline_email.js deleted file mode 100644 index 3a22f49bfa..0000000000 --- a/cypress/integration/timeline_email.js +++ /dev/null @@ -1,93 +0,0 @@ -context("Timeline Email", () => { - before(() => { - cy.visit("/login"); - cy.login(); - cy.visit("/app/todo"); - }); - - it("Adding new ToDo", () => { - cy.click_listview_primary_button("Add ToDo"); - cy.get(".custom-actions:visible > .btn").contains("Edit Full Form").click({ delay: 500 }); - cy.fill_field("description", "Test ToDo", "Text Editor"); - cy.wait(500); - cy.get(".primary-action").contains("Save").click({ force: true }); - cy.wait(700); - }); - - it("Adding email and verifying timeline content for email attachment", () => { - cy.visit("/app/todo"); - cy.click_listview_row_item_with_text("Test ToDo"); - - //Creating a new email - cy.get(".timeline-actions > .timeline-item > .action-buttons > .action-btn").click(); - cy.fill_field("recipients", "test@example.com", "MultiSelect"); - cy.get( - '.modal.show > .modal-dialog > .modal-content > .modal-body > :nth-child(1) > .form-layout > .form-page > :nth-child(3) > .section-body > .form-column > form > [data-fieldtype="Text Editor"] > .form-group > .control-input-wrapper > .control-input > .ql-container > .ql-editor' - ).type("Test Mail"); - - //Adding attachment to the email - cy.get(".add-more-attachments > .btn").click(); - cy.get(".mt-2 > .btn > .mt-1").eq(2).click(); - cy.get(".input-group > .form-control").type( - "https://wallpaperplay.com/walls/full/8/2/b/72402.jpg" - ); - cy.get(".btn-primary").contains("Upload").click(); - - //Sending the email - cy.click_modal_primary_button("Send", { delay: 500 }); - - //To check if the sent mail content is shown in the timeline content - cy.get('[data-doctype="Communication"] > .timeline-content').should( - "contain", - "Test Mail" - ); - - //To check if the attachment of email is shown in the timeline content - cy.get(".timeline-content").should("contain", "Added 72402.jpg"); - - //Deleting the sent email - cy.get('[title="Open Communication"] > .icon').first().click({ force: true }); - cy.get( - "#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .btn" - ).click(); - cy.get( - "#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .dropdown-menu > li > .grey-link" - ) - .eq(9) - .click(); - cy.get( - ".modal.show > .modal-dialog > .modal-content > .modal-footer > .standard-actions > .btn-primary" - ).click(); - }); - - it("Deleting attachment and ToDo", () => { - cy.visit("/app/todo"); - cy.click_listview_row_item_with_text("Test ToDo"); - - //Removing the added attachment - cy.get(".attachment-row > .data-pill > .remove-btn > .icon").click(); - cy.wait(500); - cy.get(".modal-footer:visible > .standard-actions > .btn-primary").contains("Yes").click(); - - //To check if the removed attachment is shown in the timeline content - cy.get(".timeline-content").should("contain", "Removed 72402.jpg"); - cy.wait(500); - - //To check if the discard button functionality in email is working correctly - cy.get(".timeline-actions > .timeline-item > .action-buttons > .action-btn").click(); - cy.fill_field("recipients", "test@example.com", "MultiSelect"); - cy.get(".modal-footer > .standard-actions > .btn-secondary").contains("Discard").click(); - cy.wait(500); - cy.get(".timeline-actions > .timeline-item > .action-buttons > .action-btn").click(); - cy.wait(500); - cy.get_field("recipients", "MultiSelect").should("have.text", ""); - cy.get(".modal-header:visible > .modal-actions > .btn-modal-close > .icon").click(); - - //Deleting the added ToDo - cy.get(".menu-btn-group:visible > .btn").click(); - cy.get(".menu-btn-group:visible > .dropdown-menu > li > .dropdown-item") - .contains("Delete") - .click(); - cy.get(".modal-footer:visible > .standard-actions > .btn-primary").click(); - }); -}); diff --git a/cypress/integration/workspace_blocks.js b/cypress/integration/workspace_blocks.js index 774595b6b8..47c5424bce 100644 --- a/cypress/integration/workspace_blocks.js +++ b/cypress/integration/workspace_blocks.js @@ -68,7 +68,7 @@ context("Workspace Blocks", () => { cy.intercept({ method: "GET", - url: "api/method/frappe.desk.form.load.getdoctype", + url: "api/method/frappe.desk.form.load.getdoctype?**", }).as("get_doctype"); cy.visit("/app/tools"); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index cbb88cb8cb..fb1ff99678 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -281,12 +281,12 @@ Cypress.Commands.add("get_open_dialog", () => { }); Cypress.Commands.add("save", () => { - cy.intercept("/api").as("api"); + cy.intercept("/api/method/frappe.desk.form.save.savedocs").as("save_call"); cy.get(`button[data-label="Save"]:visible`).click({ scrollBehavior: false, force: true }); - cy.wait("@api"); + cy.wait("@save_call"); }); Cypress.Commands.add("hide_dialog", () => { - cy.wait(300); + cy.wait(500); cy.get_open_dialog().focus().find(".btn-modal-close").click(); cy.get(".modal:visible").should("not.exist"); }); @@ -459,3 +459,26 @@ Cypress.Commands.add("select_listview_row_checkbox", (row_no) => { Cypress.Commands.add("click_form_section", (section_name) => { cy.get(".section-head").contains(section_name).click(); }); + +const compare_document = (expected, actual) => { + for (const prop in expected) { + if (expected[prop] instanceof Array) { + // recursively compare child documents. + expected[prop].forEach((item, idx) => { + compare_document(item, actual[prop][idx]); + }); + } else { + assert.equal(expected[prop], actual[prop], `${prop} should be equal.`); + } + } +}; + +Cypress.Commands.add("compare_document", (expected_document) => { + cy.window() + .its("cur_frm") + .then((frm) => { + // Don't remove this, cypress can't magically wait for events it has no control over. + cy.wait(1000); + compare_document(expected_document, frm.doc); + }); +}); diff --git a/cypress/support/index.js b/cypress/support/e2e.js similarity index 100% rename from cypress/support/index.js rename to cypress/support/e2e.js diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 3658a35992..c875667c82 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -879,7 +879,7 @@ def run_ui_tests( click.secho("Installing Cypress...", fg="yellow") packages = " ".join( [ - "cypress@^6", + "cypress@^10", "cypress-file-upload@^5", "@4tw/cypress-drag-drop@^2", "cypress-real-events", diff --git a/frappe/tests/ui_test_helpers.py b/frappe/tests/ui_test_helpers.py index f9be638385..39a23f0468 100644 --- a/frappe/tests/ui_test_helpers.py +++ b/frappe/tests/ui_test_helpers.py @@ -38,8 +38,7 @@ def create_if_not_exists(doc): @frappe.whitelist() def create_todo_records(): - if frappe.get_all("ToDo", {"description": "this is first todo"}): - return + frappe.db.truncate("ToDo") frappe.get_doc( {"doctype": "ToDo", "date": add_to_date(now(), days=7), "description": "this is first todo"} From 1285d84aa75bd85921c6857bfc962e6523aa3f77 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sat, 3 Sep 2022 15:07:24 +0530 Subject: [PATCH 15/19] fix: show unique workspaces in sidebar (cherry picked from commit 7331e3a7be0ebbe559a41babe791331eaa86f9f3) --- frappe/public/js/frappe/views/workspace/workspace.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/views/workspace/workspace.js b/frappe/public/js/frappe/views/workspace/workspace.js index 88ff21caa7..f5fdab24ab 100644 --- a/frappe/public/js/frappe/views/workspace/workspace.js +++ b/frappe/public/js/frappe/views/workspace/workspace.js @@ -117,6 +117,7 @@ frappe.views.Workspace = class Workspace { (page) => page.parent_page == "" || page.parent_page == null ); } + root_pages = root_pages.uniqBy((d) => d.title); this.build_sidebar_section(category, root_pages); }); From 434ed3d153380d8829e9b783f839d58b0965c859 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sat, 3 Sep 2022 15:08:57 +0530 Subject: [PATCH 16/19] fix: do not remove workspaces while app is uninstalled (cherry picked from commit cab95e12f4a2de96e7e85bd4f76d828f7ab0a2a1) --- frappe/desk/doctype/workspace/workspace.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/desk/doctype/workspace/workspace.py b/frappe/desk/doctype/workspace/workspace.py index a3c4fe33cc..82077cc59c 100644 --- a/frappe/desk/doctype/workspace/workspace.py +++ b/frappe/desk/doctype/workspace/workspace.py @@ -138,6 +138,7 @@ class Workspace(Document): def disable_saving_as_public(): return ( frappe.flags.in_install + or frappe.flags.in_uninstall or frappe.flags.in_patch or frappe.flags.in_test or frappe.flags.in_fixtures From 727adb1054c5614da1bb6002f18d793783bad557 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 4 Sep 2022 11:14:16 +0530 Subject: [PATCH 17/19] fix(DX): reduce retry count for esbuild-redis closes https://github.com/frappe/frappe/issues/17976 --- esbuild/utils.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esbuild/utils.js b/esbuild/utils.js index db58b89e8b..3edccfd024 100644 --- a/esbuild/utils.js +++ b/esbuild/utils.js @@ -111,15 +111,15 @@ function get_redis_subscriber(kind) { let retry_strategy; let { get_redis_subscriber: get_redis, get_conf } = require("../node_utils"); - if (process.env.CI == 1 || get_conf().developer_mode == 1) { + if (process.env.CI == 1 || get_conf().developer_mode == 0) { retry_strategy = () => {}; } else { retry_strategy = function (options) { - // abort after 10 connection attempts - if (options.attempt > 10) { + // abort after 5 x 3 connection attempts ~= 3 seconds + if (options.attempt > 4) { return undefined; } - return Math.min(options.attempt * 100, 2000); + return options.attempt * 100; }; } return get_redis(kind, { retry_strategy }); From 7249c179edd6ab9abf1f952f51e793551e43f337 Mon Sep 17 00:00:00 2001 From: Nikhil Kothari Date: Tue, 6 Sep 2022 13:48:00 +0530 Subject: [PATCH 18/19] feat: option to disable user pass based login (#18000) * Added checkbox to disable pass login in settings * Added user_pass disable option in Login page context * Hide user-pass fields when option disabled * Added check for social login key and LDAP * feat: Disable API based usr-pwd login * style: format with black * refactor: simpify auth validation No need for else clause * refactor: fixup sys setting json and move field * refactor: sys settings validation * refactor: simpler imports * chore: undo unintional changes * test: add test for disabled user pass Co-authored-by: Ankush Menat --- frappe/auth.py | 3 +++ .../doctype/system_settings/system_settings.json | 10 +++++++++- .../doctype/system_settings/system_settings.py | 16 ++++++++++++++++ frappe/tests/test_auth.py | 9 +++++++++ frappe/www/login.html | 11 ++++++++--- frappe/www/login.py | 4 +++- 6 files changed, 48 insertions(+), 5 deletions(-) diff --git a/frappe/auth.py b/frappe/auth.py index 7b64ecc083..7ce2b17680 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -140,6 +140,9 @@ class LoginManager: self.set_user_info() def login(self): + if frappe.get_system_settings("disable_user_pass_login"): + frappe.throw(_("Login with username and password is not allowed."), frappe.AuthenticationError) + # clear cache frappe.clear_cache(user=frappe.form_dict.get("usr")) user, pwd = get_cached_user_pass() diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index a444062b5a..0d612149a6 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -39,6 +39,7 @@ "deny_multiple_sessions", "allow_login_using_mobile_number", "allow_login_using_user_name", + "disable_user_pass_login", "allow_error_traceback", "strip_exif_metadata_from_uploaded_images", "allow_older_web_view_links", @@ -525,12 +526,19 @@ "fieldname": "email_retry_limit", "fieldtype": "Int", "label": "Email Retry Limit" + }, + { + "default": "0", + "description": "Make sure to configure a Social Login Key before disabling to prevent lockout", + "fieldname": "disable_user_pass_login", + "fieldtype": "Check", + "label": "Disable Username/Password Login" } ], "icon": "fa fa-cog", "issingle": 1, "links": [], - "modified": "2022-06-21 13:55:04.796152", + "modified": "2022-09-06 03:16:59.090906", "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 4bd41be974..1fc27ca114 100644 --- a/frappe/core/doctype/system_settings/system_settings.py +++ b/frappe/core/doctype/system_settings/system_settings.py @@ -43,6 +43,22 @@ class SystemSettings(Document): ): frappe.flags.update_last_reset_password_date = True + self.validate_user_pass_login() + + def validate_user_pass_login(self): + if not self.disable_user_pass_login: + return + + social_login_enabled = frappe.db.exists("Social Login Key", {"enable_social_login": 1}) + ldap_enabled = frappe.db.get_single_value("LDAP Settings", "enabled") + + if not (social_login_enabled or ldap_enabled): + frappe.throw( + _( + "Please enable atleast one Social Login Key or LDAP before disabling username/password based login." + ) + ) + def on_update(self): self.set_defaults() diff --git a/frappe/tests/test_auth.py b/frappe/tests/test_auth.py index 4378d75484..403d0cf34c 100644 --- a/frappe/tests/test_auth.py +++ b/frappe/tests/test_auth.py @@ -98,6 +98,7 @@ class TestAuth(FrappeTestCase): def test_deny_multiple_login(self): self.set_system_settings("deny_multiple_sessions", 1) + self.addCleanup(self.set_system_settings, "deny_multiple_sessions", 0) first_login = FrappeClient(self.HOST_NAME, self.test_user_email, self.test_user_password) first_login.get_list("ToDo") @@ -114,6 +115,14 @@ class TestAuth(FrappeTestCase): second_login.get_list("ToDo") third_login.get_list("ToDo") + def test_disable_user_pass_login(self): + FrappeClient(self.HOST_NAME, self.test_user_email, self.test_user_password).get_list("ToDo") + self.set_system_settings("disable_user_pass_login", 1) + self.addCleanup(self.set_system_settings, "disable_user_pass_login", 0) + + with self.assertRaises(Exception): + FrappeClient(self.HOST_NAME, self.test_user_email, self.test_user_password).get_list("ToDo") + class TestLoginAttemptTracker(FrappeTestCase): def test_account_lock(self): diff --git a/frappe/www/login.html b/frappe/www/login.html index 1aaaf85656..897fdacd76 100644 --- a/frappe/www/login.html +++ b/frappe/www/login.html @@ -1,6 +1,7 @@ {% extends "templates/web.html" %} {% macro email_login_body() -%} +{% if not disable_user_pass_login %}
@@ -38,13 +39,15 @@

- {{ _("Forgot Password?") }}

+ {{ _("Forgot Password?") }} +

- +{% endif %}
+ {% if not disable_user_pass_login %} - + {% endif %} {% if ldap_settings and ldap_settings.enabled %} @@ -83,7 +86,9 @@ {{ email_login_body() }}