Repository: frappe/frappe

Issue:
New Dashboard Chart throws TypeError: format requires a mapping
## Description of the issue

I have created an elementary, stripped-down report to demonstrate the problem. It has one filter called "period" of type "Select" and has three options, as shown below:

Period (filter field)

![01](https://github.com/frappe/frappe/assets/364244/fe86625d-262c-48f7-bebd-5d31e4350be7)

The report doc:

![02](https://github.com/frappe/frappe/assets/364244/e528e5c3-f0d6-4c69-91fd-c4fec0976d44)

When I run the report using the "Show Report" button and select one of the filter options, it simply lists that option, as shown below.

![03](https://github.com/frappe/frappe/assets/364244/b9f10978-15c7-4cb5-a5db-553cbbdc6e34)

The next step is to create a chart out of this report. I open the New Dashboard Chart screen, specify Chart Name, change Chart Type to Report and select "Test Report" from the "Report Name" list.

![04](https://github.com/frappe/frappe/assets/364244/b81b1605-04e6-416c-96ea-30259aeada31)

### Observed result
As soon as I select the report name in the dropdown, It shows the following error:

![05](https://github.com/frappe/frappe/assets/364244/8cc4a548-773b-4224-8c37-8eb40c1aa3f4)

### Expected result
I should be able to select fields in the X-field and Y-axis as well as Filters should be populated.

### Stacktrace / full error message

### App Versions
```
{
"erpnext": "14.25.1",
"frappe": "14.36.3",
"payments": "0.0.1"
}
```

### Route
```
Form/Dashboard Chart/new-dashboard-chart-3
```

### Traceback
```
Traceback (most recent call last):
  File "apps/frappe/frappe/app.py", line 66, in application
    response = frappe.api.handle()
  File "apps/frappe/frappe/api.py", line 54, in handle
    return frappe.handler.handle()
  File "apps/frappe/frappe/handler.py", line 45, in handle
    data = execute_cmd(cmd)
  File "apps/frappe/frappe/handler.py", line 83, in execute_cmd
    return frappe.call(method, **frappe.form_dict)
  File "apps/frappe/frappe/__init__.py", line 1607, in call
    return fn(*args, **newargs)
  File "apps/frappe/frappe/__init__.py", line 789, in wrapper_fn
    retval = fn(*args, **get_newargs(fn, kwargs))
  File "apps/frappe/frappe/desk/query_report.py", line 231, in run
    result = generate_report_result(report, filters, user, custom_columns, is_tree, parent_field)
  File "apps/frappe/frappe/__init__.py", line 789, in wrapper_fn
    retval = fn(*args, **get_newargs(fn, kwargs))
  File "apps/frappe/frappe/desk/query_report.py", line 90, in generate_report_result
    res = get_report_result(report, filters) or []
  File "apps/frappe/frappe/desk/query_report.py", line 68, in get_report_result
    res = report.execute_query_report(filters)
  File "apps/frappe/frappe/core/doctype/report/report.py", line 117, in execute_query_report
    result = [list(t) for t in frappe.db.sql(self.query, filters)]
  File "apps/frappe/frappe/database/database.py", line 219, in sql
    self._cursor.execute(query, values)
  File "env/lib/python3.10/site-packages/pymysql/cursors.py", line 156, in execute
    query = self.mogrify(query, args)
  File "env/lib/python3.10/site-packages/pymysql/cursors.py", line 134, in mogrify
    query = query % self._escape_args(args, conn)
TypeError: format requires a mapping
```

### Request Data
```
{
"type": "POST",
"args": {
"report_name": "Sales Profitability",
"filters": null,
"ignore_prepared_report": 1
},
"headers": {},
"error_handlers": {},
"url": "/api/method/frappe.desk.query_report.run"
}
```

### Response Data
```
{
"exception": "TypeError: format requires a mapping"
}
```

## Additional information
Hosted on Frappe Cloud



Discussion:
On a careful inspection of the code, here is my finding:

On report selection from the dropdown, it runs the following code block in the `dashboard_char.js`

```javascript
report_name: function (frm) {
	frm.set_value("x_field", "");
	frm.set_value("y_axis", []);
	frm.set_df_property("x_field", "options", []);
	frm.set_value("filters_json", "{}");
	frm.set_value("dynamic_filters_json", "{}");
	frm.set_value("use_report_chart", 0);
	frm.trigger("set_chart_report_filters");
},

set_chart_report_filters: function (frm) {
	let report_name = frm.doc.report_name;

	if (report_name) {
		if (frm.doc.filters_json.length > 2) {
			frm.trigger("show_filters");
			frm.trigger("set_chart_field_options");
		} else {
			frappe.report_utils.get_report_filters(report_name).then((filters) => {
				if (filters) {
					frm.chart_filters = filters;
					let filter_values = frappe.report_utils.get_filter_values(filters);
					frm.set_value("filters_json", JSON.stringify(filter_values));
				}
				frm.trigger("show_filters");
				frm.trigger("set_chart_field_options");
			});
		}
	}
},
```

The `get_report_filters method` in the `report_utils.js` call the `get_script` method on the `query_report.py`

```js
get_report_filters: function (report_name) {
	if (frappe.query_reports[report_name]) {
		let filters = frappe.query_reports[report_name].filters;
		return Promise.resolve(filters);
	}

	return frappe
		.xcall("frappe.desk.query_report.get_script", {
			report_name: report_name,
		})
		.then((r) => {
			frappe.dom.eval(r.script || "");
			return frappe.after_ajax(() => {
				return (
					frappe.query_reports[report_name] &&
					frappe.query_reports[report_name].filters
				);
			});
		});
},
```

and the `get_script` method does not return any filters

```python
@frappe.whitelist()
def get_script(report_name):
	report = get_report_doc(report_name)
	module = report.module or frappe.db.get_value("DocType", report.ref_doctype, "module")

	is_custom_module = frappe.get_cached_value("Module Def", module, "custom")

	# custom modules are virtual modules those exists in DB but not in disk.
	module_path = "" if is_custom_module else get_module_path(module)
	report_folder = module_path and os.path.join(module_path, "report", scrub(report.name))
	script_path = report_folder and os.path.join(report_folder, scrub(report.name) + ".js")
	print_path = report_folder and os.path.join(report_folder, scrub(report.name) + ".html")

	script = None
	if os.path.exists(script_path):
		with open(script_path) as f:
			script = f.read()
			script += f"\n\n//# sourceURL={scrub(report.name)}.js"

	html_format = get_html_format(print_path)

	if not script and report.javascript:
		script = report.javascript
		script += f"\n\n//# sourceURL={scrub(report.name)}__custom"

	if not script:
		script = "frappe.query_reports['%s']={}" % report_name

	return {
		"script": render_include(script),
		"html_format": html_format,
		"execution_time": frappe.cache().hget("report_execution_time", report_name) or 0,
	}
```

Resolving patch:
diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py
--- a/frappe/desk/query_report.py
+++ b/frappe/desk/query_report.py
@@ -186,6 +186,7 @@ def get_script(report_name):
 		"script": render_include(script),
 		"html_format": html_format,
 		"execution_time": frappe.cache().hget("report_execution_time", report_name) or 0,
+		"filters": report.filters,
 	}
 
 


Test patch: