track_event() mechanism for analytics and plugins

* Closes #2240
* Documentation for event plugin hooks, refs #2240
* Include example track_event plugin in docs, refs #2240
* Tests for track_event() and register_events() hooks, refs #2240
* Initial documentation for core events, refs #2240
* Internals documentation for datasette.track_event()
This commit is contained in:
Simon Willison 2024-01-31 15:21:40 -08:00 committed by GitHub
commit bcc4f6bf1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 614 additions and 10 deletions

View file

@ -1759,3 +1759,103 @@ top_canned_query(datasette, request, database, query_name)
The name of the canned query.
Returns HTML to be displayed at the top of the canned query page.
.. _plugin_event_tracking:
Event tracking
--------------
Datasette includes an internal mechanism for tracking analytical events. This can be used for analytics, but can also be used by plugins that want to listen out for when key events occur (such as a table being created) and take action in response.
Plugins can register to receive events using the ``track_event`` plugin hook.
They can also define their own events for other plugins to receive using the ``register_events`` plugin hook, combined with calls to the ``datasette.track_event(...)`` internal method.
.. _plugin_hook_track_event:
track_event(datasette, event)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``datasette`` - :ref:`internals_datasette`
You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)``.
``event`` - ``Event``
Information about the event, represented as an instance of a subclass of the ``Event`` base class.
This hook will be called any time an event is tracked by code that calls the :ref:`datasette.track_event(...) <datasette_track_event>` internal method.
The ``event`` object will always have the following properties:
- ``name``: a string representing the name of the event, for example ``logout`` or ``create-table``.
- ``actor``: a dictionary representing the actor that triggered the event, or ``None`` if the event was not triggered by an actor.
- ``created``: a ``datatime.datetime`` object in the ``timezone.utc`` timezone representing the time the event object was created.
Other properties on the event will be available depending on the type of event. You can also access those as a dictionary using ``event.properties()``.
The events fired by Datasette core are :ref:`documented here <events>`.
This example plugin logs details of all events to standard error:
.. code-block:: python
from datasette import hookimpl
import json
import sys
@hookimpl
def track_event(event):
name = event.name
actor = event.actor
properties = event.properties()
msg = json.dumps(
{
"name": name,
"actor": actor,
"properties": properties,
}
)
print(msg, file=sys.stderr, flush=True)
.. _plugin_hook_register_events:
register_events(datasette)
~~~~~~~~~~~~~~~~~~~~~~~~~~
``datasette`` - :ref:`internals_datasette`
You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)``.
This hook should return a list of ``Event`` subclasses that represent custom events that the plugin might send to the ``datasette.track_event()`` method.
This example registers event subclasses for ``ban-user`` and ``unban-user`` events:
.. code-block:: python
from dataclasses import dataclass
from datasette import hookimpl, Event
@dataclass
class BanUserEvent(Event):
name = "ban-user"
user: dict
@dataclass
class UnbanUserEvent(Event):
name = "unban-user"
user: dict
@hookimpl
def register_events():
return [BanUserEvent, UnbanUserEvent]
The plugin can then call ``datasette.track_event(...)`` to send a ``ban-user`` event:
.. code-block:: python
await datasette.track_event(
BanUserEvent(user={"id": 1, "username": "cleverbot"})
)