Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Action menu descriptions #2295

Merged
merged 4 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions datasette/static/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,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
Loading