fix: Add output for individual failures or skipped parts
Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
@@ -41,4 +41,17 @@ class Assignment extends RelationalEntity implements JsonSerializable {
|
|||||||
$this->addType('type', 'integer');
|
$this->addType('type', 'integer');
|
||||||
$this->addResolvable('participant');
|
$this->addResolvable('participant');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getTypeString(): string {
|
||||||
|
switch ($this->getType()) {
|
||||||
|
case self::TYPE_USER:
|
||||||
|
return 'user';
|
||||||
|
case self::TYPE_GROUP:
|
||||||
|
return 'group';
|
||||||
|
case self::TYPE_CIRCLE:
|
||||||
|
return 'circle';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ namespace OCA\Deck\Service\Importer;
|
|||||||
|
|
||||||
use OCA\Deck\Exceptions\ConflictException;
|
use OCA\Deck\Exceptions\ConflictException;
|
||||||
use OCA\Deck\NotFoundException;
|
use OCA\Deck\NotFoundException;
|
||||||
use OCA\Deck\Service\Importer\Systems\DeckJsonService;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
@@ -78,11 +77,12 @@ class BoardImportCommandService extends BoardImportService {
|
|||||||
|
|
||||||
protected function validateConfig(): void {
|
protected function validateConfig(): void {
|
||||||
// FIXME: Make config optional for deck plain importer (but use a call on the importer insterad)
|
// FIXME: Make config optional for deck plain importer (but use a call on the importer insterad)
|
||||||
if ($this->getImportSystem() instanceof DeckJsonService) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
$config = $this->getInput()->getOption('config');
|
$config = $this->getInput()->getOption('config');
|
||||||
|
if (!$config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (is_string($config)) {
|
if (is_string($config)) {
|
||||||
if (!is_file($config)) {
|
if (!is_file($config)) {
|
||||||
throw new NotFoundException('It\'s not a valid config file.');
|
throw new NotFoundException('It\'s not a valid config file.');
|
||||||
@@ -191,33 +191,56 @@ class BoardImportCommandService extends BoardImportService {
|
|||||||
public function bootstrap(): void {
|
public function bootstrap(): void {
|
||||||
$this->setSystem($this->getInput()->getOption('system'));
|
$this->setSystem($this->getInput()->getOption('system'));
|
||||||
parent::bootstrap();
|
parent::bootstrap();
|
||||||
|
|
||||||
|
$this->registerErrorCollector(function ($error, $exception) {
|
||||||
|
$message = $error;
|
||||||
|
if ($exception instanceof \Throwable) {
|
||||||
|
$message .= ': ' . $exception->getMessage();
|
||||||
|
}
|
||||||
|
$this->getOutput()->writeln('<error>' . $message . '</error>');
|
||||||
|
if ($exception instanceof \Throwable && $this->getOutput()->isVeryVerbose()) {
|
||||||
|
$this->getOutput()->writeln($exception->getTraceAsString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->registerOutputCollector(function ($info) {
|
||||||
|
if ($this->getOutput()->isVerbose()) {
|
||||||
|
$this->getOutput()->writeln('<info>' . $info . '</info>', );
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function import(): void {
|
public function import(): void {
|
||||||
$this->getOutput()->writeln('Starting import...');
|
$this->getOutput()->writeln('Starting import...');
|
||||||
$this->bootstrap();
|
$this->bootstrap();
|
||||||
|
$this->validateSystem();
|
||||||
|
$this->validateConfig();
|
||||||
$boards = $this->getImportSystem()->getBoards();
|
$boards = $this->getImportSystem()->getBoards();
|
||||||
|
|
||||||
foreach ($boards as $board) {
|
foreach ($boards as $board) {
|
||||||
$this->reset();
|
try {
|
||||||
$this->setData($board);
|
$this->reset();
|
||||||
$this->getOutput()->writeln('Importing board "' . $this->getBoard()->getTitle() . '".');
|
$this->setData($board);
|
||||||
$this->importBoard();
|
$this->getOutput()->writeln('Importing board "' . $board->title . '".');
|
||||||
$this->getOutput()->writeln('Assign users to board...');
|
$this->importBoard();
|
||||||
$this->importAcl();
|
$this->getOutput()->writeln('Assign users to board...');
|
||||||
$this->getOutput()->writeln('Importing labels...');
|
$this->importAcl();
|
||||||
$this->importLabels();
|
$this->getOutput()->writeln('Importing labels...');
|
||||||
$this->getOutput()->writeln('Importing stacks...');
|
$this->importLabels();
|
||||||
$this->importStacks();
|
$this->getOutput()->writeln('Importing stacks...');
|
||||||
$this->getOutput()->writeln('Importing cards...');
|
$this->importStacks();
|
||||||
$this->importCards();
|
$this->getOutput()->writeln('Importing cards...');
|
||||||
$this->getOutput()->writeln('Assign cards to labels...');
|
$this->importCards();
|
||||||
$this->assignCardsToLabels();
|
$this->getOutput()->writeln('Assign cards to labels...');
|
||||||
$this->getOutput()->writeln('Importing comments...');
|
$this->assignCardsToLabels();
|
||||||
$this->importComments();
|
$this->getOutput()->writeln('Importing comments...');
|
||||||
$this->getOutput()->writeln('Importing participants...');
|
$this->importComments();
|
||||||
$this->importCardAssignments();
|
$this->getOutput()->writeln('Importing participants...');
|
||||||
$this->getOutput()->writeln('<info>Finished board import of "' . $this->getBoard()->getTitle() . '"</info>');
|
$this->importCardAssignments();
|
||||||
|
$this->getOutput()->writeln('<info>Finished board import of "' . $this->getBoard()->getTitle() . '"</info>');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->output->writeln('<error>Import failed for board ' . $board->title . ': ' . $e->getMessage() . '</error>');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ use OCP\Comments\NotFoundException as CommentNotFoundException;
|
|||||||
use OCP\EventDispatcher\IEventDispatcher;
|
use OCP\EventDispatcher\IEventDispatcher;
|
||||||
use OCP\IUserManager;
|
use OCP\IUserManager;
|
||||||
use OCP\Server;
|
use OCP\Server;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
class BoardImportService {
|
class BoardImportService {
|
||||||
private string $system = '';
|
private string $system = '';
|
||||||
@@ -70,6 +71,11 @@ class BoardImportService {
|
|||||||
private $data;
|
private $data;
|
||||||
private Board $board;
|
private Board $board;
|
||||||
|
|
||||||
|
/** @var callable[] */
|
||||||
|
private array $errorCollectors = [];
|
||||||
|
/** @var callable[] */
|
||||||
|
private array $outputCollectors = [];
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private IUserManager $userManager,
|
private IUserManager $userManager,
|
||||||
private BoardMapper $boardMapper,
|
private BoardMapper $boardMapper,
|
||||||
@@ -80,7 +86,8 @@ class BoardImportService {
|
|||||||
private AttachmentMapper $attachmentMapper,
|
private AttachmentMapper $attachmentMapper,
|
||||||
private CardMapper $cardMapper,
|
private CardMapper $cardMapper,
|
||||||
private ICommentsManager $commentsManager,
|
private ICommentsManager $commentsManager,
|
||||||
private IEventDispatcher $eventDispatcher
|
private IEventDispatcher $eventDispatcher,
|
||||||
|
private LoggerInterface $logger
|
||||||
) {
|
) {
|
||||||
$this->board = new Board();
|
$this->board = new Board();
|
||||||
$this->disableCommentsEvents();
|
$this->disableCommentsEvents();
|
||||||
@@ -88,6 +95,28 @@ class BoardImportService {
|
|||||||
$this->config = new \stdClass();
|
$this->config = new \stdClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function registerErrorCollector(callable $errorCollector): void {
|
||||||
|
$this->errorCollectors[] = $errorCollector;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerOutputCollector(callable $outputCollector): void {
|
||||||
|
$this->outputCollectors[] = $outputCollector;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addError(string $message, $exception): void {
|
||||||
|
$message .= ' (on board ' . $this->getBoard()->getTitle() . ')';
|
||||||
|
foreach ($this->errorCollectors as $errorCollector) {
|
||||||
|
$errorCollector($message, $exception);
|
||||||
|
}
|
||||||
|
$this->logger->error($message, ['exception' => $exception]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addOutput(string $message): void {
|
||||||
|
foreach ($this->outputCollectors as $outputCollector) {
|
||||||
|
$outputCollector($message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function disableCommentsEvents(): void {
|
private function disableCommentsEvents(): void {
|
||||||
if (defined('PHPUNIT_RUN')) {
|
if (defined('PHPUNIT_RUN')) {
|
||||||
return;
|
return;
|
||||||
@@ -117,6 +146,7 @@ class BoardImportService {
|
|||||||
$this->importComments();
|
$this->importComments();
|
||||||
$this->importCardAssignments();
|
$this->importCardAssignments();
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
|
$this->logger->error('Failed to import board', ['exception' => $th]);
|
||||||
throw new BadRequestException($th->getMessage());
|
throw new BadRequestException($th->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,6 +225,10 @@ class BoardImportService {
|
|||||||
|
|
||||||
public function importBoard(): void {
|
public function importBoard(): void {
|
||||||
$board = $this->getImportSystem()->getBoard();
|
$board = $this->getImportSystem()->getBoard();
|
||||||
|
if (!$this->userManager->userExists($board->getOwner())) {
|
||||||
|
throw new \Exception('Target owner ' . $board->getOwner() . ' not found. Please provide a mapping through the import config.');
|
||||||
|
}
|
||||||
|
|
||||||
if ($board) {
|
if ($board) {
|
||||||
$this->boardMapper->insert($board);
|
$this->boardMapper->insert($board);
|
||||||
$this->board = $board;
|
$this->board = $board;
|
||||||
@@ -211,8 +245,13 @@ class BoardImportService {
|
|||||||
public function importAcl(): void {
|
public function importAcl(): void {
|
||||||
$aclList = $this->getImportSystem()->getAclList();
|
$aclList = $this->getImportSystem()->getAclList();
|
||||||
foreach ($aclList as $code => $acl) {
|
foreach ($aclList as $code => $acl) {
|
||||||
$this->aclMapper->insert($acl);
|
try {
|
||||||
$this->getImportSystem()->updateAcl($code, $acl);
|
$this->aclMapper->insert($acl);
|
||||||
|
$this->getImportSystem()->updateAcl($code, $acl);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->addError('Failed to import acl rule for ' . $acl->getParticipant(), $e);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->getBoard()->setAcl($aclList);
|
$this->getBoard()->setAcl($aclList);
|
||||||
}
|
}
|
||||||
@@ -220,8 +259,12 @@ class BoardImportService {
|
|||||||
public function importLabels(): void {
|
public function importLabels(): void {
|
||||||
$labels = $this->getImportSystem()->getLabels();
|
$labels = $this->getImportSystem()->getLabels();
|
||||||
foreach ($labels as $code => $label) {
|
foreach ($labels as $code => $label) {
|
||||||
$this->labelMapper->insert($label);
|
try {
|
||||||
$this->getImportSystem()->updateLabel($code, $label);
|
$this->labelMapper->insert($label);
|
||||||
|
$this->getImportSystem()->updateLabel($code, $label);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->addError('Failed to import label ' . $label->getTitle(), $e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->getBoard()->setLabels($labels);
|
$this->getBoard()->setLabels($labels);
|
||||||
}
|
}
|
||||||
@@ -229,8 +272,12 @@ class BoardImportService {
|
|||||||
public function importStacks(): void {
|
public function importStacks(): void {
|
||||||
$stacks = $this->getImportSystem()->getStacks();
|
$stacks = $this->getImportSystem()->getStacks();
|
||||||
foreach ($stacks as $code => $stack) {
|
foreach ($stacks as $code => $stack) {
|
||||||
$this->stackMapper->insert($stack);
|
try {
|
||||||
$this->getImportSystem()->updateStack($code, $stack);
|
$this->stackMapper->insert($stack);
|
||||||
|
$this->getImportSystem()->updateStack($code, $stack);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->addError('Failed to import list ' . $stack->getTitle(), $e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->getBoard()->setStacks(array_values($stacks));
|
$this->getBoard()->setStacks(array_values($stacks));
|
||||||
}
|
}
|
||||||
@@ -238,22 +285,26 @@ class BoardImportService {
|
|||||||
public function importCards(): void {
|
public function importCards(): void {
|
||||||
$cards = $this->getImportSystem()->getCards();
|
$cards = $this->getImportSystem()->getCards();
|
||||||
foreach ($cards as $code => $card) {
|
foreach ($cards as $code => $card) {
|
||||||
$createdAt = $card->getCreatedAt();
|
try {
|
||||||
$lastModified = $card->getLastModified();
|
$createdAt = $card->getCreatedAt();
|
||||||
$this->cardMapper->insert($card);
|
$lastModified = $card->getLastModified();
|
||||||
$updateDate = false;
|
$this->cardMapper->insert($card);
|
||||||
if ($createdAt && $createdAt !== $card->getCreatedAt()) {
|
$updateDate = false;
|
||||||
$card->setCreatedAt($createdAt);
|
if ($createdAt && $createdAt !== $card->getCreatedAt()) {
|
||||||
$updateDate = true;
|
$card->setCreatedAt($createdAt);
|
||||||
|
$updateDate = true;
|
||||||
|
}
|
||||||
|
if ($lastModified && $lastModified !== $card->getLastModified()) {
|
||||||
|
$card->setLastModified($lastModified);
|
||||||
|
$updateDate = true;
|
||||||
|
}
|
||||||
|
if ($updateDate) {
|
||||||
|
$this->cardMapper->update($card, false);
|
||||||
|
}
|
||||||
|
$this->getImportSystem()->updateCard($code, $card);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->addError('Failed to import card ' . $card->getTitle(), $e);
|
||||||
}
|
}
|
||||||
if ($lastModified && $lastModified !== $card->getLastModified()) {
|
|
||||||
$card->setLastModified($lastModified);
|
|
||||||
$updateDate = true;
|
|
||||||
}
|
|
||||||
if ($updateDate) {
|
|
||||||
$this->cardMapper->update($card, false);
|
|
||||||
}
|
|
||||||
$this->getImportSystem()->updateCard($code, $card);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,11 +325,15 @@ class BoardImportService {
|
|||||||
$data = $this->getImportSystem()->getCardLabelAssignment();
|
$data = $this->getImportSystem()->getCardLabelAssignment();
|
||||||
foreach ($data as $cardId => $assignemnt) {
|
foreach ($data as $cardId => $assignemnt) {
|
||||||
foreach ($assignemnt as $assignmentId => $labelId) {
|
foreach ($assignemnt as $assignmentId => $labelId) {
|
||||||
$this->assignCardToLabel(
|
try {
|
||||||
$cardId,
|
$this->assignCardToLabel(
|
||||||
$labelId
|
$cardId,
|
||||||
);
|
$labelId
|
||||||
$this->getImportSystem()->updateCardLabelsAssignment($cardId, $assignmentId, $labelId);
|
);
|
||||||
|
$this->getImportSystem()->updateCardLabelsAssignment($cardId, $assignmentId, $labelId);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->addError('Failed to assign label ' . $labelId . ' to ' . $cardId, $e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,9 +375,14 @@ class BoardImportService {
|
|||||||
public function importCardAssignments(): void {
|
public function importCardAssignments(): void {
|
||||||
$allAssignments = $this->getImportSystem()->getCardAssignments();
|
$allAssignments = $this->getImportSystem()->getCardAssignments();
|
||||||
foreach ($allAssignments as $cardId => $assignments) {
|
foreach ($allAssignments as $cardId => $assignments) {
|
||||||
foreach ($assignments as $assignmentId => $assignment) {
|
foreach ($assignments as $assignment) {
|
||||||
$this->assignmentMapper->insert($assignment);
|
try {
|
||||||
$this->getImportSystem()->updateCardAssignment($cardId, $assignmentId, $assignment);
|
$assignment = $this->assignmentMapper->insert($assignment);
|
||||||
|
$this->getImportSystem()->updateCardAssignment($cardId, (string)$assignment->getId(), $assignment);
|
||||||
|
$this->addOutput('Assignment ' . $assignment->getParticipant() . ' added');
|
||||||
|
} catch (NotFoundException $e) {
|
||||||
|
$this->addError('No origin or mapping found for card "' . $cardId . '" and ' . $assignment->getTypeString() .' assignment "' . $assignment->getParticipant(), $e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,6 +193,8 @@ namespace Symfony\Component\Console\Output {
|
|||||||
class OutputInterface {
|
class OutputInterface {
|
||||||
public const VERBOSITY_VERBOSE = 1;
|
public const VERBOSITY_VERBOSE = 1;
|
||||||
public function writeln($text, int $flat = 0) {}
|
public function writeln($text, int $flat = 0) {}
|
||||||
|
public function isVerbose(): bool {}
|
||||||
|
public function isVeryVerbose(): bool {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ use OCP\IDBConnection;
|
|||||||
use OCP\IUser;
|
use OCP\IUser;
|
||||||
use OCP\IUserManager;
|
use OCP\IUserManager;
|
||||||
use PHPUnit\Framework\MockObject\MockObject;
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
class BoardImportServiceTest extends \Test\TestCase {
|
class BoardImportServiceTest extends \Test\TestCase {
|
||||||
/** @var IDBConnection|MockObject */
|
/** @var IDBConnection|MockObject */
|
||||||
@@ -92,7 +93,8 @@ class BoardImportServiceTest extends \Test\TestCase {
|
|||||||
$this->attachmentMapper,
|
$this->attachmentMapper,
|
||||||
$this->cardMapper,
|
$this->cardMapper,
|
||||||
$this->commentsManager,
|
$this->commentsManager,
|
||||||
$this->eventDispatcher
|
$this->eventDispatcher,
|
||||||
|
$this->createMock(LoggerInterface::class),
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->boardImportService->setSystem('trelloJson');
|
$this->boardImportService->setSystem('trelloJson');
|
||||||
@@ -145,6 +147,9 @@ class BoardImportServiceTest extends \Test\TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testImportSuccess() {
|
public function testImportSuccess() {
|
||||||
|
$this->userManager->method('userExists')
|
||||||
|
->willReturn(true);
|
||||||
|
|
||||||
$this->boardMapper
|
$this->boardMapper
|
||||||
->expects($this->once())
|
->expects($this->once())
|
||||||
->method('insert');
|
->method('insert');
|
||||||
|
|||||||
Reference in New Issue
Block a user