diff --git a/frappe/__init__.py b/frappe/__init__.py index 1d09bcbfe8..79c9f000ca 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -17,7 +17,7 @@ from faker import Faker from .exceptions import * from .utils.jinja import (get_jenv, get_template, render_template, get_email_from_template, get_jloader) -__version__ = '10.1.65' +__version__ = '10.1.67' __title__ = "Frappe Framework" local = Local() diff --git a/frappe/core/doctype/prepared_report/prepared_report.js b/frappe/core/doctype/prepared_report/prepared_report.js index 002e069874..6a7cf2728c 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.js +++ b/frappe/core/doctype/prepared_report/prepared_report.js @@ -15,7 +15,7 @@ frappe.ui.form.on('Prepared Report', {
`); - const filters = JSON.parse(JSON.parse(frm.doc.filters)); + const filters = JSON.parse(frm.doc.filters); Object.keys(filters).forEach(key => { const filter_row = $(`${task._start.format('MMM D')} - ${task._end.format('MMM D')}
`; +${moment(task._start).format('MMM D')} - ${moment(task._end).format('MMM D')}
`; // custom html in doctype settings var custom = me.settings.gantt_custom_popup_html; @@ -207,3 +207,4 @@ frappe.views.GanttView = class GanttView extends frappe.views.ListView { ]; } }; + diff --git a/frappe/public/js/frappe/views/kanban/kanban_view.js b/frappe/public/js/frappe/views/kanban/kanban_view.js index 7b163baf97..74dc283326 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_view.js +++ b/frappe/public/js/frappe/views/kanban/kanban_view.js @@ -32,10 +32,14 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { this.page_title = this.board_name; this.card_meta = this.get_card_meta(); - return this.get_board() - .then(() => { - this.filters = this.board.filters_array; - }); + this.menu_items.push({ + label: __('Save filters'), + action: () => { + this.save_kanban_board_filters(); + } + }); + + return this.get_board(); } get_board() { @@ -43,9 +47,14 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { .then(board => { this.board = board; this.board.filters_array = JSON.parse(this.board.filters || '[]'); + this.filters = this.board.filters_array; }); } + before_refresh() { + + } + setup_view() { } @@ -60,13 +69,40 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { this.save_view_user_settings({ last_kanban_board: this.board_name }); + } + + on_filter_change() { + if (JSON.stringify(this.board.filters_array) !== JSON.stringify(this.filter_area.get())) { + this.page.set_indicator(__('Not Saved'), 'orange'); + } else { + this.page.clear_indicator(); + } + } + + save_kanban_board_filters() { + const filters = this.filter_area.get(); frappe.call({ method: 'frappe.desk.doctype.kanban_board.kanban_board.save_filters', args: { board_name: this.board_name, - filters: this.filter_area.get() + filters: filters } + }).then(r => { + if (r.exc) { + frappe.show_alert({ + indicator: 'red', + message: __('There was an error saving filters') + }); + return; + } + frappe.show_alert({ + indicator: 'green', + message: __('Filters saved') + }); + + this.board.filters_array = filters; + this.on_filter_change(); }); } diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 984699f3ad..4fc6d4da69 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -63,9 +63,17 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { setup_events() { frappe.realtime.on("report_generated", (data) => { if(data.report_name) { - let alert_message = `Report ${this.report_name} generated. - View`; - frappe.show_alert({message: alert_message, indicator: 'orange'}); + this.prepared_report_action = "Rebuild"; + // If generated report and currently active Prepared Report has same fiters + // then refresh the Prepared Report + // Otherwise show alert with the link to the Prepared Report + if(data.name == this.prepared_report_doc_name) { + this.refresh(); + } else { + let alert_message = `Report ${this.report_name} generated. + View`; + frappe.show_alert({message: alert_message, indicator: 'orange'}); + } } }); } @@ -93,6 +101,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.page_title = __(this.report_name); this.menu_items = this.get_menu_items(); this.datatable = null; + this.prepared_report_action = "New"; frappe.run_serially([ () => this.get_report_doc(), @@ -266,8 +275,22 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.hide_status(); - if (data.prepared_report){ + if (data.prepared_report) { this.prepared_report = true; + const query_string = frappe.utils.get_query_string(frappe.get_route_str()); + const query_params = frappe.utils.get_query_params(query_string); + // If query_string contains prepared_report_name then set filters + // to match the mentioned prepared report doc and disable editing + if(query_params.prepared_report_name) { + this.prepared_report_action = "Edit"; + const filters_from_report = JSON.parse(data.doc.filters); + Object.values(this.filters).forEach(function(field) { + if (filters_from_report[field.fieldname]) { + field.set_input(filters_from_report[field.fieldname]); + } + field.input.disabled = true; + }); + } this.add_prepared_report_buttons(data.doc); } this.toggle_message(false); @@ -297,19 +320,43 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { +"dn="+encodeURIComponent(doc.name))); }); - this.show_status(__(` - This report was generated - on ${frappe.datetime.convert_to_user_tz(doc.report_end_time)}. - See all past reports. - `)); + const part1 = __('This report was generated {0}.', [frappe.datetime.comment_when(doc.report_end_time)]); + const part2 = __('To get the updated report, click on {0}.', [__('Rebuild')]); + const part3 = __('See all past reports.'); + + this.show_status(` + + ${part1} + ${part2} + ${part3} + + `); }; - // if - - this.page.set_primary_action( - __("Generate New Report"), - this.generate_background_report.bind(this) - ); + // Three cases + // 1. First time with given filters, no data. + // 2. Showing data from specific report + // 3. Showing data from an old report without specific report name + if(this.prepared_report_action == "New") { + this.page.set_primary_action( + __("Generate New Report"), + () => { + this.generate_background_report(); + } + ); + } else if(this.prepared_report_action == "Edit") { + this.page.set_primary_action( + __("Edit"), + () => { + frappe.set_route(frappe.get_route()); + } + ); + } else if(this.prepared_report_action == "Rebuild"){ + this.page.set_primary_action( + __("Rebuild"), + this.generate_background_report.bind(this) + ); + } } generate_background_report() { @@ -327,6 +374,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { callback: resolve })).then(r => { const data = r.message; + // Rememeber the name of Prepared Report doc + this.prepared_report_doc_name = data.name; let alert_message = `Report initiated. You can track its status here`; frappe.show_alert({message: alert_message, indicator: 'orange'}); @@ -347,23 +396,25 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { if (this.datatable) { this.datatable.options.treeView = this.tree_report; this.datatable.refresh(this.data, this.columns); - return; + } else { + let datatable_options = { + columns: this.columns, + data: this.data, + inlineFilters: true, + treeView: this.tree_report, + layout: 'fixed', + cellHeight: 33 + }; + + if (this.report_settings.get_datatable_options) { + datatable_options = this.report_settings.get_datatable_options(datatable_options); + } + this.datatable = new DataTable(this.$report[0], datatable_options); } - let datatable_options = { - columns: this.columns, - data: this.data, - inlineFilters: true, - treeView: this.tree_report, - layout: 'fixed', - cellHeight: 33 - }; - - if (this.report_settings.get_datatable_options) { - datatable_options = this.report_settings.get_datatable_options(datatable_options); + if (this.report_settings.after_datatable_render) { + this.report_settings.after_datatable_render(this.datatable); } - - this.datatable = new DataTable(this.$report[0], datatable_options); } get_chart_options(data) { @@ -896,6 +947,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { reset_report_view() { this.hide_status(); this.toggle_nothing_to_show(true); + this.refresh(); } toggle_loading(flag) { @@ -910,6 +962,10 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { Please set the appropriate filters and then generate a new one.`); } this.toggle_message(flag, message); + if(flag){ + this.prepared_report_action = "New"; + } + this.add_prepared_report_buttons(); } toggle_message(flag, message) { diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 02f617e141..67f4180965 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -121,6 +121,16 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { } } + on_filter_change() { + if (this.report_doc) { + if (JSON.stringify(this.filters) !== JSON.stringify(this.filter_area.get())) { + this.page.set_indicator(__('Not Saved'), 'orange'); + } else { + this.page.clear_indicator(); + } + } + } + update_row(doc, flash_row) { const to_refresh = []; @@ -899,6 +909,10 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { if(r.message != this.report_name) { frappe.set_route('List', this.doctype, 'Report', r.message); } + + // reset dirty state + this.filters = this.filter_area.get(); + this.on_filter_change(); } }); diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 403353ee59..b3b46696bb 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -14,6 +14,8 @@ from email.utils import parseaddr, formataddr from frappe.utils.data import * from six.moves.urllib.parse import quote from six import text_type, string_types +import io +from gzip import GzipFile default_fields = ['doctype', 'name', 'owner', 'creation', 'modified', 'modified_by', 'parent', 'parentfield', 'parenttype', 'idx', 'docstatus'] @@ -620,3 +622,21 @@ def call(fn, *args, **kwargs): bench --site erpnext.local execute frappe.utils.call --args '''["frappe.get_all", "Activity Log"]''' --kwargs '''{"fields": ["user", "creation", "full_name"], "filters":{"Operation": "Login", "Status": "Success"}, "limit": "10"}''' """ return json.loads(frappe.as_json(frappe.call(fn, *args, **kwargs))) + +# Following methods are aken as-is from Python 3 codebase +# since gzip.compress and gzip.decompress are not available in Python 2.7 +def gzip_compress(data, compresslevel=9): + """Compress data in one shot and return the compressed string. + Optional argument is the compression level, in range of 0-9. + """ + buf = io.BytesIO() + with GzipFile(fileobj=buf, mode='wb', compresslevel=compresslevel) as f: + f.write(data) + return buf.getvalue() + +def gzip_decompress(data): + """Decompress a gzip compressed string in one shot. + Return the decompressed string. + """ + with GzipFile(fileobj=io.BytesIO(data)) as f: + return f.read() diff --git a/frappe/website/render.py b/frappe/website/render.py index 16c18b7c5f..a5006abae7 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -173,9 +173,6 @@ def build_page(path): frappe.local.path = path context = get_context(path) - if context.title and "{{" in cstr(context.title): - title_template = context.pop('title') - context.title = frappe.render_template(title_template, context) if context.source: html = frappe.render_template(context.source, context) diff --git a/package.json b/package.json index 752cceae83..eed4c72e05 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "awesomplete": "^1.1.2", "cookie": "^0.3.1", "express": "^4.16.2", - "frappe-datatable": "^1.5.5", + "frappe-datatable": "^1.6.1", "frappe-gantt": "^0.1.0", "fuse.js": "^3.2.0", "highlight.js": "^9.12.0", diff --git a/yarn.lock b/yarn.lock index 48ddcbac93..3475604959 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1219,10 +1219,10 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= -frappe-datatable@^1.5.5: - version "1.5.5" - resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.5.5.tgz#52c4e4263b2598d1685cdfc72dff248365091868" - integrity sha512-0Euo4otAzkpm1S+NrvhYHQ+Bug0aPus/k2W5FvyCXdRyKyDu5aSYAku+Xquj2mhES/EJGhyfhcuTcGLt0Q1h7g== +frappe-datatable@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.6.1.tgz#e57850923b5f307fd02328c522c5abe35a17dd89" + integrity sha512-u2l4I2Pwu4jTLSBF7vW7EDwzcRdfrlKbgaUCyhDUYlOhWl0sRt6rO2ZmIhU7znSYz7TNkKkAt7hkI+x+Mg6ONw== dependencies: hyperlist "^1.0.0-beta" lodash "^4.17.5"