Compare commits

...

15 Commits

Author SHA1 Message Date
Julius Härtl
cfee259b38 Bump version to 1.4.1
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-20 11:34:48 +02:00
Julius Härtl
f94cdb3ebb Merge pull request #2994 from nextcloud/backport/2950/stable1.4 2021-04-20 07:44:48 -01:00
Julius Härtl
1ed50fdca6 Fix tests
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-20 10:17:39 +02:00
Julius Härtl
56e460004f Filter out current user when emitting share notifications to groups
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-20 10:17:39 +02:00
Julius Härtl
a95f78d188 Remove notification on unshare/unassign and add type hints
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-20 10:17:38 +02:00
Julius Härtl
df09a9a7b2 Remove app code check
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-20 10:16:53 +02:00
Julius Härtl
990ee2aef9 Merge pull request #3008 from nextcloud/backport/3005/stable1.4
[stable1.4] Do not query the lookupserver when looking for sharees
2021-04-19 10:38:34 -01:00
Julius Härtl
486ecd12db Merge pull request #3006 from nextcloud/backport/3003/stable1.4
[stable1.4] Only import debounce
2021-04-19 09:02:42 -01:00
Julius Härtl
c9cdd7bb11 Do not query the lookupserver when looking for sharees
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-19 10:02:05 +00:00
Julius Härtl
2c753fd084 Only import debounce
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-19 09:22:50 +00:00
Julius Härtl
79d2d2f3f5 Merge pull request #2990 from nextcloud/backport/2989/stable1.4 2021-04-16 13:26:08 -01:00
Julius Härtl
24d9b55bfc Cast column when comparing comment object_id with the card id
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-16 13:54:19 +00:00
Julius Härtl
28cd9fcf77 Add test for unified comments search
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-16 13:54:19 +00:00
Christoph Wurst
d8a36f0602 Merge pull request #2984 from nextcloud/backport/2983/stable1.4
[stable1.4] Fix codemirror description width
2021-04-14 20:39:03 +02:00
Julius Härtl
de06033dcd Fix codemirror description width
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-14 18:08:19 +00:00
17 changed files with 197 additions and 101 deletions

View File

@@ -1,55 +0,0 @@
name: Nextcloud app code check
on:
pull_request:
push:
branches:
- master
- stable*
env:
APP_NAME: deck
jobs:
unit-tests:
runs-on: ubuntu-latest
strategy:
matrix:
php-versions: ['7.4']
server-versions: ['master', 'stable18', 'stable19', 'stable20']
name: AppCode check php${{ matrix.php-versions }}-${{ matrix.server-versions }}
steps:
- name: Checkout server
uses: actions/checkout@v2
with:
repository: nextcloud/server
ref: ${{ matrix.server-versions }}
- name: Checkout submodules
shell: bash
run: |
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
git submodule sync --recursive
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
- name: Checkout app
uses: actions/checkout@v2
with:
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@v1
with:
php-version: ${{ matrix.php-versions }}
tools: phpunit
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite
- name: Checkout app
uses: actions/checkout@v2
with:
path: apps/${{ env.APP_NAME }}
- name: App code check
run: php occ app:check-code ${{ env.APP_NAME }}

View File

