diff --git a/frappe/core/report/prepared_report_analytics/__init__.py b/frappe/core/report/prepared_report_analytics/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/report/prepared_report_analytics/prepared_report_analytics.js b/frappe/core/report/prepared_report_analytics/prepared_report_analytics.js new file mode 100644 index 0000000000..d659f77e59 --- /dev/null +++ b/frappe/core/report/prepared_report_analytics/prepared_report_analytics.js @@ -0,0 +1,24 @@ +// Copyright (c) 2024, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.query_reports["Prepared Report Analytics"] = { + filters: [ + { + fieldname: "report", + label: __("Report"), + fieldtype: "Data", + }, + { + fieldname: "top_10", + label: __("Top 10"), + fieldtype: "Check", + default: 0, + }, + { + fieldname: "in_minutes", + label: __("In Minutes"), + fieldtype: "Check", + default: 0, + }, + ], +}; diff --git a/frappe/core/report/prepared_report_analytics/prepared_report_analytics.json b/frappe/core/report/prepared_report_analytics/prepared_report_analytics.json new file mode 100644 index 0000000000..49e0cbc262 --- /dev/null +++ b/frappe/core/report/prepared_report_analytics/prepared_report_analytics.json @@ -0,0 +1,27 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2024-12-18 11:58:00.693755", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "letterhead": null, + "modified": "2025-02-07 17:27:53.441631", + "modified_by": "Administrator", + "module": "Core", + "name": "Prepared Report Analytics", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Prepared Report", + "report_name": "Prepared Report Analytics", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + } + ], + "timeout": 0 +} \ No newline at end of file diff --git a/frappe/core/report/prepared_report_analytics/prepared_report_analytics.py b/frappe/core/report/prepared_report_analytics/prepared_report_analytics.py new file mode 100644 index 0000000000..b7f564e2fe --- /dev/null +++ b/frappe/core/report/prepared_report_analytics/prepared_report_analytics.py @@ -0,0 +1,106 @@ +# Copyright (c) 2024, Frappe Technologies and contributors +# For license information, please see license.txt + +from pypika import Order + +import frappe +from frappe import _, qb +from frappe.query_builder import Criterion +from frappe.utils import add_months, nowdate + + +def execute(filters: dict | None = None): + """Return columns and data for the report. + + This is the main entry point for the report. It accepts the filters as a + dictionary and should return columns and data. It is called by the framework + every time the report is refreshed or a filter is updated. + """ + columns = get_columns(filters) + data = get_data(filters=filters) + + return columns, data + + +def get_columns(filters) -> list[dict]: + """Return columns for the report. + + One field definition per column, just like a DocType field definition. + """ + return [ + { + "label": _("Prepared Report"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Prepared Report", + "width": 250, + }, + { + "label": _("Report Name"), + "fieldname": "report_name", + "fieldtype": "Data", + "width": 250, + }, + { + "label": _("Start"), + "fieldname": "creation", + "fieldtype": "DateTime", + "width": 250, + }, + { + "label": _("End"), + "fieldname": "report_end_time", + "fieldtype": "DateTime", + "width": 250, + }, + { + "label": _("Runtime in Minutes") if filters.in_minutes else _("Runtime in Seconds"), + "fieldname": "runtime", + "fieldtype": "float", + "width": 250, + }, + { + "label": _("Memory Usage in MB"), + "fieldname": "peak_memory_usage", + "fieldtype": "float", + "width": 250, + }, + ] + + +def get_data(filters) -> list[list]: + """Return data for the report. + + The report data is a list of rows, with each row being a list of cell values. + """ + + pr = qb.DocType("Prepared Report") + + conditions = [pr.status.eq("Completed"), pr.creation.gte(add_months(nowdate(), -2))] + + if filters.report: + conditions.append(pr.report_name.like(f"%{filters.report}%")) + + divisor = 1 + if filters.in_minutes: + divisor = 60 + + query = ( + qb.from_(pr) + .select( + pr.name, + pr.report_name, + pr.creation, + pr.report_end_time, + (pr.peak_memory_usage / 1024).as_("peak_memory_usage"), + ) + .select(((pr.report_end_time - pr.creation) / divisor).as_("runtime")) + .where(Criterion.all(conditions)) + .orderby(qb.Field("runtime"), order=Order.desc) + ) + if filters.top_10: + query = query.limit(10) + + res = query.run(as_dict=True) + + return res