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

Issue:
Dashboard filter default value is not applied automatically
When specifying a default value for a filter, it is not applied automatically when opening the dashboard.

As done by Metabase (and others), when accessing a dashboard without query parameters, the request should be redirected to the same dashboard but with query arguments filled with filters default values.

Example:

Using this configuration:

```yaml
plugins:
  datasette-dashboards:
    job-offers-stats:
      title: Job offers statistics
      description: Gather metrics about job offers
      layout:
        - [analysis-note, offers-day, offers-day, offers-count]
        - [analysis-note, offers-source, offers-day-source, offers-region]
      filters:
        date_start:
          name: Date Start
          type: date
          default: '2021-01-01'
        date_end:
          name: Date End
          type: date
```

When accessing `/-/dashboards/job-dashboard`, it should be redirected to `/-/dashboards/job-dashboard?date_start=2021-01-01`.


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 919676b..368f84c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 - Select filter type
 
+### Fixed
+- Automatically redirect with dashboard default filter values
+
 ### Changed
 - Update dependencies
 
diff --git a/datasette_dashboards/__init__.py b/datasette_dashboards/__init__.py
index bde772a..e155974 100644
--- a/datasette_dashboards/__init__.py
+++ b/datasette_dashboards/__init__.py
@@ -99,6 +99,16 @@ async def dashboard_view(request, datasette):
     query_parameters = get_dashboard_filters(request, options_keys)
     query_string = generate_dashboard_filters_qs(request, options_keys)
 
+    default_filters = {
+        k: v["default"] for k, v in dashboard["filters"].items() if v.get("default")
+    }
+    if len(query_parameters.keys()) == 0 and len(default_filters) > 0:
+        qs = urllib.parse.urlencode(default_filters)
+        response = Response.redirect(f"{request.path}?{qs}")
+        for k, v in request.cookies.items():
+            response.set_cookie(k, v)
+        return response
+
     for chart in dashboard["charts"].values():
         fill_chart_query_options(chart, query_parameters)
 


Test patch:
diff --git a/tests/test_dashboard_view.py b/tests/test_dashboard_view.py
index d29e5d9..a9b8328 100644
--- a/tests/test_dashboard_view.py
+++ b/tests/test_dashboard_view.py
@@ -8,7 +8,9 @@
 async def test_dashboard_views(datasette):
     dashboards = datasette._metadata["plugins"]["datasette-dashboards"]
     for slug, dashboard in dashboards.items():
-        response = await datasette.client.get(f"/-/dashboards/{slug}")
+        response = await datasette.client.get(
+            f"/-/dashboards/{slug}", follow_redirects=True
+        )
         assert response.status_code == 200
         assert f'<h1>{dashboard["title"]}</h1>' in response.text
         assert f'<p>{dashboard["description"]}</p>' in response.text
@@ -43,16 +45,12 @@ async def test_dashboard_views(datasette):
             )
 
             if chart["library"] == "vega":
-                assert (
-                    f'<p><a href="/-/dashboards/{slug}/{chart_slug}">{chart["title"]}</a></p>'
-                    in response.text
-                )
+                assert f'<a href="/-/dashboards/{slug}/{chart_slug}?' in response.text
+                assert f'>{chart["title"]}</a>' in response.text
                 assert f"renderVegaChart('#chart-{chart_slug}', " in response.text
             elif chart["library"] == "metric":
-                assert (
-                    f'<p><a href="/-/dashboards/{slug}/{chart_slug}">{chart["title"]}</a></p>'
-                    in response.text
-                )
+                assert f'<a href="/-/dashboards/{slug}/{chart_slug}?' in response.text
+                assert f'>{chart["title"]}</a>' in response.text
                 assert f"renderMetricChart('#chart-{chart_slug}', " in response.text
 
 
@@ -65,7 +63,9 @@ async def test_dashboard_view_layout(datasette_db, datasette_metadata):
     ]
     datasette = Datasette([str(datasette_db)], metadata=metadata)
 
-    response = await datasette.client.get("/-/dashboards/job-dashboard")
+    response = await datasette.client.get(
+        "/-/dashboards/job-dashboard", follow_redirects=True
+    )
     assert response.status_code == 200
 
     assert (
@@ -79,6 +79,16 @@ async def test_dashboard_view_layout(datasette_db, datasette_metadata):
     assert "grid-template-columns: repeat(2, 1fr);" not in response.text
 
 
+@pytest.mark.asyncio
+async def test_dashboard_view_filters_default_redirect(datasette):
+    response = await datasette.client.get("/-/dashboards/job-dashboard")
+    assert response.status_code == 302
+    assert (
+        response.headers["location"]
+        == "/-/dashboards/job-dashboard?date_start=2021-01-01&date_end=2021-12-31"
+    )
+
+
 @pytest.mark.asyncio
 async def test_dashboard_view_parameters(datasette):
     response = await datasette.client.get(
@@ -176,5 +186,7 @@ async def test_dashboard_view_permissions(
         cookies["ds_actor"] = datasette.sign({"a": {"id": "user"}}, "actor")
 
     slug = list(datasette_metadata["plugins"]["datasette-dashboards"].keys())[0]
-    response = await datasette.client.get(f"/-/dashboards/{slug}", cookies=cookies)
+    response = await datasette.client.get(
+        f"/-/dashboards/{slug}", cookies=cookies, follow_redirects=True
+    )
     assert response.status_code == expected_status