mirror of
https://github.com/simonw/datasette.git
synced 2026-05-31 06:07:05 +02:00
Move debug links into jump menu
This commit is contained in:
parent
1590444fa3
commit
be1b5b2b5c
12 changed files with 173 additions and 65 deletions
36
datasette/default_debug_menu.py
Normal file
36
datasette/default_debug_menu.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
from datasette import hookimpl
|
||||
from datasette.jump import JumpSQL
|
||||
|
||||
DEBUG_MENU_ITEMS = (
|
||||
("/-/databases", "Databases"),
|
||||
("/-/plugins", "Installed plugins"),
|
||||
("/-/versions", "Version info"),
|
||||
("/-/settings", "Settings"),
|
||||
("/-/permissions", "Debug permissions"),
|
||||
("/-/messages", "Debug messages"),
|
||||
("/-/allow-debug", "Debug allow rules"),
|
||||
("/-/threads", "Debug threads"),
|
||||
("/-/actor", "Debug actor"),
|
||||
("/-/patterns", "Pattern portfolio"),
|
||||
)
|
||||
|
||||
|
||||
@hookimpl
|
||||
def jump_items_sql(datasette, actor, request):
|
||||
async def inner():
|
||||
if not await datasette.allowed(action="debug-menu", actor=actor):
|
||||
return []
|
||||
|
||||
return [
|
||||
JumpSQL.menu_item(
|
||||
label=label,
|
||||
url=datasette.urls.path(path),
|
||||
description="Debug menu",
|
||||
source="datasette.default_debug_menu",
|
||||
sort_key=70 + index,
|
||||
item_type="debug",
|
||||
)
|
||||
for index, (path, label) in enumerate(DEBUG_MENU_ITEMS)
|
||||
]
|
||||
|
||||
return inner
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
from datasette import hookimpl
|
||||
|
||||
|
||||
@hookimpl
|
||||
def menu_links(datasette, actor):
|
||||
async def inner():
|
||||
if not await datasette.allowed(action="debug-menu", actor=actor):
|
||||
return []
|
||||
|
||||
return [
|
||||
{"href": datasette.urls.path("/-/databases"), "label": "Databases"},
|
||||
{
|
||||
"href": datasette.urls.path("/-/plugins"),
|
||||
"label": "Installed plugins",
|
||||
},
|
||||
{
|
||||
"href": datasette.urls.path("/-/versions"),
|
||||
"label": "Version info",
|
||||
},
|
||||
{
|
||||
"href": datasette.urls.path("/-/settings"),
|
||||
"label": "Settings",
|
||||
},
|
||||
{
|
||||
"href": datasette.urls.path("/-/permissions"),
|
||||
"label": "Debug permissions",
|
||||
},
|
||||
{
|
||||
"href": datasette.urls.path("/-/messages"),
|
||||
"label": "Debug messages",
|
||||
},
|
||||
{
|
||||
"href": datasette.urls.path("/-/allow-debug"),
|
||||
"label": "Debug allow rules",
|
||||
},
|
||||
{"href": datasette.urls.path("/-/threads"), "label": "Debug threads"},
|
||||
{"href": datasette.urls.path("/-/actor"), "label": "Debug actor"},
|
||||
{"href": datasette.urls.path("/-/patterns"), "label": "Pattern portfolio"},
|
||||
]
|
||||
|
||||
return inner
|
||||
|
|
@ -10,6 +10,49 @@ class JumpSQL:
|
|||
sql: str
|
||||
params: dict[str, Any] | None = None
|
||||
|
||||
@classmethod
|
||||
def menu_item(
|
||||
cls,
|
||||
*,
|
||||
label: str,
|
||||
url: str,
|
||||
description: str = "Menu item",
|
||||
source: str = "datasette",
|
||||
sort_key: int = 50,
|
||||
search_text: str | None = None,
|
||||
display_name: str | None = None,
|
||||
item_type: str = "menu",
|
||||
) -> "JumpSQL":
|
||||
if search_text is None:
|
||||
search_text = " ".join(
|
||||
text for text in (label, display_name, description) if text is not None
|
||||
)
|
||||
return cls(
|
||||
sql="""
|
||||
SELECT
|
||||
:type AS type,
|
||||
:label AS label,
|
||||
:description AS description,
|
||||
:url AS url,
|
||||
NULL AS database_name,
|
||||
NULL AS resource_name,
|
||||
:search_text AS search_text,
|
||||
:sort_key AS sort_key,
|
||||
:source AS source,
|
||||
:display_name AS display_name
|
||||
""",
|
||||
params={
|
||||
"type": item_type,
|
||||
"label": label,
|
||||
"description": description,
|
||||
"url": url,
|
||||
"search_text": search_text,
|
||||
"sort_key": sort_key,
|
||||
"source": source,
|
||||
"display_name": display_name,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
_PARAM_RE = re.compile(r"(?<!:):([A-Za-z_][A-Za-z0-9_]*)")
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ DEFAULT_PLUGINS = (
|
|||
"datasette.default_column_types",
|
||||
"datasette.default_magic_parameters",
|
||||
"datasette.blob_renderer",
|
||||
"datasette.default_menu_links",
|
||||
"datasette.default_debug_menu",
|
||||
"datasette.default_jump_items",
|
||||
"datasette.handle_exception",
|
||||
"datasette.forbidden",
|
||||
|
|
|
|||
|
|
@ -1398,4 +1398,4 @@ Actor is allowed to view the ``/-/permissions`` debug tools.
|
|||
debug-menu
|
||||
----------
|
||||
|
||||
Controls if the various debug pages are displayed in the navigation menu.
|
||||
Controls if the various debug pages are displayed in the jump menu.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@ Unreleased
|
|||
- Fixed a bug where stale tables and other related resources were not removed from ``catalog_*`` tables when a database was removed. (:issue:`2723`)
|
||||
- Fixed a Safari bug with the table search mechanism triggered by pressing ``/``. (:issue:`2724`)
|
||||
- New "Jump to..." menu item, always visible, for triggering the previously undocumented ``/`` menu. (:issue:`2725`)
|
||||
- The ``/`` jump-to search interface now covers databases, views, canned queries and plugin-provided items in addition to tables. The endpoint backing it has been renamed from ``/-/tables`` to ``/-/jump``.
|
||||
- New :ref:`plugin_hook_jump_items_sql` plugin hook, allowing plugins to contribute additional items to the jump-to menu by returning SQL that queries the internal catalog.
|
||||
- ``datasette.jump.JumpSQL.menu_item()`` is a shortcut for adding individual jump menu items that are not backed by resources in the internal catalog.
|
||||
- New :ref:`javascript_plugins_makeJumpSections` JavaScript plugin hook, allowing plugins to add custom blank-state sections to the jump-to menu before the user has typed a query.
|
||||
- Debug menu links now appear in the jump-to menu instead of the top-right app menu.
|
||||
- New documented :ref:`datasette.fixtures.populate_fixture_database(conn) <datasette_fixtures_populate_fixture_database>` helper for creating the fixture database tables used by Datasette's own tests, intended for plugin test suites.
|
||||
|
||||
.. _v1_0_a29:
|
||||
|
|
@ -274,7 +279,7 @@ Other changes
|
|||
~~~~~~~~~~~~~
|
||||
|
||||
- The internal ``catalog_views`` table now tracks SQLite views alongside tables in the introspection database. (:issue:`2495`)
|
||||
- Hitting the ``/`` brings up a search interface for navigating to databases, tables, views, canned queries and plugin-provided items that the current user can view. A new ``/-/jump`` endpoint supports this functionality, and JavaScript plugins can add custom blank-state sections using ``makeJumpSections()``. (:issue:`2523`)
|
||||
- Hitting the ``/`` brings up a search interface for navigating to tables that the current user can view. A new ``/-/tables`` endpoint supports this functionality. (:issue:`2523`)
|
||||
- Datasette attempts to detect some configuration errors on startup.
|
||||
- Datasette now supports Python 3.14 and no longer tests against Python 3.9.
|
||||
|
||||
|
|
|
|||
|
|
@ -152,8 +152,6 @@ Shows currently attached databases. `Databases example <https://latest.datasette
|
|||
Returns a JSON list of items that the current actor has permission to view for Datasette's jump menu. By default this includes visible databases, tables, views and canned queries, and plugins can contribute additional items.
|
||||
|
||||
The endpoint supports a ``?q=`` query parameter for filtering items by name.
|
||||
Plugins can provide an optional ``display_name`` field from
|
||||
``jump_items_sql`` by returning a ``display_name`` column.
|
||||
|
||||
`Jump example <https://latest.datasette.io/-/jump>`_:
|
||||
|
||||
|
|
|
|||
|
|
@ -1943,22 +1943,21 @@ This example adds a "Plugin dashboard" result for signed-in users:
|
|||
def jump_items_sql(actor):
|
||||
if not actor:
|
||||
return None
|
||||
return JumpSQL(sql="""
|
||||
SELECT
|
||||
'dashboard' AS type,
|
||||
'plugin-dashboard' AS label,
|
||||
'Dashboard' AS description,
|
||||
'/-/plugin-dashboard' AS url,
|
||||
NULL AS database_name,
|
||||
NULL AS resource_name,
|
||||
'plugin dashboard' AS search_text,
|
||||
80 AS sort_key,
|
||||
'my-plugin' AS source,
|
||||
'Plugin dashboard' AS display_name
|
||||
""")
|
||||
return JumpSQL.menu_item(
|
||||
item_type="dashboard",
|
||||
label="plugin-dashboard",
|
||||
description="Dashboard",
|
||||
url="/-/plugin-dashboard",
|
||||
search_text="plugin dashboard",
|
||||
sort_key=80,
|
||||
source="my-plugin",
|
||||
display_name="Plugin dashboard",
|
||||
)
|
||||
|
||||
Use ``params=`` to pass SQL parameters. Datasette will automatically namespace those parameters before combining SQL fragments from different plugins.
|
||||
|
||||
``JumpSQL.menu_item(...)`` is a shortcut for adding a single jump menu item that is not backed by a resource in Datasette's internal catalog tables. It returns ``NULL`` for ``database_name`` and ``resource_name`` and accepts the keyword arguments shown above.
|
||||
|
||||
.. _plugin_actions:
|
||||
|
||||
Action hooks
|
||||
|
|
|
|||
|
|
@ -235,12 +235,12 @@ If you run ``datasette plugins --all`` it will include default plugins that ship
|
|||
]
|
||||
},
|
||||
{
|
||||
"name": "datasette.default_menu_links",
|
||||
"name": "datasette.default_debug_menu",
|
||||
"static": false,
|
||||
"templates": false,
|
||||
"version": null,
|
||||
"hooks": [
|
||||
"menu_links"
|
||||
"jump_items_sql"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -994,7 +994,7 @@ def test_edit_sql_link_not_shown_if_user_lacks_permission(has_permission):
|
|||
[
|
||||
(None, None, None),
|
||||
("test", None, ["/-/permissions"]),
|
||||
("root", ["/-/permissions", "/-/allow-debug"], None),
|
||||
("root", None, ["/-/permissions", "/-/allow-debug"]),
|
||||
],
|
||||
)
|
||||
async def test_navigation_menu_links(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import pytest_asyncio
|
|||
|
||||
from datasette import hookimpl
|
||||
from datasette.app import Datasette
|
||||
from datasette.jump import JumpSQL
|
||||
from datasette.plugins import pm
|
||||
|
||||
|
||||
|
|
@ -140,9 +141,77 @@ async def test_jump_respects_resource_permissions(ds_for_jump):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_jump_uses_plugin_sql_with_namespaced_parameters(ds_for_jump):
|
||||
from datasette.jump import JumpSQL
|
||||
async def test_jump_sql_menu_item_helper(ds_for_jump):
|
||||
fragment = JumpSQL.menu_item(
|
||||
label="Plugin dashboard",
|
||||
url="/-/plugin-dashboard",
|
||||
description="Plugin tool",
|
||||
source="test-plugin",
|
||||
sort_key=70,
|
||||
search_text="dashboard plugin",
|
||||
display_name="Plugin Dashboard",
|
||||
item_type="plugin",
|
||||
)
|
||||
result = await ds_for_jump.get_internal_database().execute(
|
||||
fragment.sql, fragment.params
|
||||
)
|
||||
assert dict(result.first()) == {
|
||||
"type": "plugin",
|
||||
"label": "Plugin dashboard",
|
||||
"description": "Plugin tool",
|
||||
"url": "/-/plugin-dashboard",
|
||||
"database_name": None,
|
||||
"resource_name": None,
|
||||
"search_text": "dashboard plugin",
|
||||
"sort_key": 70,
|
||||
"source": "test-plugin",
|
||||
"display_name": "Plugin Dashboard",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_debug_menu_items_are_in_jump_for_debug_menu_permission():
|
||||
ds = Datasette(
|
||||
config={
|
||||
"permissions": {
|
||||
"debug-menu": {"id": "debugger"},
|
||||
}
|
||||
}
|
||||
)
|
||||
await ds.invoke_startup()
|
||||
response = await ds.client.get("/-/jump.json?q=debug", actor={"id": "debugger"})
|
||||
assert response.status_code == 200
|
||||
debug_matches = [
|
||||
match for match in response.json()["matches"] if match["type"] == "debug"
|
||||
]
|
||||
assert {match["name"]: match["url"] for match in debug_matches} == {
|
||||
"Databases": "/-/databases",
|
||||
"Installed plugins": "/-/plugins",
|
||||
"Version info": "/-/versions",
|
||||
"Settings": "/-/settings",
|
||||
"Debug permissions": "/-/permissions",
|
||||
"Debug messages": "/-/messages",
|
||||
"Debug allow rules": "/-/allow-debug",
|
||||
"Debug threads": "/-/threads",
|
||||
"Debug actor": "/-/actor",
|
||||
"Pattern portfolio": "/-/patterns",
|
||||
}
|
||||
assert {match["description"] for match in debug_matches} == {"Debug menu"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_debug_menu_items_are_hidden_without_debug_menu_permission():
|
||||
ds = Datasette()
|
||||
await ds.invoke_startup()
|
||||
response = await ds.client.get("/-/jump.json?q=debug", actor={"id": "regular"})
|
||||
assert response.status_code == 200
|
||||
assert [
|
||||
match for match in response.json()["matches"] if match["type"] == "debug"
|
||||
] == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_jump_uses_plugin_sql_with_namespaced_parameters(ds_for_jump):
|
||||
class JumpPlugin:
|
||||
@hookimpl
|
||||
def jump_items_sql(self, datasette, actor, request):
|
||||
|
|
|
|||
|
|
@ -430,7 +430,6 @@ async def test_permissions_debug(ds_client, filter_):
|
|||
"result": True,
|
||||
"actor": {"id": "root"},
|
||||
},
|
||||
{"action": "debug-menu", "result": False, "actor": None},
|
||||
{
|
||||
"action": "view-instance",
|
||||
"result": True,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue