Merge branch 'develop' into mariadb-client-refactor

This commit is contained in:
gavin 2022-06-20 15:31:53 +05:30 committed by GitHub
commit 73f6141e0d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 168 additions and 100 deletions

View file

@ -13,7 +13,7 @@ jobs:
steps:
- name: 'Setup Environment'
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: 3.8

View file

@ -12,7 +12,7 @@ jobs:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: 3.8

View file

@ -31,12 +31,12 @@ jobs:
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Setup Node
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: 14
check-latest: true
@ -56,7 +56,7 @@ jobs:
- name: Cache pip
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
@ -66,7 +66,7 @@ jobs:
- name: Cache node modules
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v2
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
@ -82,7 +82,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
- uses: actions/cache@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
id: yarn-cache
with:

View file

@ -13,10 +13,10 @@ jobs:
- uses: actions/checkout@v2
with:
path: 'frappe'
- uses: actions/setup-node@v1
- uses: actions/setup-node@v3
with:
node-version: 14
- uses: actions/setup-python@v2
- uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Set up bench and build assets

View file

@ -16,10 +16,10 @@ jobs:
- uses: actions/checkout@v2
with:
path: 'frappe'
- uses: actions/setup-node@v1
- uses: actions/setup-node@v3
with:
python-version: '12.x'
- uses: actions/setup-python@v2
- uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Set up bench and build assets

View file

@ -17,7 +17,7 @@ jobs:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js v14
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: 14
- name: Setup dependencies

View file

@ -40,7 +40,7 @@ jobs:
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: '3.9'
@ -53,7 +53,7 @@ jobs:
PR_NUMBER: ${{ github.event.number }}
REPO_NAME: ${{ github.repository }}
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
with:
node-version: 14
@ -67,7 +67,7 @@ jobs:
- name: Cache pip
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
@ -77,7 +77,7 @@ jobs:
- name: Cache node modules
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v2
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
@ -93,7 +93,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
- uses: actions/cache@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
id: yarn-cache
with:
@ -126,7 +126,7 @@ jobs:
- name: Upload coverage data
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v3
with:
name: MariaDB
fail_ci_if_error: true

View file

@ -43,7 +43,7 @@ jobs:
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: '3.9'
@ -56,7 +56,7 @@ jobs:
PR_NUMBER: ${{ github.event.number }}
REPO_NAME: ${{ github.repository }}
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
with:
node-version: '14'
@ -70,7 +70,7 @@ jobs:
- name: Cache pip
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
@ -80,7 +80,7 @@ jobs:
- name: Cache node modules
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v2
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
@ -96,7 +96,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
- uses: actions/cache@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
id: yarn-cache
with:
@ -129,7 +129,7 @@ jobs:
- name: Upload coverage data
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v3
with:
name: Postgres
fail_ci_if_error: true

View file

@ -39,7 +39,7 @@ jobs:
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: '3.9'
@ -52,7 +52,7 @@ jobs:
PR_NUMBER: ${{ github.event.number }}
REPO_NAME: ${{ github.repository }}
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
with:
node-version: 14
@ -66,7 +66,7 @@ jobs:
- name: Cache pip
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
@ -76,7 +76,7 @@ jobs:
- name: Cache node modules
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v2
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
@ -92,7 +92,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
- uses: actions/cache@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
id: yarn-cache
with:
@ -103,7 +103,7 @@ jobs:
- name: Cache cypress binary
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.cache
key: ${{ runner.os }}-cypress-
@ -158,7 +158,7 @@ jobs:
- name: Upload Coverage Data
if: ${{ steps.check-build.outputs.build == 'strawberry' && steps.check_coverage.outputs.files_exists == 'true' }}
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v3
with:
name: Cypress
fail_ci_if_error: true
@ -168,7 +168,7 @@ jobs:
- name: Upload Server Coverage Data
if: ${{ steps.check-build.outputs.build-server == 'strawberry' }}
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v3
with:
name: MariaDB
fail_ci_if_error: true

View file

@ -28,6 +28,7 @@ context('Awesome Bar', () => {
cy.findByPlaceholderText('ID')
.should('have.value', '%test%');
cy.clear_filters();
});
it('navigates to new form', () => {

View file

@ -0,0 +1,40 @@
const list_view = "/app/todo";
// test round trip with filter types
const test_queries = [
"?status=Open",
`?date=%5B"Between"%2C%5B"2022-06-01"%2C"2022-06-30"%5D%5D`,
`?date=%5B">"%2C"2022-06-01"%5D`,
`?name=%5B"like"%2C"%2542%25"%5D`,
`?status=%5B"not%20in"%2C%5B"Open"%2C"Closed"%5D%5D`,
];
describe("SPA Routing", { scrollBehavior: false }, () => {
before(() => {
cy.login();
cy.go_to_list("ToDo");
});
after(() => {
cy.clear_filters(); // avoid flake in future tests
});
it("should apply filter on list view from route", () => {
test_queries.forEach((query) => {
const full_url = `${list_view}${query}`;
cy.visit(full_url);
cy.findByTitle("To Do").should("exist");
const expected = new URLSearchParams(query);
cy.location().then((loc) => {
const actual = new URLSearchParams(loc.search);
// This might appear like a dumb test checking visited URL to itself
// but it's actually doing a round trip
// URL with params -> parsed filters -> new URL
// if it's same that means everything worked in between.
expect(actual.toString()).to.eq(expected.toString());
});
});
});
});

View file

@ -927,7 +927,7 @@ def has_permission(
if throw and not out:
# mimics frappe.throw
document_label = f"{doc.doctype} {doc.name}" if doc else doctype
document_label = f"{_(doc.doctype)} {doc.name}" if doc else _(doctype)
msgprint(
_("No permission for {0}").format(document_label),
raise_exception=ValidationError,

View file

@ -101,7 +101,7 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False, paren
check_parent_permission(parent, doctype)
if not frappe.has_permission(doctype, parent_doctype=parent):
frappe.throw(_("No permission for {0}").format(doctype), frappe.PermissionError)
frappe.throw(_("No permission for {0}").format(_(doctype)), frappe.PermissionError)
filters = get_safe_filters(filters)
if isinstance(filters, str):
@ -143,7 +143,7 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False, paren
@frappe.whitelist()
def get_single_value(doctype, field):
if not frappe.has_permission(doctype):
frappe.throw(_("No permission for {0}").format(doctype), frappe.PermissionError)
frappe.throw(_("No permission for {0}").format(_(doctype)), frappe.PermissionError)
value = frappe.db.get_single_value(doctype, field)
return value

View file

@ -170,12 +170,13 @@ def delete_contact_and_address(doctype, docname):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def filter_dynamic_link_doctypes(txt: str, filters: Dict) -> List[List[str]]:
def filter_dynamic_link_doctypes(
doctype, txt: str, searchfield, start, page_len, filters: Dict
) -> List[List[str]]:
from frappe.permissions import get_doctypes_with_read
txt = txt or ""
filters = filters or {}
TXT_PATTERN = re.compile(f"{txt}.*")
_doctypes_from_df = frappe.get_all(
"DocField",
@ -184,13 +185,13 @@ def filter_dynamic_link_doctypes(txt: str, filters: Dict) -> List[List[str]]:
distinct=True,
order_by=None,
)
doctypes_from_df = {d for d in _doctypes_from_df if TXT_PATTERN.search(_(d), re.IGNORECASE)}
doctypes_from_df = {d for d in _doctypes_from_df if txt.lower() in _(d).lower()}
filters.update({"dt": ("not in", doctypes_from_df)})
_doctypes_from_cdf = frappe.get_all(
"Custom Field", filters=filters, pluck="dt", distinct=True, order_by=None
)
doctypes_from_cdf = {d for d in _doctypes_from_cdf if TXT_PATTERN.search(_(d), re.IGNORECASE)}
doctypes_from_cdf = {d for d in _doctypes_from_cdf if txt.lower() in _(d).lower()}
all_doctypes = doctypes_from_df.union(doctypes_from_cdf)
allowed_doctypes = set(get_doctypes_with_read())

View file

@ -743,7 +743,7 @@ class DatabaseQuery(object):
):
only_if_shared = True
if not self.shared:
frappe.throw(_("No permission to read {0}").format(self.doctype), frappe.PermissionError)
frappe.throw(_("No permission to read {0}").format(_(self.doctype)), frappe.PermissionError)
else:
self.conditions.append(self.get_share_condition())

View file

@ -10,7 +10,6 @@ import "./frappe/form/templates/set_sharing.html";
import "./frappe/form/templates/timeline_message_box.html";
import "./frappe/form/templates/users_in_sidebar.html";
import "./frappe/form/controls/control.js";
import "./frappe/views/formview.js";
import "./frappe/form/form.js";
import "./frappe/meta_tag.js";

View file

@ -575,8 +575,6 @@ frappe.ui.form.Form = class FrappeForm {
this.$wrapper.trigger('render_complete');
this.layout.set_first_tab_as_active(switched || this.cscript.is_onload);
if(!this.hidden) {
this.layout.show_empty_form_message();
}
@ -1842,6 +1840,15 @@ frappe.ui.form.Form = class FrappeForm {
});
});
}
set_active_tab(tab) {
if (!this.active_tab_map) {
this.active_tab_map = {};
}
this.active_tab_map[this.docname] = tab;
}
get_active_tab() {
return this.active_tab_map && this.active_tab_map[this.docname];
}
};
frappe.validated = 0;

