8000 chore: release v13 by frappe-pr-bot · Pull Request #18879 · frappe/frappe · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

chore: release v13 #18879

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion frappe/database/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import datetime
import re
from contextlib import suppress
from time import time
from typing import Dict, List, Union

Expand All @@ -21,7 +22,7 @@
from frappe.database.query import Query
from frappe.model.utils.link_count import flush_local_link_count
from frappe.query_builder.utils import DocType
from frappe.utils import cast, get_datetime, get_table_name, getdate, now, sbool
from frappe.utils import cast, cint, get_datetime, get_table_name, getdate, now, sbool


class Database(object):
Expand Down Expand Up @@ -85,6 +86,18 @@ def connect(self):
self._cursor = self._conn.cursor()
frappe.local.rollback_observers = []

try:
execution_timeout = get_query_execution_timeout()
if execution_timeout:
self.set_execution_timeout(execution_timeout)
except Exception as e:
frappe.logger("database").warning(f"Couldn't set execution timeout {e}")

def set_execution_timeout(self, seconds: int):
"""Set session speicifc timeout on exeuction of statements.
If any statement takes more time it will be killed along with entire transaction."""
raise NotImplementedError

def use(self, db_name):
"""`USE` db_name."""
self._conn.select_db(db_name)
Expand Down Expand Up @@ -1273,3 +1286,26 @@ def enqueue_jobs_after_commit():
result_ttl=RQ_RESULTS_TTL,
)
frappe.flags.enqueue_after_commit = []


def get_query_execution_timeout() -> int:
"""Get execution timeout based on current timeout in different contexts.
HTTP requests: HTTP timeout or a default (300)
Background jobs: Job timeout
Console/Commands: No timeout = 0.
Note: Timeout adds 1.5x as "safety factor"
"""
from rq import get_current_job

if not frappe.conf.get("enable_db_statement_timeout"):
return 0

# Zero means no timeout, which is the default value in db.
timeout = 0
with suppress(Exception):
if getattr(frappe.local, "request", None):
timeout = frappe.conf.http_timeout or 300
elif get_current_job():
timeout = get_current_job().timeout

return int(cint(timeout) * 1.5)
7 changes: 7 additions & 0 deletions frappe/database/mariadb/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ def get_connection(self):

return conn

def set_execution_timeout(self, seconds: int):
self.sql("set session max_statement_time = %s", int(seconds))

def get_database_size(self):
"""'Returns database size in MB"""
db_size = self.sql(
Expand Down Expand Up @@ -154,6 +157,10 @@ def is_deadlocked(e):
def is_timedout(e):
return e.args[0] == ER.LOCK_WAIT_TIMEOUT

@staticmethod
def is_statement_timeout(e):
return e.args[0] == 1969

@staticmethod
def is_table_missing(e):
return e.args[0] == ER.NO_SUCH_TABLE
Expand Down
8 changes: 8 additions & 0 deletions frappe/database/postgres/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ def get_connection(self):

return conn

def set_execution_timeout(self, seconds: int):
# Postgres expects milliseconds as input
self.sql("set local statement_timeout = %s", int(seconds) * 1000)

def escape(self, s, percent=True):
"""Excape quotes and percent in given string."""
if isinstance(s, bytes):
Expand Down Expand Up @@ -157,6 +161,10 @@ def is_timedout(e):
# http://initd.org/psycopg/docs/extensions.html?highlight=datatype#psycopg2.extensions.QueryCanceledError
return isinstance(e, psycopg2.extensions.QueryCanceledError)

@staticmethod
def is_statement_timeout(e):
return PostgresDatabase.is_timedout(e) or isinstance(e, frappe.QueryTimeoutError)

@staticmethod
def is_table_missing(e):
return getattr(e, "pgcode", None) == "42P01"
Expand Down
12 changes: 9 additions & 3 deletions frappe/desk/desktop.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,14 +607,20 @@ def update_onboarding_step(name, field, value):


@frappe.whitelist()
def reset_customization(page):
def reset_customization(page: str) -> None:
"""Reset workspace customizations for a user

Args:
page (string): Name of the page to be reset
"""
page_doc = get_custom_workspace_for_user(page)
page_doc.delete()
if not isinstance(page, str):
raise TypeError("page must be a string")

workspace_name = frappe.db.get_value(
"Workspace", {"extends": page, "for_user": frappe.session.user}
)
if workspace_name:
frappe.delete_doc("Workspace", workspace_name, ignore_permissions=True)


def merge_cards_based_on_label(cards):
Expand Down
2 changes: 1 addition & 1 deletion frappe/desk/reportview.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def validate_args(data):
def validate_fields(data):
wildcard = update_wildcard_field_param(data)

for field in data.fields or []:
for field in list(data.fields or []):
fieldname = extract_fieldname(field)
if is_standard(fieldname):
continue
Expand Down
30 changes: 20 additions & 10 deletions frappe/model/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@
from __future__ import unicode_literals

import json
from typing import TYPE_CHECKING, List, Union

from six import string_types

import frappe
from frappe import _
from frappe.utils import cint

if TYPE_CHECKING:
from frappe.model.document import Document
from frappe.workflow.doctype.workflow.workflow import Workflow


class WorkflowStateError(frappe.ValidationError):
pass
Expand All @@ -36,20 +41,22 @@ def get_workflow_name(doctype):


@frappe.whitelist()
def get_transitions(doc, workflow=None, raise_exception=False):
def get_transitions(
doc: Union["Document", str, dict], workflow: "Workflow" = None, raise_exception: bool = False
) -> List[dict]:
"""Return list of possible transitions for the given doc"""
doc = frappe.get_doc(frappe.parse_json(doc))
from frappe.model.document import Document

if not isinstance(doc, Document):
doc = frappe.get_doc(frappe.parse_json(doc))
doc.load_from_db()

if doc.is_new():
return []

doc.load_from_db()

frappe.has_permission(doc, "read", throw=True)
roles = frappe.get_roles()
doc.check_permission("read")

if not workflow:
workflow = get_workflow(doc.doctype)
workflow = workflow or get_workflow(doc.doctype)
current_state = doc.get(workflow.workflow_state_field)

if not current_state:
Expand All @@ -59,11 +66,14 @@ def get_transitions(doc, workflow=None, raise_exception=False):
frappe.throw(_("Workflow State not set"), WorkflowStateError)

transitions = []
roles = frappe.get_roles()

for transition in workflow.transitions:
if transition.state == current_state and transition.allowed in roles:
if not is_transition_condition_satisfied(transition, doc):
continue
transitions.append(transition.as_dict())

return transitions


Expand All @@ -83,7 +93,7 @@ def get_workflow_safe_globals():
)


