From 6647a3b442d4f4c230369513315a366811cedeab Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 14 Apr 2026 22:12:48 +0200 Subject: [PATCH] docs: consolidate developer documentation for the Activity API Replaces the one-line stub with comprehensive inline documentation covering all extension interfaces (IProvider, ISetting, IFilter), event creation, rich object strings, activity merging, info.xml registration, and the full REST API v2 reference with examples. Links to the in-repo docs for additional detail. AI-Assisted-By: Claude Opus 4.6 Signed-off-by: Anna Larch --- developer_manual/client_apis/activity-api.rst | 486 +++++++++++++++++- 1 file changed, 482 insertions(+), 4 deletions(-) diff --git a/developer_manual/client_apis/activity-api.rst b/developer_manual/client_apis/activity-api.rst index d71d6fe9cfc..b78706564fa 100644 --- a/developer_manual/client_apis/activity-api.rst +++ b/developer_manual/client_apis/activity-api.rst @@ -1,5 +1,483 @@ -======== -Activity -======== +.. _activity-api: -Find the documentation in the `Activity app repo `__. +============ +Activity API +============ + +The Activity app provides an API for other Nextcloud apps to publish +events, display them in the activity stream, and let users control +their notification preferences. It also exposes a REST API for +clients to consume the activity stream. + +The extension API consists of four interfaces, all in the ``OCP`` +namespace: + +* **IEvent** — The activity event object +* **IProvider** — Parses and translates activities for display +* **ISetting** — Defines activity types and notification preferences +* **IFilter** — Adds filters to the activity stream sidebar + +All three extension interfaces (IProvider, ISetting, IFilter) are +registered in your app's ``appinfo/info.xml``. + + +Registering activity components +------------------------------- + +Add an ```` section to your ``appinfo/info.xml`` to register +providers, settings, and filters: + +.. code-block:: xml + + + + ... + + + OCA\MyApp\Activity\Setting + + + + OCA\MyApp\Activity\Filter + + + + OCA\MyApp\Activity\Provider + + + + +Each value is the fully qualified class name of the implementation. + + +Creating and publishing events +------------------------------ + +To create and publish an activity event, generate an ``IEvent`` from +the activity manager and call ``publish()``: + +.. code-block:: php + + activityManager->generateEvent(); + $event->setApp('myapp') + ->setType('myapp_action') + ->setAffectedUser('targetUser') + ->setSubject('subject_key', ['param1' => 'value1']) + ->setObject('myobject', 42, 'Object Name'); + $this->activityManager->publish($event); + +Required fields +^^^^^^^^^^^^^^^ + +The following must be set before publishing: + +* ``setApp()`` — Your app ID +* ``setType()`` — Must match an ``ISetting::getIdentifier()`` +* ``setAffectedUser()`` — The user who will see this activity +* ``setSubject()`` — A subject key and optional parameters +* ``setObject()`` — The object type, ID, and name + +Optional fields +^^^^^^^^^^^^^^^ + +* ``setAuthor()`` — If unset, the current user is used +* ``setTimestamp()`` — If unset, the current time is used +* ``setMessage()`` — An additional message key and parameters +* ``setLink()`` — Should typically be set in ``IProvider::parse()`` + instead +* ``setIcon()`` — Should typically be set in ``IProvider::parse()`` + instead + +.. note:: Do not call ``setParsedSubject()``, ``setRichSubject()``, + ``setParsedMessage()``, ``setRichMessage()``, or + ``setChildEvent()`` when publishing. These are not persisted and + should be set in your provider's ``parse()`` method. + + +Implementing a provider +----------------------- + +Providers implement ``OCP\Activity\IProvider`` to parse, translate, +and beautify activities for display. The interface has a single +``parse()`` method. + +Check responsibility +^^^^^^^^^^^^^^^^^^^^ + +First, check whether the event belongs to your app. If not, throw +``UnknownActivityException`` so the activity app passes it to the +next provider: + +.. code-block:: php + + getApp() !== 'myapp') { + throw new \OCP\Activity\Exceptions\UnknownActivityException(); + } + + // ... parse the event + } + +.. note:: ``UnknownActivityException`` was added in Nextcloud 30. + For older versions, throw ``\InvalidArgumentException`` instead. + +Set the parsed subject +^^^^^^^^^^^^^^^^^^^^^^ + +At minimum, you must call ``setParsedSubject()`` with a translated, +plain-text string: + +.. code-block:: php + + $event->setParsedSubject( + $this->l->t('You created %1$s', [$event->getObjectName()]) + ); + +Additionally, you should call: + +* ``setIcon()`` — A full URL to an icon for the activity +* ``setRichSubject()`` — A translated string with placeholders and + a parameter array for rich display + +.. note:: Starting with Nextcloud 26, calling ``setRichSubject()`` + automatically generates a parsed subject, so a separate + ``setParsedSubject()`` call is no longer required. + +Rich object strings +^^^^^^^^^^^^^^^^^^^ + +Rich subjects allow the UI to render interactive elements like file +links and user avatars. The ``setRichSubject()`` method takes a +translated string with placeholders and an array of typed objects: + +.. code-block:: php + + $event->setRichSubject( + $this->l->t('You added {file} to your favorites'), + ['file' => [ + 'type' => 'file', + 'id' => (string) $event->getObjectId(), + 'name' => basename($event->getObjectName()), + 'path' => $event->getObjectName(), + ]] + ); + +Available object types and their required keys are defined in +`OCP\\RichObjectStrings\\Definitions `__. + +Short vs. long translations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +File-related activities support a short form (e.g. "Added to +favorites") for the sidebar and a long form (e.g. "You added +hello.jpg to your favorites") for the stream. Check +``IManager::isFormattingFilteredObject()`` to decide which form to +use. + +Merging activities +^^^^^^^^^^^^^^^^^^ + +Related activities can be merged automatically using +``OCP\Activity\IEventMerger`` (inject via dependency injection). +The merger combines events when all of the following are met: + +* Same ``getApp()`` +* No message set (``getMessage()`` is empty) +* Same ``getSubject()`` +* Same ``getObjectType()`` +* Time difference is less than 3 hours +* At most 5 events are merged + +Do not merge events manually if these requirements are not met. + + +Implementing a setting +---------------------- + +Settings define activity types and appear on the personal +notification preferences page. They implement +``OCP\Activity\ISetting``. + +.. list-table:: + :header-rows: 1 + :widths: 25 75 + + * - Method + - Description + * - ``getIdentifier()`` + - Unique ID (lowercase ``a-z`` and underscores only). **Must + match** the value used in ``IEvent::setType()``. + * - ``getName()`` + - Translated, short description. May use ```` for + emphasis. + * - ``getIcon()`` + - Absolute URL to a 32x32 pixel icon (SVG preferred). + * - ``getPriority()`` + - ``0`` (first) to ``100`` (last). Use ``70`` as default. + Values below ``10`` are reserved. + * - ``isDefaultEnabledStream()`` + - Whether stream display is enabled by default for new users. + * - ``isDefaultEnabledMail()`` + - Whether email notifications are enabled by default. + * - ``canChangeStream()`` + - Whether users can toggle stream display. + * - ``canChangeMail()`` + - Whether users can toggle email notifications. + +When both ``canChangeStream()`` and ``canChangeMail()`` return +``false``, the setting is hidden from the personal page entirely. + + +Implementing a filter +--------------------- + +Filters restrict the activity stream and appear in the sidebar +navigation. They implement ``OCP\Activity\IFilter``. + +.. list-table:: + :header-rows: 1 + :widths: 25 75 + + * - Method + - Description + * - ``getIdentifier()`` + - Unique ID (lowercase ``a-z`` and underscores only). Used in + URLs. + * - ``getName()`` + - Translated label, 1-3 short words. + * - ``getIcon()`` + - Absolute URL to a 32x32 pixel icon (SVG preferred). + * - ``getPriority()`` + - ``0`` (first) to ``100`` (last). Use ``70`` as default. + Values below ``10`` are reserved. + * - ``allowedApps()`` + - Return an array of app IDs to show, or an empty array for + all apps. + * - ``filterTypes()`` + - Receives a list of type identifiers (from ``ISetting``), + return a filtered subset. Return the input unchanged to show + all types. + +Example filtering to only show calendar todo activities: + +.. code-block:: php + + public function filterTypes(array $types): array { + return array_intersect(['calendar_todo'], $types); + } + + +REST API (v2) +------------- + +The Activity app exposes a REST API for clients to consume the +activity stream. + +Capabilities +^^^^^^^^^^^^ + +The API advertises its features via capabilities: + +.. code-block:: xml + + GET /ocs/v2.php/cloud/capabilities + + + + filters + previews + rich-strings + + + +Endpoints +^^^^^^^^^ + +.. code-block:: text + + GET /ocs/v2.php/apps/activity/api/v2/activity + GET /ocs/v2.php/apps/activity/api/v2/activity/{filter} + GET /ocs/v2.php/apps/activity/api/v2/activity/filters + +Parameters +^^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + :widths: 20 15 65 + + * - Name + - Type + - Description + * - ``since`` + - int + - ID of the last activity seen (optional) + * - ``limit`` + - int + - Number of activities to return (default: ``50``, optional) + * - ``object_type`` + - string + - Filter by object type. Requires ``object_id`` and the + ``filter`` type filter (optional) + * - ``object_id`` + - string + - Filter by object ID. Requires ``object_type`` and the + ``filter`` type filter (optional) + * - ``sort`` + - string + - ``asc`` or ``desc`` (default: ``desc``, optional) + +HTTP status codes +^^^^^^^^^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + :widths: 20 80 + + * - Code + - Description + * - ``200 OK`` + - Activities returned + * - ``204 No Content`` + - User has no activities selected for the stream + * - ``304 Not Modified`` + - ETag matches or end of list reached + * - ``403 Forbidden`` + - Offset activity belongs to another user, or not logged in + * - ``404 Not Found`` + - Unknown filter + +Response headers +^^^^^^^^^^^^^^^^ + +``Link`` + URL for the next page of results, including all parameters: + ``; rel="next"`` + +``X-Activity-First-Known`` + ID of the first known activity (when ``since`` was not recognized). + +``X-Activity-Last-Given`` + ID to use as ``since`` for the next request. + +Activity element fields +^^^^^^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + :widths: 20 15 65 + + * - Field + - Type + - Description + * - ``activity_id`` + - int + - Unique activity ID + * - ``datetime`` + - string + - ISO 8601 timestamp + * - ``app`` + - string + - App that created the activity + * - ``type`` + - string + - Activity type identifier + * - ``user`` + - string + - User ID of the actor (may be empty) + * - ``subject`` + - string + - Translated plain-text subject + * - ``subject_rich`` + - array + - Rich subject: ``[0]`` is the template string, ``[1]`` is the + parameters object + * - ``message`` + - string + - Translated plain-text message (optional) + * - ``message_rich`` + - array + - Rich message, same format as ``subject_rich`` (optional) + * - ``icon`` + - string + - Full URL to the activity icon (optional) + * - ``link`` + - string + - Full URL to the relevant location (optional) + * - ``object_type`` + - string + - Type of the related object (optional) + * - ``object_id`` + - int + - ID of the related object (optional) + * - ``object_name`` + - string + - Name or path of the related object (optional) + * - ``previews`` + - array + - List of preview elements for file activities (optional) + +Example response +^^^^^^^^^^^^^^^^ + +.. code-block:: json + + { + "activity_id": 1, + "datetime": "2015-11-20T12:49:31+00:00", + "app": "files", + "type": "file_created", + "user": "test1", + "subject": "test1 created hello.txt", + "subject_rich": { + "0": "{user1} created {file1}", + "1": { + "user1": { + "type": "user", + "id": "test1", + "name": "Test User" + }, + "file1": { + "type": "file", + "id": 23, + "name": "hello.txt", + "path": "/test/hello.txt" + } + } + }, + "icon": "https://cloud.example.com/apps/files/img/add-color.svg", + "link": "https://cloud.example.com/apps/files/?dir=/test", + "object_type": "files", + "object_id": 23, + "object_name": "/test/hello.txt", + "previews": [ + { + "source": "https://cloud.example.com/core/preview.png?file=/hello.txt&x=150&y=150", + "link": "https://cloud.example.com/apps/files/?dir=/test&scrollto=hello.txt", + "mimeType": "text/plain", + "fileId": 23, + "view": "files", + "isMimeTypeIcon": false, + "filename": "hello.txt" + } + ] + } + +.. note:: Additional fields not listed above may appear in responses + for backwards compatibility but should be ignored. + + +Further reading +--------------- + +The Activity app repository contains additional documentation with +more detailed examples: + +* `Creating events `__ +* `Implementing a provider `__ +* `Implementing a setting `__ +* `Implementing a filter `__ +* `REST API v2 endpoint `__