diff --git a/lib/Command/BoardImport.php b/lib/Command/BoardImport.php index 4c70c61ae..66ee33b3e 100644 --- a/lib/Command/BoardImport.php +++ b/lib/Command/BoardImport.php @@ -25,6 +25,7 @@ namespace OCA\Deck\Command; use OCA\Deck\Service\Importer\BoardImportCommandService; 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; @@ -41,7 +42,9 @@ class BoardImport extends Command { */ protected function configure() { $allowedSystems = $this->boardImportCommandService->getAllowedImportSystems(); - $names = array_column($allowedSystems, 'name'); + $names = array_map(function ($name) { + return '"' . $name . '"'; + }, array_column($allowedSystems, 'internalName')); $this ->setName('deck:import') ->setDescription('Import data') @@ -50,7 +53,7 @@ class BoardImport extends Command { null, InputOption::VALUE_REQUIRED, 'Source system for import. Available options: ' . implode(', ', $names) . '.', - null + 'DeckJson', ) ->addOption( 'config', @@ -66,6 +69,11 @@ class BoardImport extends Command { 'Data file to import.', 'data.json' ) + ->addArgument( + 'file', + InputArgument::OPTIONAL, + 'File to import', + ) ; } diff --git a/lib/Db/RelationalEntity.php b/lib/Db/RelationalEntity.php index 8c27daba5..919d40ca1 100644 --- a/lib/Db/RelationalEntity.php +++ b/lib/Db/RelationalEntity.php @@ -138,7 +138,7 @@ class RelationalEntity extends Entity implements \JsonSerializable { $attr = lcfirst(substr($methodName, 3)); if (array_key_exists($attr, $this->_resolvedProperties) && str_starts_with($methodName, 'set')) { - if (!is_scalar($args[0])) { + if ($args[0] !== null && !is_scalar($args[0])) { $args[0] = $args[0]['primaryKey']; } parent::setter($attr, $args); diff --git a/lib/Service/Importer/BoardImportCommandService.php b/lib/Service/Importer/BoardImportCommandService.php index e599f7ff6..23257e499 100644 --- a/lib/Service/Importer/BoardImportCommandService.php +++ b/lib/Service/Importer/BoardImportCommandService.php @@ -25,6 +25,7 @@ namespace OCA\Deck\Service\Importer; use OCA\Deck\Exceptions\ConflictException; use OCA\Deck\NotFoundException; +use OCA\Deck\Service\Importer\Systems\DeckJsonService; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -76,6 +77,10 @@ class BoardImportCommandService extends BoardImportService { } protected function validateConfig(): void { + // FIXME: Make config optional for deck plain importer (but use a call on the importer insterad) + if ($this->getImportSystem() instanceof DeckJsonService) { + return; + } try { $config = $this->getInput()->getOption('config'); if (is_string($config)) { @@ -145,6 +150,18 @@ class BoardImportCommandService extends BoardImportService { if (!$this->getImportSystem()->needValidateData()) { return; } + $data = $this->getInput()->getArgument('file'); + if (is_string($data)) { + if (!file_exists($data)) { + throw new \OCP\Files\NotFoundException('Could not find file ' . $data); + } + $data = json_decode(file_get_contents($data)); + if ($data instanceof \stdClass) { + $this->setData($data); + return; + } + } + $data = $this->getInput()->getOption('data'); if (is_string($data)) { $data = json_decode(file_get_contents($data)); diff --git a/lib/Service/Importer/BoardImportService.php b/lib/Service/Importer/BoardImportService.php index 364fd1bce..dcd0bbc0c 100644 --- a/lib/Service/Importer/BoardImportService.php +++ b/lib/Service/Importer/BoardImportService.php @@ -84,6 +84,8 @@ class BoardImportService { ) { $this->board = new Board(); $this->disableCommentsEvents(); + + $this->config = new \stdClass(); } private function disableCommentsEvents(): void { @@ -151,6 +153,11 @@ class BoardImportService { public function getAllowedImportSystems(): array { if (!$this->allowedSystems) { + $this->addAllowedImportSystem([ + 'name' => DeckJsonService::$name, + 'class' => DeckJsonService::class, + 'internalName' => 'DeckJson' + ]); $this->addAllowedImportSystem([ 'name' => TrelloApiService::$name, 'class' => TrelloApiService::class, @@ -161,11 +168,6 @@ 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 index 703068482..57caff6c7 100644 --- a/lib/Service/Importer/Systems/DeckJsonService.php +++ b/lib/Service/Importer/Systems/DeckJsonService.php @@ -31,8 +31,6 @@ use OCA\Deck\Db\Card; use OCA\Deck\Db\Label; use OCA\Deck\Db\Stack; use OCA\Deck\Service\Importer\ABoardImportService; -use OCP\IL10N; -use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserManager; @@ -44,8 +42,6 @@ class DeckJsonService extends ABoardImportService { public function __construct( private IUserManager $userManager, - private IURLGenerator $urlGenerator, - private IL10N $l10n ) { } @@ -86,6 +82,20 @@ class DeckJsonService extends ABoardImportService { } } + public function mapMember($uid): ?string { + + $uidCandidate = $this->members[$uid]?->getUID() ?? null; + if ($uidCandidate) { + return $uidCandidate; + } + + if ($this->userManager->userExists($uid)) { + return $uid; + } + + return null; + } + public function getCardAssignments(): array { $assignments = []; foreach ($this->tmpCards as $sourceCard) { @@ -176,6 +186,7 @@ class DeckJsonService extends ABoardImportService { $card = new Card(); $card->setTitle($cardSource->title); $card->setLastModified($cardSource->lastModified); + $card->setCreatedAt($cardSource->createdAt); $card->setArchived($cardSource->archived); $card->setDescription($cardSource->description); $card->setStackId($this->stacks[$cardSource->stackId]->getId()); diff --git a/tests/integration/import/ImportExportTest.php b/tests/integration/import/ImportExportTest.php index f6e339d6a..4ee73c5ad 100644 --- a/tests/integration/import/ImportExportTest.php +++ b/tests/integration/import/ImportExportTest.php @@ -26,6 +26,7 @@ namespace OCA\Deck\Db; use OCA\Deck\Command\BoardImport; use OCA\Deck\Service\Importer\BoardImportService; use OCA\Deck\Service\Importer\Systems\DeckJsonService; +use OCP\AppFramework\Db\Entity; use OCP\IDBConnection; use OCP\IGroupManager; use OCP\IUserManager; @@ -117,10 +118,98 @@ class ImportExportTest extends \Test\TestCase { public function assertDatabase() { $boardMapper = \OCP\Server::get(BoardMapper::class); + $stackMapper = \OCP\Server::get(StackMapper::class); + $cardMapper = \OCP\Server::get(CardMapper::class); + $boards = $boardMapper->findAllByOwner('admin'); - self::assertEquals('My test board', $boards[0]->getTitle()); - self::assertEquals('Shared board', $boards[1]->getTitle()); self::assertEquals(2, count($boards)); + + $board = $boards[0]; + self::assertEntity(Board::fromRow([ + 'title' => 'My test board', + 'color' => 'e0ed31', + 'owner' => 'admin', + ]), $board); + + $stacks = $stackMapper->findAll($board->getId()); + self::assertCount(3, $stacks); + self::assertEntity(Stack::fromRow([ + 'title' => 'A', + 'order' => 999, + 'boardId' => $boards[0]->getId(), + ]), $stacks[0]); + self::assertEntity(Stack::fromRow([ + 'title' => 'B', + 'order' => 999, + 'boardId' => $boards[0]->getId(), + ]), $stacks[1]); + self::assertEntity(Stack::fromRow([ + 'title' => 'C', + 'order' => 999, + 'boardId' => $boards[0]->getId(), + ]), $stacks[2]); + + $cards = $cardMapper->findAll($stacks[0]->getId()); + self::assertEntity(Card::fromRow([ + 'title' => '1', + 'description' => '', + 'type' => 'plain', + 'lastModified' => 1689667779, + 'createdAt' => 1689667569, + 'owner' => 'admin', + 'duedate' => new \DateTime('2050-07-24T22:00:00.000000+0000'), + 'order' => 999, + 'stackId' => $stacks[0]->getId(), + ]), $cards[0]); + self::assertEntity(Card::fromRow([ + 'title' => '2', + 'duedate' => new \DateTime('2050-07-24T22:00:00.000000+0000'), + ]), $cards[1], true); + self::assertEntity(Card::fromParams([ + 'title' => '3', + 'duedate' => null, + ]), $cards[2], true); + + $cards = $cardMapper->findAll($stacks[1]->getId()); + self::assertEntity(Card::fromParams([ + 'title' => '6', + 'duedate' => null, + 'description' => "# Test description\n\nHello world", + ]), $cards[2], true); + + // Shared board + $sharedBoard = $boards[1]; + self::assertEntity(Board::fromRow([ + 'title' => 'Shared board', + 'color' => '30b6d8', + 'owner' => 'admin', + ]), $sharedBoard); + + $stacks = $stackMapper->findAll($sharedBoard->getId()); + self::assertCount(3, $stacks); + } + + public static function assertEntity(Entity $expected, Entity $actual, bool $checkProperties = false) { + if ($checkProperties === true) { + $e = clone $expected; + $a = clone $actual; + foreach ($e->getUpdatedFields() as $property => $updated) { + $expectedValue = call_user_func([$e, 'get' . ucfirst($property)]); + $actualValue = call_user_func([$a, 'get' . ucfirst($property)]); + self::assertEquals( + $expectedValue, + $actualValue + ); + } + } else { + $e = clone $expected; + $e->setId(null); + $a = clone $actual; + $a->setId(null); + $e->resetUpdatedFields(); + $a->resetUpdatedFields(); + self::assertEquals($e, $a); + } } public function tearDown(): void { diff --git a/tests/unit/Service/Importer/BoardImportServiceTest.php b/tests/unit/Service/Importer/BoardImportServiceTest.php index 9e4229529..c652599b0 100644 --- a/tests/unit/Service/Importer/BoardImportServiceTest.php +++ b/tests/unit/Service/Importer/BoardImportServiceTest.php @@ -118,6 +118,9 @@ class BoardImportServiceTest extends \Test\TestCase { $this->trelloJsonService ->method('getJsonSchemaPath') ->willReturn($configFile); + $this->trelloJsonService + ->method('getBoards') + ->willReturn([$data]); $this->boardImportService->setImportSystem($this->trelloJsonService); $owner = $this->createMock(IUser::class); @@ -192,8 +195,7 @@ class BoardImportServiceTest extends \Test\TestCase { ->expects($this->once()) ->method('insert'); - $actual = $this->boardImportService->import(); - - $this->assertNull($actual); + $this->boardImportService->import(); + self::assertTrue(true); } }