diff --git a/datasette/templates/query.html b/datasette/templates/query.html index 543561d838..75f7f1b1ca 100644 --- a/datasette/templates/query.html +++ b/datasette/templates/query.html @@ -33,7 +33,9 @@

-

Custom SQL query{% if display_rows %} returning {% if truncated %}more than {% endif %}{{ "{:,}".format(display_rows|length) }} row{% if display_rows|length == 1 %}{% else %}s{% endif %}{% endif %}{% if not query_error %} {% if hide_sql %}(show){% else %}(hide){% endif %}{% endif %}

+

Custom SQL query{% if display_rows %} returning {% if truncated %}more than {% endif %}{{ "{:,}".format(display_rows|length) }} row{% if display_rows|length == 1 %}{% else %}s{% endif %}{% endif %}{% if not query_error %} + ({{ show_hide_text }}) + {% endif %}

{% if error %}

{{ error }}

{% endif %} @@ -44,8 +46,11 @@

Custom SQL query{% if display_rows %} returning {% if truncated %}more than
{% if query %}{{ query.sql }}{% endif %}
{% endif %} {% else %} - {% if not canned_query %}{% endif %} - + {% if not canned_query %} + + {% endif %} {% endif %} {% if named_parameter_values %}

Query parameters

@@ -54,9 +59,10 @@

Query parameters

{% endfor %} {% endif %}

- + {% if not hide_sql %}{% endif %} {% if canned_write %}{% endif %} + {{ show_hide_hidden }} {% if canned_query and edit_sql_url %}Edit SQL{% endif %}

