feat: sync dashboards from json files
This commit is contained in:
parent
cac7cc402f
commit
3aa3832bd3
12 changed files with 217 additions and 51 deletions
|
|
@ -5,6 +5,20 @@ frappe.ui.form.on('Dashboard', {
|
|||
refresh: function(frm) {
|
||||
frm.add_custom_button(__("Show Dashboard"), () => frappe.set_route('dashboard', frm.doc.name));
|
||||
|
||||
if (!frappe.boot.developer_mode) {
|
||||
frm.disable_form();
|
||||
} else {
|
||||
frm.add_custom_button(__("Export"), () => {
|
||||
frappe.call({
|
||||
method: 'frappe.desk.doctype.dashboard.dashboard.export_dashboard',
|
||||
freeze: true,
|
||||
args: {
|
||||
doc: frm.doc
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
frm.set_query("chart", "charts", function() {
|
||||
return {
|
||||
filters: {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
"field_order": [
|
||||
"dashboard_name",
|
||||
"is_default",
|
||||
"is_standard",
|
||||
"module",
|
||||
"charts",
|
||||
"chart_options",
|
||||
"cards"
|
||||
|
|
@ -35,21 +37,34 @@
|
|||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"description": "Set Default Options for all charts on this Dashboard (Ex: \"colors\": [\"#d1d8dd\", \"#ff5858\"])",
|
||||
"fieldname": "chart_options",
|
||||
"fieldtype": "Code",
|
||||
"label": "Chart Options",
|
||||
"options": "JSON"
|
||||
"description": "Set Default Options for all charts on this Dashboard (Ex: \"colors\": [\"#d1d8dd\", \"#ff5858\"])",
|
||||
"fieldname": "chart_options",
|
||||
"fieldtype": "Code",
|
||||
"label": "Chart Options",
|
||||
"options": "JSON"
|
||||
},
|
||||
{
|
||||
"fieldname": "cards",
|
||||
"fieldtype": "Table",
|
||||
"label": "Cards",
|
||||
"options": "Number Card Link"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_standard",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Standard",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "module",
|
||||
"fieldtype": "Link",
|
||||
"label": "Module",
|
||||
"options": "Module Def"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-04-29 13:26:37.362482",
|
||||
"modified": "2020-05-26 18:31:10.062311",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Dashboard",
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
from frappe.model.document import Document
|
||||
from frappe.modules.export_file import export_to_files
|
||||
import frappe
|
||||
from frappe import _
|
||||
import json
|
||||
from frappe.utils.dashboard import create_filters_file_after_export
|
||||
|
||||
class Dashboard(Document):
|
||||
def on_update(self):
|
||||
|
|
@ -16,6 +18,8 @@ class Dashboard(Document):
|
|||
tabDashboard set is_default = 0 where name != %s''', self.name)
|
||||
|
||||
def validate(self):
|
||||
if not frappe.conf.developer_mode and self.is_standard:
|
||||
frappe.throw('Cannot edit Standard Dashboards')
|
||||
self.validate_custom_options()
|
||||
|
||||
def validate_custom_options(self):
|
||||
|
|
@ -25,6 +29,30 @@ class Dashboard(Document):
|
|||
except ValueError as error:
|
||||
frappe.throw(_("Invalid json added in the custom options: {0}").format(error))
|
||||
|
||||
@frappe.whitelist()
|
||||
def export_dashboard(doc):
|
||||
doc = frappe._dict(frappe.parse_json(doc))
|
||||
card_count = 0
|
||||
chart_count = 0
|
||||
|
||||
if not doc.module:
|
||||
frappe.msgprint(_('Please set Module'))
|
||||
|
||||
if frappe.conf.developer_mode and doc.module:
|
||||
export_to_files(record_list=[['Dashboard', doc.name, doc.module + ' Dashboard']], record_module=doc.module,)
|
||||
record_list = []
|
||||
for chart in doc.charts:
|
||||
record_list.append(['Dashboard Chart', chart.get('chart'), 'Dashboard Charts'])
|
||||
chart_count+=1
|
||||
for card in doc.cards:
|
||||
record_list.append(['Number Card', card.get('card'), 'Number Cards'])
|
||||
card_count+=1
|
||||
|
||||
export_to_files(record_list=record_list, record_module=doc.module)
|
||||
frappe.msgprint(_('Successfully exported <b>{chart_count} Charts</b> and <b>{card_count} Cards</b>').format(chart_count=chart_count, card_count=card_count))
|
||||
|
||||
create_filters_file_after_export(module_name=doc.module.lower(), dashboard_name=doc.name)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_permitted_charts(dashboard_name):
|
||||
permitted_charts = []
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
|
||||
refresh: function(frm) {
|
||||
frm.chart_filters = null;
|
||||
|
||||
if (!frappe.boot.developer_mode && frm.doc.is_standard) {
|
||||
frm.disable_form();
|
||||
}
|
||||
|
||||
frm.add_custom_button('Add Chart to Dashboard', () => {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Add to Dashboard'),
|
||||
|
|
@ -240,11 +245,11 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
show_filters: function(frm) {
|
||||
frm.chart_filters = [];
|
||||
frappe.dashboard_utils.get_filters_for_chart_type(frm.doc).then(filters => {
|
||||
if (filters) {
|
||||
frm.chart_filters = filters;
|
||||
}
|
||||
if (filters) {
|
||||
frm.chart_filters = filters;
|
||||
}
|
||||
|
||||
frm.trigger('render_filters_table');
|
||||
frm.trigger('render_filters_table');
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
"aggregate_function_based_on",
|
||||
"number_of_groups",
|
||||
"column_break_6",
|
||||
"is_standard",
|
||||
"is_public",
|
||||
"heatmap_year",
|
||||
"timespan",
|
||||
|
|
@ -33,9 +34,9 @@
|
|||
"filters_section",
|
||||
"filters_json",
|
||||
"chart_options_section",
|
||||
"color",
|
||||
"column_break_2",
|
||||
"custom_options",
|
||||
"column_break_2",
|
||||
"color",
|
||||
"section_break_10",
|
||||
"last_synced_on"
|
||||
],
|
||||
|
|
@ -235,10 +236,18 @@
|
|||
"fieldname": "heatmap_year",
|
||||
"fieldtype": "Select",
|
||||
"label": "Year"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_standard",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Standard",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-05-16 15:03:02.455395",
|
||||
"modified": "2020-06-04 15:59:52.046492",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Dashboard Chart",
|
||||
|
|
|
|||
|
|
@ -80,7 +80,9 @@ def get(chart_name = None, chart = None, no_cache = None, filters = None, from_d
|
|||
to_date = get_datetime(chart.to_date)
|
||||
|
||||
timegrain = time_interval or chart.time_interval
|
||||
filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json) or []
|
||||
filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json)
|
||||
if not filters:
|
||||
filters = []
|
||||
|
||||
# don't include cancelled documents
|
||||
filters.append([chart.document_type, 'docstatus', '<', 2, False])
|
||||
|
|
@ -349,6 +351,8 @@ class DashboardChart(Document):
|
|||
frappe.cache().delete_key('chart-data:{}'.format(self.name))
|
||||
|
||||
def validate(self):
|
||||
if not frappe.conf.developer_mode and self.is_standard:
|
||||
frappe.throw('Cannot edit Standard charts')
|
||||
if self.chart_type != 'Custom' and self.chart_type != 'Report':
|
||||
self.check_required_field()
|
||||
self.check_document_type()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
frappe.ui.form.on('Number Card', {
|
||||
refresh: function(frm) {
|
||||
if (!frappe.boot.developer_mode && frm.doc.is_standard) {
|
||||
frm.disable_form();
|
||||
}
|
||||
frm.set_df_property("filters_section", "hidden", 1);
|
||||
frm.trigger('set_options');
|
||||
frm.trigger('render_filters_table');
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"aggregate_function_based_on",
|
||||
"column_break_2",
|
||||
"document_type",
|
||||
"is_standard",
|
||||
"is_public",
|
||||
"stats_section",
|
||||
"show_percentage_stats",
|
||||
|
|
@ -95,10 +96,17 @@
|
|||
"fieldname": "stats_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Stats"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_standard",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Standard",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-05-06 19:47:57.753574",
|
||||
"modified": "2020-05-26 17:30:41.248436",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Number Card",
|
||||
|
|
|
|||
|
|
@ -67,6 +67,9 @@ def get_result(doc, to_date=None):
|
|||
|
||||
filters = frappe.parse_json(doc.filters_json)
|
||||
|
||||
if not filters:
|
||||
filters = []
|
||||
|
||||
if to_date:
|
||||
filters.append([doc.document_type, 'creation', '<', to_date, False])
|
||||
|
||||
|
|
|
|||
|
|
@ -12,16 +12,17 @@ def export_doc(doc):
|
|||
|
||||
def export_to_files(record_list=None, record_module=None, verbose=0, create_init=None):
|
||||
"""
|
||||
Export record_list to files. record_list is a list of lists ([doctype],[docname] ) ,
|
||||
Export record_list to files. record_list is a list of lists ([doctype, docname, folder name],) ,
|
||||
"""
|
||||
if frappe.flags.in_import:
|
||||
return
|
||||
|
||||
if record_list:
|
||||
for record in record_list:
|
||||
write_document_file(frappe.get_doc(record[0], record[1]), record_module, create_init=create_init)
|
||||
folder_name = record[2] if len(record) == 3 else None
|
||||
write_document_file(frappe.get_doc(record[0], record[1]), record_module, create_init=create_init, folder_name=folder_name)
|
||||
|
||||
def write_document_file(doc, record_module=None, create_init=True):
|
||||
def write_document_file(doc, record_module=None, create_init=True, folder_name=None):
|
||||
newdoc = doc.as_dict(no_nulls=True)
|
||||
doc.run_method("before_export", newdoc)
|
||||
|
||||
|
|
@ -35,7 +36,10 @@ def write_document_file(doc, record_module=None, create_init=True):
|
|||
module = record_module or get_module_name(doc)
|
||||
|
||||
# create folder
|
||||
folder = create_folder(module, doc.doctype, doc.name, create_init)
|
||||
if folder_name:
|
||||
folder = create_folder(module, folder_name, doc.name, create_init)
|
||||
else:
|
||||
folder = create_folder(module, doc.doctype, doc.name, create_init)
|
||||
|
||||
# write the data file
|
||||
fname = scrub(doc.name)
|
||||
|
|
|
|||
|
|
@ -842,6 +842,15 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
this.page.clear_primary_action();
|
||||
}
|
||||
|
||||
disable_form() {
|
||||
this.set_read_only();
|
||||
this.fields
|
||||
.forEach((field) => {
|
||||
this.set_df_property(field.df.fieldname, "read_only", "1");
|
||||
});
|
||||
this.disable_save();
|
||||
}
|
||||
|
||||
handle_save_fail(btn, on_error) {
|
||||
$(btn).prop('disabled', false);
|
||||
if (on_error) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ from frappe import _
|
|||
from functools import wraps
|
||||
from frappe.utils import add_to_date, cint, get_link_to_form
|
||||
from frappe.modules.import_file import import_doc
|
||||
import os
|
||||
from os.path import isfile, join
|
||||
import ast
|
||||
|
||||
|
||||
def cache_source(function):
|
||||
|
|
@ -74,6 +77,18 @@ def get_from_date_from_timespan(to_date, timespan):
|
|||
return add_to_date(to_date, years=years, months=months, days=days,
|
||||
as_datetime=True)
|
||||
|
||||
def create_filters_file_after_export(module_name, dashboard_name):
|
||||
dashboard_path = frappe.get_module_path(\
|
||||
module_name,\
|
||||
"{module_name}_dashboard".format(module_name=module_name),\
|
||||
"{dashboard}".format(dashboard=dashboard_name)\
|
||||
)
|
||||
charts_path = frappe.get_module_path(module_name, "dashboard charts")
|
||||
create_filters_file(charts_path, dashboard_path, 'dashboard_chart_filters')
|
||||
cards_path = frappe.get_module_path(module_name, "number cards")
|
||||
create_filters_file(cards_path, dashboard_path, 'number_card_filters')
|
||||
|
||||
|
||||
def sync_dashboards(app=None):
|
||||
"""Import, overwrite fixtures from `[app]/fixtures`"""
|
||||
if not cint(frappe.db.get_single_value('System Settings', 'setup_complete')):
|
||||
|
|
@ -86,39 +101,88 @@ def sync_dashboards(app=None):
|
|||
for app_name in apps:
|
||||
print("Updating Dashboard for {app}".format(app=app_name))
|
||||
for module_name in frappe.local.app_modules.get(app_name) or []:
|
||||
config = get_config(app_name, module_name)
|
||||
if config:
|
||||
frappe.flags.in_import = True
|
||||
try:
|
||||
make_records(config.charts, "Dashboard Chart")
|
||||
make_records(config.number_cards, "Number Card")
|
||||
make_records(config.dashboards, "Dashboard")
|
||||
except Exception as e:
|
||||
frappe.log_error(e, _("Dashboard Import Error"))
|
||||
finally:
|
||||
frappe.flags.in_import = False
|
||||
frappe.flags.in_import = True
|
||||
setup_dashboards_from_file(app_name, module_name)
|
||||
frappe.flags.in_import = False
|
||||
|
||||
def make_records(config, doctype):
|
||||
if not config:
|
||||
return
|
||||
def setup_dashboards_from_file(app, module):
|
||||
dashboards_path = frappe.get_module_path(module, "{module}_dashboard".format(module=module))
|
||||
chart_filters = {}
|
||||
card_filters = {}
|
||||
if os.path.isdir(dashboards_path):
|
||||
for fname in os.listdir(dashboards_path):
|
||||
dashboard_path = dashboards_path + '/{}'.format(fname)
|
||||
if os.path.isdir(dashboard_path):
|
||||
if fname == '__pycache__':
|
||||
continue
|
||||
|
||||
# create records for all dashboards in the module
|
||||
make_records(dashboards_path)
|
||||
chart_filters.update(get_filters(app, module, fname, 'dashboard_chart_filters'))
|
||||
card_filters.update(get_filters(app, module, fname, 'number_card_filters'))
|
||||
|
||||
charts_path = frappe.get_module_path(module, "dashboard charts")
|
||||
cards_path = frappe.get_module_path(module, "number cards")
|
||||
make_records(charts_path, chart_filters)
|
||||
make_records(cards_path, card_filters)
|
||||
|
||||
def get_filters(app, module, dashboard, filters_file):
|
||||
module_name = '{app}.{module}.{module}_dashboard.{dashboard}.{filters_file}'.format(
|
||||
app=app,
|
||||
module=module,
|
||||
dashboard=dashboard,
|
||||
filters_file=filters_file
|
||||
)
|
||||
try:
|
||||
for item in config:
|
||||
item["doctype"] = doctype
|
||||
import_doc(item)
|
||||
frappe.db.commit()
|
||||
except frappe.DuplicateEntryError:
|
||||
pass
|
||||
module = frappe.get_module(module_name)
|
||||
filters = getattr(module, 'get_filters')()
|
||||
return filters
|
||||
except ModuleNotFoundError:
|
||||
frappe.throw('No Dashboard filters file created')
|
||||
|
||||
def get_config(app, module):
|
||||
try:
|
||||
module_dashboards = frappe.get_module('{app}.{module}.dashboard_fixtures'.format(app=app, module=module))
|
||||
if hasattr(module_dashboards, 'get_data'):
|
||||
return frappe._dict(module_dashboards.get_data())
|
||||
return None
|
||||
except ImportError:
|
||||
return None
|
||||
except Exception as e:
|
||||
print(_("Failed to import dashboard fixtures for module {module}").format(module=module))
|
||||
frappe.log_error(e, _("Dashboard Fixture Import Error"))
|
||||
return None
|
||||
def create_filters_file(doc_folder_path, dashboard_path, fname):
|
||||
filters_dict = get_filters_dict(doc_folder_path)
|
||||
file_path = '{dashboard_path}/{fname}.py'.format(dashboard_path=dashboard_path, fname=fname)
|
||||
|
||||
with open(file_path, "w") as f:
|
||||
f.write('''import frappe\n
|
||||
def get_filters():\n\treturn\\
|
||||
''')
|
||||
f.write(frappe.as_json(filters_dict, indent='\t'))
|
||||
|
||||
|
||||
def get_filters_dict(path):
|
||||
filters_list = []
|
||||
for fname in os.listdir(path):
|
||||
try:
|
||||
doc_dict = frappe.get_file_json("{path}/{fname}/{fname}.json".format(path=path, fname=fname))
|
||||
doc_name = doc_dict['name']
|
||||
filters = frappe.parse_json(doc_dict.get('filters_json'))
|
||||
if isinstance(filters, list):
|
||||
for f in filters:
|
||||
if len(f) == 5:
|
||||
f[4] = cint(f)
|
||||
doc_filter = '''"{doc_name}": {filters}'''.format(doc_name=doc_name, filters=filters)
|
||||
filters_list.append(doc_filter)
|
||||
except FileNotFoundError:
|
||||
frappe.log_error(message=frappe.get_traceback(), title="Dashboard Import Error")
|
||||
pass
|
||||
|
||||
filters_dict = ast.literal_eval('{' + ', '.join(filters_list) + '}')
|
||||
return filters_dict
|
||||
|
||||
def make_records(path, filters=None):
|
||||
for fname in os.listdir(path):
|
||||
if os.path.isdir(join(path, fname)):
|
||||
if fname == '__pycache__':
|
||||
continue
|
||||
try:
|
||||
doc_dict = frappe.get_file_json("{path}/{fname}/{fname}.json".format(path=path, fname=fname))
|
||||
doc_name = doc_dict['name']
|
||||
doc_dict['is_standard'] = 1
|
||||
if filters:
|
||||
doc_dict['filters_json'] = frappe.as_json(filters[doc_name])
|
||||
import_doc(doc_dict)
|
||||
except FileNotFoundError as e:
|
||||
frappe.log_error(message=frappe.get_traceback(), title="Dashboard Import Error")
|
||||
pass
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue