From 39f504be23c97175231e44cf10b08bb962c3b5fe Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Thu, 10 Jun 2021 13:51:29 +0530 Subject: [PATCH 01/76] fix: number format converting to decimals --- frappe/public/js/frappe/form/controls/int.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/int.js b/frappe/public/js/frappe/form/controls/int.js index 12652bf86e..1aa4a49b9a 100644 --- a/frappe/public/js/frappe/form/controls/int.js +++ b/frappe/public/js/frappe/form/controls/int.js @@ -10,12 +10,11 @@ frappe.ui.form.ControlInt = class ControlInt extends frappe.ui.form.ControlData super.make_input(); this.$input // .addClass("text-right") - .on("focus", function () { + .on("focusout", function () { setTimeout(function () { if (!document.activeElement) return; document.activeElement.value = me.validate(document.activeElement.value); - document.activeElement.select(); }, 100); return false; }); From 7ad1e2d11db2261dc34693d68526104e0a51ce78 Mon Sep 17 00:00:00 2001 From: shariquerik Date: Tue, 22 Jun 2021 17:13:20 +0530 Subject: [PATCH 02/76] fix: Webform Permission for custom doctypem --- frappe/www/list.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frappe/www/list.py b/frappe/www/list.py index 5e4e491c80..975347adac 100644 --- a/frappe/www/list.py +++ b/frappe/www/list.py @@ -161,6 +161,14 @@ def get_list_context(context, doctype, web_form_name=None): module = load_doctype_module(doctype) list_context = update_context_from_module(module, list_context) + # get context for custom doctype + if meta.custom: + get_custom_website_context = frappe.get_hooks('get_custom_website_context') + if get_custom_website_context: + out = frappe._dict(frappe.get_attr(get_custom_website_context[0])() or {}) + if out: + list_context = out + # get context from web form module if web_form_name: web_form = frappe.get_doc('Web Form', web_form_name) From d39a389fd5a066bb08b5805550f6b86d0b4618d3 Mon Sep 17 00:00:00 2001 From: shariquerik Date: Mon, 12 Jul 2021 17:01:30 +0530 Subject: [PATCH 03/76] fix: app check condition for getting correct list_context --- frappe/www/list.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/www/list.py b/frappe/www/list.py index 975347adac..3ed103b69d 100644 --- a/frappe/www/list.py +++ b/frappe/www/list.py @@ -161,11 +161,11 @@ def get_list_context(context, doctype, web_form_name=None): module = load_doctype_module(doctype) list_context = update_context_from_module(module, list_context) - # get context for custom doctype - if meta.custom: - get_custom_website_context = frappe.get_hooks('get_custom_website_context') - if get_custom_website_context: - out = frappe._dict(frappe.get_attr(get_custom_website_context[0])() or {}) + # get context for custom webform + if meta.custom and web_form_name: + list_context_for_custom_webform = frappe.get_hooks('get_list_context_for_custom_webform') + if list_context_for_custom_webform: + out = frappe._dict(frappe.get_attr(list_context_for_custom_webform[0])(meta.module) or {}) if out: list_context = out From 52941e55b105a64da970de33b7c340422540fbab Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Tue, 27 Jul 2021 00:29:01 +0530 Subject: [PATCH 04/76] fix: no need for focusout in favour of onchange --- frappe/public/js/frappe/form/controls/int.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/int.js b/frappe/public/js/frappe/form/controls/int.js index 1aa4a49b9a..ce2336bb82 100644 --- a/frappe/public/js/frappe/form/controls/int.js +++ b/frappe/public/js/frappe/form/controls/int.js @@ -8,16 +8,6 @@ frappe.ui.form.ControlInt = class ControlInt extends frappe.ui.form.ControlData make_input () { var me = this; super.make_input(); - this.$input - // .addClass("text-right") - .on("focusout", function () { - setTimeout(function () { - if (!document.activeElement) return; - document.activeElement.value - = me.validate(document.activeElement.value); - }, 100); - return false; - }); } validate (value) { return this.parse(value); From 895520885e08e216da52bcc8e7f668170f0bd408 Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Tue, 31 Aug 2021 11:27:33 +0530 Subject: [PATCH 05/76] chore: Better naming convention Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- frappe/www/list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/www/list.py b/frappe/www/list.py index 3ed103b69d..74fadfb70f 100644 --- a/frappe/www/list.py +++ b/frappe/www/list.py @@ -163,7 +163,7 @@ def get_list_context(context, doctype, web_form_name=None): # get context for custom webform if meta.custom and web_form_name: - list_context_for_custom_webform = frappe.get_hooks('get_list_context_for_custom_webform') + webform_list_contexts = frappe.get_hooks('webform_list_context') if list_context_for_custom_webform: out = frappe._dict(frappe.get_attr(list_context_for_custom_webform[0])(meta.module) or {}) if out: From d8d25bdf19819633715bf558ecf41e55db859e00 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Tue, 31 Aug 2021 11:34:42 +0530 Subject: [PATCH 06/76] chore: Better naming convention --- frappe/www/list.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/www/list.py b/frappe/www/list.py index 74fadfb70f..f8e4a4eb93 100644 --- a/frappe/www/list.py +++ b/frappe/www/list.py @@ -164,8 +164,8 @@ def get_list_context(context, doctype, web_form_name=None): # get context for custom webform if meta.custom and web_form_name: webform_list_contexts = frappe.get_hooks('webform_list_context') - if list_context_for_custom_webform: - out = frappe._dict(frappe.get_attr(list_context_for_custom_webform[0])(meta.module) or {}) + if webform_list_contexts: + out = frappe._dict(frappe.get_attr(webform_list_contexts[0])(meta.module) or {}) if out: list_context = out From 765a255a009dfd5dcbc3ee46ebb23415ce6d6b5d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 31 Aug 2021 18:26:15 +0530 Subject: [PATCH 07/76] ci: use ubuntu-latest for all jobs --- .github/workflows/patch-mariadb-tests.yml | 2 +- .github/workflows/server-mariadb-tests.yml | 4 ++-- .github/workflows/server-postgres-tests.yml | 2 +- .github/workflows/translation_linter.yml | 22 --------------------- .github/workflows/ui-tests.yml | 2 +- 5 files changed, 5 insertions(+), 27 deletions(-) delete mode 100644 .github/workflows/translation_linter.yml diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml index 0dd4cd51d8..3ac5cfa349 100644 --- a/.github/workflows/patch-mariadb-tests.yml +++ b/.github/workflows/patch-mariadb-tests.yml @@ -9,7 +9,7 @@ concurrency: jobs: test: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest name: Patch Test diff --git a/.github/workflows/server-mariadb-tests.yml b/.github/workflows/server-mariadb-tests.yml index fb6e56037c..9187aa3c30 100644 --- a/.github/workflows/server-mariadb-tests.yml +++ b/.github/workflows/server-mariadb-tests.yml @@ -13,7 +13,7 @@ concurrency: jobs: test: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest strategy: fail-fast: false @@ -146,7 +146,7 @@ jobs: name: Coverage Wrap Up needs: test container: python:3-slim - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v1 with: diff --git a/.github/workflows/server-postgres-tests.yml b/.github/workflows/server-postgres-tests.yml index 1539e8c2d5..539cf53f65 100644 --- a/.github/workflows/server-postgres-tests.yml +++ b/.github/workflows/server-postgres-tests.yml @@ -10,7 +10,7 @@ concurrency: jobs: test: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/.github/workflows/translation_linter.yml b/.github/workflows/translation_linter.yml deleted file mode 100644 index 4becaebd6b..0000000000 --- a/.github/workflows/translation_linter.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Frappe Linter -on: - pull_request: - branches: - - develop - - version-12-hotfix - - version-11-hotfix -jobs: - check_translation: - name: Translation Syntax Check - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@v2 - - name: Setup python3 - uses: actions/setup-python@v1 - with: - python-version: 3.6 - - name: Validating Translation Syntax - run: | - git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q - files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) - python $GITHUB_WORKSPACE/.github/helper/translation.py $files diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 2a55546ec4..0727b06043 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -12,7 +12,7 @@ concurrency: jobs: test: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest strategy: fail-fast: false From c663ab7d4493be0ad2e738519f4135114f0f2a7a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 31 Aug 2021 21:59:06 +0530 Subject: [PATCH 08/76] test: improve test failure message --- frappe/tests/test_commands.py | 40 ++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py index 1797698a11..564ddafe54 100644 --- a/frappe/tests/test_commands.py +++ b/frappe/tests/test_commands.py @@ -63,6 +63,28 @@ def clean(value): return value +def missing_in_backup(doctypes, file): + """Checks if the list of doctypes exist in the database.sql.gz file supplied + + Args: + doctypes (list): List of DocTypes to be checked + file (str): Path of the database file + + Returns: + doctypes(list): doctypes that are missing in backup + """ + predicate = ( + 'COPY public."tab{}"' + if frappe.conf.db_type == "postgres" + else "CREATE TABLE `tab{}`" + ) + with gzip.open(file, "rb") as f: + content = f.read().decode("utf8").lower() + + return [doctype for doctype in doctypes + if predicate.format(doctype).lower() not in content] + + def exists_in_backup(doctypes, file): """Checks if the list of doctypes exist in the database.sql.gz file supplied @@ -73,14 +95,8 @@ def exists_in_backup(doctypes, file): Returns: bool: True if all tables exist """ - predicate = ( - 'COPY public."tab{}"' - if frappe.conf.db_type == "postgres" - else "CREATE TABLE `tab{}`" - ) - with gzip.open(file, "rb") as f: - content = f.read().decode("utf8") - return all(predicate.format(doctype).lower() in content.lower() for doctype in doctypes) + missing_doctypes = missing_in_backup(doctypes, file) + return len(missing_doctypes) == 0 class BaseTestCommands(unittest.TestCase): @@ -222,7 +238,7 @@ class TestCommands(BaseTestCommands): self.execute("bench --site {site} backup --verbose") self.assertEqual(self.returncode, 0) database = fetch_latest_backups(partial=True)["database"] - self.assertTrue(exists_in_backup(backup["includes"]["includes"], database)) + self.assertEqual([], missing_in_backup(backup["includes"]["includes"], database)) # test 8: take a backup with frappe.conf.backup.excludes self.execute( @@ -233,7 +249,7 @@ class TestCommands(BaseTestCommands): self.assertEqual(self.returncode, 0) database = fetch_latest_backups(partial=True)["database"] self.assertFalse(exists_in_backup(backup["excludes"]["excludes"], database)) - self.assertTrue(exists_in_backup(backup["includes"]["includes"], database)) + self.assertEqual([], missing_in_backup(backup["includes"]["includes"], database)) # test 9: take a backup with --include (with frappe.conf.excludes still set) self.execute( @@ -242,7 +258,7 @@ class TestCommands(BaseTestCommands): ) self.assertEqual(self.returncode, 0) database = fetch_latest_backups(partial=True)["database"] - self.assertTrue(exists_in_backup(backup["includes"]["includes"], database)) + self.assertEqual([], missing_in_backup(backup["includes"]["includes"], database)) # test 10: take a backup with --exclude self.execute( @@ -257,7 +273,7 @@ class TestCommands(BaseTestCommands): self.execute("bench --site {site} backup --ignore-backup-conf") self.assertEqual(self.returncode, 0) database = fetch_latest_backups()["database"] - self.assertTrue(exists_in_backup(backup["excludes"]["excludes"], database)) + self.assertEqual([], missing_in_backup(backup["excludes"]["excludes"], database)) def test_restore(self): # step 0: create a site to run the test on From 6c847987f124ec0408a1ee0075878fdcf615d844 Mon Sep 17 00:00:00 2001 From: Youssef Date: Wed, 1 Sep 2021 10:07:49 +0000 Subject: [PATCH 09/76] feat: Choose Letter Head when printing multiple documents from List /Report (cherry picked from commit 1c6688fd9c39008e86e88617ce901ba02b9076de) # Conflicts: # frappe/public/js/frappe/list/bulk_operations.js --- .../public/js/frappe/list/bulk_operations.js | 82 +++++++++++++------ 1 file changed, 57 insertions(+), 25 deletions(-) diff --git a/frappe/public/js/frappe/list/bulk_operations.js b/frappe/public/js/frappe/list/bulk_operations.js index 3b99560411..3bb7b33853 100644 --- a/frappe/public/js/frappe/list/bulk_operations.js +++ b/frappe/public/js/frappe/list/bulk_operations.js @@ -4,7 +4,7 @@ export default class BulkOperations { this.doctype = doctype; } - print(docs) { + print (docs) { const print_settings = frappe.model.get_doc(':Print Settings', 'Print Settings'); const allow_print_for_draft = cint(print_settings.allow_print_for_draft); const is_submittable = frappe.model.is_submittable(this.doctype); @@ -27,31 +27,38 @@ export default class BulkOperations { if (valid_docs.length > 0) { const dialog = new frappe.ui.Dialog({ title: __('Print Documents'), - fields: [{ - 'fieldtype': 'Check', - 'label': __('With Letterhead'), - 'fieldname': 'with_letterhead' - }, - { - 'fieldtype': 'Select', - 'label': __('Print Format'), - 'fieldname': 'print_sel', - options: frappe.meta.get_print_formats(this.doctype) - }] + fields: [ + { + 'fieldtype': 'Select', + 'label': __('Letter Head'), + 'fieldname': 'letter_sel', + 'default': __('No Letterhead'), + options: this.get_letterhead_options() + }, + { + 'fieldtype': 'Select', + 'label': __('Print Format'), + 'fieldname': 'print_sel', + options: frappe.meta.get_print_formats(this.doctype) + } + ] }); dialog.set_primary_action(__('Print'), args => { if (!args) return; const default_print_format = frappe.get_meta(this.doctype).default_print_format; - const with_letterhead = args.with_letterhead ? 1 : 0; + const with_letterhead = args.letter_sel == __("No Letterhead") ? 0 : 1; const print_format = args.print_sel ? args.print_sel : default_print_format; const json_string = JSON.stringify(valid_docs); - + const letterhead = args.letter_sel; const w = window.open('/api/method/frappe.utils.print_format.download_multi_pdf?' + 'doctype=' + encodeURIComponent(this.doctype) + '&name=' + encodeURIComponent(json_string) + '&format=' + encodeURIComponent(print_format) + - '&no_letterhead=' + (with_letterhead ? '0' : '1')); + '&no_letterhead=' + (with_letterhead ? '0' : '1') + + '&letterhead=' + encodeURIComponent(letterhead) + ); + if (!w) { frappe.msgprint(__('Please enable pop-ups')); return; @@ -64,7 +71,28 @@ export default class BulkOperations { } } - delete(docnames, done = null) { + get_letterhead_options () { + const letterhead_options = [__("No Letterhead")]; + frappe.call({ + method: "frappe.client.get_list", + args: { + doctype: 'Letter Head', + fields: ['name', 'is_default'], + limit: 0 + }, + async: false, + callback (r) { + if (r.message) { + r.message.forEach(letterhead => { + letterhead_options.push(letterhead.name); + }); + } + } + }); + return letterhead_options; + } + + delete (docnames, done = null) { frappe .call({ method: 'frappe.desk.reportview.delete_items', @@ -88,7 +116,7 @@ export default class BulkOperations { }); } - assign(docnames, done) { + assign (docnames, done) { if (docnames.length > 0) { const assign_to = new frappe.ui.form.AssignToDialog({ obj: this, @@ -106,7 +134,7 @@ export default class BulkOperations { } } - apply_assignment_rule(docnames, done) { + apply_assignment_rule (docnames, done) { if (docnames.length > 0) { frappe.call('frappe.automation.doctype.assignment_rule.assignment_rule.bulk_apply', { doctype: this.doctype, @@ -115,7 +143,7 @@ export default class BulkOperations { } } - submit_or_cancel(docnames, action='submit', done=null) { + submit_or_cancel (docnames, action = 'submit', done = null) { action = action.toLowerCase(); frappe .call({ @@ -140,7 +168,7 @@ export default class BulkOperations { }); } - edit(docnames, field_mappings, done) { + edit (docnames, field_mappings, done) { let field_options = Object.keys(field_mappings).sort(); const status_regex = /status/i; @@ -198,16 +226,16 @@ export default class BulkOperations { if (default_field) set_value_field(dialog); // to set `Value` df based on default `Field` - function set_value_field(dialogObj) { + function set_value_field (dialogObj) { const new_df = Object.assign({}, field_mappings[dialogObj.get_value('field')]); /* if the field label has status in it and if it has select fieldtype with no default value then set a default value from the available option. */ - if(new_df.label.match(status_regex) && + if (new_df.label.match(status_regex) && new_df.fieldtype === 'Select' && !new_df.default) { let options = []; - if(typeof new_df.options==="string") { + if (typeof new_df.options === "string") { options = new_df.options.split("\n"); } //set second option as default if first option is an empty string @@ -224,7 +252,7 @@ export default class BulkOperations { } - add_tags(docnames, done) { + add_tags (docnames, done) { const dialog = new frappe.ui.Dialog({ title: __('Add Tags'), fields: [ @@ -233,7 +261,7 @@ export default class BulkOperations { fieldname: 'tags', label: __("Tags"), reqd: true, - get_data: function(txt) { + get_data: function (txt) { return frappe.db.get_link_options("Tag", txt); } }, @@ -261,6 +289,7 @@ export default class BulkOperations { }); dialog.show(); } +<<<<<<< HEAD export(doctype, docnames) { frappe.require('data_import_tools.bundle.js', () => { @@ -272,3 +301,6 @@ export default class BulkOperations { }); } } +======= +} +>>>>>>> 1c6688fd9c (feat: Choose Letter Head when printing multiple documents from List /Report) From ba07f3bc95bffad45edcbd8d8a4f7fcaeb8b79fb Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Thu, 2 Sep 2021 11:30:57 +0200 Subject: [PATCH 10/76] fix: resolve merge conflicts --- frappe/public/js/frappe/list/bulk_operations.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/list/bulk_operations.js b/frappe/public/js/frappe/list/bulk_operations.js index 3bb7b33853..931f2cf587 100644 --- a/frappe/public/js/frappe/list/bulk_operations.js +++ b/frappe/public/js/frappe/list/bulk_operations.js @@ -251,7 +251,6 @@ export default class BulkOperations { dialog.show(); } - add_tags (docnames, done) { const dialog = new frappe.ui.Dialog({ title: __('Add Tags'), @@ -289,9 +288,8 @@ export default class BulkOperations { }); dialog.show(); } -<<<<<<< HEAD - export(doctype, docnames) { + export (doctype, docnames) { frappe.require('data_import_tools.bundle.js', () => { const data_exporter = new frappe.data_import.DataExporter(doctype, 'Insert New Records'); data_exporter.dialog.set_value('export_records', 'by_filter'); @@ -301,6 +299,3 @@ export default class BulkOperations { }); } } -======= -} ->>>>>>> 1c6688fd9c (feat: Choose Letter Head when printing multiple documents from List /Report) From b20e543de91cf49c5df9d9967b97dc92bcc47524 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 3 Sep 2021 16:55:44 +0530 Subject: [PATCH 11/76] test: test hook to get website_list_context --- frappe/tests/test_webform.py | 67 +++++++++++++++++++++++++++++++ frappe/www/_test/_test_webform.py | 6 +++ 2 files changed, 73 insertions(+) create mode 100644 frappe/tests/test_webform.py create mode 100644 frappe/www/_test/_test_webform.py diff --git a/frappe/tests/test_webform.py b/frappe/tests/test_webform.py new file mode 100644 index 0000000000..92dc441bdf --- /dev/null +++ b/frappe/tests/test_webform.py @@ -0,0 +1,67 @@ +import unittest + +import frappe +from frappe.www.list import get_list_context + + +class TestWebsite(unittest.TestCase): + def test_get_context_hook_of_webform(self): + create_custom_doctype() + create_webform() + + # check context for apps without any hook + context_list = get_list_context("", "Custom Doctype", "test-webform") + self.assertFalse(context_list) + + # create a hook to get webform_context + set_webform_hook( + "webform_list_context", + "frappe.www._test._test_webform.webform_list_context", + ) + # check context for apps with hook + context_list = get_list_context("", "Custom Doctype", "test-webform") + self.assertTrue(context_list) + + +def create_custom_doctype(): + frappe.get_doc( + { + "doctype": "DocType", + "name": "Custom Doctype", + "module": "Core", + "custom": 1, + "fields": [{"label": "Title", "fieldname": "title", "fieldtype": "Data"}], + } + ).insert(ignore_if_duplicate=True) + + +def create_webform(): + frappe.get_doc( + { + "doctype": "Web Form", + "module": "Core", + "title": "Test Webform", + "route": "test-webform", + "doc_type": "Custom Doctype", + "web_form_fields": [ + { + "doctype": "Web Form Field", + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + } + ], + } + ).insert(ignore_if_duplicate=True) + + +def set_webform_hook(key, value): + from frappe import hooks + + # reset hooks + for hook in "webform_list_context": + if hasattr(hooks, hook): + delattr(hooks, hook) + + setattr(hooks, key, value) + frappe.cache().delete_key("app_hooks") diff --git a/frappe/www/_test/_test_webform.py b/frappe/www/_test/_test_webform.py new file mode 100644 index 0000000000..3209e3e03c --- /dev/null +++ b/frappe/www/_test/_test_webform.py @@ -0,0 +1,6 @@ +def webform_list_context(module): + return {"get_list": get_webform_context_list} + + +def get_webform_context_list(): + pass From 46c617afde8dd3792b78cb35ba17e061ad93daaf Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 6 Sep 2021 11:28:32 +0530 Subject: [PATCH 12/76] fix(UI): Checked icon not visible in PDF --- frappe/templates/print_formats/standard_macros.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/templates/print_formats/standard_macros.html b/frappe/templates/print_formats/standard_macros.html index f8dc6c370c..ec60af1ce0 100644 --- a/frappe/templates/print_formats/standard_macros.html +++ b/frappe/templates/print_formats/standard_macros.html @@ -135,7 +135,7 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}" {% elif df.fieldtype=="Check" and doc[df.fieldname] %} From 768e4052f9de0d5bde38e11475617c3a4a69c677 Mon Sep 17 00:00:00 2001 From: leela Date: Fri, 3 Sep 2021 12:32:05 +0530 Subject: [PATCH 13/76] fix: node creation from tree view (cherry picked from commit f8cd2ad388c96eefff4730ce130806231bc0d87e) --- frappe/desk/treeview.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/frappe/desk/treeview.py b/frappe/desk/treeview.py index 704e5d8ed6..f40c135653 100644 --- a/frappe/desk/treeview.py +++ b/frappe/desk/treeview.py @@ -69,13 +69,11 @@ def make_tree_args(**kwarg): doctype = kwarg['doctype'] parent_field = 'parent_' + doctype.lower().replace(' ', '_') - name_field = kwarg.get('name_field', doctype.lower().replace(' ', '_') + '_name') if kwarg['is_root'] == 'false': kwarg['is_root'] = False if kwarg['is_root'] == 'true': kwarg['is_root'] = True kwarg.update({ - name_field: kwarg[name_field], parent_field: kwarg.get("parent") or kwarg.get(parent_field) }) From 5669d9c5591fd266bbd7346339b184128b0802cd Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 28 Aug 2021 01:42:20 +0530 Subject: [PATCH 14/76] fix: pass distinct as argument in set_modules --- frappe/core/doctype/user_type/user_type.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/user_type/user_type.py b/frappe/core/doctype/user_type/user_type.py index 82ffb090f1..abcaf0fea9 100644 --- a/frappe/core/doctype/user_type/user_type.py +++ b/frappe/core/doctype/user_type/user_type.py @@ -36,8 +36,11 @@ class UserType(Document): if not self.user_doctypes: return - modules = frappe.get_all('DocType', fields=['distinct module as module'], - filters={'name': ('in', [d.document_type for d in self.user_doctypes])}) + modules = frappe.get_all('DocType', + fields=['module'], + filters={'name': ('in', [d.document_type for d in self.user_doctypes])}, + distinct=True + ) self.set('user_type_modules', []) for row in modules: From 08b7430ba216b994bde2eba866b962253752aaa4 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 2 Sep 2021 18:15:21 +0530 Subject: [PATCH 15/76] fix: change fieldname for postgres create index --- frappe/database/postgres/database.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index 00e60fb8d2..78df54a48a 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -263,9 +263,9 @@ class PostgresDatabase(Database): Index name will be `fieldname1_fieldname2_index`""" index_name = index_name or self.get_index_name(fields) table_name = 'tab' + doctype - + fields_str = re.sub(r"\(.*\)", "", '", "'.join(fields)) self.commit() - self.sql("""CREATE INDEX IF NOT EXISTS "{}" ON `{}`("{}")""".format(index_name, table_name, '", "'.join(fields))) + self.sql(f'CREATE INDEX IF NOT EXISTS "{index_name}" ON `{table_name}`("{fields_str}")') def add_unique(self, doctype, fields, constraint_name=None): if isinstance(fields, str): From 6f0a02aaa533bdf7a0848f46547d2aab61399dfc Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 6 Sep 2021 19:37:39 +0530 Subject: [PATCH 16/76] fix: Handle multiple comment types in get_comments --- frappe/desk/form/load.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index c86efbcefd..80c0b2d471 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -1,6 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE +from typing import Dict, List, Union import frappe, json import frappe.utils import frappe.share @@ -138,10 +139,11 @@ def get_communications(doctype, name, start=0, limit=20): return _get_communications(doctype, name, start, limit) -def get_comments(doctype, name, comment_type='Comment'): - comment_types = [comment_type] +def get_comments(doctype: str, name: str, comment_type : Union[str, List[str]] = "Comment") -> List[frappe._dict]: + if isinstance(comment_type, list): + comment_types = comment_type - if comment_type == 'share': + elif comment_type == 'share': comment_types = ['Shared', 'Unshared'] elif comment_type == 'assignment': @@ -150,15 +152,21 @@ def get_comments(doctype, name, comment_type='Comment'): elif comment_type == 'attachment': comment_types = ['Attachment', 'Attachment Removed'] - comments = frappe.get_all('Comment', fields = ['name', 'creation', 'content', 'owner', 'comment_type'], filters=dict( - reference_doctype = doctype, - reference_name = name, - comment_type = ['in', comment_types] - )) + else: + comment_types = [comment_type] + + comments = frappe.get_all("Comment", + fields=["name", "creation", "content", "owner", "comment_type"], + filters={ + "reference_doctype": doctype, + "reference_name": name, + "comment_type": ['in', comment_types], + } + ) # convert to markdown (legacy ?) - if comment_type == 'Comment': - for c in comments: + for c in comments: + if c.comment_type == "Comment": c.content = frappe.utils.markdown(c.content) return comments From a233bf1061ac7ad1deb9df9b169dfcd3a6e63f7d Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 6 Sep 2021 19:39:08 +0530 Subject: [PATCH 17/76] fix: Add workflow comments in form timeline * Add Edit, Label along with Info timeline logs * Added title field for timeline icon --- frappe/desk/form/load.py | 3 ++- .../js/frappe/form/footer/form_timeline.js | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index 80c0b2d471..d276a9707f 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -106,9 +106,10 @@ def get_docinfo(doc=None, doctype=None, name=None): "assignment_logs": get_comments(doc.doctype, doc.name, 'assignment'), "permissions": get_doc_permissions(doc), "shared": frappe.share.get_users(doc.doctype, doc.name), - "info_logs": get_comments(doc.doctype, doc.name, 'Info'), + "info_logs": get_comments(doc.doctype, doc.name, comment_type=['Info', 'Edit', 'Label']), "share_logs": get_comments(doc.doctype, doc.name, 'share'), "like_logs": get_comments(doc.doctype, doc.name, 'Like'), + "workflow_logs": get_comments(doc.doctype, doc.name, comment_type="Workflow"), "views": get_view_logs(doc.doctype, doc.name), "energy_point_logs": get_point_logs(doc.doctype, doc.name), "additional_timeline_content": get_additional_timeline_content(doc.doctype, doc.name), diff --git a/frappe/public/js/frappe/form/footer/form_timeline.js b/frappe/public/js/frappe/form/footer/form_timeline.js index 115a62e098..adffce7c9c 100644 --- a/frappe/public/js/frappe/form/footer/form_timeline.js +++ b/frappe/public/js/frappe/form/footer/form_timeline.js @@ -136,6 +136,7 @@ class FormTimeline extends BaseTimeline { this.timeline_items.push(...this.get_energy_point_timeline_contents()); this.timeline_items.push(...this.get_version_timeline_contents()); this.timeline_items.push(...this.get_share_timeline_contents()); + this.timeline_items.push(...this.get_workflow_timeline_contents()); this.timeline_items.push(...this.get_like_timeline_contents()); this.timeline_items.push(...this.get_custom_timeline_contents()); this.timeline_items.push(...this.get_assignment_timeline_contents()); @@ -339,11 +340,26 @@ class FormTimeline extends BaseTimeline { icon_size: 'sm', creation: like_log.creation, content: __('{0} Liked', [this.get_user_link(like_log.owner)]), + title: "Like", }); }); return like_timeline_contents; } + get_workflow_timeline_contents() { + let workflow_timeline_contents = []; + (this.doc_info.workflow_logs || []).forEach(workflow_log => { + workflow_timeline_contents.push({ + icon: 'branch', + icon_size: 'sm', + creation: workflow_log.creation, + content: __(workflow_log.content), + title: "Workflow", + }); + }); + return workflow_timeline_contents; + } + get_custom_timeline_contents() { let custom_timeline_contents = []; (this.doc_info.additional_timeline_content || []).forEach(custom_item => { From f0b9eb5f7c848fa7e81e9ab25c427964674b96e3 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 6 Sep 2021 19:40:45 +0530 Subject: [PATCH 18/76] fix: Update available Frappe icons * Added icons for milestone * Updated Workflow and Branch icons for consistency --- frappe/public/icons/timeless/symbol-defs.svg | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/public/icons/timeless/symbol-defs.svg b/frappe/public/icons/timeless/symbol-defs.svg index b2f1428967..b878f713e9 100644 --- a/frappe/public/icons/timeless/symbol-defs.svg +++ b/frappe/public/icons/timeless/symbol-defs.svg @@ -35,10 +35,13 @@ - + + + + @@ -680,7 +683,7 @@ - + From ab7dee13cfe31fa4882b6a1f4cc313d2c92ed893 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 2 Sep 2021 02:12:19 +0530 Subject: [PATCH 19/76] chore: Added return type for DatabaseQuery.execute --- frappe/model/db_query.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index ae159c1a69..fd74a8cfe4 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -2,6 +2,7 @@ # License: MIT. See LICENSE """build query for doclistview and return results""" +from typing import List import frappe.defaults import frappe.share from frappe import _ @@ -33,7 +34,7 @@ class DatabaseQuery(object): join='left join', distinct=False, start=None, page_length=None, limit=None, ignore_ifnull=False, save_user_settings=False, save_user_settings_fields=False, update=None, add_total_row=None, user_settings=None, reference_doctype=None, - return_query=False, strict=True, pluck=None, ignore_ddl=False): + return_query=False, strict=True, pluck=None, ignore_ddl=False) -> List: if not ignore_permissions and \ not frappe.has_permission(self.doctype, "select", user=user) and \ not frappe.has_permission(self.doctype, "read", user=user): From d55254e7dc204c1d22685c26f3552da3c2e9d382 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 6 Sep 2021 19:45:44 +0530 Subject: [PATCH 20/76] feat(minor): Show title for form timeline icons --- frappe/public/js/frappe/form/footer/base_timeline.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/footer/base_timeline.js b/frappe/public/js/frappe/form/footer/base_timeline.js index ab4ad95a81..702d964442 100644 --- a/frappe/public/js/frappe/form/footer/base_timeline.js +++ b/frappe/public/js/frappe/form/footer/base_timeline.js @@ -86,7 +86,7 @@ class BaseTimeline { }); if (item.icon) { timeline_item.append(` -
+
${frappe.utils.icon(item.icon, item.icon_size || 'md')}
`); From 8b252d493aecf2a5ee6f0d99cd5b98a1087d9a98 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Mon, 6 Sep 2021 22:49:48 +0530 Subject: [PATCH 21/76] ci: Conditionally run codecov upload --- .github/workflows/server-mariadb-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/server-mariadb-tests.yml b/.github/workflows/server-mariadb-tests.yml index 57a7fa304d..72b085c495 100644 --- a/.github/workflows/server-mariadb-tests.yml +++ b/.github/workflows/server-mariadb-tests.yml @@ -121,6 +121,7 @@ jobs: ORCHESTRATOR_URL: http://test-orchestrator.frappe.io - name: Upload coverage data + if: ${{ steps.check-build.outputs.build == 'strawberry' }} uses: codecov/codecov-action@v2 with: name: MariaDB From fba196484c4efc184f3997779e57dccdb24c7adc Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Mon, 6 Sep 2021 22:50:42 +0530 Subject: [PATCH 22/76] ci: Conditionally run codecov upload --- .github/workflows/server-postgres-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/server-postgres-tests.yml b/.github/workflows/server-postgres-tests.yml index 57ac9c6c60..ddc41c049c 100644 --- a/.github/workflows/server-postgres-tests.yml +++ b/.github/workflows/server-postgres-tests.yml @@ -124,6 +124,7 @@ jobs: ORCHESTRATOR_URL: http://test-orchestrator.frappe.io - name: Upload coverage data + if: ${{ steps.check-build.outputs.build == 'strawberry' }} uses: codecov/codecov-action@v2 with: name: Postgres From 3594890eb283c7b1b8144d5029bb69551da01f61 Mon Sep 17 00:00:00 2001 From: leela Date: Mon, 6 Sep 2021 19:57:09 +0530 Subject: [PATCH 23/76] fix: customizing print formats Currently system managers can only customize the print formats even though the other roles has a permissions to do so. Fixed it to show customize button based on permissions. (cherry picked from commit f9486fce5fb24402fce31e3d36370120c4524a9b) --- .../doctype/print_format/print_format.js | 30 ++++++++++--------- frappe/printing/page/print/print.js | 2 +- frappe/public/js/frappe/views/pageview.js | 2 +- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/frappe/printing/doctype/print_format/print_format.js b/frappe/printing/doctype/print_format/print_format.js index 786f8f97ab..adc5e2363c 100644 --- a/frappe/printing/doctype/print_format/print_format.js +++ b/frappe/printing/doctype/print_format/print_format.js @@ -36,21 +36,23 @@ frappe.ui.form.on("Print Format", { else if (frm.doc.custom_format && !frm.doc.raw_printing) { frm.set_df_property("html", "reqd", 1); } - frappe.db.get_value('DocType', frm.doc.doc_type, 'default_print_format', (r) => { - if (r.default_print_format != frm.doc.name) { - frm.add_custom_button(__("Set as Default"), function () { - frappe.call({ - method: "frappe.printing.doctype.print_format.print_format.make_default", - args: { - name: frm.doc.name - }, - callback: function() { - frm.refresh(); - } + if (frappe.perm.has_perm('DocType', 0, 'read', frm.doc.doc_type)) { + frappe.db.get_value('DocType', frm.doc.doc_type, 'default_print_format', (r) => { + if (r.default_print_format != frm.doc.name) { + frm.add_custom_button(__("Set as Default"), function () { + frappe.call({ + method: "frappe.printing.doctype.print_format.print_format.make_default", + args: { + name: frm.doc.name + }, + callback: function() { + frm.refresh(); + } + }); }); - }); - } - }); + } + }); + } } }, custom_format: function (frm) { diff --git a/frappe/printing/page/print/print.js b/frappe/printing/page/print/print.js index ca2a340661..da34dfda96 100644 --- a/frappe/printing/page/print/print.js +++ b/frappe/printing/page/print/print.js @@ -174,7 +174,7 @@ frappe.ui.form.PrintView = class { }); } - if (frappe.user.has_role('System Manager')) { + if (frappe.perm.has_perm('Print Format', 0, 'create')) { this.page.add_menu_item(__('Customize'), () => this.edit_print_format() ); diff --git a/frappe/public/js/frappe/views/pageview.js b/frappe/public/js/frappe/views/pageview.js index 705d13b7f0..c8944e272a 100644 --- a/frappe/public/js/frappe/views/pageview.js +++ b/frappe/public/js/frappe/views/pageview.js @@ -148,4 +148,4 @@ frappe.show_message_page = function(opts) { ); frappe.container.change_to(opts.page_name); -}; \ No newline at end of file +}; From 2178ea7c965250a2251543d7177d043c39b12bfa Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 7 Sep 2021 11:08:02 +0530 Subject: [PATCH 24/76] style: Fix indentation for get_all query --- frappe/core/doctype/user_type/user_type.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/core/doctype/user_type/user_type.py b/frappe/core/doctype/user_type/user_type.py index abcaf0fea9..d385b8665b 100644 --- a/frappe/core/doctype/user_type/user_type.py +++ b/frappe/core/doctype/user_type/user_type.py @@ -36,11 +36,11 @@ class UserType(Document): if not self.user_doctypes: return - modules = frappe.get_all('DocType', - fields=['module'], - filters={'name': ('in', [d.document_type for d in self.user_doctypes])}, - distinct=True - ) + modules = frappe.get_all("DocType", + fields=["module"], + filters={"name": ("in", [d.document_type for d in self.user_doctypes])}, + distinct=True, + ) self.set('user_type_modules', []) for row in modules: From 07c5d0a28c7e554f774cd98bc1284f02f3aea07c Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 7 Sep 2021 11:27:38 +0530 Subject: [PATCH 25/76] fix(db_pg): Strip index size from each field --- frappe/database/postgres/database.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index 78df54a48a..a40c48c4c8 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -261,11 +261,11 @@ class PostgresDatabase(Database): def add_index(self, doctype, fields, index_name=None): """Creates an index with given fields if not already created. Index name will be `fieldname1_fieldname2_index`""" + table_name = get_table_name(doctype) index_name = index_name or self.get_index_name(fields) - table_name = 'tab' + doctype - fields_str = re.sub(r"\(.*\)", "", '", "'.join(fields)) - self.commit() - self.sql(f'CREATE INDEX IF NOT EXISTS "{index_name}" ON `{table_name}`("{fields_str}")') + fields_str = '", "'.join(re.sub(r"\(.*\)", "", field) for field in fields) + + self.sql_ddl(f'CREATE INDEX IF NOT EXISTS "{index_name}" ON `{table_name}` ("{fields_str}")') def add_unique(self, doctype, fields, constraint_name=None): if isinstance(fields, str): From b4bb4c039fb156557c9eb0d861282b544abe71ea Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 7 Sep 2021 15:24:32 +0530 Subject: [PATCH 26/76] test: Update click_sidebar_button to accept btn_name - to make tests cases readable --- cypress/integration/sidebar.js | 15 +++++++-------- cypress/support/commands.js | 10 ++++------ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/cypress/integration/sidebar.js b/cypress/integration/sidebar.js index e05f1877bf..cd771430c6 100644 --- a/cypress/integration/sidebar.js +++ b/cypress/integration/sidebar.js @@ -6,12 +6,12 @@ context('Sidebar', () => { }); it('Test for checking "Assigned To" counter value, adding filter and adding & removing an assignment', () => { - cy.click_sidebar_button(0); + cy.click_sidebar_button("Assigned To"); //To check if no filter is available in "Assigned To" dropdown cy.get('.empty-state').should('contain', 'No filters found'); - cy.click_sidebar_button(1); + cy.click_sidebar_button("Created By"); //To check if "Created By" dropdown contains filter cy.get('.group-by-item > .dropdown-item').should('contain', 'Me'); @@ -22,7 +22,7 @@ context('Sidebar', () => { cy.get_field('assign_to_me', 'Check').click(); cy.get('.modal-footer > .standard-actions > .btn-primary').click(); cy.visit('/app/doctype'); - cy.click_sidebar_button(0); + cy.click_sidebar_button("Assigned To"); //To check if filter is added in "Assigned To" dropdown after assignment cy.get('.group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item').should('contain', '1'); @@ -38,20 +38,19 @@ context('Sidebar', () => { cy.get('.fieldname-select-area > .awesomplete > .form-control').should('have.value', 'Assigned To'); cy.get('.condition').should('have.value', 'like'); cy.get('.filter-field > .form-group > .input-with-feedback').should('have.value', '%Administrator%'); + cy.click_filter_button(); //To remove the applied filter - cy.get('.filter-action-buttons > div > .btn-secondary').contains('Clear Filters').click(); - cy.click_filter_button(); - cy.get('.filter-selector > .btn').should('contain', 'Filter'); + cy.clear_filters(); //To remove the assignment cy.visit('/app/doctype'); cy.click_listview_row_item(0); cy.get('.assignments > .avatar-group > .avatar > .avatar-frame').click(); cy.get('.remove-btn').click({force: true}); - cy.get('.modal.show > .modal-dialog > .modal-content > .modal-header > .modal-actions > .btn-modal-close').click(); + cy.hide_dialog(); cy.visit('/app/doctype'); - cy.click_sidebar_button(0); + cy.click_sidebar_button("Assigned To"); cy.get('.empty-state').should('contain', 'No filters found'); }); }); \ No newline at end of file diff --git a/cypress/support/commands.js b/cypress/support/commands.js index c941652487..c48d6dee89 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -324,16 +324,14 @@ Cypress.Commands.add('clear_filters', () => { cy.window().its('cur_list').then(cur_list => { cur_list && cur_list.filter_area && cur_list.filter_area.clear(); }); - - }); Cypress.Commands.add('click_modal_primary_button', (btn_name) => { cy.get('.modal-footer > .standard-actions > .btn-primary').contains(btn_name).trigger('click', {force: true}); }); -Cypress.Commands.add('click_sidebar_button', (btn_no) => { - cy.get('.list-group-by-fields > .group-by-field > .btn').eq(btn_no).click(); +Cypress.Commands.add('click_sidebar_button', (btn_name) => { + cy.get('.list-group-by-fields .list-link > a').contains(btn_name).click({force: true}); }); Cypress.Commands.add('click_listview_row_item', (row_no) => { @@ -348,6 +346,6 @@ Cypress.Commands.add('click_listview_primary_button', (btn_name) => { cy.get('.primary-action').contains(btn_name).click({force: true}); }); -Cypress.Commands.add('click_timeline_action_btn', (btn_no) => { - cy.get('.timeline-content > .timeline-message-box > .justify-between > .actions > .btn').eq(btn_no).first().click(); +Cypress.Commands.add('click_timeline_action_btn', (btn_name) => { + cy.get('.timeline-content > .timeline-message-box > .justify-between > .actions > .btn').contains(btn_name).click(); }); \ No newline at end of file From 3aed8f9e22e2e2f64a1748f271cbd87c398d31ec Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 7 Sep 2021 16:19:27 +0530 Subject: [PATCH 27/76] test: Miscellaneous fixes to avoid flaky tests --- cypress/integration/list_view.js | 4 ++-- cypress/integration/timeline.js | 10 +++++----- cypress/support/commands.js | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/cypress/integration/list_view.js b/cypress/integration/list_view.js index 633d1335ab..298bb20432 100644 --- a/cypress/integration/list_view.js +++ b/cypress/integration/list_view.js @@ -7,11 +7,11 @@ context('List View', () => { }); }); it('enables "Actions" button', () => { - const actions = ['Approve', 'Reject', 'Edit', 'Assign To', 'Apply Assignment Rule', 'Add Tags', 'Print', 'Delete']; + const actions = ['Approve', 'Reject', 'Edit', 'Export', 'Assign To', 'Apply Assignment Rule', 'Add Tags', 'Print', 'Delete']; cy.go_to_list('ToDo'); cy.get('.list-row-container:contains("Pending") .list-row-checkbox').click({ multiple: true, force: true }); cy.get('.actions-btn-group button').contains('Actions').should('be.visible').click(); - cy.get('.dropdown-menu li:visible .dropdown-item').should('have.length', 8).each((el, index) => { + cy.get('.dropdown-menu li:visible .dropdown-item').should('have.length', 9).each((el, index) => { cy.wrap(el).contains(actions[index]); }).then((elements) => { cy.intercept({ diff --git a/cypress/integration/timeline.js b/cypress/integration/timeline.js index 7a8f3a159b..6387485220 100644 --- a/cypress/integration/timeline.js +++ b/cypress/integration/timeline.js @@ -4,11 +4,11 @@ context('Timeline', () => { before(() => { cy.visit('/login'); cy.login(); - cy.visit('/app/todo'); }); it('Adding new ToDo, adding new comment, verifying comment addition & deletion and deleting ToDo', () => { //Adding new ToDo + cy.visit('/app/todo'); cy.click_listview_primary_button('Add ToDo'); cy.findByRole('button', {name: 'Edit in full page'}).click(); cy.get('[data-fieldname="description"] .ql-editor').eq(0).type('Test ToDo', {force: true}); @@ -28,15 +28,15 @@ context('Timeline', () => { cy.get('.timeline-content').should('contain', 'Testing Timeline'); //Editing comment - cy.click_timeline_action_btn(0); + cy.click_timeline_action_btn("Edit"); cy.get('.timeline-content [data-fieldname="comment"] .ql-editor').first().type(' 123'); - cy.click_timeline_action_btn(0); + cy.click_timeline_action_btn("Save"); //To check if the edited comment text is visible in timeline content cy.get('.timeline-content').should('contain', 'Testing Timeline 123'); //Discarding comment - cy.click_timeline_action_btn(0); + cy.click_timeline_action_btn("Edit"); cy.findByRole('button', {name: 'Dismiss'}).click(); //To check if after discarding the timeline content is same as previous @@ -81,7 +81,7 @@ context('Timeline', () => { cy.visit('/app/custom-submittable-doctype'); cy.get('.list-subject > .select-like > .list-row-checkbox').eq(0).click(); cy.findByRole('button', {name: 'Actions'}).click(); - cy.get('.actions-btn-group > .dropdown-menu > li > .grey-link').eq(7).click(); + cy.get('.actions-btn-group > .dropdown-menu > li > .dropdown-item').contains("Delete").click(); cy.click_modal_primary_button('Yes', {force: true, delay: 700}); //Deleting the custom doctype diff --git a/cypress/support/commands.js b/cypress/support/commands.js index c48d6dee89..00415b487d 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -252,7 +252,8 @@ Cypress.Commands.add('new_form', doctype => { }); Cypress.Commands.add('go_to_list', doctype => { - cy.visit(`/app/list/${doctype}/list`); + let dt_in_route = doctype.toLowerCase().replace(/ /g, '-'); + cy.visit(`/app/${dt_in_route}`); }); Cypress.Commands.add('clear_cache', () => { From 74d6ed32d69991ce7c1f100ea974771b2fdbf2cd Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 7 Sep 2021 17:40:55 +0530 Subject: [PATCH 28/76] test: add frappe.db.add_index --- frappe/database/mariadb/database.py | 4 +-- frappe/database/postgres/database.py | 2 +- frappe/tests/test_db.py | 40 ++++++++++++++++++++++++---- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index d4a119804b..71acefe17c 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -256,11 +256,11 @@ class MariaDBDatabase(Database): index_name=index_name )) - def add_index(self, doctype, fields, index_name=None): + def add_index(self, doctype: str, fields: List, index_name: str = None): """Creates an index with given fields if not already created. Index name will be `fieldname1_fieldname2_index`""" index_name = index_name or self.get_index_name(fields) - table_name = 'tab' + doctype + table_name = get_table_name(doctype) if not self.has_index(table_name, index_name): self.commit() self.sql("""ALTER TABLE `%s` diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index a40c48c4c8..264d3bbf14 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -258,7 +258,7 @@ class PostgresDatabase(Database): return self.sql("""SELECT 1 FROM pg_indexes WHERE tablename='{table_name}' and indexname='{index_name}' limit 1""".format(table_name=table_name, index_name=index_name)) - def add_index(self, doctype, fields, index_name=None): + def add_index(self, doctype: str, fields: List, index_name: str = None): """Creates an index with given fields if not already created. Index name will be `fieldname1_fieldname2_index`""" table_name = get_table_name(doctype) diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py index 044ce455d9..741c355ad8 100644 --- a/frappe/tests/test_db.py +++ b/frappe/tests/test_db.py @@ -75,7 +75,7 @@ class TestDB(unittest.TestCase): frappe.db.set_value("Print Settings", "Print Settings", fieldname, inp["value"]) self.assertEqual(frappe.db.get_single_value("Print Settings", fieldname), inp["value"]) - #teardown + #teardown clear_custom_fields("Print Settings") def test_log_touched_tables(self): @@ -192,6 +192,7 @@ class TestDB(unittest.TestCase): frappe.delete_doc(test_doctype, doc) clear_custom_fields(test_doctype) + @run_only_if(db_type_is.MARIADB) class TestDDLCommandsMaria(unittest.TestCase): test_table_name = "TestNotes" @@ -200,7 +201,7 @@ class TestDDLCommandsMaria(unittest.TestCase): frappe.db.commit() frappe.db.sql( f""" - CREATE TABLE `tab{self.test_table_name}` (`id` INT NULL,PRIMARY KEY (`id`)); + CREATE TABLE `tab{self.test_table_name}` (`id` INT NULL, content TEXT, PRIMARY KEY (`id`)); """ ) @@ -225,7 +226,10 @@ class TestDDLCommandsMaria(unittest.TestCase): def test_describe(self) -> None: self.assertEqual( - (("id", "int(11)", "NO", "PRI", None, ""),), + ( + ("id", "int(11)", "NO", "PRI", None, ""), + ("content", "text", "YES", "", None, ""), + ), frappe.db.describe(self.test_table_name), ) @@ -235,6 +239,17 @@ class TestDDLCommandsMaria(unittest.TestCase): self.assertGreater(len(test_table_description), 0) self.assertIn("varchar(255)", test_table_description[0]) + def test_add_index(self) -> None: + index_name = "test_index" + frappe.db.add_index(self.test_table_name, ["id", "content(50)"], index_name) + indexs_in_table = frappe.db.sql( + f""" + SHOW INDEX FROM tab{self.test_table_name} + WHERE Key_name = '{index_name}'; + """ + ) + self.assertEquals(len(indexs_in_table), 2) + @run_only_if(db_type_is.POSTGRES) class TestDDLCommandsPost(unittest.TestCase): @@ -243,7 +258,7 @@ class TestDDLCommandsPost(unittest.TestCase): def setUp(self) -> None: frappe.db.sql( f""" - CREATE TABLE "tab{self.test_table_name}" ("id" INT NULL,PRIMARY KEY ("id")) + CREATE TABLE "tab{self.test_table_name}" ("id" INT NULL, content text, PRIMARY KEY ("id")) """ ) @@ -268,7 +283,9 @@ class TestDDLCommandsPost(unittest.TestCase): self.test_table_name = new_table_name def test_describe(self) -> None: - self.assertEqual([("id",)], frappe.db.describe(self.test_table_name)) + self.assertEqual( + [("id",), ("content",)], frappe.db.describe(self.test_table_name) + ) def test_change_type(self) -> None: frappe.db.change_column_type(self.test_table_name, "id", "varchar(255)") @@ -286,3 +303,16 @@ class TestDDLCommandsPost(unittest.TestCase): ) self.assertGreater(len(check_change), 0) self.assertIn("character varying", check_change[0]) + + def test_add_index(self) -> None: + index_name = "test_index" + frappe.db.add_index(self.test_table_name, ["id", "content(50)"], index_name) + indexs_in_table = frappe.db.sql( + f""" + SELECT indexname + FROM pg_indexes + WHERE tablename = 'tab{self.test_table_name}' + AND indexname = '{index_name}' ; + """, + ) + self.assertEquals(len(indexs_in_table), 1) From a56b117ca409ccceb78e082a0ddbb60ee407f6f2 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 7 Sep 2021 19:23:32 +0530 Subject: [PATCH 29/76] test: Wait for filter save on clear filter --- cypress/support/commands.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 00415b487d..f3260bd6be 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -317,7 +317,11 @@ Cypress.Commands.add('add_filter', () => { }); Cypress.Commands.add('clear_filters', () => { - cy.get('.filter-section .filter-button').click(); + cy.intercept({ + method: 'POST', + url: 'api/method/frappe.model.utils.user_settings.save' + }).as('filter-saved'); + cy.get('.filter-section .filter-button').click({force: true}); cy.wait(300); cy.get('.filter-popover').should('exist'); cy.get('.filter-popover').find('.clear-filters').click(); @@ -325,6 +329,7 @@ Cypress.Commands.add('clear_filters', () => { cy.window().its('cur_list').then(cur_list => { cur_list && cur_list.filter_area && cur_list.filter_area.clear(); }); + cy.wait('@filter-saved'); }); Cypress.Commands.add('click_modal_primary_button', (btn_name) => { From 8e9a46fff9f230f00c6dfb29bcfda2ceeec2c38a Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 7 Sep 2021 19:57:57 +0530 Subject: [PATCH 30/76] test: Miscellaneous fixes to avoid flaky tests --- cypress/integration/api.js | 11 ++++++++--- .../datetime_field_form_validation.js | 17 +++++++---------- frappe/tests/ui_test_helpers.py | 11 +++++------ 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/cypress/integration/api.js b/cypress/integration/api.js index 7a5b1611b0..e8c39e6e25 100644 --- a/cypress/integration/api.js +++ b/cypress/integration/api.js @@ -31,8 +31,13 @@ context('API Resources', () => { }); it('Removes the Comments', () => { - cy.get_list('Comment').then(body => body.data.forEach(comment => { - cy.remove_doc('Comment', comment.name); - })); + cy.get_list('Comment').then(body => { + let comment_names = []; + body.data.map(comment => comment_names.push(comment.name)); + comment_names = [...new Set(comment_names)]; // remove duplicates + comment_names.forEach((comment_name) => { + cy.remove_doc('Comment', comment_name); + }); + }); }); }); diff --git a/cypress/integration/datetime_field_form_validation.js b/cypress/integration/datetime_field_form_validation.js index 66fdde6863..cb862152b4 100644 --- a/cypress/integration/datetime_field_form_validation.js +++ b/cypress/integration/datetime_field_form_validation.js @@ -2,18 +2,15 @@ context('Datetime Field Validation', () => { before(() => { cy.login(); cy.visit('/app/communication'); - cy.window().its('frappe').then(frappe => { - frappe.call("frappe.tests.ui_test_helpers.create_communication_records"); - }); }); - // validating datetime field value when value is set from backend and get validated on form load. it('datetime field form validation', () => { - cy.visit('/app/communication'); - cy.get('a[title="Test Form Communication 1"]').invoke('attr', 'data-name') - .then((name) => { - cy.visit(`/app/communication/${name}`); - cy.get('.indicator-pill').should('contain', 'Open').should('have.class', 'red'); - }); + // validating datetime field value when value is set from backend and get validated on form load. + cy.window().its('frappe').then(frappe => { + return frappe.xcall("frappe.tests.ui_test_helpers.create_communication_record"); + }).then(doc => { + cy.visit(`/app/communication/${doc.name}`); + cy.get('.indicator-pill').should('contain', 'Open').should('have.class', 'red'); + }); }); }); \ No newline at end of file diff --git a/frappe/tests/ui_test_helpers.py b/frappe/tests/ui_test_helpers.py index d8ad728136..9f6ad70a35 100644 --- a/frappe/tests/ui_test_helpers.py +++ b/frappe/tests/ui_test_helpers.py @@ -62,16 +62,15 @@ def create_todo_records(): }).insert() @frappe.whitelist() -def create_communication_records(): - if frappe.db.get_all('Communication', {'subject': 'Test Form Communication 1'}): - return - - frappe.get_doc({ +def create_communication_record(): + doc = frappe.get_doc({ "doctype": "Communication", "recipients": "test@gmail.com", "subject": "Test Form Communication 1", "communication_date": frappe.utils.now_datetime(), - }).insert() + }) + doc.insert() + return doc @frappe.whitelist() def setup_workflow(): From 5f9340fda29d74a943be3993cfe788a4c345dc35 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 7 Sep 2021 20:33:08 +0530 Subject: [PATCH 31/76] test: Set delay for fill_field --- cypress/support/commands.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index f3260bd6be..47c37a56a0 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -187,7 +187,7 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype = 'Data') => { if (fieldtype === 'Select') { cy.get('@input').select(value); } else { - cy.get('@input').type(value, {waitForAnimations: false, force: true}); + cy.get('@input').type(value, {waitForAnimations: false, force: true, delay: 100}); } return cy.get('@input'); }); From 3fb93b9b3dee5ab93d1edf2250a358283f3be3ed Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Tue, 7 Sep 2021 20:34:31 +0530 Subject: [PATCH 32/76] ci: Update threshold for codecov --- codecov.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index eb81252b61..41b22001a5 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,9 +1,13 @@ codecov: require_ci_to_pass: yes + +coverage: status: project: default: + target: auto threshold: 0.5% + comment: - layout: "diff, flags, files" + layout: "diff" require_changes: true From 4441eb7d903fc326f4e109be7497672ecace8de3 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 8 Sep 2021 11:45:15 +0530 Subject: [PATCH 33/76] test: Fix test_request_language_resolution_with_cookie --- frappe/tests/test_translate.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frappe/tests/test_translate.py b/frappe/tests/test_translate.py index 9620978c4f..949e4f9d77 100644 --- a/frappe/tests/test_translate.py +++ b/frappe/tests/test_translate.py @@ -63,11 +63,12 @@ class TestTranslate(unittest.TestCase): Case 2: frappe.form_dict._lang is not set, but preferred_language cookie is """ - with patch.object(frappe.translate, "get_preferred_language_cookie", return_value=second_lang): - set_request(method="POST", path="/", headers=[("Accept-Language", third_lang)]) + with patch.object(frappe.translate, "get_preferred_language_cookie", return_value='fr'): + set_request(method="POST", path="/", headers=[("Accept-Language", 'hr')]) return_val = get_language() - - self.assertNotIn(return_val, [second_lang, get_parent_language(second_lang)]) + # system default language + self.assertEqual(return_val, 'en') + self.assertNotIn(return_val, [second_lang, get_parent_language(second_lang)]) def test_guest_request_language_resolution_with_cookie(self): """Test for frappe.translate.get_language From 842f947301d9d691c77ff4203b783b124188e764 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 8 Sep 2021 11:49:22 +0530 Subject: [PATCH 34/76] test: Comment out a flaky test case - It is flaky possibly because of different timezone in CI --- .../datetime_field_form_validation.js | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/cypress/integration/datetime_field_form_validation.js b/cypress/integration/datetime_field_form_validation.js index cb862152b4..ef47a0fbf7 100644 --- a/cypress/integration/datetime_field_form_validation.js +++ b/cypress/integration/datetime_field_form_validation.js @@ -1,16 +1,19 @@ -context('Datetime Field Validation', () => { - before(() => { - cy.login(); - cy.visit('/app/communication'); - }); +// TODO: Enable this again +// currently this is flaky possibly because of different timezone in CI - it('datetime field form validation', () => { - // validating datetime field value when value is set from backend and get validated on form load. - cy.window().its('frappe').then(frappe => { - return frappe.xcall("frappe.tests.ui_test_helpers.create_communication_record"); - }).then(doc => { - cy.visit(`/app/communication/${doc.name}`); - cy.get('.indicator-pill').should('contain', 'Open').should('have.class', 'red'); - }); - }); -}); \ No newline at end of file +// context('Datetime Field Validation', () => { +// before(() => { +// cy.login(); +// cy.visit('/app/communication'); +// }); + +// it('datetime field form validation', () => { +// // validating datetime field value when value is set from backend and get validated on form load. +// cy.window().its('frappe').then(frappe => { +// return frappe.xcall("frappe.tests.ui_test_helpers.create_communication_record"); +// }).then(doc => { +// cy.visit(`/app/communication/${doc.name}`); +// cy.get('.indicator-pill').should('contain', 'Open').should('have.class', 'red'); +// }); +// }); +// }); \ No newline at end of file From 646ceb4c9e022d6ee28bd19e1d43d96d3a88e947 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 8 Sep 2021 12:31:05 +0530 Subject: [PATCH 35/76] fix: not checked cell should be empty and make check centered --- frappe/templates/print_formats/standard_macros.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/templates/print_formats/standard_macros.html b/frappe/templates/print_formats/standard_macros.html index ec60af1ce0..55c76a00c2 100644 --- a/frappe/templates/print_formats/standard_macros.html +++ b/frappe/templates/print_formats/standard_macros.html @@ -140,6 +140,8 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}" style="width: 12px; height: 12px; margin-top: 5px;"> + {% elif df.fieldtype=="Check" and not doc[df.fieldname] %} + {% elif df.fieldtype in ("Image", "Attach Image") and frappe.utils.is_image(doc[doc.meta.get_field(df.fieldname).options]) %} = 3 %}{{ "" }} {%- elif df.align -%}{{ "text-" + df.align }} - {%- elif df.fieldtype in ("Int", "Float", "Currency", "Check", "Percent") -%}{{ "text-right" }} + {%- elif df.fieldtype in ("Int", "Float", "Currency", "Percent") -%}{{ "text-right" }} + {%- elif df.fieldtype in ("Check") -%}{{ "text-center" }} {%- else -%}{{ "" }} {%- endif -%} {% endmacro %} From 61d067710ffd1819f0ed26548ea64d915a8cbc45 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 8 Sep 2021 13:25:19 +0530 Subject: [PATCH 36/76] feat: @frappe.write_only When would you want to use it? Typically if you want to be sure that "this" particular thing will be make WRITES to the database. If this were to be called via a function wrapped with @frappe.read_only. This will momentarily switch the connection from replica to primary --- frappe/__init__.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/frappe/__init__.py b/frappe/__init__.py index 7c6005a350..c9b2c2edb9 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -629,6 +629,31 @@ def read_only(): return wrapper_fn return innfn +def write_only(): + # if replica connection exists, we have to replace it momentarily with the primary connection + def innfn(fn): + def wrapper_fn(*args, **kwargs): + # switch to primary connection + primary_db = getattr(local, "primary_db", None) + replica_db = getattr(local, "replica_db", None) + in_read_only = getattr(local, "db", None) != primary_db + + if in_read_only and primary_db: + local.db = local.primary_db + + try: + retval = fn(*args, **get_newargs(fn, kwargs)) + except: + raise + finally: + # switch back to replica connection + if in_read_only and replica_db: + local.db = replica_db + + return retval + return wrapper_fn + return innfn + def only_for(roles, message=False): """Raise `frappe.PermissionError` if the user does not have any of the given **Roles**. From 70ac05025b274d70cd6f63e4ef165ac985c0b234 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 8 Sep 2021 13:44:00 +0530 Subject: [PATCH 37/76] fix: Make `make_access_log` write only Write to primary db if connection is currently set to replica --- frappe/__init__.py | 2 +- frappe/core/doctype/access_log/access_log.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index c9b2c2edb9..0d009b426f 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -633,11 +633,11 @@ def write_only(): # if replica connection exists, we have to replace it momentarily with the primary connection def innfn(fn): def wrapper_fn(*args, **kwargs): - # switch to primary connection primary_db = getattr(local, "primary_db", None) replica_db = getattr(local, "replica_db", None) in_read_only = getattr(local, "db", None) != primary_db + # switch to primary connection if in_read_only and primary_db: local.db = local.primary_db diff --git a/frappe/core/doctype/access_log/access_log.py b/frappe/core/doctype/access_log/access_log.py index 0f5776ce2f..d93da02d25 100644 --- a/frappe/core/doctype/access_log/access_log.py +++ b/frappe/core/doctype/access_log/access_log.py @@ -9,6 +9,7 @@ class AccessLog(Document): @frappe.whitelist() +@frappe.write_only() def make_access_log(doctype=None, document=None, method=None, file_type=None, report_name=None, filters=None, page=None, columns=None): From 309eaabcf96f35906e0a16a4ba35ccb3049e055b Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 8 Sep 2021 14:01:38 +0530 Subject: [PATCH 38/76] fix: Get rid of pointless except: raise --- frappe/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 0d009b426f..38904c68d0 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -618,8 +618,6 @@ def read_only(): try: retval = fn(*args, **get_newargs(fn, kwargs)) - except: - raise finally: if local and hasattr(local, 'primary_db'): local.db.close() @@ -643,8 +641,6 @@ def write_only(): try: retval = fn(*args, **get_newargs(fn, kwargs)) - except: - raise finally: # switch back to replica connection if in_read_only and replica_db: From 2b00bdb37f30c1208d5b536eae019f98a4ded7b6 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 8 Sep 2021 14:03:33 +0530 Subject: [PATCH 39/76] fix(Web Form): Add fields to row that haven't been added yet --- frappe/website/doctype/web_form/web_form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/doctype/web_form/web_form.js b/frappe/website/doctype/web_form/web_form.js index 4d38d11891..d69d21c64d 100644 --- a/frappe/website/doctype/web_form/web_form.js +++ b/frappe/website/doctype/web_form/web_form.js @@ -47,7 +47,7 @@ frappe.ui.form.on("Web Form", { frm.add_custom_button(__('Get Fields'), () => { let webform_fieldtypes = frappe.meta.get_field('Web Form Field', 'fieldtype').options.split('\n'); - let fieldnames = (frm.doc.fields || []).map(d => d.fieldname); + let fieldnames = (frm.doc.web_form_fields || []).map(d => d.fieldname); frappe.model.with_doctype(frm.doc.doc_type, () => { let meta = frappe.get_meta(frm.doc.doc_type); for (let field of meta.fields) { From 9eff8bee21de05ac23e92457353f005ccb2207d1 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Wed, 8 Sep 2021 15:02:56 +0530 Subject: [PATCH 40/76] test: tests for float control --- cypress/integration/control_float.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 cypress/integration/control_float.js diff --git a/cypress/integration/control_float.js b/cypress/integration/control_float.js new file mode 100644 index 0000000000..25b1d65523 --- /dev/null +++ b/cypress/integration/control_float.js @@ -0,0 +1,28 @@ +context('Control Float', () => { + before(() => { + cy.login(); + cy.visit('/app/website'); + cy.window().its('frappe').then(frappe => { + frappe.boot.sysdefaults.number_format = '#.###,##' + }); + }); + + function get_dialog_with_float() { + return cy.dialog({ + title: 'Float Check', + fields: [{ + 'fieldname': 'float_number', + 'fieldtype': 'Float', + 'Label': 'Float', + }] + }); + } + + it('check value changes', () => { + get_dialog_with_float().as('dialog'); + cy.get_field('float_number', 'Float').clear() + cy.fill_field('float_number', '36487,334', 'Float').blur(); + cy.get_field('float_number', 'Float').should('have.value', '36.487,334'); + + }); +}); From 8aea1254f9023d013fab81bc1f29ce2347a95a7c Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Wed, 8 Sep 2021 15:15:47 +0530 Subject: [PATCH 41/76] chore: add semi colon --- cypress/integration/control_float.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/control_float.js b/cypress/integration/control_float.js index 25b1d65523..45edff0346 100644 --- a/cypress/integration/control_float.js +++ b/cypress/integration/control_float.js @@ -3,7 +3,7 @@ context('Control Float', () => { cy.login(); cy.visit('/app/website'); cy.window().its('frappe').then(frappe => { - frappe.boot.sysdefaults.number_format = '#.###,##' + frappe.boot.sysdefaults.number_format = '#.###,##'; }); }); From 6ca2788cc5cc357fff8bfc870eecdfca2540889a Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 9 Sep 2021 11:48:20 +0530 Subject: [PATCH 42/76] fix: spacing issue --- frappe/contacts/doctype/address/address.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/contacts/doctype/address/address.py b/frappe/contacts/doctype/address/address.py index 755bc63064..86e26953e5 100644 --- a/frappe/contacts/doctype/address/address.py +++ b/frappe/contacts/doctype/address/address.py @@ -65,7 +65,7 @@ class Address(Document): def has_link(self, doctype, name): for link in self.links: - if link.link_doctype==doctype and link.link_name== name: + if link.link_doctype == doctype and link.link_name == name: return True def has_common_link(self, doc): From 35d563edb0ebbd7246957161e0239aedf9549020 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 9 Sep 2021 11:50:47 +0530 Subject: [PATCH 43/76] fix: contact.py spacing issue --- frappe/contacts/doctype/contact/contact.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index d1dd1f1010..412c781266 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -47,14 +47,14 @@ class Contact(Document): def get_link_for(self, link_doctype): '''Return the link name, if exists for the given link DocType''' for link in self.links: - if link.link_doctype==link_doctype: + if link.link_doctype == link_doctype: return link.link_name return None def has_link(self, doctype, name): for link in self.links: - if link.link_doctype==doctype and link.link_name== name: + if link.link_doctype == doctype and link.link_name == name: return True def has_common_link(self, doc): From 84562d5a05d21f9d95746c85850fa0535fef588f Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Thu, 9 Sep 2021 12:14:47 +0530 Subject: [PATCH 44/76] fix(UI): Incorrect text color on version document in dark mode (#14147) --- frappe/core/doctype/version/version.css | 21 ---------------- frappe/public/scss/desk/index.scss | 1 + frappe/public/scss/desk/version.scss | 33 +++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 21 deletions(-) delete mode 100644 frappe/core/doctype/version/version.css create mode 100644 frappe/public/scss/desk/version.scss diff --git a/frappe/core/doctype/version/version.css b/frappe/core/doctype/version/version.css deleted file mode 100644 index 769b352585..0000000000 --- a/frappe/core/doctype/version/version.css +++ /dev/null @@ -1,21 +0,0 @@ -.version-info { - overflow: auto; -} - -.version-info pre { - border: 0px; - margin: 0px; - background-color: inherit; -} - -.version-info .table { - background-color: inherit; -} - -.version-info .success { - background-color: #dff0d8 !important; -} - -.version-info .danger { - background-color: #f2dede !important; -} diff --git a/frappe/public/scss/desk/index.scss b/frappe/public/scss/desk/index.scss index c5b8271a36..1d1124bd58 100644 --- a/frappe/public/scss/desk/index.scss +++ b/frappe/public/scss/desk/index.scss @@ -47,3 +47,4 @@ @import "link_preview"; @import "../common/quill"; @import "plyr"; +@import "version"; diff --git a/frappe/public/scss/desk/version.scss b/frappe/public/scss/desk/version.scss new file mode 100644 index 0000000000..ddcf1f07a5 --- /dev/null +++ b/frappe/public/scss/desk/version.scss @@ -0,0 +1,33 @@ +.version-info { + overflow: auto; + + pre { + border: 0px; + margin: 0px; + background-color: inherit; + } + + .table { + background-color: inherit; + } + + .success { + background-color: var(--green-100) !important; + } + + .danger { + background-color: var(--red-100) !important; + } +} + +[data-theme="dark"] { + .version-info { + .danger, .success { + color: var(--gray-900); + + td { + color: var(--gray-900); + } + } + } +} \ No newline at end of file From 0e2d98a473fe7c6684a9df10c0be4a82ccf83203 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 9 Sep 2021 13:33:28 +0530 Subject: [PATCH 45/76] feat(minor): Show Processlist in System Console --- .../doctype/system_console/system_console.js | 41 +++++++++++++++++++ .../system_console/system_console.json | 31 ++++++++++++-- .../doctype/system_console/system_console.py | 7 +++- 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/frappe/desk/doctype/system_console/system_console.js b/frappe/desk/doctype/system_console/system_console.js index 48dd2ba108..2d097f01ad 100644 --- a/frappe/desk/doctype/system_console/system_console.js +++ b/frappe/desk/doctype/system_console/system_console.js @@ -20,5 +20,46 @@ frappe.ui.form.on('System Console', { $btn.text(__('Execute')); }); }); + }, + + show_processlist: function(frm) { + if (frm.doc.show_processlist) { + // keep refreshing every 5 seconds + frm.events.refresh_processlist(frm); + frm.processlist_interval = setInterval(() => frm.events.refresh_processlist(frm), 5000); + } else { + if (frm.processlist_interval) { + + // end it + clearInterval(frm.processlist_interval); + } + } + }, + + refresh_processlist: function(frm) { + let timestamp = new Date(); + frappe.call('frappe.desk.doctype.system_console.system_console.show_processlist').then(r => { + let rows = ''; + for (let row of r.message) { + rows += ` + ${row.Id} + ${row.Time} + ${row.State} + ${row.Info} + ${row.Progress} + ` + } + frm.get_field('processlist').html(` +

