diff --git a/code_samples/recent_activity/config/append_to_services.yaml b/code_samples/recent_activity/config/append_to_services.yaml new file mode 100644 index 0000000000..aad27a8e96 --- /dev/null +++ b/code_samples/recent_activity/config/append_to_services.yaml @@ -0,0 +1,3 @@ +services: + + App\ActivityLog\ClassNameMapper\MyFeatureNameMapper: ~ diff --git a/code_samples/recent_activity/src/ActivityLog/ClassNameMapper/MyFeatureNameMapper.php b/code_samples/recent_activity/src/ActivityLog/ClassNameMapper/MyFeatureNameMapper.php new file mode 100644 index 0000000000..92b08b11d8 --- /dev/null +++ b/code_samples/recent_activity/src/ActivityLog/ClassNameMapper/MyFeatureNameMapper.php @@ -0,0 +1,24 @@ + 'my_feature'; + } + + public static function getTranslationMessages(): array + { + return [ + (new Message('ibexa.activity_log.search_form.object_class.my_feature', 'ibexa_activity_log')) + ->setDesc('My Feature'), + ]; + } +} diff --git a/code_samples/recent_activity/src/Command/ActivityLogContextTestCommand.php b/code_samples/recent_activity/src/Command/ActivityLogContextTestCommand.php new file mode 100644 index 0000000000..342b268c8c --- /dev/null +++ b/code_samples/recent_activity/src/Command/ActivityLogContextTestCommand.php @@ -0,0 +1,87 @@ +activityLogService = $activityLogService; + $this->contentService = $contentService; + $this->contentTypeService = $contentTypeService; + $this->eventDispatcher = $eventDispatcher; + $this->permissionResolver = $permissionResolver; + $this->userService = $userService; + } + + protected function configure(): void + { + $this->addArgument('id', InputArgument::REQUIRED, 'A test number'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $id = $input->getArgument('id'); + $this->permissionResolver->setCurrentUserReference($this->userService->loadUserByLogin('admin')); + + $this->activityLogService->prepareContext('my_feature', 'Operation description'); + + $activityLogStruct = $this->activityLogService->build(MyFeature::class, $id, 'init'); + $activityLogStruct->setObjectName("My Feature #$id"); + $this->activityLogService->save($activityLogStruct); + + $contentCreateStruct = $this->contentService->newContentCreateStruct($this->contentTypeService->loadContentTypeByIdentifier('folder'), 'eng-GB'); + $contentCreateStruct->setField('name', "My Feature Folder #$id", 'eng-GB'); + $locationCreateStruct = new LocationCreateStruct(['parentLocationId' => 2]); + $draft = $this->contentService->createContent($contentCreateStruct, [$locationCreateStruct]); + $this->contentService->publishVersion($draft->versionInfo); + + $event = new MyFeatureEvent(new MyFeature(['id' => $id, 'name' => "My Feature #$id"]), 'simulate'); + $this->eventDispatcher->dispatch($event); + + $activityLogStruct = $this->activityLogService->build(MyFeature::class, $id, 'complete'); + $activityLogStruct->setObjectName("My Feature #$id"); + $this->activityLogService->save($activityLogStruct); + + $this->activityLogService->dismissContext(); + + return Command::SUCCESS; + } +} diff --git a/code_samples/recent_activity/src/Command/DispatchMyFeatureEventCommand.php b/code_samples/recent_activity/src/Command/DispatchMyFeatureEventCommand.php new file mode 100644 index 0000000000..070c4a7753 --- /dev/null +++ b/code_samples/recent_activity/src/Command/DispatchMyFeatureEventCommand.php @@ -0,0 +1,36 @@ +eventDispatcher = $eventDispatcher; + parent::__construct(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $event = new MyFeatureEvent(new MyFeature(['id' => 123, 'name' => 'Logged Name']), 'simulate'); + $this->eventDispatcher->dispatch($event); + + $event = new MyFeatureEvent((object) ['id' => 456, 'name' => 'Some Name'], 'simulate'); + $this->eventDispatcher->dispatch($event); + + return Command::SUCCESS; + } +} diff --git a/code_samples/recent_activity/src/Command/MonitorRecentContentCreationCommand.php b/code_samples/recent_activity/src/Command/MonitorRecentContentCreationCommand.php new file mode 100644 index 0000000000..5749775ed9 --- /dev/null +++ b/code_samples/recent_activity/src/Command/MonitorRecentContentCreationCommand.php @@ -0,0 +1,80 @@ +permissionResolver = $permissionResolver; + $this->userService = $userService; + $this->activityLogService = $activityLogService; + parent::__construct(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $query = new Query([ + new Criterion\ObjectCriterion(Content::class), + new Criterion\ActionCriterion([ActivityLogServiceInterface::ACTION_CREATE]), + new Criterion\LoggedAtCriterion(new \DateTime('- 1 hour'), Criterion\LoggedAtCriterion::GTE), + ], [new LoggedAtSortClause(LoggedAtSortClause::DESC)], 0, 10); + + $io = new SymfonyStyle($input, $output); + + $this->permissionResolver->setCurrentUserReference($this->userService->loadUserByLogin('admin')); + + foreach ($this->activityLogService->findGroups($query) as $activityLogGroup) { + if ($activityLogGroup->getSource()) { + $io->section($activityLogGroup->getSource()->getName()); + } + if ($activityLogGroup->getDescription()) { + $io->text($activityLogGroup->getDescription()); + } + $table = []; + foreach ($activityLogGroup->getActivityLogs() as $activityLog) { + /** @var \Ibexa\Contracts\Core\Repository\Values\Content\Content $content */ + $content = $activityLog->getRelatedObject(); + $name = $content && $content->getName() && $content->getName() !== $activityLog->getObjectName() ? "“{$content->getName()}” (formerly “{$activityLog->getObjectName()}”)" : "“{$activityLog->getObjectName()}”"; + $table[] = [ + $activityLogGroup->getLoggedAt()->format(\DateTime::ATOM), + $activityLog->getObjectId(), + $name, + $activityLog->getAction(), + $activityLogGroup->getUser()->login, + ]; + } + $io->table([ + 'Logged at', + 'Obj. ID', + 'Object Name', + 'Action', + 'User', + ], $table); + } + + return Command::SUCCESS; + } +} diff --git a/code_samples/recent_activity/src/Event/MyFeatureEvent.php b/code_samples/recent_activity/src/Event/MyFeatureEvent.php new file mode 100644 index 0000000000..53dbfcb8cf --- /dev/null +++ b/code_samples/recent_activity/src/Event/MyFeatureEvent.php @@ -0,0 +1,28 @@ +object = $object; + $this->action = $action; + } + + public function getObject(): object + { + return $this->object; + } + + public function getAction(): string + { + return $this->action; + } +} diff --git a/code_samples/recent_activity/src/EventSubscriber/MyFeatureEventSubscriber.php b/code_samples/recent_activity/src/EventSubscriber/MyFeatureEventSubscriber.php new file mode 100644 index 0000000000..7a08832154 --- /dev/null +++ b/code_samples/recent_activity/src/EventSubscriber/MyFeatureEventSubscriber.php @@ -0,0 +1,36 @@ +activityLogService = $activityLogService; + } + + public static function getSubscribedEvents(): array + { + return [ + MyFeatureEvent::class => 'onMyFeatureEvent', + ]; + } + + public function onMyFeatureEvent(MyFeatureEvent $event): void + { + /** @var App\MyFeature\MyFeature $object */ + $object = $event->getObject(); + $className = get_class($object); + $id = (string)$object->id; + $action = $event->getAction(); + $activityLog = $this->activityLogService->build($className, $id, $action); + $activityLog->setObjectName($object->name); + $this->activityLogService->save($activityLog); + } +} diff --git a/code_samples/recent_activity/src/EventSubscriber/MyFeaturePostActivityListLoadEventSubscriber.php b/code_samples/recent_activity/src/EventSubscriber/MyFeaturePostActivityListLoadEventSubscriber.php new file mode 100644 index 0000000000..93d87e8a7b --- /dev/null +++ b/code_samples/recent_activity/src/EventSubscriber/MyFeaturePostActivityListLoadEventSubscriber.php @@ -0,0 +1,56 @@ +myFeatureService = $myFeatureService; + } + + public static function getSubscribedEvents(): array + { + return [ + PostActivityGroupListLoadEvent::class => ['loadMyFeature'], + ]; + } + + public function loadMyFeature(PostActivityGroupListLoadEvent $event): void + { + $visitedIds = []; + $list = $event->getList(); + foreach ($list as $logGroup) { + foreach ($logGroup->getActivityLogs() as $log) { + if ($log->getObjectClass() !== MyFeature::class) { + continue; + } + + $id = (int)$log->getObjectId(); + try { + if (!array_key_exists($id, $visitedIds)) { + $visitedIds[$id] = $this->myFeatureService->load($id); + } + + if ($visitedIds[$id] === null) { + continue; + } + + $log->setRelatedObject($visitedIds[$id]); + } catch (NotFoundException|UnauthorizedException $e) { + $visitedIds[$id] = null; + } + } + } + } +} diff --git a/code_samples/recent_activity/src/MyFeature/MyFeature.php b/code_samples/recent_activity/src/MyFeature/MyFeature.php new file mode 100644 index 0000000000..e5b9b50be3 --- /dev/null +++ b/code_samples/recent_activity/src/MyFeature/MyFeature.php @@ -0,0 +1,13 @@ + $propertyValue) { + $this->$propertyName = $propertyValue; + } + } +} diff --git a/code_samples/recent_activity/src/MyFeature/MyFeatureService.php b/code_samples/recent_activity/src/MyFeature/MyFeatureService.php new file mode 100644 index 0000000000..1f0428bf28 --- /dev/null +++ b/code_samples/recent_activity/src/MyFeature/MyFeatureService.php @@ -0,0 +1,11 @@ + $myFeatureId, 'name' => 'Actual Name']); + } +} diff --git a/code_samples/recent_activity/templates/themes/admin/activity_log/ui/default.html.twig b/code_samples/recent_activity/templates/themes/admin/activity_log/ui/default.html.twig new file mode 100644 index 0000000000..bb933d1951 --- /dev/null +++ b/code_samples/recent_activity/templates/themes/admin/activity_log/ui/default.html.twig @@ -0,0 +1,5 @@ +{% extends '@IbexaActivityLog/themes/admin/activity_log/ui/default.html.twig' %} + +{%- block activity_log_description_widget -%} + {{ dump(log) }} +{%- endblock activity_log_description_widget -%} diff --git a/code_samples/recent_activity/templates/themes/admin/activity_log/ui/my_feature/simulate.html.twig b/code_samples/recent_activity/templates/themes/admin/activity_log/ui/my_feature/simulate.html.twig new file mode 100644 index 0000000000..1fe2690c43 --- /dev/null +++ b/code_samples/recent_activity/templates/themes/admin/activity_log/ui/my_feature/simulate.html.twig @@ -0,0 +1,21 @@ +{% extends '@ibexadesign/activity_log/ui/default.html.twig' %} + +{%- block activity_log_description_widget -%} + {% if log.getRelatedObject() is not null %} + + {{- log.getRelatedObject().name -}} + + {% if log.getRelatedObject().name != log.getObjectName() %} + (was named “{{ log.getObjectName() }}”) + {% endif %} + {% else %} + {{ log.getObjectName() }} (which doesn't exist anymore) + {% endif %} +{%- endblock activity_log_description_widget -%} diff --git a/code_samples/recent_activity/translations/ibexa_activity_log.en.xlf b/code_samples/recent_activity/translations/ibexa_activity_log.en.xlf new file mode 100644 index 0000000000..eca5e97de8 --- /dev/null +++ b/code_samples/recent_activity/translations/ibexa_activity_log.en.xlf @@ -0,0 +1,16 @@ + + + +
+ + The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. +
+ + + My Feature + My Feature + key: ibexa.activity_log.search_form.object_class.my_feature + + +
+
diff --git a/docs/administration/img/activity_log_group.png b/docs/administration/img/activity_log_group.png new file mode 100644 index 0000000000..6fb7ad544d Binary files /dev/null and b/docs/administration/img/activity_log_group.png differ diff --git a/docs/administration/recent_activity/recent_activity.md b/docs/administration/recent_activity/recent_activity.md new file mode 100644 index 0000000000..a95d56f140 --- /dev/null +++ b/docs/administration/recent_activity/recent_activity.md @@ -0,0 +1,253 @@ +--- +description: Log and monitor activity through UI, PHP API and REST API. +--- + +# Recent activity [[% include 'snippets/experience_badge.md' %]] [[% include 'snippets/commerce_badge.md' %]] + +Recent activity log displays last actions in the repository (whatever their origin is, for example, Back Office, REST, migration, CLI, or CRON). + +To learn more about its Back Office usage and the actions logged by default, see [Recent activity in the User documentation]([[= user_doc =]]/recent_activity/recent_activity/). + +## Configuration and cronjob + +With some configuration, you can customize the log length in the database or on screen. +A command maintains the log size in database, it should be scheduled through CRON. + +- The configuration `ibexa.system..activity_log.pagination.activity_logs_limit` sets the number of log items shown per page in the Back Office (default value: 25). +A log item is a group of entries, or an entry without group. +- The configuration `ibexa.repositories..activity_log.truncate_after_days` sets the number of days a log entry is kept before it's deleted by the `ibexa:activity-log:truncate` command (default value: 30 days). + +For example, the following configuration sets 15 days of life to the log entries on the `default` repository, and 20 context groups per page for the `admin_group` SiteAccess group: + +```yaml +ibexa: + repositories: + default: + activity_log: + truncate_after_days: 15 + system: + admin_group: + activity_log: + pagination: + activity_logs_limit: 20 +``` + +To automate a regular truncation, the command `ibexa:activity-log:truncate` must be added to a crontab. +To minimize the number of entries to delete, it's recommended to execute the command more than one time a day. + +For every exact hour, the cronjob line is: +`0 * * * * cd [path-to-ibexa]; php bin/console ibexa:activity-log:truncate --quiet --env=prod` + +## Permission and security + +The ([`activity_log/read`](policies.md#activity-log)) policy gives a role the access to the **Admin** -> **Activity list**, the dashboard's **Recent activity** block, and the user profile's **Recent activity**. +It can be limited to "Only own logs" ([`ActivityLogOwner`](limitation_reference.md#activitylogowner-limitation)). + +The policy should be given to every roles having access to the Back Office, at least with the `ActivityLogOwner` owner limitation, +to allow them to use the "Recent activity" block in the [default dashboard](configure_default_dashboard.md) or their [custom dashboard](customize_dashboard.md). +This policy is required to view [activity log in user profile]([[= user_doc =]]/recent_activity/recent_activity/#user-profile), if [profile]([[= user_doc =]]/getting_started/get_started/#edit-user-profile) is enabled. + +!!! caution + + Do not assign `activity_log/read` permission to the Anonymous role, even with the owner limitation, because this role is shared among all unauthenticated users. + +## PHP API + +The `ActivityLogService` PHP API can be used to browse activity logs and write new entries. + +### Searching in the Activity Log groups + +You can search among the activity log entry groups with the `ActivityLogService::findGroups` method, by passing an `Ibexa\Contracts\ActivityLog\Values\ActivityLog\Query` object. +This `Query`'s constructor has four arguments: + +- `$criteria` - an array of criteria from `Ibexa\Contracts\ActivityLog\Values\ActivityLog\Criterion` combined as a logical AND. +- `$sortClauses` - an array of `Ibexa\Contracts\ActivityLog\Values\ActivityLog\SortClause`. +- `$offset` - a zero-based index integer indicating at which group to start, its default value is `0` (zero, nothing skipped). +- `$limit` - an integer as the maximum returned group count, default is 25. + +See [Activity Log Search Criteria reference](activity_log_criteria.md) and [Activity Log Search Sort Clauses reference](activity_log_sort_clauses.md) to discover query possibilities. + +In the following example, log groups that contain at least one creation of a Content item are displayed in terminal, with a maximum of 10 groups within the last hour. +It uses the default `admin` user that has a [permission](#permission-and-security) to list everyone's entries. + +```php hl_lines="39-43" +[[= include_file('code_samples/recent_activity/src/Command/MonitorRecentContentCreationCommand.php') =]] +``` + +```console +% php bin/console app:monitor-content-creation + +web +--- + + --------------------------- --------- --------------------------- -------- ---------- + Logged at Obj. ID Object Name Action User + --------------------------- --------- --------------------------- -------- ---------- + 2024-01-29T15:01:57+00:00 323 “Bar” (formerly “Folder”) create jane_doe + --------------------------- --------- --------------------------- -------- ---------- + +migration +--------- + + Migrating file: create_foo_company + --------------------------- --------- -------------------- -------------- ------- + Logged at Obj. ID Object Name Action User + --------------------------- --------- -------------------- -------------- ------- + 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ create admin + 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ publish admin + 2024-01-29T14:58:53+00:00 318 “Members“ create admin + 2024-01-29T14:58:53+00:00 318 “Members“ publish admin + 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ create_draft admin + 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ update admin + 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ publish admin + 2024-01-29T14:58:53+00:00 319 “Address Book“ create admin + 2024-01-29T14:58:53+00:00 319 “Address Book“ publish admin + 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ create_draft admin + 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ update admin + 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ publish admin + 2024-01-29T14:58:53+00:00 320 “HQ“ create admin + 2024-01-29T14:58:53+00:00 320 “HQ“ publish admin + 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ create_draft admin + 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ update admin + 2024-01-29T14:58:53+00:00 317 “Foo Company Ltd.“ publish admin + --------------------------- --------- -------------------- -------------- ------- +``` + +### Adding custom Activity Log entries + +!!! caution + + Keep activity logging as light as possible. + Do not make database requests or heavy computation at logging time. + Keep them for activity log list display time. + +#### Entry + +Your custom features can write into the activity log. + +First, inject `Ibexa\Contracts\ActivityLog\ActivityLogServiceInterface` into your PHP class from where you want to log an activity (such as a custom event subscriber, event listener, service, or controller). + +In the following example, an event subscriber is subscribing to an event dispatched by a custom feature. +This event has the information needed by a log entry (see details after the example). + +```php +[[= include_file('code_samples/recent_activity/src/EventSubscriber/MyFeatureEventSubscriber.php') =]] +``` + +`ActivityLogService::build()` function returns an `Ibexa\Contracts\ActivityLog\Values\CreateActivityLogStruct` which can then be passed to `ActivityLogService::save`. + +`ActivityLogService::build` has three arguments: + +- `$className` is a FQCN of the object actually manipulated by the feature, for example `Ibexa\Contracts\Core\Repository\Values\Content\Content::class` +- `$id` is an ID or identifier of the manipulated object, for example, the Content ID cast to string +- `$action` is an identifier of the performed object manipulation, or example, `create`, `update` or `delete` + +The returned `CreateActivityLogStruct` is always related to the currently logged-in user. + +You can still display activity log of an object which was deleted or renamed. +To store the name of the log, you need to use `CreateActivityLogStruct::setName` before saving the log entry. +This stored name can be used at the time of displaying information whether the associated object isn't available anymore, or to check if it has been renamed. + +#### Context group + +If you log several related entries at once, you can group them into a context. +Context is a set of actions done for the same purpose, for example, it could group the actions of a CRON that fetches third party data and updates Content items. +The built-in contexts include: + +- `web` - groups actions made in the Back Office, like the update and the publishing of a new content item's version +- `migration` - groups every action from a migration file execution + +A context group counts as one item in regard to `activity_logs_limit` configuration and `ActivityLogService::findGroups`'s `$limit` argument. + +To open a context group, use `ActivityLogService::prepareContext` which has two arguments: + +- `$source` - describes, usually through a short identifier, what is triggering the set of actions. +For example, some already existing sources are `web` (incl. actions from the Back Office), `graphql`, `rest` and `migration` +- `$description` - an optional, more specific contextualisation. +For example, `migration` context source is associated with the migration file name in its context description + +To close a context group, use `ActivityLogService::dismissContext`. + +In the following example, several actions are logged into one context group, even those triggered by a cascade outside the piece of code: + +- `my_feature` + - `init` + - `create` + - `publish` + - `simulate` + - `complete` + +``` php +[[= include_file('code_samples/recent_activity/src/Command/ActivityLogContextTestCommand.php', 63, 83) =]] +``` + +Context groups can't be nested. +If a new context is prepared when a context is already grouping log entries, this new context is ignored. +To start a new context, make sure to previously dismiss the existing one. + +When displayed in the Back Office, a context group is folded below its first entry. +The `my_feature` context from the example is folded below its first action, the `init` action. +Other actions are displayed after you click the **Show more** button. + +![The example context group displayed on the Recent Activity page](activity_log_group.png "`my_feature` context from the example") + +#### Displaying log entries + +To display your log entry, if your object's PHP class isn't already covered, you'll have to: + +- implement `ClassNameMapperInterface` to associate the class name with an identifier, +- eventually create a `PostActivityListLoadEvent` subscriber if you need to load the object for the template, +- create a template to display this class log entries. + +You can have a template that is: + +- specific to a class identifier and placed in `templates/themes//activity_log/ui/.html.twig` +- specific to an action on an identifier and placed in `templates/themes//activity_log/ui//.html.twig` + +Template existence is tested in reverse order: if there is no action that specifies the template, the identifier's default is used. +For the same identifier, you could have specific templates for few actions, and a default one for the remaining actions. + +A default template is used if no template is found for the identifier. +The built-in default template `@ibexadesign/activity_log/ui/default.html.twig` has an empty `activity_log_description_widget` block and doesn't display anything for unknown objects. +Your template can extend `@ibexadesign/activity_log/ui/default.html.twig`, and only redefine the `activity_log_description_widget` block for your objects. + +First, follow an example of a default template overriding the one from the bundle. +It can be used during development as a fallback for classes that aren't mapped yet. + +``` twig +[[= include_file('code_samples/recent_activity/templates/themes/admin/activity_log/ui/default.html.twig') =]] +``` + +Here is an example of a `ClassNameMapperInterface` associating the class `App\MyFeature\MyFeature` with the identifier `my_feature`: + +``` php +[[= include_file('code_samples/recent_activity/src/ActivityLog/ClassNameMapper/MyFeatureNameMapper.php') =]] +``` + +This mapper also provides a translation for the class name in the **Filters** menu. +This translation can be extracted with `php bin/console translation:extract en --domain=ibexa_activity_log --dir=src --output-dir=translations`. + +To be taken into account, this mapper must be registered as a service: + +``` yaml +[[= include_file('code_samples/recent_activity/config/append_to_services.yaml') =]] +``` + +Here is an example of a `PostActivityListLoadEvent` subscriber which loads the related object when it's an `App\MyFeature\MyFeature`, and attaches it to the log entry: + +``` php +[[= include_file('code_samples/recent_activity/src/EventSubscriber/MyFeaturePostActivityListLoadEventSubscriber.php') =]] +``` + +The following template is made to display the object of `App\MyFeature\MyFeature` (now identified as `my_feature`) when the action is `simulate`, +so, it's named in `templates/themes/admin/activity_log/ui/my_feature/simulate.html.twig`. +Thanks to the previous subscriber, the related object is available at display time: + +``` twig +[[= include_file('code_samples/recent_activity/templates/themes/admin/activity_log/ui/my_feature/simulate.html.twig') =]] +``` + +## REST API + +You can browse activity logs with REST API. +For more information, see the [REST API reference](../../api/rest_api/rest_api_reference/rest_api_reference.html#monitoring-activity). diff --git a/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/GET/ActivityLogGroupList.json.example b/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/GET/ActivityLogGroupList.json.example new file mode 100644 index 0000000000..3ab278114e --- /dev/null +++ b/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/GET/ActivityLogGroupList.json.example @@ -0,0 +1,50 @@ +{ + "ActivityLogGroupList": { + "_media-type": "application\/vnd.ibexa.api.ActivityLogGroupList+json", + "_href": "\/api\/ibexa\/v2\/activity-log-group\/list", + "ActivityLogGroups": [ + { + "_media-type": "application\/vnd.ibexa.api.ActivityLogGroup+json", + "user_id": 14, + "logged_at": 1707478796, + "ActivityLogEntries": [ + { + "_media-type": "application\/vnd.ibexa.api.ActivityLog+json", + "object_id": "396", + "object_class": "Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\Content", + "action": "create", + "data": {} + }, + { + "_media-type": "application\/vnd.ibexa.api.ActivityLog+json", + "object_id": "396", + "object_class": "Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\Content", + "action": "publish", + "data": {} + } + ] + }, + { + "_media-type": "application\/vnd.ibexa.api.ActivityLogGroup+json", + "user_id": 14, + "logged_at": 1707475048, + "ActivityLogEntries": [ + { + "_media-type": "application\/vnd.ibexa.api.ActivityLog+json", + "object_id": "395", + "object_class": "Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\Content", + "action": "create", + "data": {} + }, + { + "_media-type": "application\/vnd.ibexa.api.ActivityLog+json", + "object_id": "395", + "object_class": "Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\Content", + "action": "publish", + "data": {} + } + ] + } + ] + } +} diff --git a/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/GET/ActivityLogGroupList.xml.example b/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/GET/ActivityLogGroupList.xml.example new file mode 100644 index 0000000000..e8e738f21f --- /dev/null +++ b/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/GET/ActivityLogGroupList.xml.example @@ -0,0 +1,35 @@ + + + + 14 + 1707478796 + + 396 + Ibexa\Contracts\Core\Repository\Values\Content\Content + create + + + + 396 + Ibexa\Contracts\Core\Repository\Values\Content\Content + publish + + + + + 14 + 1707475048 + + 395 + Ibexa\Contracts\Core\Repository\Values\Content\Content + create + + + + 395 + Ibexa\Contracts\Core\Repository\Values\Content\Content + publish + + + + diff --git a/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/POST/ActivityLogGroupList.json.example b/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/POST/ActivityLogGroupList.json.example new file mode 100644 index 0000000000..3ab278114e --- /dev/null +++ b/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/POST/ActivityLogGroupList.json.example @@ -0,0 +1,50 @@ +{ + "ActivityLogGroupList": { + "_media-type": "application\/vnd.ibexa.api.ActivityLogGroupList+json", + "_href": "\/api\/ibexa\/v2\/activity-log-group\/list", + "ActivityLogGroups": [ + { + "_media-type": "application\/vnd.ibexa.api.ActivityLogGroup+json", + "user_id": 14, + "logged_at": 1707478796, + "ActivityLogEntries": [ + { + "_media-type": "application\/vnd.ibexa.api.ActivityLog+json", + "object_id": "396", + "object_class": "Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\Content", + "action": "create", + "data": {} + }, + { + "_media-type": "application\/vnd.ibexa.api.ActivityLog+json", + "object_id": "396", + "object_class": "Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\Content", + "action": "publish", + "data": {} + } + ] + }, + { + "_media-type": "application\/vnd.ibexa.api.ActivityLogGroup+json", + "user_id": 14, + "logged_at": 1707475048, + "ActivityLogEntries": [ + { + "_media-type": "application\/vnd.ibexa.api.ActivityLog+json", + "object_id": "395", + "object_class": "Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\Content", + "action": "create", + "data": {} + }, + { + "_media-type": "application\/vnd.ibexa.api.ActivityLog+json", + "object_id": "395", + "object_class": "Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\Content", + "action": "publish", + "data": {} + } + ] + } + ] + } +} diff --git a/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/POST/ActivityLogGroupList.xml.example b/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/POST/ActivityLogGroupList.xml.example new file mode 100644 index 0000000000..e8e738f21f --- /dev/null +++ b/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/POST/ActivityLogGroupList.xml.example @@ -0,0 +1,35 @@ + + + + 14 + 1707478796 + + 396 + Ibexa\Contracts\Core\Repository\Values\Content\Content + create + + + + 396 + Ibexa\Contracts\Core\Repository\Values\Content\Content + publish + + + + + 14 + 1707475048 + + 395 + Ibexa\Contracts\Core\Repository\Values\Content\Content + create + + + + 395 + Ibexa\Contracts\Core\Repository\Values\Content\Content + publish + + + + diff --git a/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/POST/ActivityLogGroupListInput.json.example b/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/POST/ActivityLogGroupListInput.json.example new file mode 100644 index 0000000000..1844b3404c --- /dev/null +++ b/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/POST/ActivityLogGroupListInput.json.example @@ -0,0 +1,14 @@ +{ + "ActivityLogGroupListInput": { + "offset": 0, + "limit": 10, + "criteria": [ + {"type": "object_class", "class": "Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\Content"}, + {"type": "action", "value": ["create"]}, + {"type": "logged_at", "value": "- 1 hour", "operator": ">="} + ], + "sortClauses": [ + {"type": "logged_at", "direction": "DESC"} + ] + } +} diff --git a/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/POST/ActivityLogGroupListInput.xml.example b/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/POST/ActivityLogGroupListInput.xml.example new file mode 100644 index 0000000000..9489b9be71 --- /dev/null +++ b/docs/api/rest_api/rest_api_reference/input/examples/activityloggroup/POST/ActivityLogGroupListInput.xml.example @@ -0,0 +1,20 @@ + + + 0 + 10 + + + Ibexa\Contracts\Core\Repository\Values\Content\Content + + + create + + + - 1 hour + >= + + + + DESC + + \ No newline at end of file diff --git a/docs/api/rest_api/rest_api_reference/input/ibexa-activityloggroup.raml b/docs/api/rest_api/rest_api_reference/input/ibexa-activityloggroup.raml new file mode 100644 index 0000000000..4a8caa079d --- /dev/null +++ b/docs/api/rest_api/rest_api_reference/input/ibexa-activityloggroup.raml @@ -0,0 +1,94 @@ +displayName: Monitoring activity +/list: + get: + displayName: List of activity log groups + description: Lists last activity log groups and their entries. + headers: + Accept: + description: If set, the list is returned in XML or JSON format. + example: | + application/vnd.ibexa.api.ActivityLogGroupList+xml + application/vnd.ibexa.api.ActivityLogGroupList+json + queryParameters: + offset: + type: string + description: | + (default: offset=0) + limit: + type: string + description: | + Maximum number of returned log groups (default: limit=25) + + Example: limit=10 + filter: + type: array + description: | + List of filters (no filter by default). + A filter is a type and parameters depending on this type. + + - type: `object_class`, parameter: `class` (a FQCN) + - type: `action`, parameter: `value` (the action) + - type: `logged_at`, parameters: `value` and `operator` + - `logged_at` `value` value can be anything accepted by `DateTimeImmutable` + - `logged_at` `operator` value can be one of the following: + + | Comparison | Value | + |-----------------------|-------| + | Equal | `=` | + | Not equal | `<>` | + | Less than | `<` | + | Less than or equal | `<=` | + | Greater than | `>` | + | Greater than or equal | `>=` | + + Example: `filter[0][type]=object_class&filter[0][class]=Ibexa\Contracts\Core\Repository\Values\Content\Content&filter[1][type]=action&filter[1][value]=create&filter[2][type]=logged_at&filter[2][value]=-1hour&filter[2][operator]=>=` + sort: + type: array + description: | + List of sort clause and direction pairs + (default: sort[0][type]=logged_at&sort[0][direction]=ASC) + + Example: sort[0][type]=logged_at&sort[0][direction]=DESC + responses: + 200: + body: + application/vnd.ibexa.api.ActivityLogGroupList+xml: + type: ActivityLogGroupList + example: !include examples/activityloggroup/GET/ActivityLogGroupList.xml.example + application/vnd.ibexa.api.ActivityLogGroupList+json: + type: ActivityLogGroupList + example: !include examples/activityloggroup/GET/ActivityLogGroupList.json.example + 401: + description: Error - the user is not authorized to list activities. + post: + displayName: List of filtered activity log groups + description: Lists activity log groups of entries filtered according to the input payload. List input reflects the criteria model of the PHP API. See Activity Log Search reference. + headers: + Accept: + description: If set, the list is returned in XML or JSON format. + example: | + application/vnd.ibexa.api.ActivityLogGroupList+xml + application/vnd.ibexa.api.ActivityLogGroupList+json + Content-Type: + description: The ActivityLogListInput query schema encoded in XML or JSON format. + example: | + application/vnd.ibexa.api.ActivityLogGroupListInput+xml + application/vnd.ibexa.api.ActivityLogGroupListInput+json + body: + application/vnd.ibexa.api.ActivityLogGroupListInput+xml: + type: ActivityLogGroupListInput + example: !include examples/activityloggroup/POST/ActivityLogGroupListInput.xml.example + application/vnd.ibexa.api.ActivityLogGroupListInput+json: + type: ActivityLogGroupListInput + example: !include examples/activityloggroup/POST/ActivityLogGroupListInput.json.example + responses: + 200: + body: + application/vnd.ibexa.api.ActivityLogGroupList+xml: + type: ActivityLogGroupList + example: !include examples/activityloggroup/POST/ActivityLogGroupList.xml.example + application/vnd.ibexa.api.ActivityLogGroupList+json: + type: ActivityLogGroupList + example: !include examples/activityloggroup/POST/ActivityLogGroupList.json.example + 401: + description: Error - the user is not authorized to list activities. diff --git a/docs/api/rest_api/rest_api_reference/input/ibexa-types.raml b/docs/api/rest_api/rest_api_reference/input/ibexa-types.raml index 1af76e4e3b..c970d76e06 100644 --- a/docs/api/rest_api/rest_api_reference/input/ibexa-types.raml +++ b/docs/api/rest_api/rest_api_reference/input/ibexa-types.raml @@ -3968,3 +3968,27 @@ SalesRepresentativesList: Users: type: User[] +ActivityLogGroupListInput: + description: "This class represents the activity log group search query's criteria and sort clauses." + type: object + +ActivityLogGroupList: + description: "This class represents activity log context groups" + type: object + properties: + ActivityLogGroups: + type: object + properties: + user_id: + type: integer + description: "The ID of the user performing the logged actions" + logged_at: + type: integer + description: "Timestamp representing when the group has been logged" + ActivityLogEntries: + type: ActivityLog[] + description: "Log entries from the group" + +ActivityLog: + description: 'This class represents an activity log entry.' + type: object diff --git a/docs/api/rest_api/rest_api_reference/input/ibexa.raml b/docs/api/rest_api/rest_api_reference/input/ibexa.raml index 81fffaa0ea..8b52aa24cf 100644 --- a/docs/api/rest_api/rest_api_reference/input/ibexa.raml +++ b/docs/api/rest_api/rest_api_reference/input/ibexa.raml @@ -77,4 +77,6 @@ baseUri: ../rest_api_usage/rest_api_usage/ /taxonomy: !include ibexa-taxonomy.raml +/activity-log-group: !include ibexa-activityloggroup.raml + types: !include ibexa-types.raml diff --git a/docs/api/rest_api/rest_api_reference/rest_api_reference.html b/docs/api/rest_api/rest_api_reference/rest_api_reference.html index 3c4c231c33..b3dde91ddb 100644 --- a/docs/api/rest_api/rest_api_reference/rest_api_reference.html +++ b/docs/api/rest_api/rest_api_reference/rest_api_reference.html @@ -125122,6 +125122,1262 @@
Types
] } } + + + + + + + + + + + + + + + + +
+

+ Monitoring activity + +

+
+
+

/activity-log-group/list

+ +
+
+
+
+
+
+ List of activity log groups + +
+
+
+

+ GET + /activity-log-group/list +

+

Lists last activity log groups and their entries.

+

+
+
Header parameters
+
+

Accept

+

If set, the list is returned in XML or JSON format.

+
+ + + + + + + + + + + + + + + + + +
PropertyValue
Type + + + string + + + + +
Examples + application/vnd.ibexa.api.ActivityLogGroupList+xml +application/vnd.ibexa.api.ActivityLogGroupList+json + +
+
+
+
+ +
+
Query parameters
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeValue
+ offset + + + + string + + + + + +

(default: offset=0)

+ +
+ limit + + + + string + + + + + +

Maximum number of returned log groups (default: limit=25)

+

Example: limit=10

+ +
+ filter + + + +

List of filters (no filter by default). +A filter is a type and parameters depending on this type.

+
    +
  • type: object_class, parameter: class (a FQCN)
  • +
  • type: action, parameter: value (the action)
  • +
  • type: logged_at, parameters: value and operator +
      +
    • +logged_at value value can be anything accepted by DateTimeImmutable +
    • +
    • +logged_at operator value can be one of the following:
    • +
    +
  • +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ComparisonValue
Equal=
Not equal<>
Less than<
Less than or equal<=
Greater than>
Greater than or equal>=
+

Example: filter[0][type]=object_class&filter[0][class]=Ibexa\Contracts\Core\Repository\Values\Content\Content&filter[1][type]=action&filter[1][value]=create&filter[2][type]=logged_at&filter[2][value]=-1hour&filter[2][operator]=>=

+ +
+ sort + + + +

List of sort clause and direction pairs +(default: sort[0][type]=logged_at&sort[0][direction]=ASC)

+

Example: sort[0][type]=logged_at&sort[0][direction]=DESC

+ +
+
+
+ +
+
Possible responses
+
+ + + + + + + + + + + + + + + + + +
CodeDescription
+ + 200 + + +

+
+ 401 + +

Error - the user is not authorized to list activities.

+
+
+
+ +
+
Types
+
+ + + + + + + + + + + + + +
TypeDescription
+ + ActivityLogGroupList + + This class represents activity log context groups
+
+
+ +
+
+ +
+
+
+
+
+ +
+ Code: 200 +
+
+
+
+
+
+

+ file_copy + +

+
+                                <?xml version="1.0" encoding="UTF-8"?>
+<ActivityLogGroupList media-type="application/vnd.ibexa.api.ActivityLogGroupList+xml" href="/api/ibexa/v2/activity-log-group/list">
+ <ActivityLogGroup media-type="application/vnd.ibexa.api.ActivityLogGroup+xml">
+  <user_id>14</user_id>
+  <logged_at>1707478796</logged_at>
+  <ActivityLog media-type="application/vnd.ibexa.api.ActivityLog+xml">
+   <object_id>396</object_id>
+   <object_class>Ibexa\Contracts\Core\Repository\Values\Content\Content</object_class>
+   <action>create</action>
+   <data/>
+  </ActivityLog>
+  <ActivityLog media-type="application/vnd.ibexa.api.ActivityLog+xml">
+   <object_id>396</object_id>
+   <object_class>Ibexa\Contracts\Core\Repository\Values\Content\Content</object_class>
+   <action>publish</action>
+   <data/>
+  </ActivityLog>
+ </ActivityLogGroup>
+ <ActivityLogGroup media-type="application/vnd.ibexa.api.ActivityLogGroup+xml">
+  <user_id>14</user_id>
+  <logged_at>1707475048</logged_at>
+  <ActivityLog media-type="application/vn
+                            
+
+ View more +
+
+
+

+ file_copy + +

+
+                                {
+    "ActivityLogGroupList": {
+        "_media-type": "application\/vnd.ibexa.api.ActivityLogGroupList+json",
+        "_href": "\/api\/ibexa\/v2\/activity-log-group\/list",
+        "ActivityLogGroups": [
+            {
+                "_media-type": "application\/vnd.ibexa.api.ActivityLogGroup+json",
+                "user_id": 14,
+                "logged_at": 1707478796,
+                "ActivityLogEntries": [
+                    {
+                        "_media-type": "application\/vnd.ibexa.api.ActivityLog+json",
+                        "object_id": "396",
+                        "object_class": "Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\Content",
+                        "action": "create",
+                        "data": {}
+                    },
+                    {
+                        "_media-type": "application\/vnd.ibexa.api.ActivityLog+json",
+                        "object_id": "396",
+                        "object_class": "Ibexa\\Contracts\\Core\\Repository\
+                            
+
+ View more +
+
+
+ +
+
+
+
+
+
+
+
+
+ List of filtered activity log groups + +
+
+
+

+ POST + /activity-log-group/list +

+

Lists activity log groups of entries filtered according to the input payload. List input reflects the criteria model of the PHP API. See Activity Log Search reference.

+

+
+
Header parameters
+
+

Accept

+

If set, the list is returned in XML or JSON format.

+
+ + + + + + + + + + + + + + + + + +
PropertyValue
Type + + + string + + + + +
Examples + application/vnd.ibexa.api.ActivityLogGroupList+xml +application/vnd.ibexa.api.ActivityLogGroupList+json + +
+
+
+
+

Content-Type

+

The ActivityLogListInput query schema encoded in XML or JSON format.

+
+ + + + + + + + + + + + + + + + + +
PropertyValue
Type + + + string + + + + +
Examples + application/vnd.ibexa.api.ActivityLogGroupListInput+xml +application/vnd.ibexa.api.ActivityLogGroupListInput+json + +
+
+
+
+ + +
+
Possible responses
+
+ + + + + + + + + + + + + + + + + +
CodeDescription
+ + 200 + + +

+
+ 401 + +

Error - the user is not authorized to list activities.

+
+
+
+ +
+
Types
+
+ + + + + + + + + + + + + + + + + +
TypeDescription
+ + ActivityLogGroupListInput + + This class represents the activity log group search query's criteria and sort clauses.
+ + ActivityLogGroupList + + This class represents activity log context groups
+
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+

+ file_copy + +

+
+                                <?xml version="1.0" encoding="UTF-8" ?>
+<ActivityLogListInput>
+    <offset>0</offset>
+    <limit>10</limit>
+    <criteria>
+        <criterion type="object_class">
+            <class>Ibexa\Contracts\Core\Repository\Values\Content\Content</class>
+        </criterion>
+        <criterion type="action">
+            <value>create</value>
+        </criterion>
+        <criterion type="logged_at">
+            <value>- 1 hour</value>
+            <operator>&gt;=</operator>
+        </criterion>
+    </criteria>
+    <sortClauses>
+        <sortClause type="logged_at">DESC</sortClause>
+    </sortClauses>
+</ActivityLogListInput>
+                            
+
+ View more +
+
+
+

+ file_copy + +

+
+                                {
+    "ActivityLogGroupListInput": {
+        "offset": 0,
+        "limit": 10,
+        "criteria": [
+            {"type": "object_class", "class": "Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\Content"},
+            {"type": "action", "value": ["create"]},
+            {"type": "logged_at", "value": "- 1 hour", "operator": ">="}
+        ],
+        "sortClauses": [
+            {"type":  "logged_at", "direction": "DESC"}
+        ]
+    }
+}
+
+                            
+
+ View more +
+
+
+ +
+
+
+
+
+ +
+ Code: 200 +
+
+
+
+
+
+

+ file_copy + +

+
+                                <?xml version="1.0" encoding="UTF-8"?>
+<ActivityLogGroupList media-type="application/vnd.ibexa.api.ActivityLogGroupList+xml" href="/api/ibexa/v2/activity-log-group/list">
+ <ActivityLogGroup media-type="application/vnd.ibexa.api.ActivityLogGroup+xml">
+  <user_id>14</user_id>
+  <logged_at>1707478796</logged_at>
+  <ActivityLog media-type="application/vnd.ibexa.api.ActivityLog+xml">
+   <object_id>396</object_id>
+   <object_class>Ibexa\Contracts\Core\Repository\Values\Content\Content</object_class>
+   <action>create</action>
+   <data/>
+  </ActivityLog>
+  <ActivityLog media-type="application/vnd.ibexa.api.ActivityLog+xml">
+   <object_id>396</object_id>
+   <object_class>Ibexa\Contracts\Core\Repository\Values\Content\Content</object_class>
+   <action>publish</action>
+   <data/>
+  </ActivityLog>
+ </ActivityLogGroup>
+ <ActivityLogGroup media-type="application/vnd.ibexa.api.ActivityLogGroup+xml">
+  <user_id>14</user_id>
+  <logged_at>1707475048</logged_at>
+  <ActivityLog media-type="application/vn
+                            
+
+ View more +
+
+
+

+ file_copy + +

+
+                                {
+    "ActivityLogGroupList": {
+        "_media-type": "application\/vnd.ibexa.api.ActivityLogGroupList+json",
+        "_href": "\/api\/ibexa\/v2\/activity-log-group\/list",
+        "ActivityLogGroups": [
+            {
+                "_media-type": "application\/vnd.ibexa.api.ActivityLogGroup+json",
+                "user_id": 14,
+                "logged_at": 1707478796,
+                "ActivityLogEntries": [
+                    {
+                        "_media-type": "application\/vnd.ibexa.api.ActivityLog+json",
+                        "object_id": "396",
+                        "object_class": "Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\Content",
+                        "action": "create",
+                        "data": {}
+                    },
+                    {
+                        "_media-type": "application\/vnd.ibexa.api.ActivityLog+json",
+                        "object_id": "396",
+                        "object_class": "Ibexa\\Contracts\\Core\\Repository\
+                            
+
+ View more +
+
+
+