Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): Allow to replace and delete CLI notifications #2019

Merged
merged 3 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/phpunit-mysql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ jobs:
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, mysql, pdo_mysql
coverage: none
ini-file: development
# Temporary workaround for missing pcntl_* in PHP 8.3
ini-values: disable_functions=
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/phpunit-oci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ jobs:
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, oci8
coverage: none
ini-file: development
# Temporary workaround for missing pcntl_* in PHP 8.3
ini-values: disable_functions=
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/phpunit-pgsql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ jobs:
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, pgsql, pdo_pgsql
coverage: none
ini-file: development
# Temporary workaround for missing pcntl_* in PHP 8.3
ini-values: disable_functions=
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/phpunit-sqlite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ jobs:
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
coverage: none
ini-file: development
# Temporary workaround for missing pcntl_* in PHP 8.3
ini-values: disable_functions=
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Expand Down
1 change: 1 addition & 0 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
</background-jobs>

<commands>
<command>OCA\Notifications\Command\Delete</command>
<command>OCA\Notifications\Command\Generate</command>
<command>OCA\Notifications\Command\TestPush</command>
</commands>
Expand Down
27 changes: 21 additions & 6 deletions docs/admin-notifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,39 @@ Allows admins to generate notifications for users via the console or an HTTP end

```
$ sudo -u www-data ./occ notification:generate \
admin "Short message up to 255 characters" \
-l "Optional: longer message with more details, up to 4000 characters"
'admin' 'Short message up to 255 characters' \
-l 'Optional: longer message with more details, up to 4000 characters'
```

### Help

> [!TIP]
> Specify an object type and object id to delete previous notifications about
> the same thing,e.g. when the notification is about an update for "LibX" to
> version "12", use `--object-type='update' --object-id='libx'`, so that a later
> notification for version "13" can automatically dismiss the notification for
> version "12" if it was not removed in the meantime.

> [!TIP]
> Specify the `--output-id-only` option and store it to later be able to delete
> the generated notification using the `notification:delete` command.

```
$ sudo -u www-data ./occ notification:generate --help
Usage:
notification:generate [options] [--] <user-id> <short-message>

Arguments:
user-id User ID of the user to notify
short-message Short message to be sent to the user (max. 255 characters)
user-id User ID of the user to notify
short-message Short message to be sent to the user (max. 255 characters)

Options:
-l, --long-message=LONG-MESSAGE Long mesage to be sent to the user (max. 4000 characters) [default: ""]

--short-parameters=SHORT-PARAMETERS JSON encoded array of Rich objects to fill the short-message, see https://github.com/nextcloud/server/blob/master/lib/public/RichObjectStrings/Definitions.php for more information
-l, --long-message=LONG-MESSAGE Long message to be sent to the user (max. 4000 characters) [default: ""]
--long-parameters=LONG-PARAMETERS JSON encoded array of Rich objects to fill the long-message, see https://github.com/nextcloud/server/blob/master/lib/public/RichObjectStrings/Definitions.php for more information
--object-type=OBJECT-TYPE If an object type and id is provided, previous notifications with the same type and id will be deleted for this user (max. 64 characters)
--object-id=OBJECT-ID If an object type and id is provided, previous notifications with the same type and id will be deleted for this user (max. 64 characters)
--output-id-only When specified only the notification ID that was generated will be printed in case of success
```

## HTTP request
Expand Down
66 changes: 66 additions & 0 deletions lib/Command/Delete.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Notifications\Command;

use OCA\Notifications\Exceptions\NotificationNotFoundException;
use OCA\Notifications\Handler;
use OCP\Notification\IManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Delete extends Command {
public function __construct(
protected IManager $notificationManager,
protected Handler $notificationHandler,
) {
parent::__construct();
}

protected function configure(): void {
$this
->setName('notification:delete')
->setDescription('Delete a generated admin notification for the given user')
->addArgument(
'user-id',
InputArgument::REQUIRED,
'User ID of the user to notify'
)
->addArgument(
'notification-id',
InputArgument::REQUIRED,
'The notification ID returned by the "notification:generate" command'
)
;
}

/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int {

$userId = (string)$input->getArgument('user-id');
$notificationId = (int)$input->getArgument('notification-id');

try {
$notification = $this->notificationHandler->getById($notificationId, $userId);
} catch (NotificationNotFoundException) {
$output->writeln('<error>Notification not found for user</error>');
return 1;
}

$this->notificationManager->markProcessed($notification);
$output->writeln('<info>Notification deleted successfully</info>');
return 0;
}
}
123 changes: 98 additions & 25 deletions lib/Command/Generate.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,27 @@

namespace OCA\Notifications\Command;