Requested on: ${timestamp}

+ + + + ${rows}`); + }); } }); diff --git a/frappe/desk/doctype/system_console/system_console.json b/frappe/desk/doctype/system_console/system_console.json index 14e36e6fd3..753e672cdc 100644 --- a/frappe/desk/doctype/system_console/system_console.json +++ b/frappe/desk/doctype/system_console/system_console.json @@ -17,9 +17,13 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "execute_section", "console", "commit", - "output" + "output", + "database_processes_section", + "show_processlist", + "processlist" ], "fields": [ { @@ -40,13 +44,34 @@ "fieldname": "commit", "fieldtype": "Check", "label": "Commit" + }, + { + "fieldname": "execute_section", + "fieldtype": "Section Break", + "label": "Execute" + }, + { + "fieldname": "database_processes_section", + "fieldtype": "Section Break", + "label": "Database Processes" + }, + { + "default": "0", + "fieldname": "show_processlist", + "fieldtype": "Check", + "label": "Show Processlist" + }, + { + "fieldname": "processlist", + "fieldtype": "HTML", + "label": "processlist" } ], "hide_toolbar": 1, "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-08-21 14:44:35.296877", + "modified": "2021-09-09 13:10:14.237113", "modified_by": "Administrator", "module": "Desk", "name": "System Console", @@ -65,4 +90,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file diff --git a/frappe/desk/doctype/system_console/system_console.py b/frappe/desk/doctype/system_console/system_console.py index f7f31cc3ba..843891bc78 100644 --- a/frappe/desk/doctype/system_console/system_console.py +++ b/frappe/desk/doctype/system_console/system_console.py @@ -33,4 +33,9 @@ class SystemConsole(Document): def execute_code(doc): console = frappe.get_doc(json.loads(doc)) console.run() - return console.as_dict() \ No newline at end of file + return console.as_dict() + +@frappe.whitelist() +def show_processlist(): + frappe.only_for('System Manager') + return frappe.db.sql('show processlist', as_dict=1) From 9dc8f84aedfe33f8e1de76f58b9089c89b19582d Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Thu, 9 Sep 2021 14:26:40 +0530 Subject: [PATCH 46/76] test: tests for control: float --- cypress/integration/control_float.js | 97 +++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 16 deletions(-) diff --git a/cypress/integration/control_float.js b/cypress/integration/control_float.js index 45edff0346..670d1fe73e 100644 --- a/cypress/integration/control_float.js +++ b/cypress/integration/control_float.js @@ -1,28 +1,93 @@ -context('Control Float', () => { +context("Control Float", () => { before(() => { cy.login(); - cy.visit('/app/website'); - cy.window().its('frappe').then(frappe => { - frappe.boot.sysdefaults.number_format = '#.###,##'; - }); + cy.visit("/app/website"); }); function get_dialog_with_float() { return cy.dialog({ - title: 'Float Check', - fields: [{ - 'fieldname': 'float_number', - 'fieldtype': 'Float', - 'Label': 'Float', - }] + title: "Float Check", + fields: [ + { + fieldname: "float_number", + fieldtype: "Float", + Label: "Float" + } + ] }); } - it('check value changes', () => { - get_dialog_with_float().as('dialog'); - cy.get_field('float_number', 'Float').clear() - cy.fill_field('float_number', '36487,334', 'Float').blur(); - cy.get_field('float_number', 'Float').should('have.value', '36.487,334'); + it("check value changes", () => { + get_dialog_with_float().as("dialog"); + let data = get_data(); + data.forEach(x => { + cy.window() + .its("frappe") + .then(frappe => { + frappe.boot.sysdefaults.number_format = x.number_format; + }); + x.values.forEach(d => { + cy.get_field("float_number", "Float").clear(); + cy.fill_field("float_number", d.input, "Float").blur(); + cy.get_field("float_number", "Float").should( + "have.value", + d.blur_expected + ); + + cy.get_field("float_number", "Float").focus(); + cy.get_field("float_number", "Float").blur(); + cy.get_field("float_number", "Float").focus(); + cy.get_field("float_number", "Float").should( + "have.value", + d.focus_expected + ); + }); + }); }); + + function get_data() { + return [ + { + number_format: "#.###,##", + values: [ + { + input: "364.87,334", + blur_expected: "36.487,334", + focus_expected: "36487.334" + }, + { + input: "36487,334", + blur_expected: "36.487,334", + focus_expected: "36487.334" + }, + { + input: "100", + blur_expected: "100,000", + focus_expected: "100" + } + ] + }, + { + number_format: "#,###.##", + values: [ + { + input: "364,87.334", + blur_expected: "36,487.334", + focus_expected: "36487.334" + }, + { + input: "36487.334", + blur_expected: "36,487.334", + focus_expected: "36487.334" + }, + { + input: "100", + blur_expected: "100.000", + focus_expected: "100" + } + ] + } + ]; + } }); From 21a4331be45ab32a2b6dd12c7d45b5f7d612c60b Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Thu, 9 Sep 2021 14:27:23 +0530 Subject: [PATCH 47/76] revert: removal of flt on focus event --- frappe/public/js/frappe/form/controls/int.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frappe/public/js/frappe/form/controls/int.js b/frappe/public/js/frappe/form/controls/int.js index ce2336bb82..12652bf86e 100644 --- a/frappe/public/js/frappe/form/controls/int.js +++ b/frappe/public/js/frappe/form/controls/int.js @@ -8,6 +8,17 @@ frappe.ui.form.ControlInt = class ControlInt extends frappe.ui.form.ControlData make_input () { var me = this; super.make_input(); + this.$input + // .addClass("text-right") + .on("focus", function () { + setTimeout(function () { + if (!document.activeElement) return; + document.activeElement.value + = me.validate(document.activeElement.value); + document.activeElement.select(); + }, 100); + return false; + }); } validate (value) { return this.parse(value); From f29138d6f853233913bf675655a743f5b642f5ba Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Thu, 9 Sep 2021 14:28:17 +0530 Subject: [PATCH 48/76] fix: convert back to number_format on change --- frappe/public/js/frappe/form/controls/float.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/frappe/public/js/frappe/form/controls/float.js b/frappe/public/js/frappe/form/controls/float.js index 89f8f23cc5..b81a988608 100644 --- a/frappe/public/js/frappe/form/controls/float.js +++ b/frappe/public/js/frappe/form/controls/float.js @@ -1,4 +1,17 @@ frappe.ui.form.ControlFloat = class ControlFloat extends frappe.ui.form.ControlInt { + + make_input() { + super.make_input() + const change_handler = e => { + if (this.change) this.change(e); + else { + let value = this.get_input_value(); + this.parse_validate_and_set_in_model(value, e); + } + }; + // convert to number format on focusout since focus converts it to flt. + this.$input.on("focusout", change_handler); + } parse(value) { value = this.eval_expression(value); return isNaN(parseFloat(value)) ? null : flt(value, this.get_precision()); @@ -25,3 +38,5 @@ frappe.ui.form.ControlFloat = class ControlFloat extends frappe.ui.form.ControlI }; frappe.ui.form.ControlPercent = frappe.ui.form.ControlFloat; + +34.4534,45 \ No newline at end of file From 4d0b0d35efa64a1692e075b71ae70c4969b81cda Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Thu, 9 Sep 2021 14:29:00 +0530 Subject: [PATCH 49/76] fix: make flt value idempotent --- frappe/public/js/frappe/utils/number_format.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/utils/number_format.js b/frappe/public/js/frappe/utils/number_format.js index 1c39f42ec5..5f60a5a6d8 100644 --- a/frappe/public/js/frappe/utils/number_format.js +++ b/frappe/public/js/frappe/utils/number_format.js @@ -8,7 +8,12 @@ if (!window.frappe) window.frappe = {}; function flt(v, decimals, number_format) { if (v == null || v == '') return 0; - if (typeof v !== "number") { + if (! (typeof v === "number" || String(parseFloat(v)) == v)){ + // cases in which this block should not run + // 1. 'v' is already a number + // 2. v is already parsed but in string form + // if (typeof v !== "number") { + v = v + ""; // strip currency symbol if exists @@ -25,6 +30,7 @@ function flt(v, decimals, number_format) { v = 0; } + v = parseFloat(v); if (decimals != null) return _round(v, decimals); return v; From 906c70b2456f681a6afaa7b56ea84695f364f824 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Thu, 9 Sep 2021 14:35:04 +0530 Subject: [PATCH 50/76] chore: sider --- frappe/public/js/frappe/form/controls/float.js | 4 +--- frappe/public/js/frappe/utils/number_format.js | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/float.js b/frappe/public/js/frappe/form/controls/float.js index b81a988608..e00f74238c 100644 --- a/frappe/public/js/frappe/form/controls/float.js +++ b/frappe/public/js/frappe/form/controls/float.js @@ -1,7 +1,7 @@ frappe.ui.form.ControlFloat = class ControlFloat extends frappe.ui.form.ControlInt { make_input() { - super.make_input() + super.make_input(); const change_handler = e => { if (this.change) this.change(e); else { @@ -38,5 +38,3 @@ frappe.ui.form.ControlFloat = class ControlFloat extends frappe.ui.form.ControlI }; frappe.ui.form.ControlPercent = frappe.ui.form.ControlFloat; - -34.4534,45 \ No newline at end of file diff --git a/frappe/public/js/frappe/utils/number_format.js b/frappe/public/js/frappe/utils/number_format.js index 5f60a5a6d8..32e3669caf 100644 --- a/frappe/public/js/frappe/utils/number_format.js +++ b/frappe/public/js/frappe/utils/number_format.js @@ -8,7 +8,7 @@ if (!window.frappe) window.frappe = {}; function flt(v, decimals, number_format) { if (v == null || v == '') return 0; - if (! (typeof v === "number" || String(parseFloat(v)) == v)){ + if (!(typeof v === "number" || String(parseFloat(v)) == v)) { // cases in which this block should not run // 1. 'v' is already a number // 2. v is already parsed but in string form From bc4b0d9348d431074fcca21da775b07171578b2a Mon Sep 17 00:00:00 2001 From: Akash Chaudhari <30781942+akazyti@users.noreply.github.com> Date: Fri, 10 Sep 2021 09:32:59 +0530 Subject: [PATCH 51/76] fix: Dont translate brand name (#14162) --- frappe/translations/af.csv | 2 +- frappe/translations/am.csv | 2 +- frappe/translations/da.csv | 2 +- frappe/translations/id.csv | 2 +- frappe/translations/is.csv | 2 +- frappe/translations/it.csv | 2 +- frappe/translations/ja.csv | 2 +- frappe/translations/km.csv | 2 +- frappe/translations/mr.csv | 2 +- frappe/translations/rw.csv | 2 +- frappe/translations/sk.csv | 2 +- frappe/translations/sq.csv | 2 +- frappe/translations/sv.csv | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/frappe/translations/af.csv b/frappe/translations/af.csv index b383fea767..fb52b4038d 100644 --- a/frappe/translations/af.csv +++ b/frappe/translations/af.csv @@ -3262,7 +3262,7 @@ Drop,drop, Drop Here,Drop hier, Drop files here,Laat lêers hier neer, Dynamic Template,Dinamiese sjabloon, -ERPNext Role,ERPVolgende rol, +ERPNext Role,ERPNext rol, Email / Notifications,E-pos / kennisgewings, Email Account setup please enter your password for: {0},Voer u wagwoord in vir die e-posrekening vir: {0}, Email Address whose Google Contacts are to be synced.,E-posadres waarvan die Google-kontakte gesinkroniseer moet word., diff --git a/frappe/translations/am.csv b/frappe/translations/am.csv index 52ed7c5f11..4dfa1bd8cd 100644 --- a/frappe/translations/am.csv +++ b/frappe/translations/am.csv @@ -3262,7 +3262,7 @@ Drop,ጣል ያድርጉ።, Drop Here,እዚህ ጣል ያድርጉ።, Drop files here,ፋይሎችን እዚህ ይጣሉ።, Dynamic Template,ተለዋዋጭ አብነት, -ERPNext Role,የኢአርኤክስ ቀጣይ ሚና።, +ERPNext Role,ERPNext ሚና, Email / Notifications,ኢሜይል / ማስታወቂያዎች, Email Account setup please enter your password for: {0},የኢሜል አካውንት ማዋቀር እባክዎን ለሚከተለው ይለፍ ቃልዎን ያስገቡ ፦ {0}, Email Address whose Google Contacts are to be synced.,የጉግል አድራሻዎች የሚመሳሰሉበት የኢሜል አድራሻ ፡፡, diff --git a/frappe/translations/da.csv b/frappe/translations/da.csv index 32f2ac9f6d..4516ed82ca 100644 --- a/frappe/translations/da.csv +++ b/frappe/translations/da.csv @@ -3262,7 +3262,7 @@ Drop,Dråbe, Drop Here,Drop Here, Drop files here,Slip filer her, Dynamic Template,Dynamisk skabelon, -ERPNext Role,ERPNæste rolle, +ERPNext Role,ERPNext rolle, Email / Notifications,E-mail / underretninger, Email Account setup please enter your password for: {0},Opsætning af e-mail-konto: indtast venligst din adgangskode til: {0}, Email Address whose Google Contacts are to be synced.,"E-mail-adresse, hvis Google-kontakter skal synkroniseres.", diff --git a/frappe/translations/id.csv b/frappe/translations/id.csv index 6207ecaa05..c4eae3e145 100644 --- a/frappe/translations/id.csv +++ b/frappe/translations/id.csv @@ -3262,7 +3262,7 @@ Drop,Penurunan, Drop Here,Jatuhkan Di Sini, Drop files here,Letakkan file di sini, Dynamic Template,Template Dinamis, -ERPNext Role,Peran ERPN, +ERPNext Role,Peran ERPNext, Email / Notifications,Notifikasi email, Email Account setup please enter your password for: {0},"Pengaturan Akun Email, harap masukkan kata sandi Anda untuk: {0}", Email Address whose Google Contacts are to be synced.,Alamat Email yang Kontak Google-nya harus disinkronkan., diff --git a/frappe/translations/is.csv b/frappe/translations/is.csv index c06065b120..fd0b552701 100644 --- a/frappe/translations/is.csv +++ b/frappe/translations/is.csv @@ -3262,7 +3262,7 @@ Drop,Dropi, Drop Here,Sendu hér, Drop files here,Sendu skrár hér, Dynamic Template,Dynamískt sniðmát, -ERPNext Role,ERPNæsta hlutverk, +ERPNext Role,ERPNext hlutverk, Email / Notifications,Netfang / tilkynningar, Email Account setup please enter your password for: {0},Uppsetning tölvupóstreikninga vinsamlegast sláðu inn lykilorðið þitt fyrir: {0}, Email Address whose Google Contacts are to be synced.,Netfang þar sem samstillt er Google tengiliði, diff --git a/frappe/translations/it.csv b/frappe/translations/it.csv index f61d467ebe..1d4c1af0f2 100644 --- a/frappe/translations/it.csv +++ b/frappe/translations/it.csv @@ -3262,7 +3262,7 @@ Drop,Far cadere, Drop Here,Drop Here, Drop files here,Trascina i file qui, Dynamic Template,Modello dinamico, -ERPNext Role,ERPSuccessivo ruolo, +ERPNext Role,ruolo ERPNext, Email / Notifications,Notifiche di posta elettronica, Email Account setup please enter your password for: {0},"Impostazione dell'account e-mail, inserire la password per: {0}", Email Address whose Google Contacts are to be synced.,Indirizzo email i cui contatti Google devono essere sincronizzati., diff --git a/frappe/translations/ja.csv b/frappe/translations/ja.csv index 441cfa44ef..35029e0058 100644 --- a/frappe/translations/ja.csv +++ b/frappe/translations/ja.csv @@ -3262,7 +3262,7 @@ Drop,ドロップ, Drop Here,ここにドロップ, Drop files here,ここにファイルをドロップします, Dynamic Template,動的テンプレート, -ERPNext Role,ERP次のロール, +ERPNext Role,ERPNext の役割, Email / Notifications,メール/通知, Email Account setup please enter your password for: {0},メールアカウントのセットアップ:{0}のパスワードを入力してください, Email Address whose Google Contacts are to be synced.,Googleの連絡先を同期するメールアドレス。, diff --git a/frappe/translations/km.csv b/frappe/translations/km.csv index 70a719d63d..5e8e1fc2d5 100644 --- a/frappe/translations/km.csv +++ b/frappe/translations/km.csv @@ -3262,7 +3262,7 @@ Drop,ទម្លាក់។, Drop Here,ទម្លាក់នៅទីនេះ។, Drop files here,ទម្លាក់ឯកសារនៅទីនេះ។, Dynamic Template,គំរូឌីណាមិក, -ERPNext Role,តួនាទី ERP បន្ទាប់។, +ERPNext Role,ERPNext តួនាទី។, Email / Notifications,អ៊ីមែល / ការជូនដំណឹង, Email Account setup please enter your password for: {0},រៀបចំគណនីអ៊ីមែលសូមបញ្ចូលពាក្យសម្ងាត់របស់អ្នកសម្រាប់៖ {0}, Email Address whose Google Contacts are to be synced.,អាសយដ្ឋានអ៊ីមែលដែលទំនាក់ទំនងរបស់ Google នឹងត្រូវធ្វើសមកាលកម្ម។, diff --git a/frappe/translations/mr.csv b/frappe/translations/mr.csv index 383b7a1c1e..6e98f94434 100644 --- a/frappe/translations/mr.csv +++ b/frappe/translations/mr.csv @@ -3262,7 +3262,7 @@ Drop,थेंब, Drop Here,येथे ड्रॉप करा, Drop files here,फायली येथे सोडा, Dynamic Template,डायनॅमिक टेम्पलेट, -ERPNext Role,ईआरपीनेक्स्ट रोल, +ERPNext Role,ERPNext रोल, Email / Notifications,ईमेल / सूचना, Email Account setup please enter your password for: {0},ईमेल खाते सेटअप यासाठी आपला संकेतशब्द प्रविष्ट करा: {0}, Email Address whose Google Contacts are to be synced.,ज्यांचे Google संपर्क समक्रमित केले जातील असा ईमेल पत्ता., diff --git a/frappe/translations/rw.csv b/frappe/translations/rw.csv index e0a1142a6a..002d22e72b 100644 --- a/frappe/translations/rw.csv +++ b/frappe/translations/rw.csv @@ -3262,7 +3262,7 @@ Drop,Tera, Drop Here,Tera Hano, Drop files here,Tera dosiye hano, Dynamic Template,Icyitegererezo, -ERPNext Role,Uruhare rwa ERPN, +ERPNext Role,uruhare rwa ERPNext, Email / Notifications,Imeri / Amatangazo, Email Account setup please enter your password for: {0},Imeri ya konte ya imeri nyamuneka andika ijambo ryibanga rya: {0}, Email Address whose Google Contacts are to be synced.,Aderesi ya imeri abo Google igomba guhuza., diff --git a/frappe/translations/sk.csv b/frappe/translations/sk.csv index 1d9442ffdb..7583eec96e 100644 --- a/frappe/translations/sk.csv +++ b/frappe/translations/sk.csv @@ -3262,7 +3262,7 @@ Drop,Pokles, Drop Here,Drop sem, Drop files here,Sem presuňte súbory, Dynamic Template,Dynamická šablóna, -ERPNext Role,ERPĎalšia rola, +ERPNext Role,ERPNext rola, Email / Notifications,E-mail / Upozornenia, Email Account setup please enter your password for: {0},"Nastavenie e-mailového účtu, zadajte heslo pre: {0}", Email Address whose Google Contacts are to be synced.,"E-mailová adresa, ktorej kontakty Google sa majú synchronizovať.", diff --git a/frappe/translations/sq.csv b/frappe/translations/sq.csv index 3d3fe564b3..b26b104850 100644 --- a/frappe/translations/sq.csv +++ b/frappe/translations/sq.csv @@ -3262,7 +3262,7 @@ Drop,Drop, Drop Here,Hidh këtu, Drop files here,Hidh skedarët këtu, Dynamic Template,Modeli Dinamik, -ERPNext Role,Roli ERPN, +ERPNext Role,Roli i ERPNext, Email / Notifications,Email / njoftime, Email Account setup please enter your password for: {0},Konfigurimi i llogarisë email ju lutemi shkruani fjalëkalimin tuaj për: {0, Email Address whose Google Contacts are to be synced.,Adresa e Email-it Kontaktet e të cilit Google duhet të sinkronizohen., diff --git a/frappe/translations/sv.csv b/frappe/translations/sv.csv index fe8458de35..8ab480d659 100644 --- a/frappe/translations/sv.csv +++ b/frappe/translations/sv.csv @@ -3262,7 +3262,7 @@ Drop,Släppa, Drop Here,Släpp här, Drop files here,Släpp filer här, Dynamic Template,Dynamisk mall, -ERPNext Role,ERPNästa roll, +ERPNext Role,ERPNext roll, Email / Notifications,E-post / aviseringar, Email Account setup please enter your password for: {0},"Ange e-postkonto, ange ditt lösenord för: {0}", Email Address whose Google Contacts are to be synced.,E-postadress vars Google-kontakter ska synkroniseras., From c280671a7aa2079ed8030a98bd2241568fd82c2b Mon Sep 17 00:00:00 2001 From: ahmadRagheb Date: Fri, 10 Sep 2021 07:17:02 +0300 Subject: [PATCH 52/76] fix(event streaming): Notify consumers on document cancel (#14160) * event streaming not working on cancel fix for https://github.com/frappe/frappe/issues/14141 event streaming not working on cancel * style: Remove extra line Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- frappe/hooks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/hooks.py b/frappe/hooks.py index f3d25d6bf4..3cfdebc12e 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -164,7 +164,8 @@ doc_events = { "after_rename": "frappe.desk.notifications.clear_doctype_notifications", "on_cancel": [ "frappe.desk.notifications.clear_doctype_notifications", - "frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions" + "frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions", + "frappe.event_streaming.doctype.event_update_log.event_update_log.notify_consumers" ], "on_trash": [ "frappe.desk.notifications.clear_doctype_notifications", From 22f143898bbd690cf17324b4f4eae9abbce9c214 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 10 Sep 2021 11:27:36 +0530 Subject: [PATCH 53/76] fix(minor): frappe.toast alist from frappe.show_alert, and added toasts to website.js, plus show full processlist --- frappe/desk/doctype/system_console/system_console.py | 2 +- frappe/public/js/frappe/ui/messages.js | 4 ++-- frappe/public/scss/website/index.scss | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/system_console/system_console.py b/frappe/desk/doctype/system_console/system_console.py index 843891bc78..8382dc8638 100644 --- a/frappe/desk/doctype/system_console/system_console.py +++ b/frappe/desk/doctype/system_console/system_console.py @@ -38,4 +38,4 @@ def execute_code(doc): @frappe.whitelist() def show_processlist(): frappe.only_for('System Manager') - return frappe.db.sql('show processlist', as_dict=1) + return frappe.db.sql('show full processlist', as_dict=1) diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js index 067fed233c..185d275ac3 100644 --- a/frappe/public/js/frappe/ui/messages.js +++ b/frappe/public/js/frappe/ui/messages.js @@ -140,7 +140,7 @@ frappe.msgprint = function(msg, title, is_minimizable) { return; } - if(data.alert) { + if(data.alert || data.toast) { frappe.show_alert(data); return; } @@ -361,7 +361,7 @@ frappe.hide_progress = function() { } // Floating Message -frappe.show_alert = function(message, seconds=7, actions={}) { +frappe.show_alert = frappe.toast = function(message, seconds=7, actions={}) { let indicator_icon_map = { 'orange': "solid-warning", 'yellow': "solid-warning", diff --git a/frappe/public/scss/website/index.scss b/frappe/public/scss/website/index.scss index 823ec9b08a..eb6e83e7fe 100644 --- a/frappe/public/scss/website/index.scss +++ b/frappe/public/scss/website/index.scss @@ -8,6 +8,7 @@ @import "../common/flex"; @import "../common/buttons"; @import "../common/modal"; +@import "../desk/toast"; @import "../common/indicator"; @import "../common/controls"; @import "../common/awesomeplete"; From eb4e94f52d6474e75ad1ba4c70c7b8c6e1f0b87b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 12 Sep 2021 18:10:14 +0530 Subject: [PATCH 54/76] fix: install mariadb client GitHub action's Ubuntu 20.04 image has mysql8 tooling which is not compatible with mariadb. --- .github/helper/install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index f6f0cad31a..93189d2b1f 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -17,6 +17,7 @@ if [ "$TYPE" == "server" ]; then fi if [ "$DB" == "mariadb" ];then + sudo apt install mariadb-client-10.3 mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"; mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"; @@ -58,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 \ No newline at end of file +bench build --app frappe From ae5cf9c32c5acfcf90f990d40757b8440cd50799 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Sun, 12 Sep 2021 19:09:05 +0530 Subject: [PATCH 55/76] chore: change `missing_in_backup` function docstring --- frappe/tests/test_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py index 564ddafe54..ee184843ad 100644 --- a/frappe/tests/test_commands.py +++ b/frappe/tests/test_commands.py @@ -64,7 +64,7 @@ def clean(value): def missing_in_backup(doctypes, file): - """Checks if the list of doctypes exist in the database.sql.gz file supplied + """Returns list of missing doctypes in the backup. Args: doctypes (list): List of DocTypes to be checked From c37ec8fdd992d386761a35ad40aa0518574f56db Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 16 Feb 2021 20:34:50 +0530 Subject: [PATCH 56/76] refactor: get_fields of multi select dialog --- .../js/frappe/form/multi_select_dialog.js | 121 +++++++++++------- 1 file changed, 73 insertions(+), 48 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index dd96b57fb5..e8306a3baf 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -2,66 +2,74 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { constructor(opts) { /* Options: doctype, target, setters, get_query, action, add_filters_group, data_fields, primary_action_label */ Object.assign(this, opts); - var me = this; - if (this.doctype != "[Select]") { - frappe.model.with_doctype(this.doctype, function () { - me.make(); - }); + this.for_select = this.doctype == "[Select]"; + if (!this.for_select) { + frappe.model.with_doctype(this.doctype, () => this.init()); } else { - this.make(); + this.init(); } } - make() { - let me = this; + init() { this.page_length = 20; this.start = 0; - let fields = this.get_primary_filters(); + this.fields = this.get_fields(); - // Make results area - fields = fields.concat([ - { fieldtype: "HTML", fieldname: "results_area" }, + this.make(); + } + + get_fields() { + const primary_fields = this.get_primary_filters(); + const result_fields = this.get_result_fields(); + const data_fields = this.get_data_fields(); + + return [...primary_fields, ...result_fields, ...data_fields]; + } + + get_result_fields() { + const show_next_page = () => { this.start += 20; this.get_results(); } + return [ { - fieldtype: "Button", fieldname: "more_btn", label: __("More"), - click: () => { - this.start += 20; - this.get_results(); - } + fieldtype: "HTML", fieldname: "results_area" + }, + { + fieldtype: "Button", fieldname: "more_btn", + label: __("More"), click: show_next_page } - ]); + ]; + } - // Custom Data Fields - if (this.data_fields) { - fields.push({ fieldtype: "Section Break" }); - fields = fields.concat(this.data_fields); + get_data_fields() { + if (this.data_fields && this.data_fields.length) { + // Custom Data Fields + return [ + { fieldtype: "Section Break" }, + ...this.data_fields + ]; + } else { + return []; } + } + + make() { let doctype_plural = this.doctype.plural(); + let title = __("Select {0}", [this.for_select ? __("value") : __(doctype_plural)]) this.dialog = new frappe.ui.Dialog({ - title: __("Select {0}", [(this.doctype == '[Select]') ? __("value") : __(doctype_plural)]), - fields: fields, + title: title, + fields: this.fields, primary_action_label: this.primary_action_label || __("Get Items"), - secondary_action_label: __("Make {0}", [__(me.doctype)]), - primary_action: function () { - let filters_data = me.get_custom_filters(); - me.action(me.get_checked_values(), cur_dialog.get_values(), me.args, filters_data); + secondary_action_label: __("Make {0}", [__(this.doctype)]), + primary_action: () => { + let filters_data = this.get_custom_filters(); + this.action(this.get_checked_values(), cur_dialog.get_values(), this.args, filters_data); }, - secondary_action: function (e) { + secondary_action: (e) => { // If user wants to close the modal if (e) { - frappe.route_options = {}; - if (Array.isArray(me.setters)) { - for (let df of me.setters) { - frappe.route_options[df.fieldname] = me.dialog.fields_dict[df.fieldname].get_value() || undefined; - } - } else { - Object.keys(me.setters).forEach(function (setter) { - frappe.route_options[setter] = me.dialog.fields_dict[setter].get_value() || undefined; - }); - } - - frappe.new_doc(me.doctype, true); + this.set_route_options(); + frappe.new_doc(this.doctype, true); } } }); @@ -70,18 +78,35 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { this.make_filter_area(); } + this.args = {}; + + this.setup_results(); + this.bind_events(); + this.get_results(); + this.dialog.show(); + } + + set_route_options() { + // set route options to get pre-filled form fields + frappe.route_options = {}; + if (Array.isArray(this.setters)) { + for (let df of this.setters) { + frappe.route_options[df.fieldname] = this.dialog.fields_dict[df.fieldname].get_value() || undefined; + } + } else { + Object.keys(this.setters).forEach(setter => { + frappe.route_options[setter] = this.dialog.fields_dict[setter].get_value() || undefined; + }); + } + } + + setup_results() { this.$parent = $(this.dialog.body); this.$wrapper = this.dialog.fields_dict.results_area.$wrapper.append(`
`); this.$results = this.$wrapper.find('.results'); this.$results.append(this.make_list_row()); - - this.args = {}; - - this.bind_events(); - this.get_results(); - this.dialog.show(); } get_primary_filters() { @@ -94,7 +119,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { columns[0] = [ { fieldtype: "Data", - label: __("Search"), + label: __("Name"), fieldname: "search_term" } ]; From 22d7aca5765012c3427984188f9d96e5764b3981 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 22 Feb 2021 14:28:28 +0530 Subject: [PATCH 57/76] feat: child selection in multi select dialog --- .../js/frappe/form/multi_select_dialog.js | 169 ++++++++++++++++-- frappe/public/js/frappe/ui/dialog.js | 2 +- 2 files changed, 159 insertions(+), 12 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index e8306a3baf..c9b85ba9b4 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -22,12 +22,13 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { const primary_fields = this.get_primary_filters(); const result_fields = this.get_result_fields(); const data_fields = this.get_data_fields(); + const child_selection_fields = this.get_child_selection_fields(); - return [...primary_fields, ...result_fields, ...data_fields]; + return [...primary_fields, ...result_fields, ...data_fields, ...child_selection_fields]; } get_result_fields() { - const show_next_page = () => { this.start += 20; this.get_results(); } + const show_next_page = () => { this.start += 20; this.get_results(); }; return [ { fieldtype: "HTML", fieldname: "results_area" @@ -51,6 +52,13 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { } } + get_child_selection_fields() { + const fields = []; + if (this.child_selection_mode && this.selectable_child) { + fields.push({ fieldtype: "HTML", fieldname: "child_selection_area" }); + } + return fields + } make() { let doctype_plural = this.doctype.plural(); @@ -63,15 +71,16 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { secondary_action_label: __("Make {0}", [__(this.doctype)]), primary_action: () => { let filters_data = this.get_custom_filters(); - this.action(this.get_checked_values(), cur_dialog.get_values(), this.args, filters_data); + const data_values = cur_dialog.get_values(); // to pass values of data fields + const filtered_children = this.get_checked_child_values(); + this.action(this.get_checked_values(), { + ...this.args, + ...data_values, + ...filters_data, + filtered_children + }); }, - secondary_action: (e) => { - // If user wants to close the modal - if (e) { - this.set_route_options(); - frappe.new_doc(this.doctype, true); - } - } + secondary_action: this.make_new_document.bind(this) }); if (this.add_filters_group) { @@ -81,11 +90,20 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { this.args = {}; this.setup_results(); + this.setup_child_selection(); this.bind_events(); this.get_results(); this.dialog.show(); } + make_new_document(e) { + // If user wants to close the modal + if (e) { + this.set_route_options(); + frappe.new_doc(this.doctype, true); + } + } + set_route_options() { // set route options to get pre-filled form fields frappe.route_options = {}; @@ -109,6 +127,111 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { this.$results.append(this.make_list_row()); } + setup_child_selection() { + if (!this.child_selection_mode) return; + + this.$child_wrapper = this.dialog.fields_dict.child_selection_area.$wrapper; + const grid_template = ` +
+
+
+
+
+ Grid Empty State + ${__("No Data")} +
+
+
`; + + this.$child_wrapper.addClass('hidden'); + this.$child_wrapper.append(grid_template); + const header = this.get_grid_row(0, ['name', ...this.child_cols], true); + this.$child_wrapper.find('.grid-heading-row').append(header); + } + + get_grid_row(idx, cols, for_header) { + const $cols = cols.slice(1).map(col => { + return ` +
+
${frappe.unscrub(col)}
+
`; + }).join(''); + + return ` +
+
+
+ + +
+ ${ $cols } +
+
`; + }; + + reset_child_selection() { + this.$child_wrapper.find('.grid-empty').removeClass('hidden'); + this.$child_wrapper.addClass('hidden'); + + this.dialog.set_secondary_action_label(__("Make {0}", [__(this.doctype)])); + this.dialog.set_secondary_action(this.make_new_document.bind(this)); + } + + refresh_child_selection() { + let checked_values = this.get_checked_values(); + if (!checked_values.length) { + this.reset_child_selection(); + return; + } + + const fetch_child_items = () => { + frappe.call({ + method: "frappe.client.get_list", + args: { + doctype: child_doctype, + filters: [ + ["parent", "in", checked_values] + ], + fields: ['name', ...this.child_cols], + parent: this.doctype + } + }).then((r) => { + this.child_results = r.message; + if(!r.exc) { + this.$child_wrapper.find('.rows').html('').append( + r.message.map((d, idx) => this.get_grid_row(idx, Object.values(d), false)).join('') + ); + this.$child_wrapper.find('.grid-empty').addClass('hidden'); + this.$child_wrapper.removeClass('hidden'); + } else { + frappe.msgprint(__("Error occured while fetching data. Please try again.")); + } + }); + } + + let child_fieldname = undefined; + const child_doctype = this.selectable_child; + frappe.get_meta(this.doctype).fields.some(d => { + if(d.options === child_doctype) { + child_fieldname = d.fieldname; + return true; + } + }); + + if (child_fieldname) { + // refresh secondary option + this.dialog.set_secondary_action_label(__("Fetch {0}", [child_doctype.plural()])); + this.dialog.set_secondary_action(fetch_child_items.bind(this)); + } else { + // child doctype is not linked to parent doctype + } + + } + get_primary_filters() { let fields = []; @@ -191,7 +314,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { }); }, {}); } else { - return []; + return {}; } } @@ -202,6 +325,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { if (!$(e.target).is(':checkbox') && !$(e.target).is('a')) { $(this).find(':checkbox').trigger('click'); } + if (me.child_selection_mode) me.refresh_child_selection(); }); this.$results.on('click', '.list-item--head :checkbox', (e) => { @@ -209,6 +333,13 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { .prop("checked", ($(e.target).is(':checked'))); }); + if (this.child_selection_mode) { + this.$child_wrapper.on('click', '.grid-heading-row :checkbox', (e) => { + this.$child_wrapper.find('.grid-row-check') + .prop("checked", ($(e.target).is(':checked'))); + }); + } + this.$parent.find('.input-with-feedback').on('change', () => { frappe.flags.auto_scroll = false; this.get_results(); @@ -225,6 +356,22 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { }); } + get_checked_child_values() { + return this.$child_wrapper.find('.rows > .grid-row').map(function () { + const checkedbox = $(this).find('.grid-row-check:checkbox:checked'); + if (checkedbox.length > 0) { + return checkedbox.attr('data-item-name'); + } + }).get(); + } + + get_checked_child_items() { + if(!this.child_results) return; + + let checked_values = this.get_checked_child_values(); + return this.child_results.filter(res => checked_values.includes(res.name)); + } + get_checked_values() { // Return name of checked value. return this.$results.find('.list-item-container').map(function () { diff --git a/frappe/public/js/frappe/ui/dialog.js b/frappe/public/js/frappe/ui/dialog.js index 00336a2137..58175381cf 100644 --- a/frappe/public/js/frappe/ui/dialog.js +++ b/frappe/public/js/frappe/ui/dialog.js @@ -153,7 +153,7 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup { set_secondary_action(click) { this.footer.removeClass('hide'); - this.get_secondary_btn().removeClass('hide').on('click', click); + this.get_secondary_btn().removeClass('hide').off('click').on('click', click); } set_secondary_action_label(label) { From e9926c92a82967b018cc1b772916ee028958fde6 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 23 Feb 2021 14:00:37 +0530 Subject: [PATCH 58/76] feat: use datatable for filtering child items --- .../js/frappe/form/multi_select_dialog.js | 199 ++++++++---------- 1 file changed, 85 insertions(+), 114 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index c9b85ba9b4..b78edce6ce 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -54,7 +54,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { get_child_selection_fields() { const fields = []; - if (this.child_selection_mode && this.selectable_child) { + if (this.child_selection_mode && this.child_doctype) { fields.push({ fieldtype: "HTML", fieldname: "child_selection_area" }); } return fields @@ -72,7 +72,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { primary_action: () => { let filters_data = this.get_custom_filters(); const data_values = cur_dialog.get_values(); // to pass values of data fields - const filtered_children = this.get_checked_child_values(); + const filtered_children = this.get_checked_child_names(); this.action(this.get_checked_values(), { ...this.args, ...data_values, @@ -90,7 +90,6 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { this.args = {}; this.setup_results(); - this.setup_child_selection(); this.bind_events(); this.get_results(); this.dialog.show(); @@ -127,109 +126,90 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { this.$results.append(this.make_list_row()); } - setup_child_selection() { - if (!this.child_selection_mode) return; - - this.$child_wrapper = this.dialog.fields_dict.child_selection_area.$wrapper; - const grid_template = ` -
-
-
-
-
- Grid Empty State - ${__("No Data")} -
-
-
`; - - this.$child_wrapper.addClass('hidden'); - this.$child_wrapper.append(grid_template); - const header = this.get_grid_row(0, ['name', ...this.child_cols], true); - this.$child_wrapper.find('.grid-heading-row').append(header); + get_child_datatable_columns() { + return ['name', 'parent', ...this.child_columns].map(d => ({ name: frappe.unscrub(d), editable: false })); } - get_grid_row(idx, cols, for_header) { - const $cols = cols.slice(1).map(col => { - return ` -
-
${frappe.unscrub(col)}
-
`; - }).join(''); - - return ` -
-
-
- - -
- ${ $cols } -
-
`; - }; - - reset_child_selection() { - this.$child_wrapper.find('.grid-empty').removeClass('hidden'); - this.$child_wrapper.addClass('hidden'); - - this.dialog.set_secondary_action_label(__("Make {0}", [__(this.doctype)])); - this.dialog.set_secondary_action(this.make_new_document.bind(this)); + toggle_secondary_action(label, action) { + this.dialog.set_secondary_action_label(label); + this.dialog.set_secondary_action(action.bind(this)); } - refresh_child_selection() { - let checked_values = this.get_checked_values(); - if (!checked_values.length) { - this.reset_child_selection(); - return; - } + show_parent_selector() { + const label = __("Fetch {0}", [this.child_doctype.plural()]); + this.toggle_secondary_action(label, this.fetch_child_items); - const fetch_child_items = () => { - frappe.call({ - method: "frappe.client.get_list", - args: { - doctype: child_doctype, - filters: [ - ["parent", "in", checked_values] - ], - fields: ['name', ...this.child_cols], - parent: this.doctype - } - }).then((r) => { - this.child_results = r.message; - if(!r.exc) { - this.$child_wrapper.find('.rows').html('').append( - r.message.map((d, idx) => this.get_grid_row(idx, Object.values(d), false)).join('') - ); - this.$child_wrapper.find('.grid-empty').addClass('hidden'); - this.$child_wrapper.removeClass('hidden'); - } else { - frappe.msgprint(__("Error occured while fetching data. Please try again.")); - } - }); - } + this.$wrapper.removeClass('hidden'); + this.$child_wrapper.addClass('hidden'); + } - let child_fieldname = undefined; - const child_doctype = this.selectable_child; - frappe.get_meta(this.doctype).fields.some(d => { - if(d.options === child_doctype) { - child_fieldname = d.fieldname; - return true; + get_child_result() { + const selected_parent = this.get_checked_values(); + return frappe.call({ + method: "frappe.client.get_list", + args: { + doctype: this.child_doctype, + filters: [ + ["parent", "in", selected_parent] + ], + fields: ['name', 'parent', ...this.child_columns], + parent: this.doctype } }); + } - if (child_fieldname) { - // refresh secondary option - this.dialog.set_secondary_action_label(__("Fetch {0}", [child_doctype.plural()])); - this.dialog.set_secondary_action(fetch_child_items.bind(this)); + async fetch_child_items() { + const label = __("Select {0}", [this.doctype.plural()]); + this.toggle_secondary_action(label, this.show_parent_selector); + + this.get_child_result().then(r => { + this.child_results = r.message || []; + this.show_child_datatable(); + + this.$wrapper.addClass('hidden'); + this.$child_wrapper.removeClass('hidden'); + }); + } + + show_child_datatable() { + if (!this.child_datatable) { + this.setup_child_datatable(); } else { - // child doctype is not linked to parent doctype + this.child_datatable.refresh(this.child_results.map(d => Object.values(d))); + this.child_datatable.rowmanager.checkMap = []; } + } + setup_child_datatable() { + const header_columns = this.get_child_datatable_columns(); + this.$child_wrapper = this.dialog.fields_dict.child_selection_area.$wrapper; + this.$child_wrapper.addClass('mt-3'); + + this.child_datatable = new frappe.DataTable(this.$child_wrapper.get(0), { + columns: header_columns, + data: this.child_results.map(d => Object.values(d)), + layout: 'fluid', + inlineFilters: true, + serialNoColumn: false, + checkboxColumn: true, + cellHeight: 35, + noDataMessage: __('No Data'), + disableReorderColumn: true + }); + } + + show_fetch_child_items() { + const selected_parent = this.get_checked_values(); + + if (selected_parent.length == 0) { + // update secondary option and link default secondary action + const label = __("Make {0}", [this.doctype.plural()]); + this.toggle_secondary_action(label, this.make_new_document); + } else { + // update secondary option and link new secondary action + const label = __("Fetch {0}", [this.child_doctype.plural()]); + this.toggle_secondary_action(label, this.fetch_child_items); + } } get_primary_filters() { @@ -325,7 +305,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { if (!$(e.target).is(':checkbox') && !$(e.target).is('a')) { $(this).find(':checkbox').trigger('click'); } - if (me.child_selection_mode) me.refresh_child_selection(); + if (me.child_selection_mode) me.show_fetch_child_items(); }); this.$results.on('click', '.list-item--head :checkbox', (e) => { @@ -333,13 +313,6 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { .prop("checked", ($(e.target).is(':checked'))); }); - if (this.child_selection_mode) { - this.$child_wrapper.on('click', '.grid-heading-row :checkbox', (e) => { - this.$child_wrapper.find('.grid-row-check') - .prop("checked", ($(e.target).is(':checked'))); - }); - } - this.$parent.find('.input-with-feedback').on('change', () => { frappe.flags.auto_scroll = false; this.get_results(); @@ -356,20 +329,18 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { }); } - get_checked_child_values() { - return this.$child_wrapper.find('.rows > .grid-row').map(function () { - const checkedbox = $(this).find('.grid-row-check:checkbox:checked'); - if (checkedbox.length > 0) { - return checkedbox.attr('data-item-name'); + get_checked_child_names() { + if (!this.child_datatable.datamanager.rows.length) return []; + + let checked_names = this.child_datatable.rowmanager.checkMap.reduce((checked_names, checked, index) => { + if (checked == 1) { + const child_row_name = this.child_datatable.datamanager.rows[index][1].content; + checked_names.push(child_row_name); } - }).get(); - } + return checked_names; + }, []); - get_checked_child_items() { - if(!this.child_results) return; - - let checked_values = this.get_checked_child_values(); - return this.child_results.filter(res => checked_values.includes(res.name)); + return checked_names; } get_checked_values() { From 0da9d55f01ae1588354231ad5523a83f6953cfd5 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 23 Feb 2021 15:01:35 +0530 Subject: [PATCH 59/76] fix: datatable rendering --- frappe/public/js/frappe/form/multi_select_dialog.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index b78edce6ce..37bb950c6c 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -11,6 +11,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { } init() { + window.test = this; this.page_length = 20; this.start = 0; this.fields = this.get_fields(); @@ -119,7 +120,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { setup_results() { this.$parent = $(this.dialog.body); - this.$wrapper = this.dialog.fields_dict.results_area.$wrapper.append(`
`); this.$results = this.$wrapper.find('.results'); @@ -175,8 +176,10 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { if (!this.child_datatable) { this.setup_child_datatable(); } else { - this.child_datatable.refresh(this.child_results.map(d => Object.values(d))); - this.child_datatable.rowmanager.checkMap = []; + setTimeout(() => { + this.child_datatable.rowmanager.checkMap = []; + this.child_datatable.refresh(this.child_results.map(d => Object.values(d))); + }, 500); } } From 48f1abc86b1e2fec80fc1f9f29d57a802ee36c4a Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 23 Feb 2021 15:20:31 +0530 Subject: [PATCH 60/76] chore: remove name column --- .../js/frappe/form/multi_select_dialog.js | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index 37bb950c6c..a18f0044b7 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -11,7 +11,6 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { } init() { - window.test = this; this.page_length = 20; this.start = 0; this.fields = this.get_fields(); @@ -127,10 +126,6 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { this.$results.append(this.make_list_row()); } - get_child_datatable_columns() { - return ['name', 'parent', ...this.child_columns].map(d => ({ name: frappe.unscrub(d), editable: false })); - } - toggle_secondary_action(label, action) { this.dialog.set_secondary_action_label(label); this.dialog.set_secondary_action(action.bind(this)); @@ -165,32 +160,42 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { this.get_child_result().then(r => { this.child_results = r.message || []; - this.show_child_datatable(); + this.render_child_datatable(); this.$wrapper.addClass('hidden'); this.$child_wrapper.removeClass('hidden'); }); } - show_child_datatable() { + render_child_datatable() { if (!this.child_datatable) { this.setup_child_datatable(); } else { setTimeout(() => { this.child_datatable.rowmanager.checkMap = []; - this.child_datatable.refresh(this.child_results.map(d => Object.values(d))); + this.child_datatable.refresh(this.get_child_datatable_rows()); }, 500); } } + get_child_datatable_columns() { + const parent = this.doctype; + return [parent, ...this.child_columns].map(d => ({ name: frappe.unscrub(d), editable: false })); + } + + get_child_datatable_rows() { + return this.child_results.map(d => Object.values(d).slice(1)); // slice name field + } + setup_child_datatable() { const header_columns = this.get_child_datatable_columns(); + const rows = this.get_child_datatable_rows(); this.$child_wrapper = this.dialog.fields_dict.child_selection_area.$wrapper; this.$child_wrapper.addClass('mt-3'); this.child_datatable = new frappe.DataTable(this.$child_wrapper.get(0), { columns: header_columns, - data: this.child_results.map(d => Object.values(d)), + data: rows, layout: 'fluid', inlineFilters: true, serialNoColumn: false, @@ -337,7 +342,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { let checked_names = this.child_datatable.rowmanager.checkMap.reduce((checked_names, checked, index) => { if (checked == 1) { - const child_row_name = this.child_datatable.datamanager.rows[index][1].content; + const child_row_name = this.child_results[index].name; checked_names.push(child_row_name); } return checked_names; From 1ecb0b63d38a64e080dcf9effd18775acbe64e49 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 23 Feb 2021 15:44:19 +0530 Subject: [PATCH 61/76] fix: datatable height --- frappe/public/js/frappe/form/multi_select_dialog.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index a18f0044b7..17bfdc6546 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -204,6 +204,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { noDataMessage: __('No Data'), disableReorderColumn: true }); + this.$child_wrapper.find('.dt-scrollable').css('height', '300px !immportant'); } show_fetch_child_items() { @@ -319,6 +320,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { this.$results.on('click', '.list-item--head :checkbox', (e) => { this.$results.find('.list-item-container .list-row-check') .prop("checked", ($(e.target).is(':checked'))); + if (me.child_selection_mode) me.show_fetch_child_items(); }); this.$parent.find('.input-with-feedback').on('change', () => { From 290150df31d5e90af99854d7c9a44f0eb20339a7 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 24 Feb 2021 13:19:57 +0530 Subject: [PATCH 62/76] fix: datatable height --- frappe/public/js/frappe/form/multi_select_dialog.js | 2 +- 1 file changed, 1 insertion(+), 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 17bfdc6546..1e91c8af0d 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -204,7 +204,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { noDataMessage: __('No Data'), disableReorderColumn: true }); - this.$child_wrapper.find('.dt-scrollable').css('height', '300px !immportant'); + this.$child_wrapper.find('.dt-scrollable').css('height', '300px'); } show_fetch_child_items() { From 52bf3f6bbae6e3b0b079238eb10806869746fe8e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 26 Feb 2021 15:51:49 +0530 Subject: [PATCH 63/76] fix: sider issues --- frappe/public/js/frappe/form/multi_select_dialog.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index 1e91c8af0d..186bbbd3c1 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -28,14 +28,17 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { } get_result_fields() { - const show_next_page = () => { this.start += 20; this.get_results(); }; + const show_next_page = () => { + this.start += 20; + this.get_results(); + }; return [ { fieldtype: "HTML", fieldname: "results_area" }, { fieldtype: "Button", fieldname: "more_btn", - label: __("More"), click: show_next_page + label: __("More"), click: show_next_page.bind(this) } ]; } @@ -57,12 +60,12 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { if (this.child_selection_mode && this.child_doctype) { fields.push({ fieldtype: "HTML", fieldname: "child_selection_area" }); } - return fields + return fields; } make() { let doctype_plural = this.doctype.plural(); - let title = __("Select {0}", [this.for_select ? __("value") : __(doctype_plural)]) + let title = __("Select {0}", [this.for_select ? __("value") : __(doctype_plural)]); this.dialog = new frappe.ui.Dialog({ title: title, From 75c20efd02ad82c18193672de113ae675345942f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 1 Mar 2021 14:15:32 +0530 Subject: [PATCH 64/76] chore: hide apply filter button --- frappe/public/js/frappe/form/multi_select_dialog.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index 186bbbd3c1..58da0f2ccf 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -296,6 +296,9 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { this.get_results(); } }); + // 'Apply Filter' breaks since the filers are not in a popover + // Hence keeping it hidden + this.filter_group.wrapper.find('.apply-filters').hide(); } get_custom_filters() { @@ -432,6 +435,8 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { me.$results.append(me.make_list_row(result)); }); + this.$results.find(".list-item--head").css("z-index", 0); + if (frappe.flags.auto_scroll) { this.$results.animate({ scrollTop: me.$results.prop('scrollHeight') }, 500); } From 6dbe0ef5cf8d2681a5c5a4da6d688e41b8b15d30 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 10 May 2021 16:27:39 +0530 Subject: [PATCH 65/76] refactor: replace child_selection_toggle button with checkbox --- .../js/frappe/form/multi_select_dialog.js | 69 ++++++++----------- 1 file changed, 27 insertions(+), 42 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index 58da0f2ccf..760aa3123b 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -129,27 +129,14 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { this.$results.append(this.make_list_row()); } - toggle_secondary_action(label, action) { - this.dialog.set_secondary_action_label(label); - this.dialog.set_secondary_action(action.bind(this)); - } - - show_parent_selector() { - const label = __("Fetch {0}", [this.child_doctype.plural()]); - this.toggle_secondary_action(label, this.fetch_child_items); - - this.$wrapper.removeClass('hidden'); - this.$child_wrapper.addClass('hidden'); - } - get_child_result() { - const selected_parent = this.get_checked_values(); + const parents = this.results.map(res => res.name); return frappe.call({ method: "frappe.client.get_list", args: { doctype: this.child_doctype, filters: [ - ["parent", "in", selected_parent] + ["parent", "in", parents] ], fields: ['name', 'parent', ...this.child_columns], parent: this.doctype @@ -157,17 +144,21 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { }); } - async fetch_child_items() { - const label = __("Select {0}", [this.doctype.plural()]); - this.toggle_secondary_action(label, this.show_parent_selector); - - this.get_child_result().then(r => { - this.child_results = r.message || []; - this.render_child_datatable(); - - this.$wrapper.addClass('hidden'); - this.$child_wrapper.removeClass('hidden'); - }); + toggle_child_selection() { + if (this.dialog.fields_dict['child_selection_mode'].get_value()) { + this.get_child_result().then(r => { + this.child_results = r.message || []; + this.render_child_datatable(); + + this.$wrapper.addClass('hidden'); + this.$child_wrapper.removeClass('hidden'); + }); + } else { + this.child_results = []; + this.get_results(); + this.$wrapper.removeClass('hidden'); + this.$child_wrapper.addClass('hidden'); + } } render_child_datatable() { @@ -177,6 +168,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { setTimeout(() => { this.child_datatable.rowmanager.checkMap = []; this.child_datatable.refresh(this.get_child_datatable_rows()); + this.$child_wrapper.find('.dt-scrollable').css('height', '300px'); }, 500); } } @@ -210,20 +202,6 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { this.$child_wrapper.find('.dt-scrollable').css('height', '300px'); } - show_fetch_child_items() { - const selected_parent = this.get_checked_values(); - - if (selected_parent.length == 0) { - // update secondary option and link default secondary action - const label = __("Make {0}", [this.doctype.plural()]); - this.toggle_secondary_action(label, this.make_new_document); - } else { - // update secondary option and link new secondary action - const label = __("Fetch {0}", [this.child_doctype.plural()]); - this.toggle_secondary_action(label, this.fetch_child_items); - } - } - get_primary_filters() { let fields = []; @@ -267,6 +245,15 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { // now a is a fixed-size array with mutable entries } + if (this.child_selection_mode) { + columns[0].push({ + fieldtype: "Check", + label: __("Select Individual Items"), + fieldname: "child_selection_mode", + onchange: this.toggle_child_selection.bind(this) + }); + } + fields = [ ...columns[0], { fieldtype: "Column Break" }, @@ -320,13 +307,11 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { if (!$(e.target).is(':checkbox') && !$(e.target).is('a')) { $(this).find(':checkbox').trigger('click'); } - if (me.child_selection_mode) me.show_fetch_child_items(); }); this.$results.on('click', '.list-item--head :checkbox', (e) => { this.$results.find('.list-item-container .list-row-check') .prop("checked", ($(e.target).is(':checked'))); - if (me.child_selection_mode) me.show_fetch_child_items(); }); this.$parent.find('.input-with-feedback').on('change', () => { From 54f34bdce8ca690fa589b5fb8ffee2cd1a8747a9 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 10 May 2021 17:07:44 +0530 Subject: [PATCH 66/76] fix: fetching of child items --- .../js/frappe/form/multi_select_dialog.js | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index 760aa3123b..036fad3f4b 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -75,8 +75,9 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { primary_action: () => { let filters_data = this.get_custom_filters(); const data_values = cur_dialog.get_values(); // to pass values of data fields - const filtered_children = this.get_checked_child_names(); - this.action(this.get_checked_values(), { + const filtered_children = this.get_selected_child_names(); + const selected_documents = [...this.get_checked_values(), ...this.get_parent_name_of_selected_children()]; + this.action(selected_documents, { ...this.args, ...data_values, ...filters_data, @@ -330,7 +331,21 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { }); } - get_checked_child_names() { + get_parent_name_of_selected_children() { + if (!this.child_datatable.datamanager.rows.length) return []; + + let parent_names = this.child_datatable.rowmanager.checkMap.reduce((parent_names, checked, index) => { + if (checked == 1) { + const parent_name = this.child_results[index].parent; + parent_names.push(parent_name); + } + return parent_names; + }, []); + + return parent_names; + } + + get_selected_child_names() { if (!this.child_datatable.datamanager.rows.length) return []; let checked_names = this.child_datatable.rowmanager.checkMap.reduce((checked_names, checked, index) => { From fa6efae9218f3cb3f72ed8f0609d5e52480245d8 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 3 Jun 2021 19:06:37 +0530 Subject: [PATCH 67/76] refactor: use child fieldname for child doctype selection --- .../public/js/frappe/form/multi_select_dialog.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index 036fad3f4b..2f889cab97 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -57,7 +57,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { get_child_selection_fields() { const fields = []; - if (this.child_selection_mode && this.child_doctype) { + if (this.allow_child_item_selection && this.child_fieldname) { fields.push({ fieldtype: "HTML", fieldname: "child_selection_area" }); } return fields; @@ -131,13 +131,12 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { } get_child_result() { - const parents = this.results.map(res => res.name); return frappe.call({ method: "frappe.client.get_list", args: { doctype: this.child_doctype, filters: [ - ["parent", "in", parents] + ["parentfield", "=", this.child_fieldname] ], fields: ['name', 'parent', ...this.child_columns], parent: this.doctype @@ -146,7 +145,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { } toggle_child_selection() { - if (this.dialog.fields_dict['child_selection_mode'].get_value()) { + if (this.dialog.fields_dict['allow_child_item_selection'].get_value()) { this.get_child_result().then(r => { this.child_results = r.message || []; this.render_child_datatable(); @@ -246,11 +245,12 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { // now a is a fixed-size array with mutable entries } - if (this.child_selection_mode) { + if (this.allow_child_item_selection) { + this.child_doctype = frappe.meta.get_docfield(this.doctype, this.child_fieldname).options; columns[0].push({ fieldtype: "Check", - label: __("Select Individual Items"), - fieldname: "child_selection_mode", + label: __("Select {0}", [this.child_doctype]), + fieldname: "allow_child_item_selection", onchange: this.toggle_child_selection.bind(this) }); } From 96398b5e8cf84559286753ae0c7c0d42ddf7f743 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 30 Aug 2021 19:40:42 +0530 Subject: [PATCH 68/76] feat: ui test for multiselect dialog --- cypress/integration/multi_select_dialog.js | 58 ++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 cypress/integration/multi_select_dialog.js diff --git a/cypress/integration/multi_select_dialog.js b/cypress/integration/multi_select_dialog.js new file mode 100644 index 0000000000..a45fba8d32 --- /dev/null +++ b/cypress/integration/multi_select_dialog.js @@ -0,0 +1,58 @@ +context('MultiSelectDialog', () => { + before(() => { + cy.login(); + cy.visit('/app'); + }); + + function open_multi_select_dialog() { + cy.window().its('frappe').then(frappe => { + new frappe.ui.form.MultiSelectDialog({ + doctype: "Assignment Rule", + target: {}, + setters: { + document_type: null, + priority: null + }, + add_filters_group: 1, + allow_child_item_selection: 1, + child_fieldname: "assignment_days", + child_columns: ["day"] + }); + }); + } + + it('multi select dialog api works', () => { + open_multi_select_dialog(); + cy.get_open_dialog().should('contain', 'Select Assignment Rules'); + }); + + it('checks for filters', () => { + ['search_term', 'document_type', 'priority'].forEach(fieldname => { + cy.get_open_dialog().get(`.frappe-control[data-fieldname="${fieldname}"]`).should('exist'); + }); + + // add_filters_group: 1 should add a filter group + cy.get_open_dialog().get(`.frappe-control[data-fieldname="filter_area"]`).should('exist'); + + }); + + it('checks for child item selection', () => { + cy.get_open_dialog() + .get(`.dt-row-header`).should('not.exist'); + + cy.get_open_dialog() + .get(`.frappe-control[data-fieldname="allow_child_item_selection"]`) + .should('exist') + .click(); + + cy.get_open_dialog() + .get(`.frappe-control[data-fieldname="child_selection_area"]`) + .should('exist'); + + cy.get_open_dialog() + .get(`.dt-row-header`).should('contain', 'Assignment Rule'); + + cy.get_open_dialog() + .get(`.dt-row-header`).should('contain', 'Day'); + }); +}); \ No newline at end of file From 0ee7c94a9f586539941178d2d7d7e06b9d26b95e Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 13 Sep 2021 13:11:21 +0530 Subject: [PATCH 69/76] feat(minor): empty state for list view --- frappe/public/js/frappe/list/base_list.js | 20 +++++-- frappe/public/js/frappe/list/list_factory.js | 56 ++++++++++++-------- frappe/public/scss/desk/desktop.scss | 4 ++ frappe/public/scss/desk/global.scss | 6 +-- 4 files changed, 56 insertions(+), 30 deletions(-) diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index beacb136e6..3c7f8ac39a 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -3,9 +3,15 @@ frappe.provide("frappe.views"); frappe.views.BaseList = class BaseList { constructor(opts) { Object.assign(this, opts); + this.init_page() } show() { + this.meta = frappe.get_meta(this.doctype); + this.set_title(); + // in loading state? + if (!this.meta) return; + frappe.run_serially([ () => this.init(), () => this.before_refresh(), @@ -34,8 +40,6 @@ frappe.views.BaseList = class BaseList { setup_defaults() { this.page_name = frappe.get_route_str(); - this.page_title = this.page_title || frappe.router.doctype_layout || __(this.doctype); - this.meta = frappe.get_meta(this.doctype); this.settings = frappe.listview_settings[this.doctype] || {}; this.user_settings = frappe.get_user_settings(this.doctype); @@ -150,13 +154,21 @@ frappe.views.BaseList = class BaseList { } } - setup_page() { + init_page() { this.page = this.parent.page; + this.make_skeleton(); this.$page = $(this.parent); !this.hide_card_layout && this.page.main.addClass('frappe-card'); this.page.page_form.removeClass("row").addClass("flex"); this.hide_page_form && this.page.page_form.hide(); this.hide_sidebar && this.$page.addClass('no-list-sidebar'); + } + + make_skeleton() { + this.skeleton = $(`
`).prependTo(this.page.main.parent()); + } + + setup_page() { this.setup_page_head(); } @@ -167,6 +179,7 @@ frappe.views.BaseList = class BaseList { } set_title() { + this.page_title = this.page_title || frappe.router.doctype_layout || __(this.doctype); this.page.set_title(this.page_title); } @@ -280,6 +293,7 @@ frappe.views.BaseList = class BaseList { } setup_list_wrapper() { + this.skeleton.remove(); // clear skeleton this.$frappe_list = $('
').appendTo( this.page.main ); diff --git a/frappe/public/js/frappe/list/list_factory.js b/frappe/public/js/frappe/list/list_factory.js index b467919d7e..d870fdb6fc 100644 --- a/frappe/public/js/frappe/list/list_factory.js +++ b/frappe/public/js/frappe/list/list_factory.js @@ -8,38 +8,50 @@ frappe.views.ListFactory = class ListFactory extends frappe.views.Factory { make (route) { var me = this; var doctype = route[1]; + const meta_loaded = frappe.get_meta(doctype) ? true : false; + const page_name = frappe.get_route_str(); + let view_class = this.get_view_class(route, doctype); + this.make_list_view_page(page_name, doctype, view_class); + + if (view_class && view_class.load_last_view && view_class.load_last_view()) { + // view can have custom routing logic + return; + } frappe.model.with_doctype(doctype, function () { + if (!meta_loaded) { + frappe.views.list_view[page_name].show(); + } if (locals['DocType'][doctype].issingle) { frappe.set_re_route('Form', doctype); } else { - // List / Gantt / Kanban / etc - // File is a special view - const view_name = doctype !== 'File' ? frappe.utils.to_title_case(route[2] || 'List') : 'File'; - let view_class = frappe.views[view_name + 'View']; - if (!view_class) view_class = frappe.views.ListView; - - if (view_class && view_class.load_last_view && view_class.load_last_view()) { - // view can have custom routing logic - return; - } - - frappe.provide('frappe.views.list_view.' + doctype); - const page_name = frappe.get_route_str(); - - if (!frappe.views.list_view[page_name]) { - frappe.views.list_view[page_name] = new view_class({ - doctype: doctype, - parent: me.make_page(true, page_name) - }); - } else { - frappe.container.change_to(page_name); - } + frappe.container.change_to(page_name); me.set_cur_list(); } }); } + get_view_class(route, doctype) { + // List / Gantt / Kanban / etc + // File is a special view + const view_name = doctype !== 'File' ? frappe.utils.to_title_case(route[2] || 'List') : 'File'; + let view_class = frappe.views[view_name + 'View']; + if (!view_class) view_class = frappe.views.ListView; + + return view_class; + } + + make_list_view_page(page_name, doctype, view_class) { + frappe.provide('frappe.views.list_view.' + doctype); + + if (!frappe.views.list_view[page_name]) { + frappe.views.list_view[page_name] = new view_class({ + doctype: doctype, + parent: this.make_page(true, page_name) + }); + } + } + show() { if (this.re_route_to_view()) { return; diff --git a/frappe/public/scss/desk/desktop.scss b/frappe/public/scss/desk/desktop.scss index 1257d9b3a4..0de7103cc3 100644 --- a/frappe/public/scss/desk/desktop.scss +++ b/frappe/public/scss/desk/desktop.scss @@ -739,6 +739,10 @@ body { animation-duration: 400ms; } +.skeleton-bg { + background-color: var(--skeleton-bg); +} + .workspace-skeleton { transition: ease; .widget-group-title { diff --git a/frappe/public/scss/desk/global.scss b/frappe/public/scss/desk/global.scss index 333ee30e4d..ec7fc35cfe 100644 --- a/frappe/public/scss/desk/global.scss +++ b/frappe/public/scss/desk/global.scss @@ -327,10 +327,6 @@ select.input-xs { } } -// .frappe-card { -// @include card(); -// } - .head-title { font-size: var(--text-lg); font-weight: 700; @@ -591,4 +587,4 @@ details > summary:focus { .chart-container { direction: ltr; } -*/ \ No newline at end of file +*/ From 3ae80af2c40dd2724291b85432b2388538794edf Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 13 Sep 2021 15:17:39 +0530 Subject: [PATCH 70/76] chore: Drop unmaintained sample config file Use https://frappeframework.com/docs/user/en/basics/site_config for reference instead --- frappe/data/sample_site_config.json | 45 ----------------------------- 1 file changed, 45 deletions(-) delete mode 100644 frappe/data/sample_site_config.json diff --git a/frappe/data/sample_site_config.json b/frappe/data/sample_site_config.json deleted file mode 100644 index 715cd7b9fa..0000000000 --- a/frappe/data/sample_site_config.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "db_name": "testdb", - "db_password": "password", - "mute_emails": true, - - "limits": { - "emails": 1500, - "space": 0.157, - "expiry": "2016-07-25", - "users": 1 - }, - - "developer_mode": 1, - "auto_cache_clear": true, - "disable_website_cache": true, - "max_file_size": 1000000, - - "mail_server": "localhost", - "mail_login": null, - "mail_password": null, - "mail_port": 25, - "use_ssl": 0, - "auto_email_id": "hello@example.com", - - "google_analytics_id": "google_analytics_id", - "google_analytics_anonymize_ip": 1, - - "google_login": { - "client_id": "google_client_id", - "client_secret": "google_client_secret" - }, - "github_login": { - "client_id": "github_client_id", - "client_secret": "github_client_secret" - }, - "facebook_login": { - "client_id": "facebook_client_id", - "client_secret": "facebook_client_secret" - }, - - "celery_broker": "redis://localhost", - "celery_result_backend": null, - "scheduler_interval": 300, - "celery_queue_per_site": true -} From 528855588eab672804c87c424d575806fadfb954 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 13 Sep 2021 15:48:21 +0530 Subject: [PATCH 71/76] refactor: child table filtering --- .../js/frappe/form/multi_select_dialog.js | 117 +++++++++++++----- 1 file changed, 83 insertions(+), 34 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index 2f889cab97..bb1398e1c1 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -130,20 +130,6 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { this.$results.append(this.make_list_row()); } - get_child_result() { - return frappe.call({ - method: "frappe.client.get_list", - args: { - doctype: this.child_doctype, - filters: [ - ["parentfield", "=", this.child_fieldname] - ], - fields: ['name', 'parent', ...this.child_columns], - parent: this.doctype - } - }); - } - toggle_child_selection() { if (this.dialog.fields_dict['allow_child_item_selection'].get_value()) { this.get_child_result().then(r => { @@ -152,6 +138,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { this.$wrapper.addClass('hidden'); this.$child_wrapper.removeClass('hidden'); + this.dialog.fields_dict.more_btn.$wrapper.hide(); }); } else { this.child_results = []; @@ -458,7 +445,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { this.render_result_list(checked, 0, false); } - get_results() { + get_filters_from_setters() { let me = this; let filters = this.get_query ? this.get_query().filters : {} || {}; let filter_fields = []; @@ -482,12 +469,18 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { }); } - let filter_group = this.get_custom_filters(); - Object.assign(filters, filter_group); + return [filters, filter_fields]; + } - let args = { - doctype: me.doctype, - txt: me.dialog.fields_dict["search_term"].get_value(), + get_args_for_search() { + let [filters, filter_fields] = this.get_filters_from_setters(); + + let custom_filters = this.get_custom_filters(); + Object.assign(filters, custom_filters); + + return { + doctype: this.doctype, + txt: this.dialog.fields_dict["search_term"].get_value(), filters: filters, filter_fields: filter_fields, start: this.start, @@ -495,25 +488,81 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { query: this.get_query ? this.get_query().query : '', as_dict: 1 }; - frappe.call({ + } + + async perform_search(args) { + const res = await frappe.call({ type: "GET", method: 'frappe.desk.search.search_widget', no_spinner: true, args: args, - callback: function (r) { - let more = 0; - me.results = []; - if (r.values.length) { - if (r.values.length > me.page_length) { - r.values.pop(); - more = 1; - } - r.values.forEach(function (result) { - result.checked = 0; - me.results.push(result); - }); + }); + const more = res.values.length && res.values.length > this.page_length ? 1 : 0; + if (more) { + res.values.pop(); + } + + return [res, more]; + } + + async get_results() { + const args = this.get_args_for_search(); + const [res, more] = await this.perform_search(args); + + this.results = []; + if (res.values.length) { + res.values.forEach(result => { + result.checked = 0; + this.results.push(result); + }); + } + this.render_result_list(this.results, more); + } + + async get_filtered_parents_for_child_search() { + const parent_search_args = this.get_args_for_search(); + parent_search_args.filter_fields = ['name']; + // eslint-disable-next-line no-unused-vars + const [response, _] = await this.perform_search(parent_search_args); + + let parent_names = []; + if (response.values.length) { + parent_names = response.values.map(v => v.name); + } + return parent_names; + } + + async add_parent_filters(filters) { + const parent_names = await this.get_filtered_parents_for_child_search(); + if (parent_names.length) { + filters.push([ "parent", "in", parent_names ]); + } + } + + add_custom_child_filters(filters) { + if (this.add_filters_group && this.filter_group) { + this.filter_group.get_filters().forEach(filter => { + if (filter[0] == this.child_doctype) { + filters.push([filter[1], filter[2], filter[3]]); } - me.render_result_list(me.results, more); + }); + } + } + + async get_child_result() { + let filters = [["parentfield", "=", this.child_fieldname]]; + + await this.add_parent_filters(filters); + this.add_custom_child_filters(filters); + + return frappe.call({ + method: "frappe.client.get_list", + args: { + doctype: this.child_doctype, + filters: filters, + fields: ['name', 'parent', ...this.child_columns], + parent: this.doctype, + order_by: 'parent' } }); } From 602bfd4b62995d55ac2ab4cf7cbd94613805a923 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 13 Sep 2021 15:56:00 +0530 Subject: [PATCH 72/76] fix: Show user name with workflow state in timeline --- frappe/public/js/frappe/form/footer/form_timeline.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/footer/form_timeline.js b/frappe/public/js/frappe/form/footer/form_timeline.js index adffce7c9c..dfaf64bc8d 100644 --- a/frappe/public/js/frappe/form/footer/form_timeline.js +++ b/frappe/public/js/frappe/form/footer/form_timeline.js @@ -147,7 +147,9 @@ class FormTimeline extends BaseTimeline { } get_user_link(user) { - const user_display_text = (frappe.user_info(user).fullname || '').bold(); + const user_display_text = ( + (frappe.session.user == user ? "You" : frappe.user_info(user).fullname) || '' + ).bold(); return frappe.utils.get_form_link('User', user, true, user_display_text); } @@ -353,7 +355,7 @@ class FormTimeline extends BaseTimeline { icon: 'branch', icon_size: 'sm', creation: workflow_log.creation, - content: __(workflow_log.content), + content: `${this.get_user_link(workflow_log.owner)} ${__(workflow_log.content)}`, title: "Workflow", }); }); From 1c37cdde34c2e5419968d9f75c0ce4c5a5213fe7 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 13 Sep 2021 17:43:24 +0530 Subject: [PATCH 73/76] fix: Translate "You" in get_user_link --- frappe/public/js/frappe/form/footer/form_timeline.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/footer/form_timeline.js b/frappe/public/js/frappe/form/footer/form_timeline.js index dfaf64bc8d..b3feae3ee8 100644 --- a/frappe/public/js/frappe/form/footer/form_timeline.js +++ b/frappe/public/js/frappe/form/footer/form_timeline.js @@ -148,7 +148,7 @@ class FormTimeline extends BaseTimeline { get_user_link(user) { const user_display_text = ( - (frappe.session.user == user ? "You" : frappe.user_info(user).fullname) || '' + (frappe.session.user == user ? __("You") : frappe.user_info(user).fullname) || '' ).bold(); return frappe.utils.get_form_link('User', user, true, user_display_text); } From e27f58cc0fd93e877ef0a55c3ea04013e71c6a10 Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Mon, 13 Sep 2021 21:16:33 +0530 Subject: [PATCH 74/76] fix: Total Row in query report still hidden (#14183) --- frappe/public/js/frappe/views/reports/query_report.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 1053f9b7c5..1478f24b13 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -832,6 +832,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { if (this.raw_data.add_total_row) { data = data.slice(); data.splice(-1, 1); + this.$page.find('.layout-main-section').css('--report-total-height', '310px'); } this.$report.show(); @@ -854,10 +855,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } }; - if (this.raw_data.add_total_row) { - this.$page.find('.layout-main-section').css('--report-total-height', '310px'); - } - if (this.report_settings.get_datatable_options) { datatable_options = this.report_settings.get_datatable_options(datatable_options); } From edea08718ce2483ae11c66414faac1ca6447ba0e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 14 Sep 2021 12:09:01 +0530 Subject: [PATCH 75/76] fix: publish realtime to work with localhost (backport #14174) (#14196) Co-authored-by: leela --- socketio.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/socketio.js b/socketio.js index 502a80f07b..ac97c77d32 100644 --- a/socketio.js +++ b/socketio.js @@ -265,9 +265,11 @@ function get_chat_room(socket, room) { } function get_site_name(socket) { + var hostname_from_host = get_hostname(socket.request.headers.host); + if (socket.request.headers['x-frappe-site-name']) { return get_hostname(socket.request.headers['x-frappe-site-name']); - } else if (['localhost', '127.0.0.1'].indexOf(socket.request.headers.host) !== -1 && + } else if (['localhost', '127.0.0.1'].indexOf(hostname_from_host) !== -1 && conf.default_site) { // from currentsite.txt since host is localhost return conf.default_site; From fbe4bdaa9ece56f79f5a96702d44ba8c4ea3f5da Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 14 Sep 2021 12:09:28 +0530 Subject: [PATCH 76/76] fix: cannot read property 'datamanager' of undefined (backport #14178) (#14194) Co-authored-by: Saqib Ansari --- frappe/public/js/frappe/form/multi_select_dialog.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index bb1398e1c1..ba522a4085 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -319,7 +319,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { } get_parent_name_of_selected_children() { - if (!this.child_datatable.datamanager.rows.length) return []; + if (!this.child_datatable || !this.child_datatable.datamanager.rows.length) return []; let parent_names = this.child_datatable.rowmanager.checkMap.reduce((parent_names, checked, index) => { if (checked == 1) { @@ -333,7 +333,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { } get_selected_child_names() { - if (!this.child_datatable.datamanager.rows.length) return []; + if (!this.child_datatable || !this.child_datatable.datamanager.rows.length) return []; let checked_names = this.child_datatable.rowmanager.checkMap.reduce((checked_names, checked, index) => { if (checked == 1) {
Id + Time + State + Info + Progress +