diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml index d9a6ca6f59..52fa987994 100644 --- a/.github/workflows/patch-mariadb-tests.yml +++ b/.github/workflows/patch-mariadb-tests.yml @@ -10,6 +10,7 @@ concurrency: jobs: test: runs-on: ubuntu-latest + timeout-minutes: 60 name: Patch Test diff --git a/.github/workflows/server-mariadb-tests.yml b/.github/workflows/server-mariadb-tests.yml index 588f357f26..4edf74ba71 100644 --- a/.github/workflows/server-mariadb-tests.yml +++ b/.github/workflows/server-mariadb-tests.yml @@ -14,6 +14,7 @@ concurrency: jobs: test: runs-on: ubuntu-latest + timeout-minutes: 60 strategy: fail-fast: false @@ -128,4 +129,4 @@ jobs: fail_ci_if_error: true files: /home/runner/frappe-bench/sites/coverage.xml verbose: true - flags: server \ No newline at end of file + flags: server diff --git a/.github/workflows/server-postgres-tests.yml b/.github/workflows/server-postgres-tests.yml index 78f379837b..895af5184e 100644 --- a/.github/workflows/server-postgres-tests.yml +++ b/.github/workflows/server-postgres-tests.yml @@ -13,6 +13,7 @@ concurrency: jobs: test: runs-on: ubuntu-latest + timeout-minutes: 60 strategy: fail-fast: false diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index fcc53ba59c..cb502f68a7 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -13,6 +13,7 @@ concurrency: jobs: test: runs-on: ubuntu-latest + timeout-minutes: 60 strategy: fail-fast: false diff --git a/cypress/integration/report_view.js b/cypress/integration/report_view.js index 0253e8fd43..629ae72eb8 100644 --- a/cypress/integration/report_view.js +++ b/cypress/integration/report_view.js @@ -7,6 +7,8 @@ context('Report View', () => { cy.visit('/app/website'); cy.insert_doc('DocType', custom_submittable_doctype, true); cy.clear_cache(); + }); + it('Field with enabled allow_on_submit should be editable.', () => { cy.insert_doc(doctype_name, { 'title': 'Doc 1', 'description': 'Random Text', @@ -14,8 +16,6 @@ context('Report View', () => { // submit document 'docstatus': 1 }, true).as('doc'); - }); - it('Field with enabled allow_on_submit should be editable.', () => { cy.intercept('POST', 'api/method/frappe.client.set_value').as('value-update'); cy.visit(`/app/List/${doctype_name}/Report`); // check status column added from docstatus diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index 4d22075b78..54ddbce2c4 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -146,25 +146,43 @@ def add_attachments(name, attachments): }) _file.save(ignore_permissions=True) -@frappe.whitelist(allow_guest=True) -def mark_email_as_seen(name=None): +@frappe.whitelist(allow_guest=True, methods=("GET",)) +def mark_email_as_seen(name: str = None): try: - if name and frappe.db.exists("Communication", name) and not frappe.db.get_value("Communication", name, "read_by_recipient"): - frappe.db.set_value("Communication", name, "read_by_recipient", 1) - frappe.db.set_value("Communication", name, "delivery_status", "Read") - frappe.db.set_value("Communication", name, "read_by_recipient_on", get_datetime()) - frappe.db.commit() + update_communication_as_read(name) + frappe.db.commit() # nosemgrep: this will be called in a GET request + except Exception: frappe.log_error(frappe.get_traceback()) - finally: - # Return image as response under all circumstances - from PIL import Image - import io - im = Image.new('RGBA', (1, 1)) - im.putdata([(255,255,255,0)]) - buffered_obj = io.BytesIO() - im.save(buffered_obj, format="PNG") - frappe.response["type"] = 'binary' - frappe.response["filename"] = "imaginary_pixel.png" - frappe.response["filecontent"] = buffered_obj.getvalue() + finally: + frappe.response.update({ + "type": "binary", + "filename": "imaginary_pixel.png", + "filecontent": ( + b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00" + b"\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\r" + b"IDATx\x9cc\xf8\xff\xff?\x03\x00\x08\xfc\x02\xfe\xa7\x9a\xa0" + b"\xa0\x00\x00\x00\x00IEND\xaeB`\x82" + ) + }) + +def update_communication_as_read(name): + if not name or not isinstance(name, str): + return + + communication = frappe.db.get_value( + "Communication", + name, + "read_by_recipient", + as_dict=True + ) + + if not communication or communication.read_by_recipient: + return + + frappe.db.set_value("Communication", name, { + "read_by_recipient": 1, + "delivery_status": "Read", + "read_by_recipient_on": get_datetime() + }) diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index ea31e76a57..cf05ce0c15 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -599,7 +599,7 @@ "fieldname": "desk_theme", "fieldtype": "Select", "label": "Desk Theme", - "options": "Light\nDark" + "options": "Light\nDark\nAutomatic" }, { "fieldname": "module_profile", @@ -669,7 +669,7 @@ } ], "max_attachments": 5, - "modified": "2021-10-27 17:17:16.098457", + "modified": "2021-11-17 17:17:16.098457", "modified_by": "Administrator", "module": "Core", "name": "User", diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 86fd1cb4a6..b127cf5f0c 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -1046,7 +1046,7 @@ def generate_keys(user): @frappe.whitelist() def switch_theme(theme): - if theme in ["Dark", "Light"]: + if theme in ["Dark", "Light", "Automatic"]: frappe.db.set_value("User", frappe.session.user, "desk_theme", theme) def get_enabled_users(): diff --git a/frappe/desk/doctype/notification_settings/notification_settings.json b/frappe/desk/doctype/notification_settings/notification_settings.json index fc12022e89..fc535fa405 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.json +++ b/frappe/desk/doctype/notification_settings/notification_settings.json @@ -15,7 +15,9 @@ "enable_email_energy_point", "enable_email_share", "user", - "seen" + "seen", + "system_notifications_section", + "energy_points_system_notifications" ], "fields": [ { @@ -84,15 +86,27 @@ "fieldtype": "Check", "hidden": 1, "label": "Seen" + }, + { + "fieldname": "system_notifications_section", + "fieldtype": "Section Break", + "label": "System Notifications" + }, + { + "default": "1", + "fieldname": "energy_points_system_notifications", + "fieldtype": "Check", + "label": "Energy Points" } ], "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-11-04 12:54:57.989317", + "modified": "2021-11-16 12:18:46.955501", "modified_by": "Administrator", "module": "Desk", "name": "Notification Settings", + "naming_rule": "Set by user", "owner": "Administrator", "permissions": [ { @@ -111,4 +125,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index a53368d67a..2855c6ae7c 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -64,6 +64,19 @@ frappe.Application = class Application { } }); + frappe.ui.add_system_theme_switch_listener(); + const root = document.documentElement; + + const observer = new MutationObserver(() => { + frappe.ui.set_theme(); + }); + observer.observe(root, { + attributes: true, + attributeFilter: ['data-theme-mode'] + }); + + frappe.ui.set_theme(); + // page container this.make_page_container(); this.set_route(); diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js index 9d5e7cbe09..df4dbf09e7 100644 --- a/frappe/public/js/frappe/form/dashboard.js +++ b/frappe/public/js/frappe/form/dashboard.js @@ -14,6 +14,7 @@ frappe.ui.form.Dashboard = class FormDashboard { this.progress_area = this.make_section({ css_class: 'progress-area', hidden: 1, + collapsible: 1, is_dashboard_section: 1, }); @@ -21,6 +22,7 @@ frappe.ui.form.Dashboard = class FormDashboard { label: __("Overview"), css_class: 'form-heatmap', hidden: 1, + collapsible: 1, is_dashboard_section: 1, body_html: `
@@ -32,6 +34,7 @@ frappe.ui.form.Dashboard = class FormDashboard { label: __("Graph"), css_class: 'form-graph', hidden: 1, + collapsible: 1, is_dashboard_section: 1 }); @@ -40,6 +43,7 @@ frappe.ui.form.Dashboard = class FormDashboard { label: __("Stats"), css_class: 'form-stats', hidden: 1, + collapsible: 1, is_dashboard_section: 1, body_html: this.stats_area_row }); @@ -50,6 +54,7 @@ frappe.ui.form.Dashboard = class FormDashboard { label: __("Connections"), css_class: 'form-links', hidden: 1, + collapsible: 1, is_dashboard_section: 1, body_html: this.transactions_area }); @@ -84,9 +89,10 @@ frappe.ui.form.Dashboard = class FormDashboard { hidden, body_html, make_card: true, + collapsible: 1, is_dashboard_section: 1 }; - return new Section(this.frm.layout.wrapper, options).body; + return new Section(this.parent, options).body; } add_progress(title, percent, message) { @@ -203,7 +209,7 @@ frappe.ui.form.Dashboard = class FormDashboard { after_refresh() { // show / hide new buttons (if allowed) this.links_area.body.find('.btn-new').each((i, el) => { - if (this.frm.can_create($(this).attr('data-doctype'))) { + if (this.frm.can_create($(el).attr('data-doctype'))) { $(el).removeClass('hidden'); } }); diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 75d68b12db..27281d8927 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -156,8 +156,11 @@ frappe.ui.form.Form = class FrappeForm { let dashboard_parent = $('