Repository: rclement/datasette-dashboards (language: python)

Issue:
Allow dashboards to auto-refresh
It would be nice for dashboards to be able to auto-refresh itself at a given time interval.

Here is how it looks like in Metabase:

<img width="288" alt="Screenshot 2023-03-21 at 17 08 50" src="https://user-images.githubusercontent.com/1238873/226670218-d34dab48-6abf-44c6-b61e-d0086b2b15b5.png">

Sensible auto-refresh time interval values in minutes: 1, 5, 10, 15, 30, 60

Apparently it can be done directly by specifying HTML meta tag (don't know if all browsers honor this):
```html
<meta http-equiv="refresh" content="30">
```

Otherwise, a more traditional way is to trigger the refresh in JS:
```js
window.setTimeout(function() { window.location.reload() }, 1000)
```


Discussion / hints:
The auto-refresh time interval (in minutes) could be defined statically in metadata file:

```yaml
dashboards:
  my-dashboard:
    settings:
      autorefresh: 1
```

In practice, nobody shuffles auto-refresh times as they are defined in function of the underlying data refresh rate.
According to [Meta Refresh Wikipedia article](https://en.wikipedia.org/wiki/Meta_refresh) and this [W3C announcement](https://www.w3.org/TR/WCAG10-HTML-TECHS/#meta-element), the `http-equiv="refresh"` meta tag has been deprecated for a while.

Advised workarounds:
- Use JS (`setTimeout`)
- Trigger a manual refresh with a link (seems a bit redundant with the natural abilities of web browsers)


Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fb398be..d4be18f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Table chart component
 - Map chart component
 - Fullscreen mode for dashboards
+- Autorefresh mode for dashboards
 
 ### Changed
 - Improve filters layout
diff --git a/README.md b/README.md
index 7c13737..4803be0 100644
--- a/README.md
+++ b/README.md
@@ -98,9 +98,10 @@ Dashboard properties:
 
 Dashboard settings:
 
-| Property           | Type   | Description                                                    |
-| ------------------ | ------ | -------------------------------------------------------------- |
-| `allow_fullscreen` | `bool` | Allow dashboard to be toggled in fullscreen  (default `false`) |
+| Property           | Type     | Description                                                    |
+| ------------------ | -------- | -------------------------------------------------------------- |
+| `allow_fullscreen` | `bool`   | Allow dashboard to be toggled in fullscreen  (default `false`) |
+| `autorefresh`      | `number` | Auto-refresh timeout in minutes                                |
 
 Dashboard filters:
 
diff --git a/datasette_dashboards/static/dashboards.js b/datasette_dashboards/static/dashboards.js
index f1a8fed..8df751c 100644
--- a/datasette_dashboards/static/dashboards.js
+++ b/datasette_dashboards/static/dashboards.js
@@ -201,3 +201,8 @@ function toggleFullscreen() {
     el.requestFullscreen()
   }
 }
+
+function autorefresh(minutes) {
+  const timeout = Math.round(minutes * 60 * 1000)
+  window.setTimeout(function () { window.location.reload() }, timeout)
+}
diff --git a/datasette_dashboards/templates/dashboard_view.html b/datasette_dashboards/templates/dashboard_view.html
index cb517d9..1e29c2c 100644
--- a/datasette_dashboards/templates/dashboard_view.html
+++ b/datasette_dashboards/templates/dashboard_view.html
@@ -107,5 +107,9 @@ <h1>{{ dashboard.title }}</h1>
       {% for chart_slug, chart in dashboard.charts.items() %}
       renderChart('#chart-{{ chart_slug }}', {{ chart|tojson }}, '{{ query_string|safe }}')
       {% endfor %}
+
+      {% if settings.autorefresh %}
+      autorefresh({{ settings.autorefresh }})
+      {% endif %}
     </script>
 {% endblock %}
diff --git a/demo/metadata.yml b/demo/metadata.yml
index d757024..ae31c4c 100644
--- a/demo/metadata.yml
+++ b/demo/metadata.yml
@@ -11,6 +11,7 @@ plugins:
       description: Gather metrics about job offers
       settings:
         allow_fullscreen: true
+        autorefresh: 1
       layout:
         - [analysis-note, offers-day, offers-day, offers-count]
         - [analysis-note, offers-source, offers-day-source, offers-region]


Test patch:
diff --git a/tests/test_dashboard_view.py b/tests/test_dashboard_view.py
index f9cf839..d2283ee 100644
--- a/tests/test_dashboard_view.py
+++ b/tests/test_dashboard_view.py
@@ -23,6 +23,7 @@ async def test_dashboard_views(datasette):
             '<button type="button" onclick="toggleFullscreen()">Fullscreen</button>'
             not in response.text
         )
+        assert "autorefresh" not in response.text
 
         assert '<div class="dashboard-filters">' in response.text
         for key, flt in dashboard["filters"].items():
@@ -180,6 +181,22 @@ async def test_dashboard_view_allow_fullscreen(datasette_db, datasette_metadata)
     )
 
 
+@pytest.mark.asyncio
+async def test_dashboard_view_enable_autorefresh(datasette_db, datasette_metadata):
+    metadata = copy.deepcopy(datasette_metadata)
+    metadata["plugins"]["datasette-dashboards"]["job-dashboard"]["settings"][
+        "autorefresh"
+    ] = 1
+    datasette = Datasette([str(datasette_db)], metadata=metadata)
+
+    response = await datasette.client.get(
+        "/-/dashboards/job-dashboard", follow_redirects=True
+    )
+    assert response.status_code == 200
+
+    assert "autorefresh(1)" in response.text
+
+
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
     "metadata,authenticated,expected_status",