View file

@ -123,7 +123,7 @@ frappe.ui.form.Layout = class Layout {
if (this.is_tabbed_layout()) {
// add a tab without `fieldname` to avoid conflicts
let default_tab = {label: __('Details'), fieldtype: "Tab Break"};
let default_tab = {label: __('Details'), fieldtype: "Tab Break", fieldname: "__details"};
let first_tab = this.fields[1].fieldtype === "Tab Break" ? this.fields[1] : null;
if (!first_tab) {
this.fields.splice(1, 0, default_tab);
@ -336,12 +336,17 @@ frappe.ui.form.Layout = class Layout {
if (visible_tabs && visible_tabs.length == 1) {
visible_tabs[0].parent.toggleClass('hide show');
}
this.set_tab_as_active();
}
set_first_tab_as_active(switched) {
if (this.tabs.length && (switched || !this.frm.active_tab)) {
set_tab_as_active() {
let frm_active_tab = this?.frm.get_active_tab?.();
if (frm_active_tab) {
frm_active_tab.set_active();
} else if (this.tabs.length) {
// set first tab as active when opening for first time, or new doc
this.tabs[0].set_active();
let first_visible_tab = this.tabs.find(tab => !tab.is_hidden());
first_visible_tab && first_visible_tab.set_active();
}
}

View file

@ -10,6 +10,7 @@ export default class Tab {
this.fields_list = [];
this.fields_dict = {};
this.make();
this.setup_listeners();
this.refresh();
}
@ -79,7 +80,6 @@ export default class Tab {
set_active() {
this.parent.find('.nav-link').tab('show');
this.wrapper.addClass('active');
this.frm.active_tab = this;
}
is_active() {
@ -87,7 +87,12 @@ export default class Tab {
}
is_hidden() {
this.wrapper.hasClass('hide')
&& this.parent.hasClass('hide');
return this.wrapper.hasClass('hide');
}
setup_listeners() {
this.parent.find('.nav-link').on('shown.bs.tab', () => {
this?.frm.set_active_tab?.(this);
});
}
}

View file

@ -621,6 +621,8 @@ class FilterArea {
filters = filters.filter(f => !this.exists(f));
// standard filters = filters visible on list view
// non-standard filters = filters set by filter button
const { non_standard_filters, promise } = this.set_standard_filter(
filters
);
@ -680,9 +682,14 @@ class FilterArea {
out.promise = out.promise || Promise.resolve();
out.non_standard_filters = out.non_standard_filters || [];
// set in list view area if filters are present
// don't set like filter on link fields (gets reset)
if (
fields_dict[fieldname] &&
(condition === "=" || condition === "like")
(
condition === "=" ||
(condition === "like" && fields_dict[fieldname]?.df?.fieldtype != "Link")
)
) {
// standard filter
out.promise = out.promise.then(() =>

View file

@ -291,6 +291,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
super.refresh().then(() => {
this.render_header(refresh_header);
this.update_checkbox();
this.update_url_with_filters();
});
}
@ -1454,12 +1455,15 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
on_update() {}
get_share_url() {
update_url_with_filters() {
window.history.replaceState(null, null, this.get_url_with_filters());
}
get_url_with_filters() {
const query_params = this.get_filters_for_args()
.map((filter) => {
filter[3] = encodeURIComponent(filter[3]);
if (filter[2] === "=") {
return `${filter[1]}=${filter[3]}`;
return `${filter[1]}=${encodeURIComponent(filter[3])}`;
}
return [
filter[1],
@ -1469,34 +1473,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
})
.join("&");
let full_url = window.location.href;
let full_url = window.location.href.replace(window.location.search, "");
if (query_params) {
full_url += "?" + query_params;
}
return full_url;
}
share_url() {
const d = new frappe.ui.Dialog({
title: __("Share URL"),
fields: [
{
fieldtype: "Code",
fieldname: "url",
label: "URL",
default: this.get_share_url(),
read_only: 1,
},
],
primary_action_label: __("Copy to clipboard"),
primary_action: () => {
frappe.utils.copy_to_clipboard(this.get_share_url());
d.hide();
},
});
d.show();
}
get_menu_items() {
const doctype = this.doctype;
const items = [];
@ -1561,13 +1544,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
shortcut: "Ctrl+K",
});
items.push({
label: __("Share URL", null, "Button in list view menu"),
action: () => this.share_url(),
standard: true,
shortcut: "Ctrl+L",
});
if (
frappe.user.has_role("System Manager") &&
frappe.boot.developer_mode === 1

View file

@ -121,7 +121,7 @@ frappe.router = {
route = this.get_sub_path_string(route).split('/');
if (!route) return [];
route = $.map(route, this.decode_component);
this.set_route_options_from_url(route);
this.set_route_options_from_url();
return this.convert_to_standard_route(route);
},
@ -410,18 +410,17 @@ frappe.router = {
return route;
},
set_route_options_from_url(route) {
set_route_options_from_url() {
// set query parameters as frappe.route_options
var last_part = route[route.length - 1];
if (last_part.indexOf("?") < last_part.indexOf("=")) {
// has ? followed by =
let parts = last_part.split("?");
let query_string = window.location.search;
// route should not contain string after ?
route[route.length - 1] = parts[0];
if (!frappe.route_options) {
frappe.route_options = {};
}
let query_params = frappe.utils.get_query_params(parts[1]);
frappe.route_options = $.extend(frappe.route_options || {}, query_params);
let params = new URLSearchParams(query_string);
for (const [key, value] of params) {
frappe.route_options[key] = value;
}
},

View file

@ -64,10 +64,6 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
});
}
before_refresh() {
}
setup_page() {
this.hide_sidebar = true;
this.hide_page_form = true;
@ -154,7 +150,10 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
get_card_meta() {
var meta = frappe.get_meta(this.doctype);
// preserve route options erased by new doc
let route_options = {...frappe.route_options};
var doc = frappe.model.get_new_doc(this.doctype);
frappe.route_options = route_options;
var title_field = null;
var quick_entry = false;

View file

@ -57,6 +57,30 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
this.menu_items = [];
}
update_url_with_filters() {
window.history.replaceState(null, null, this.get_url_with_filters());
}
get_url_with_filters() {
const query_params = Object.entries(this.get_filter_values())
.map(([field, value], _idx) => {
// multiselects
if (Array.isArray(value)) {
if (!value.length) return '';
value = JSON.stringify(value);
}
return `${field}=${encodeURIComponent(value)}`;
})
.filter(Boolean)
.join("&");
let full_url = window.location.href.replace(window.location.search, "");
if (query_params) {
full_url += "?" + query_params;
}
return full_url;
}
set_default_secondary_action() {
this.refresh_button && this.refresh_button.remove();
this.refresh_button = this.page.add_action_icon("refresh", () => {
@ -538,7 +562,11 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
const promises = filters_to_set.map(f => {
return () => {
const value = route_options[f.df.fieldname];
let value = route_options[f.df.fieldname];
if (typeof value === 'string' && value[0] === '[') {
// multiselect array
value = JSON.parse(value);
}
f.set_value(value);
};
});
@ -652,6 +680,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
frappe.hide_progress();
}).finally(() => {
this.hide_loading_screen();
this.update_url_with_filters();
});
}

View file

@ -175,7 +175,7 @@ def check_share_permission(doctype, name):
"""Check if the user can share with other users"""
if not frappe.has_permission(doctype, ptype="share", doc=name):
frappe.throw(
_("No permission to {0} {1} {2}").format("share", doctype, name), frappe.PermissionError
_("No permission to {0} {1} {2}").format("share", _(doctype), name), frappe.PermissionError
)
@ -190,7 +190,7 @@ def notify_assignment(shared_by, doctype, doc_name, everyone, notify=0):
reference_user = get_fullname(frappe.session.user)
notification_message = _("{0} shared a document {1} {2} with you").format(
frappe.bold(reference_user), frappe.bold(doctype), get_title_html(title)
frappe.bold(reference_user), frappe.bold(_(doctype)), get_title_html(title)
)
notification_doc = {