From bbe72b93d922de0ec380b283d9cd0a6d5941a170 Mon Sep 17 00:00:00 2001 From: Kostiantyn Miakshyn Date: Sun, 7 Sep 2025 16:58:30 +0200 Subject: [PATCH] Feat: Highlight cards with important labels Signed-off-by: Kostiantyn Miakshyn --- docs/API.md | 49 ++++++++++------ lib/Controller/LabelApiController.php | 12 ++-- lib/Controller/LabelController.php | 10 ++-- lib/Db/Label.php | 19 ++++++ .../Version20000Date20250907000000.php | 32 ++++++++++ lib/Service/CardService.php | 2 +- lib/Service/LabelService.php | 10 +++- src/components/board/Board.vue | 1 + src/components/board/BoardSidebar.vue | 1 + src/components/board/TagsTabSidebar.vue | 58 ++++++++++++++++--- src/components/cards/CardItem.vue | 23 +++++++- src/store/main.js | 1 + tests/unit/Db/LabelTest.php | 3 + tests/unit/Service/LabelServiceTest.php | 6 +- 14 files changed, 186 insertions(+), 41 deletions(-) create mode 100644 lib/Migration/Version20000Date20250907000000.php diff --git a/docs/API.md b/docs/API.md index 6fb4ba540..890c9ed73 100644 --- a/docs/API.md +++ b/docs/API.md @@ -212,28 +212,32 @@ Returns an array of board items "color": "31CC7C", "boardId": 10, "cardId": null, - "id": 37 + "id": 37, + "customSettings": {} }, { "title": "To review", "color": "317CCC", "boardId": 10, "cardId": null, - "id": 38 + "id": 38, + "customSettings": {} }, { "title": "Action needed", "color": "FF7A66", "boardId": 10, "cardId": null, - "id": 39 + "id": 39, + "customSettings": { "isImportant": true } }, { "title": "Later", "color": "F1DB50", "boardId": 10, "cardId": null, - "id": 40 + "id": 40, + "customSettings": {} } ], "acl": [], @@ -282,28 +286,32 @@ A 403 response might be returned if the users ability to create new boards has b "color": "31CC7C", "boardId": "10", "cardId": null, - "id": 37 + "id": 37, + "customSettings": {} }, { "title": "To review", "color": "317CCC", "boardId": "10", "cardId": null, - "id": 38 + "id": 38, + "customSettings": {} }, { "title": "Action needed", "color": "FF7A66", "boardId": "10", "cardId": null, - "id": 39 + "id": 39, + "customSettings": {} }, { "title": "Later", "color": "F1DB50", "boardId": "10", "cardId": null, - "id": 40 + "id": 40, + "customSettings": {} } ], "acl": [], @@ -867,7 +875,8 @@ The request can fail with a bad request response for the following reasons: "color": "31CC7C", "boardId": "2", "cardId": null, - "id": 5 + "id": 5, + "customSettings": { "isImportant": false } } ``` @@ -875,16 +884,18 @@ The request can fail with a bad request response for the following reasons: #### Request parameters -| Parameter | Type | Description | -| --------- | ------- | ---------------------------------------- | -| boardId | Integer | The id of the board the label belongs to | +| Parameter | Type | Description | +|----------------|---------|------------------------------------------------------------------------------| +| boardId | Integer | The id of the board the label belongs to | +| customSettings | Object | An key-value structure, currently supported only bool property `isImportant` | #### Request data ```json { "title": "Finished", - "color": "31CC7C" + "color": "31CC7C", + "customSettings": { "isImportant": false } } ``` @@ -896,10 +907,11 @@ The request can fail with a bad request response for the following reasons: #### Request parameters -| Parameter | Type | Description | -| --------- | ------- | ---------------------------------------- | -| boardId | Integer | The id of the board the label belongs to | -| labelId | Integer | The id of the label | +| Parameter | Type | Description | +| --------- | ------- |-----------------------------------------------------------------------------------| +| boardId | Integer | The id of the board the label belongs to | +| labelId | Integer | The id of the label | +| customSettings | Object | An key-value structure, currently supported only bool property `isImportant` | #### Request data @@ -907,7 +919,8 @@ The request can fail with a bad request response for the following reasons: ```json { "title": "Finished", - "color": "31CC7C" + "color": "31CC7C", + "customSettings": { } } ``` diff --git a/lib/Controller/LabelApiController.php b/lib/Controller/LabelApiController.php index f4e4b3457..0c9575ed8 100644 --- a/lib/Controller/LabelApiController.php +++ b/lib/Controller/LabelApiController.php @@ -50,10 +50,12 @@ public function get() { * * @params $title * @params $color + * @param array $customSettings + * * Create a new label */ - public function create($title, $color) { - $label = $this->labelService->create($title, $color, $this->request->getParam('boardId')); + public function create($title, $color, array $customSettings = []) { + $label = $this->labelService->create($title, $color, $this->request->getParam('boardId'), $customSettings); return new DataResponse($label, HTTP::STATUS_OK); } @@ -64,10 +66,12 @@ public function create($title, $color) { * * @params $title * @params $color + * @param array $customSettings + * * Update a specific label */ - public function update($title, $color) { - $label = $this->labelService->update($this->request->getParam('labelId'), $title, $color); + public function update($title, $color, array $customSettings = []) { + $label = $this->labelService->update($this->request->getParam('labelId'), $title, $color, $customSettings); return new DataResponse($label, HTTP::STATUS_OK); } diff --git a/lib/Controller/LabelController.php b/lib/Controller/LabelController.php index 83eae9f4f..41c69e311 100644 --- a/lib/Controller/LabelController.php +++ b/lib/Controller/LabelController.php @@ -25,10 +25,11 @@ public function __construct( * @param $title * @param $color * @param $boardId + * @param array $customSettings * @return \OCP\AppFramework\Db\Entity */ - public function create($title, $color, $boardId) { - return $this->labelService->create($title, $color, $boardId); + public function create($title, $color, $boardId, array $customSettings = []) { + return $this->labelService->create($title, $color, $boardId, $customSettings); } /** @@ -36,10 +37,11 @@ public function create($title, $color, $boardId) { * @param $id * @param $title * @param $color + * @param array $customSettings * @return \OCP\AppFramework\Db\Entity */ - public function update($id, $title, $color) { - return $this->labelService->update($id, $title, $color); + public function update($id, $title, $color, array $customSettings = []) { + return $this->labelService->update($id, $title, $color, $customSettings); } /** diff --git a/lib/Db/Label.php b/lib/Db/Label.php index f5d737dad..47efc925a 100644 --- a/lib/Db/Label.php +++ b/lib/Db/Label.php @@ -9,6 +9,8 @@ /** * @method getTitle(): string + * @method getCustomSettings(): string + * @method setCustomSettings(string $customSettings) */ class Label extends RelationalEntity { protected $title; @@ -16,15 +18,32 @@ class Label extends RelationalEntity { protected $boardId; protected $cardId; protected $lastModified; + protected $customSettings; public function __construct() { $this->addType('id', 'integer'); $this->addType('boardId', 'integer'); $this->addType('cardId', 'integer'); $this->addType('lastModified', 'integer'); + $this->addType('customSettings', 'string'); } public function getETag() { return md5((string)$this->getLastModified()); } + + public function getCustomSettingsArray(): array { + return $this->customSettings ? json_decode($this->customSettings, true) : []; + } + + public function setCustomSettingsArray(array $customSettings): void { + $this->setCustomSettings(json_encode($customSettings ?: new \stdClass())); + } + + public function jsonSerialize(): array { + $data = parent::jsonSerialize(); + $data['customSettings'] = $this->getCustomSettingsArray() ?: new \stdClass(); + + return $data; + } } diff --git a/lib/Migration/Version20000Date20250907000000.php b/lib/Migration/Version20000Date20250907000000.php new file mode 100644 index 000000000..0ce60eb5e --- /dev/null +++ b/lib/Migration/Version20000Date20250907000000.php @@ -0,0 +1,32 @@ +getTable('deck_labels'); + if (!$table->hasColumn('custom_settings')) { + $table->addColumn('custom_settings', Types::JSON, [ + 'notnull' => false, + ]); + } + + return $schema; + } +} diff --git a/lib/Service/CardService.php b/lib/Service/CardService.php index 8fad26447..a0d222ecc 100644 --- a/lib/Service/CardService.php +++ b/lib/Service/CardService.php @@ -340,7 +340,7 @@ public function update($id, $title, $stackId, $type, $owner, $description = '', // clone labels that are assigned to card but don't exist in new board if (empty($filteredLabels)) { if ($this->permissionService->getPermissions($boardId)[Acl::PERMISSION_MANAGE] === true) { - $newLabel = $this->labelService->create($label->getTitle(), $label->getColor(), $board->getId()); + $newLabel = $this->labelService->create($label->getTitle(), $label->getColor(), $board->getId(), $label->getCustomSettingsArray()); $boardLabels[] = $label; $this->assignLabel($card->getId(), $newLabel->getId()); } diff --git a/lib/Service/LabelService.php b/lib/Service/LabelService.php index 69f372c42..3899a51d9 100644 --- a/lib/Service/LabelService.php +++ b/lib/Service/LabelService.php @@ -62,6 +62,7 @@ public function find($labelId) { * @param $title * @param $color * @param $boardId + * @param array $customSettings * @return \OCP\AppFramework\Db\Entity * @throws StatusException * @throws \OCA\Deck\NoPermissionException @@ -69,7 +70,7 @@ public function find($labelId) { * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws BadRequestException */ - public function create($title, $color, $boardId) { + public function create($title, $color, $boardId, array $customSettings = []) { $this->labelServiceValidator->check(compact('title', 'color', 'boardId')); $this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_MANAGE); @@ -89,6 +90,7 @@ public function create($title, $color, $boardId) { $label->setTitle($title); $label->setColor($color); $label->setBoardId($boardId); + $label->setCustomSettingsArray($customSettings); $this->changeHelper->boardChanged($boardId); return $this->labelMapper->insert($label); } @@ -99,7 +101,7 @@ public function cloneLabelIfNotExists(int $labelId, int $targetBoardId): Label { $originLabel = $this->find($labelId); $filteredValues = array_values(array_filter($boardLabels, fn ($item) => $item->getTitle() === $originLabel->getTitle())); if (empty($filteredValues)) { - $label = $this->create($originLabel->getTitle(), $originLabel->getColor(), $targetBoardId); + $label = $this->create($originLabel->getTitle(), $originLabel->getColor(), $targetBoardId, $originLabel->getCustomSettingsArray()); return $label; } return $originLabel; @@ -130,6 +132,7 @@ public function delete($id) { * @param $id * @param $title * @param $color + * @param array $customSettings * @return \OCP\AppFramework\Db\Entity * @throws StatusException * @throws \OCA\Deck\NoPermissionException @@ -137,7 +140,7 @@ public function delete($id) { * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws BadRequestException */ - public function update($id, $title, $color) { + public function update($id, $title, $color, array $customSettings = []) { $this->labelServiceValidator->check(compact('title', 'color', 'id')); $this->permissionService->checkPermission($this->labelMapper, $id, Acl::PERMISSION_MANAGE); @@ -161,6 +164,7 @@ public function update($id, $title, $color) { $label->setTitle($title); $label->setColor($color); + $label->setCustomSettingsArray($customSettings); $this->changeHelper->boardChanged($label->getBoardId()); return $this->labelMapper->update($label); } diff --git a/src/components/board/Board.vue b/src/components/board/Board.vue index 81f097e2d..a018cb1af 100644 --- a/src/components/board/Board.vue +++ b/src/components/board/Board.vue @@ -281,6 +281,7 @@ export default { } .board { + padding-left: $board-gap; position: relative; overflow-x: auto; flex-grow: 1; diff --git a/src/components/board/BoardSidebar.vue b/src/components/board/BoardSidebar.vue index 7afe616d8..bcb91d8d8 100644 --- a/src/components/board/BoardSidebar.vue +++ b/src/components/board/BoardSidebar.vue @@ -7,6 +7,7 @@
- + + {{ t('deck', 'Important') }} + +