def is_transition_condition_satisfied(transition, doc):
def is_transition_condition_satisfied(transition, doc) -> bool:
if not transition.condition:
return True
else:
Expand Down Expand Up @@ -202,7 +212,7 @@ def validate_workflow(doc):
)


def get_workflow(doctype):
def get_workflow(doctype) -> "Workflow":
return frappe.get_doc("Workflow", get_workflow_name(doctype))


Expand Down
2 changes: 1 addition & 1 deletion frappe/public/js/frappe/list/list_view_select.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ frappe.views.ListViewSelect = class ListViewSelect {
account.email_id == "All Accounts"
? "All Accounts"
: account.email_account;
let route = `/app/communication/inbox/${email_account}`;
let route = `/app/communication/view/inbox/${email_account}`;
let display_name = [
"All Accounts",
"Sent Mail",
Expand Down
12 changes: 8 additions & 4 deletions frappe/public/js/frappe/ui/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,13 +316,14 @@ frappe.ui.Page = Class.extend({

//--- Menu --//

add_menu_item: function(label, click, standard, shortcut) {
add_menu_item: function(label, click, standard, shortcut, show_parent) {
return this.add_dropdown_item({
label,
click,
standard,
parent: this.menu,
shortcut
shortcut,
show_parent,
});
},

Expand Down Expand Up @@ -406,7 +407,7 @@ frappe.ui.Page = Class.extend({
*/
add_dropdown_item: function({label, click, standard, parent, shortcut, show_parent=true, icon=null}) {
if (show_parent) {
parent.parent().removeClass("hide");
parent.parent().removeClass("hide hidden-xl");
}

let $link = this.is_in_group_button_dropdown(parent, 'li > a.grey-link > span', label);
Expand Down Expand Up @@ -576,8 +577,11 @@ frappe.ui.Page = Class.extend({
};
// Add actions as menu item in Mobile View
let menu_item_label = group ? `${group} > ${label}` : label;
let menu_item = this.add_menu_item(menu_item_label, _action, false);
let menu_item = this.add_menu_item(menu_item_label, _action, false, false, false);
menu_item.parent().addClass("hidden-xl");
if (this.menu_btn_group.hasClass("hide")) {
this.menu_btn_group.removeClass("hide").addClass("hidden-xl");
}

if (group) {
var $group = this.get_or_add_inner_group_button(group);
Expand Down
4 changes: 2 additions & 2 deletions frappe/templates/includes/navbar/navbar_search.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<li>
<form action='/search'>
<input name='q' class='form-control navbar-search' type='text'
value='{{ frappe.form_dict.q or ''}}'
value='{{ frappe.form_dict.q|e or ''}}'
{% if not frappe.form_dict.q%}placeholder="{{ _("Search...") }}"{% endif %}>
</form>
</li>
{% endif %}
{% endif %}
13 changes: 13 additions & 0 deletions frappe/tests/test_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,19 @@ def test_get_single_value(self):
# teardown
clear_custom_fields("Print Settings")

@run_only_if(db_type_is.MARIADB)
def test_db_statement_execution_timeout(self):
frappe.db.set_execution_timeout(2)
# Setting 0 means no timeout.
self.addCleanup(frappe.db.set_execution_timeout, 0)

try:
frappe.db.sql("select sleep(10)")
except Exception as e:
self.assertTrue(frappe.db.is_statement_timeout(e), f"exepcted {e} to be timeout error")
else:
self.fail("Long running queries not timing out")

def test_log_touched_tables(self):
frappe.flags.in_migrate = True
frappe.flags.touched_tables = set()
Expand Down
0