Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ability to mark a card as done #4137

Merged
merged 8 commits into from Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion appinfo/info.xml
Expand Up @@ -16,7 +16,7 @@
- 🚀 Get your project organized

</description>
<version>1.11.0-dev.1</version>
<version>1.11.0-dev.2</version>
<licence>agpl</licence>
<author>Julius Härtl</author>
<documentation>
Expand Down
2 changes: 2 additions & 0 deletions appinfo/routes.php
Expand Up @@ -61,6 +61,8 @@
['name' => 'card#reorder', 'url' => '/cards/{cardId}/reorder', 'verb' => 'PUT'],
['name' => 'card#archive', 'url' => '/cards/{cardId}/archive', 'verb' => 'PUT'],
['name' => 'card#unarchive', 'url' => '/cards/{cardId}/unarchive', 'verb' => 'PUT'],
['name' => 'card#done', 'url' => '/cards/{cardId}/done', 'verb' => 'PUT'],
['name' => 'card#undone', 'url' => '/cards/{cardId}/undone', 'verb' => 'PUT'],
['name' => 'card#assignLabel', 'url' => '/cards/{cardId}/label/{labelId}', 'verb' => 'POST'],
['name' => 'card#removeLabel', 'url' => '/cards/{cardId}/label/{labelId}', 'verb' => 'DELETE'],
['name' => 'card#assignUser', 'url' => '/cards/{cardId}/assign', 'verb' => 'POST'],
Expand Down
46 changes: 27 additions & 19 deletions docs/API.md
Expand Up @@ -80,7 +80,7 @@ An ETag header is returned in order to determine if further child elements have
- Fetch a single card of a board `GET /api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}`
- Fetch attachments of a card `GET /api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments`

If a `If-None-Match` header is provided and the requested element has not changed a `304` Not Modified response will be returned.
If a `If-None-Match` header is provided and the requested element has not changed a `304` Not Modified response will be returned.

Changes of child elements will propagate to their parents and also cause an update of the ETag which will be useful for determining if a sync is necessary on any client integration side. As an example, if a label is added to a card, the ETag of all related entities (the card, stack and board) will change.

