From 507510f60be1fb847124dfd43c106943346b55d9 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 12 Jul 2023 13:03:17 +0200 Subject: [PATCH] WIP: enh(import): import deck json exports Signed-off-by: Max --- .../Importer/BoardImportCommandService.php | 4 +- lib/Service/Importer/BoardImportService.php | 6 + .../Importer/Systems/DeckJsonService.php | 241 ++++++++++++++++++ .../fixtures/config-deckJson-schema.json | 24 ++ 4 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 lib/Service/Importer/Systems/DeckJsonService.php create mode 100644 lib/Service/Importer/fixtures/config-deckJson-schema.json diff --git a/lib/Service/Importer/BoardImportCommandService.php b/lib/Service/Importer/BoardImportCommandService.php index d45c784de..ced94fd47 100644 --- a/lib/Service/Importer/BoardImportCommandService.php +++ b/lib/Service/Importer/BoardImportCommandService.php @@ -95,7 +95,7 @@ class BoardImportCommandService extends BoardImportService { $helper = $this->getCommand()->getHelper('question'); $question = new Question( "You can get more info on https://deck.readthedocs.io/en/latest/User_documentation_en/#6-import-boards\n" . - 'Please inform a valid config json file: ', + 'Please provide a valid config json file: ', 'config.json' ); $question->setValidator(function (string $answer) { @@ -130,7 +130,7 @@ class BoardImportCommandService extends BoardImportService { $allowedSystems = $this->getAllowedImportSystems(); $names = array_column($allowedSystems, 'name'); $question = new ChoiceQuestion( - 'Please inform a source system', + 'Please select a source system', $names, 0 ); diff --git a/lib/Service/Importer/BoardImportService.php b/lib/Service/Importer/BoardImportService.php index ee1ff7dc7..20fd959f6 100644 --- a/lib/Service/Importer/BoardImportService.php +++ b/lib/Service/Importer/BoardImportService.php @@ -42,6 +42,7 @@ use OCA\Deck\NotFoundException; use OCA\Deck\Service\FileService; use OCA\Deck\Service\Importer\Systems\TrelloApiService; use OCA\Deck\Service\Importer\Systems\TrelloJsonService; +use OCA\Deck\Service\Importer\Systems\DeckJsonService; use OCP\Comments\IComment; use OCP\Comments\ICommentsManager; use OCP\Comments\NotFoundException as CommentNotFoundException; @@ -174,6 +175,11 @@ class BoardImportService { 'class' => TrelloJsonService::class, 'internalName' => 'TrelloJson' ]); + $this->addAllowedImportSystem([ + 'name' => DeckJsonService::$name, + 'class' => DeckJsonService::class, + 'internalName' => 'DeckJson' + ]); } $this->eventDispatcher->dispatchTyped(new BoardImportGetAllowedEvent($this)); return $this->allowedSystems; diff --git a/lib/Service/Importer/Systems/DeckJsonService.php b/lib/Service/Importer/Systems/DeckJsonService.php new file mode 100644 index 000000000..70b1f23a2 --- /dev/null +++ b/lib/Service/Importer/Systems/DeckJsonService.php @@ -0,0 +1,241 @@ + + * + * @author Vitor Mattos + * + * @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 . + * + */ + +namespace OCA\Deck\Service\Importer\Systems; + +use OC\Comments\Comment; +use OCA\Deck\BadRequestException; +use OCA\Deck\Db\Acl; +use OCA\Deck\Db\Assignment; +use OCA\Deck\Db\Attachment; +use OCA\Deck\Db\Board; +use OCA\Deck\Db\Card; +use OCA\Deck\Db\Label; +use OCA\Deck\Db\Stack; +use OCA\Deck\Service\Importer\ABoardImportService; +use OCP\Comments\IComment; +use OCP\IL10N; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\IUserManager; + +class DeckJsonService extends ABoardImportService { + /** @var string */ + public static $name = 'Deck JSON'; + /** @var IUserManager */ + private $userManager; + /** @var IURLGenerator */ + private $urlGenerator; + /** @var IL10N */ + private $l10n; + /** @var IUser[] */ + private $members = []; + private array $tmpCards = []; + + public function __construct( + IUserManager $userManager, + IURLGenerator $urlGenerator, + IL10N $l10n + ) { + $this->userManager = $userManager; + $this->urlGenerator = $urlGenerator; + $this->l10n = $l10n; + } + + public function bootstrap(): void { + $this->validateUsers(); + } + + public function getJsonSchemaPath(): string { + return implode(DIRECTORY_SEPARATOR, [ + __DIR__, + '..', + 'fixtures', + 'config-deckJson-schema.json', + ]); + } + + public function validateUsers(): void { + if (empty($this->getImportService()->getConfig('uidRelation'))) { + return; + } + foreach ($this->getImportService()->getConfig('uidRelation') as $exportUid => $nextcloudUid) { + $user = array_filter($this->getImportService()->getData()->members, function (\stdClass $u) use ($exportUid) { + return $u->username === $exportUid; + }); + if (!$user) { + throw new \LogicException('Trello user ' . $exportUid . ' not found in property "members" of json data'); + } + if (!is_string($nextcloudUid) && !is_numeric($nextcloudUid)) { + throw new \LogicException('User on setting uidRelation is invalid'); + } + $nextcloudUid = (string) $nextcloudUid; + $this->getImportService()->getConfig('uidRelation')->$exportUid = $this->userManager->get($nextcloudUid); + if (!$this->getImportService()->getConfig('uidRelation')->$exportUid) { + throw new \LogicException('User on setting uidRelation not found: ' . $nextcloudUid); + } + $user = current($user); + $this->members[$user->id] = $this->getImportService()->getConfig('uidRelation')->$exportUid; + } + } + + public function getCardAssignments(): array { + $assignments = []; + foreach ($this->tmpCards as $sourceCard) { + foreach ($sourceCard->assignedUsers as $idMember) { + $assignment = new Assignment(); + $assignment->setCardId($this->cards[$sourceCard->id]->getId()); + $assignment->setParticipant($idMember->participant->uid); + $assignment->setType($idMember->participant->type); + $assignments[$sourceCard->id][] = $assignment; + } + } + return $assignments; + } + + public function getComments(): array { + // Comments are not implemented in export + return []; + } + + public function getCardLabelAssignment(): array { + $cardsLabels = []; + foreach ($this->tmpCards as $sourceCard) { + foreach ($sourceCard->labels as $label) { + $cardId = $this->cards[$sourceCard->id]->getId(); + $labelId = $this->labels[$label->id]->getId(); + $cardsLabels[$cardId][] = $labelId; + } + } + return $cardsLabels; + } + + public function getBoard(): Board { + $board = $this->getImportService()->getBoard(); + if (empty($this->getImportService()->getData()->title)) { + throw new BadRequestException('Invalid name of board'); + } + $board->setTitle($this->getImportService()->getData()->title); + $board->setOwner($this->getImportService()->getData()->owner->uid); + $board->setColor($this->getImportService()->getData()->color); + return $board; + } + + /** + * @return Label[] + */ + public function getLabels(): array { + foreach ($this->getImportService()->getData()->labels as $label) { + $newLabel = new Label(); + $newLabel->setTitle($label->title); + $newLabel->setColor($label->color); + $newLabel->setBoardId($this->getImportService()->getBoard()->getId()); + $this->labels[$label->id] = $newLabel; + } + return $this->labels; + } + + /** + * @return Stack[] + */ + public function getStacks(): array { + $return = []; + foreach ($this->getImportService()->getData()->stacks as $index => $source) { + if ($source->title) { + $stack = new Stack(); + $stack->setTitle($source->title); + $stack->setBoardId($this->getImportService()->getBoard()->getId()); + $stack->setOrder($source->order); + $return[$source->id] = $stack; + } + + if ($source->cards) { + foreach ($source->cards as $card) { + $card->stackId = $index; + $this->tmpCards[] = $card; + } + // TODO: check older exports as currently there is a bug that adds lists to it with different index + } + } + return $return; + } + + /** + * @return Card[] + */ + public function getCards(): array { + $cards = []; + foreach ($this->tmpCards as $cardSource) { + $card = new Card(); + $card->setTitle($cardSource->title); + $card->setLastModified($cardSource->lastModified); + $card->setArchived($cardSource->archived); + $card->setDescription($cardSource->description); + $card->setStackId($this->stacks[$cardSource->stackId]->getId()); + $card->setType('plain'); + $card->setOrder($cardSource->order); + $card->setOwner($this->getBoard()->getOwner()); + $card->setDuedate($cardSource->duedate); + $cards[$cardSource->id] = $card; + } + return $cards; + } + + /** + * @return Acl[] + */ + public function getAclList(): array { + // FIXME: To implement + $return = []; + foreach ($this->members as $member) { + if ($member->getUID() === $this->getImportService()->getConfig('owner')->getUID()) { + continue; + } + $acl = new Acl(); + $acl->setBoardId($this->getImportService()->getBoard()->getId()); + $acl->setType(Acl::PERMISSION_TYPE_USER); + $acl->setParticipant($member->getUID()); + $acl->setPermissionEdit(false); + $acl->setPermissionShare(false); + $acl->setPermissionManage(false); + $return[] = $acl; + } + return $return; + } + + private function replaceUsernames(string $text): string { + foreach ($this->getImportService()->getConfig('uidRelation') as $trello => $nextcloud) { + $text = str_replace($trello, $nextcloud->getUID(), $text); + } + return $text; + } + + public function getBoards(): array { + return get_object_vars($this->getImportService()->getData()); + } + + public function reset(): void { + parent::reset(); + $this->tmpCards = []; + } +} diff --git a/lib/Service/Importer/fixtures/config-deckJson-schema.json b/lib/Service/Importer/fixtures/config-deckJson-schema.json new file mode 100644 index 000000000..7635727c1 --- /dev/null +++ b/lib/Service/Importer/fixtures/config-deckJson-schema.json @@ -0,0 +1,24 @@ +{ + "type": "object", + "properties": { + "uidRelation": { + "type": "object", + "comment": "Relationship between Trello and Nextcloud usernames", + "example": { + "johndoe": "admin" + } + }, + "owner": { + "type": "string", + "required": true, + "comment": "Nextcloud owner username" + }, + "color": { + "type": "string", + "required": true, + "pattern": "^[0-9a-fA-F]{6}$", + "comment": "Default color for the board. If you don't inform, the default color will be used.", + "default": "0800fd" + } + } +} \ No newline at end of file