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

Add CLI Commands #779

Merged
merged 89 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
3f7687e
add background job and bump version
mwinkens Mar 11, 2024
a17f532
add group, scheduleTime and deleteTime fields to Announcements
mwinkens Mar 11, 2024
5b544c8
add scheduleTime and deleteTime to API
mwinkens Mar 11, 2024
9fde067
add AnnouncementScheduler together with processing service
mwinkens Mar 11, 2024
db95fa8
rework publishing in order to not directly publish if an Annoucement …
mwinkens Mar 11, 2024
2e09ca0
advance frontend and add new buttons
mwinkens Mar 11, 2024
d98e569
improve service by filtering out jobs with 0 scheduleTime or deleteTime
mwinkens Mar 11, 2024
a19d2c2
fix annoucementSchedulerJob copy pasta from developer manual
mwinkens Mar 11, 2024
f12c3ff
fix unit tests by adding default values
mwinkens Mar 11, 2024
ee54ec0
fix various linting issues
mwinkens Mar 11, 2024
cee420c
Fix sequence
mwinkens Mar 11, 2024
50a8a60
rename old BackgroundJob in order to reduce name confusion
mwinkens Mar 12, 2024
fbd025c
add notification options to database in order to schedule them later
mwinkens Mar 12, 2024
8ec0e0d
move notification execution into Manager
mwinkens Mar 12, 2024
6ee54f8
fix issues with the background job
mwinkens Mar 12, 2024
c54e4ec
fix typos
mwinkens Mar 12, 2024
721365c
fix linting issues
mwinkens Mar 12, 2024
006c9b3
fix new constructors
mwinkens Mar 12, 2024
47eb024
fix wrong number of argument for announce function since notification…
mwinkens Mar 12, 2024
9b0a5e6
register services missing for proper dependency injection
mwinkens Mar 12, 2024
edeb52c
bump version
mwinkens Mar 12, 2024
beb8095
fix annoying typo preveting background job to work
mwinkens Mar 13, 2024
68382aa
fix announcements not being updated when scheduled due to changed dat…
mwinkens Mar 13, 2024
024e568
fix linting issues
mwinkens Mar 13, 2024
6d82776
only allow scheduled annouce and deletion dates in the future
mwinkens Mar 13, 2024
13c7b36
remove dependency injections
mwinkens Mar 13, 2024
30af9c3
fix tests using wrong TestCase class
mwinkens Mar 14, 2024
10d9610
Add unittests for annoucement scheduling
mwinkens Mar 14, 2024
f2e0d32
fix linting
mwinkens Mar 14, 2024
e7f6099
minor refactoring
mwinkens Mar 14, 2024
1f730ac
Update lib/AnnouncementSchedulerJob.php
mwinkens Mar 18, 2024
846e69c
Update lib/Migration/Version6009Date20240311074015.php
mwinkens Mar 18, 2024
5cb3211
Update lib/Migration/Version6009Date20240311074015.php
mwinkens Mar 18, 2024
0dc485d
Update lib/Migration/Version6009Date20240311074015.php
mwinkens Mar 18, 2024
c045148
Update tests/AnnouncementSchedulerJobTest.php
mwinkens Mar 18, 2024
59751c2
Update tests/Model/NotificationTypeTest.php
mwinkens Mar 18, 2024
b23a13c
Update tests/Service/AnnouncementSchedulerProcessorTest.php
mwinkens Mar 18, 2024
d5137bf
minor adjustments, adding license, linting, removing unused methods
mwinkens Mar 18, 2024
6881dec
Update src/Components/NewForm.vue
mwinkens Mar 18, 2024
844f920
Update src/Components/NewForm.vue
mwinkens Mar 18, 2024
53aacda
remove logger from job construction
mwinkens Mar 18, 2024
2b57ffe
chore: update workflows from templates
skjnldsv Mar 8, 2024
4bdc34b
Fix(l10n): Update translations from Transifex
nextcloud-bot Mar 14, 2024
54601eb
Updating phpunit-mysql.yml workflow from template
nextcloud-bot Mar 15, 2024
2f6adb7
chore(deps): Bump follow-redirects from 1.15.4 to 1.15.6
dependabot[bot] Mar 15, 2024
3204482
chore(deps): Bump @nextcloud/vue from 8.9.1 to 8.11.0
dependabot[bot] Mar 16, 2024
659bfa8
chore(deps-dev): Bump psalm/phar from 5.22.2 to 5.23.1
dependabot[bot] Mar 16, 2024
7aa26f7
fix(settings): Fix missing admin settings
nickvergessen Mar 21, 2024
64ba51e
fix(UI): Fix missing names after vue lib update
nickvergessen Mar 21, 2024
a5d1a2a
fix(form): Fix event-trigger for searching more groups
nickvergessen Mar 21, 2024
bbf9aa9
fix(form): Add label for accessibility
nickvergessen Mar 21, 2024
840f4e8
chore(release): Bump version to 6.8.1
nickvergessen Mar 21, 2024
df2f5ce
fix(form): Fix accessibility label option
nickvergessen Mar 21, 2024
b5558eb
Fix(l10n): Update translations from Transifex
nextcloud-bot Mar 23, 2024
77c13c2
chore(deps): Bump @nextcloud/vue from 8.11.0 to 8.11.1
dependabot[bot] Mar 23, 2024
7df1e9d
chore(deps-dev): Bump webpack-dev-middleware from 5.3.3 to 5.3.4
dependabot[bot] Mar 23, 2024
b8e1696
chore(deps-dev): Bump phpunit/phpunit from 9.6.17 to 9.6.18
dependabot[bot] Mar 23, 2024
7493d7f
Add CLI commands for annonce, list and delete
mwinkens Mar 25, 2024
9e8102d
add limit to the optional parameters of list
mwinkens Mar 25, 2024
ecb8976
overengineer table output
mwinkens Mar 25, 2024
60395ae
Switch from left to right pad
mwinkens Mar 25, 2024
5405cb0
make a lot of parameters optional, properly handle boolean type param…
mwinkens Mar 26, 2024
66f21c6
properly catch if the announcement ID which should get deleted exists
mwinkens Mar 26, 2024
4610757
fix formatting
mwinkens Mar 26, 2024
c5f6fdb
Send user schedule and deletion date if entered
mwinkens Mar 26, 2024
653862a
add check, that the announcement has any notification option
mwinkens Mar 26, 2024
32d3891
Merge branch 'main' of github.com:nextcloud/announcementcenter into c…
mwinkens Mar 26, 2024
382a683
fix description
mwinkens Mar 26, 2024
85eab1d
add license
mwinkens Mar 26, 2024
57307ef
Capitalize description
mwinkens Mar 26, 2024
decec32
add missing psalm classes
mwinkens Mar 26, 2024
7b24120
add unit tests for CLI commands
mwinkens Mar 26, 2024
e97746d
add symfony dev dependency
mwinkens Mar 26, 2024
02a66d9
remove smyfony composer dependency and remove symfony static integer …
mwinkens Mar 26, 2024
76942fd
Merge branch 'main' of github.com:nextcloud/announcementcenter into c…
mwinkens Jul 29, 2024
3a69433
remove callHidden helper function and use self::invokePrivate instead
mwinkens Jul 29, 2024
cce0e11
Update lib/Command/Announce.php
mwinkens Jul 29, 2024
a1f29b6
Update lib/Command/Announce.php
mwinkens Jul 29, 2024
67675db
Update lib/Command/Announce.php
mwinkens Jul 29, 2024
f85a5c2
Update lib/Command/Announce.php
mwinkens Jul 29, 2024
2e9e646
Update lib/Command/Announce.php
mwinkens Jul 29, 2024
ae62f95
return an error value instead of throwing an exception
mwinkens Jul 29, 2024
ab36328
test for result values bigger than zero and not for exceptions
mwinkens Jul 29, 2024
cb96aed
fix composer issues
mwinkens Jul 29, 2024
a241d6a
fix test being broken due to removed exception
mwinkens Jul 29, 2024
a54d313
remove restriction on notification types for CLI
mwinkens Jul 30, 2024
9c9b144
Update lib/Command/AnnouncementList.php
mwinkens Jul 30, 2024
b1a0d68
remove linebreaks from list command
mwinkens Jul 30, 2024
b4610c8
fix test not properly setting non exististing user
mwinkens Jul 30, 2024
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
6 changes: 6 additions & 0 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@
</post-migration>
</repair-steps>