Expand Down Expand Up @@ -117,6 +117,7 @@ This API version has become available with **Deck 1.3.0**.
- [GET /boards/import/getSystems - Import a board](#get-boardsimportgetsystems-import-a-board)
- [GET /boards/import/config/system/{schema} - Import a board](#get-boardsimportconfigsystemschema-import-a-board)
- [POST /boards/import - Import a board](#post-boardsimport-import-a-board)
- The `done` property was added to cards

# Endpoints

Expand Down Expand Up @@ -587,7 +588,7 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit
#### Response

```json
{
{
"title":"Test",
"description":null,
"stackId":6,
Expand All @@ -601,6 +602,7 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit
"owner":"admin",
"order":999,
"archived":false,
"done":null,
"duedate": "2019-12-24T19:29:30+00:00",
"deletedAt":0,
"commentsUnread":0,
Expand All @@ -623,22 +625,28 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit

#### Request data

| Parameter | Type | Description |
|-------------|-----------|------------------------------------------------------|
| title | String | The title of the card, maximum length is limited to 255 characters |
| description | String | The markdown description of the card |
| type | String | Type of the card (for later use) use 'plain' for now |
| order | Integer | Order for sorting the stacks |
| duedate | timestamp | The ISO-8601 formatted duedate of the card or null |
| Parameter | Type | Description |
|-------------|-----------------|-----------------------------------------------------------------------------------------------------|
| title | String | The title of the card, maximum length is limited to 255 characters |
| description | String | The markdown description of the card |
| type | String | Type of the card (for later use) use 'plain' for now |
| owner | String | The user that owns the card |
| order | Integer | Order for sorting the stacks |
| duedate | timestamp | The ISO-8601 formatted duedate of the card or null |
| archived | bool | Whether the card is archived or not |
| done | timestamp\|null | The ISO-8601 formatted date when the card is marked as done (optional, null indicates undone state) |


```
{
{
"title": "Test card",
"description": "A card description",
"type": "plain",
"owner": "admin",
"order": 999,
"duedate": "2019-12-24T19:29:30+00:00",
"archived": false,
"done": null,
}
```

Expand Down Expand Up @@ -977,7 +985,7 @@ For now only `deck_file` is supported as an attachment type.

### DELETE /boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId} - Delete an attachment


#### Request parameters

| Parameter | Type | Description |
Expand Down Expand Up @@ -1051,23 +1059,23 @@ Make a request to see the json schema of system

# OCS API

The following endpoints are available through the Nextcloud OCS endpoint, which is available at `/ocs/v2.php/apps/deck/api/v1.0/`.
The following endpoints are available through the Nextcloud OCS endpoint, which is available at `/ocs/v2.php/apps/deck/api/v1.0/`.
This has the benefit that both the web UI as well as external integrations can use the same API.

## Config

Deck stores user and app configuration values globally and per board. The GET endpoint allows to fetch the current global configuration while board settings will be exposed through the board element on the regular API endpoints.
Deck stores user and app configuration values globally and per board. The GET endpoint allows to fetch the current global configuration while board settings will be exposed through the board element on the regular API endpoints.

### GET /api/v1.0/config - Fetch app configuration values

#### Response

| Config key | Description |
| --- | --- |
| calendar | Determines if the calendar/tasks integration through the CalDAV backend is enabled for the user (boolean) |
| cardDetailsInModal | Determines if the bigger view is used (boolean) |
| cardIdBadge | Determines if the ID badges are displayed on cards (boolean) |
| groupLimit | Determines if creating new boards is limited to certain groups of the instance. The resulting output is an array of group objects with the id and the displayname (Admin only)|
| calendar | Determines if the calendar/tasks integration through the CalDAV backend is enabled for the user (boolean) |
| cardDetailsInModal | Determines if the bigger view is used (boolean) |
| cardIdBadge | Determines if the ID badges are displayed on cards (boolean) |
| groupLimit | Determines if creating new boards is limited to certain groups of the instance. The resulting output is an array of group objects with the id and the displayname (Admin only)|

```
{
Expand Down Expand Up @@ -1112,7 +1120,7 @@ Deck stores user and app configuration values globally and per board. The GET en
| calendar | Boolean |
| cardDetailsInModal | Boolean |
| cardIdBadge | Boolean |

#### Example request

```
Expand Down Expand Up @@ -1186,7 +1194,7 @@ A list of comments will be provided under the `ocs.data` key. If no or no more c
}
```

In case a comment is marked as a reply to another comment object, the parent comment will be added as `replyTo` entry to the response. Only the next parent node is added, nested replies are not exposed directly.
In case a comment is marked as a reply to another comment object, the parent comment will be added as `replyTo` entry to the response. Only the next parent node is added, nested replies are not exposed directly.

```json
[
Expand Down
31 changes: 19 additions & 12 deletions docs/User_documentation_en.md
Expand Up @@ -12,11 +12,12 @@ Overall, Deck is easy to use. You can create boards, add users, share the Deck,
1. [Create my first board](#1-create-my-first-board)
2. [Create stacks and cards](#2-create-stacks-and-cards)
3. [Handle cards options](#3-handle-cards-options)
4. [Archive old tasks](#4-archive-old-tasks)
5. [Manage your board](#5-manage-your-board)
6. [Import boards](#6-import-boards)
7. [Search](#7-search)
8. [New owner for the deck entities](#8-new-owner-for-the-deck-entities)
4. [Mark task as done](#4-mark-as-done)
5. [Archive old tasks](#5-archive-old-tasks)
6. [Manage your board](#6-manage-your-board)
7. [Import boards](#7-import-boards)
8. [Search](#8-search)
9. [New owner for the deck entities](#9-new-owner-for-the-deck-entities)

### 1. Create my first board
In this example, we're going to create a board and share it with an other nextcloud user.
Expand Down Expand Up @@ -53,12 +54,18 @@ And even :

![Gif for puting infos on tasks 2](resources/gifs/EN_put_infos_2.gif)

### 4. Archive old tasks
Once finished or obsolete, a task could be archived. The tasks is not deleted, it's just archived, and you can retrieve it later
### 4. Mark as done
Once a task has been completed, you can mark it as done. This will prevent it from becoming overdue and hide it from the upcoming cards.
You can mark it as not done at any time.

![Gif for puting infos on tasks 2](resources/gifs/EN_archive.gif)
![Gif for marking a card as done](resources/gifs/EN_done.gif)

### 5. Manage your board
### 5. Archive old tasks
Once obsolete, a task could be archived. The task is not deleted, it's just archived, and you can retrieve it later

![Gif for archiving a task](resources/gifs/EN_archive.gif)

### 6. Manage your board
You can manage the settings of your Deck once you are inside it, by clicking on the small wheel at the top right.
Once in this menu, you have access to several things:

Expand All @@ -72,7 +79,7 @@ The **sharing tab** allows you to add users or even groups to your boards.
**Deleted objects** allows you to return previously deleted stacks or cards.
The **Timeline** allows you to see everything that happened in your boards. Everything!

### 6. Import boards
### 7. Import boards

Importing can be done using the API or the `occ` `deck:import` command.

Expand Down Expand Up @@ -138,7 +145,7 @@ Example configuration file:
}
```

### 7. Search
### 8. Search

Deck provides a global search either through the unified search in the Nextcloud header or with the inline search next to the board controls.
This search allows advanced filtering of cards across all board of the logged in user.
Expand All @@ -161,7 +168,7 @@ Other text tokens will be used to perform a case-insensitive search on the card

In addition, quotes can be used to pass a query with spaces, e.g. `"Exact match with spaces"` or `title:"My card"`.

### 8. New owner for the deck entities
### 9. New owner for the deck entities
You can transfer ownership of boards, cards, etc to a new user, using `occ` command `deck:transfer-ownership`

```bash
Expand Down
Binary file added docs/resources/gifs/EN_done.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions lib/Activity/ActivityManager.php
Expand Up @@ -91,6 +91,8 @@ class ActivityManager {
public const SUBJECT_CARD_UPDATE_DUEDATE = 'card_update_duedate';
public const SUBJECT_CARD_UPDATE_ARCHIVE = 'card_update_archive';
public const SUBJECT_CARD_UPDATE_UNARCHIVE = 'card_update_unarchive';
public const SUBJECT_CARD_UPDATE_DONE = 'card_update_done';
public const SUBJECT_CARD_UPDATE_UNDONE = 'card_update_undone';
public const SUBJECT_CARD_UPDATE_STACKID = 'card_update_stackId';
public const SUBJECT_CARD_USER_ASSIGN = 'card_user_assign';
public const SUBJECT_CARD_USER_UNASSIGN = 'card_user_unassign';
Expand Down Expand Up @@ -198,6 +200,12 @@ public function getActivityFormat($language, $subjectIdentifier, $subjectParams
case self::SUBJECT_CARD_UPDATE_UNARCHIVE:
$subject = $ownActivity ? $l->t('You have unarchived card {card} in list {stack} on board {board}') : $l->t('{user} has unarchived card {card} in list {stack} on board {board}');
break;
case self::SUBJECT_CARD_UPDATE_DONE:
$subject = $ownActivity ? $l->t('You have marked the card {card} as done in list {stack} on board {board}') : $l->t('{user} has marked card {card} as done in list {stack} on board {board}');
break;
case self::SUBJECT_CARD_UPDATE_UNDONE:
$subject = $ownActivity ? $l->t('You have marked the card {card} as undone in list {stack} on board {board}') : $l->t('{user} has marked the card {card} as undone in list {stack} on board {board}');
break;
case self::SUBJECT_CARD_UPDATE_DUEDATE:
if (!isset($subjectParams['after'])) {
$subject = $ownActivity ? $l->t('You have removed the due date of card {card}') : $l->t('{user} has removed the due date of card {card}');
Expand Down Expand Up @@ -357,6 +365,8 @@ private function createEvent($objectType, $entity, $subject, $additionalParams =
case self::SUBJECT_CARD_DELETE:
case self::SUBJECT_CARD_UPDATE_ARCHIVE:
case self::SUBJECT_CARD_UPDATE_UNARCHIVE:
case self::SUBJECT_CARD_UPDATE_DONE:
case self::SUBJECT_CARD_UPDATE_UNDONE:
case self::SUBJECT_CARD_UPDATE_TITLE:
case self::SUBJECT_CARD_UPDATE_DESCRIPTION:
case self::SUBJECT_CARD_UPDATE_DUEDATE:
Expand Down
4 changes: 3 additions & 1 deletion lib/Controller/CardApiController.php
Expand Up @@ -25,6 +25,7 @@

namespace OCA\Deck\Controller;

use OCA\Deck\Model\OptionalNullableValue;
use OCA\Deck\Service\AssignmentService;
use OCA\Deck\Service\CardService;
use OCP\AppFramework\ApiController;
Expand Down Expand Up @@ -105,7 +106,8 @@ public function create($title, $type = 'plain', $order = 999, $description = '',
* Update a card
*/
public function update($title, $type, $owner, $description = '', $order = 0, $duedate = null, $archived = null) {
$card = $this->cardService->update($this->request->getParam('cardId'), $title, $this->request->getParam('stackId'), $type, $owner, $description, $order, $duedate, 0, $archived);
$done = array_key_exists('done', $this->request->getParams()) ? new OptionalNullableValue($this->request->getParam('done', null)) : null;
$card = $this->cardService->update($this->request->getParam('cardId'), $title, $this->request->getParam('stackId'), $type, $owner, $description, $order, $duedate, 0, $archived, $done);
return new DataResponse($card, HTTP::STATUS_OK);
}

Expand Down
18 changes: 18 additions & 0 deletions lib/Controller/CardController.php
Expand Up @@ -143,6 +143,24 @@ public function unarchive($cardId) {
return $this->cardService->unarchive($cardId);
}

/**
* @NoAdminRequired
* @param $cardId
* @return \OCP\AppFramework\Db\Entity
*/
public function done(int $cardId) {
return $this->cardService->done($cardId);
}

/**
* @NoAdminRequired
* @param $cardId
* @return \OCP\AppFramework\Db\Entity
*/
public function undone(int $cardId) {
return $this->cardService->undone($cardId);
}

/**
* @NoAdminRequired
* @param $cardId
Expand Down
30 changes: 20 additions & 10 deletions lib/Db/Card.php
@@ -1,4 +1,7 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
*
Expand Down Expand Up @@ -37,6 +40,8 @@
* @method int getCreatedAt()
* @method bool getArchived()
* @method bool getNotified()
* @method ?DateTime getDone()
* @method void setDone(?DateTime $done)
*
* @method void setLabels(Label[] $labels)
* @method null|Label[] getLabels()
Expand Down Expand Up @@ -83,6 +88,7 @@ class Card extends RelationalEntity {
protected $owner;
protected $order;
protected $archived = false;
protected $done = null;
protected $duedate;
protected $notified = false;
protected $deletedAt = 0;
Expand All @@ -106,6 +112,7 @@ public function __construct() {
$this->addType('lastModified', 'integer');
$this->addType('createdAt', 'integer');
$this->addType('archived', 'boolean');
$this->addType('done', 'datetime');
$this->addType('notified', 'boolean');
$this->addType('deletedAt', 'integer');
$this->addType('duedate', 'datetime');
Expand Down Expand Up @@ -139,19 +146,22 @@ public function getCalendarObject(): VCalendar {
$event->add('RELATED-TO', 'deck-stack-' . $this->getStackId());

// FIXME: For write support: CANCELLED / IN-PROCESS handling
$event->STATUS = $this->getArchived() ? "COMPLETED" : "NEEDS-ACTION";
if ($this->getArchived()) {
if ($this->getDone() || $this->getArchived()) {
$date = new DateTime();
$date->setTimestamp($this->getLastModified());
$event->COMPLETED = $date;
//$event->add('PERCENT-COMPLETE', 100);
}
if (count($this->getLabels()) > 0) {
$event->CATEGORIES = array_map(function ($label) {
return $label->getTitle();
}, $this->getLabels());
$event->STATUS = 'COMPLETED';
$event->COMPLETED = $this->getDone() ? $this->$this->getDone() : $this->getArchived();
} else {
$event->STATUS = 'NEEDS-ACTION';
}

// $event->add('PERCENT-COMPLETE', 100);

$labels = $this->getLabels() ?? [];
$event->CATEGORIES = array_map(function ($label): string {
return $label->getTitle();
}, $labels);

$event->SUMMARY = $this->getTitle();
$event->DESCRIPTION = $this->getDescription();
$calendar->add($event);
Expand All @@ -177,7 +187,7 @@ public function getCalendarPrefix(): string {
return 'card';
}

public function getETag() {
public function getETag(): string {
return md5((string)$this->getLastModified());
}
}
3 changes: 3 additions & 0 deletions lib/Db/CardMapper.php
Expand Up @@ -263,6 +263,7 @@ public function findAllWithDue(array $boardIds) {
->where($qb->expr()->in('s.board_id', $qb->createNamedParameter($boardIds, IQueryBuilder::PARAM_INT_ARRAY)))
->andWhere($qb->expr()->isNotNull('c.duedate'))
->andWhere($qb->expr()->eq('c.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
->andWhere($qb->expr()->isNull('done'))
->andWhere($qb->expr()->eq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('s.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('b.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
Expand All @@ -284,6 +285,7 @@ public function findToMeOrNotAssignedCards(array $boardIds, string $username) {
)
// Filter out archived/deleted cards and board
->andWhere($qb->expr()->eq('c.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
->andWhere($qb->expr()->isNull('done'))
->andWhere($qb->expr()->eq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('s.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('b.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
Expand All @@ -298,6 +300,7 @@ public function findOverdue() {
->where($qb->expr()->lt('duedate', $qb->createFunction('NOW()')))
->andWhere($qb->expr()->eq('notified', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
->andWhere($qb->expr()->isNull('done'))
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
return $this->findEntities($qb);
}
Expand Down