Skip to content

Commit

Permalink
Action menu descriptions
Browse files Browse the repository at this point in the history
* Refactor tests to extract get_actions_links() helper
* Table, database and query action menu items now support optional descriptions

Closes #2294
  • Loading branch information
simonw authored Mar 7, 2024
1 parent c6e8a4a commit 090dff5
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 35 deletions.
7 changes: 7 additions & 0 deletions datasette/static/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,13 @@ svg.dropdown-menu-icon {
.dropdown-menu a:hover {
background-color: #eee;
}
.dropdown-menu .dropdown-description {
margin: 0;
color: #666;
font-size: 0.8em;
max-width: 80vw;
white-space: normal;
}
.dropdown-menu .hook {
display: block;
position: absolute;
Expand Down
6 changes: 5 additions & 1 deletion datasette/templates/database.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ <h1>{{ metadata.title or database }}{% if private %} 🔒{% endif %}</h1>
{% if links %}
<ul>
{% for link in links %}
<li><a href="{{ link.href }}">{{ link.label }}</a></li>
<li><a href="{{ link.href }}">{{ link.label }}
{% if link.description %}
<p class="dropdown-description">{{ link.description }}</p>
{% endif %}</a>
</li>
{% endfor %}
</ul>
{% endif %}
Expand Down
6 changes: 5 additions & 1 deletion datasette/templates/query.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ <h1 style="padding-left: 10px; border-left: 10px solid #{{ database_color }}">{{
{% if links %}
<ul>
{% for link in links %}
<li><a href="{{ link.href }}">{{ link.label }}</a></li>
<li><a href="{{ link.href }}">{{ link.label }}
{% if link.description %}
<p class="dropdown-description">{{ link.description }}</p>
{% endif %}</a>
</li>
{% endfor %}
</ul>
{% endif %}
Expand Down
6 changes: 5 additions & 1 deletion datasette/templates/table.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ <h1>{{ metadata.get("title") or table }}{% if is_view %} (view){% endif %}{% if
{% if links %}
<ul>
{% for link in links %}
<li><a href="{{ link.href }}">{{ link.label }}</a></li>
<li><a href="{{ link.href }}">{{ link.label }}
{% if link.description %}
<p class="dropdown-description">{{ link.description }}</p>
{% endif %}</a>
</li>
{% endfor %}
</ul>
{% endif %}
Expand Down
4 changes: 3 additions & 1 deletion docs/plugin_hooks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1493,7 +1493,7 @@ table_actions(datasette, actor, database, table, request)
``request`` - :ref:`internals_request` or None
The current HTTP request. This can be ``None`` if the request object is not available.

This hook allows table actions to be displayed in a menu accessed via an action icon at the top of the table page. It should return a list of ``{"href": "...", "label": "..."}`` menu items.
This hook allows table actions to be displayed in a menu accessed via an action icon at the top of the table page. It should return a list of ``{"href": "...", "label": "..."}`` menu items, with optional ``"description": "..."`` keys describing each action in more detail.

It can alternatively return an ``async def`` awaitable function which returns a list of menu items.

Expand All @@ -1515,6 +1515,7 @@ This example adds a new table action if the signed in user is ``"root"``:
)
),
"label": "Edit schema for this table",
"description": "Add, remove, rename or alter columns for this table.",
}
]
Expand Down Expand Up @@ -1571,6 +1572,7 @@ This example adds a new query action linking to a page for explaining a query:
}
),
"label": "Explain this query",
"description": "Get a summary of how SQLite executes the query",
},
]
Expand Down
1 change: 1 addition & 0 deletions tests/plugins/my_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ def query_actions(datasette, database, query_name, sql):
}
),
"label": "Explain this query",
"description": "Runs a SQLite explain",
},
]

Expand Down
64 changes: 33 additions & 31 deletions tests/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -925,26 +925,36 @@ def get_menu_links(html):
@pytest.mark.asyncio
@pytest.mark.parametrize("table_or_view", ["facetable", "simple_view"])
async def test_hook_table_actions(ds_client, table_or_view):
def get_table_actions_links(html):
soup = Soup(html, "html.parser")
details = soup.find("details", {"class": "actions-menu-links"})
if details is None:
return []
return [{"label": a.text, "href": a["href"]} for a in details.select("a")]

response = await ds_client.get(f"/fixtures/{table_or_view}")
assert get_table_actions_links(response.text) == []
assert get_actions_links(response.text) == []

response_2 = await ds_client.get(f"/fixtures/{table_or_view}?_bot=1&_hello=BOB")
assert sorted(
get_table_actions_links(response_2.text), key=lambda link: link["label"]
get_actions_links(response_2.text), key=lambda link: link["label"]
) == [
{"label": "Database: fixtures", "href": "/"},
{"label": "From async BOB", "href": "/"},
{"label": f"Table: {table_or_view}", "href": "/"},
{"label": "Database: fixtures", "href": "/", "description": None},
{"label": "From async BOB", "href": "/", "description": None},
{"label": f"Table: {table_or_view}", "href": "/", "description": None},
]


def get_actions_links(html):
soup = Soup(html, "html.parser")
details = soup.find("details", {"class": "actions-menu-links"})
if details is None:
return []
links = []
for a_el in details.select("a"):
description = None
if a_el.find("p") is not None:
description = a_el.find("p").text.strip()
a_el.find("p").extract()
label = a_el.text.strip()
href = a_el["href"]
links.append({"label": label, "href": href, "description": description})
return links


@pytest.mark.asyncio
@pytest.mark.parametrize(
"path,expected_url",
Expand All @@ -959,37 +969,29 @@ def get_table_actions_links(html):
),
)
async def test_hook_query_actions(ds_client, path, expected_url):
def get_table_actions_links(html):
soup = Soup(html, "html.parser")
details = soup.find("details", {"class": "actions-menu-links"})
if details is None:
return []
return [{"label": a.text, "href": a["href"]} for a in details.select("a")]

response = await ds_client.get(path)
assert response.status_code == 200
links = get_table_actions_links(response.text)
links = get_actions_links(response.text)
if expected_url is None:
assert links == []
else:
assert links == [{"label": "Explain this query", "href": expected_url}]
assert links == [
{
"label": "Explain this query",
"href": expected_url,
"description": "Runs a SQLite explain",
}
]


@pytest.mark.asyncio
async def test_hook_database_actions(ds_client):
def get_table_actions_links(html):
soup = Soup(html, "html.parser")
details = soup.find("details", {"class": "actions-menu-links"})
if details is None:
return []
return [{"label": a.text, "href": a["href"]} for a in details.select("a")]

response = await ds_client.get("/fixtures")
assert get_table_actions_links(response.text) == []
assert get_actions_links(response.text) == []

response_2 = await ds_client.get("/fixtures?_bot=1&_hello=BOB")
assert get_table_actions_links(response_2.text) == [
{"label": "Database: fixtures - BOB", "href": "/"},
assert get_actions_links(response_2.text) == [
{"label": "Database: fixtures - BOB", "href": "/", "description": None},
]


Expand Down

0 comments on commit 090dff5

Please sign in to comment.