<commands>
<command>OCA\AnnouncementCenter\Command\Announce</command>
<command>OCA\AnnouncementCenter\Command\AnnouncementList</command>
<command>OCA\AnnouncementCenter\Command\AnnouncementDelete</command>
</commands>

<settings>
<admin>OCA\AnnouncementCenter\Settings\Admin</admin>
</settings>
Expand Down
200 changes: 200 additions & 0 deletions lib/Command/Announce.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
<?php
/**
* @copyright Copyright (c) 2024 Marvin Winkens <m.winkens@fz-juelich.de>
*
* @author Marvin Winkens <m.winkens@fz-juelich.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\AnnouncementCenter\Command;

use OCA\AnnouncementCenter\Manager;
use OCA\AnnouncementCenter\Model\NotificationType;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IUserManager;
use Psr\Log\LoggerInterface;
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 Announce extends Command {
protected IUserManager $userManager;
protected ITimeFactory $time;
protected Manager $manager;
protected NotificationType $notificationType;
protected LoggerInterface $logger;
public function __construct(IUserManager $userManager, ITimeFactory $time, Manager $manager, NotificationType $notificationType, LoggerInterface $logger) {
parent::__construct();
$this->userManager = $userManager;
$this->time = $time;
$this->manager = $manager;
$this->notificationType = $notificationType;
$this->logger = $logger;
}

protected function configure(): void {
$this
->setName('announcementcenter:announce')
->setDescription('Create an announcement')
->addArgument(
'user',
InputArgument::REQUIRED,
'User who creates the announcement',
)
->addArgument(
'subject',
InputArgument::REQUIRED,
'Subject of the announcement',
)
->addArgument(
'message',
InputArgument::REQUIRED,
'Message of the announcement (supports markdown)',
)
->addOption(
'activities',
null,
InputOption::VALUE_NONE,
'Generate activities',
)
->addOption(
'notifications',
null,
InputOption::VALUE_NONE,
'Generate notifications',
)
->addOption(
'emails',
null,
InputOption::VALUE_NONE,
'Notify users via email',
)
->addOption(
'comments',
null,
InputOption::VALUE_NONE,
'Allow comments',
)
->addOption(
'schedule-time',
's',
InputOption::VALUE_OPTIONAL,
'Publishing time of the announcement (see php strtotime)',
null,
)
->addOption(
'delete-time',
'd',
InputOption::VALUE_OPTIONAL,
'Deletion time of the announcement (see php strtotime)',
null,
)
->addOption(
'group',
'g',
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
'Group to set send announcement to (default "everyone", multiple allowed)',
['everyone'],
);
}

private function plainifyMessage(string $message) {
# TODO use Parsedown or Markdownify here
return $message;
}

protected function execute(InputInterface $input, OutputInterface $output): int {
// required
$user = $input->getArgument('user');
if (!$this->userManager->userExists($user)) {
$output->writeln("User <$user> in unknown.");
return 1;
}
$subject = $input->getArgument('subject');
$message = $input->getArgument('message');

// options
$groups = $input->getOption('group');

// notification types
$activities = $input->getOption('activities');
$notifications = $input->getOption('notifications');
$emails = $input->getOption('emails');
$comments = $input->getOption('comments');

// times
try {
$scheduleTime = $this->parseTimestamp($input->getOption('schedule-time'));
$deleteTime = $this->parseTimestamp($input->getOption('delete-time'));
} catch(\InvalidArgumentException $e) {
$output->writeln($e->getMessage());
return 2;
}

// validation
if ($scheduleTime && $deleteTime && $deleteTime < $scheduleTime) {
$output->writeln('Publishing time is after deletion time');
return 2;
}

$plainMessage = $this->plainifyMessage($message);
$notificationOptions = $this->notificationType->setNotificationTypes($activities, $notifications, $emails);

$result = $this->manager->announce($subject, $message, $plainMessage, $user, $this->time->getTime(), $groups, $comments, $notificationOptions, $scheduleTime, $deleteTime);
$output->writeln("Created announcement #" . $result->getId() . ": " . $result->getSubject());

if ($scheduleTime) {
$output->writeln("Scheduled announcement for '" . date("D M j G:i:s T Y", $scheduleTime) . "'");
}

if ($deleteTime) {
$output->writeln("Scheduled deletion for '" . date("D M j G:i:s T Y", $deleteTime) . "'");
}

$this->logger->info('Admin ' . $user . ' posted a new announcement: "' . $result->getSubject() . '" over CLI');
return 0;
}

/**
* Parses an arbitrary $argument into a timestamp
* @param null|int|string $argument argument provided by CLI for a time
* Examples 1:
* '1711440621' a plain unix timestamp
* Examples 2 see strtotime (https://www.php.net/manual/de/function.strtotime.php):
* 'now', 10 September 200', '+1 day', 'tomorrow'
* @return int|null a timestamp, returns null if $argument is null
* @throws \InvalidArgumentException If the time could not be interpreted or the time is in the past
*/
private function parseTimestamp(null|int|string $argument): int|null {
if (is_null($argument)) {
return null;
} elseif (is_numeric($argument)) {
$timestamp = intval($argument);
} elseif (($convTime = strtotime($argument)) !== false) {
$timestamp = $convTime;
} else {
throw new \InvalidArgumentException("Could not interprete time '" . $argument . "'");
}

if ($timestamp < $this->time->getTime()) {
throw new \InvalidArgumentException("Time '" . $argument . "' is not allowed, because it's in the past");
}
return $timestamp;
}
}
76 changes: 76 additions & 0 deletions lib/Command/AnnouncementDelete.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php
/**
* @copyright Copyright (c) 2024 Marvin Winkens <m.winkens@fz-juelich.de>
*
* @author Marvin Winkens <m.winkens@fz-juelich.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\AnnouncementCenter\Command;

use InvalidArgumentException;
use OCA\AnnouncementCenter\Manager;
use OCP\AppFramework\Db\DoesNotExistException;
use Psr\Log\LoggerInterface;
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 AnnouncementDelete extends Command {
protected Manager $manager;
protected LoggerInterface $logger;
public function __construct(Manager $manager, LoggerInterface $logger) {
parent::__construct();
$this->manager = $manager;
$this->logger = $logger;
}

protected function configure(): void {
$this
->setName('announcementcenter:delete')
->setDescription('Delete announcement by id')
->addArgument(
'id',
InputArgument::REQUIRED,
'Id of announcement to delete',
);
}

protected function execute(InputInterface $input, OutputInterface $output): int {
try {
$deleteId = $this->parseId($input->getArgument('id'));
$this->manager->delete($deleteId);
} catch (DoesNotExistException) {
$output->writeln("Announcement with #" . $deleteId . " does not exist!");
return 1;
} catch (InvalidArgumentException $e) {
$output->writeln($e->getMessage());
return 1;
}
$output->writeln("Successfully deleted #" . $deleteId);
$this->logger->info('Admin deleted announcement #' . $deleteId . ' over CLI');
return 0;
}

private function parseId(mixed $value) {
if (is_numeric($value)) {
return intval($value);
}
throw new InvalidArgumentException('Id "' . $value . '" is not an integer');
}
}
105 changes: 105 additions & 0 deletions lib/Command/AnnouncementList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php
/**
* @copyright Copyright (c) 2024 Marvin Winkens <m.winkens@fz-juelich.de>
*
* @author Marvin Winkens <m.winkens@fz-juelich.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\AnnouncementCenter\Command;

use OCA\AnnouncementCenter\Manager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Terminal;

class AnnouncementList extends Command {
protected Manager $manager;
public function __construct(Manager $manager) {
parent::__construct();
$this->manager = $manager;
}

protected function configure(): void {
$this
->setName('announcementcenter:list')
->setDescription('List all announcements')
->addArgument(
'limit',
InputArgument::OPTIONAL,
'Maximal number of announcements listed',
10,
);
}

protected function execute(InputInterface $input, OutputInterface $output): int {
$ulimit = $input->getArgument('limit');
if (!is_numeric($ulimit)) {
$output->writeln('"' . $ulimit . '" is not numeric');
return 1;
}
$ulimit = intval($ulimit);
$announcements = $this->manager->getAnnouncements(0, $ulimit + 1);

// Calculate table size
$terminal = new Terminal();
$width = $terminal->getWidth();
$minimalWidth = 6;
$minimalWidthText = 10;
$widthSubject = max($minimalWidthText, intdiv($width - $minimalWidth, 3));
$widthMessage = max($minimalWidthText, $width - $minimalWidth - $widthSubject);

$widths = [$minimalWidth - 2, $widthSubject, $widthMessage];
$text = $this->formatTableRow(["ID", "Subject", "Message"], $widths);
$output->writeln($text);
$text = $this->formatTableRow(["", "", ""], $widths, "-");
$output->writeln($text);

foreach ($announcements as $index => $ann) {
if ($index === $ulimit) {
$output->writeln("And more ...");
break;
}
$texts = [$ann->getId(), $ann->getParsedSubject(), $ann->getPlainMessage()];

$text = $this->formatTableRow($texts, $widths);
$output->writeln($text);
}
return 0;
}

private function ellipseAndPadText(string $text, int $width, string $sep = " "): string {
$text = str_replace(["\r", "\n"], ' ', $text);
$text = str_pad($text, $width, $sep, STR_PAD_RIGHT);
$text = strlen($text) > $width ? substr($text, 0, $width - 2) . " …" : $text;
return $text;
}

private function formatTableRow(array $texts, array $widths, string $sep = " "): string {
$callback = function ($a, $b) use ($sep) {
return $this->ellipseAndPadText($a, $b, $sep);
};
$formattedTexts = array_map(
$callback,
$texts,
$widths
);
return implode("|", $formattedTexts);
}
}
Loading
Loading