Merge pull request #54117 from nextcloud/feat/noid/add-bulk-activity

feat(activity): add bulk activity option
pull/54058/head
Anna 2025-08-14 15:53:33 +07:00 committed by GitHub
commit 1a2d0d5c1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 91 additions and 8 deletions

@ -49,6 +49,7 @@ return array(
'OCP\\Activity\\Exceptions\\InvalidValueException' => $baseDir . '/lib/public/Activity/Exceptions/InvalidValueException.php',
'OCP\\Activity\\Exceptions\\SettingNotFoundException' => $baseDir . '/lib/public/Activity/Exceptions/SettingNotFoundException.php',
'OCP\\Activity\\Exceptions\\UnknownActivityException' => $baseDir . '/lib/public/Activity/Exceptions/UnknownActivityException.php',
'OCP\\Activity\\IBulkConsumer' => $baseDir . '/lib/public/Activity/IBulkConsumer.php',
'OCP\\Activity\\IConsumer' => $baseDir . '/lib/public/Activity/IConsumer.php',
'OCP\\Activity\\IEvent' => $baseDir . '/lib/public/Activity/IEvent.php',
'OCP\\Activity\\IEventMerger' => $baseDir . '/lib/public/Activity/IEventMerger.php',

@ -90,6 +90,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Activity\\Exceptions\\InvalidValueException' => __DIR__ . '/../../..' . '/lib/public/Activity/Exceptions/InvalidValueException.php',
'OCP\\Activity\\Exceptions\\SettingNotFoundException' => __DIR__ . '/../../..' . '/lib/public/Activity/Exceptions/SettingNotFoundException.php',
'OCP\\Activity\\Exceptions\\UnknownActivityException' => __DIR__ . '/../../..' . '/lib/public/Activity/Exceptions/UnknownActivityException.php',
'OCP\\Activity\\IBulkConsumer' => __DIR__ . '/../../..' . '/lib/public/Activity/IBulkConsumer.php',
'OCP\\Activity\\IConsumer' => __DIR__ . '/../../..' . '/lib/public/Activity/IConsumer.php',
'OCP\\Activity\\IEvent' => __DIR__ . '/../../..' . '/lib/public/Activity/IEvent.php',
'OCP\\Activity\\IEventMerger' => __DIR__ . '/../../..' . '/lib/public/Activity/IEventMerger.php',

@ -450,7 +450,6 @@ class Event implements IEvent {
return
$this->getApp() !== ''
&& $this->getType() !== ''
&& $this->getAffectedUser() !== ''
&& $this->getTimestamp() !== 0
/**
* Disabled for BC with old activities

@ -11,12 +11,14 @@ use OCP\Activity\ActivitySettings;
use OCP\Activity\Exceptions\FilterNotFoundException;
use OCP\Activity\Exceptions\IncompleteActivityException;
use OCP\Activity\Exceptions\SettingNotFoundException;
use OCP\Activity\IBulkConsumer;
use OCP\Activity\IConsumer;
use OCP\Activity\IEvent;
use OCP\Activity\IFilter;
use OCP\Activity\IManager;
use OCP\Activity\IProvider;
use OCP\Activity\ISetting;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IRequest;
@ -46,6 +48,7 @@ class Manager implements IManager {
protected IValidator $validator,
protected IRichTextFormatter $richTextFormatter,
protected IL10N $l10n,
protected ITimeFactory $timeFactory,
) {
}
@ -96,6 +99,31 @@ class Manager implements IManager {
* {@inheritDoc}
*/
public function publish(IEvent $event): void {
if ($event->getAuthor() === '' && $this->session->getUser() instanceof IUser) {
$event->setAuthor($this->session->getUser()->getUID());
}
if (!$event->getTimestamp()) {
$event->setTimestamp($this->timeFactory->getTime());
}
if ($event->getAffectedUser() === '' || !$event->isValid()) {
throw new IncompleteActivityException('The given event is invalid');
}
foreach ($this->getConsumers() as $c) {
$c->receive($event);
}
}
/**
* {@inheritDoc}
*/
public function bulkPublish(IEvent $event, array $affectedUserIds, ISetting $setting): void {
if (empty($affectedUserIds)) {
throw new IncompleteActivityException('The given event is invalid');
}
if ($event->getAuthor() === '') {
if ($this->session->getUser() instanceof IUser) {
$event->setAuthor($this->session->getUser()->getUID());
@ -103,7 +131,7 @@ class Manager implements IManager {
}
if (!$event->getTimestamp()) {
$event->setTimestamp(time());
$event->setTimestamp($this->timeFactory->getTime());
}
if (!$event->isValid()) {
@ -111,10 +139,17 @@ class Manager implements IManager {
}
foreach ($this->getConsumers() as $c) {
$c->receive($event);
if ($c instanceof IBulkConsumer) {
$c->bulkReceive($event, $affectedUserIds, $setting);
}
foreach ($affectedUserIds as $affectedUserId) {
$event->setAffectedUser($affectedUserId);
$c->receive($event);
}
}
}
/**
* In order to improve lazy loading a closure can be registered which will be called in case
* activity consumers are actually requested

@ -657,7 +657,8 @@ class Server extends ServerContainer implements IServerContainer {
$c->get(\OCP\IConfig::class),
$c->get(IValidator::class),
$c->get(IRichTextFormatter::class),
$l10n
$l10n,
$c->get(ITimeFactory::class),
);
});

@ -0,0 +1,24 @@
<?php
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCP\Activity;
/**
* Interface IBulkConsumer
*
* @since 32.0.0
*/
interface IBulkConsumer extends IConsumer {
/**
* @param IEvent $event
* @param array $affectedUserIds
* @param ISetting $setting
* @return void
* @since 32.0.0
*/
public function bulkReceive(IEvent $event, array $affectedUserIds, ISetting $setting): void;
}

@ -51,6 +51,20 @@ interface IManager {
*/
public function publish(IEvent $event): void;
/**
* Bulk publish an event for multiple users
* taking into account the app specific activity settings
*
* Make sure to call at least the following methods before sending an Event:
* - setApp()
* - setType()
*
* @param IEvent $event
* @throws IncompleteActivityException if required values have not been set
* @since 32.0.0
*/
public function bulkPublish(IEvent $event, array $affectedUserIds, ISetting $setting): void;
/**
* In order to improve lazy loading a closure can be registered which will be called in case
* activity consumers are actually requested

@ -11,6 +11,7 @@ namespace Test\Activity;
use OCP\Activity\Exceptions\IncompleteActivityException;
use OCP\Activity\IConsumer;
use OCP\Activity\IEvent;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IRequest;
@ -30,6 +31,7 @@ class ManagerTest extends TestCase {
protected IConfig&MockObject $config;
protected IValidator&MockObject $validator;
protected IRichTextFormatter&MockObject $richTextFormatter;
private ITimeFactory&MockObject $time;
protected function setUp(): void {
parent::setUp();
@ -39,6 +41,7 @@ class ManagerTest extends TestCase {
$this->config = $this->createMock(IConfig::class);
$this->validator = $this->createMock(IValidator::class);
$this->richTextFormatter = $this->createMock(IRichTextFormatter::class);
$this->time = $this->createMock(ITimeFactory::class);
$this->activityManager = new \OC\Activity\Manager(
$this->request,
@ -46,7 +49,8 @@ class ManagerTest extends TestCase {
$this->config,
$this->validator,
$this->richTextFormatter,
$this->createMock(IL10N::class)
$this->createMock(IL10N::class),
$this->time,
);
$this->assertSame([], self::invokePrivate($this->activityManager, 'getConsumers'));
@ -217,6 +221,11 @@ class ManagerTest extends TestCase {
->willReturn($authorObject);
}
$time = time();
$this->time
->method('getTime')
->willReturn($time);
$event = $this->activityManager->generateEvent();
$event->setApp('test')
->setType('test_type')
@ -230,9 +239,8 @@ class ManagerTest extends TestCase {
$consumer->expects($this->once())
->method('receive')
->with($event)
->willReturnCallback(function (IEvent $event) use ($expected): void {
$this->assertLessThanOrEqual(time() + 2, $event->getTimestamp(), 'Timestamp not set correctly');
$this->assertGreaterThanOrEqual(time() - 2, $event->getTimestamp(), 'Timestamp not set correctly');
->willReturnCallback(function (IEvent $event) use ($expected, $time): void {
$this->assertEquals($time, $event->getTimestamp(), 'Timestamp not set correctly');
$this->assertSame($expected, $event->getAuthor(), 'Author name not set correctly');
});
$this->activityManager->registerConsumer(function () use ($consumer) {