From 007ea403e09d9175d4bb6e5fe55e1207e1ce51df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 23 May 2024 17:52:09 +0200 Subject: [PATCH 1/3] feat: Add events for row added and row updated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/Event/RowAddedEvent.php | 18 ++++++++++++++++++ lib/Event/RowUpdatedEvent.php | 18 ++++++++++++++++++ lib/Service/RowService.php | 14 ++++++++++++-- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 lib/Event/RowAddedEvent.php create mode 100644 lib/Event/RowUpdatedEvent.php diff --git a/lib/Event/RowAddedEvent.php b/lib/Event/RowAddedEvent.php new file mode 100644 index 000000000..f744378e4 --- /dev/null +++ b/lib/Event/RowAddedEvent.php @@ -0,0 +1,18 @@ +row; + } +} diff --git a/lib/Event/RowUpdatedEvent.php b/lib/Event/RowUpdatedEvent.php new file mode 100644 index 000000000..246e3092b --- /dev/null +++ b/lib/Event/RowUpdatedEvent.php @@ -0,0 +1,18 @@ +row; + } +} diff --git a/lib/Service/RowService.php b/lib/Service/RowService.php index f69b98929..fd083e853 100644 --- a/lib/Service/RowService.php +++ b/lib/Service/RowService.php @@ -13,7 +13,9 @@ use OCA\Tables\Errors\InternalError; use OCA\Tables\Errors\NotFoundError; use OCA\Tables\Errors\PermissionError; +use OCA\Tables\Event\RowAddedEvent; use OCA\Tables\Event\RowDeletedEvent; +use OCA\Tables\Event\RowUpdatedEvent; use OCA\Tables\Model\RowDataInput; use OCA\Tables\ResponseDefinitions; use OCA\Tables\Service\ColumnTypes\IColumnTypeBusiness; @@ -214,7 +216,11 @@ public function create(?int $tableId, ?int $viewId, RowDataInput|array $data): R $row2->setTableId($tableId); $row2->setData($data); try { - return $this->row2Mapper->insert($row2, $this->columnMapper->findAllByTable($tableId)); + $insertedRow = $this->row2Mapper->insert($row2, $this->columnMapper->findAllByTable($tableId)); + + $this->eventDispatcher->dispatchTyped(new RowAddedEvent(row: $insertedRow)); + + return $insertedRow; } catch (InternalError|Exception $e) { $this->logger->error($e->getMessage(), ['exception' => $e]); throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage()); @@ -411,7 +417,11 @@ public function updateSet( } } - return $this->filterRowResult($view ?? null, $this->row2Mapper->update($item, $columns)); + $updatedRow = $this->row2Mapper->update($item, $columns); + + $this->eventDispatcher->dispatchTyped(new RowUpdatedEvent(row: $updatedRow)); + + return $this->filterRowResult($view ?? null, $updatedRow); } /** From 2a6a33887eaf5e4cacbf716f2f1c4c9a8ed533df Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 16 Jul 2024 17:50:08 +0200 Subject: [PATCH 2/3] feat(Events): webhook compatible Row events with public model - implements OCP\EventDispatcher\IWebhookCompatibleEvent - add lib/Model/Public/Row as sort of API-like model for Tables Row data - change exisiting events to use new abstract row event class Signed-off-by: Arthur Schiwon --- lib/Db/Row2.php | 10 ++++ lib/Event/AbstractRowEvent.php | 43 +++++++++++++++++ lib/Event/RowAddedEvent.php | 12 +---- lib/Event/RowDeletedEvent.php | 12 +---- lib/Event/RowUpdatedEvent.php | 12 +---- .../WhenRowDeletedAuditLogListener.php | 2 +- lib/Model/Public/Row.php | 46 +++++++++++++++++++ lib/Service/RowService.php | 7 +-- .../Listener/WhenRowDeletedAuditLogTest.php | 5 +- 9 files changed, 110 insertions(+), 39 deletions(-) create mode 100644 lib/Event/AbstractRowEvent.php create mode 100644 lib/Model/Public/Row.php diff --git a/lib/Db/Row2.php b/lib/Db/Row2.php index cd7796bdd..684f160ab 100644 --- a/lib/Db/Row2.php +++ b/lib/Db/Row2.php @@ -3,6 +3,7 @@ namespace OCA\Tables\Db; use JsonSerializable; +use OCA\Tables\Model\Public\Row; use OCA\Tables\ResponseDefinitions; /** @@ -132,6 +133,15 @@ public function jsonSerialize(): array { ]; } + public function toPublicRow(?array $previousValues = null): Row { + return new Row( + tableId: $this->tableId, + rowId: $this->id, + previousValues: $previousValues, + values: $this->data, + ); + } + /** * Can only be changed by private methods * @param int $columnId diff --git a/lib/Event/AbstractRowEvent.php b/lib/Event/AbstractRowEvent.php new file mode 100644 index 000000000..4275997b6 --- /dev/null +++ b/lib/Event/AbstractRowEvent.php @@ -0,0 +1,43 @@ +row = $rowEntity->toPublicRow($previousValues); + } + + public function getRow(): Row { + return $this->row; + } + + public function getWebhookSerializable(): array { + return $this->row->jsonSerialize(); + } + } +} else { + // need this block as long as NC < 30 is supported + abstract class AbstractRowEvent extends Event { + protected Row $row; + + public function __construct(Row2 $rowEntity, ?array $previousValues = null) { + parent::__construct(); + $this->row = $rowEntity->toPublicRow($previousValues); + } + + public function getRow(): Row { + return $this->row; + } + } +} diff --git a/lib/Event/RowAddedEvent.php b/lib/Event/RowAddedEvent.php index f744378e4..0c0eafde5 100644 --- a/lib/Event/RowAddedEvent.php +++ b/lib/Event/RowAddedEvent.php @@ -4,15 +4,5 @@ namespace OCA\Tables\Event; -use OCA\Tables\Db\Row2; -use OCP\EventDispatcher\Event; - -final class RowAddedEvent extends Event { - public function __construct(protected Row2 $row) { - parent::__construct(); - } - - public function getRow(): Row2 { - return $this->row; - } +final class RowAddedEvent extends AbstractRowEvent { } diff --git a/lib/Event/RowDeletedEvent.php b/lib/Event/RowDeletedEvent.php index afcfaa715..06714695a 100644 --- a/lib/Event/RowDeletedEvent.php +++ b/lib/Event/RowDeletedEvent.php @@ -4,15 +4,5 @@ namespace OCA\Tables\Event; -use OCA\Tables\Db\Row2; -use OCP\EventDispatcher\Event; - -final class RowDeletedEvent extends Event { - public function __construct(protected Row2 $row) { - parent::__construct(); - } - - public function getRow(): Row2 { - return $this->row; - } +final class RowDeletedEvent extends AbstractRowEvent { } diff --git a/lib/Event/RowUpdatedEvent.php b/lib/Event/RowUpdatedEvent.php index 246e3092b..cd13f7f0a 100644 --- a/lib/Event/RowUpdatedEvent.php +++ b/lib/Event/RowUpdatedEvent.php @@ -4,15 +4,5 @@ namespace OCA\Tables\Event; -use OCA\Tables\Db\Row2; -use OCP\EventDispatcher\Event; - -final class RowUpdatedEvent extends Event { - public function __construct(protected Row2 $row) { - parent::__construct(); - } - - public function getRow(): Row2 { - return $this->row; - } +final class RowUpdatedEvent extends AbstractRowEvent { } diff --git a/lib/Listener/WhenRowDeletedAuditLogListener.php b/lib/Listener/WhenRowDeletedAuditLogListener.php index b9ca7bde5..4cbd47125 100644 --- a/lib/Listener/WhenRowDeletedAuditLogListener.php +++ b/lib/Listener/WhenRowDeletedAuditLogListener.php @@ -22,7 +22,7 @@ public function handle(Event $event): void { } $row = $event->getRow(); - $rowId = $row->getId(); + $rowId = $row->rowId; $this->auditLogService->log("Row with ID: $rowId was deleted", [ 'row' => $row->jsonSerialize(), diff --git a/lib/Model/Public/Row.php b/lib/Model/Public/Row.php new file mode 100644 index 000000000..f6ed9a364 --- /dev/null +++ b/lib/Model/Public/Row.php @@ -0,0 +1,46 @@ +|null $previousValues key is the columnId. Only set on Update events. + * @param array|null $values key is the columnId. Contains values across all events, including Delete. + * + * @since 0.8 + */ + public function __construct( + /** @readonly */ + public int $tableId, + /** @readonly */ + public int $rowId, + /** @readonly */ + public null|array $previousValues = null, + /** @readonly */ + public null|array $values = null, + ) { + } + + + /** + * @return array{"tableId": int, "rowId": int, "previousValues": null|array, "values": null|array} + * + * @since 0.8 + */ + public function jsonSerialize(): array { + return [ + 'tableId' => $this->tableId, + 'rowId' => $this->rowId, + 'previousValues' => $this->previousValues, + 'values' => $this->values, + ]; + } +} diff --git a/lib/Service/RowService.php b/lib/Service/RowService.php index fd083e853..a835b3764 100644 --- a/lib/Service/RowService.php +++ b/lib/Service/RowService.php @@ -218,7 +218,7 @@ public function create(?int $tableId, ?int $viewId, RowDataInput|array $data): R try { $insertedRow = $this->row2Mapper->insert($row2, $this->columnMapper->findAllByTable($tableId)); - $this->eventDispatcher->dispatchTyped(new RowAddedEvent(row: $insertedRow)); + $this->eventDispatcher->dispatchTyped(new RowAddedEvent($insertedRow)); return $insertedRow; } catch (InternalError|Exception $e) { @@ -405,6 +405,7 @@ public function updateSet( } } + $previousData = $item->getData(); $data = $this->cleanupData($data, $columns, $item->getTableId(), $viewId); foreach ($data as $entry) { @@ -419,7 +420,7 @@ public function updateSet( $updatedRow = $this->row2Mapper->update($item, $columns); - $this->eventDispatcher->dispatchTyped(new RowUpdatedEvent(row: $updatedRow)); + $this->eventDispatcher->dispatchTyped(new RowUpdatedEvent($updatedRow, $previousData)); return $this->filterRowResult($view ?? null, $updatedRow); } @@ -476,7 +477,7 @@ public function delete(int $id, ?int $viewId, string $userId): Row2 { try { $deletedRow = $this->row2Mapper->delete($item); - $event = new RowDeletedEvent(row: $item); + $event = new RowDeletedEvent($item, $item->getData()); $this->eventDispatcher->dispatchTyped($event); diff --git a/tests/unit/Listener/WhenRowDeletedAuditLogTest.php b/tests/unit/Listener/WhenRowDeletedAuditLogTest.php index 7afa7726a..c51c0fb5a 100644 --- a/tests/unit/Listener/WhenRowDeletedAuditLogTest.php +++ b/tests/unit/Listener/WhenRowDeletedAuditLogTest.php @@ -22,8 +22,9 @@ protected function setUp(): void { public function testHandle(): void { $row = new Row2(); $row->setId(1); + $row->setTableId(161); - $event = new RowDeletedEvent(row: $row); + $event = new RowDeletedEvent($row); $this->auditLogService ->expects($this->once()) @@ -31,7 +32,7 @@ public function testHandle(): void { ->with( $this->equalTo("Row with ID: {$row->getId()} was deleted"), $this->equalTo([ - 'row' => $row->jsonSerialize(), + 'row' => $row->toPublicRow()->jsonSerialize(), ]) ); From 550a1064795c450e889d18c88c0c3d3248f4a62a Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 16 Jul 2024 17:51:00 +0200 Subject: [PATCH 3/3] ci(psalm): update psalm to gain a bug fix otherwise we would see: ERROR: UndefinedClass - lib/Event/AbstractRowEvent.php:12:22 - Class, interface or enum named OCP\EventDispatcher\IWebhookCompatibleEvent does not exist (see https://psalm.dev/019) if (interface_exists(\OCP\EventDispatcher\IWebhookCompatibleEvent::class)) { Signed-off-by: Arthur Schiwon --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 1e1227953..e21e5abcb 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "nextcloud/ocp": "dev-master", "staabm/annotate-pull-request-from-checkstyle": "^1.8", "phpunit/phpunit": "^9", - "psalm/phar": "^5.18", + "psalm/phar": "^5.25", "bamarni/composer-bin-plugin": "^1.8" }, "config": { diff --git a/composer.lock b/composer.lock index c35495786..4b0380d3a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "583101c4e273135ddfc7ece0a8b5c78c", + "content-hash": "050d909f8eb7bb5ef411a16873b5fd22", "packages": [ { "name": "ezyang/htmlpurifier", @@ -1548,16 +1548,16 @@ }, { "name": "psalm/phar", - "version": "5.18.0", + "version": "5.25.0", "source": { "type": "git", "url": "https://github.com/psalm/phar.git", - "reference": "a78b5c2e8860c3b4242c63bc0864621278705f9a" + "reference": "d42708449bd2d99ec6509924332fd94263974b20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/psalm/phar/zipball/a78b5c2e8860c3b4242c63bc0864621278705f9a", - "reference": "a78b5c2e8860c3b4242c63bc0864621278705f9a", + "url": "https://api.github.com/repos/psalm/phar/zipball/d42708449bd2d99ec6509924332fd94263974b20", + "reference": "d42708449bd2d99ec6509924332fd94263974b20", "shasum": "" }, "require": { @@ -1577,9 +1577,9 @@ "description": "Composer-based Psalm Phar", "support": { "issues": "https://github.com/psalm/phar/issues", - "source": "https://github.com/psalm/phar/tree/5.18.0" + "source": "https://github.com/psalm/phar/tree/5.25.0" }, - "time": "2023-12-16T09:41:14+00:00" + "time": "2024-06-19T20:02:02+00:00" }, { "name": "psr/clock", @@ -2864,4 +2864,4 @@ "php": "8.0" }, "plugin-api-version": "2.6.0" -} \ No newline at end of file +}