Skip to content

Commit

Permalink
Documentation for event plugin hooks, refs #2240
Browse files Browse the repository at this point in the history
  • Loading branch information
simonw committed Jan 31, 2024
1 parent f003284 commit 7e95feb
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 6 deletions.
1 change: 1 addition & 0 deletions datasette/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datasette.permissions import Permission # noqa
from datasette.version import __version_info__, __version__ # noqa
from datasette.events import Event # noqa
from datasette.utils.asgi import Forbidden, NotFound, Request, Response # noqa
from datasette.utils import actor_matches_allow # noqa
from datasette.views import Context # noqa
Expand Down
7 changes: 4 additions & 3 deletions datasette/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -885,10 +885,11 @@ async def track_event(self, event: Event):
assert isinstance(event, self.event_classes), "Invalid event type: {}".format(
type(event)
)
properties = dataclasses.asdict(event)
actor = properties.pop("actor")
for hook in pm.hook.track_event(
datasette=self, name=event.name, actor=actor, properties=properties
datasette=self,
name=event.name,
actor=event.actor,
properties=event.properties(),
):
await await_me_maybe(hook)

Expand Down
7 changes: 6 additions & 1 deletion datasette/events.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from abc import ABC, abstractproperty
from dataclasses import dataclass
from dataclasses import asdict, dataclass
from datasette.hookspecs import hookimpl


Expand All @@ -11,6 +11,11 @@ def name(self):

actor: dict

def properties(self):
properties = asdict(self)
properties.pop("actor", None)
return properties


@dataclass
class LogoutEvent(Event):
Expand Down
4 changes: 2 additions & 2 deletions datasette/hookspecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ def handle_exception(datasette, request, exception):


@hookspec
def track_event(datasette, name, actor, properties):
"""Respond to a named event tracked by Datasette"""
def track_event(datasette, event):
"""Respond to an event tracked by Datasette"""


@hookspec
Expand Down
71 changes: 71 additions & 0 deletions docs/plugin_hooks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1759,3 +1759,74 @@ 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, name, 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 through code calling the ``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.

Other properties on the event will be available depending on the type of event. TODO: Link to documentation of these.

.. _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"}))
9 changes: 9 additions & 0 deletions docs/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,15 @@ If you run ``datasette plugins --all`` it will include default plugins that ship
"skip_csrf"
]
},
{
"name": "datasette.events",
"static": false,
"templates": false,
"version": null,
"hooks": [
"register_events"
]
},
{
"name": "datasette.facets",
"static": false,
Expand Down

0 comments on commit 7e95feb

Please sign in to comment.