Commit 721d44fe authored by 20after4's avatar 20after4
Browse files

jinja template renderer

parent bf87331e
from pprint import pprint
import re
import sys
from typing import Coroutine
import urllib
from datasette import hookimpl
from typing import Coroutine, Mapping
import urllib.parse
from datasette.database import Database
from datasette.plugins import pm
from datasette.hookspecs import hookspec
from datasette.hookspecs import hookspec, hookimpl
from datasette.utils.asgi import Forbidden, NotFound, Response
from jinja2 import Markup # type: ignore
from jinja2.utils import Markup
import jinja2 # type: ignore
sql_opt_pattern = re.compile(r"(?P<opt>\[\[[^\]]*\]\])")
......@@ -16,21 +17,32 @@ sql_var_pattern = re.compile(r"\:(?P<var>[a-zA-Z0-9_]+)")
@hookspec(firstresult=True)
async def render_custom_dashboard_chart(chart_display):
""" Render a custom dashboard chart display. """
"""Render a custom dashboard chart display."""
pm.add_hookspecs(sys.modules[__name__])
@hookimpl
def extra_template_vars(datasette, database):
async def render_custom_dashboard_chart(chart_display):
result = pm.hook.render_custom_dashboard_chart(chart_display=chart_display) # type: ignore
result = pm.hook.render_custom_dashboard_chart(chart_display=chart_display) # type: ignore
if not result:
result = chart_display
if isinstance(result, Coroutine):
result = await result
return Markup(result)
return {"render_custom_dashboard_chart": render_custom_dashboard_chart }
def alter_query_url(query: str, overrides={}):
url = urllib.parse.parse_qs(query)
url.update(overrides)
return urllib.parse.urlencode(url)
return {
"render_custom_dashboard_chart": render_custom_dashboard_chart,
"alter_query_url": alter_query_url,
}
async def check_permission_instance(request, datasette):
if (
......@@ -61,11 +73,26 @@ def get_dashboard_filters_keys(request, dashboard):
async def populate_dashboard_filter_queries(database, dashboard):
"""
Populate filters with values returned by a query.
"""
filters = dashboard.get("filters") or {}
for key,filter in filters.items():
if 'query' in filter:
for key, filter in filters.items():
if "query" in filter:
res = await database.execute(filter["query"])
filter['options'] = {row['key']:row['label'] for row in res}
filter["options"] = {row["key"]: row["label"] for row in res}
async def populate_chart_canned_queries(datasette, database, request, chart):
"""
Reference 'canned queries' by specifying a canned query name in the
chart definition metadata.
"""
queries = await datasette.get_canned_queries(database.name, request.actor)
if chart["query"] in queries.keys():
query = queries.get(chart["query"], None)
if query:
chart["query"] = query["sql"]
def get_dashboard_filters(request, opts_keys):
......@@ -119,6 +146,7 @@ async def dashboard_view(request, datasette):
raise NotFound(f"Dashboard not found: {slug}")
dbs = set([chart["db"] for chart in dashboard["charts"].values() if "db" in chart])
database: Database = None # type: ignore
for db in dbs:
try:
database = datasette.get_database(db)
......@@ -131,7 +159,17 @@ async def dashboard_view(request, datasette):
query_string = generate_dashboard_filters_qs(request, options_keys)
for chart in dashboard["charts"].values():
fill_chart_query_options(chart, options_keys)
await render_chart(
datasette,
dashboard,
database,
request,
options_keys,
query_string,
slug,
query_parameters,
chart,
)
return Response.html(
await datasette.render_template(
......@@ -146,6 +184,36 @@ async def dashboard_view(request, datasette):
)
async def render_chart(
datasette,
dashboard,
database,
request,
options_keys,
query_string,
slug,
query_parameters,
chart,
):
await populate_chart_canned_queries(datasette, database, request, chart)
fill_chart_query_options(chart, options_keys)
if chart["library"] == "jinja":
data = await database.execute(chart["query"], query_parameters)
data = [{col: row[col] for col in data.columns} for row in data.rows]
chart["html"] = Markup(
await datasette.render_template(
chart["template"],
{
"slug": slug,
"query_parameters": query_parameters,
"query_string": query_string,
"dashboard": dashboard,
"data": data,
},
)
)
async def dashboard_chart(request, datasette):
await check_permission_instance(request, datasette)
......@@ -167,10 +235,22 @@ async def dashboard_chart(request, datasette):
if db:
database = datasette.get_database(db)
await check_permission_execute_sql(request, datasette, database)
await populate_dashboard_filter_queries(database, dashboard)
options_keys = get_dashboard_filters_keys(request, dashboard)
query_parameters = get_dashboard_filters(request, options_keys)
query_string = generate_dashboard_filters_qs(request, options_keys)
fill_chart_query_options(chart, options_keys)
await render_chart(
datasette,
dashboard,
database,
request,
options_keys,
query_string,
slug,
query_parameters,
chart,
)
return Response.html(
await datasette.render_template(
......
function renderVegaChart(el, chart, query_string, height_style = undefined) {
const query = encodeURIComponent(chart.query)
var data = []
function datasource(chart, dataspec, query_string) {
var type = dataspec['type']
var query = encodeURIComponent( dataspec['query']);
if (type == 'sql') {
return {
name: dataspec['name'],
url: `/${chart.db}.csv?sql=${query}&${query_string}`,
format: {'type': 'csv'}
}
} else if (type == 'query') {
return {
name: dataspec['name'],
url: `/${chart.db}/${query}.csv?${query_string}`,
format: {'type': 'csv'}
}
}
}
const spec = {
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
description: chart.title,
......@@ -15,15 +36,27 @@ function renderVegaChart(el, chart, query_string, height_style = undefined) {
point: true
}
},
data: {
url: `/${chart.db}.csv?sql=${query}&${query_string}`,
format: {'type': 'csv'}
datasets: {
},
data: datasource(chart, {name: 'data', type:'sql', query:chart.query}, query_string),
...chart.display
};
vegaEmbed(el, spec).then(function(result) {
console.log('Vega-embed view:',result.view);
for (var i in chart.data) {
var dataspec = chart.data[i];
var ds = datasource(chart, dataspec, query_string);
var loaded = result.view._loader.load(ds.url);
loaded.then(function(val){
console.log(val);
spec.datasets[dataspec['name']] = val;
});
}
console.log(spec);
result.view.run();
});
}
......
......@@ -40,21 +40,23 @@
<div id="chart" class="chart-container">
{% if chart.library == 'markdown' %}
{{ render_markdown(chart.display) }}
{% elif chart.library == 'jinja' %}
{{ chart.html }}
{% elif chart.library == 'custom' %}
{{ render_custom_dashboard_chart(chart.display) }}
{% endif %}
</div>
</div>
<script id="data" type="application/json">{{ chart|tojson }}</script>
<script src="https://cdn.jsdelivr.net/npm/vega@5.20.2"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-lite@5.1.0"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-embed@6.17.0"></script>
<script src="{{ urls.static_plugins('datasette_dashboards', 'dashboards.js') }}"></script>
<script type="text/javascript">
{% if chart.library == 'vega' %}
renderVegaChart('#chart', JSON.parse('{{ chart|tojson }}'), '{{ query_string|safe }}', 'container')
renderVegaChart('#chart', JSON.parse(document.getElementById("data").text), '{{ query_string|safe }}', 'container')
{% elif chart.library == 'metric' %}
renderMetricChart('#chart', JSON.parse('{{ chart|tojson }}'), '{{ query_string|safe }}')
renderMetricChart('#chart', JSON.parse(document.getElementById("data").text), '{{ query_string|safe }}')
{% endif %}
</script>
{% endblock %}
......@@ -73,7 +73,7 @@
<div class="dashboard-grid">
{% for chart_slug, chart in dashboard.charts.items() %}
<div id="card-{{ chart_slug }}" class="dashboard-card">
{% if chart.library != 'markdown' %}
{% if chart.title and chart.library != 'markdown' %}
<div class="dashboard-card-title">
<p><a href="{{ urls.path('-/dashboards') }}/{{ slug }}/{{ chart_slug }}{% if query_string|length > 0 %}?{{ query_string|safe }}{% endif %}">{{ chart.title }}</a></p>
</div>
......@@ -82,6 +82,8 @@
<div id="chart-{{ chart_slug }}" class="dashboard-card-chart">
{% if chart.library == 'markdown' %}
{{ render_markdown(chart.display) }}
{% elif chart.library == 'jinja' %}
{{ chart.html }}
{% elif chart.library == 'custom' %}
{{ render_custom_dashboard_chart(chart.display) }}
{% endif %}
......@@ -89,7 +91,7 @@
</div>
{% endfor %}
</div>
<script id="data" type="application/json">{{ dashboard.charts|tojson }}</script>
<script src="https://cdn.jsdelivr.net/npm/vega@5.20.2"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-lite@5.1.0"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-embed@6.17.0"></script>
......@@ -97,9 +99,9 @@
<script type="text/javascript">
{% for chart_slug, chart in dashboard.charts.items() %}
{% if chart.library == 'vega' %}
renderVegaChart('#chart-{{ chart_slug }}', JSON.parse('{{ chart|tojson }}'), '{{ query_string|safe }}')
renderVegaChart('#chart-{{ chart_slug }}', JSON.parse(document.getElementById("data").text)['{{chart_slug}}'], '{{ query_string|safe }}')
{% elif chart.library == 'metric' %}
renderMetricChart('#chart-{{ chart_slug }}', JSON.parse('{{ chart|tojson }}'), '{{ query_string|safe }}')
renderMetricChart('#chart-{{ chart_slug }}', JSON.parse(document.getElementById("data").text)['{{chart_slug}}'], '{{ query_string|safe }}')
{% endif %}
{% endfor %}
</script>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment