From 55e2af20c66b7bf490ae3c4cfb5d3f4d58e885ca Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 7 Sep 2021 17:51:46 +0530 Subject: [PATCH 01/51] feat: Enhancements in Server Script --- frappe/utils/safe_exec.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 2e27859faa..3fb55af926 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -111,6 +111,7 @@ def get_safe_globals(): get_hooks=frappe.get_hooks, sanitize_html=frappe.utils.sanitize_html, log_error=frappe.log_error + cache=frappe.cache ), FrappeClient=FrappeClient, style=frappe._dict( @@ -141,7 +142,8 @@ def get_safe_globals(): get_single_value = frappe.db.get_single_value, get_default = frappe.db.get_default, escape = frappe.db.escape, - sql = read_sql + sql = read_sql, + commit = frappe.db.commit ) if frappe.response: From a1251aace99bef5166925e79926a146f79bd7cb6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 7 Sep 2021 18:29:04 +0530 Subject: [PATCH 02/51] fix: Syntax Error --- 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 3fb55af926..983e4aabf8 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -110,7 +110,7 @@ def get_safe_globals(): socketio_port=frappe.conf.socketio_port, get_hooks=frappe.get_hooks, sanitize_html=frappe.utils.sanitize_html, - log_error=frappe.log_error + log_error=frappe.log_error, cache=frappe.cache ), FrappeClient=FrappeClient, From d8d8c8e54ee7cc96eb740bf5c756887f57d4d108 Mon Sep 17 00:00:00 2001 From: MitulDavid Date: Mon, 20 Sep 2021 21:11:47 +0530 Subject: [PATCH 03/51] ci: Code coverage for JS files --- .github/workflows/ui-tests.yml | 19 ++++++++++++++++++- .gitignore | 1 + cypress/plugins/index.js | 8 ++++---- cypress/support/index.js | 1 + frappe/commands/utils.py | 11 +++++++---- package.json | 8 ++++++-- 6 files changed, 37 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 2a55546ec4..530b988919 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -122,12 +122,29 @@ jobs: DB: mariadb TYPE: ui + - name: Instrument Source Code + if: ${{ steps.check-build.outputs.build == 'strawberry' }} + run: cd ~/frappe-bench/apps/frappe/ && npx nyc instrument -x 'frappe/public/dist/**' -x 'frappe/public/js/lib/**' -x '**/*.bundle.js' --compact=false --in-place frappe + + - name: Build + if: ${{ steps.check-build.outputs.build == 'strawberry' }} + run: cd ~/frappe-bench/ && bench build --apps frappe + - name: Site Setup if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: cd ~/frappe-bench/ && bench --site test_site execute frappe.utils.install.complete_setup_wizard - name: UI Tests if: ${{ steps.check-build.outputs.build == 'strawberry' }} - run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests frappe --headless --parallel --ci-build-id $GITHUB_RUN_ID + run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests frappe --with-coverage --headless --parallel --ci-build-id $GITHUB_RUN_ID env: CYPRESS_RECORD_KEY: 4a48f41c-11b3-425b-aa88-c58048fa69eb + + - name: Upload Coverage Data + if: ${{ steps.check-build.outputs.build == 'strawberry' }} + uses: codecov/codecov-action@v2 + with: + name: Cypress + fail_ci_if_error: true + directory: /home/runner/frappe-bench/apps/frappe/.cypress-coverage/ + verbose: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1ff3122d70..c9dd8f38f3 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,7 @@ coverage.xml *.cover .hypothesis/ .pytest_cache/ +.cypress-coverage # Translations *.mo diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index 07d9804a73..c08f9e594c 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -11,7 +11,7 @@ // This function is called when a project is opened or re-opened (e.g. due to // the project's config changing) -module.exports = () => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config -}; +module.exports = (on, config) => { + require('@cypress/code-coverage/task')(on, config) + return config +} \ No newline at end of file diff --git a/cypress/support/index.js b/cypress/support/index.js index 1bee72d2ca..9cd770a31e 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -15,6 +15,7 @@ // Import commands.js using ES2015 syntax: import './commands'; +import '@cypress/code-coverage/support'; // Alternatively you can use CommonJS syntax: diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index b0151106db..bef558c859 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -592,9 +592,10 @@ def run_parallel_tests(context, app, build_number, total_builds, with_coverage=F @click.argument('app') @click.option('--headless', is_flag=True, help="Run UI Test in headless mode") @click.option('--parallel', is_flag=True, help="Run UI Test in parallel mode") +@click.option('--with-coverage', is_flag=True, help="Generate coverage report") @click.option('--ci-build-id') @pass_context -def run_ui_tests(context, app, headless=False, parallel=True, ci_build_id=None): +def run_ui_tests(context, app, headless=False, parallel=True, with_coverage=False, ci_build_id=None): "Run UI tests" site = get_site(context) app_base_path = os.path.abspath(os.path.join(frappe.get_app_path(app), '..')) @@ -604,6 +605,7 @@ def run_ui_tests(context, app, headless=False, parallel=True, ci_build_id=None): # override baseUrl using env variable site_env = f'CYPRESS_baseUrl={site_url}' password_env = f'CYPRESS_adminPassword={admin_password}' if admin_password else '' + coverage_env = f'CYPRESS_coverage={str(with_coverage).lower()}' os.chdir(app_base_path) @@ -611,22 +613,23 @@ def run_ui_tests(context, app, headless=False, parallel=True, ci_build_id=None): cypress_path = f"{node_bin}/cypress" plugin_path = f"{node_bin}/../cypress-file-upload" testing_library_path = f"{node_bin}/../@testing-library" + coverage_plugin_path = f"{node_bin}/../@cypress/code-coverage" # check if cypress in path...if not, install it. if not ( os.path.exists(cypress_path) and os.path.exists(plugin_path) and os.path.exists(testing_library_path) + and os.path.exists(coverage_plugin_path) and cint(subprocess.getoutput("npm view cypress version")[:1]) >= 6 ): # install cypress click.secho("Installing Cypress...", fg="yellow") - frappe.commands.popen("yarn add cypress@^6 cypress-file-upload@^5 @testing-library/cypress@^8 --no-lockfile") + frappe.commands.popen("yarn add cypress@^6 cypress-file-upload@^5 @testing-library/cypress@^8 @cypress/code-coverage@^3 --no-lockfile") # run for headless mode run_or_open = 'run --browser firefox --record' if headless else 'open' - command = '{site_env} {password_env} {cypress} {run_or_open}' - formatted_command = command.format(site_env=site_env, password_env=password_env, cypress=cypress_path, run_or_open=run_or_open) + formatted_command = f'{site_env} {password_env} {coverage_env} {cypress_path} {run_or_open}' if parallel: formatted_command += ' --parallel' diff --git a/package.json b/package.json index 2283a44533..8579b9a517 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "build": "node esbuild", "production": "node esbuild --production", "watch": "node esbuild --watch", - "snyk-protect": "snyk protect" + "snyk-protect": "snyk protect", + "coverage:report": "npx nyc report --reporter=clover" }, "repository": { "type": "git", @@ -74,5 +75,8 @@ "rtlcss": "^3.2.1", "yargs": "^16.2.0" }, - "snyk": true + "snyk": true, + "nyc": { + "report-dir": ".cypress-coverage" + } } From b82162769e500bf00a7deb3cb4266af5d74a82e9 Mon Sep 17 00:00:00 2001 From: MitulDavid Date: Mon, 20 Sep 2021 21:13:08 +0530 Subject: [PATCH 04/51] test: Fix uncaught exception in dashboard_links.js --- cypress/integration/dashboard_links.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cypress/integration/dashboard_links.js b/cypress/integration/dashboard_links.js index b77965ee1a..155f4986be 100644 --- a/cypress/integration/dashboard_links.js +++ b/cypress/integration/dashboard_links.js @@ -51,13 +51,12 @@ context('Dashboard links', () => { cur_frm.dashboard.data.reports = [ { 'label': 'Reports', - 'items': ['Permitted Documents For User'] + 'items': ['Website Analytics'] } ]; cur_frm.dashboard.render_report_links(); - cy.get('[data-report="Permitted Documents For User"]').contains('Permitted Documents For User').click(); - cy.findByText('Permitted Documents For User'); - cy.findByPlaceholderText('User').should("have.value", "Administrator"); + cy.get('[data-report="Website Analytics"]').contains('Website Analytics').click(); + cy.findByText('Website Analytics'); }); }); }); From e8263245d2a85bf8545f5bd2d8fd4b3117238626 Mon Sep 17 00:00:00 2001 From: MitulDavid Date: Tue, 21 Sep 2021 08:47:35 +0530 Subject: [PATCH 05/51] style: Fix sider issues --- cypress/plugins/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index c08f9e594c..9720faa666 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -12,6 +12,6 @@ // the project's config changing) module.exports = (on, config) => { - require('@cypress/code-coverage/task')(on, config) - return config -} \ No newline at end of file + require('@cypress/code-coverage/task')(on, config); + return config; +}; \ No newline at end of file From eb16a09a5773eeb6a24a708fd89069042548b636 Mon Sep 17 00:00:00 2001 From: Mitul David <49085834+mituldavid@users.noreply.github.com> Date: Tue, 21 Sep 2021 20:26:14 +0530 Subject: [PATCH 06/51] ci: Upload coverage only if report is generated --- .github/workflows/ui-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 530b988919..44316043cb 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -141,10 +141,10 @@ jobs: CYPRESS_RECORD_KEY: 4a48f41c-11b3-425b-aa88-c58048fa69eb - name: Upload Coverage Data - if: ${{ steps.check-build.outputs.build == 'strawberry' }} + if: ${{ steps.check-build.outputs.build == 'strawberry' && hashFiles('/home/runner/frappe-bench/apps/frappe/.cypress-coverage/clover.xml') != '' }} uses: codecov/codecov-action@v2 with: name: Cypress fail_ci_if_error: true directory: /home/runner/frappe-bench/apps/frappe/.cypress-coverage/ - verbose: true \ No newline at end of file + verbose: true From dd563001096f957722f708e829cfe5c28a802d21 Mon Sep 17 00:00:00 2001 From: leela Date: Wed, 22 Sep 2021 18:20:57 +0530 Subject: [PATCH 07/51] fix: date mismatches while displaying in short form --- frappe/public/js/frappe/utils/pretty_date.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/utils/pretty_date.js b/frappe/public/js/frappe/utils/pretty_date.js index 84fd276068..3ebe2c1ae2 100644 --- a/frappe/public/js/frappe/utils/pretty_date.js +++ b/frappe/public/js/frappe/utils/pretty_date.js @@ -25,11 +25,11 @@ function prettyDate(date, mini) { if (day_diff < 7) { return __("{0} d", [day_diff]); } else if (day_diff < 31) { - return __("{0} w", [Math.ceil(day_diff / 7)]); + return __("{0} w", [Math.floor(day_diff / 7)]); } else if (day_diff < 365) { - return __("{0} M", [Math.ceil(day_diff / 30)]); + return __("{0} M", [Math.floor(day_diff / 30)]); } else { - return __("{0} y", [Math.ceil(day_diff / 365)]); + return __("{0} y", [Math.floor(day_diff / 365)]); } } } else { @@ -54,15 +54,15 @@ function prettyDate(date, mini) { } else if (day_diff < 14) { return __("1 week ago"); } else if (day_diff < 31) { - return __("{0} weeks ago", [Math.ceil(day_diff / 7)]); + return __("{0} weeks ago", [Math.floor(day_diff / 7)]); } else if (day_diff < 62) { return __("1 month ago"); } else if (day_diff < 365) { - return __("{0} months ago", [Math.ceil(day_diff / 30)]); + return __("{0} months ago", [Math.floor(day_diff / 30)]); } else if (day_diff < 730) { return __("1 year ago"); } else { - return __("{0} years ago", [Math.ceil(day_diff / 365)]); + return __("{0} years ago", [Math.floor(day_diff / 365)]); } } } From eeae52bbf99cdb3218bc908dcd49d66c387e892d Mon Sep 17 00:00:00 2001 From: MitulDavid Date: Wed, 22 Sep 2021 19:07:32 +0530 Subject: [PATCH 08/51] ci: Use file-existence-action instead of hashFiles() --- .github/workflows/ui-tests.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 44316043cb..2e8aca4e31 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -140,8 +140,14 @@ jobs: env: CYPRESS_RECORD_KEY: 4a48f41c-11b3-425b-aa88-c58048fa69eb + - name: Check If Coverage Report Exists + id: check_coverage + uses: andstor/file-existence-action@v1 + with: + files: "/home/runner/frappe-bench/apps/frappe/.cypress-coverage/clover.xml" + - name: Upload Coverage Data - if: ${{ steps.check-build.outputs.build == 'strawberry' && hashFiles('/home/runner/frappe-bench/apps/frappe/.cypress-coverage/clover.xml') != '' }} + if: ${{ steps.check-build.outputs.build == 'strawberry' && steps.check_coverage.outputs.files_exists == 'true' }} uses: codecov/codecov-action@v2 with: name: Cypress From dffd78d3fca385dc4e5ee536beae93099746cdff Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 27 Sep 2021 12:02:31 +0530 Subject: [PATCH 09/51] fix: Validate server script for doc events --- frappe/core/doctype/server_script/server_script.py | 9 +++++++++ frappe/utils/safe_exec.py | 10 ++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index 79fe7a9140..8aa2dd0bae 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -11,6 +11,8 @@ from frappe.model.document import Document from frappe.utils.safe_exec import get_safe_globals, safe_exec, NamespaceDict from frappe import _ +import re + class ServerScript(Document): def validate(self): @@ -94,8 +96,15 @@ class ServerScript(Document): Args: doc (Document): Executes script with for a certain document's events """ + self.validate_script_for_doc_events() safe_exec(self.script, _locals={"doc": doc}) + def validate_script_for_doc_events(self): + for line in self.script.splitlines(): + line = line.strip() + if not line.startswith('#') and "frappe.db.commit()" in line: + frappe.throw(_("Commit cannot be used in DocType Event server script")) + def execute_scheduled_method(self): """Specific to Scheduled Jobs via Server Scripts diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 1c09a17a66..6f9398745e 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -116,8 +116,7 @@ def get_safe_globals(): socketio_port=frappe.conf.socketio_port, get_hooks=frappe.get_hooks, sanitize_html=frappe.utils.sanitize_html, - log_error=frappe.log_error, - cache=frappe.cache + log_error=frappe.log_error ), FrappeClient=FrappeClient, style=frappe._dict( @@ -157,6 +156,13 @@ def get_safe_globals(): commit = frappe.db.commit ) + out.frappe.cache = NamespaceDict( + get_value = frappe.cache().get_value, + set_value = frappe.cache().set_value, + hset = frappe.cache().hset, + hget = frappe.cache().hget + ) + if frappe.response: out.frappe.response = frappe.response From 6ab38e5d65406f927bfee1eeb708ae553a0f962d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 27 Sep 2021 12:09:32 +0530 Subject: [PATCH 10/51] fix: Linting Issues --- frappe/core/doctype/server_script/server_script.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index 8aa2dd0bae..03b33443eb 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -11,8 +11,6 @@ from frappe.model.document import Document from frappe.utils.safe_exec import get_safe_globals, safe_exec, NamespaceDict from frappe import _ -import re - class ServerScript(Document): def validate(self): From e13f99dcb1577a7bfaa26f206be4eb0389c7786f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 27 Sep 2021 12:56:08 +0530 Subject: [PATCH 11/51] test: Check for doc event validations and cache methods --- .../doctype/server_script/server_script.py | 4 +- .../server_script/test_server_script.py | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index 03b33443eb..14f173f906 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -11,6 +11,8 @@ from frappe.model.document import Document from frappe.utils.safe_exec import get_safe_globals, safe_exec, NamespaceDict from frappe import _ +class CommitNotAllowed(frappe.ValidationError): + pass class ServerScript(Document): def validate(self): @@ -101,7 +103,7 @@ class ServerScript(Document): for line in self.script.splitlines(): line = line.strip() if not line.startswith('#') and "frappe.db.commit()" in line: - frappe.throw(_("Commit cannot be used in DocType Event server script")) + frappe.throw(_("Commit cannot be used in DocType Event server script"), CommitNotAllowed) def execute_scheduled_method(self): """Specific to Scheduled Jobs via Server Scripts diff --git a/frappe/core/doctype/server_script/test_server_script.py b/frappe/core/doctype/server_script/test_server_script.py index 6c028ff136..cf49430ff4 100644 --- a/frappe/core/doctype/server_script/test_server_script.py +++ b/frappe/core/doctype/server_script/test_server_script.py @@ -5,6 +5,7 @@ import frappe import unittest import requests from frappe.utils import get_site_url +from frappe.core.doctype.server_script.server_script import CommitNotAllowed scripts = [ dict( @@ -59,6 +60,26 @@ conditions = '1 = 1' reference_doctype = 'Note', script = ''' frappe.method_that_doesnt_exist("do some magic") +''' + ), + dict( + name='test_todo_commit', + script_type = 'DocType Event', + doctype_event = 'Before Save', + reference_doctype = 'ToDo', + disabled = 1, + script = ''' +frappe.db.commit() +''' + ), + dict( + name='test_cache_methods', + script_type = 'DocType Event', + doctype_event = 'Before Save', + reference_doctype = 'ToDo', + disabled = 1, + script = ''' +frappe.cache.set_value('test_key', doc.name) ''' ) ] @@ -119,3 +140,24 @@ class TestServerScript(unittest.TestCase): self.assertTrue("invalid python code" in str(se.exception).lower(), msg="Python code validation not working") + + def test_commit_in_doctype_event(self): + server_script = frappe.get_doc('Server Script', 'test_todo_commit') + server_script.disabled = 0 + server_script.save() + + self.assertRaises(CommitNotAllowed, frappe.get_doc(dict(doctype='ToDo', description='test me')).insert) + + server_script.disabled = 1 + server_script.save() + + def test_cache_methods_in_server_script(self): + server_script = frappe.get_doc('Server Script', 'test_cache_methods') + server_script.disabled = 0 + server_script.save() + + todo = frappe.get_doc(dict(doctype='ToDo', description='test me')).insert() + self.assertEqual(todo.name, frappe.cache().get_value('test_key')) + + server_script.disabled = 1 + server_script.save() From 624cd3a04cac9c69ed2ff95b65e5cc39c0e9cdd5 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 30 Sep 2021 16:21:20 +0530 Subject: [PATCH 12/51] fix: Scrollbar is hidden when search modal is minimized --- frappe/public/js/frappe/ui/dialog.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/dialog.js b/frappe/public/js/frappe/ui/dialog.js index 58175381cf..b1a22c8929 100644 --- a/frappe/public/js/frappe/ui/dialog.js +++ b/frappe/public/js/frappe/ui/dialog.js @@ -78,6 +78,8 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup { this.$wrapper .on("hide.bs.modal", function() { me.display = false; + me.is_minimized = false; + me.hide_scrollbar(false); if(frappe.ui.open_dialogs[frappe.ui.open_dialogs.length-1]===me) { frappe.ui.open_dialogs.pop(); @@ -96,6 +98,7 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup { window.cur_dialog = me; frappe.ui.open_dialogs.push(me); me.focus_on_first_input(); + me.hide_scrollbar(true); me.on_page_show && me.on_page_show(); $(document).trigger('frappe.ui.Dialog:shown'); $(document).off('focusin.modal'); @@ -233,7 +236,11 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup { this.get_minimize_btn().html(frappe.utils.icon(icon)); this.on_minimize_toggle && this.on_minimize_toggle(this.is_minimized); this.header.find('.modal-title').toggleClass('cursor-pointer'); - $("body").css("overflow", this.is_minimized ? "auto" : "hidden"); + this.hide_scrollbar(!this.is_minimized); + } + + hide_scrollbar(bool) { + $("body").css("overflow", bool ? "hidden" : "auto"); } add_custom_action(label, action, css_class=null) { From b798b96debb15304be660ac8442d919a6b475e64 Mon Sep 17 00:00:00 2001 From: MitulDavid Date: Mon, 4 Oct 2021 08:21:40 +0530 Subject: [PATCH 13/51] ci: Set Codecov flags to categorize coverage --- .github/workflows/server-mariadb-tests.yml | 3 ++- .github/workflows/server-postgres-tests.yml | 1 + .github/workflows/ui-tests.yml | 1 + codecov.yml | 12 +++++++++++- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/server-mariadb-tests.yml b/.github/workflows/server-mariadb-tests.yml index 0b187fc44c..2e50aa48f6 100644 --- a/.github/workflows/server-mariadb-tests.yml +++ b/.github/workflows/server-mariadb-tests.yml @@ -127,4 +127,5 @@ jobs: name: MariaDB fail_ci_if_error: true files: /home/runner/frappe-bench/sites/coverage.xml - verbose: true \ No newline at end of file + verbose: true + flags: server \ No newline at end of file diff --git a/.github/workflows/server-postgres-tests.yml b/.github/workflows/server-postgres-tests.yml index a5630121a4..2203b657ad 100644 --- a/.github/workflows/server-postgres-tests.yml +++ b/.github/workflows/server-postgres-tests.yml @@ -131,3 +131,4 @@ jobs: fail_ci_if_error: true files: /home/runner/frappe-bench/sites/coverage.xml verbose: true + flags: server diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 2cbe4cce41..a9d331c44d 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -154,3 +154,4 @@ jobs: fail_ci_if_error: true directory: /home/runner/frappe-bench/apps/frappe/.cypress-coverage/ verbose: true + flags: ui-tests diff --git a/codecov.yml b/codecov.yml index 41b22001a5..b2dfa82fe1 100644 --- a/codecov.yml +++ b/codecov.yml @@ -9,5 +9,15 @@ coverage: threshold: 0.5% comment: - layout: "diff" + layout: "diff, flags" require_changes: true + +flags: + server: + paths: + - ".*\.py" + carryforward: true + ui-tests: + paths: + - ".*\.js" + carryforward: true \ No newline at end of file From 4ebdabda485e19777bb64613d9eb0fefa3c76b27 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 4 Oct 2021 10:59:47 +0530 Subject: [PATCH 14/51] fix: Encapsulate cache methods --- .../doctype/server_script/test_server_script.py | 2 +- frappe/utils/safe_exec.py | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/frappe/core/doctype/server_script/test_server_script.py b/frappe/core/doctype/server_script/test_server_script.py index cf49430ff4..a3de577ef2 100644 --- a/frappe/core/doctype/server_script/test_server_script.py +++ b/frappe/core/doctype/server_script/test_server_script.py @@ -79,7 +79,7 @@ frappe.db.commit() reference_doctype = 'ToDo', disabled = 1, script = ''' -frappe.cache.set_value('test_key', doc.name) +frappe.cache().set_value('test_key', doc.name) ''' ) ] diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 6f9398745e..79a103c9b3 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -156,12 +156,7 @@ def get_safe_globals(): commit = frappe.db.commit ) - out.frappe.cache = NamespaceDict( - get_value = frappe.cache().get_value, - set_value = frappe.cache().set_value, - hset = frappe.cache().hset, - hget = frappe.cache().hget - ) + out.frappe.cache = cache if frappe.response: out.frappe.response = frappe.response @@ -180,6 +175,14 @@ def get_safe_globals(): return out +def cache(): + return NamespaceDict( + get_value = frappe.cache().get_value, + set_value = frappe.cache().set_value, + hset = frappe.cache().hset, + hget = frappe.cache().hget + ) + def read_sql(query, *args, **kwargs): '''a wrapper for frappe.db.sql to allow reads''' if query.strip().split(None, 1)[0].lower() == 'select': From cbbfdf33dea2e56cf8499a5d12bbd777cbbe12e0 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 4 Oct 2021 17:49:21 +0530 Subject: [PATCH 15/51] fix: use site path instead of site name to generate DB name --- frappe/installer.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/frappe/installer.py b/frappe/installer.py index f0bf0cb51c..7234c9ba70 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -29,6 +29,10 @@ def _new_site( ): """Install a new Frappe site""" + from frappe.commands.scheduler import _is_scheduler_enabled + from frappe.utils import get_site_path, scheduler, touch_file + + if not force and os.path.exists(site): print("Site {0} already exists".format(site)) sys.exit(1) @@ -37,14 +41,11 @@ def _new_site( print("--no-mariadb-socket requires db_type to be set to mariadb.") sys.exit(1) - if not db_name: - import hashlib - db_name = "_" + hashlib.sha1(site.encode()).hexdigest()[:16] - frappe.init(site=site) - from frappe.commands.scheduler import _is_scheduler_enabled - from frappe.utils import get_site_path, scheduler, touch_file + if not db_name: + import hashlib + db_name = "_" + hashlib.sha1(get_site_path().encode()).hexdigest()[:16] try: # enable scheduler post install? From f2cf8981b5c5b4ba41093bab326a042c604fb74d Mon Sep 17 00:00:00 2001 From: Pruthvi Patel Date: Mon, 4 Oct 2021 17:59:34 +0530 Subject: [PATCH 16/51] fix: get abs path --- frappe/installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/installer.py b/frappe/installer.py index 7234c9ba70..8b840ede46 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -45,7 +45,7 @@ def _new_site( if not db_name: import hashlib - db_name = "_" + hashlib.sha1(get_site_path().encode()).hexdigest()[:16] + db_name = "_" + hashlib.sha1(os.path.realpath(frappe.get_site_path()).encode()).hexdigest()[:16] try: # enable scheduler post install? From 25f694ab429308e83c9bcc4de194c5815e641360 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 5 Oct 2021 12:55:07 +0530 Subject: [PATCH 17/51] docs: frappe school link in readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 6c2804d843..0ca60b9532 100644 --- a/README.md +++ b/README.md @@ -55,5 +55,9 @@ Full-stack web application framework that uses Python and MariaDB on the server For details and documentation, see the website [https://frappeframework.com](https://frappeframework.com) +## Learning + +1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community. + ### License This repository has been released under the [MIT License](LICENSE). From f22101a9a4aa6409054060821277ce77bc9e72d4 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 5 Oct 2021 13:02:11 +0530 Subject: [PATCH 18/51] docs: making all headings same in readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0ca60b9532..6d1ad7b1c4 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,12 @@ Full-stack web application framework that uses Python and MariaDB on the server side and a tightly integrated client side library. Built for [ERPNext](https://erpnext.com) -### Table of Contents +## Table of Contents * [Installation](https://frappeframework.com/docs/user/en/installation) * [Documentation](https://frappeframework.com/docs) * [License](#license) -### Installation +## Installation * [Install via Docker](https://github.com/frappe/frappe_docker) * [Install via Frappe Bench](https://github.com/frappe/bench) @@ -50,7 +50,7 @@ Full-stack web application framework that uses Python and MariaDB on the server 1. [Contribution Guidelines](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines) 1. [Translations](https://translate.erpnext.com) -### Website +## Website For details and documentation, see the website [https://frappeframework.com](https://frappeframework.com) @@ -59,5 +59,5 @@ For details and documentation, see the website 1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community. -### License +## License This repository has been released under the [MIT License](LICENSE). From 8d48122633d3938951d35eacc79ad3e2cf81fdfe Mon Sep 17 00:00:00 2001 From: MitulDavid Date: Tue, 5 Oct 2021 13:15:46 +0530 Subject: [PATCH 19/51] ci: Fix Codecov configuration --- codecov.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/codecov.yml b/codecov.yml index b2dfa82fe1..eeba1ff381 100644 --- a/codecov.yml +++ b/codecov.yml @@ -4,9 +4,17 @@ codecov: coverage: status: project: - default: + default: false + server: target: auto threshold: 0.5% + flags: + - server + ui-tests: + target: auto + threshold: 0.5% + flags: + - ui-tests comment: layout: "diff, flags" @@ -15,9 +23,9 @@ comment: flags: server: paths: - - ".*\.py" + - ".*\\.py" carryforward: true ui-tests: paths: - - ".*\.js" + - ".*\\.js" carryforward: true \ No newline at end of file From 25277e02da994dafe7bfe4da81f81ac6bdfd70e0 Mon Sep 17 00:00:00 2001 From: Aradhya-Tripathi Date: Tue, 5 Oct 2021 15:48:52 +0530 Subject: [PATCH 20/51] fix: Fixed get_values --- frappe/database/database.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/frappe/database/database.py b/frappe/database/database.py index 4af7252cc1..381247ffa2 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -20,6 +20,7 @@ from frappe.query_builder.functions import Count from frappe.query_builder.functions import Min, Max, Avg, Sum from frappe.query_builder.utils import Column from .query import Query +from pypika.terms import PseudoColumn class Database(object): @@ -521,11 +522,19 @@ class Database(object): return self.get_single_value(*args, **kwargs) def _get_values_from_table(self, fields, filters, doctype, as_dict, debug, order_by=None, update=None, for_update=False): + field_objects = [] + for field in fields: + if "(" in field or " as " in field: + field_objects.append(PseudoColumn(field)) + else: + field_objects.append(field) + criterion = self.query.build_conditions(table=doctype, filters=filters, orderby=order_by, for_update=for_update) + if isinstance(fields, (list, tuple)): - query = self.query.build_conditions(table=doctype, filters=filters, orderby=order_by, for_update=for_update).select(*fields) + query = criterion.select(*field_objects) else: if fields=="*": - query = self.query.build_conditions(table=doctype, filters=filters, orderby=order_by, for_update=for_update).select(fields) + query = criterion.select(fields) as_dict = True r = self.sql(query, as_dict=as_dict, debug=debug, update=update) From f0010439c77796879e1455997974849732176d3b Mon Sep 17 00:00:00 2001 From: Aradhya-Tripathi Date: Tue, 5 Oct 2021 15:54:27 +0530 Subject: [PATCH 21/51] style: formatted code --- frappe/database/database.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/database/database.py b/frappe/database/database.py index 381247ffa2..f07e0c38e3 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -523,11 +523,13 @@ class Database(object): def _get_values_from_table(self, fields, filters, doctype, as_dict, debug, order_by=None, update=None, for_update=False): field_objects = [] + for field in fields: if "(" in field or " as " in field: field_objects.append(PseudoColumn(field)) else: field_objects.append(field) + criterion = self.query.build_conditions(table=doctype, filters=filters, orderby=order_by, for_update=for_update) if isinstance(fields, (list, tuple)): From 4919d1ba627c43c8067a267b2cb042f1795d5616 Mon Sep 17 00:00:00 2001 From: Aradhya-Tripathi Date: Tue, 5 Oct 2021 16:02:51 +0530 Subject: [PATCH 22/51] feat: Added tests for get_value with sql functions --- frappe/tests/test_db.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py index 20f38dc964..2607bf7393 100644 --- a/frappe/tests/test_db.py +++ b/frappe/tests/test_db.py @@ -22,6 +22,8 @@ class TestDB(unittest.TestCase): self.assertNotEqual(frappe.db.get_value("User", {"name": ["!=", "Guest"]}), "Guest") self.assertEqual(frappe.db.get_value("User", {"name": ["<", "Adn"]}), "Administrator") self.assertEqual(frappe.db.get_value("User", {"name": ["<=", "Administrator"]}), "Administrator") + self.assertEqual(frappe.db.get_value("User", {}, ["Max(name)"]), "Guest") + self.assertEqual(frappe.db.get_value("User", {}, "Max(name)"), "Guest") self.assertEqual(frappe.db.sql("""SELECT name FROM `tabUser` WHERE name > 's' ORDER BY MODIFIED DESC""")[0][0], frappe.db.get_value("User", {"name": [">", "s"]})) From 162645d7037ad2029408eff4927b8c2384a2d149 Mon Sep 17 00:00:00 2001 From: Aradhya-Tripathi Date: Tue, 5 Oct 2021 16:23:37 +0530 Subject: [PATCH 23/51] fix: fixed get_value tests --- frappe/tests/test_db.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py index 2607bf7393..0d32a72756 100644 --- a/frappe/tests/test_db.py +++ b/frappe/tests/test_db.py @@ -22,8 +22,8 @@ class TestDB(unittest.TestCase): self.assertNotEqual(frappe.db.get_value("User", {"name": ["!=", "Guest"]}), "Guest") self.assertEqual(frappe.db.get_value("User", {"name": ["<", "Adn"]}), "Administrator") self.assertEqual(frappe.db.get_value("User", {"name": ["<=", "Administrator"]}), "Administrator") - self.assertEqual(frappe.db.get_value("User", {}, ["Max(name)"]), "Guest") - self.assertEqual(frappe.db.get_value("User", {}, "Max(name)"), "Guest") + self.assertEqual(frappe.db.get_value("User", {}, ["Max(name)"]), frappe.db.sql("SELECT Max(name) FROM tabUser")[0][0]) + self.assertEqual(frappe.db.get_value("User", {}, "Min(name)"), frappe.db.sql("SELECT Min(name) FROM tabUser")[0][0]) self.assertEqual(frappe.db.sql("""SELECT name FROM `tabUser` WHERE name > 's' ORDER BY MODIFIED DESC""")[0][0], frappe.db.get_value("User", {"name": [">", "s"]})) From dca6dd2750bcefa4acab2123806240cbd1f5bcc7 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Tue, 5 Oct 2021 17:03:17 +0530 Subject: [PATCH 24/51] fix: Changing the drag icon in list settings --- frappe/public/js/frappe/list/list_settings.js | 4 ++-- frappe/public/js/frappe/utils/utils.js | 4 ++-- frappe/public/scss/desk/list.scss | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index 4877d6fbb7..3a82935a53 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -114,14 +114,14 @@ export default class ListSettings {
- + ${frappe.utils.icon("drag", "xs", "", "sortable-handle " + show_sortable_handle)}
${me.fields[idx].label}
diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index f534dff1c6..0a80bb081c 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1123,7 +1123,7 @@ Object.assign(frappe.utils, { } }, - icon(icon_name, size="sm", icon_class="") { + icon(icon_name, size="sm", icon_class="", svg_class="") { let size_class = ""; let icon_style = ""; if (typeof size == "object") { @@ -1131,7 +1131,7 @@ Object.assign(frappe.utils, { } else { size_class = `icon-${size}`; } - return ` + return ` `; }, diff --git a/frappe/public/scss/desk/list.scss b/frappe/public/scss/desk/list.scss index 1818f6d8b3..9c78dd0102 100644 --- a/frappe/public/scss/desk/list.scss +++ b/frappe/public/scss/desk/list.scss @@ -228,6 +228,10 @@ input.list-check-all, input.list-row-checkbox { z-index: 500; top: 0; } + + .sortable-handle { + cursor: -webkit-grabbing; + } } .list-items { From dc9f6a58c85ff2b02f5fc29930aca9eadbeb845f Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Tue, 5 Oct 2021 17:26:52 +0530 Subject: [PATCH 25/51] fix: Resolved conflict --- frappe/public/js/frappe/list/list_settings.js | 2 +- frappe/public/js/frappe/utils/utils.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index 3a82935a53..782a077a78 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -114,7 +114,7 @@ export default class ListSettings {
- ${frappe.utils.icon("drag", "xs", "", "sortable-handle " + show_sortable_handle)} + ${frappe.utils.icon("drag", "xs", "", "", "sortable-handle " + show_sortable_handle)}
${me.fields[idx].label} diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 0a80bb081c..825bd8c1a1 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1123,7 +1123,7 @@ Object.assign(frappe.utils, { } }, - icon(icon_name, size="sm", icon_class="", svg_class="") { + icon(icon_name, size="sm", icon_class="", icon_style="", svg_class="") { let size_class = ""; let icon_style = ""; if (typeof size == "object") { From f0e7605b65f145257cc560d8d6d715126929ea44 Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Wed, 6 Oct 2021 14:18:46 +0530 Subject: [PATCH 26/51] fix: Resolved redefine object issue on webform load (#14367) --- frappe/public/js/frappe/utils/datetime.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/utils/datetime.js b/frappe/public/js/frappe/utils/datetime.js index 0c6fea2986..99d47a6deb 100644 --- a/frappe/public/js/frappe/utils/datetime.js +++ b/frappe/public/js/frappe/utils/datetime.js @@ -242,18 +242,21 @@ Object.defineProperties(window, { get: function() { console.warn('Please use `frappe.datetime` instead of `dateutil`. It will be deprecated soon.'); return frappe.datetime; - } + }, + configurable: true }, 'date': { get: function() { console.warn('Please use `frappe.datetime` instead of `date`. It will be deprecated soon.'); return frappe.datetime; - } + }, + configurable: true }, 'get_today': { get: function() { console.warn('Please use `frappe.datetime.get_today` instead of `get_today`. It will be deprecated soon.'); return frappe.datetime.get_today; - } + }, + configurable: true } }); From 1d9000fd85346dbba2aae5850e07162bb9b445ef Mon Sep 17 00:00:00 2001 From: Sun Howwrongbum Date: Wed, 6 Oct 2021 14:21:03 +0530 Subject: [PATCH 27/51] feat: expose frappe.db.exists to Server Script --- 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 0d1574b49a..1751d41b66 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -147,6 +147,7 @@ def get_safe_globals(): set_value = frappe.db.set_value, get_single_value = frappe.db.get_single_value, get_default = frappe.db.get_default, + exists = frappe.db.exists, count = frappe.db.count, min = frappe.db.min, max = frappe.db.max, From d254ca2f505833c6d74a5302c283b574f4d8ae9d Mon Sep 17 00:00:00 2001 From: MitulDavid Date: Wed, 6 Oct 2021 17:37:20 +0530 Subject: [PATCH 28/51] fix: Make images in comments private --- frappe/core/doctype/file/file.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index d9ecd85533..569c6ab23b 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -839,6 +839,7 @@ def extract_images_from_html(doc, content): doctype = doc.parenttype if doc.parent else doc.doctype name = doc.parent or doc.name + is_private = True if doctype == "Comment" else False _file = frappe.get_doc({ "doctype": "File", @@ -846,7 +847,8 @@ def extract_images_from_html(doc, content): "attached_to_doctype": doctype, "attached_to_name": name, "content": content, - "decode": False + "decode": False, + "is_private": is_private }) _file.save(ignore_permissions=True) file_url = _file.file_url From 34ba19a131765e8110b6e261f20199e93e2929e5 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 7 Oct 2021 10:27:47 +0530 Subject: [PATCH 29/51] fix: browser printing broken --- frappe/printing/page/print/print.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/printing/page/print/print.js b/frappe/printing/page/print/print.js index d3b1c69815..12a48eaa88 100644 --- a/frappe/printing/page/print/print.js +++ b/frappe/printing/page/print/print.js @@ -464,7 +464,7 @@ frappe.ui.form.PrintView = class { printit() { let me = this; - if (me.print_settings.enable_print_server) { + if (cint(me.print_settings.enable_print_server)) { if (localStorage.getItem('network_printer')) { me.print_by_server(); } else { From 5387e48a5995039d0aa284da943922a214134b1b Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 7 Oct 2021 11:24:25 +0530 Subject: [PATCH 30/51] fix: network printer settings permission issue --- .../network_printer_settings.json | 12 +++++++++++- frappe/printing/page/print/print.js | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/frappe/printing/doctype/network_printer_settings/network_printer_settings.json b/frappe/printing/doctype/network_printer_settings/network_printer_settings.json index cbef4b8ba4..11f1382225 100644 --- a/frappe/printing/doctype/network_printer_settings/network_printer_settings.json +++ b/frappe/printing/doctype/network_printer_settings/network_printer_settings.json @@ -41,10 +41,11 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-09-17 11:30:16.781655", + "modified": "2021-10-07 11:23:13.799402", "modified_by": "Administrator", "module": "Printing", "name": "Network Printer Settings", + "naming_rule": "Set by user", "owner": "Administrator", "permissions": [ { @@ -58,6 +59,15 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1 } ], "sort_field": "modified", diff --git a/frappe/printing/page/print/print.js b/frappe/printing/page/print/print.js index 12a48eaa88..c1ab56b181 100644 --- a/frappe/printing/page/print/print.js +++ b/frappe/printing/page/print/print.js @@ -177,7 +177,7 @@ frappe.ui.form.PrintView = class { ); } - if (this.print_settings.enable_print_server) { + if (cint(this.print_settings.enable_print_server)) { this.page.add_menu_item(__('Select Network Printer'), () => this.network_printer_setting_dialog() ); From 3a5b967bfe5f178c3d948876beda8a596ff89f12 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 7 Oct 2021 12:45:18 +0530 Subject: [PATCH 31/51] docs: Update README * Fix Table of contents * Restructure and reword Resources section * Link code of conduct & security policy to README --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6d1ad7b1c4..2f9ed0c4dd 100644 --- a/README.md +++ b/README.md @@ -36,28 +36,28 @@ Full-stack web application framework that uses Python and MariaDB on the server side and a tightly integrated client side library. Built for [ERPNext](https://erpnext.com) ## Table of Contents -* [Installation](https://frappeframework.com/docs/user/en/installation) -* [Documentation](https://frappeframework.com/docs) +* [Installation](#installation) +* [Contributing](#contributing) +* [Resources](#resources) * [License](#license) ## Installation * [Install via Docker](https://github.com/frappe/frappe_docker) * [Install via Frappe Bench](https://github.com/frappe/bench) +* [Offical Documentation](https://frappeframework.com/docs/user/en/installation) ## Contributing +1. [Code of Conduct](CODE_OF_CONDUCT) 1. [Contribution Guidelines](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines) +1. [Security Policy](SECURITY) 1. [Translations](https://translate.erpnext.com) -## Website +## Resources -For details and documentation, see the website -[https://frappeframework.com](https://frappeframework.com) - -## Learning - -1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community. +1. [frappeframework.com](https://frappeframework.com) - Official documentation of the Frappe Framework. +1. [frappe.school](https://frappe.school) - Pick from the various courses by the maintainers or from the community. ## License This repository has been released under the [MIT License](LICENSE). From b34178a3844ea863ffe297aace3a1694817ff023 Mon Sep 17 00:00:00 2001 From: gavin Date: Thu, 7 Oct 2021 12:51:44 +0530 Subject: [PATCH 32/51] docs: Fix links in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2f9ed0c4dd..c0d9966340 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,9 @@ Full-stack web application framework that uses Python and MariaDB on the server ## Contributing -1. [Code of Conduct](CODE_OF_CONDUCT) +1. [Code of Conduct](CODE_OF_CONDUCT.md) 1. [Contribution Guidelines](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines) -1. [Security Policy](SECURITY) +1. [Security Policy](SECURITY.md) 1. [Translations](https://translate.erpnext.com) ## Resources From 0f73909d60ee5d60c5739b83e0bb60a6a0c45e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20de=20Chazelles?= Date: Wed, 29 Sep 2021 16:40:58 +0200 Subject: [PATCH 33/51] fix: include 'Duration' in fieldtype to fetch (cherry picked from commit 06ad3b534c3131504dcf481be29cb8e1a78739ae) --- frappe/public/js/frappe/form/script_manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/script_manager.js b/frappe/public/js/frappe/form/script_manager.js index f960f4f767..f877a7cf8b 100644 --- a/frappe/public/js/frappe/form/script_manager.js +++ b/frappe/public/js/frappe/form/script_manager.js @@ -193,7 +193,7 @@ frappe.ui.form.ScriptManager = class ScriptManager { function setup_add_fetch(df) { if ((['Data', 'Read Only', 'Text', 'Small Text', 'Currency', 'Check', - 'Text Editor', 'Code', 'Link', 'Float', 'Int', 'Date', 'Select'].includes(df.fieldtype) || df.read_only==1) + 'Text Editor', 'Code', 'Link', 'Float', 'Int', 'Date', 'Select', 'Duration'].includes(df.fieldtype) || df.read_only==1) && df.fetch_from && df.fetch_from.indexOf(".")!=-1) { var parts = df.fetch_from.split("."); me.frm.add_fetch(parts[0], parts[1], df.fieldname); From 0879c35cbcff34a0784b64a4bb218cd96ac8ee83 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Thu, 7 Oct 2021 15:50:34 +0530 Subject: [PATCH 34/51] chore: Only show server side coverage for now --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c0d9966340..f8a1907da2 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ - +
From 6b91ade73c07dc1c070ed137cf54a29a3e7b0993 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 7 Oct 2021 15:58:23 +0530 Subject: [PATCH 35/51] fix: `convert_dates_to_str` converts everything except `date` objects (#14340) Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- frappe/model/base_document.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 5605ac61ed..1826cca9a3 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -267,7 +267,12 @@ class BaseDocument(object): if isinstance(d[fieldname], list) and df.fieldtype not in table_fields: frappe.throw(_('Value for {0} cannot be a list').format(_(df.label))) - if convert_dates_to_str and isinstance(d[fieldname], (datetime.datetime, datetime.time, datetime.timedelta)): + if convert_dates_to_str and isinstance(d[fieldname], ( + datetime.datetime, + datetime.date, + datetime.time, + datetime.timedelta + )): d[fieldname] = str(d[fieldname]) if d[fieldname] == None and ignore_nulls: From 6c450e9d9d560f3d4995dd885010f3e4e1513b4e Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 7 Oct 2021 16:18:33 +0530 Subject: [PATCH 36/51] fix: Fixed minimized modal border shadow --- frappe/public/scss/common/modal.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/scss/common/modal.scss b/frappe/public/scss/common/modal.scss index 19975610e2..54843290fc 100644 --- a/frappe/public/scss/common/modal.scss +++ b/frappe/public/scss/common/modal.scss @@ -169,7 +169,7 @@ body.modal-open[style^="padding-right"] { border-radius: var(--border-radius-md); border-bottom-right-radius: 0; border-bottom-left-radius: 0; - box-shadow: -10px 10px rgba(0, 0, 0, 0.100661); + box-shadow: var(--shadow-lg); } @include media-breakpoint-down(sm) { From 05cb2c16cd037a9b6110a45295a46bfe2c07295b Mon Sep 17 00:00:00 2001 From: MitulDavid Date: Thu, 7 Oct 2021 16:41:44 +0530 Subject: [PATCH 37/51] fix: Optional parameter for setting extracted image as private --- frappe/core/doctype/file/file.py | 3 +-- frappe/desk/form/utils.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 569c6ab23b..4df9ef3132 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -813,7 +813,7 @@ def extract_images_from_doc(doc, fieldname): doc.set(fieldname, content) -def extract_images_from_html(doc, content): +def extract_images_from_html(doc, content, is_private=False): frappe.flags.has_dataurl = False def _save_file(match): @@ -839,7 +839,6 @@ def extract_images_from_html(doc, content): doctype = doc.parenttype if doc.parent else doc.doctype name = doc.parent or doc.name - is_private = True if doctype == "Comment" else False _file = frappe.get_doc({ "doctype": "File", diff --git a/frappe/desk/form/utils.py b/frappe/desk/form/utils.py index a4dcee4ab3..bd04e1915c 100644 --- a/frappe/desk/form/utils.py +++ b/frappe/desk/form/utils.py @@ -66,7 +66,7 @@ def add_comment(reference_doctype, reference_name, content, comment_email, comme comment_type='Comment', comment_by=comment_by )) - doc.content = extract_images_from_html(doc, content) + doc.content = extract_images_from_html(doc, content, is_private=True) doc.insert(ignore_permissions=True) follow_document(doc.reference_doctype, doc.reference_name, frappe.session.user) From 8653b90ec6b522ea857b03681b15082e1ae41365 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 7 Oct 2021 16:44:20 +0530 Subject: [PATCH 38/51] fix: Replacing -webkit-grabbing with grabbing --- frappe/public/scss/desk/desktop.scss | 9 ++++----- frappe/public/scss/desk/list.scss | 3 ++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frappe/public/scss/desk/desktop.scss b/frappe/public/scss/desk/desktop.scss index 2ab6d98e20..6ab01a744c 100644 --- a/frappe/public/scss/desk/desktop.scss +++ b/frappe/public/scss/desk/desktop.scss @@ -164,12 +164,11 @@ body { .drag-handle { cursor: all-scroll; - cursor: -webkit-grabbing; + cursor: grabbing; &:active { + cursor: all-scroll; cursor: grabbing; - cursor: -moz-grabbing; - cursor: -webkit-grabbing; } } @@ -813,7 +812,7 @@ body { .drag-handle { cursor: all-scroll; - cursor: -webkit-grabbing; + cursor: grabbing; display: none; } @@ -966,7 +965,7 @@ body { .drag-handle { cursor: all-scroll; - cursor: -webkit-grabbing; + cursor: grabbing; } } } diff --git a/frappe/public/scss/desk/list.scss b/frappe/public/scss/desk/list.scss index 9c78dd0102..4456acabb3 100644 --- a/frappe/public/scss/desk/list.scss +++ b/frappe/public/scss/desk/list.scss @@ -230,7 +230,8 @@ input.list-check-all, input.list-row-checkbox { } .sortable-handle { - cursor: -webkit-grabbing; + cursor: all-scroll; + cursor: grabbing; } } From f7a8ee7756fb0b684293077e078144d95cd1b66c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 7 Oct 2021 16:50:12 +0530 Subject: [PATCH 39/51] fix: custom dialog box not working --- frappe/public/js/frappe/form/grid.js | 22 ++++++++++++---------- frappe/public/js/frappe/form/grid_row.js | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 574543c3a6..1d302f5e1f 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -773,16 +773,18 @@ export default class Grid { } setup_user_defined_columns() { - let user_settings = frappe.get_user_settings(this.frm.doctype, 'GridView'); - if (user_settings && user_settings[this.doctype] && user_settings[this.doctype].length) { - this.user_defined_columns = user_settings[this.doctype].map(row => { - let column = frappe.meta.get_docfield(this.doctype, row.fieldname); - if (column) { - column.in_list_view = 1; - column.columns = row.columns; - return column; - } - }); + if (this.frm) { + let user_settings = frappe.get_user_settings(this.frm.doctype, 'GridView'); + if (user_settings && user_settings[this.doctype] && user_settings[this.doctype].length) { + this.user_defined_columns = user_settings[this.doctype].map(row => { + let column = frappe.meta.get_docfield(this.doctype, row.fieldname); + if (column) { + column.in_list_view = 1; + column.columns = row.columns; + return column; + } + }); + } } } diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 20a04ee206..de174cf37f 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -497,7 +497,7 @@ export default class GridRow { } update_user_settings_for_grid() { - if (!this.selected_columns_for_grid) { + if (!this.selected_columns_for_grid || !this.frm) { return; } From 6886036002a7860ddb349eadd8d0428d288a40a6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 7 Oct 2021 17:32:45 +0530 Subject: [PATCH 40/51] feat: option to set the width for the multi-select dialog box --- frappe/public/js/frappe/form/multi_select_dialog.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index ba522a4085..37b7e08a80 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -70,6 +70,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { this.dialog = new frappe.ui.Dialog({ title: title, fields: this.fields, + size: this.size, primary_action_label: this.primary_action_label || __("Get Items"), secondary_action_label: __("Make {0}", [__(this.doctype)]), primary_action: () => { @@ -135,7 +136,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { this.get_child_result().then(r => { this.child_results = r.message || []; this.render_child_datatable(); - + this.$wrapper.addClass('hidden'); this.$child_wrapper.removeClass('hidden'); this.dialog.fields_dict.more_btn.$wrapper.hide(); From 42acb7647f1eab113485e05d68d84228826523b0 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 7 Oct 2021 20:04:04 +0530 Subject: [PATCH 41/51] ci: Upgrade Py37 to Py39 --- .github/workflows/patch-mariadb-tests.yml | 2 +- .github/workflows/publish-assets-develop.yml | 2 +- .github/workflows/publish-assets-releases.yml | 2 +- .github/workflows/server-mariadb-tests.yml | 2 +- .github/workflows/server-postgres-tests.yml | 2 +- .github/workflows/ui-tests.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml index 56137d0bea..8758c4e273 100644 --- a/.github/workflows/patch-mariadb-tests.yml +++ b/.github/workflows/patch-mariadb-tests.yml @@ -29,7 +29,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: '3.9' - name: Check if build should be run id: check-build diff --git a/.github/workflows/publish-assets-develop.yml b/.github/workflows/publish-assets-develop.yml index 85f3f7c3b0..f56d1460b5 100644 --- a/.github/workflows/publish-assets-develop.yml +++ b/.github/workflows/publish-assets-develop.yml @@ -18,7 +18,7 @@ jobs: node-version: 14 - uses: actions/setup-python@v2 with: - python-version: '3.7' + python-version: '3.9' - name: Set up bench and build assets run: | npm install -g yarn diff --git a/.github/workflows/publish-assets-releases.yml b/.github/workflows/publish-assets-releases.yml index a5cc1f8872..2582632fa0 100644 --- a/.github/workflows/publish-assets-releases.yml +++ b/.github/workflows/publish-assets-releases.yml @@ -21,7 +21,7 @@ jobs: python-version: '12.x' - uses: actions/setup-python@v2 with: - python-version: '3.7' + python-version: '3.9' - name: Set up bench and build assets run: | npm install -g yarn diff --git a/.github/workflows/server-mariadb-tests.yml b/.github/workflows/server-mariadb-tests.yml index 2e50aa48f6..588f357f26 100644 --- a/.github/workflows/server-mariadb-tests.yml +++ b/.github/workflows/server-mariadb-tests.yml @@ -38,7 +38,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: '3.9' - name: Check if build should be run id: check-build diff --git a/.github/workflows/server-postgres-tests.yml b/.github/workflows/server-postgres-tests.yml index 2203b657ad..78f379837b 100644 --- a/.github/workflows/server-postgres-tests.yml +++ b/.github/workflows/server-postgres-tests.yml @@ -41,7 +41,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: '3.9' - name: Check if build should be run id: check-build diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index a9d331c44d..fcc53ba59c 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -37,7 +37,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: '3.9' - name: Check if build should be run id: check-build From 3537316a2a6d6804c6418c6e4bfa1ececfccaa1c Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 7 Oct 2021 20:40:44 +0530 Subject: [PATCH 42/51] fix: Routing with hash in URL - Hash in document name should be encoded --- frappe/public/js/frappe/list/list_view.js | 2 +- frappe/public/js/frappe/router.js | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 1cf4b4c6ac..09072e106e 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -907,7 +907,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { return this.settings.get_form_link(doc); } - const docname = doc.name.match(/[%'"\s]/) + const docname = doc.name.match(/[%'"#\s]/) ? encodeURIComponent(doc.name) : doc.name; diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index 484f1ac911..349eadffb0 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -56,10 +56,6 @@ $('body').on('click', 'a', function(e) { return override(e.currentTarget.hash); } - if (frappe.router.is_app_route(e.currentTarget.pathname)) { - // target has "/app, this is a v2 style route. - return override(e.currentTarget.pathname + e.currentTarget.hash); - } }); frappe.router = { @@ -263,7 +259,9 @@ frappe.router = { return new Promise(resolve => { route = this.get_route_from_arguments(route); route = this.convert_from_standard_route(route); - const sub_path = this.make_url(route); + let sub_path = this.make_url(route); + // replace each # occurrences in the URL with encoded character except for last + // sub_path = sub_path.replace(/[#](?=.*[#])/g, "%23"); this.push_state(sub_path); setTimeout(() => { @@ -347,7 +345,7 @@ frappe.router = { return null; } else { a = String(a); - if (a && a.match(/[%'"\s\t]/)) { + if (a && a.match(/[%'"#\s\t]/)) { // if special chars, then encode a = encodeURIComponent(a); } @@ -374,7 +372,7 @@ frappe.router = { // return clean sub_path from hash or url // supports both v1 and v2 routing if (!route) { - route = window.location.pathname + window.location.hash + window.location.search; + route = window.location.pathname; if (route.includes('app#')) { // to support v1 route = window.location.hash; From f1c5fc4fe642fa8d88e6db01f4d6c6dda7e102b4 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 7 Oct 2021 21:50:08 +0530 Subject: [PATCH 43/51] feat: Shareable link for comments and communications - Also, highlight comment/communication that has been shared. --- .../js/frappe/form/footer/base_timeline.js | 8 +++-- .../js/frappe/form/footer/form_timeline.js | 36 ++++++++++++------- frappe/public/js/frappe/form/form.js | 10 +++++- .../form/templates/timeline_message_box.html | 14 ++++++++ frappe/public/js/frappe/utils/utils.js | 16 +++++++-- frappe/public/scss/common/css_variables.scss | 2 ++ frappe/public/scss/desk/dark.scss | 2 ++ frappe/public/scss/desk/global.scss | 13 +++++++ frappe/public/scss/desk/timeline.scss | 2 +- 9 files changed, 84 insertions(+), 19 deletions(-) diff --git a/frappe/public/js/frappe/form/footer/base_timeline.js b/frappe/public/js/frappe/form/footer/base_timeline.js index 702d964442..beeba16459 100644 --- a/frappe/public/js/frappe/form/footer/base_timeline.js +++ b/frappe/public/js/frappe/form/footer/base_timeline.js @@ -97,9 +97,13 @@ class BaseTimeline { } timeline_item.append(`
`); - timeline_item.find('.timeline-content').append(item.content); + let timeline_content = timeline_item.find('.timeline-content'); + timeline_content.append(item.content); if (!item.hide_timestamp && !item.is_card) { - timeline_item.find('.timeline-content').append(` - ${comment_when(item.creation)}`); + timeline_content.append(` - ${comment_when(item.creation)}`); + } + if (item.id) { + timeline_content.attr("id", item.id); } return timeline_item; } diff --git a/frappe/public/js/frappe/form/footer/form_timeline.js b/frappe/public/js/frappe/form/footer/form_timeline.js index b3feae3ee8..128bd355ad 100644 --- a/frappe/public/js/frappe/form/footer/form_timeline.js +++ b/frappe/public/js/frappe/form/footer/form_timeline.js @@ -96,6 +96,7 @@ class FormTimeline extends BaseTimeline { render_timeline_items() { super.render_timeline_items(); this.set_document_info(); + frappe.utils.bind_actions_with_object(this.timeline_items_wrapper, this); } set_document_info() { @@ -179,6 +180,7 @@ class FormTimeline extends BaseTimeline { is_card: true, content: this.get_communication_timeline_content(communication), doctype: "Communication", + id: `communication-${communication.name}`, name: communication.name }); }); @@ -246,6 +248,7 @@ class FormTimeline extends BaseTimeline { creation: comment.creation, is_card: true, doctype: "Comment", + id: `comment-${comment.name}`, name: comment.name, content: this.get_comment_timeline_content(comment), }; @@ -394,7 +397,7 @@ class FormTimeline extends BaseTimeline { } setup_reply(communication_box, communication_doc) { - let actions = communication_box.find('.actions'); + let actions = communication_box.find('.custom-actions'); let reply = $(`${frappe.utils.icon('reply', 'md')}`).click(() => { this.compose_mail(communication_doc); }); @@ -446,14 +449,16 @@ class FormTimeline extends BaseTimeline { let edit_wrapper = $(`
`).hide(); let edit_box = this.make_editable(edit_wrapper); let content_wrapper = comment_wrapper.find('.content'); - - let delete_button = $(); + let more_actions_wrapper = comment_wrapper.find('.more-actions'); if (frappe.model.can_delete("Comment")) { - delete_button = $(` - + const delete_option = $(` +
  • + + ${__("Delete")} + +
  • `).click(() => this.delete_comment(doc.name)); + more_actions_wrapper.find('.dropdown-menu').append(delete_option); } let dismiss_button = $(` @@ -493,15 +498,14 @@ class FormTimeline extends BaseTimeline { edit_button.toggle_edit_mode = () => { edit_button.edit_mode = !edit_button.edit_mode; edit_button.text(edit_button.edit_mode ? __('Save') : __('Edit')); - delete_button.toggle(!edit_button.edit_mode); + more_actions_wrapper.toggle(!edit_button.edit_mode); dismiss_button.toggle(edit_button.edit_mode); edit_wrapper.toggle(edit_button.edit_mode); content_wrapper.toggle(!edit_button.edit_mode); }; - - comment_wrapper.find('.actions').append(edit_button); - comment_wrapper.find('.actions').append(dismiss_button); - comment_wrapper.find('.actions').append(delete_button); + let actions_wrapper = comment_wrapper.find('.custom-actions'); + actions_wrapper.append(edit_button); + actions_wrapper.append(dismiss_button); } make_editable(container) { @@ -559,6 +563,14 @@ class FormTimeline extends BaseTimeline { }); }); } + + copy_link(ev) { + let doc_link = frappe.urllib.get_full_url( + frappe.utils.get_form_link(this.frm.doctype, this.frm.docname) + ); + let element_id = $(ev.currentTarget).closest(".timeline-content").attr("id"); + frappe.utils.copy_to_clipboard(`${doc_link}#${element_id}`); + } } export default FormTimeline; diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 97473c3069..a095956dfe 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -480,7 +480,11 @@ frappe.ui.form.Form = class FrappeForm { this.layout.show_empty_form_message(); } - this.scroll_to_element(); + frappe.after_ajax(() => { + $(document).ready(() => { + this.scroll_to_element(); + }); + }); } set_first_tab_as_active() { @@ -598,6 +602,8 @@ frappe.ui.form.Form = class FrappeForm { this.validate_form_action(save_action, resolve); var after_save = function(r) { + // to remove hash from URL to avoid scroll after save + history.replaceState(null, null, ' '); if(!r.exc) { if (["Save", "Update", "Amend"].indexOf(save_action)!==-1) { frappe.utils.play_sound("click"); @@ -1195,6 +1201,8 @@ frappe.ui.form.Form = class FrappeForm { if (selector.length) { frappe.utils.scroll_to(selector); } + } else if (window.location.hash && $(window.location.hash).length) { + frappe.utils.scroll_to(window.location.hash, true, 200, null, null, true); } } diff --git a/frappe/public/js/frappe/form/templates/timeline_message_box.html b/frappe/public/js/frappe/form/templates/timeline_message_box.html index 3884918165..a38e36c7b5 100644 --- a/frappe/public/js/frappe/form/templates/timeline_message_box.html +++ b/frappe/public/js/frappe/form/templates/timeline_message_box.html @@ -63,6 +63,20 @@ {% } %} +
    +
    diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index f534dff1c6..33dc447890 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -268,7 +268,8 @@ Object.assign(frappe.utils, {

    '); return content.html(); }, - scroll_to: function(element, animate=true, additional_offset, element_to_be_scrolled, callback) { + scroll_to: function(element, animate=true, additional_offset, + element_to_be_scrolled, callback, highlight_element=false) { if (frappe.flags.disable_auto_scroll) return; element_to_be_scrolled = element_to_be_scrolled || $("html, body"); @@ -291,11 +292,20 @@ Object.assign(frappe.utils, { } if (animate) { - element_to_be_scrolled.animate({ scrollTop: scroll_top }).promise().then(callback); + element_to_be_scrolled.animate({ + scrollTop: scroll_top + }, null, () => { + if (highlight_element) { + $(element).addClass('highlight'); + document.addEventListener("click", function() { + $(element).removeClass('highlight'); + }, {once: true}); + } + callback && callback(); + }); } else { element_to_be_scrolled.scrollTop(scroll_top); } - }, get_scroll_position: function(element, additional_offset) { let header_offset = $(".navbar").height() + $(".page-head:visible").height(); diff --git a/frappe/public/scss/common/css_variables.scss b/frappe/public/scss/common/css_variables.scss index 112238bfe5..ede3dd8ba9 100644 --- a/frappe/public/scss/common/css_variables.scss +++ b/frappe/public/scss/common/css_variables.scss @@ -209,6 +209,8 @@ --highlight-color: var(--gray-50); --yellow-highlight-color: var(--yellow-50); + --highlight-shadow: 1px 1px 10px var(--blue-50), 0px 0px 4px var(--blue-600); + // Border Sizes --border-radius-sm: 4px; --border-radius: 6px; diff --git a/frappe/public/scss/desk/dark.scss b/frappe/public/scss/desk/dark.scss index 7f0dfe73b8..35cdffc91a 100644 --- a/frappe/public/scss/desk/dark.scss +++ b/frappe/public/scss/desk/dark.scss @@ -75,6 +75,8 @@ --highlight-color: var(--gray-700); --yellow-highlight-color: var(--yellow-700); + --highlight-shadow: 1px 1px 10px var(--blue-900), 0px 0px 4px var(--blue-500); + // input --input-disabled-bg: none; diff --git a/frappe/public/scss/desk/global.scss b/frappe/public/scss/desk/global.scss index 8c646395e9..d157a43bc3 100644 --- a/frappe/public/scss/desk/global.scss +++ b/frappe/public/scss/desk/global.scss @@ -561,6 +561,19 @@ details > summary:focus { display: none; } +.highlight { + transition: 0.5s ease background-color; + box-shadow: var(--highlight-shadow) !important; +} + +.dropdown-menu.small { + font-size: var(--text-sm); + min-width: 140px; + .dropdown-item { + padding: var(--padding-xs); + } +} + // REDESIGN TODO: Handling of broken images? // img.no-image:before { // .img-background(); diff --git a/frappe/public/scss/desk/timeline.scss b/frappe/public/scss/desk/timeline.scss index a7e5d3dd9c..1861ee018b 100644 --- a/frappe/public/scss/desk/timeline.scss +++ b/frappe/public/scss/desk/timeline.scss @@ -117,7 +117,7 @@ $threshold: 34; .actions { display: flex; - > * { + > *:not(.indicator-pill) { color: var(--text-muted); } } From 59e0501cc7cd765cbefc2204df3aa4ec5bcbb94a Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 8 Oct 2021 08:58:52 +0530 Subject: [PATCH 44/51] test: Fix timeline tests --- cypress/integration/timeline.js | 7 ++++--- cypress/support/commands.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cypress/integration/timeline.js b/cypress/integration/timeline.js index 6387485220..decde5c5de 100644 --- a/cypress/integration/timeline.js +++ b/cypress/integration/timeline.js @@ -43,13 +43,14 @@ context('Timeline', () => { cy.get('.timeline-content').should('contain', 'Testing Timeline 123'); //Deleting the added comment - cy.get('.actions > .btn > .icon').first().click(); + cy.get('.more-actions > .action-btn').click(); + cy.get('.more-actions .dropdown-item').contains('Delete').click(); cy.findByRole('button', {name: 'Yes'}).click(); cy.click_modal_primary_button('Yes'); //Deleting the added ToDo - cy.get('.menu-btn-group button').eq(1).click(); - cy.get('.menu-btn-group [data-label="Delete"]').click(); + cy.get('.menu-btn-group [data-original-title="Menu"]').click(); + cy.get('.menu-btn-group .dropdown-item').contains('Delete').click(); cy.findByRole('button', {name: 'Yes'}).click(); }); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 47c37a56a0..6484370946 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -353,5 +353,5 @@ Cypress.Commands.add('click_listview_primary_button', (btn_name) => { }); Cypress.Commands.add('click_timeline_action_btn', (btn_name) => { - cy.get('.timeline-content > .timeline-message-box > .justify-between > .actions > .btn').contains(btn_name).click(); + cy.get('.timeline-message-box .custom-actions > .btn').contains(btn_name).click(); }); \ No newline at end of file From 3a004d38dbfba2db7f1e8a9532c90c014d7a24d6 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 8 Oct 2021 09:39:31 +0530 Subject: [PATCH 45/51] test: Fix flaky test - Due to improper cleanup one of the test of Energy Point was failing --- frappe/email/doctype/notification/test_notification.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/email/doctype/notification/test_notification.py b/frappe/email/doctype/notification/test_notification.py index a086ded3fb..f05d35be3e 100644 --- a/frappe/email/doctype/notification/test_notification.py +++ b/frappe/email/doctype/notification/test_notification.py @@ -274,4 +274,7 @@ class TestNotification(unittest.TestCase): self.assertTrue('test2@example.com' in recipients) self.assertTrue('test1@example.com' in recipients) - + @classmethod + def tearDownClass(cls): + frappe.delete_doc_if_exists("Notification", "ToDo Status Update") + frappe.delete_doc_if_exists("Notification", "Contact Status Update") \ No newline at end of file From e6bbc698d07e46a8ec6455e990ec2bb001facdf4 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 8 Oct 2021 11:40:31 +0530 Subject: [PATCH 46/51] ci: fail CI if asset bundling fails (#14364) * ci: fail CI if asset bundling fails * chore: formatting Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- .github/helper/install.sh | 2 +- esbuild/esbuild.js | 5 ++++- frappe/build.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index 93189d2b1f..6c81d6298a 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -59,4 +59,4 @@ cd ../.. bench start & bench --site test_site reinstall --yes if [ "$TYPE" == "server" ]; then bench --site test_site_producer reinstall --yes; fi -bench build --app frappe +CI=Yes bench build --app frappe diff --git a/esbuild/esbuild.js b/esbuild/esbuild.js index 9074beae06..bf4436358e 100644 --- a/esbuild/esbuild.js +++ b/esbuild/esbuild.js @@ -104,6 +104,9 @@ async function execute() { log_error("There were some problems during build"); log(); log(chalk.dim(e.stack)); + if (process.env.CI) { + process.kill(process.pid); + } return; } @@ -528,4 +531,4 @@ function log_rebuilt_assets(prev_assets, new_assets) { log(" " + filename); } log(); -} \ No newline at end of file +} diff --git a/frappe/build.py b/frappe/build.py index 879d5ec432..05fa213018 100644 --- a/frappe/build.py +++ b/frappe/build.py @@ -246,7 +246,7 @@ def bundle(mode, apps=None, hard_link=False, make_copy=False, restore=False, ver check_node_executable() frappe_app_path = frappe.get_app_path("frappe", "..") - frappe.commands.popen(command, cwd=frappe_app_path, env=get_node_env()) + frappe.commands.popen(command, cwd=frappe_app_path, env=get_node_env(), raise_err=True) def watch(apps=None): From 7f24e10852adac4232afea489dc4d9b90c8bd104 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 8 Oct 2021 13:54:16 +0530 Subject: [PATCH 47/51] fix: Use promise to wait for all animations to complete --- frappe/public/js/frappe/utils/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 401d945b11..d79f86b084 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -294,7 +294,7 @@ Object.assign(frappe.utils, { if (animate) { element_to_be_scrolled.animate({ scrollTop: scroll_top - }, null, () => { + }).promise().then(() => { if (highlight_element) { $(element).addClass('highlight'); document.addEventListener("click", function() { From a1ddd25dff8a110c297e1210314ed089d9b35433 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 8 Oct 2021 15:36:58 +0530 Subject: [PATCH 48/51] fix: unable to delete role permissions --- frappe/core/page/permission_manager/permission_manager.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/core/page/permission_manager/permission_manager.js b/frappe/core/page/permission_manager/permission_manager.js index 41cc900a97..6b427fdebf 100644 --- a/frappe/core/page/permission_manager/permission_manager.js +++ b/frappe/core/page/permission_manager/permission_manager.js @@ -325,15 +325,15 @@ frappe.PermissionEngine = class PermissionEngine { .attr("data-doctype", d.parent) .attr("data-role", d.role) .attr("data-permlevel", d.permlevel) - .click(function () { + .on("click", () => { return frappe.call({ module: "frappe.core", page: "permission_manager", method: "remove", args: { - doctype: $(this).attr("data-doctype"), - role: $(this).attr("data-role"), - permlevel: $(this).attr("data-permlevel") + doctype: d.parent, + role: d.role, + permlevel: d.permlevel }, callback: (r) => { if (r.exc) { From 4f74f9e4e10b565b66b8099246005a595b67aefd Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 8 Oct 2021 15:52:44 +0530 Subject: [PATCH 49/51] fix: Add rollback to safe_exec --- frappe/utils/safe_exec.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 0e5b6cc142..bc677991a7 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -155,7 +155,8 @@ def get_safe_globals(): sum = frappe.db.sum, escape = frappe.db.escape, sql = read_sql, - commit = frappe.db.commit + commit = frappe.db.commit, + rollback = frappe.db.rollback ) out.frappe.cache = cache From e245c8da09e0f99d59cbb3b71c1404ff1c6f6167 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 8 Oct 2021 18:15:12 +0530 Subject: [PATCH 50/51] fix: Don't load entire db file into memory Cleaning up my own mess :') Read and write entire SQL file line by line --- frappe/installer.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/frappe/installer.py b/frappe/installer.py index 8b840ede46..e1b67ee10c 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -456,9 +456,21 @@ def convert_archive_content(sql_file_path): if frappe.conf.db_type == "mariadb": # ever since mariaDB 10.6, row_format COMPRESSED has been deprecated and removed # this step is added to ease restoring sites depending on older mariaDB servers - contents = open(sql_file_path).read() - with open(sql_file_path, "w") as f: - f.write(contents.replace("ROW_FORMAT=COMPRESSED", "ROW_FORMAT=DYNAMIC")) + from frappe.utils import random_string + from pathlib import Path + + old_sql_file_path = Path(f"{sql_file_path}_{random_string(10)}") + sql_file_path = Path(sql_file_path) + + os.rename(sql_file_path, old_sql_file_path) + sql_file_path.unlink(missing_ok=True) + sql_file_path.touch() + + with open(old_sql_file_path) as r, open(sql_file_path, "a") as w: + for line in r: + w.write(line.replace("ROW_FORMAT=COMPRESSED", "ROW_FORMAT=DYNAMIC")) + + old_sql_file_path.unlink(missing_ok=True) def extract_sql_gzip(sql_gz_path): From 1127d3d18472ad92afe8a0686b42c98a346a659a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 10 Oct 2021 17:41:26 +0530 Subject: [PATCH 51/51] fix: Restrict commit and rollback for doc events --- frappe/core/doctype/server_script/server_script.py | 11 +---------- .../core/doctype/server_script/test_server_script.py | 3 +-- frappe/utils/safe_exec.py | 6 +++++- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index 14f173f906..5b1aab1241 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -11,8 +11,6 @@ from frappe.model.document import Document from frappe.utils.safe_exec import get_safe_globals, safe_exec, NamespaceDict from frappe import _ -class CommitNotAllowed(frappe.ValidationError): - pass class ServerScript(Document): def validate(self): @@ -96,14 +94,7 @@ class ServerScript(Document): Args: doc (Document): Executes script with for a certain document's events """ - self.validate_script_for_doc_events() - safe_exec(self.script, _locals={"doc": doc}) - - def validate_script_for_doc_events(self): - for line in self.script.splitlines(): - line = line.strip() - if not line.startswith('#') and "frappe.db.commit()" in line: - frappe.throw(_("Commit cannot be used in DocType Event server script"), CommitNotAllowed) + safe_exec(self.script, _locals={"doc": doc}, restrict_commit_rollback=True) def execute_scheduled_method(self): """Specific to Scheduled Jobs via Server Scripts diff --git a/frappe/core/doctype/server_script/test_server_script.py b/frappe/core/doctype/server_script/test_server_script.py index a3de577ef2..de858327a9 100644 --- a/frappe/core/doctype/server_script/test_server_script.py +++ b/frappe/core/doctype/server_script/test_server_script.py @@ -5,7 +5,6 @@ import frappe import unittest import requests from frappe.utils import get_site_url -from frappe.core.doctype.server_script.server_script import CommitNotAllowed scripts = [ dict( @@ -146,7 +145,7 @@ class TestServerScript(unittest.TestCase): server_script.disabled = 0 server_script.save() - self.assertRaises(CommitNotAllowed, frappe.get_doc(dict(doctype='ToDo', description='test me')).insert) + self.assertRaises(AttributeError, frappe.get_doc(dict(doctype='ToDo', description='test me')).insert) server_script.disabled = 1 server_script.save() diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index bc677991a7..6aac8d819d 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -29,7 +29,7 @@ class NamespaceDict(frappe._dict): return ret -def safe_exec(script, _globals=None, _locals=None): +def safe_exec(script, _globals=None, _locals=None, restrict_commit_rollback=False): # server scripts can be disabled via site_config.json # they are enabled by default if 'server_script_enabled' in frappe.conf: @@ -45,6 +45,10 @@ def safe_exec(script, _globals=None, _locals=None): if _globals: exec_globals.update(_globals) + if restrict_commit_rollback: + exec_globals.frappe.db.pop('commit', None) + exec_globals.frappe.db.pop('rollback', None) + # execute script compiled by RestrictedPython exec(compile_restricted(script), exec_globals, _locals) # pylint: disable=exec-used