@@ -1,6 +1,17 @@
# Changelog
All notable changes to this project will be documented in this file.
## 1.4.1 - 2021-04-20
### Fixed
* [#2984](https://github.com/nextcloud/deck/pull/2984) Fix codemirror description width
* [#2990](https://github.com/nextcloud/deck/pull/2990) Fix unified comments search with postgres
* [#2994](https://github.com/nextcloud/deck/pull/2994) Remove notification on unshare and add type hints
* [#3006](https://github.com/nextcloud/deck/pull/3006) Only import debounce
* [#3008](https://github.com/nextcloud/deck/pull/3008) Do not query the lookupserver when looking for sharees
## 1.4.0 - 2021-04-13
### Added

View File

@@ -17,7 +17,7 @@
- 🚀 Get your project organized
</description>
<version>1.4.0</version>
<version>1.4.1</version>
<licence>agpl</licence>
<author>Julius Härtl</author>
<namespace>Deck</namespace>

View File

@@ -321,7 +321,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
$this->extendQueryByFilter($qb, $query);
$qb->innerJoin('c', 'comments', 'comments', $qb->expr()->andX(
$qb->expr()->eq('comments.object_id', 'c.id', IQueryBuilder::PARAM_STR),
$qb->expr()->eq('comments.object_id', $qb->expr()->castColumn('c.id', IQueryBuilder::PARAM_STR)),
$qb->expr()->eq('comments.object_type', $qb->createNamedParameter(Application::COMMENT_ENTITY_TYPE, IQueryBuilder::PARAM_STR))
));
$qb->selectAlias('comments.id', 'comment_id');
@@ -339,7 +339,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
$tokenMatching
);
$qb->groupBy('comments.id');
$qb->groupBy('comments.id', 'c.id');
$qb->orderBy('comments.id', 'DESC');
if ($limit !== null) {
$qb->setMaxResults($limit);

View File

@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2017 Julius Härtl <jus@bitgrid.net>
*
@@ -24,19 +27,24 @@
namespace OCA\Deck\Notification;
use DateTime;
use Exception;
use OCA\Deck\AppInfo\Application;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\AssignmentMapper;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\Card;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\User;
use OCA\Deck\Service\ConfigService;
use OCA\Deck\Service\PermissionService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\Comments\IComment;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\Notification\IManager;
use OCP\Notification\INotification;
class NotificationHelper {
@@ -80,10 +88,10 @@ class NotificationHelper {
}
/**
* @param $card
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws DoesNotExistException
* @throws Exception thrown on invalid due date
*/
public function sendCardDuedate($card) {
public function sendCardDuedate(Card $card): void {
// check if notification has already been sent
// ideally notifications should not be deleted once seen by the user so we can
// also deliver due date notifications for users who have been added later to a board
@@ -117,7 +125,7 @@ class NotificationHelper {
$notification
->setApp('deck')
->setUser((string)$user->getUID())
->setObject('card', $card->getId())
->setObject('card', (string)$card->getId())
->setSubject('card-overdue', [
$card->getTitle(), $board->getTitle()
])
@@ -128,25 +136,29 @@ class NotificationHelper {
$this->cardMapper->markNotified($card);
}
public function markDuedateAsRead($card) {
public function markDuedateAsRead(Card $card): void {
$notification = $this->notificationManager->createNotification();
$notification
->setApp('deck')
->setObject('card', $card->getId())
->setObject('card', (string)$card->getId())
->setSubject('card-overdue', []);
$this->notificationManager->markProcessed($notification);
}
public function sendCardAssigned($card, $userId) {
public function sendCardAssigned(Card $card, string $userId): void {
$boardId = $this->cardMapper->findBoardId($card->getId());
$board = $this->getBoard($boardId);
try {
$board = $this->getBoard($boardId);
} catch (Exception $e) {
return;
}
$notification = $this->notificationManager->createNotification();
$notification
->setApp('deck')
->setUser((string) $userId)
->setUser($userId)
->setDateTime(new DateTime())
->setObject('card', $card->getId())
->setObject('card', (string)$card->getId())
->setSubject('card-assigned', [
$card->getTitle(),
$board->getTitle(),
@@ -155,29 +167,56 @@ class NotificationHelper {
$this->notificationManager->notify($notification);
}
public function markCardAssignedAsRead(Card $card, string $userId): void {
$notification = $this->notificationManager->createNotification();
$notification
->setApp('deck')
->setUser($userId)
->setObject('card', (string)$card->getId())
->setSubject('card-assigned', []);
$this->notificationManager->markProcessed($notification);
}
/**
* Send notifications that a board was shared with a user/group
*
* @param $boardId
* @param Acl $acl
* @throws \InvalidArgumentException
*/
public function sendBoardShared($boardId, $acl) {
$board = $this->getBoard($boardId);
public function sendBoardShared(int $boardId, Acl $acl, bool $markAsRead = false): void {
try {
$board = $this->getBoard($boardId);
} catch (Exception $e) {
return;
}
if ($acl->getType() === Acl::PERMISSION_TYPE_USER) {
$notification = $this->generateBoardShared($board, $acl->getParticipant());
$this->notificationManager->notify($notification);
if ($markAsRead) {
$this->notificationManager->markProcessed($notification);
} else {
$notification->setDateTime(new DateTime());
$this->notificationManager->notify($notification);
}
}
if ($acl->getType() === Acl::PERMISSION_TYPE_GROUP) {
$group = $this->groupManager->get($acl->getParticipant());
if ($group === null) {
return;
}
foreach ($group->getUsers() as $user) {
if ($user->getUID() === $this->currentUser) {
continue;
}
$notification = $this->generateBoardShared($board, $user->getUID());
$this->notificationManager->notify($notification);
if ($markAsRead) {
$this->notificationManager->markProcessed($notification);
} else {
$notification->setDateTime(new DateTime());
$this->notificationManager->notify($notification);
}
}
}
}
public function sendMention(IComment $comment) {
public function sendMention(IComment $comment): void {
foreach ($comment->getMentions() as $mention) {
$card = $this->cardMapper->find($comment->getObjectId());
$boardId = $this->cardMapper->findBoardId($card->getId());
@@ -194,27 +233,22 @@ class NotificationHelper {
}
/**
* @param $boardId
* @return Board
* @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
*/
private function getBoard($boardId, bool $withLabels = false, bool $withAcl = false) {
private function getBoard(int $boardId, bool $withLabels = false, bool $withAcl = false): Board {
if (!array_key_exists($boardId, $this->boards)) {
$this->boards[$boardId] = $this->boardMapper->find($boardId, $withLabels, $withAcl);
}
return $this->boards[$boardId];
}
/**
* @param Board $board
*/
private function generateBoardShared($board, $userId) {
private function generateBoardShared(Board $board, string $userId): INotification {
$notification = $this->notificationManager->createNotification();
$notification
->setApp('deck')
->setUser((string) $userId)
->setDateTime(new DateTime())
->setObject('board', $board->getId())
->setUser($userId)
->setObject('board', (string)$board->getId())
->setSubject('board-shared', [$board->getTitle(), $this->currentUser]);
return $notification;
}

View File

@@ -74,6 +74,8 @@ class AssignmentService {
* @var IEventDispatcher
*/
private $eventDispatcher;
/** @var string|null */
private $currentUser;
public function __construct(
PermissionService $permissionService,
@@ -138,8 +140,7 @@ class AssignmentService {
}
if ($userId !== $this->currentUser) {
/* Notifyuser about the card assignment */
if ($type === Assignment::TYPE_USER && $userId !== $this->currentUser) {
$this->notificationHelper->sendCardAssigned($card, $userId);
}
@@ -183,8 +184,12 @@ class AssignmentService {
$assignment = $this->assignedUsersMapper->delete($assignment);
$card = $this->cardMapper->find($cardId);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_USER_UNASSIGN, ['assigneduser' => $userId]);
if ($type === Assignment::TYPE_USER && $userId !== $this->currentUser) {
$this->notificationHelper->markCardAssignedAsRead($card, $userId);
}
$this->changeHelper->cardChanged($cardId);
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card));
return $assignment;

View File

@@ -510,11 +510,10 @@ class BoardService {
$acl->setPermissionEdit($edit);
$acl->setPermissionShare($share);
$acl->setPermissionManage($manage);
$this->notificationHelper->sendBoardShared($boardId, $acl);
$newAcl = $this->aclMapper->insert($acl);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE);
$this->notificationHelper->sendBoardShared((int)$boardId, $acl);
$this->boardMapper->mapAcl($newAcl);
$this->changeHelper->boardChanged($boardId);
@@ -599,9 +598,8 @@ class BoardService {
}
}
$this->notificationHelper->sendBoardShared($acl->getBoardId(), $acl);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $acl, ActivityManager::SUBJECT_BOARD_UNSHARE);
$this->notificationHelper->sendBoardShared($acl->getBoardId(), $acl, true);
$this->changeHelper->boardChanged($acl->getBoardId());
$version = \OCP\Util::getVersion()[0];

View File

@@ -243,6 +243,7 @@ class CardService {
$this->cardMapper->update($card);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_DELETE);
$this->notificationHelper->markDuedateAsRead($card);
$this->changeHelper->cardChanged($card->getId(), false);
$this->eventDispatcher->dispatchTyped(new CardDeletedEvent($card));
@@ -322,6 +323,15 @@ class CardService {
$card->setOrder($order);
$card->setOwner($owner);
$card->setDuedate($duedate);
$resetDuedateNotification = false;
if (
$card->getDuedate() === null ||
(new \DateTime($card->getDuedate())) != (new \DateTime($changes->getBefore()->getDuedate()))
) {
$card->setNotified(false);
$resetDuedateNotification = true;
}
if ($deletedAt !== null) {
$card->setDeletedAt($deletedAt);
}
@@ -341,6 +351,9 @@ class CardService {
$card = $this->cardMapper->update($card);
if ($resetDuedateNotification) {
$this->notificationHelper->markDuedateAsRead($card);
}
$this->changeHelper->cardChanged($card->getId(), true);
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card));

View File

@@ -73,7 +73,7 @@ import { CollectionList } from 'nextcloud-vue-collections'
import { mapGetters, mapState } from 'vuex'
import { getCurrentUser } from '@nextcloud/auth'
import { showError } from '@nextcloud/dialogs'
import { debounce } from 'lodash'
import debounce from 'lodash/debounce'
export default {
name: 'SharingTabSidebar',

View File

@@ -306,6 +306,7 @@ h5 {
padding: 0;
background-color: var(--color-main-background);
color: var(--color-main-text);
width: 100%;
}
.CodeMirror-placeholder {

View File

@@ -420,6 +420,7 @@ export default new Vuex.Store({
params.append('format', 'json')
params.append('perPage', 20)
params.append('itemType', [0, 1, 4, 7])
params.append('lookup', false)
const response = await axios.get(generateOcsUrl('apps/files_sharing/api/v1') + 'sharees', { params })
commit('setSharees', response.data.ocs.data)

View File

@@ -8,4 +8,5 @@ default:
baseUrl: http://localhost:8080/
- RequestContext
- BoardContext
- CommentContext
- SearchContext

View File

@@ -27,6 +27,10 @@ class BoardContext implements Context {
$this->serverContext = $environment->getContext('ServerContext');
}
public function getLastUsedCard() {
return $this->card;
}
/**
* @Given /^creates a board named "([^"]*)" with color "([^"]*)"$/
*/

View File

@@ -0,0 +1,31 @@
<?php
use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
require_once __DIR__ . '/../../vendor/autoload.php';
class CommentContext implements Context {
use RequestTrait;
/** @var BoardContext */
protected $boardContext;
/** @BeforeScenario */
public function gatherContexts(BeforeScenarioScope $scope) {
$environment = $scope->getEnvironment();
$this->boardContext = $environment->getContext('BoardContext');
}
/**
* @Given /^post a comment with content "([^"]*)" on the card$/
*/
public function postACommentWithContentOnTheCard($content) {
$card = $this->boardContext->getLastUsedCard();
$this->requestContext->sendOCSRequest('POST', '/apps/deck/api/v1.0/cards/' . $card['id'] . '/comments', [
'message' => $content,
'parentId' => null
]);
}
}

View File

@@ -13,6 +13,7 @@ class SearchContext implements Context {
protected $boardContext;
private $searchResults;
private $unifiedSearchResult;
/** @BeforeScenario */
public function gatherContexts(BeforeScenarioScope $scope) {
@@ -32,6 +33,18 @@ class SearchContext implements Context {
$this->searchResults = json_decode($data, true);
}
/**
* @When /^searching for "([^"]*)" in comments in unified search$/
* @param string $term
* https://cloud.nextcloud.com/ocs/v2.php/search/providers/talk-conversations/search?term=an&from=%2Fapps%2Fdashboard%2F
*/
public function searchingForComments(string $term) {
$this->requestContext->sendOCSRequest('GET', '/search/providers/deck-comment/search?term=' . urlencode($term), []);
$this->requestContext->getResponse()->getBody()->seek(0);
$data = (string)$this->getResponse()->getBody();
$this->unifiedSearchResult = json_decode($data, true);
}
/**
* @When /^searching for '([^']*)'$/
* @param string $term
@@ -78,4 +91,33 @@ class SearchContext implements Context {
public function theCardIsNotFound($arg1) {
Assert::assertFalse($this->cardIsFound($arg1), 'Card can not be found');
}
/**
* @Then /^the comment with "([^"]*)" is found$/
*/
public function theCommentWithIsFound($arg1) {
$ocsData = $this->unifiedSearchResult['ocs']['data']['entries'];
$found = null;
foreach ($ocsData as $result) {
if ($result['subline'] === $arg1) {
$found = $result;
}
}
Assert::assertNotNull($found, 'Comment was expected but was not found');
Assert::assertEquals('admin on Card with comment', $found['title']);
}
/**
* @Then /^the comment with "([^"]*)" is not found$/
*/
public function theCommentWithIsNotFound($arg1) {
$ocsData = $this->unifiedSearchResult['ocs']['data']['entries'];
$found = null;
foreach ($ocsData as $result) {
if ($result['subline'] === $arg1) {
$found = $result;
}
}
Assert::assertNull($found, 'Comment was found but not expected');
}
}

View File

@@ -257,3 +257,10 @@ Feature: Searching for cards
Then the card "Example task 1" is not found
And the card "Labeled card" is not found
And the card "Multi labeled card" is found
Scenario: Search for a card comment
Given create a card named "Card with comment"
And post a comment with content "My first comment" on the card
When searching for "My first comment" in comments in unified search
Then the comment with "My first comment" is found
Then the comment with "Any other" is not found

View File

@@ -130,7 +130,8 @@ class NotificationHelperTest extends \Test\TestCase {
$card = Card::fromParams([
'notified' => false,
'id' => 123,
'title' => 'MyCardTitle'
'title' => 'MyCardTitle',
'duedate' => '2020-12-24'
]);
$this->cardMapper->expects($this->once())
->method('findBoardId')
@@ -225,7 +226,8 @@ class NotificationHelperTest extends \Test\TestCase {
$card = Card::fromParams([
'notified' => false,
'id' => 123,
'title' => 'MyCardTitle'
'title' => 'MyCardTitle',
'duedate' => '2020-12-24'
]);
$card->setAssignedUsers([
new User($users[0])
@@ -323,7 +325,8 @@ class NotificationHelperTest extends \Test\TestCase {
$card = Card::fromParams([
'notified' => false,
'id' => 123,
'title' => 'MyCardTitle'
'title' => 'MyCardTitle',
'duedate' => '2020-12-24'
]);
$card->setAssignedUsers([
new User($users[0])
@@ -470,7 +473,7 @@ class NotificationHelperTest extends \Test\TestCase {
->with(123)
->willReturn($board);
$user = $this->createMock(IUser::class);
$user->expects($this->once())
$user->expects($this->any())
->method('getUID')
->willReturn('userA');
$group = $this->createMock(IGroup::class);