diff --git a/REUSE.toml b/REUSE.toml index 3e377ca06..e41b75d5f 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -12,7 +12,7 @@ SPDX-FileCopyrightText = "none" SPDX-License-Identifier = "CC0-1.0" [[annotations]] -path = ["l10n/**.js", "l10n/**.json", "js/**.js.map", "js/**.js", "js/**.mjs", "js/**.mjs.map", "js/templates/**.handlebars", "lib/Service/Importer/fixtures/config-deckJson-schema.json", "lib/Service/Importer/fixtures/config-trelloApi-schema.json", "lib/Service/Importer/fixtures/config-trelloJson-schema.json", "screenshots/screenshot1.png", "src/assets/file-placeholder.svg", "img/favicon.ico", "img/favicon.png", "img/favicon.svg", "img/activity.svg", "img/activity-dark.svg", "img/deck.svg", "img/deck-current.svg", "img/deck-dark.svg", "img/details-white.svg", "img/card.svg"] +path = ["l10n/**.js", "l10n/**.json", "js/**.js.map", "js/**.js", "js/**.mjs", "js/**.mjs.map", "js/templates/**.handlebars", "lib/Service/Importer/fixtures/config-deckJson-schema.json", "lib/Service/Importer/fixtures/config-trelloApi-schema.json", "lib/Service/Importer/fixtures/config-trelloJson-schema.json", "lib/Service/fixtures/default-board.json", "screenshots/screenshot1.png", "src/assets/file-placeholder.svg", "img/favicon.ico", "img/favicon.png", "img/favicon.svg", "img/activity.svg", "img/activity-dark.svg", "img/deck.svg", "img/deck-current.svg", "img/deck-dark.svg", "img/details-white.svg", "img/card.svg", "img/sample-image.jpg"] precedence = "aggregate" SPDX-FileCopyrightText = "2019 Nextcloud GmbH and Nextcloud contributors" SPDX-License-Identifier = "AGPL-3.0-or-later" diff --git a/cypress/e2e/deckDashboard.js b/cypress/e2e/deckDashboard.js index 996770e50..a5027c50e 100644 --- a/cypress/e2e/deckDashboard.js +++ b/cypress/e2e/deckDashboard.js @@ -22,10 +22,10 @@ describe('Deck dashboard', function() { .should($el => expect($el.text().trim()).to.equal('Upcoming cards')) }) - it('Can see the default "Personal Board" created for user by default', function() { + it('Can see the default "Welcome Board" created for user by default', function() { cy.visit('/apps/deck') - const defaultBoard = 'Personal' + const defaultBoard = 'Welcome to Nextcloud Deck!' cy.get('.app-navigation-entry-wrapper[icon=icon-deck]') .find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + defaultBoard + ')') diff --git a/img/sample-image.jpg b/img/sample-image.jpg new file mode 100644 index 000000000..02065157f Binary files /dev/null and b/img/sample-image.jpg differ diff --git a/lib/Middleware/DefaultBoardMiddleware.php b/lib/Middleware/DefaultBoardMiddleware.php index 4089b8785..d2dce250b 100644 --- a/lib/Middleware/DefaultBoardMiddleware.php +++ b/lib/Middleware/DefaultBoardMiddleware.php @@ -27,7 +27,7 @@ public function __construct( public function beforeController($controller, $methodName) { try { if ($this->userId !== null && $this->defaultBoardService->checkFirstRun($this->userId) && $this->permissionService->canCreate()) { - $this->defaultBoardService->createDefaultBoard($this->l10n->t('Personal'), $this->userId, '0087C5'); + $this->defaultBoardService->createDefaultBoard($this->l10n->t('Welcome to Nextcloud Deck!'), $this->userId, 'bf678b'); } } catch (\Throwable $e) { $this->logger->error('Could not create default board', ['exception' => $e]); diff --git a/lib/Service/DefaultBoardService.php b/lib/Service/DefaultBoardService.php index 528be0939..caeffafd0 100644 --- a/lib/Service/DefaultBoardService.php +++ b/lib/Service/DefaultBoardService.php @@ -9,6 +9,7 @@ use OCA\Deck\AppInfo\Application; use OCA\Deck\BadRequestException; +use OCA\Deck\Db\Board; use OCA\Deck\Db\BoardMapper; use OCP\IConfig; use OCP\IL10N; @@ -21,6 +22,8 @@ class DefaultBoardService { private $cardService; private $config; private $l10n; + private LabelService $labelService; + private AttachmentService $attachmentService; public function __construct( IL10N $l10n, @@ -29,6 +32,8 @@ public function __construct( StackService $stackService, CardService $cardService, IConfig $config, + LabelService $labelService, + AttachmentService $attachmentService, ) { $this->boardService = $boardService; $this->stackService = $stackService; @@ -36,6 +41,8 @@ public function __construct( $this->config = $config; $this->boardMapper = $boardMapper; $this->l10n = $l10n; + $this->labelService = $labelService; + $this->attachmentService = $attachmentService; } /** @@ -59,10 +66,13 @@ public function checkFirstRun($userId): bool { return false; } + private function getDefaultBoardData(): array { + $defaultBoardDataJson = file_get_contents(__DIR__ . '/fixtures/default-board.json'); + return json_decode($defaultBoardDataJson, true); + } + /** - * @param $title * @param $userId - * @param $color * @return \OCP\AppFramework\Db\Entity * @throws \OCA\Deck\NoPermissionException * @throws \OCA\Deck\StatusException @@ -71,19 +81,71 @@ public function checkFirstRun($userId): bool { * @throws BadRequestException */ public function createDefaultBoard(string $title, string $userId, string $color) { - $defaultBoard = $this->boardService->create($title, $userId, $color); - $defaultStacks = []; - $defaultCards = []; + $boardData = $this->getDefaultBoardData(); + /** @var Board $defaultBoard */ + $defaultBoard = $this->boardService->create( + $boardData['title'] ?? $title, + $userId, + $boardData['color'] ?? $color, + ); $boardId = $defaultBoard->getId(); + $additionLabels = []; + $translatedLabelTitles = [ + 'Read more inside' => $this->l10n->t('Read more inside'), + ]; + $translatedStackTitles = [ + 'Custom lists - click to rename!' => $this->l10n->t('Custom lists - click to rename!'), + 'To Do' => $this->l10n->t('To Do'), + 'In Progress' => $this->l10n->t('In Progress'), + 'Done' => $this->l10n->t('Done'), + ]; + $translatedCardTitles = [ + '1. Open to learn more about boards and cards' => $this->l10n->t('1. Open to learn more about boards and cards'), + '2. Drag cards left and right, up and down' => $this->l10n->t('2. Drag cards left and right, up and down'), + '3. Apply rich formatting and link content' => $this->l10n->t('3. Apply rich formatting and link content'), + '4. Share, comment and collaborate!' => $this->l10n->t('4. Share, comment and collaborate!'), + 'Create your first card!' => $this->l10n->t('Create your first card!'), + ]; + + foreach ($boardData['addition_labels'] as $labelData) { + $additionLabels[] = $this->labelService->create( + $translatedLabelTitles[$labelData['title']] ?? $labelData['title'], + $labelData['color'], + $boardId + ); + } + + $defaultLabels = array_merge($defaultBoard->getLabels() ?? [], $additionLabels); - $defaultStacks[] = $this->stackService->create($this->l10n->t('To do'), $boardId, 1); - $defaultStacks[] = $this->stackService->create($this->l10n->t('Doing'), $boardId, 1); - $defaultStacks[] = $this->stackService->create($this->l10n->t('Done'), $boardId, 1); + foreach ($boardData['stacks'] as $stackData) { + $stack = $this->stackService->create( + $translatedStackTitles[$stackData['title']] ?? $stackData['title'], + $boardId, + $stackData['order'] + ); - $defaultCards[] = $this->cardService->create($this->l10n->t('Example Task 3'), $defaultStacks[0]->getId(), 'text', 0, $userId); - $defaultCards[] = $this->cardService->create($this->l10n->t('Example Task 2'), $defaultStacks[1]->getId(), 'text', 0, $userId); - $defaultCards[] = $this->cardService->create($this->l10n->t('Example Task 1'), $defaultStacks[2]->getId(), 'text', 0, $userId); + foreach ($stackData['cards'] as $cardData) { + $card = $this->cardService->create( + $translatedCardTitles[$cardData['title']] ?? $cardData['title'], + $stack->getId(), + $cardData['type'], + $cardData['order'], + $userId, + $cardData['description'], + ); + + foreach ($defaultLabels as $defaultLabel) { + if ($defaultLabel && in_array($defaultLabel->getTitle(), $cardData['labels'])) { + $this->cardService->assignLabel($card->getId(), $defaultLabel->getId()); + } + } + + if (!empty($cardData['has_example_attachment'])) { + $this->attachmentService->create($card->getId(), 'file', 'DEFAULT_SAMPLE_FILE'); + } + } + } return $defaultBoard; } diff --git a/lib/Service/FilesAppService.php b/lib/Service/FilesAppService.php index 79c4b02f7..7b65f8fe6 100644 --- a/lib/Service/FilesAppService.php +++ b/lib/Service/FilesAppService.php @@ -162,7 +162,15 @@ public function display(Attachment $attachment) { } public function create(Attachment $attachment) { - $file = $this->getUploadedFile(); + if ($attachment->getData() === 'DEFAULT_SAMPLE_FILE' && !$this->request->getUploadedFile('file')) { + $file = [ + 'name' => 'Nextcloud sample image - add your image here!.jpg', + 'tmp_name' => __DIR__ . '/../../img/sample-image.jpg', + ]; + } else { + $file = $this->getUploadedFile(); + } + $fileName = $file['name']; // get shares for current card diff --git a/lib/Service/fixtures/default-board.json b/lib/Service/fixtures/default-board.json new file mode 100644 index 000000000..d4bc00d68 --- /dev/null +++ b/lib/Service/fixtures/default-board.json @@ -0,0 +1,81 @@ +{ + "color": "bf678b", + "addition_labels": [ + { + "title": "Read more inside", + "color": "CC317C" + } + ], + "stacks": [ + { + "title": "Custom lists - click to rename!", + "order": 0, + "cards": [ + { + "title": "1. Open to learn more about boards and cards", + "description": "## Welcome to Nextcloud Deck!\n\nNextcloud Deck is a kanban style project management app that integrates seamlessly with the Nextcloud ecosystem. Here, you can create and manage boards to streamline your projects and organize project tasks visually using cards. A versatile tool, Nextcloud Deck will help you stay efficient in both personal and collaborative settings!\n\n### \ud83d\udccc How to set up a new board?\n\nAdd a new board via the \u201c+\u201d button or \u201cAdd board\u201d. Name your board and add a description to clarify its purpose if you wish.\n\nAdd task lists to your board via \u201cAdd List\u201d (e.g., \"To Do\"). Add cards to these lists to represent individual tasks or items. Organize your cards visually by assigning labels or colors.\n\nManage your board list: In the board menu next to the board name, you can edit, clone, archive and export your boards.\n\n### \ud83d\uddc2\ufe0f How to create and edit task cards?\n\nAdd cards via the \u201c+\u201d button on top of the list of your choice. Name your card and add a description using the available rich text formatting instruments.\n\nSet a due date to track deadlines and add responsible team members. Label your cards with tags for better navigation. You can create and manage tags in the sidebar, which is accessible via the toolbar button.\n\n### \ud83d\udca1Useful tips for Nextcloud Deck\n\n- Integrate Nextcloud Deck with Nextcloud Calendar and track the upcoming card deadlines.\n- Use comments to keep discussions around a task within its dedicated card.\n- Enable Nextcloud Deck notifications to always stay updated on changes to your boards.", + "labels": [ + "Read more inside" + ], + "type": "text", + "order": 0 + } + ] + }, + { + "title": "To Do", + "order": 1, + "cards": [ + { + "title": "2. Drag cards left and right, up and down", + "description": "## \ud83d\udea6 Manage your boards and cards\n\nDrag your cards around the board to change position in the list or move them to other lists.\n\nArchive cards to remove them from the board without losing your content. You can always access archived cards and put them back by going to \"Show archived cards\" in the board menu.\n\nUse tags to assign types, statuses and other special attributes to your cards to aid visual navigation. Open the \"Tags\" tab in the sidebar of Nextcloud Deck to manage tags you can add to your cards.\n\n## \ud83d\udc53 Change how you view your boards\n\nTo navigate large boards easily, you can filter your cards by tags, team members, statuses and due dates. Open filter menu via Nextcloud Deck toolbar.\n\nEnable compact display mode in the Deck Board menu via the three dots menu in the toolbar. In the same menu, you can disable and enable cover images.", + "labels": [ + "Read more inside" + ], + "type": "text", + "order": 0 + }, + { + "title": "Create your first card!", + "description": "", + "labels": [ + "Action needed" + ], + "type": "text", + "order": 1 + } + ] + }, + { + "title": "In Progress", + "order": 2, + "cards": [ + { + "title": "3. Apply rich formatting and link content", + "description": "## \ud83c\udfa8 Make use of rich formatting in card descriptions\n\nYou can use various instruments to make your content more structured and informative. Try all the instruments yourself - use this checklist!\n\n- [ ] Apply headings of different hierarchy (H1-H6).\n- [ ] Use **bold** *italic* __underline__ and ~~strikethrough~~ text formatting.\n- [ ] Create unordered, ordered and to-do lists.\n- [ ] Highlight code and blockquotes, add various illustrated callouts.\n- [ ] Create and format tables\n- [ ] Add details\n- [ ] Add links, for example to [the Nextcloud website](https:\/\/nextcloud.com\/)\n- [ ] Insert attachments\n- [ ] Add emojis \ud83d\udd76\ufe0f \n\n## \ud83d\udd17 Attach files to cards\n\nYou can upload any files from your machine or pick from your Nextcloud Files, such as documents, presentations, images, video clips and anything else. Add files via \"Attachments\" tab.\n\n## \ud83c\udf04 Illustrate cards with images\n\nAttach images to your cards - they will automatically work as card covers.", + "labels": [ + "Read more inside" + ], + "type": "text", + "order": 0, + "has_example_attachment": true + } + ] + }, + { + "title": "Done", + "order": 3, + "cards": [ + { + "title": "4. Share, comment and collaborate!", + "description": "## \ud83e\udd1d How to collaborate in your board?\n\nShare your cards and boards with others. In many Nextcloud apps, linked Nextcloud Deck items will appear as rich, interactive widgets. Once you assign someone to a card, they will receive a notification.\n\nEdit cards together, leave notes for your teammates, share task-related files by adding attachments and discuss your tasks using card comments.\n\n### ", + "labels": [ + "Read more inside" + ], + "type": "text", + "order": 0 + } + ] + } + ] +} diff --git a/tests/integration/base-query-count.txt b/tests/integration/base-query-count.txt index 43afd1034..9a4f4313f 100644 --- a/tests/integration/base-query-count.txt +++ b/tests/integration/base-query-count.txt @@ -1 +1 @@ -71780 +80536 diff --git a/tests/unit/Service/DefaultBoardServiceTest.php b/tests/unit/Service/DefaultBoardServiceTest.php index 0bcad8e4d..f5a1ad975 100644 --- a/tests/unit/Service/DefaultBoardServiceTest.php +++ b/tests/unit/Service/DefaultBoardServiceTest.php @@ -46,6 +46,12 @@ class DefaultBoardServiceTest extends TestCase { /** @var CardService */ private $cardService; + /** @var LabelService */ + private $labelService; + + /** @var AttachmentService */ + private $attachmentService; + /** @var BoardMapper */ private $boardMapper; @@ -62,6 +68,8 @@ public function setUp(): void { $this->boardService = $this->createMock(BoardService::class); $this->stackService = $this->createMock(StackService::class); $this->cardService = $this->createMock(CardService::class); + $this->labelService = $this->createMock(LabelService::class); + $this->attachmentService = $this->createMock(AttachmentService::class); $this->config = $this->createMock(IConfig::class); $this->l10n = $this->createMock(IL10N::class); $this->userId = 'admin'; @@ -72,7 +80,9 @@ public function setUp(): void { $this->boardService, $this->stackService, $this->cardService, - $this->config + $this->config, + $this->labelService, + $this->attachmentService, ); } @@ -109,8 +119,8 @@ public function testCheckFirstRunCaseFalse() { } public function testCreateDefaultBoard() { - $title = 'Personal'; - $color = '317CCC'; + $title = 'Welcome to Nextcloud Deck!'; + $color = 'bf678b'; $boardId = 5; $board = new Board(); @@ -128,34 +138,70 @@ public function testCreateDefaultBoard() { return $text; }); + $stackCustomId = '122'; + $stackCustom = $this->assembleTestStack('Custom lists - click to rename!', $stackCustomId, $boardId); + $stackToDoId = '123'; - $stackToDo = $this->assembleTestStack('To do', $stackToDoId, $boardId); + $stackToDo = $this->assembleTestStack('To Do', $stackToDoId, $boardId); $stackDoingId = '124'; - $stackDoing = $this->assembleTestStack('Doing', $stackDoingId, $boardId); + $stackDoing = $this->assembleTestStack('In Progress', $stackDoingId, $boardId); $stackDoneId = '125'; $stackDone = $this->assembleTestStack('Done', $stackDoneId, $boardId); - $this->stackService->expects($this->exactly(3)) + + $this->stackService->expects($this->exactly(4)) ->method('create') ->withConsecutive( - [$this->l10n->t('To do'), $boardId, 1], - [$this->l10n->t('Doing'), $boardId, 1], - [$this->l10n->t('Done'), $boardId, 1] + [$this->l10n->t('Custom lists - click to rename!'), $boardId, 0], + [$this->l10n->t('To Do'), $boardId, 1], + [$this->l10n->t('In Progress'), $boardId, 2], + [$this->l10n->t('Done'), $boardId, 3] ) - ->willReturnOnConsecutiveCalls($stackToDo, $stackDoing, $stackDone); + ->willReturnOnConsecutiveCalls($stackCustom, $stackToDo, $stackDoing, $stackDone); + + $cardExampleTask1 = $this->assembleTestCard( + '1. Open to learn more about boards and cards', + $stackCustomId, + $this->userId + ); + $cardExampleTask2 = $this->assembleTestCard( + '2. Drag cards left and right, up and down', + $stackToDoId, + $this->userId + ); + $cardExampleTask3 = $this->assembleTestCard( + 'Create your first card!', + $stackToDoId, + $this->userId + ); + $cardExampleTask4 = $this->assembleTestCard( + '3. Apply rich formatting and link content', + $stackDoingId, + $this->userId + ); + $cardExampleTask5 = $this->assembleTestCard( + '4. Share, comment and collaborate!', + $stackDoneId, + $this->userId + ); - $cardExampleTask3 = $this->assembleTestCard('Example Task 3', $stackToDoId, $this->userId); - $cardExampleTask2 = $this->assembleTestCard('Example Task 2', $stackDoingId, $this->userId); - $cardExampleTask1 = $this->assembleTestCard('Example Task 1', $stackDoneId, $this->userId); - $this->cardService->expects($this->exactly(3)) + $this->cardService->expects($this->exactly(5)) ->method('create') ->withConsecutive( - ['Example Task 3', $stackToDoId, 'text', 0, $this->userId], - ['Example Task 2', $stackDoingId, 'text', 0, $this->userId], - ['Example Task 1', $stackDoneId, 'text', 0, $this->userId] + ['1. Open to learn more about boards and cards', $stackCustomId, 'text', 0, $this->userId], + ['2. Drag cards left and right, up and down', $stackToDoId, 'text', 0, $this->userId], + ['Create your first card!', $stackToDoId, 'text', 1, $this->userId], + ['3. Apply rich formatting and link content', $stackDoingId, 'text', 0, $this->userId], + ['4. Share, comment and collaborate!', $stackDoneId, 'text', 0, $this->userId] ) - ->willReturnonConsecutiveCalls($cardExampleTask3, $cardExampleTask2, $cardExampleTask1); + ->willReturnonConsecutiveCalls( + $cardExampleTask1, + $cardExampleTask2, + $cardExampleTask3, + $cardExampleTask4, + $cardExampleTask5 + ); $result = $this->service->createDefaultBoard($title, $this->userId, $color);