From d8bfb05ff8251c78f20264049232f001ff87e799 Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 27 Sep 2019 16:11:00 +0530 Subject: [PATCH 1/7] feat(Dashboards): Add group by dashboard chart type --- frappe/core/page/dashboard/dashboard.css | 2 +- frappe/core/page/dashboard/dashboard.js | 33 +- .../dashboard_chart/dashboard_chart.js | 10 + .../dashboard_chart/dashboard_chart.json | 654 ++---------------- .../dashboard_chart/dashboard_chart.py | 117 +++- 5 files changed, 188 insertions(+), 628 deletions(-) diff --git a/frappe/core/page/dashboard/dashboard.css b/frappe/core/page/dashboard/dashboard.css index 3657374c8b..51db4bc78a 100644 --- a/frappe/core/page/dashboard/dashboard.css +++ b/frappe/core/page/dashboard/dashboard.css @@ -17,7 +17,7 @@ font-weight: bold; } -.chart-loading-state { +.chart-loading-state, .chart-empty-state { height: 100%; margin-top: 160px; text-align: center; diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard/dashboard.js index 6b36908260..ba9dbc6d11 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard/dashboard.js @@ -226,23 +226,26 @@ class DashboardChart { "Line": "line", "Bar": "bar", }; - let chart_args = { - title: this.chart_doc.chart_name, - data: this.data, - type: chart_type_map[this.chart_doc.type], - truncateLegends: 1, - colors: [this.chart_doc.color || "light-blue"], - axisOptions: { - xIsSeries: this.chart_doc.timeseries, - shortenYAxisNumbers: 1 - } - }; - this.chart_container.find('.chart-loading-state').addClass('hide'); - if(!this.chart) { - this.chart = new frappe.Chart(this.chart_container.find(".chart-wrapper")[0], chart_args); + this.chart_container.find('.chart-loading-state').addClass('hide'); + if (!this.data) { + this.chart_container.find('.chart-empty-state').removeClass('hide'); } else { - this.chart.update(this.data); + let chart_args = { + title: this.chart_doc.chart_name, + data: this.data, + type: chart_type_map[this.chart_doc.type], + colors: [this.chart_doc.color || "light-blue"], + axisOptions: { + xIsSeries: this.chart_doc.timeseries, + shortenYAxisNumbers: 1 + } + }; + if(!this.chart) { + this.chart = new frappe.Chart(this.chart_container.find(".chart-wrapper")[0], chart_args); + } else { + this.chart.update(this.data); + } } } diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index f42faea0e5..7be2b0fa41 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -23,6 +23,8 @@ frappe.ui.form.on('Dashboard Chart', { // set timeseries based on chart type if (['Count', 'Average', 'Sum'].includes(frm.doc.chart_type)) { frm.set_value('timeseries', 1); + } else { + frm.set_value('timeseries', 0); } frm.set_value('document_type', ''); }, @@ -55,10 +57,14 @@ frappe.ui.form.on('Dashboard Chart', { {label: __('Last Modified On'), value: 'modified'} ]; let value_fields = []; + let group_by_fields = []; + let aggregate_function_fields = []; let update_form = function() { // update select options frm.set_df_property('based_on', 'options', date_fields); frm.set_df_property('value_based_on', 'options', value_fields); + frm.set_df_property('group_by_based_on', 'options', group_by_fields); + frm.set_df_property('aggregate_function_based_on', 'options', aggregate_function_fields); frm.trigger("show_filters"); } @@ -72,6 +78,10 @@ frappe.ui.form.on('Dashboard Chart', { } if (['Int', 'Float', 'Currency', 'Percent'].includes(df.fieldtype)) { value_fields.push({label: df.label, value: df.fieldname}); + aggregate_function_fields.push({label: df.label, value: df.fieldname}); + } + if(['Link', 'Select'].includes(df.fieldtype)) { + group_by_fields.push({label: df.label, value: df.fieldname}); } }); update_form(); diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json index 1f6941c955..07460a1439 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -1,703 +1,197 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, "allow_rename": 1, "autoname": "field:chart_name", - "beta": 0, "creation": "2019-01-10 12:28:06.282875", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "chart_name", + "chart_type", + "source", + "document_type", + "based_on", + "value_based_on", + "group_by_type", + "group_by_based_on", + "aggregate_function_based_on", + "number_of_groups", + "column_break_6", + "timespan", + "time_interval", + "timeseries", + "filters_section", + "filters_json", + "chart_options_section", + "type", + "width", + "column_break_2", + "color", + "section_break_10", + "last_synced_on" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "chart_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Chart Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, "unique": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "chart_type", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Chart Type", - "length": 0, - "no_copy": 0, - "options": "Count\nSum\nAverage\nCustom", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Count\nSum\nAverage\nCustom\nGroup By" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.chart_type === 'Custom'", - "fetch_if_empty": 0, "fieldname": "source", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Chart Source", - "length": 0, - "no_copy": 0, - "options": "Dashboard Chart Source", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Dashboard Chart Source" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval: doc.chart_type !== 'Custom'", - "fetch_if_empty": 0, "fieldname": "document_type", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Document Type", - "length": 0, - "no_copy": 0, - "options": "DocType", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "DocType" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval: doc.chart_type !== 'Custom'", - "fetch_if_empty": 0, + "depends_on": "eval: ['Count', 'Sum', 'Average'].includes(doc.chart_type)", "fieldname": "based_on", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Time Series Based On", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Time Series Based On" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval: ['Sum', 'Average'].includes(doc.chart_type)", - "fetch_if_empty": 0, + "depends_on": "eval: ['Sum', 'Average'].includes(doc.chart_type)\n", "fieldname": "value_based_on", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Value Based On", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Value Based On" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "column_break_6", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "timeseries", - "fetch_if_empty": 0, "fieldname": "timespan", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Timespan", - "length": 0, - "no_copy": 0, - "options": "Last Year\nLast Quarter\nLast Month\nLast Week", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Last Year\nLast Quarter\nLast Month\nLast Week" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "timeseries", - "fetch_if_empty": 0, "fieldname": "time_interval", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Time Interval", - "length": 0, - "no_copy": 0, - "options": "Quarterly\nMonthly\nWeekly\nDaily", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Quarterly\nMonthly\nWeekly\nDaily" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "timeseries", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Time Series", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Time Series" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "filters_section", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Filters", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Filters" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "filters_json", "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Filters JSON", - "length": 0, - "no_copy": 0, "options": "JSON", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "chart_options_section", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Chart Options", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Chart Options" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "type", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Type", - "length": 0, - "no_copy": 0, "options": "Line\nBar", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "width", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Width", - "length": 0, - "no_copy": 0, "options": "Half\nFull", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "color", "fieldtype": "Color", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Color", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Color" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "section_break_10", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "last_synced_on", "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Last Synced On", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 + }, + { + "depends_on": "eval:doc.chart_type === 'Group By'", + "fieldname": "group_by_based_on", + "fieldtype": "Select", + "label": "Group By Based On" + }, + { + "default": "Count", + "depends_on": "eval:doc.chart_type === 'Group By'", + "fieldname": "group_by_type", + "fieldtype": "Select", + "label": "Group By Type", + "options": "Count\nSum\nAverage" + }, + { + "depends_on": "eval: ['Sum', 'Average'].includes(doc.group_by_type)", + "fieldname": "aggregate_function_based_on", + "fieldtype": "Select", + "label": "Aggregate Function Based On" + }, + { + "depends_on": "eval:doc.chart_type === 'Group By'", + "fieldname": "number_of_groups", + "fieldtype": "Int", + "label": "Number of Groups" } ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-04-09 15:04:02.434131", + "modified": "2019-09-27 15:34:05.593069", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard Chart", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "title_field": "", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index bf8d3020a5..0d7ea31a62 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -32,50 +32,96 @@ def get(chart_name = None, chart = None, no_cache = None, from_date=None, to_dat # get conditions from filters conditions, values = frappe.db.build_conditions(filters) + if chart.chart_type == 'Group By': + chart_config = get_group_by_chart_config(chart, filters) + else: + print('conditions', conditions) + # query will return year, unit and aggregate value + data = frappe.db.sql(''' + select + extract(year from {datefield}) as _year, + {unit_function} as _unit, + {aggregate_function}({value_field}) + from `tab{doctype}` + where + {conditions} + and {datefield} >= '{from_date}' + and {datefield} <= '{to_date}' + group by _year, _unit + order by _year asc, _unit asc + '''.format( + unit_function = get_unit_function(chart.based_on, timegrain), + datefield = chart.based_on, + aggregate_function = get_aggregate_function(chart.chart_type), + value_field = chart.value_based_on or '1', + doctype = chart.document_type, + conditions = conditions, + from_date = from_date.strftime('%Y-%m-%d'), + to_date = to_date + ), values) - # query will return year, unit and aggregate value + # result given as year, unit -> convert it to end of period of that unit + result = convert_to_dates(data, timegrain) + + # add missing data points for periods where there was no result + result = add_missing_values(result, timegrain, from_date, to_date) + + chart_config = { + "labels": [formatdate(r[0].strftime('%Y-%m-%d')) for r in result], + "datasets": [{ + "name": chart.name, + "values": [r[1] for r in result] + }] + } + + return chart_config + + +def get_group_by_chart_config(chart, filters): + conditions, values = frappe.db.build_conditions(filters) data = frappe.db.sql(''' select - extract(year from {datefield}) as _year, - {unit_function} as _unit, - {aggregate_function}({value_field}) + {aggregate_function}({value_field}) as count, + {group_by_field} as name from `tab{doctype}` - where - {conditions} - and {datefield} >= '{from_date}' - and {datefield} <= '{to_date}' - group by _year, _unit - order by _year asc, _unit asc + where {conditions} + group by {group_by_field} + order by count desc '''.format( - unit_function = get_unit_function(chart.based_on, timegrain), - datefield = chart.based_on, - aggregate_function = get_aggregate_function(chart.chart_type), - value_field = chart.value_based_on or '1', + aggregate_function = get_aggregate_function(chart.group_by_type), + value_field = chart.aggregate_function_based_on or '1', + field = chart.aggregate_function_based_on or chart.group_by_based_on, + group_by_field = chart.group_by_based_on, doctype = chart.document_type, conditions = conditions, - from_date = from_date.strftime('%Y-%m-%d'), - to_date = to_date - ), values) + ), values, as_dict = True) - # result given as year, unit -> convert it to end of period of that unit - result = convert_to_dates(data, timegrain) + print('data', data) + if data: + if chart.number_of_groups and chart.number_of_groups < len(data): + other_count = 0 + for i in range(chart.number_of_groups - 1, len(data)): + other_count += data[i]['count'] + data = data[0: chart.number_of_groups - 1] + data.append({'name': 'Other', 'count': other_count}) - # add missing data points for periods where there was no result - result = add_missing_values(result, timegrain, from_date, to_date) + chart_config = { + "labels": [item['name'] if item['name'] else 'Not Specified' for item in data], + "datasets": [{ + "name": chart.name, + "values": [item['count'] for item in data] + }] + } + return chart_config + else: + return None - return { - "labels": [formatdate(r[0].strftime('%Y-%m-%d')) for r in result], - "datasets": [{ - "name": chart.name, - "values": [r[1] for r in result] - }] - } def get_aggregate_function(chart_type): return { "Sum": "SUM", "Count": "COUNT", - "Average": "AVG" + "Average": "AVG", }[chart_type] def convert_to_dates(data, timegrain): @@ -210,7 +256,14 @@ class DashboardChart(Document): self.check_required_field() def check_required_field(self): - if not self.based_on: - frappe.throw(_("Time series based on is required to create a dashboard chart")) if not self.document_type: - frappe.throw(_("Document type is required to create a dashboard chart")) + frappe.throw(_("Document type is required to create a dashboard chart")) + + if self.chart_type == 'Group By': + if not self.group_by_based_on: + frappe.throw(_("Group By field is required to create a dashboard chart")) + if self.group_by_type in ['Sum', 'Average'] and not self.aggregate_function_based_on: + frappe.throw(_("Aggregate Function field is required to create a dashboard chart")) + else: + if not self.based_on: + frappe.throw(_("Time series based on is required to create a dashboard chart")) From fda597c8f24e24056f5ced2c89127a1a14de232d Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 11 Oct 2019 15:54:01 +0530 Subject: [PATCH 2/7] fix: codacy --- frappe/core/page/dashboard/dashboard.js | 2 +- frappe/desk/doctype/dashboard_chart/dashboard_chart.js | 2 +- frappe/desk/doctype/dashboard_chart/dashboard_chart.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard/dashboard.js index ba9dbc6d11..54e8bddfd5 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard/dashboard.js @@ -241,7 +241,7 @@ class DashboardChart { shortenYAxisNumbers: 1 } }; - if(!this.chart) { + if (!this.chart) { this.chart = new frappe.Chart(this.chart_container.find(".chart-wrapper")[0], chart_args); } else { this.chart.update(this.data); diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index 7be2b0fa41..42f1245901 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -80,7 +80,7 @@ frappe.ui.form.on('Dashboard Chart', { value_fields.push({label: df.label, value: df.fieldname}); aggregate_function_fields.push({label: df.label, value: df.fieldname}); } - if(['Link', 'Select'].includes(df.fieldtype)) { + if (['Link', 'Select'].includes(df.fieldtype)) { group_by_fields.push({label: df.label, value: df.fieldname}); } }); diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 0d7ea31a62..9e2fed8150 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -110,7 +110,7 @@ def get_group_by_chart_config(chart, filters): "datasets": [{ "name": chart.name, "values": [item['count'] for item in data] - }] + }] } return chart_config else: From 064a19870ecf142ddfb1aaf003a809800a840a36 Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 11 Oct 2019 16:12:31 +0530 Subject: [PATCH 3/7] fix: hide timeseries checkbox for group by chart type --- frappe/desk/doctype/dashboard_chart/dashboard_chart.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json index 07460a1439..2e5540536f 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -91,6 +91,7 @@ }, { "default": "0", + "depends_on": "eval:doc.chart_type !== 'Group By'", "fieldname": "timeseries", "fieldtype": "Check", "label": "Time Series" @@ -172,7 +173,7 @@ "label": "Number of Groups" } ], - "modified": "2019-09-27 15:34:05.593069", + "modified": "2019-10-11 16:11:17.051669", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard Chart", From 558630486cc290b05c5eb50b8a61591250aad52c Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 29 Oct 2019 01:34:02 +0530 Subject: [PATCH 4/7] fix: make separate get_group_by_chart_config function --- .../dashboard_chart/dashboard_chart.py | 82 ++++++++++--------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 9e2fed8150..946a866c0d 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -25,6 +25,15 @@ def get(chart_name = None, chart = None, no_cache = None, from_date=None, to_dat # don't include cancelled documents filters['docstatus'] = ('<', 2) + if chart.chart_type == 'Group By': + chart_config = get_group_by_chart_config(chart, filters) + else: + chart_config = get_chart_config(chart, filters, timespan, timegrain, from_date, to_date) + + return chart_config + + +def get_chart_config(chart, filters, timespan, timegrain, from_date, to_date): if not from_date: from_date = get_from_date_from_timespan(to_date, timespan) if not to_date: @@ -32,47 +41,43 @@ def get(chart_name = None, chart = None, no_cache = None, from_date=None, to_dat # get conditions from filters conditions, values = frappe.db.build_conditions(filters) - if chart.chart_type == 'Group By': - chart_config = get_group_by_chart_config(chart, filters) - else: - print('conditions', conditions) - # query will return year, unit and aggregate value - data = frappe.db.sql(''' - select - extract(year from {datefield}) as _year, - {unit_function} as _unit, - {aggregate_function}({value_field}) - from `tab{doctype}` - where - {conditions} - and {datefield} >= '{from_date}' - and {datefield} <= '{to_date}' - group by _year, _unit - order by _year asc, _unit asc - '''.format( - unit_function = get_unit_function(chart.based_on, timegrain), - datefield = chart.based_on, - aggregate_function = get_aggregate_function(chart.chart_type), - value_field = chart.value_based_on or '1', - doctype = chart.document_type, - conditions = conditions, - from_date = from_date.strftime('%Y-%m-%d'), - to_date = to_date - ), values) + # query will return year, unit and aggregate value + data = frappe.db.sql(''' + select + extract(year from {datefield}) as _year, + {unit_function} as _unit, + {aggregate_function}({value_field}) + from `tab{doctype}` + where + {conditions} + and {datefield} >= '{from_date}' + and {datefield} <= '{to_date}' + group by _year, _unit + order by _year asc, _unit asc + '''.format( + unit_function = get_unit_function(chart.based_on, timegrain), + datefield = chart.based_on, + aggregate_function = get_aggregate_function(chart.chart_type), + value_field = chart.value_based_on or '1', + doctype = chart.document_type, + conditions = conditions, + from_date = from_date.strftime('%Y-%m-%d'), + to_date = to_date + ), values) - # result given as year, unit -> convert it to end of period of that unit - result = convert_to_dates(data, timegrain) + # result given as year, unit -> convert it to end of period of that unit + result = convert_to_dates(data, timegrain) - # add missing data points for periods where there was no result - result = add_missing_values(result, timegrain, from_date, to_date) + # add missing data points for periods where there was no result + result = add_missing_values(result, timegrain, from_date, to_date) - chart_config = { - "labels": [formatdate(r[0].strftime('%Y-%m-%d')) for r in result], - "datasets": [{ - "name": chart.name, - "values": [r[1] for r in result] - }] - } + chart_config = { + "labels": [formatdate(r[0].strftime('%Y-%m-%d')) for r in result], + "datasets": [{ + "name": chart.name, + "values": [r[1] for r in result] + }] + } return chart_config @@ -96,7 +101,6 @@ def get_group_by_chart_config(chart, filters): conditions = conditions, ), values, as_dict = True) - print('data', data) if data: if chart.number_of_groups and chart.number_of_groups < len(data): other_count = 0 From 08da73cddf741358ee0b99d17caece6ae84bc103 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 29 Oct 2019 02:27:39 +0530 Subject: [PATCH 5/7] feat: allow date range to be selected as timespan --- .../doctype/dashboard_chart/dashboard_chart.js | 1 + .../dashboard_chart/dashboard_chart.json | 18 ++++++++++++++++-- .../doctype/dashboard_chart/dashboard_chart.py | 4 +++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index 42f1245901..99ba49bc4f 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -40,6 +40,7 @@ frappe.ui.form.on('Dashboard Chart', { timespan: function(frm) { const time_interval_options = { + "Select Date Range": ["Quarterly", "Monthly", "Weekly", "Daily"], "Last Year": ["Quarterly", "Monthly", "Weekly", "Daily"], "Last Quarter": ["Monthly", "Weekly", "Daily"], "Last Month": ["Weekly", "Daily"], diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json index 2e5540536f..200e191ae1 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -18,6 +18,8 @@ "number_of_groups", "column_break_6", "timespan", + "from_date", + "to_date", "time_interval", "timeseries", "filters_section", @@ -80,7 +82,7 @@ "fieldname": "timespan", "fieldtype": "Select", "label": "Timespan", - "options": "Last Year\nLast Quarter\nLast Month\nLast Week" + "options": "Last Year\nLast Quarter\nLast Month\nLast Week\nSelect Date Range" }, { "depends_on": "timeseries", @@ -171,9 +173,21 @@ "fieldname": "number_of_groups", "fieldtype": "Int", "label": "Number of Groups" + }, + { + "depends_on": "eval:doc.timespan === 'Choose Time Span'", + "fieldname": "from_date", + "fieldtype": "Date", + "label": "From Date" + }, + { + "depends_on": "eval:doc.timespan === 'Choose Time Span'", + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date" } ], - "modified": "2019-10-11 16:11:17.051669", + "modified": "2019-10-29 02:26:14.838057", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard Chart", diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 946a866c0d..845eaa9055 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -12,13 +12,15 @@ from frappe.model.document import Document @frappe.whitelist() @cache_source -def get(chart_name = None, chart = None, no_cache = None, from_date=None, to_date=None, refresh = None): +def get(chart_name = None, chart = None, no_cache = None, from_date = None, to_date = None, refresh = None): if chart_name: chart = frappe.get_doc('Dashboard Chart', chart_name) else: chart = frappe._dict(frappe.parse_json(chart)) timespan = chart.timespan + from_date = chart.from_date + to_date = chart.to_date timegrain = chart.time_interval filters = frappe.parse_json(chart.filters_json) From 2393096761c71b84414b22a96f3e514baadd54af Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 29 Oct 2019 16:49:54 +0530 Subject: [PATCH 6/7] test: add test case for group by chart type --- .../dashboard_chart/test_dashboard_chart.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py index 35e468fc3c..e71b9476c9 100644 --- a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py @@ -129,3 +129,25 @@ class TestDashboardChart(unittest.TestCase): self.assertEqual(result.get('datasets')[0].get('values')[2], 0) frappe.db.rollback() + + def test_group_by_chart_type(self): + if frappe.db.exists('Dashboard Chart', 'Test Group By Dashboard Chart'): + frappe.delete_doc('Dashboard Chart', 'Test Group By Dashboard Chart') + + frappe.get_doc({"doctype":"ToDo", "description": "test"}).insert() + + frappe.get_doc(dict( + doctype = 'Dashboard Chart', + chart_name = 'Test Group By Dashboard Chart', + chart_type = 'Group By', + document_type = 'ToDo', + group_by_based_on = 'status', + filters_json = '{}', + )).insert() + + result = get(chart_name ='Test Group By Dashboard Chart', refresh = 1) + todo_status_count = frappe.db.count('ToDo', {'status': result.get('labels')[0]}) + + self.assertEqual(result.get('datasets')[0].get('values')[0], todo_status_count) + + frappe.db.rollback() \ No newline at end of file From 05a845547a3a81f67ed1d55e4fc449ad9cde8320 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 30 Oct 2019 17:25:12 +0530 Subject: [PATCH 7/7] fix: codacy --- frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py index e71b9476c9..fc74448d10 100644 --- a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py @@ -133,7 +133,7 @@ class TestDashboardChart(unittest.TestCase): def test_group_by_chart_type(self): if frappe.db.exists('Dashboard Chart', 'Test Group By Dashboard Chart'): frappe.delete_doc('Dashboard Chart', 'Test Group By Dashboard Chart') - + frappe.get_doc({"doctype":"ToDo", "description": "test"}).insert() frappe.get_doc(dict(