fix: minor comments, TODO items and formatting

This has almost no impact, outside of rewording some items.
This commit is contained in:
Régis Behmo 2025-08-12 21:30:24 +02:00 committed by Régis Behmo
parent 097be3e3fe
commit b60655ed54
7 changed files with 37 additions and 29 deletions

View File

@ -17,8 +17,8 @@ from quart import (
request, request,
url_for, url_for,
) )
from quart.helpers import WerkzeugResponse
from quart.typing import ResponseTypes from quart.typing import ResponseTypes
from werkzeug.sansio.response import Response as BaseResponse
from tutor.plugins.v1 import discover_package from tutor.plugins.v1 import discover_package
from tutordeck.server.utils import current_page_plugins, pagination_context from tutordeck.server.utils import current_page_plugins, pagination_context
@ -80,7 +80,7 @@ async def plugin_store_list() -> str:
"name": p.name, "name": p.name,
"url": p.url, "url": p.url,
"index": p.index, "index": p.index,
"author": p.author.split("<")[0].strip(), "author": tutorclient.Client.get_plugin_author(p),
"description": p.short_description, "description": p.short_description,
"is_installed": p.name in g.installed_plugins, "is_installed": p.name in g.installed_plugins,
"is_enabled": p.name in g.enabled_plugins, "is_enabled": p.name in g.enabled_plugins,
@ -102,13 +102,16 @@ async def plugin_store_list() -> str:
@app.get("/plugin/installed/list") @app.get("/plugin/installed/list")
async def plugin_installed_list() -> str: async def plugin_installed_list() -> str:
# TODO IMPORTANT this displays only the plugins that are in the store. When a plugin
# is installed locally but not in the store, we must display it here anyway.
# TODO this is duplicated code from plugin_store_list
search_query = request.args.get("search", "") search_query = request.args.get("search", "")
plugins: list[dict[str, t.Any]] = [ plugins: list[dict[str, t.Any]] = [
{ {
"name": p.name, "name": p.name,
"url": p.url, "url": p.url,
"index": p.index, "index": p.index,
"author": p.author.split("<")[0].strip(), "author": tutorclient.Client.get_plugin_author(p),
"description": p.short_description, "description": p.short_description,
"is_enabled": p.name in g.enabled_plugins, "is_enabled": p.name in g.enabled_plugins,
} }
@ -129,15 +132,16 @@ async def plugin(name: str) -> Response:
if not index_entry: if not index_entry:
return Response("Plugin not found", status=404) return Response("Plugin not found", status=404)
# TODO this seq_command_executed argument is confusing and causing issues, for
# instance with the "unset" button. We need to get rid of it.
seq_command_executed = request.args.get("seq_command_executed") seq_command_executed = request.args.get("seq_command_executed")
author = index_entry.author.split("<")[0].strip()
description = markdown(index_entry.description) description = markdown(index_entry.description)
rendered_template = await render_template( rendered_template = await render_template(
"plugin.html", "plugin.html",
plugin_name=name, plugin_name=name,
is_enabled=name in g.enabled_plugins, is_enabled=name in g.enabled_plugins,
is_installed=name in g.installed_plugins, is_installed=name in g.installed_plugins,
author_name=author, author_name=tutorclient.Client.get_plugin_author(index_entry),
plugin_description=description, plugin_description=description,
seq_command_executed=seq_command_executed, seq_command_executed=seq_command_executed,
plugin_config_unique=tutorclient.Client.plugin_config_unique(name), plugin_config_unique=tutorclient.Client.plugin_config_unique(name),
@ -146,6 +150,7 @@ async def plugin(name: str) -> Response:
) )
# Redirect to plugin page # Redirect to plugin page
# TODO this is useful only after a POST to plugin/<name>/update. I don't think these two things should be handled in the same place.
response = Response(rendered_template, status=200, content_type="text/html") response = Response(rendered_template, status=200, content_type="text/html")
response.headers["HX-Redirect"] = url_for( response.headers["HX-Redirect"] = url_for(
"plugin", name=name, seq_command_executed=seq_command_executed "plugin", name=name, seq_command_executed=seq_command_executed
@ -188,11 +193,12 @@ async def plugin_toggle(name: str) -> Response:
@app.post("/plugin/<name>/install") @app.post("/plugin/<name>/install")
async def plugin_install(name: str) -> WerkzeugResponse: async def plugin_install(name: str) -> BaseResponse:
async def bg_install_and_reload() -> None: async def bg_install_and_reload() -> None:
tutorclient.CliPool.run_parallel(app, ["plugins", "install", name]) tutorclient.CliPool.run_parallel(app, ["plugins", "install", name])
while tutorclient.CliPool.THREAD and tutorclient.CliPool.THREAD.is_alive(): while tutorclient.CliPool.THREAD and tutorclient.CliPool.THREAD.is_alive():
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
# TODO this is hackish. How can we improve?
discover_package(importlib_metadata.entry_points().__getitem__(name)) discover_package(importlib_metadata.entry_points().__getitem__(name))
asyncio.create_task(bg_install_and_reload()) asyncio.create_task(bg_install_and_reload())
@ -205,7 +211,7 @@ async def plugin_install(name: str) -> WerkzeugResponse:
@app.post("/plugin/<name>/upgrade") @app.post("/plugin/<name>/upgrade")
async def plugin_upgrade(name: str) -> WerkzeugResponse: async def plugin_upgrade(name: str) -> BaseResponse:
tutorclient.CliPool.run_parallel(app, ["plugins", "upgrade", name]) tutorclient.CliPool.run_parallel(app, ["plugins", "upgrade", name])
return redirect( return redirect(
url_for( url_for(
@ -344,7 +350,7 @@ async def suggest() -> Response:
@app.post("/command") @app.post("/command")
async def command() -> WerkzeugResponse: async def command() -> BaseResponse:
form = await request.form form = await request.form
command_string = form.get("command", "") command_string = form.get("command", "")
command_args = command_string.split() command_args = command_string.split()

View File

@ -8,16 +8,12 @@ Developer Mode
Search for any tutor command and execute it with a single click. Search for any tutor command and execute it with a single click.
{% endblock %} {% endblock %}
{% block page_button %}
{% endblock %}
{% block searchbar %} {% block searchbar %}
{% endblock %} {% endblock %}
{% block warning %} {% block warning %}
{% endblock %} {% endblock %}
{% set sidebar_active_tab = "advanced" %} {% set sidebar_active_tab = "advanced" %}
{% block workspace_content %} {% block workspace_content %}

View File

@ -40,7 +40,7 @@
{% else %} {% else %}
<p>N/A</p> <p>N/A</p>
{% endif %} {% endif %}
<button type="submit">Update All</button> <button type="submit">Save changes</button>
</form> </form>
{% endif %} {% endif %}
@ -79,6 +79,7 @@
bar.style.display = isPluginInstalled ? 'flex' : 'none'; bar.style.display = isPluginInstalled ? 'flex' : 'none';
} }
async function checkIfPluginInstalled(pluginName) { async function checkIfPluginInstalled(pluginName) {
// TODO let's avoid hard-coded urls
const response = await fetch(`/plugin/${pluginName}/is-installed`); const response = await fetch(`/plugin/${pluginName}/is-installed`);
const data = await response.json(); const data = await response.json();
return data.installed; return data.installed;

View File

@ -114,7 +114,8 @@ class Cli:
self.log_to_file(e.args[0]) self.log_to_file(e.args[0])
self.log_to_file("\nCancelled!\n") self.log_to_file("\nCancelled!\n")
except SystemExit: except SystemExit:
# TODO Is there a better way to notify command completion??? # TODO Is there a better way to notify command completion??? The
# frontend relies on this hard-coded string to detect launch completion.
self.log_to_file("\nSuccess!") self.log_to_file("\nSuccess!")
def stop(self) -> None: def stop(self) -> None:
@ -326,9 +327,7 @@ class Client:
@classmethod @classmethod
def plugins_matching_pattern(cls, pattern: str) -> list[str]: def plugins_matching_pattern(cls, pattern: str) -> list[str]:
return [ return [
plugin._data["name"] plugin.name for plugin in cls.plugins_in_store() if plugin.match(pattern)
for plugin in cls.plugins_in_store()
if plugin.match(pattern)
] ]
@classmethod @classmethod
@ -356,19 +355,25 @@ class Client:
key: user_config.get(key, value) for key, value in config_defaults.items() key: user_config.get(key, value) for key, value in config_defaults.items()
} }
@classmethod
def get_plugin_author(cls, index_entry: tutor.plugins.indexes.IndexEntry) -> str:
return index_entry.author.split("<")[0].strip()
@classmethod @classmethod
def autocomplete(cls, partial_command: str) -> list[dict[str, str]]: def autocomplete(cls, partial_command: str) -> list[dict[str, str]]:
"""
Handle CLI command completion via click_repl.ClickCompleter
https://github.com/click-contrib/click-repl/blob/master/click_repl/_completer.py
"""
cli = tutor.commands.cli.cli cli = tutor.commands.cli.cli
ctx = click.Context(cli, info_name=cli.name, parent=None) ctx = click.Context(cli, info_name=cli.name, parent=None)
completer = click_repl.ClickCompleter(cli, ctx) completer = click_repl.ClickCompleter(cli, ctx)
document = Document(partial_command, len(partial_command)) document = Document(partial_command, len(partial_command))
completions = list(completer.get_completions(document, None)) completions = list(completer.get_completions(document, None))
suggestions = [] return [
for completion in completions:
suggestions.append(
{ {
"text": completion.text, "text": completion.text,
"display": completion.display, "display": completion.display,
} }
) for completion in completions
return suggestions ]