use OCA\Notifications\App;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Notification\IManager;
use OCP\RichObjectStrings\IValidator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class Generate extends Command {
/** @var ITimeFactory */
protected $timeFactory;

/** @var IUserManager */
protected $userManager;

/** @var IManager */
protected $notificationManager;

public function __construct(ITimeFactory $timeFactory,
IUserManager $userManager,
IManager $notificationManager) {
public function __construct(
protected ITimeFactory $timeFactory,
protected IUserManager $userManager,
protected IManager $notificationManager,
protected IValidator $richValidator,
protected App $notificationApp,
) {
parent::__construct();

$this->timeFactory = $timeFactory;
$this->userManager = $userManager;
$this->notificationManager = $notificationManager;
}

protected function configure(): void {
Expand All @@ -53,19 +46,49 @@ protected function configure(): void {
InputArgument::REQUIRED,
'Short message to be sent to the user (max. 255 characters)'
)
->addOption(
'short-parameters',
null,
InputOption::VALUE_REQUIRED,
'JSON encoded array of Rich objects to fill the short-message, see https://github.com/nextcloud/server/blob/master/lib/public/RichObjectStrings/Definitions.php for more information',
)
->addOption(
'long-message',
'l',
InputOption::VALUE_REQUIRED,
'Long mesage to be sent to the user (max. 4000 characters)',
'Long message to be sent to the user (max. 4000 characters)',
''
)
->addOption(
'long-parameters',
null,
InputOption::VALUE_REQUIRED,
'JSON encoded array of Rich objects to fill the long-message, see https://github.com/nextcloud/server/blob/master/lib/public/RichObjectStrings/Definitions.php for more information',
)
->addOption(
'object-type',
null,
InputOption::VALUE_REQUIRED,
'If an object type and id is provided, previous notifications with the same type and id will be deleted for this user (max. 64 characters)',
)
->addOption(
'object-id',
null,
InputOption::VALUE_REQUIRED,
'If an object type and id is provided, previous notifications with the same type and id will be deleted for this user (max. 64 characters)',
)
->addOption(
'dummy',
'd',
InputOption::VALUE_NONE,
'Create a full-flexed dummy notification for client debugging with actions and parameters (short-message will be casted to integer and is the number of actions (max 3))'
)
->addOption(
'output-id-only',
null,
InputOption::VALUE_NONE,
'When specified only the notification ID that was generated will be printed in case of success'
)
;
}

Expand All @@ -75,10 +98,16 @@ protected function configure(): void {
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$userId = $input->getArgument('user-id');
$subject = $input->getArgument('short-message');
$message = $input->getOption('long-message');

$userId = (string)$input->getArgument('user-id');
$subject = (string)$input->getArgument('short-message');
$subjectParametersString = (string)$input->getOption('short-parameters');
$message = (string)$input->getOption('long-message');
$messageParametersString = (string)$input->getOption('long-parameters');
$dummy = $input->getOption('dummy');
$idOnly = $input->getOption('output-id-only');
$objectType = $input->getOption('object-type');
$objectId = $input->getOption('object-id');

$user = $this->userManager->get($userId);
if (!$user instanceof IUser) {
Expand All @@ -97,24 +126,63 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return 1;
}

if ($subjectParametersString !== '') {
$subjectParameters = json_decode($subjectParametersString, true);
if (!is_array($subjectParameters)) {
$output->writeln('Short message parameters is not a valid json array');
return 1;
}
$this->richValidator->validate($subject, $subjectParameters);
$storeSubjectParameters = ['subject' => $subject, 'parameters' => $subjectParameters];
} else {
$storeSubjectParameters = [$subject];
}

if ($message !== '' && $messageParametersString !== '') {
$messageParameters = json_decode($messageParametersString, true);
if (!is_array($messageParameters)) {
$output->writeln('Long message parameters is not a valid json array');
return 1;
}
$this->richValidator->validate($message, $messageParameters);
$storeMessageParameters = ['message' => $message, 'parameters' => $messageParameters];
} else {
$storeMessageParameters = [$message];
}

$subjectTitle = 'cli';
} else {
$subject = (int)$subject;
$storeSubjectParameters = [(int)$subject];
$storeMessageParameters = [];
$subjectTitle = 'dummy';
}

$notification = $this->notificationManager->createNotification();
$datetime = $this->timeFactory->getDateTime();

$isCustomObject = $objectId !== null && $objectType !== null;
if (!$isCustomObject) {
$objectId = dechex($datetime->getTimestamp());
$objectType = 'admin_notifications';
}

try {
$notification->setApp('admin_notifications')
->setUser($user->getUID())
->setDateTime($datetime)
->setObject('admin_notifications', dechex($datetime->getTimestamp()))
->setSubject($subjectTitle, [$subject]);
->setObject($objectType, $objectId);

if ($isCustomObject) {
$this->notificationManager->markProcessed($notification);
if (!$idOnly) {
$output->writeln('<comment>Previous notification for ' . $objectType . '/' . $objectId . ' marked as processed</comment>');
}
}

$notification->setDateTime($datetime)
->setSubject($subjectTitle, $storeSubjectParameters);

if ($message !== '') {
$notification->setMessage('cli', [$message]);
$notification->setMessage('cli', $storeMessageParameters);
}

$this->notificationManager->notify($notification);
Expand All @@ -123,6 +191,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return 1;
}

if ($idOnly) {
$output->writeln((string)$this->notificationApp->getLastInsertedId());
} else {
$output->writeln('<info>Notification with ID ' . $this->notificationApp->getLastInsertedId() . '</info>');
}
return 0;
}
}
Loading
Loading