diff --git a/datasette/views/database.py b/datasette/views/database.py index 53bdceed1b..d9fe2b4966 100644 --- a/datasette/views/database.py +++ b/datasette/views/database.py @@ -5,6 +5,8 @@ from markupsafe import Markup, escape from urllib.parse import parse_qsl, urlencode +import markupsafe + from datasette.utils import ( await_me_maybe, check_visibility, @@ -415,6 +417,29 @@ async def extra_template(): } ) ) + + show_hide_hidden = "" + if metadata.get("hide_sql"): + if bool(params.get("_show_sql")): + show_hide_link = path_with_removed_args(request, {"_show_sql"}) + show_hide_text = "hide" + show_hide_hidden = ( + '' + ) + else: + show_hide_link = path_with_added_args(request, {"_show_sql": 1}) + show_hide_text = "show" + else: + if bool(params.get("_hide_sql")): + show_hide_link = path_with_removed_args(request, {"_hide_sql"}) + show_hide_text = "show" + show_hide_hidden = ( + '' + ) + else: + show_hide_link = path_with_added_args(request, {"_hide_sql": 1}) + show_hide_text = "hide" + hide_sql = show_hide_text == "show" return { "display_rows": display_rows, "custom_sql": True, @@ -425,9 +450,10 @@ async def extra_template(): "metadata": metadata, "config": self.ds.config_dict(), "request": request, - "path_with_added_args": path_with_added_args, - "path_with_removed_args": path_with_removed_args, - "hide_sql": "_hide_sql" in params, + "show_hide_link": show_hide_link, + "show_hide_text": show_hide_text, + "show_hide_hidden": markupsafe.Markup(show_hide_hidden), + "hide_sql": hide_sql, } return ( diff --git a/docs/changelog.rst b/docs/changelog.rst index 883cb3eb3a..d0fee19b9f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -674,7 +674,7 @@ The main focus of this release is a major upgrade to the :ref:`plugin_register_o * Visually distinguish float and integer columns - useful for figuring out why order-by-column might be returning unexpected results. (:issue:`729`) * The :ref:`internals_request`, which is passed to several plugin hooks, is now documented. (:issue:`706`) * New ``metadata.json`` option for setting a custom default page size for specific tables and views, see :ref:`metadata_page_size`. (:issue:`751`) -* Canned queries can now be configured with a default URL fragment hash, useful when working with plugins such as `datasette-vega `__, see :ref:`canned_queries_default_fragment`. (:issue:`706`) +* Canned queries can now be configured with a default URL fragment hash, useful when working with plugins such as `datasette-vega `__, see :ref:`canned_queries_options`. (:issue:`706`) * Fixed a bug in ``datasette publish`` when running on operating systems where the ``/tmp`` directory lives in a different volume, using a backport of the Python 3.8 ``shutil.copytree()`` function. (:issue:`744`) * Every plugin hook is now covered by the unit tests, and a new unit test checks that each plugin hook has at least one corresponding test. (:issue:`771`, :issue:`773`) diff --git a/docs/sql_queries.rst b/docs/sql_queries.rst index 3049593df2..407e4ba2a3 100644 --- a/docs/sql_queries.rst +++ b/docs/sql_queries.rst @@ -187,14 +187,28 @@ You can alternatively provide an explicit list of named parameters using the ``" order by neighborhood title: Search neighborhoods -.. _canned_queries_default_fragment: +.. _canned_queries_options: -Setting a default fragment -~~~~~~~~~~~~~~~~~~~~~~~~~~ +Additional canned query options +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Additional options can be specified for canned queries in the YAML or JSON configuration. + +hide_sql +++++++++ + +Canned queries default to displaying their SQL query at the top of the page. If the query is extremely long you may want to hide it by default, with a "show" link that can be used to make it visible. + +Add the ``"hide_sql": true`` option to hide the SQL query by default. + +fragment +++++++++ Some plugins, such as `datasette-vega `__, can be configured by including additional data in the fragment hash of the URL - the bit that comes after a ``#`` symbol. -You can set a default fragment hash that will be included in the link to the canned query from the database index page using the ``"fragment"`` key: +You can set a default fragment hash that will be included in the link to the canned query from the database index page using the ``"fragment"`` key. + +This example demonstrates both ``fragment`` and ``hide_sql``: .. code-block:: json @@ -204,7 +218,8 @@ You can set a default fragment hash that will be included in the link to the can "queries": { "neighborhood_search": { "sql": "select neighborhood, facet_cities.name, state\nfrom facetable join facet_cities on facetable.city_id = facet_cities.id\nwhere neighborhood like '%' || :text || '%' order by neighborhood;", - "fragment": "fragment-goes-here" + "fragment": "fragment-goes-here", + "hide_sql": true } } } diff --git a/tests/fixtures.py b/tests/fixtures.py index 93b7dce2f5..873f9d5510 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -360,6 +360,7 @@ def generate_sortable_rows(num): "title": "Search neighborhoods", "description_html": "Demonstrating simple like search", "fragment": "fragment-goes-here", + "hide_sql": True, }, }, } diff --git a/tests/test_html.py b/tests/test_html.py index 9f5b99e307..b1b6c1f315 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -1241,7 +1241,7 @@ def test_show_hide_sql_query(app_client): def test_canned_query_with_hide_has_no_hidden_sql(app_client): # For a canned query the show/hide should NOT have a hidden SQL field # https://github.com/simonw/datasette/issues/1411 - response = app_client.get("/fixtures/neighborhood_search?_hide_sql=1") + response = app_client.get("/fixtures/pragma_cache_size?_hide_sql=1") soup = Soup(response.body, "html.parser") hiddens = soup.find("form").select("input[type=hidden]") assert [ @@ -1249,6 +1249,55 @@ def test_canned_query_with_hide_has_no_hidden_sql(app_client): ] == [(hidden["name"], hidden["value"]) for hidden in hiddens] +@pytest.mark.parametrize( + "hide_sql,querystring,expected_hidden,expected_show_hide_link,expected_show_hide_text", + ( + (False, "", None, "/_memory/one?_hide_sql=1", "hide"), + (False, "?_hide_sql=1", "_hide_sql", "/_memory/one", "show"), + (True, "", None, "/_memory/one?_show_sql=1", "show"), + (True, "?_show_sql=1", "_show_sql", "/_memory/one", "hide"), + ), +) +def test_canned_query_show_hide_metadata_option( + hide_sql, + querystring, + expected_hidden, + expected_show_hide_link, + expected_show_hide_text, +): + with make_app_client( + metadata={ + "databases": { + "_memory": { + "queries": { + "one": { + "sql": "select 1 + 1", + "hide_sql": hide_sql, + } + } + } + } + }, + memory=True, + ) as client: + expected_show_hide_fragment = '({})'.format( + expected_show_hide_link, expected_show_hide_text + ) + response = client.get("/_memory/one" + querystring) + html = response.text + show_hide_fragment = html.split('')[1].split( + "" + )[0] + assert show_hide_fragment == expected_show_hide_fragment + if expected_hidden: + assert ( + ''.format(expected_hidden) + in html + ) + else: + assert '