Merge pull request #607 from nextcloud/feature/211/activity
Activity integration
This commit is contained in:
@@ -134,6 +134,7 @@ pipeline:
|
||||
- cd ../server/
|
||||
- ./occ app:enable $APP_NAME
|
||||
- cd apps/$APP_NAME
|
||||
- composer install
|
||||
- phpunit -c tests/phpunit.xml --coverage-clover build/php-unit.coverage.xml
|
||||
- phpunit -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
|
||||
when:
|
||||
@@ -153,6 +154,7 @@ pipeline:
|
||||
- php occ app:enable deck
|
||||
- cd apps/$APP_NAME
|
||||
# Run phpunit tests
|
||||
- composer install
|
||||
- phpunit -c tests/phpunit.xml --coverage-clover build/php-unit.coverage.xml
|
||||
- phpunit -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
|
||||
when:
|
||||
@@ -171,6 +173,7 @@ pipeline:
|
||||
- cd ../server/
|
||||
- php occ app:enable deck
|
||||
- cd apps/$APP_NAME
|
||||
- composer install
|
||||
- phpunit -c tests/phpunit.xml --coverage-clover build/php-unit.coverage.xml
|
||||
- phpunit -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
|
||||
when:
|
||||
@@ -189,6 +192,7 @@ pipeline:
|
||||
- cd ../server/
|
||||
- php occ app:enable deck
|
||||
- cd apps/$APP_NAME
|
||||
- composer install
|
||||
- phpunit -c tests/phpunit.xml --coverage-clover build/php-unit.coverage.xml
|
||||
- phpunit -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
|
||||
when:
|
||||
|
||||
@@ -6,6 +6,7 @@ extends:
|
||||
env:
|
||||
browser: true
|
||||
amd: true
|
||||
es6: true
|
||||
|
||||
globals:
|
||||
global: false
|
||||
|
||||
@@ -24,6 +24,7 @@ before_script:
|
||||
- cd apps/deck
|
||||
|
||||
script:
|
||||
- composer install
|
||||
- make test-unit
|
||||
|
||||
after_success:
|
||||
|
||||
9
Makefile
9
Makefile
@@ -20,12 +20,15 @@ clean-build:
|
||||
clean-dist:
|
||||
rm -rf js/node_modules
|
||||
|
||||
install-deps:
|
||||
install-deps: install-deps-js
|
||||
composer install
|
||||
|
||||
install-deps-js:
|
||||
cd js && npm install
|
||||
|
||||
build: build-js
|
||||
build: install-deps build-js
|
||||
|
||||
build-js: install-deps
|
||||
build-js: install-deps-js
|
||||
cd js && npm run build
|
||||
|
||||
build-js-dev: install-deps
|
||||
|
||||
@@ -21,6 +21,12 @@
|
||||
*
|
||||
*/
|
||||
|
||||
if ((@include_once __DIR__ . '/../vendor/autoload.php')===false) {
|
||||
throw new Exception('Cannot include autoload. Did you run install dependencies using composer?');
|
||||
}
|
||||
|
||||
$app = new \OCA\Deck\AppInfo\Application();
|
||||
$app->registerNavigationEntry();
|
||||
$app->registerNotifications();
|
||||
$app->registerNotifications();
|
||||
|
||||
\OC_Util::addStyle('deck', 'activity');
|
||||
|
||||
@@ -5,20 +5,20 @@
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\AppInfo;
|
||||
@@ -28,4 +28,4 @@ use OCP\AppFramework\App;
|
||||
/**
|
||||
* Additional autoloader registration, e.g. registering composer autoloaders
|
||||
*/
|
||||
// require_once __DIR__ . '/../vendor/autoload.php';
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
- 🚀 Get your project organized
|
||||
|
||||
</description>
|
||||
<version>0.5.0-dev2</version>
|
||||
<version>0.5.0-dev3</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Julius Härtl</author>
|
||||
<namespace>Deck</namespace>
|
||||
@@ -40,4 +40,15 @@
|
||||
<commands>
|
||||
<command>OCA\Deck\Command\UserExport</command>
|
||||
</commands>
|
||||
<activity>
|
||||
<settings>
|
||||
<setting>OCA\Deck\Activity\Setting</setting>
|
||||
</settings>
|
||||
<filters>
|
||||
<filter>OCA\Deck\Activity\Filter</filter>
|
||||
</filters>
|
||||
<providers>
|
||||
<provider>OCA\Deck\Activity\DeckProvider</provider>
|
||||
</providers>
|
||||
</activity>
|
||||
</info>
|
||||
|
||||
@@ -8,8 +8,11 @@
|
||||
"email": "jus@bitgrid.net"
|
||||
}
|
||||
],
|
||||
"require": {},
|
||||
"require": {
|
||||
"cogpowered/finediff": "0.3.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"roave/security-advisories": "dev-master",
|
||||
"christophwurst/nextcloud": "^13.0",
|
||||
"jakub-onderka/php-parallel-lint": "^1.0.0"
|
||||
}
|
||||
|
||||
23
css/activity.css
Normal file
23
css/activity.css
Normal file
@@ -0,0 +1,23 @@
|
||||
.activitymessage .visualdiff ins {
|
||||
background-color: rgba(70, 186, 97, 0.2);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.activitymessage .visualdiff del {
|
||||
background-color: rgba(233, 50, 45, 0.2);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.activityTabView .avatardiv-container {
|
||||
display: inline-block;
|
||||
bottom: -3px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.activityTabView .avatar-name-wrapper {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.activityTabView .activitysubject a {
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -38,7 +38,7 @@
|
||||
}
|
||||
|
||||
.icon-badge {
|
||||
background-image: url('../../../core/img/places/calendar-dark.svg');
|
||||
background-image: url('../img/calendar-dark.svg');
|
||||
}
|
||||
|
||||
.icon-toggle-compact-collapsed {
|
||||
|
||||
@@ -1258,7 +1258,6 @@ input.input-inline {
|
||||
|
||||
.tab {
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1
img/calendar-dark.svg
Normal file
1
img/calendar-dark.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" version="1.1" height="32" viewbox="0 0 32 32"><path fill="#000" d="m8 2c-1.108 0-2 0.892-2 2v4c0 1.108 0.892 2 2 2s2-0.892 2-2v-4c0-1.108-0.892-2-2-2zm16 0c-1.108 0-2 0.892-2 2v4c0 1.108 0.892 2 2 2s2-0.892 2-2v-4c0-1.108-0.892-2-2-2zm-13 4v2c0 1.662-1.338 3-3 3s-3-1.338-3-3v-1.875a3.993 3.993 0 0 0 -3 3.875v16c0 2.216 1.784 4 4 4h20c2.216 0 4-1.784 4-4v-16a3.993 3.993 0 0 0 -3 -3.875v1.875c0 1.662-1.338 3-3 3s-3-1.338-3-3v-2zm-4.906 10h19.812a0.09 0.09 0 0 1 0.094 0.094v9.812a0.09 0.09 0 0 1 -0.094 0.094h-19.812a0.09 0.09 0 0 1 -0.094 -0.094v-9.812a0.09 0.09 0 0 1 0.094 -0.094z"/></svg>
|
||||
|
After Width: | Height: | Size: 646 B |
@@ -4,20 +4,20 @@
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/* global angular */
|
||||
@@ -48,13 +48,15 @@ import ngsortable from 'ng-sortable';
|
||||
import md from 'angular-markdown-it';
|
||||
import nganimate from 'angular-animate';
|
||||
import 'angular-file-upload';
|
||||
import ngInfiniteScroll from 'ng-infinite-scroll';
|
||||
|
||||
var app = angular.module('Deck', [
|
||||
ngsanitize,
|
||||
uirouter,
|
||||
angularuiselect,
|
||||
ngsortable, md, nganimate,
|
||||
'angularFileUpload'
|
||||
'angularFileUpload',
|
||||
ngInfiniteScroll
|
||||
]);
|
||||
|
||||
export default app;
|
||||
|
||||
99
js/controller/ActivityController.js
Normal file
99
js/controller/ActivityController.js
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* global OC OCA */
|
||||
|
||||
class ActivityController {
|
||||
constructor ($scope, CardService, ActivityService) {
|
||||
'ngInject';
|
||||
this.cardservice = CardService;
|
||||
this.activityservice = ActivityService;
|
||||
this.$scope = $scope;
|
||||
this.type = '';
|
||||
this.loading = false;
|
||||
|
||||
const self = this;
|
||||
this.$scope.$watch(function () {
|
||||
return self.element.id;
|
||||
}, function (params) {
|
||||
if (self.getData(self.element.id).length === 0) {
|
||||
self.loading = true;
|
||||
self.fetchUntilResults();
|
||||
}
|
||||
self.activityservice.fetchNewerActivities(self.type, self.element.id).then(function () {});
|
||||
}, true);
|
||||
}
|
||||
|
||||
getData(id) {
|
||||
return this.activityservice.getData(this.type, id);
|
||||
}
|
||||
|
||||
parseMessage(subject, parameters) {
|
||||
OCA.Activity.RichObjectStringParser._userLocalTemplate = '<span class="avatar-name-wrapper"><avatar ng-attr-contactsmenu ng-attr-tooltip ng-attr-user="{{ id }}" ng-attr-displayname="{{name}}" ng-attr-size="16"></avatar> {{ name }}</span>';
|
||||
return OCA.Activity.RichObjectStringParser.parseMessage(subject, parameters);
|
||||
}
|
||||
|
||||
fetchUntilResults () {
|
||||
const self = this;
|
||||
let dataLengthBefore = self.getData(self.element.id).length;
|
||||
let _executeFetch = function() {
|
||||
let promise = self.activityservice.fetchMoreActivities(self.type, self.element.id);
|
||||
promise.then(function (data) {
|
||||
let dataLengthAfter = self.getData(self.element.id).length;
|
||||
if (data !== null && (dataLengthAfter <= dataLengthBefore || dataLengthAfter < 5)) {
|
||||
_executeFetch();
|
||||
} else {
|
||||
self.loading = false;
|
||||
}
|
||||
}, function () {
|
||||
self.loading = false;
|
||||
self.$scope.$apply();
|
||||
});
|
||||
|
||||
};
|
||||
_executeFetch();
|
||||
}
|
||||
|
||||
page() {
|
||||
if (!this.activityservice.since[this.type][this.element.id].finished) {
|
||||
this.loading = true;
|
||||
this.fetchUntilResults();
|
||||
} else {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
loadingNewer() {
|
||||
return this.activityservice.runningNewer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let activityComponent = {
|
||||
templateUrl: OC.linkTo('deck', 'templates/part.card.activity.html'),
|
||||
controller: ActivityController,
|
||||
bindings: {
|
||||
type: '@',
|
||||
element: '='
|
||||
}
|
||||
};
|
||||
export default activityComponent;
|
||||
@@ -434,6 +434,14 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
|
||||
};
|
||||
};
|
||||
|
||||
$scope.colorValue = function(color) {
|
||||
const re = /^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/;
|
||||
if (re.test(color)) {
|
||||
return color;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
$scope.attachmentCount = function(card) {
|
||||
if (Array.isArray(card.attachments)) {
|
||||
return card.attachments.filter((obj) => obj.deletedAt === 0).length;
|
||||
|
||||
@@ -4,20 +4,20 @@
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
|
||||
@@ -32,6 +32,10 @@ app.directive('avatar', function() {
|
||||
link: function(scope, element, attr){
|
||||
scope.uid = attr.displayname;
|
||||
scope.displayname = attr.displayname;
|
||||
scope.size = attr.size;
|
||||
if (typeof scope.size === 'undefined') {
|
||||
scope.size = 32;
|
||||
}
|
||||
var value = attr.user;
|
||||
var avatardiv = $(element).find('.avatardiv');
|
||||
if(typeof attr.contactsmenu !== 'undefined' && attr.contactsmenu !== 'false') {
|
||||
@@ -44,8 +48,8 @@ app.directive('avatar', function() {
|
||||
placement: 'top'
|
||||
});
|
||||
}
|
||||
avatardiv.avatar(value, 32, false, false, false, attr.displayname);
|
||||
avatardiv.avatar(value, scope.size, false, false, false, attr.displayname);
|
||||
},
|
||||
controller: function () {}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
38
js/directive/bindHtmlCompile.js
Normal file
38
js/directive/bindHtmlCompile.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
|
||||
app.directive('bindHtmlCompile', function ($compile) {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function (scope, element, attrs) {
|
||||
scope.$watch(function () {
|
||||
return scope.$eval(attrs.bindHtmlCompile);
|
||||
}, function (value) {
|
||||
element.html(value);
|
||||
$compile(element.contents())(scope);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -14,9 +14,11 @@ import './app/Run.js';
|
||||
|
||||
import ListController from 'controller/ListController.js';
|
||||
import attachmentListComponent from './controller/AttachmentController.js';
|
||||
import activityComponent from './controller/ActivityController.js';
|
||||
|
||||
app.controller('ListController', ListController);
|
||||
app.component('attachmentListComponent', attachmentListComponent);
|
||||
app.component('activityComponent', activityComponent);
|
||||
|
||||
|
||||
// require all the js files from subdirectories
|
||||
|
||||
46
js/package-lock.json
generated
46
js/package-lock.json
generated
@@ -2970,8 +2970,7 @@
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
@@ -2992,14 +2991,12 @@
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -3014,20 +3011,17 @@
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
@@ -3144,8 +3138,7 @@
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
@@ -3157,7 +3150,6 @@
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
@@ -3172,7 +3164,6 @@
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
@@ -3180,14 +3171,12 @@
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.2.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.1",
|
||||
"yallist": "^3.0.0"
|
||||
@@ -3206,7 +3195,6 @@
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
@@ -3287,8 +3275,7 @@
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
@@ -3300,7 +3287,6 @@
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -3386,8 +3372,7 @@
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
@@ -3423,7 +3408,6 @@
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
@@ -3443,7 +3427,6 @@
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
@@ -3487,14 +3470,12 @@
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4917,6 +4898,11 @@
|
||||
"integrity": "sha512-vdqTKI9GBIYcAEbFAcpKPErKINfPF5zIuz3/niBfq8WUZjpT2tytLlFVrBgWdOtqI4uaA/Rb6No0hux39XXDuw==",
|
||||
"dev": true
|
||||
},
|
||||
"ng-infinite-scroll": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ng-infinite-scroll/-/ng-infinite-scroll-1.3.0.tgz",
|
||||
"integrity": "sha1-wumNj9E0sFJaTSz1jJXZtYN1URI="
|
||||
},
|
||||
"ng-sortable": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ng-sortable/-/ng-sortable-1.3.8.tgz",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"markdown-it": "^8.4.2",
|
||||
"markdown-it-link-target": "^1.0.2",
|
||||
"ng-infinite-scroll": "^1.3.0",
|
||||
"ng-sortable": "^1.3.8",
|
||||
"ui-select": "^0.19.8"
|
||||
},
|
||||
|
||||
187
js/service/ActivityService.js
Normal file
187
js/service/ActivityService.js
Normal file
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import app from '../app/App.js';
|
||||
|
||||
const DECK_ACTIVITY_TYPE_BOARD = 'deck_board';
|
||||
const DECK_ACTIVITY_TYPE_CARD = 'deck_card';
|
||||
|
||||
/* global OC oc_requesttoken */
|
||||
class ActivityService {
|
||||
|
||||
constructor ($rootScope, $filter, $http, $q) {
|
||||
this.running = false;
|
||||
this.runningNewer = false;
|
||||
this.$filter = $filter;
|
||||
this.$http = $http;
|
||||
this.$q = $q;
|
||||
this.data = {};
|
||||
this.data[DECK_ACTIVITY_TYPE_BOARD] = {};
|
||||
this.data[DECK_ACTIVITY_TYPE_CARD] = {};
|
||||
this.since = {
|
||||
deck_card: {
|
||||
|
||||
},
|
||||
deck_board: {
|
||||
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static getUrl(type, id, since) {
|
||||
if (type === DECK_ACTIVITY_TYPE_CARD) {
|
||||
return OC.linkToOCS('apps/activity/api/v2/activity', 2) + 'filter?format=json&object_type=deck_card&object_id=' + id + '&limit=50&since=' + since;
|
||||
}
|
||||
if (type === DECK_ACTIVITY_TYPE_BOARD) {
|
||||
return OC.linkToOCS('apps/activity/api/v2/activity', 2) + 'deck?format=json&limit=50&since=' + since;
|
||||
}
|
||||
}
|
||||
|
||||
fetchCardActivities(type, id, since) {
|
||||
this.running = true;
|
||||
|
||||
this.checkData(type, id);
|
||||
const self = this;
|
||||
return this.$http.get(ActivityService.getUrl(type, id, since)).then(function (response) {
|
||||
const objects = response.data.ocs.data;
|
||||
|
||||
for (let index in objects) {
|
||||
if (objects.hasOwnProperty(index)) {
|
||||
let item = objects[index];
|
||||
self.addItem(type, id, item);
|
||||
if (item.activity_id > self.since[type][id].latest) {
|
||||
self.since[type][id].latest = item.activity_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.data[type][id].sort(function(a, b) {
|
||||
return b.activity_id - a.activity_id;
|
||||
});
|
||||
self.since[type][id].oldest = response.headers('X-Activity-Last-Given');
|
||||
self.running = false;
|
||||
return response;
|
||||
}, function (error) {
|
||||
if (error.status === 304) {
|
||||
self.since[type][id].finished = true;
|
||||
}
|
||||
self.running = false;
|
||||
});
|
||||
}
|
||||
fetchMoreActivities(type, id) {
|
||||
this.checkData(type, id);
|
||||
if (this.running === true) {
|
||||
return this.runningPromise;
|
||||
}
|
||||
if (!this.since[type][id].finished) {
|
||||
this.runningPromise = this.fetchCardActivities(type, id, this.since[type][id].oldest);
|
||||
return this.runningPromise;
|
||||
}
|
||||
return Promise.reject();
|
||||
}
|
||||
checkData(type, id) {
|
||||
if (!Array.isArray(this.data[type][id])) {
|
||||
this.data[type][id] = [];
|
||||
}
|
||||
if (typeof this.since[type][id] === 'undefined') {
|
||||
this.since[type][id] = {
|
||||
latest: 0,
|
||||
oldestCatchedUp: false,
|
||||
oldest: '0',
|
||||
finished: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
addItem(type, id, item) {
|
||||
const existingEntry = this.data[type][id].findIndex((entry) => { return entry.activity_id === item.activity_id; });
|
||||
if (existingEntry !== -1) {
|
||||
return;
|
||||
}
|
||||
/** check if the fetched item from all deck activities is actually related */
|
||||
const isUnrelatedBoard = (item.object_type === DECK_ACTIVITY_TYPE_BOARD && item.object_id !== id);
|
||||
const isUnrelatedCard = (item.object_type === DECK_ACTIVITY_TYPE_CARD && item.subject_rich[1].board && item.subject_rich[1].board.id !== id);
|
||||
if (type === DECK_ACTIVITY_TYPE_BOARD && (isUnrelatedBoard || isUnrelatedCard)) {
|
||||
return;
|
||||
}
|
||||
item.timestamp = new Date(item.datetime).getTime();
|
||||
this.data[type][id].push(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch newer activities starting from the latest ones that are in cache
|
||||
*
|
||||
* @param type
|
||||
* @param id
|
||||
*/
|
||||
fetchNewerActivities(type, id) {
|
||||
if (this.since[type][id].latest === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
let self = this;
|
||||
return this.fetchNewer(type, id).then(function() {
|
||||
return self.fetchNewerActivities(type, id);
|
||||
});
|
||||
}
|
||||
|
||||
fetchNewer(type, id) {
|
||||
const deferred = this.$q.defer();
|
||||
this.running = true;
|
||||
this.runningNewer = true;
|
||||
const self = this;
|
||||
this.$http.get(ActivityService.getUrl(type, id, this.since[type][id].latest) + '&sort=asc').then(function (response) {
|
||||
let objects = response.data.ocs.data;
|
||||
|
||||
let data = [];
|
||||
for (let index in objects) {
|
||||
if (objects.hasOwnProperty(index)) {
|
||||
let item = objects[index];
|
||||
self.addItem(type, id, item);
|
||||
}
|
||||
}
|
||||
self.data[type][id].sort(function(a, b) {
|
||||
return b.activity_id - a.activity_id;
|
||||
});
|
||||
self.since[type][id].latest = response.headers('X-Activity-Last-Given');
|
||||
self.data[type][id] = data.concat(self.data[type][id]);
|
||||
self.running = false;
|
||||
self.runningNewer = false;
|
||||
deferred.resolve(objects);
|
||||
}, function (error) {
|
||||
self.runningNewer = false;
|
||||
self.running = false;
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
getData(type, id) {
|
||||
if (!Array.isArray(this.data[type][id])) {
|
||||
return [];
|
||||
}
|
||||
return this.data[type][id];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
app.service('ActivityService', ActivityService);
|
||||
|
||||
export default ActivityService;
|
||||
export {DECK_ACTIVITY_TYPE_BOARD, DECK_ACTIVITY_TYPE_CARD};
|
||||
481
lib/Activity/ActivityManager.php
Normal file
481
lib/Activity/ActivityManager.php
Normal file
@@ -0,0 +1,481 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Activity;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\AclMapper;
|
||||
use OCA\Deck\Db\AssignedUsers;
|
||||
use OCA\Deck\Db\Attachment;
|
||||
use OCA\Deck\Db\AttachmentMapper;
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\Label;
|
||||
use OCA\Deck\Db\Stack;
|
||||
use OCA\Deck\Db\StackMapper;
|
||||
use OCA\Deck\Service\PermissionService;
|
||||
use OCP\Activity\IEvent;
|
||||
use OCP\Activity\IManager;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\IL10N;
|
||||
use OCP\IUser;
|
||||
|
||||
class ActivityManager {
|
||||
|
||||
private $manager;
|
||||
private $userId;
|
||||
private $permissionService;
|
||||
private $boardMapper;
|
||||
private $cardMapper;
|
||||
private $attachmentMapper;
|
||||
private $aclMapper;
|
||||
private $stackMapper;
|
||||
private $l10n;
|
||||
|
||||
const DECK_OBJECT_BOARD = 'deck_board';
|
||||
const DECK_OBJECT_CARD = 'deck_card';
|
||||
|
||||
const SUBJECT_BOARD_CREATE = 'board_create';
|
||||
const SUBJECT_BOARD_UPDATE = 'board_update';
|
||||
const SUBJECT_BOARD_UPDATE_TITLE = 'board_update_title';
|
||||
const SUBJECT_BOARD_UPDATE_ARCHIVED = 'board_update_archived';
|
||||
const SUBJECT_BOARD_DELETE = 'board_delete';
|
||||
const SUBJECT_BOARD_RESTORE = 'board_restore';
|
||||
const SUBJECT_BOARD_SHARE = 'board_share';
|
||||
const SUBJECT_BOARD_UNSHARE = 'board_unshare';
|
||||
|
||||
const SUBJECT_STACK_CREATE = 'stack_create';
|
||||
const SUBJECT_STACK_UPDATE = 'stack_update';
|
||||
const SUBJECT_STACK_UPDATE_TITLE = 'stack_update_title';
|
||||
const SUBJECT_STACK_UPDATE_ORDER = 'stack_update_order';
|
||||
const SUBJECT_STACK_DELETE = 'stack_delete';
|
||||
|
||||
const SUBJECT_CARD_CREATE = 'card_create';
|
||||
const SUBJECT_CARD_DELETE = 'card_delete';
|
||||
const SUBJECT_CARD_RESTORE = 'card_restore';
|
||||
const SUBJECT_CARD_UPDATE = 'card_update';
|
||||
const SUBJECT_CARD_UPDATE_TITLE = 'card_update_title';
|
||||
const SUBJECT_CARD_UPDATE_DESCRIPTION = 'card_update_description';
|
||||
const SUBJECT_CARD_UPDATE_DUEDATE = 'card_update_duedate';
|
||||
const SUBJECT_CARD_UPDATE_ARCHIVE = 'card_update_archive';
|
||||
const SUBJECT_CARD_UPDATE_UNARCHIVE = 'card_update_unarchive';
|
||||
const SUBJECT_CARD_UPDATE_STACKID = 'card_update_stackId';
|
||||
const SUBJECT_CARD_USER_ASSIGN = 'card_user_assign';
|
||||
const SUBJECT_CARD_USER_UNASSIGN = 'card_user_unassign';
|
||||
|
||||
const SUBJECT_ATTACHMENT_CREATE = 'attachment_create';
|
||||
const SUBJECT_ATTACHMENT_UPDATE = 'attachment_update';
|
||||
const SUBJECT_ATTACHMENT_DELETE = 'attachment_delete';
|
||||
const SUBJECT_ATTACHMENT_RESTORE = 'attachment_restore';
|
||||
|
||||
const SUBJECT_LABEL_CREATE = 'label_create';
|
||||
const SUBJECT_LABEL_UPDATE = 'label_update';
|
||||
const SUBJECT_LABEL_DELETE = 'label_delete';
|
||||
const SUBJECT_LABEL_ASSIGN = 'label_assign';
|
||||
const SUBJECT_LABEL_UNASSING = 'label_unassign';
|
||||
|
||||
public function __construct(
|
||||
IManager $manager,
|
||||
PermissionService $permissionsService,
|
||||
BoardMapper $boardMapper,
|
||||
CardMapper $cardMapper,
|
||||
StackMapper $stackMapper,
|
||||
AttachmentMapper $attachmentMapper,
|
||||
AclMapper $aclMapper,
|
||||
IL10N $l10n,
|
||||
$userId
|
||||
) {
|
||||
$this->manager = $manager;
|
||||
$this->permissionService = $permissionsService;
|
||||
$this->boardMapper = $boardMapper;
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->stackMapper = $stackMapper;
|
||||
$this->attachmentMapper = $attachmentMapper;
|
||||
$this->aclMapper = $aclMapper;
|
||||
$this->l10n = $l10n;
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $subjectIdentifier
|
||||
* @param array $subjectParams
|
||||
* @param bool $ownActivity
|
||||
* @return string
|
||||
*/
|
||||
public function getActivityFormat($subjectIdentifier, $subjectParams = [], $ownActivity = false) {
|
||||
$subject = '';
|
||||
switch ($subjectIdentifier) {
|
||||
case self::SUBJECT_BOARD_CREATE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have created a new board {board}'): $this->l10n->t('{user} has created a new board {board}');
|
||||
break;
|
||||
case self::SUBJECT_BOARD_DELETE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have deleted the board {board}') : $this->l10n->t('{user} has deleted the board {board}');
|
||||
break;
|
||||
case self::SUBJECT_BOARD_RESTORE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have restored the board {board}') : $this->l10n->t('{user} has restored the board {board}');
|
||||
break;
|
||||
case self::SUBJECT_BOARD_SHARE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have shared the board {board} with {acl}') : $this->l10n->t('{user} has shared the board {board} with {sharee}');
|
||||
break;
|
||||
case self::SUBJECT_BOARD_UNSHARE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have removed {acl} from the board {board}') : $this->l10n->t('{user} has removed {acl} from the board {board}');
|
||||
break;
|
||||
|
||||
case self::SUBJECT_BOARD_UPDATE_TITLE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have renamed the board {before} to {board}') : $this->l10n->t('{user} has has renamed the board {before} to {board}');
|
||||
break;
|
||||
case self::SUBJECT_BOARD_UPDATE_ARCHIVED:
|
||||
if (isset($subjectParams['after']) && $subjectParams['after']) {
|
||||
$subject = $ownActivity ? $this->l10n->t('You have archived the board {board}') : $this->l10n->t('{user} has archived the board {before}');
|
||||
} else {
|
||||
$subject = $ownActivity ? $this->l10n->t('You have unarchived the board {board}') : $this->l10n->t('{user} has unarchived the board {before}');
|
||||
}
|
||||
break;
|
||||
|
||||
case self::SUBJECT_STACK_CREATE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have created a new stack {stack} on {board}') : $this->l10n->t('{user} has created a new stack {stack} on {board}');
|
||||
break;
|
||||
case self::SUBJECT_STACK_UPDATE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have created a new stack {stack} on {board}') : $this->l10n->t('{user} has created a new stack {stack} on {board}');
|
||||
break;
|
||||
case self::SUBJECT_STACK_UPDATE_TITLE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have renamed a new stack {before} to {stack} on {board}') : $this->l10n->t('{user} has renamed a new stack {before} to {stack} on {board}');
|
||||
break;
|
||||
case self::SUBJECT_STACK_DELETE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have deleted {stack} on {board}') : $this->l10n->t('{user} has deleted {stack} on {board}');
|
||||
break;
|
||||
case self::SUBJECT_CARD_CREATE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have created {card} in {stack} on {board}') : $this->l10n->t('{user} has created {card} in {stack} on {board}');
|
||||
break;
|
||||
case self::SUBJECT_CARD_DELETE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have deleted {card} in {stack} on {board}') : $this->l10n->t('{user} has deleted {card} in {stack} on {board}');
|
||||
break;
|
||||
case self::SUBJECT_CARD_UPDATE_TITLE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have renamed the card {before} to {card}') : $this->l10n->t('{user} has renamed the card {before} to {card}');
|
||||
break;
|
||||
case self::SUBJECT_CARD_UPDATE_DESCRIPTION:
|
||||
if (!isset($subjectParams['before'])) {
|
||||
$subject = $ownActivity ? $this->l10n->t('You have added a description to {card} in {stack} on {board}') : $this->l10n->t('{user} has added a description to {card} in {stack} on {board}');
|
||||
} else {
|
||||
$subject = $ownActivity ? $this->l10n->t('You have updated the description of {card} in {stack} on {board}') : $this->l10n->t('{user} has updated the description {card} in {stack} on {board}');
|
||||
}
|
||||
break;
|
||||
case self::SUBJECT_CARD_UPDATE_ARCHIVE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have archived {card} in {stack} on {board}') : $this->l10n->t('{user} has archived {card} in {stack} on {board}');
|
||||
break;
|
||||
case self::SUBJECT_CARD_UPDATE_UNARCHIVE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have unarchived {card} in {stack} on {board}') : $this->l10n->t('{user} has unarchived {card} in {stack} on {board}');
|
||||
break;
|
||||
case self::SUBJECT_CARD_UPDATE_DUEDATE:
|
||||
if (!isset($subjectParams['after'])) {
|
||||
$subject = $ownActivity ? $this->l10n->t('You have removed the due date of {card}') : $this->l10n->t('{user} has removed the due date of {card}');
|
||||
} else if (isset($subjectParams['before']) && !isset($subjectParams['after'])) {
|
||||
$subject = $ownActivity ? $this->l10n->t('You have set the due date of {card} to {after}') : $this->l10n->t('{user} has set the due date of {card} to {after}');
|
||||
} else {
|
||||
$subject = $ownActivity ? $this->l10n->t('You have updated the due date of {card} to {after}') : $this->l10n->t('{user} has updated the due date of {card} to {after}');
|
||||
}
|
||||
|
||||
break;
|
||||
case self::SUBJECT_LABEL_ASSIGN:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have added the label {label} to {card} in {stack} on {board}') : $this->l10n->t('{user} has added the label {label} to {card} in {stack} on {board}');
|
||||
break;
|
||||
case self::SUBJECT_LABEL_UNASSING:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have removed the label {label} from {card} in {stack} on {board}') : $this->l10n->t('{user} has removed the label {label} from {card} in {stack} on {board}');
|
||||
break;
|
||||
case self::SUBJECT_CARD_USER_ASSIGN:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have assigned {assigneduser} to {card} on {board}') : $this->l10n->t('{user} has assigned {assigneduser} to {card} on {board}');
|
||||
break;
|
||||
case self::SUBJECT_CARD_USER_UNASSIGN:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have unassigned {assigneduser} from {card} on {board}') : $this->l10n->t('{user} has unassigned {assigneduser} from {card} on {board}');
|
||||
break;
|
||||
case self::SUBJECT_CARD_UPDATE_STACKID:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have moved the card {card} from {stackBefore} to {stack}') : $this->l10n->t('{user} has moved the card {card} from {stackBefore} to {stack}');
|
||||
break;
|
||||
case self::SUBJECT_ATTACHMENT_CREATE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have added the attachment {attachment} to {card}') : $this->l10n->t('{user} has added the attachment {attachment} to {card}');
|
||||
break;
|
||||
case self::SUBJECT_ATTACHMENT_UPDATE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have updated the attachment {attachment} on {card}') : $this->l10n->t('{user} has updated the attachment {attachment} to {card}');
|
||||
break;
|
||||
case self::SUBJECT_ATTACHMENT_DELETE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have deleted the attachment {attachment} from {card}') : $this->l10n->t('{user} has deleted the attachment {attachment} to {card}');
|
||||
break;
|
||||
case self::SUBJECT_ATTACHMENT_RESTORE:
|
||||
$subject = $ownActivity ? $this->l10n->t('You have restored the attachment {attachment} to {card}') : $this->l10n->t('{user} has restored the attachment {attachment} to {card}');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return $subject;
|
||||
}
|
||||
|
||||
public function triggerEvent($objectType, $entity, $subject, $additionalParams = []) {
|
||||
try {
|
||||
$event = $this->createEvent($objectType, $entity, $subject, $additionalParams);
|
||||
$this->sendToUsers($event);
|
||||
} catch (\Exception $e) {
|
||||
// Ignore exception for undefined activities on update events
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param $objectType
|
||||
* @param ChangeSet $changeSet
|
||||
* @param $subject
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function triggerUpdateEvents($objectType, ChangeSet $changeSet, $subject) {
|
||||
$previousEntity = $changeSet->getBefore();
|
||||
$entity = $changeSet->getAfter();
|
||||
$events = [];
|
||||
if ($previousEntity !== null) {
|
||||
foreach ($entity->getUpdatedFields() as $field => $value) {
|
||||
$getter = 'get' . ucfirst($field);
|
||||
$subject = $subject . '_' . $field;
|
||||
$changes = [
|
||||
'before' => $previousEntity->$getter(),
|
||||
'after' => $entity->$getter()
|
||||
];
|
||||
if ($changes['before'] !== $changes['after']) {
|
||||
try {
|
||||
$event = $this->createEvent($objectType, $entity, $subject, $changes);
|
||||
$events[] = $event;
|
||||
} catch (\Exception $e) {
|
||||
// Ignore exception for undefined activities on update events
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$events = [$this->createEvent($objectType, $entity, $subject)];
|
||||
} catch (\Exception $e) {
|
||||
// Ignore exception for undefined activities on update events
|
||||
}
|
||||
}
|
||||
foreach ($events as $event) {
|
||||
$this->sendToUsers($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $objectType
|
||||
* @param $entity
|
||||
* @param $subject
|
||||
* @param array $additionalParams
|
||||
* @return IEvent
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function createEvent($objectType, $entity, $subject, $additionalParams = []) {
|
||||
try {
|
||||
$object = $this->findObjectForEntity($objectType, $entity);
|
||||
} catch (DoesNotExistException $e) {
|
||||
} catch (MultipleObjectsReturnedException $e) {
|
||||
\OC::$server->getLogger()->error('Could not create activity entry for ' . $subject . '. Entity not found.', $entity);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically fetch related details for subject parameters
|
||||
* depending on the subject
|
||||
*/
|
||||
$subjectParams = [];
|
||||
$message = null;
|
||||
switch ($subject) {
|
||||
// No need to enhance parameters since entity already contains the required data
|
||||
case self::SUBJECT_BOARD_CREATE:
|
||||
case self::SUBJECT_BOARD_UPDATE_TITLE:
|
||||
case self::SUBJECT_BOARD_UPDATE_ARCHIVED:
|
||||
case self::SUBJECT_BOARD_DELETE:
|
||||
case self::SUBJECT_BOARD_RESTORE:
|
||||
// Not defined as there is no activity for
|
||||
// case self::SUBJECT_BOARD_UPDATE_COLOR
|
||||
break;
|
||||
|
||||
case self::SUBJECT_STACK_CREATE:
|
||||
case self::SUBJECT_STACK_UPDATE:
|
||||
case self::SUBJECT_STACK_UPDATE_TITLE:
|
||||
case self::SUBJECT_STACK_UPDATE_ORDER:
|
||||
case self::SUBJECT_STACK_DELETE:
|
||||
$subjectParams = $this->findDetailsForStack($entity->getId());
|
||||
break;
|
||||
|
||||
case self::SUBJECT_CARD_CREATE:
|
||||
case self::SUBJECT_CARD_DELETE:
|
||||
case self::SUBJECT_CARD_UPDATE_ARCHIVE:
|
||||
case self::SUBJECT_CARD_UPDATE_UNARCHIVE:
|
||||
case self::SUBJECT_CARD_UPDATE_TITLE:
|
||||
case self::SUBJECT_CARD_UPDATE_DESCRIPTION:
|
||||
case self::SUBJECT_CARD_UPDATE_DUEDATE:
|
||||
case self::SUBJECT_CARD_UPDATE_STACKID:
|
||||
case self::SUBJECT_LABEL_ASSIGN:
|
||||
case self::SUBJECT_LABEL_UNASSING:
|
||||
case self::SUBJECT_CARD_USER_ASSIGN:
|
||||
case self::SUBJECT_CARD_USER_UNASSIGN:
|
||||
$subjectParams = $this->findDetailsForCard($entity->getId());
|
||||
$object = $entity;
|
||||
break;
|
||||
case self::SUBJECT_ATTACHMENT_CREATE:
|
||||
case self::SUBJECT_ATTACHMENT_UPDATE:
|
||||
case self::SUBJECT_ATTACHMENT_DELETE:
|
||||
case self::SUBJECT_ATTACHMENT_RESTORE:
|
||||
$subjectParams = $this->findDetailsForAttachment($entity->getId());
|
||||
$object = $subjectParams['card'];
|
||||
break;
|
||||
case self::SUBJECT_BOARD_SHARE:
|
||||
case self::SUBJECT_BOARD_UNSHARE:
|
||||
$subjectParams = $this->findDetailsForAcl($entity->getId());
|
||||
break;
|
||||
default:
|
||||
throw new \Exception('Unknown subject for activity.');
|
||||
break;
|
||||
}
|
||||
|
||||
if ($subject === self::SUBJECT_CARD_UPDATE_DESCRIPTION){
|
||||
$subjectParams['diff'] = true;
|
||||
}
|
||||
if ($subject === self::SUBJECT_CARD_UPDATE_STACKID) {
|
||||
$subjectParams['stackBefore'] = $this->stackMapper->find($additionalParams['before']);
|
||||
}
|
||||
|
||||
$event = $this->manager->generateEvent();
|
||||
$event->setApp('deck')
|
||||
->setType('deck')
|
||||
->setAuthor($this->userId)
|
||||
->setObject($objectType, (int)$object->getId(), $object->getTitle())
|
||||
->setSubject($subject, array_merge($subjectParams, $additionalParams))
|
||||
->setTimestamp(time());
|
||||
|
||||
if ($message !== null) {
|
||||
$event->setMessage($message);
|
||||
}
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish activity to all users that are part of the board of a given object
|
||||
*
|
||||
* @param IEvent $event
|
||||
*/
|
||||
private function sendToUsers(IEvent $event) {
|
||||
switch ($event->getObjectType()) {
|
||||
case self::DECK_OBJECT_BOARD:
|
||||
$mapper = $this->boardMapper;
|
||||
break;
|
||||
case self::DECK_OBJECT_CARD:
|
||||
$mapper = $this->cardMapper;
|
||||
break;
|
||||
}
|
||||
$boardId = $mapper->findBoardId($event->getObjectId());
|
||||
/** @var IUser $user */
|
||||
foreach ($this->permissionService->findUsers($boardId) as $user) {
|
||||
$event->setAffectedUser($user->getUID());
|
||||
/** @noinspection DisconnectedForeachInstructionInspection */
|
||||
$this->manager->publish($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $objectType
|
||||
* @param $entity
|
||||
* @return null|\OCA\Deck\Db\RelationalEntity|\OCP\AppFramework\Db\Entity
|
||||
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
*/
|
||||
private function findObjectForEntity($objectType, $entity) {
|
||||
$className = \get_class($entity);
|
||||
$objectId = null;
|
||||
if ($objectType === self::DECK_OBJECT_CARD) {
|
||||
switch ($className) {
|
||||
case Card::class:
|
||||
$objectId = $entity->getId();
|
||||
break;
|
||||
case Attachment::class:
|
||||
case Label::class:
|
||||
case AssignedUsers::class:
|
||||
$objectId = $entity->getCardId();
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException('No entity relation present for '. $className . ' to ' . $objectType);
|
||||
}
|
||||
return $this->cardMapper->find($objectId);
|
||||
}
|
||||
if ($objectType === self::DECK_OBJECT_BOARD) {
|
||||
switch ($className) {
|
||||
case Board::class:
|
||||
$objectId = $entity->getId();
|
||||
break;
|
||||
case Label::class:
|
||||
case Stack::class:
|
||||
case Acl::class:
|
||||
$objectId = $entity->getBoardId();
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException('No entity relation present for '. $className . ' to ' . $objectType);
|
||||
}
|
||||
return $this->boardMapper->find($objectId);
|
||||
}
|
||||
throw new InvalidArgumentException('No entity relation present for '. $className . ' to ' . $objectType);
|
||||
}
|
||||
|
||||
private function findDetailsForStack($stackId) {
|
||||
$stack = $this->stackMapper->find($stackId);
|
||||
$board = $this->boardMapper->find($stack->getBoardId());
|
||||
return [
|
||||
'stack' => $stack,
|
||||
'board' => $board
|
||||
];
|
||||
}
|
||||
|
||||
private function findDetailsForCard($cardId) {
|
||||
$card = $this->cardMapper->find($cardId);
|
||||
$stack = $this->stackMapper->find($card->getStackId());
|
||||
$board = $this->boardMapper->find($stack->getBoardId());
|
||||
return [
|
||||
'card' => $card,
|
||||
'stack' => $stack,
|
||||
'board' => $board
|
||||
];
|
||||
}
|
||||
|
||||
private function findDetailsForAttachment($attachmentId) {
|
||||
$attachment = $this->attachmentMapper->find($attachmentId);
|
||||
$data = $this->findDetailsForCard($attachment->getCardId());
|
||||
return array_merge($data, ['attachment' => $attachment]);
|
||||
}
|
||||
|
||||
private function findDetailsForAcl($aclId) {
|
||||
$acl = $this->aclMapper->find($aclId);
|
||||
$board = $this->boardMapper->find($acl->getBoardId());
|
||||
return [
|
||||
'acl' => $acl,
|
||||
'board' => $board
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
90
lib/Activity/ChangeSet.php
Normal file
90
lib/Activity/ChangeSet.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Activity;
|
||||
|
||||
|
||||
class ChangeSet implements \JsonSerializable {
|
||||
|
||||
private $before;
|
||||
private $after;
|
||||
private $diff = false;
|
||||
|
||||
public function __construct($before = null, $after = null) {
|
||||
if ($before !== null) {
|
||||
$this->setBefore($before);
|
||||
}
|
||||
if ($after !== null) {
|
||||
$this->setAfter($after);
|
||||
}
|
||||
}
|
||||
|
||||
public function enableDiff() {
|
||||
$this->diff = true;
|
||||
}
|
||||
|
||||
public function getDiff() {
|
||||
return $this->diff;
|
||||
}
|
||||
|
||||
public function setBefore($before) {
|
||||
if (is_object($before)) {
|
||||
$this->before = clone $before;
|
||||
} else {
|
||||
$this->before = $before;
|
||||
}
|
||||
}
|
||||
|
||||
public function setAfter($after) {
|
||||
if (is_object($after)) {
|
||||
$this->after = clone $after;
|
||||
} else {
|
||||
$this->after = $after;
|
||||
}
|
||||
}
|
||||
|
||||
public function getBefore() {
|
||||
return $this->before;
|
||||
}
|
||||
|
||||
public function getAfter() {
|
||||
return $this->after;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify data which should be serialized to JSON
|
||||
*
|
||||
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
||||
* @return mixed data which can be serialized by <b>json_encode</b>,
|
||||
* which is a value of any type other than a resource.
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function jsonSerialize() {
|
||||
return [
|
||||
'before' => $this->getBefore(),
|
||||
'after' => $this->getAfter(),
|
||||
'diff' => $this->getDiff(),
|
||||
'type' => get_class($this->before)
|
||||
];
|
||||
}
|
||||
}
|
||||
265
lib/Activity/DeckProvider.php
Normal file
265
lib/Activity/DeckProvider.php
Normal file
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Activity;
|
||||
|
||||
|
||||
use cogpowered\FineDiff\Diff;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCP\Activity\IEvent;
|
||||
use OCP\Activity\IProvider;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserManager;
|
||||
|
||||
class DeckProvider implements IProvider {
|
||||
|
||||
/** @var string */
|
||||
private $userId;
|
||||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
/** @var ActivityManager */
|
||||
private $activityManager;
|
||||
/** @var IUserManager */
|
||||
private $userManager;
|
||||
|
||||
public function __construct(IURLGenerator $urlGenerator, ActivityManager $activityManager, IUserManager $userManager, $userId) {
|
||||
$this->userId = $userId;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->activityManager = $activityManager;
|
||||
$this->userManager = $userManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $language The language which should be used for translating, e.g. "en"
|
||||
* @param IEvent $event The current event which should be parsed
|
||||
* @param IEvent|null $previousEvent A potential previous event which you can combine with the current one.
|
||||
* To do so, simply use setChildEvent($previousEvent) after setting the
|
||||
* combined subject on the current event.
|
||||
* @return IEvent
|
||||
* @throws \InvalidArgumentException Should be thrown if your provider does not know this event
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function parse($language, IEvent $event, IEvent $previousEvent = null) {
|
||||
if ($event->getApp() !== 'deck') {
|
||||
throw new \InvalidArgumentException();
|
||||
}
|
||||
|
||||
$event = $this->getIcon($event);
|
||||
|
||||
$subjectIdentifier = $event->getSubject();
|
||||
$subjectParams = $event->getSubjectParameters();
|
||||
$ownActivity = ($event->getAuthor() === $this->userId);
|
||||
|
||||
/**
|
||||
* Map stored parameter objects to rich string types
|
||||
*/
|
||||
$board = null;
|
||||
if ($event->getObjectType() === ActivityManager::DECK_OBJECT_BOARD) {
|
||||
$board = [
|
||||
'type' => 'highlight',
|
||||
'id' => $event->getObjectId(),
|
||||
'name' => $event->getObjectName(),
|
||||
'link' => $this->deckUrl('/board/' . $event->getObjectId()),
|
||||
];
|
||||
}
|
||||
|
||||
$card = null;
|
||||
if ($event->getObjectType() === ActivityManager::DECK_OBJECT_CARD) {
|
||||
$card = [
|
||||
'type' => 'highlight',
|
||||
'id' => $event->getObjectId(),
|
||||
'name' => $event->getObjectName(),
|
||||
];
|
||||
|
||||
if (array_key_exists('board', $subjectParams)) {
|
||||
$archivedParam = $subjectParams['card']['archived'] ? 'archived' : '';
|
||||
$card['link'] = $this->deckUrl('/board/' . $subjectParams['board']['id'] . '/' . $archivedParam . '/card/' . $event->getObjectId());
|
||||
}
|
||||
}
|
||||
|
||||
$author = $event->getAuthor();
|
||||
$user = $this->userManager->get($author);
|
||||
$params = [
|
||||
'board' => $board,
|
||||
'card' => $card,
|
||||
'user' => [
|
||||
'type' => 'user',
|
||||
'id' => $author,
|
||||
'name' => $user !== null ? $user->getDisplayName() : $author
|
||||
]
|
||||
];
|
||||
|
||||
$params = $this->parseParamForBoard('board', $subjectParams, $params);
|
||||
$params = $this->parseParamForStack('stack', $subjectParams, $params);
|
||||
$params = $this->parseParamForStack('stackBefore', $subjectParams, $params);
|
||||
$params = $this->parseParamForAttachment('attachment', $subjectParams, $params);
|
||||
$params = $this->parseParamForLabel($subjectParams, $params);
|
||||
$params = $this->parseParamForAssignedUser($subjectParams, $params);
|
||||
$params = $this->parseParamForAcl($subjectParams, $params);
|
||||
$params = $this->parseParamForChanges($subjectParams, $params, $event);
|
||||
|
||||
try {
|
||||
$subject = $this->activityManager->getActivityFormat($subjectIdentifier, $subjectParams, $ownActivity);
|
||||
$event->setParsedSubject($subject);
|
||||
$event->setRichSubject($subject, $params);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
return $event;
|
||||
}
|
||||
|
||||
private function getIcon(IEvent $event) {
|
||||
$event->setIcon($this->urlGenerator->imagePath('deck', 'deck-dark.svg'));
|
||||
if (strpos($event->getSubject(), '_update') !== false) {
|
||||
$event->setIcon($this->urlGenerator->imagePath('files', 'change.svg'));
|
||||
}
|
||||
if (strpos($event->getSubject(), '_create') !== false) {
|
||||
$event->setIcon($this->urlGenerator->imagePath('files', 'add-color.svg'));
|
||||
}
|
||||
if (strpos($event->getSubject(), '_delete') !== false) {
|
||||
$event->setIcon($this->urlGenerator->imagePath('files', 'delete-color.svg'));
|
||||
}
|
||||
if (strpos($event->getSubject(), 'archive') !== false) {
|
||||
$event->setIcon($this->urlGenerator->imagePath('deck', 'archive.svg'));
|
||||
}
|
||||
if (strpos($event->getSubject(), '_restore') !== false) {
|
||||
$event->setIcon($this->urlGenerator->imagePath('core', 'actions/history.svg'));
|
||||
}
|
||||
if (strpos($event->getSubject(), 'attachment_') !== false) {
|
||||
$event->setIcon($this->urlGenerator->imagePath('core', 'places/files.svg'));
|
||||
}
|
||||
return $event;
|
||||
}
|
||||
|
||||
private function parseParamForBoard($paramName, $subjectParams, $params) {
|
||||
if (array_key_exists($paramName, $subjectParams)) {
|
||||
$params[$paramName] = [
|
||||
'type' => 'highlight',
|
||||
'id' => $subjectParams[$paramName]['id'],
|
||||
'name' => $subjectParams[$paramName]['title'],
|
||||
'link' => $this->deckUrl('/board/' . $subjectParams[$paramName]['id'] . '/'),
|
||||
];
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
private function parseParamForStack($paramName, $subjectParams, $params) {
|
||||
if (array_key_exists($paramName, $subjectParams)) {
|
||||
$params[$paramName] = [
|
||||
'type' => 'highlight',
|
||||
'id' => $subjectParams[$paramName]['id'],
|
||||
'name' => $subjectParams[$paramName]['title'],
|
||||
];
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
private function parseParamForAttachment($paramName, $subjectParams, $params) {
|
||||
if (array_key_exists($paramName, $subjectParams)) {
|
||||
$params[$paramName] = [
|
||||
'type' => 'highlight',
|
||||
'id' => $subjectParams[$paramName]['id'],
|
||||
'name' => $subjectParams[$paramName]['data'],
|
||||
'link' => $this->urlGenerator->linkToRoute('deck.attachment.display', ['cardId' => $subjectParams['card']['id'], 'attachmentId' => $subjectParams['attachment']['id']]),
|
||||
];
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
private function parseParamForAssignedUser($subjectParams, $params) {
|
||||
if (array_key_exists('assigneduser', $subjectParams)) {
|
||||
$user = $this->userManager->get($subjectParams['assigneduser']);
|
||||
$params['assigneduser'] = [
|
||||
'type' => 'user',
|
||||
'id' => $subjectParams['assigneduser'],
|
||||
'name' => $user !== null ? $user->getDisplayName() : $subjectParams['assigneduser']
|
||||
];
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
private function parseParamForLabel($subjectParams, $params) {
|
||||
if (array_key_exists('label', $subjectParams)) {
|
||||
$params['label'] = [
|
||||
'type' => 'highlight',
|
||||
'id' => $subjectParams['label']['id'],
|
||||
'name' => $subjectParams['label']['title']
|
||||
];
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
private function parseParamForAcl($subjectParams, $params) {
|
||||
if (array_key_exists('acl', $subjectParams)) {
|
||||
if ($subjectParams['acl']['type'] === Acl::PERMISSION_TYPE_USER) {
|
||||
$user = $this->userManager->get($subjectParams['acl']['participant']);
|
||||
$params['acl'] = [
|
||||
'type' => 'user',
|
||||
'id' => $subjectParams['acl']['participant'],
|
||||
'name' => $user !== null ? $user->getDisplayName() : $subjectParams['acl']['participant']
|
||||
];
|
||||
} else {
|
||||
$params['acl'] = [
|
||||
'type' => 'highlight',
|
||||
'id' => $subjectParams['acl']['participant'],
|
||||
'name' => $subjectParams['acl']['participant']
|
||||
];
|
||||
}
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add diff to message if the subject parameter 'diff' is set, otherwise
|
||||
* the changed values are added to before/after
|
||||
*
|
||||
* @param $subjectParams
|
||||
* @param $params
|
||||
* @return mixed
|
||||
*/
|
||||
private function parseParamForChanges($subjectParams, $params, $event) {
|
||||
if (array_key_exists('diff', $subjectParams) && $subjectParams['diff']) {
|
||||
$diff = new Diff();
|
||||
$event->setMessage($subjectParams['after']);
|
||||
$event->setParsedMessage('<pre class="visualdiff">' . $diff->render($subjectParams['before'], $subjectParams['after']) . '</pre>');
|
||||
return $params;
|
||||
}
|
||||
if (array_key_exists('before', $subjectParams)) {
|
||||
$params['before'] = [
|
||||
'type' => 'highlight',
|
||||
'id' => $subjectParams['before'],
|
||||
'name' => $subjectParams['before']
|
||||
];
|
||||
}
|
||||
if (array_key_exists('after', $subjectParams)) {
|
||||
$params['after'] = [
|
||||
'type' => 'highlight',
|
||||
'id' => $subjectParams['after'],
|
||||
'name' => $subjectParams['after']
|
||||
];
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
public function deckUrl($endpoint) {
|
||||
return $this->urlGenerator->linkToRoute('deck.page.index') . '#!' . $endpoint;
|
||||
}
|
||||
}
|
||||
92
lib/Activity/Filter.php
Normal file
92
lib/Activity/Filter.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Activity;
|
||||
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
|
||||
class Filter implements \OCP\Activity\IFilter {
|
||||
|
||||
private $l10n;
|
||||
private $urlGenerator;
|
||||
|
||||
public function __construct(
|
||||
IL10N $l10n,
|
||||
IURLGenerator $urlGenerator
|
||||
) {
|
||||
$this->l10n = $l10n;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Lowercase a-z and underscore only identifier
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getIdentifier() {
|
||||
return 'deck';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string A translated string
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->l10n->t('Deck');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int whether the filter should be rather on the top or bottom of
|
||||
* the admin section. The filters are arranged in ascending order of the
|
||||
* priority values. It is required to return a value between 0 and 100.
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getPriority() {
|
||||
return 90;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Full URL to an icon, empty string when none is given
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getIcon() {
|
||||
return $this->urlGenerator->imagePath('deck', 'deck-dark.svg');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $types
|
||||
* @return string[] An array of allowed apps from which activities should be displayed
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function filterTypes(array $types) {
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] An array of allowed apps from which activities should be displayed
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function allowedApps() {
|
||||
return ['deck'];
|
||||
}
|
||||
}
|
||||
86
lib/Activity/Setting.php
Normal file
86
lib/Activity/Setting.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Activity;
|
||||
|
||||
|
||||
class Setting implements \OCP\Activity\ISetting {
|
||||
|
||||
/**
|
||||
* @return string Lowercase a-z and underscore only identifier
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getIdentifier() {
|
||||
return 'deck';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string A translated string
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getName() {
|
||||
return 'Deck';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int whether the filter should be rather on the top or bottom of
|
||||
* the admin section. The filters are arranged in ascending order of the
|
||||
* priority values. It is required to return a value between 0 and 100.
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getPriority() {
|
||||
return 90;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True when the option can be changed for the stream
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function canChangeStream() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True when the option can be changed for the stream
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function isDefaultEnabledStream() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True when the option can be changed for the mail
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function canChangeMail() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True when the option can be changed for the stream
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function isDefaultEnabledMail() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,9 @@
|
||||
namespace OCA\Deck\Service;
|
||||
|
||||
|
||||
use OCA\Deck\Activity\ActivityManager;
|
||||
use OCA\Deck\AppInfo\Application;
|
||||
use OCA\Deck\BadRequestException;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\Attachment;
|
||||
use OCA\Deck\Db\AttachmentMapper;
|
||||
@@ -52,6 +54,8 @@ class AttachmentService {
|
||||
private $cache;
|
||||
/** @var IL10N */
|
||||
private $l10n;
|
||||
/** @var ActivityManager */
|
||||
private $activityManager;
|
||||
|
||||
/**
|
||||
* AttachmentService constructor.
|
||||
@@ -65,7 +69,7 @@ class AttachmentService {
|
||||
* @param IL10N $l10n
|
||||
* @throws \OCP\AppFramework\QueryException
|
||||
*/
|
||||
public function __construct(AttachmentMapper $attachmentMapper, CardMapper $cardMapper, PermissionService $permissionService, Application $application, ICacheFactory $cacheFactory, $userId, IL10N $l10n) {
|
||||
public function __construct(AttachmentMapper $attachmentMapper, CardMapper $cardMapper, PermissionService $permissionService, Application $application, ICacheFactory $cacheFactory, $userId, IL10N $l10n, ActivityManager $activityManager) {
|
||||
$this->attachmentMapper = $attachmentMapper;
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->permissionService = $permissionService;
|
||||
@@ -73,6 +77,7 @@ class AttachmentService {
|
||||
$this->application = $application;
|
||||
$this->cache = $cacheFactory->createDistributed('deck-card-attachments-');
|
||||
$this->l10n = $l10n;
|
||||
$this->activityManager = $activityManager;
|
||||
|
||||
// Register shipped attachment services
|
||||
// TODO: move this to a plugin based approach once we have different types of attachments
|
||||
@@ -168,7 +173,7 @@ class AttachmentService {
|
||||
}
|
||||
|
||||
if ($data === false || $data === null) {
|
||||
throw new BadRequestException('data must be provided');
|
||||
//throw new BadRequestException('data must be provided');
|
||||
}
|
||||
|
||||
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
|
||||
@@ -200,6 +205,7 @@ class AttachmentService {
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
// just store the data
|
||||
}
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_CREATE);
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
@@ -261,7 +267,7 @@ class AttachmentService {
|
||||
}
|
||||
|
||||
if ($data === false || $data === null) {
|
||||
throw new BadRequestException('data must be provided');
|
||||
//throw new BadRequestException('data must be provided');
|
||||
}
|
||||
|
||||
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
|
||||
@@ -284,6 +290,7 @@ class AttachmentService {
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
// just store the data
|
||||
}
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_UPDATE);
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
@@ -317,13 +324,16 @@ class AttachmentService {
|
||||
$service = $this->getService($attachment->getType());
|
||||
if ($service->allowUndo()) {
|
||||
$service->markAsDeleted($attachment);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_DELETE);
|
||||
return $this->attachmentMapper->update($attachment);
|
||||
}
|
||||
$service->delete($attachment);
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
// just delete without further action
|
||||
}
|
||||
return $this->attachmentMapper->delete($attachment);
|
||||
$attachment = $this->attachmentMapper->delete($attachment);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_DELETE);
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
public function restore($cardId, $attachmentId) {
|
||||
@@ -344,10 +354,12 @@ class AttachmentService {
|
||||
$service = $this->getService($attachment->getType());
|
||||
if ($service->allowUndo()) {
|
||||
$attachment->setDeletedAt(0);
|
||||
return $this->attachmentMapper->update($attachment);
|
||||
$attachment = $this->attachmentMapper->update($attachment);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_RESTORE);
|
||||
return $attachment;
|
||||
}
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
}
|
||||
throw new NoPermissionException('Restore is not allowed.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
|
||||
namespace OCA\Deck\Service;
|
||||
|
||||
use OCA\Deck\Activity\ActivityManager;
|
||||
use OCA\Deck\Activity\ChangeSet;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\AclMapper;
|
||||
use OCA\Deck\Db\AssignedUsersMapper;
|
||||
@@ -51,6 +53,7 @@ class BoardService {
|
||||
private $userManager;
|
||||
private $groupManager;
|
||||
private $userId;
|
||||
private $activityManager;
|
||||
|
||||
public function __construct(
|
||||
BoardMapper $boardMapper,
|
||||
@@ -62,6 +65,7 @@ class BoardService {
|
||||
AssignedUsersMapper $assignedUsersMapper,
|
||||
IUserManager $userManager,
|
||||
IGroupManager $groupManager,
|
||||
ActivityManager $activityManager,
|
||||
$userId
|
||||
) {
|
||||
$this->boardMapper = $boardMapper;
|
||||
@@ -73,6 +77,7 @@ class BoardService {
|
||||
$this->assignedUsersMapper = $assignedUsersMapper;
|
||||
$this->userManager = $userManager;
|
||||
$this->groupManager = $groupManager;
|
||||
$this->activityManager = $activityManager;
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
@@ -244,7 +249,7 @@ class BoardService {
|
||||
$board->setTitle($title);
|
||||
$board->setOwner($userId);
|
||||
$board->setColor($color);
|
||||
$new_board = $this->boardMapper->insert($board);
|
||||
$new_board = $this->boardMapper->insert($board);
|
||||
|
||||
// create new labels
|
||||
$default_labels = [
|
||||
@@ -270,6 +275,7 @@ class BoardService {
|
||||
'PERMISSION_MANAGE' => $permissions[Acl::PERMISSION_MANAGE],
|
||||
'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE]
|
||||
]);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $new_board, ActivityManager::SUBJECT_BOARD_CREATE);
|
||||
return $new_board;
|
||||
|
||||
}
|
||||
@@ -291,7 +297,8 @@ class BoardService {
|
||||
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_READ);
|
||||
$board = $this->find($id);
|
||||
$board->setDeletedAt(time());
|
||||
$this->boardMapper->update($board);
|
||||
$board = $this->boardMapper->update($board);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $board, ActivityManager::SUBJECT_BOARD_DELETE);
|
||||
return $board;
|
||||
}
|
||||
|
||||
@@ -311,7 +318,9 @@ class BoardService {
|
||||
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_READ);
|
||||
$board = $this->find($id);
|
||||
$board->setDeletedAt(0);
|
||||
return $this->boardMapper->update($board);
|
||||
$board = $this->boardMapper->update($board);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $board, ActivityManager::SUBJECT_BOARD_RESTORE);
|
||||
return $board;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,7 +331,7 @@ class BoardService {
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
* @throws BadRequestException
|
||||
*/
|
||||
public function deleteForce($id) {
|
||||
public function deleteForce($id) {
|
||||
if (is_numeric($id) === false) {
|
||||
throw new BadRequestException('id must be a number');
|
||||
}
|
||||
@@ -363,11 +372,15 @@ class BoardService {
|
||||
|
||||
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
|
||||
$board = $this->find($id);
|
||||
$changes = new ChangeSet($board);
|
||||
$board->setTitle($title);
|
||||
$board->setColor($color);
|
||||
$board->setArchived($archived);
|
||||
$changes->setAfter($board);
|
||||
$this->boardMapper->update($board); // operate on clone so we can check for updated fields
|
||||
$this->boardMapper->mapOwner($board);
|
||||
return $this->boardMapper->update($board);
|
||||
$this->activityManager->triggerUpdateEvents(ActivityManager::DECK_OBJECT_BOARD, $changes, ActivityManager::SUBJECT_BOARD_UPDATE);
|
||||
return $board;
|
||||
}
|
||||
|
||||
|
||||
@@ -421,6 +434,7 @@ class BoardService {
|
||||
$this->notificationHelper->sendBoardShared($boardId, $acl);
|
||||
|
||||
$newAcl = $this->aclMapper->insert($acl);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE);
|
||||
$this->boardMapper->mapAcl($newAcl);
|
||||
return $newAcl;
|
||||
}
|
||||
@@ -488,6 +502,7 @@ class BoardService {
|
||||
$this->assignedUsersMapper->delete($assignement);
|
||||
}
|
||||
}
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $acl, ActivityManager::SUBJECT_BOARD_UNSHARE);
|
||||
return $this->aclMapper->delete($acl);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
|
||||
namespace OCA\Deck\Service;
|
||||
|
||||
use OCA\Deck\Activity\ActivityManager;
|
||||
use OCA\Deck\Activity\ChangeSet;
|
||||
use OCA\Deck\Db\AssignedUsers;
|
||||
use OCA\Deck\Db\AssignedUsersMapper;
|
||||
use OCA\Deck\Db\Card;
|
||||
@@ -48,17 +50,19 @@ class CardService {
|
||||
private $assignedUsersMapper;
|
||||
private $attachmentService;
|
||||
private $currentUser;
|
||||
private $activityManager;
|
||||
|
||||
public function __construct(
|
||||
CardMapper $cardMapper,
|
||||
StackMapper $stackMapper,
|
||||
BoardMapper $boardMapper,
|
||||
LabelMapper $labelMapper,
|
||||
PermissionService $permissionService,
|
||||
PermissionService $permissionService,
|
||||
BoardService $boardService,
|
||||
NotificationHelper $notificationHelper,
|
||||
AssignedUsersMapper $assignedUsersMapper,
|
||||
AssignedUsersMapper $assignedUsersMapper,
|
||||
AttachmentService $attachmentService,
|
||||
ActivityManager $activityManager,
|
||||
$userId
|
||||
) {
|
||||
$this->cardMapper = $cardMapper;
|
||||
@@ -70,6 +74,7 @@ class CardService {
|
||||
$this->notificationHelper = $notificationHelper;
|
||||
$this->assignedUsersMapper = $assignedUsersMapper;
|
||||
$this->attachmentService = $attachmentService;
|
||||
$this->activityManager = $activityManager;
|
||||
$this->currentUser = $userId;
|
||||
}
|
||||
|
||||
@@ -157,7 +162,9 @@ class CardService {
|
||||
$card->setType($type);
|
||||
$card->setOrder($order);
|
||||
$card->setOwner($owner);
|
||||
return $this->cardMapper->insert($card);
|
||||
$card = $this->cardMapper->insert($card);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_CREATE);
|
||||
return $card;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,6 +189,7 @@ class CardService {
|
||||
$card = $this->cardMapper->find($id);
|
||||
$card->setDeletedAt(time());
|
||||
$this->cardMapper->update($card);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_DELETE);
|
||||
return $card;
|
||||
}
|
||||
|
||||
@@ -204,7 +212,7 @@ class CardService {
|
||||
public function update($id, $title, $stackId, $type, $order = 0, $description = '', $owner, $duedate = null, $deletedAt) {
|
||||
|
||||
if (is_numeric($id) === false) {
|
||||
throw new BadRequestException('card id must be a number');
|
||||
throw new BadRequestException('card id must be a number');
|
||||
}
|
||||
|
||||
if ($title === false || $title === null) {
|
||||
@@ -231,6 +239,7 @@ class CardService {
|
||||
if ($card->getArchived()) {
|
||||
throw new StatusException('Operation not allowed. This card is archived.');
|
||||
}
|
||||
$changes = new ChangeSet($card);
|
||||
$card->setTitle($title);
|
||||
$card->setStackId($stackId);
|
||||
$card->setType($type);
|
||||
@@ -239,7 +248,10 @@ class CardService {
|
||||
$card->setDescription($description);
|
||||
$card->setDuedate($duedate);
|
||||
$card->setDeletedAt($deletedAt);
|
||||
return $this->cardMapper->update($card);
|
||||
$changes->setAfter($card);
|
||||
$card = $this->cardMapper->update($card);
|
||||
$this->activityManager->triggerUpdateEvents(ActivityManager::DECK_OBJECT_CARD, $changes, ActivityManager::SUBJECT_CARD_UPDATE);
|
||||
return $card;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,7 +362,9 @@ class CardService {
|
||||
}
|
||||
$card = $this->cardMapper->find($id);
|
||||
$card->setArchived(true);
|
||||
return $this->cardMapper->update($card);
|
||||
$newCard = $this->cardMapper->update($card);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_UPDATE_ARCHIVE);
|
||||
return $newCard;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -374,7 +388,9 @@ class CardService {
|
||||
}
|
||||
$card = $this->cardMapper->find($id);
|
||||
$card->setArchived(false);
|
||||
return $this->cardMapper->update($card);
|
||||
$newCard = $this->cardMapper->update($card);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_UPDATE_UNARCHIVE);
|
||||
return $newCard;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -404,7 +420,9 @@ class CardService {
|
||||
if ($card->getArchived()) {
|
||||
throw new StatusException('Operation not allowed. This card is archived.');
|
||||
}
|
||||
$label = $this->labelMapper->find($labelId);
|
||||
$this->cardMapper->assignLabel($cardId, $labelId);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_LABEL_ASSIGN, ['label' => $label]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -434,7 +452,9 @@ class CardService {
|
||||
if ($card->getArchived()) {
|
||||
throw new StatusException('Operation not allowed. This card is archived.');
|
||||
}
|
||||
$label = $this->labelMapper->find($labelId);
|
||||
$this->cardMapper->removeLabel($cardId, $labelId);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_LABEL_UNASSING, ['label' => $label]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -462,17 +482,19 @@ class CardService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$card = $this->cardMapper->find($cardId);
|
||||
|
||||
if ($userId !== $this->currentUser) {
|
||||
/* Notifyuser about the card assignment */
|
||||
$card = $this->cardMapper->find($cardId);
|
||||
$this->notificationHelper->sendCardAssigned($card, $userId);
|
||||
}
|
||||
|
||||
$assignment = new AssignedUsers();
|
||||
$assignment->setCardId($cardId);
|
||||
$assignment->setParticipant($userId);
|
||||
return $this->assignedUsersMapper->insert($assignment);
|
||||
$assignment = $this->assignedUsersMapper->insert($assignment);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_USER_ASSIGN, ['assigneduser' => $userId]);
|
||||
return $assignment;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -498,7 +520,10 @@ class CardService {
|
||||
$assignments = $this->assignedUsersMapper->find($cardId);
|
||||
foreach ($assignments as $assignment) {
|
||||
if ($assignment->getParticipant() === $userId) {
|
||||
return $this->assignedUsersMapper->delete($assignment);
|
||||
$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]);
|
||||
return $assignment;
|
||||
}
|
||||
}
|
||||
throw new NotFoundException('No assignment for ' . $userId . 'found.');
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
|
||||
namespace OCA\Deck\Service;
|
||||
|
||||
use OCA\Deck\Activity\ActivityManager;
|
||||
use OCA\Deck\Activity\ChangeSet;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
@@ -45,6 +47,7 @@ class StackService {
|
||||
private $cardService;
|
||||
private $assignedUsersMapper;
|
||||
private $attachmentService;
|
||||
private $activityManager;
|
||||
|
||||
public function __construct(
|
||||
StackMapper $stackMapper,
|
||||
@@ -55,7 +58,8 @@ class StackService {
|
||||
BoardService $boardService,
|
||||
CardService $cardService,
|
||||
AssignedUsersMapper $assignedUsersMapper,
|
||||
AttachmentService $attachmentService
|
||||
AttachmentService $attachmentService,
|
||||
ActivityManager $activityManager
|
||||
) {
|
||||
$this->stackMapper = $stackMapper;
|
||||
$this->boardMapper = $boardMapper;
|
||||
@@ -66,10 +70,11 @@ class StackService {
|
||||
$this->cardService = $cardService;
|
||||
$this->assignedUsersMapper = $assignedUsersMapper;
|
||||
$this->attachmentService = $attachmentService;
|
||||
$this->activityManager = $activityManager;
|
||||
}
|
||||
|
||||
private function enrichStackWithCards($stack) {
|
||||
$cards = $this->cardMapper->findAll($stack->id);
|
||||
$cards = $this->cardMapper->findAll($stack->getId());
|
||||
|
||||
if(is_null($cards)) {
|
||||
return;
|
||||
@@ -195,8 +200,9 @@ class StackService {
|
||||
$stack->setTitle($title);
|
||||
$stack->setBoardId($boardId);
|
||||
$stack->setOrder($order);
|
||||
return $this->stackMapper->insert($stack);
|
||||
|
||||
$stack = $this->stackMapper->insert($stack);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $stack, ActivityManager::SUBJECT_STACK_CREATE);
|
||||
return $stack;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,10 +223,11 @@ class StackService {
|
||||
|
||||
$stack = $this->stackMapper->find($id);
|
||||
$stack->setDeletedAt(time());
|
||||
$this->stackMapper->update($stack);
|
||||
$stack = $this->stackMapper->update($stack);
|
||||
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $stack, ActivityManager::SUBJECT_STACK_DELETE);
|
||||
|
||||
$this->enrichStackWithCards($stack);
|
||||
|
||||
return $stack;
|
||||
}
|
||||
|
||||
@@ -260,11 +267,15 @@ class StackService {
|
||||
throw new StatusException('Operation not allowed. This board is archived.');
|
||||
}
|
||||
$stack = $this->stackMapper->find($id);
|
||||
$changes = new ChangeSet($stack);
|
||||
$stack->setTitle($title);
|
||||
$stack->setBoardId($boardId);
|
||||
$stack->setOrder($order);
|
||||
$stack->setDeletedAt($deletedAt);
|
||||
return $this->stackMapper->update($stack);
|
||||
$changes->setAfter($stack);
|
||||
$stack = $this->stackMapper->update($stack);
|
||||
$this->activityManager->triggerUpdateEvents(ActivityManager::DECK_OBJECT_BOARD, $changes, ActivityManager::SUBJECT_STACK_UPDATE);
|
||||
return $stack;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
|
||||
use OCP\Util;
|
||||
|
||||
Util::addScript('activity', 'richObjectStringParser');
|
||||
Util::addStyle('activity', 'style');
|
||||
|
||||
Util::addStyle('deck', '../js/build/vendor');
|
||||
Util::addScript('deck', 'build/vendor');
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<div id="stack-add" ng-if="boardservice.canEdit() && checkCanEdit()">
|
||||
<form class="ng-pristine ng-valid" ng-submit="createStack()">
|
||||
<label for="new-stack-input" class="hidden-visually"><?php p($l->t('Add a new stack')); ?></label>
|
||||
<label for="new-stack-input-<?php p($_['headerControlsId']); ?>" class="hidden-visually"><?php p($l->t('Add a new stack')); ?></label>
|
||||
<input type="text" class="no-close" placeholder="<?php p($l->t('Add a new stack')); ?>"
|
||||
ng-focus="status.addStack=true"
|
||||
ng-blur="status.addStack=false"
|
||||
ng-model="newStack.title" required
|
||||
id="new-stack-input"
|
||||
id="new-stack-input-<?php p($_['headerControlsId']); ?>"
|
||||
maxlength="100" />
|
||||
<button class="button-inline icon icon-add" ng-style="{'opacity':'{{status.addStack ? 1: 0.5}}'}" type="submit" title="<?php p($l->t('Submit')); ?>">
|
||||
<span class="hidden-visually"><?php p($l->t('Submit')); ?></span>
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
</div>
|
||||
|
||||
<div class="board-header-controls hidden">
|
||||
<?php print_unescaped($this->inc('part.board.headerControls')); ?>
|
||||
<?php print_unescaped($this->inc('part.board.headerControls', ['headerControlsId' => 'main'])); ?>
|
||||
</div>
|
||||
<div class="board-header-controls app-popover-menu-utils">
|
||||
<button class="icon-more button"></button>
|
||||
<div class="popovermenu hidden">
|
||||
<div id="popover-controls">
|
||||
<?php print_unescaped($this->inc('part.board.headerControls')); ?>
|
||||
<?php print_unescaped($this->inc('part.board.headerControls', ['headerControlsId' => 'popover'])); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
<li class="tabHeader" ng-class="{'selected': (params.tab==1)}" ui-sref="{tab: 1}"><a><?php p($l->t('Tags')); ?></a></li>
|
||||
<li class="tabHeader" ng-class="{'selected': (params.tab==2)}" ui-sref="{tab: 2}"><a><?php p($l->t('Deleted Stacks')); ?></a></li>
|
||||
<li class="tabHeader" ng-class="{'selected': (params.tab==3)}" ui-sref="{tab: 3}"><a><?php p($l->t('Deleted Cards')); ?></a></li>
|
||||
<li class="tabHeader" ng-class="{'selected': (params.tab==4)}" ui-sref="{tab: 4}"><a><?php p($l->t('Activity')); ?></a></li>
|
||||
|
||||
</ul>
|
||||
<div class="tabsContainer">
|
||||
<div id="tabBoardShare" class="tab" ng-if="params.tab==0 || !params.tab">
|
||||
@@ -147,4 +149,10 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="board-detail-activity" class="tab activityTabView" ng-if="params.tab==4">
|
||||
<activity-component ng-if="boardservice.getCurrent()" type="deck_board" element="boardservice.getCurrent()"></activity-component>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
15
templates/part.card.activity.html
Normal file
15
templates/part.card.activity.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<ul class="activities" infinite-scroll="$ctrl.page()" infinite-scroll-container="'#app-sidebar'" infinite-scroll-disabled="$ctrl.activityservice.running" infinite-scroll-immediate-check="false">
|
||||
<li ng-if="$ctrl.loadingNewer()"><div class="icon-loading-small"></div></li>
|
||||
<li class="activity box" ng-repeat="activity in $ctrl.getData($ctrl.element.id) track by $index">
|
||||
<div class="activity-icon">
|
||||
<img src="{{activity.icon}}" alt="">
|
||||
</div>
|
||||
<div class="activitysubject"
|
||||
bind-html-compile="$ctrl.parseMessage(activity.subject_rich[0], activity.subject_rich[1])"></div>
|
||||
<span class="activitytime has-tooltip live-relative-timestamp"
|
||||
data-timestamp="{{ activity.timestamp }}">{{ activity.timestamp/1000 | relativeDateFilter }}</span>
|
||||
<div class="activitymessage" ng-bind-html="activity.message"></div>
|
||||
</li>
|
||||
|
||||
<li ng-if="$ctrl.loading"><div class="icon-loading-small"></div></li>
|
||||
</ul>
|
||||
@@ -90,8 +90,10 @@
|
||||
|
||||
<div class="section-header-tabbed">
|
||||
<ul class="tabHeaders ng-scope">
|
||||
<li class="tabHeader selected" ng-class="{'selected': (params.tab==0 || !params.tab)}" ui-sref="{tab: 0}"><a><?php p($l->t('Description')); ?></a></li>
|
||||
<li class="tabHeader" ng-class="{'selected': (params.tab==0 || !params.tab)}" ui-sref="{tab: 0}"><a><?php p($l->t('Description')); ?></a></li>
|
||||
<li class="tabHeader" ng-class="{'selected': (params.tab==1)}" ui-sref="{tab: 1}"><a><?php p($l->t('Attachments')); ?></a></li>
|
||||
<li class="tabHeader" ng-class="{'selected': (params.tab==2)}" ui-sref="{tab: 2}"><a><?php p($l->t('Activity')); ?></a></li>
|
||||
|
||||
</ul>
|
||||
<div class="tabDetails">
|
||||
<span class="save-indicator saved"><?php p($l->t('Saved')); ?></span>
|
||||
@@ -128,4 +130,10 @@
|
||||
ng-if="!description()"><?php p($l->t('Add a card description…')); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-content card-activity activityTabView" ng-if="params.tab === 2">
|
||||
<activity-component type="deck_card" element="cardservice.getCurrent()"></activity-component>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<div class="colorselect" ng-controller="ColorPickerController">
|
||||
<div class="color" ng-repeat="c in ::colors" ng-style="{'background-color':'#{{ c }}'}" ng-click="b=setColor(b,c)" ng-class="{'selected': (c == b.color) }"></div>
|
||||
<label class="colorselect-label{{ b.color | iconWhiteFilter }} color" ng-style="getCustomBackground(b.hashedColor)" ng-init="b.hashedColor='#' + b.color">
|
||||
<input class="color" type="color" ng-model="b.hashedColor" value="#{{b.color}}" ng-change="b=setHashedColor(b)"/>
|
||||
<input class="color" type="color" ng-model="b.hashedColor" ng-value="colorValue(b.color)" ng-change="b=setHashedColor(b)"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,7 +59,7 @@
|
||||
<div class="colorselect" ng-controller="ColorPickerController">
|
||||
<div class="color" ng-repeat="c in ::colors" ng-style="{'background-color':'#{{ c }}'}" ng-click="selectColor(c);newBoard=setColor(newBoard,c)" ng-class="{'selected': (c == newBoard.color), 'dark': (newBoard.color | textColorFilter) === '#ffffff' }"><br /></div>
|
||||
<label class="colorselect-label{{ newBoard.color | iconWhiteFilter }} color" ng-style="getCustomBackground(newBoard.hashedColor)" ng-init="newBoard.hashedColor='#' + newBoard.color">
|
||||
<input class="color" type="color" ng-model="newBoard.hashedColor" value="#{{newBoard.color}}" ng-change="newBoard=setHashedColor(newBoard)"/>
|
||||
<input class="color" type="color" ng-model="newBoard.hashedColor" ng-value="colorValue(newBoard.color)" ng-change="newBoard=setHashedColor(newBoard)"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
344
tests/unit/Activity/ActivityManagerTest.php
Normal file
344
tests/unit/Activity/ActivityManagerTest.php
Normal file
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Activity;
|
||||
|
||||
use OCA\Deck\Db\AclMapper;
|
||||
use OCA\Deck\Db\AssignedUsers;
|
||||
use OCA\Deck\Db\Attachment;
|
||||
use OCA\Deck\Db\AttachmentMapper;
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\Label;
|
||||
use OCA\Deck\Db\Stack;
|
||||
use OCA\Deck\Db\StackMapper;
|
||||
use OCA\Deck\Service\PermissionService;
|
||||
use OCP\Activity\IEvent;
|
||||
use OCP\Activity\IManager;
|
||||
use OCP\IL10N;
|
||||
use OCP\IUser;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit_Framework_MockObject_MockObject as MockObject;
|
||||
|
||||
class ActivityManagerTest extends TestCase {
|
||||
|
||||
/** @var ActivityManager */
|
||||
private $activityManager;
|
||||
/** @var IManager|MockObject */
|
||||
private $manager;
|
||||
/** @var PermissionService|MockObject */
|
||||
private $permissionService;
|
||||
/** @var BoardMapper|MockObject */
|
||||
private $boardMapper;
|
||||
/** @var CardMapper|MockObject */
|
||||
private $cardMapper;
|
||||
/** @var StackMapper|MockObject */
|
||||
private $stackMapper;
|
||||
/** @var AttachmentMapper|MockObject */
|
||||
private $attachmentMapper;
|
||||
/** @var AclMapper|MockObject */
|
||||
private $aclMapper;
|
||||
/** @var IL10N|MockObject */
|
||||
private $l10n;
|
||||
/** @var string */
|
||||
private $userId = 'admin';
|
||||
|
||||
public function setUp() {
|
||||
$this->manager = $this->createMock(IManager::class);
|
||||
$this->permissionService = $this->createMock(PermissionService::class);
|
||||
$this->boardMapper = $this->createMock(BoardMapper::class);
|
||||
$this->cardMapper = $this->createMock(CardMapper::class);
|
||||
$this->stackMapper = $this->createMock(StackMapper::class);
|
||||
$this->attachmentMapper = $this->createMock(AttachmentMapper::class);
|
||||
$this->aclMapper = $this->createMock(AclMapper::class);
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
$this->activityManager = new ActivityManager(
|
||||
$this->manager,
|
||||
$this->permissionService,
|
||||
$this->boardMapper,
|
||||
$this->cardMapper,
|
||||
$this->stackMapper,
|
||||
$this->attachmentMapper,
|
||||
$this->aclMapper,
|
||||
$this->l10n,
|
||||
$this->userId
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetActivityFormatOwn() {
|
||||
$managerClass = new \ReflectionClass(ActivityManager::class);
|
||||
$this->l10n->expects($this->any())
|
||||
->method('t')
|
||||
->will($this->returnCallback(function ($s) { return $s; }));
|
||||
|
||||
foreach ($managerClass->getConstants() as $constant => $value) {
|
||||
if (strpos($constant, 'SUBJECT') === 0) {
|
||||
$format = $this->activityManager->getActivityFormat($value, [], false);
|
||||
if ($format !== '') {
|
||||
$this->assertContains('{user}', $format);
|
||||
} else {
|
||||
/** @noinspection ForgottenDebugOutputInspection */
|
||||
print_r('No activity string found for '. $constant . PHP_EOL);
|
||||
}
|
||||
$format = $this->activityManager->getActivityFormat($value, [], true);
|
||||
if ($format !== '') {
|
||||
$this->assertStringStartsWith('You', $format);
|
||||
} else {
|
||||
/** @noinspection ForgottenDebugOutputInspection */
|
||||
print_r('No own activity string found for '. $constant . PHP_EOL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function testCreateEvent() {
|
||||
$board = new Board();
|
||||
$this->boardMapper->expects($this->once())
|
||||
->method('find')
|
||||
->willReturn($board);
|
||||
$event = $this->createMock(IEvent::class);
|
||||
$this->manager->expects($this->once())
|
||||
->method('generateEvent')
|
||||
->willReturn($event);
|
||||
$event->expects($this->once())->method('setApp')->willReturn($event);
|
||||
$event->expects($this->once())->method('setType')->willReturn($event);
|
||||
$event->expects($this->once())->method('setAuthor')->willReturn($event);
|
||||
$event->expects($this->once())->method('setObject')->willReturn($event);
|
||||
$event->expects($this->once())->method('setSubject')->willReturn($event);
|
||||
$event->expects($this->once())->method('setTimestamp')->willReturn($event);
|
||||
$actual = $this->invokePrivate($this->activityManager, 'createEvent', [
|
||||
ActivityManager::DECK_OBJECT_BOARD,
|
||||
$board,
|
||||
ActivityManager::SUBJECT_BOARD_CREATE
|
||||
]);
|
||||
$this->assertEquals($event, $actual);
|
||||
}
|
||||
|
||||
public function dataSendToUsers() {
|
||||
return [
|
||||
[ActivityManager::DECK_OBJECT_BOARD],
|
||||
[ActivityManager::DECK_OBJECT_CARD],
|
||||
];
|
||||
}
|
||||
|
||||
private function mockUser($uid) {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->expects($this->any())
|
||||
->method('getUID')
|
||||
->willReturn($uid);
|
||||
return $user;
|
||||
}
|
||||
/**
|
||||
* @dataProvider dataSendToUsers
|
||||
*/
|
||||
public function testSendToUser($objectType) {
|
||||
$users = [
|
||||
$this->mockUser('user1'),
|
||||
$this->mockUser('user2'),
|
||||
];
|
||||
$event = $this->createMock(IEvent::class);
|
||||
$event->expects($this->at(0))
|
||||
->method('getObjectType')
|
||||
->willReturn($objectType);
|
||||
$event->expects($this->at(0))
|
||||
->method('getObjectId')
|
||||
->willReturn(1);
|
||||
$event->expects($this->at(2))
|
||||
->method('setAffectedUser')
|
||||
->with('user1');
|
||||
$event->expects($this->at(3))
|
||||
->method('setAffectedUser')
|
||||
->with('user2');
|
||||
$mapper = null;
|
||||
switch ($objectType) {
|
||||
case ActivityManager::DECK_OBJECT_BOARD:
|
||||
$mapper = $this->boardMapper;
|
||||
break;
|
||||
case ActivityManager::DECK_OBJECT_CARD:
|
||||
$mapper = $this->cardMapper;
|
||||
break;
|
||||
}
|
||||
$mapper->expects($this->once())
|
||||
->method('findBoardId')
|
||||
->willReturn(123);
|
||||
$this->permissionService->expects($this->once())
|
||||
->method('findUsers')
|
||||
->willReturn($users);
|
||||
$this->manager->expects($this->at(0))
|
||||
->method('publish')
|
||||
->with($event);
|
||||
$this->manager->expects($this->at(1))
|
||||
->method('publish')
|
||||
->with($event);
|
||||
$this->invokePrivate($this->activityManager, 'sendToUsers', [$event]);
|
||||
}
|
||||
|
||||
public function dataFindObjectForEntity() {
|
||||
$board = new Board();
|
||||
$board->setId(1);
|
||||
$stack = new Stack();
|
||||
$stack->setBoardId(1);
|
||||
$card = new Card();
|
||||
$card->setId(3);
|
||||
$attachment = new Attachment();
|
||||
$attachment->setCardId(3);
|
||||
$label = new Label();
|
||||
$label->setCardId(3);
|
||||
$label->setBoardId(1);
|
||||
$assignedUser = new AssignedUsers();
|
||||
$assignedUser->setCardId(3);
|
||||
|
||||
return [
|
||||
[ActivityManager::DECK_OBJECT_BOARD, $board],
|
||||
[ActivityManager::DECK_OBJECT_BOARD, $stack],
|
||||
[ActivityManager::DECK_OBJECT_BOARD, $label],
|
||||
[ActivityManager::DECK_OBJECT_CARD, $card],
|
||||
[ActivityManager::DECK_OBJECT_CARD, $attachment],
|
||||
[ActivityManager::DECK_OBJECT_CARD, $assignedUser],
|
||||
[ActivityManager::DECK_OBJECT_CARD, $label],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $objectType
|
||||
* @param $entity
|
||||
* @dataProvider dataFindObjectForEntity
|
||||
*/
|
||||
public function testFindObjectForEntity($objectType, $entity) {
|
||||
$board = new Board();
|
||||
$board->setId(1);
|
||||
$card = new Card();
|
||||
$card->setId(3);
|
||||
$expected = null;
|
||||
if ($objectType === ActivityManager::DECK_OBJECT_BOARD) {
|
||||
$this->boardMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(1)
|
||||
->willReturn($board);
|
||||
$expected = $board;
|
||||
}
|
||||
if ($objectType === ActivityManager::DECK_OBJECT_CARD) {
|
||||
$this->cardMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(3)
|
||||
->willReturn($card);
|
||||
$expected = $card;
|
||||
}
|
||||
$actual = $this->invokePrivate($this->activityManager, 'findObjectForEntity', [$objectType, $entity]);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testFindDetailsForStack() {
|
||||
$stack = new Stack();
|
||||
$stack->setId(123);
|
||||
$stack->setBoardId(999);
|
||||
$board = new Board();
|
||||
$board->setId(999);
|
||||
$this->stackMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(123)
|
||||
->willReturn($stack);
|
||||
$this->boardMapper->expects($this->once())->method('find')
|
||||
->with(999)
|
||||
->willReturn($board);
|
||||
$this->assertEquals([
|
||||
'stack' => $stack,
|
||||
'board' => $board
|
||||
], $this->invokePrivate($this->activityManager, 'findDetailsForStack', [123]));
|
||||
}
|
||||
|
||||
|
||||
public function testFindDetailsForCard() {
|
||||
$card = new Card();
|
||||
$card->setId(555);
|
||||
$card->setStackId(123);
|
||||
$stack = new Stack();
|
||||
$stack->setId(123);
|
||||
$stack->setBoardId(999);
|
||||
$board = new Board();
|
||||
$board->setId(999);
|
||||
$this->cardMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(555)
|
||||
->willReturn($card);
|
||||
$this->stackMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(123)
|
||||
->willReturn($stack);
|
||||
$this->boardMapper->expects($this->once())->method('find')
|
||||
->with(999)
|
||||
->willReturn($board);
|
||||
$this->assertEquals([
|
||||
'stack' => $stack,
|
||||
'board' => $board,
|
||||
'card' => $card
|
||||
], $this->invokePrivate($this->activityManager, 'findDetailsForCard', [555]));
|
||||
}
|
||||
|
||||
public function testFindDetailsForAttachment() {
|
||||
$attachment = new Attachment();
|
||||
$attachment->setId(777);
|
||||
$attachment->setCardId(555);
|
||||
$card = new Card();
|
||||
$card->setId(555);
|
||||
$card->setStackId(123);
|
||||
$stack = new Stack();
|
||||
$stack->setId(123);
|
||||
$stack->setBoardId(999);
|
||||
$board = new Board();
|
||||
$board->setId(999);
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(777)
|
||||
->willReturn($attachment);
|
||||
$this->cardMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(555)
|
||||
->willReturn($card);
|
||||
$this->stackMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(123)
|
||||
->willReturn($stack);
|
||||
$this->boardMapper->expects($this->once())->method('find')
|
||||
->with(999)
|
||||
->willReturn($board);
|
||||
$this->assertEquals([
|
||||
'stack' => $stack,
|
||||
'board' => $board,
|
||||
'card' => $card,
|
||||
'attachment' => $attachment
|
||||
], $this->invokePrivate($this->activityManager, 'findDetailsForAttachment', [777]));
|
||||
}
|
||||
|
||||
public function invokePrivate(&$object, $methodName, array $parameters = array())
|
||||
{
|
||||
$reflection = new \ReflectionClass(get_class($object));
|
||||
$method = $reflection->getMethod($methodName);
|
||||
$method->setAccessible(true);
|
||||
return $method->invokeArgs($object, $parameters);
|
||||
}
|
||||
|
||||
}
|
||||
58
tests/unit/Activity/ChangeSetTest.php
Normal file
58
tests/unit/Activity/ChangeSetTest.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Activity;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
|
||||
class ChangeSetTest extends TestCase {
|
||||
|
||||
public function setUp() {
|
||||
}
|
||||
|
||||
public function testChangeSetScalar() {
|
||||
$changeSet = new ChangeSet('A', 'B');
|
||||
$this->assertEquals('A', $changeSet->getBefore());
|
||||
$this->assertEquals('B', $changeSet->getAfter());
|
||||
$this->assertFalse($changeSet->getDiff());
|
||||
$changeSet->enableDiff();
|
||||
$this->assertTrue($changeSet->getDiff());
|
||||
}
|
||||
|
||||
public function testChangeSetObject() {
|
||||
$a = new \stdClass;
|
||||
$a->data = 'A';
|
||||
$b = new \stdClass;
|
||||
$b->data = 'B';
|
||||
$changeSet = new ChangeSet($a, $b);
|
||||
$this->assertEquals('A', $changeSet->getBefore()->data);
|
||||
$this->assertEquals('B', $changeSet->getAfter()->data);
|
||||
$this->assertNotSame($a, $changeSet->getBefore());
|
||||
$this->assertNotSame($b, $changeSet->getAfter());
|
||||
$this->assertFalse($changeSet->getDiff());
|
||||
$changeSet->enableDiff();
|
||||
$this->assertTrue($changeSet->getDiff());
|
||||
}
|
||||
|
||||
}
|
||||
454
tests/unit/Activity/DeckProviderTest.php
Normal file
454
tests/unit/Activity/DeckProviderTest.php
Normal file
@@ -0,0 +1,454 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Activity;
|
||||
|
||||
use OC\Activity\Event;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCP\Activity\IEvent;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\RichObjectStrings\IValidator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit_Framework_MockObject_MockObject as MockObject;
|
||||
|
||||
class DeckProviderTest extends TestCase {
|
||||
|
||||
/** @var DeckProvider */
|
||||
private $provider;
|
||||
|
||||
/** @var IURLGenerator|MockObject */
|
||||
private $urlGenerator;
|
||||
|
||||
/** @var ActivityManager|MockObject */
|
||||
private $activityManager;
|
||||
|
||||
/** @var IUserManager|MockObject */
|
||||
private $userManager;
|
||||
|
||||
/** @var string */
|
||||
private $userId = 'admin';
|
||||
|
||||
public function setUp() {
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
||||
$this->activityManager = $this->createMock(ActivityManager::class);
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->provider = new DeckProvider($this->urlGenerator, $this->activityManager, $this->userManager, $this->userId);
|
||||
}
|
||||
|
||||
private function mockEvent($objectType, $objectId, $objectName, $subject, $subjectParameters = []) {
|
||||
$data = [];
|
||||
$event = $this->createMock(IEvent::class);
|
||||
$event->expects($this->any())->method('getApp')->willReturn('deck');
|
||||
$event->expects($this->any())->method('getSubject')->willReturn($subject);
|
||||
$event->expects($this->any())->method('getSubjectParameters')->willReturn($subjectParameters);
|
||||
$event->expects($this->any())->method('getObjectType')->willReturn($objectType);
|
||||
$event->expects($this->any())->method('getObjectId')->willReturn($objectId);
|
||||
$event->expects($this->any())->method('getObjectName')->willReturn($objectName);
|
||||
$event->expects($this->any())->method('getAuthor')->willReturn('admin');
|
||||
$event->expects($this->any())->method('getMessage')->willReturn('');
|
||||
$event->expects($this->any())->method('setIcon')->will($this->returnCallback(function($icon) use (&$data) {
|
||||
$data['icon'] = $icon;
|
||||
}));
|
||||
$event->expects($this->any())->method('getIcon')->will(
|
||||
$this->returnCallback(function() use (&$data) {
|
||||
return array_key_exists('icon', $data) ? $data['icon'] : 'noicon';
|
||||
})
|
||||
);
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \InvalidArgumentException
|
||||
*/
|
||||
public function testParseFailureApp() {
|
||||
$event = $this->createMock(IEvent::class);
|
||||
$event->expects($this->once())->method('getApp')->willReturn('notdeck');
|
||||
$this->provider->parse('en_US', $event, $event);
|
||||
}
|
||||
|
||||
public function dataEventIcons() {
|
||||
return [
|
||||
[ActivityManager::SUBJECT_LABEL_ASSIGN, 'deck', 'deck-dark.svg'],
|
||||
[ActivityManager::SUBJECT_CARD_CREATE, 'files', 'add-color.svg'],
|
||||
[ActivityManager::SUBJECT_CARD_UPDATE, 'files', 'change.svg'],
|
||||
[ActivityManager::SUBJECT_CARD_DELETE, 'files', 'delete-color.svg'],
|
||||
[ActivityManager::SUBJECT_CARD_UPDATE_ARCHIVE, 'deck', 'archive.svg'],
|
||||
[ActivityManager::SUBJECT_CARD_RESTORE, 'core', 'actions/history.svg'],
|
||||
[ActivityManager::SUBJECT_ATTACHMENT_UPDATE, 'core', 'places/files.svg'],
|
||||
];
|
||||
}
|
||||
/**
|
||||
* @dataProvider dataEventIcons
|
||||
* @param $subject
|
||||
* @param $icon
|
||||
*/
|
||||
public function testEventIcons($subject, $app, $icon) {
|
||||
$event = $this->mockEvent(
|
||||
ActivityManager::DECK_OBJECT_BOARD, 1, 'Board',
|
||||
$subject);
|
||||
$this->urlGenerator->expects($this->any())
|
||||
->method('imagePath')
|
||||
->will($this->returnCallback(function($a, $i) {
|
||||
return $a . '/' . $i;
|
||||
}));
|
||||
$this->provider->parse('en_US', $event);
|
||||
$this->assertEquals($app . '/' . $icon, $event->getIcon());
|
||||
}
|
||||
|
||||
public function testDeckUrl() {
|
||||
$this->urlGenerator->expects($this->once())
|
||||
->method('linkToRoute')
|
||||
->with('deck.page.index')
|
||||
->willReturn('http://localhost/index.php/apps/deck/');
|
||||
$this->assertEquals(
|
||||
'http://localhost/index.php/apps/deck/#!board/1/card/1',
|
||||
$this->provider->deckUrl('board/1/card/1')
|
||||
);
|
||||
}
|
||||
|
||||
public function testParseObjectTypeBoard() {
|
||||
$this->urlGenerator->expects($this->any())
|
||||
->method('imagePath')
|
||||
->will($this->returnCallback(function($a, $i) {
|
||||
return $a . '/' . $i;
|
||||
}));
|
||||
$this->activityManager->expects($this->once())
|
||||
->method('getActivityFormat')
|
||||
->willReturn('test string {board}');
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->expects($this->any())->method('getDisplayName')->willReturn('Administrator');
|
||||
$this->userManager->expects($this->any())
|
||||
->method('get')
|
||||
->willReturn($user);
|
||||
|
||||
$richValidator = $this->createMock(IValidator::class);
|
||||
$event = new Event($richValidator);
|
||||
|
||||
$event->setApp('deck');
|
||||
$event->setSubject(ActivityManager::SUBJECT_BOARD_CREATE);
|
||||
$event->setAffectedUser($this->userId);
|
||||
$event->setAuthor($this->userId);
|
||||
$event->setObject(ActivityManager::DECK_OBJECT_BOARD, 1, 'Board');
|
||||
|
||||
$this->provider->parse('en_US', $event);
|
||||
$data = [
|
||||
'board' => [
|
||||
'type' => 'highlight',
|
||||
'id' => 1,
|
||||
'name' => 'Board',
|
||||
'link' => '#!/board/1',
|
||||
],
|
||||
'card' => null,
|
||||
'user' => [
|
||||
'type' => 'user',
|
||||
'id' => 'admin',
|
||||
'name' => 'Administrator',
|
||||
]
|
||||
];
|
||||
$this->assertEquals($data, $event->getRichSubjectParameters());
|
||||
}
|
||||
|
||||
public function testParseObjectTypeCard() {
|
||||
$this->urlGenerator->expects($this->any())
|
||||
->method('imagePath')
|
||||
->will($this->returnCallback(function($a, $i) {
|
||||
return $a . '/' . $i;
|
||||
}));
|
||||
$this->activityManager->expects($this->once())
|
||||
->method('getActivityFormat')
|
||||
->willReturn('test string {board}');
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->expects($this->any())->method('getDisplayName')->willReturn('Administrator');
|
||||
$this->userManager->expects($this->any())
|
||||
->method('get')
|
||||
->willReturn($user);
|
||||
|
||||
$richValidator = $this->createMock(IValidator::class);
|
||||
$event = new Event($richValidator);
|
||||
|
||||
$event->setApp('deck');
|
||||
$event->setSubject(ActivityManager::SUBJECT_CARD_CREATE);
|
||||
$event->setAffectedUser($this->userId);
|
||||
$event->setAuthor($this->userId);
|
||||
$event->setObject(ActivityManager::DECK_OBJECT_CARD, 1, 'Card');
|
||||
|
||||
$this->provider->parse('en_US', $event);
|
||||
$data = [
|
||||
'board' => null,
|
||||
'card' => [
|
||||
'type' => 'highlight',
|
||||
'id' => 1,
|
||||
'name' => 'Card',
|
||||
],
|
||||
'user' => [
|
||||
'type' => 'user',
|
||||
'id' => 'admin',
|
||||
'name' => 'Administrator',
|
||||
]
|
||||
];
|
||||
$this->assertEquals($data, $event->getRichSubjectParameters());
|
||||
$this->assertEquals('test string {board}', $event->getParsedSubject());
|
||||
$this->assertEquals('test string {board}', $event->getRichSubject());
|
||||
$this->assertEquals('', $event->getMessage());
|
||||
|
||||
}
|
||||
|
||||
public function testParseObjectTypeCardWithDiff() {
|
||||
$this->urlGenerator->expects($this->any())
|
||||
->method('imagePath')
|
||||
->will($this->returnCallback(function($a, $i) {
|
||||
return $a . '/' . $i;
|
||||
}));
|
||||
$this->activityManager->expects($this->once())
|
||||
->method('getActivityFormat')
|
||||
->willReturn('test string {board}');
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->expects($this->any())->method('getDisplayName')->willReturn('Administrator');
|
||||
$this->userManager->expects($this->any())
|
||||
->method('get')
|
||||
->willReturn($user);
|
||||
|
||||
$richValidator = $this->createMock(IValidator::class);
|
||||
$event = new Event($richValidator);
|
||||
|
||||
$event->setApp('deck');
|
||||
$event->setSubject(ActivityManager::SUBJECT_CARD_UPDATE_DESCRIPTION, [
|
||||
'before' => 'ABC',
|
||||
'after' => 'BCD',
|
||||
'diff' => true,
|
||||
]);
|
||||
$event->setAffectedUser($this->userId);
|
||||
$event->setAuthor($this->userId);
|
||||
$event->setObject(ActivityManager::DECK_OBJECT_CARD, 1, 'Card');
|
||||
$event->setMessage('BCD');
|
||||
|
||||
$this->provider->parse('en_US', $event);
|
||||
$data = [
|
||||
'board' => null,
|
||||
'card' => [
|
||||
'type' => 'highlight',
|
||||
'id' => 1,
|
||||
'name' => 'Card',
|
||||
],
|
||||
'user' => [
|
||||
'type' => 'user',
|
||||
'id' => 'admin',
|
||||
'name' => 'Administrator',
|
||||
],
|
||||
];
|
||||
$this->assertEquals($data, $event->getRichSubjectParameters());
|
||||
$this->assertEquals('test string {board}', $event->getParsedSubject());
|
||||
$this->assertEquals('test string {board}', $event->getRichSubject());
|
||||
$this->assertEquals('BCD', $event->getMessage());
|
||||
$this->assertEquals('<pre class="visualdiff"><del>A</del>BC<ins>D</ins></pre>', $event->getParsedMessage());
|
||||
}
|
||||
|
||||
public function testParseParamForBoard() {
|
||||
$params = [];
|
||||
$subjectParams = [
|
||||
'board' => [
|
||||
'id' => 1,
|
||||
'title' => 'Board name',
|
||||
],
|
||||
];
|
||||
$expected = [
|
||||
'board' => [
|
||||
'type' => 'highlight',
|
||||
'id' => 1,
|
||||
'name' => 'Board name',
|
||||
'link' => '#!/board/1/',
|
||||
],
|
||||
];
|
||||
$actual = $this->invokePrivate($this->provider, 'parseParamForBoard', ['board', $subjectParams, $params]);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testParseParamForStack() {
|
||||
$params = [];
|
||||
$subjectParams = [
|
||||
'stack' => [
|
||||
'id' => 1,
|
||||
'title' => 'Stack name',
|
||||
],
|
||||
];
|
||||
$expected = [
|
||||
'stack' => [
|
||||
'type' => 'highlight',
|
||||
'id' => 1,
|
||||
'name' => 'Stack name',
|
||||
],
|
||||
];
|
||||
$actual = $this->invokePrivate($this->provider, 'parseParamForStack', ['stack', $subjectParams, $params]);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testParseParamForAttachment() {
|
||||
$this->urlGenerator->expects($this->once())
|
||||
->method('linkToRoute')
|
||||
->willReturn('/link/to/attachment');
|
||||
$params = [];
|
||||
$subjectParams = [
|
||||
'attachment' => [
|
||||
'id' => 1,
|
||||
'data' => 'File name',
|
||||
],
|
||||
'card' => [
|
||||
'id' => 1,
|
||||
]
|
||||
];
|
||||
$expected = [
|
||||
'attachment' => [
|
||||
'type' => 'highlight',
|
||||
'id' => 1,
|
||||
'name' => 'File name',
|
||||
'link' => '/link/to/attachment',
|
||||
],
|
||||
];
|
||||
$actual = $this->invokePrivate($this->provider, 'parseParamForAttachment', ['attachment', $subjectParams, $params]);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testParseParamForAssignedUser() {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->expects($this->once())
|
||||
->method('getDisplayName')
|
||||
->willReturn('User 1');
|
||||
$this->userManager->expects($this->once())
|
||||
->method('get')
|
||||
->willReturn($user);
|
||||
$params = [];
|
||||
$subjectParams = [
|
||||
'assigneduser' => 'user1',
|
||||
];
|
||||
$expected = [
|
||||
'assigneduser' => [
|
||||
'type' => 'user',
|
||||
'id' => 'user1',
|
||||
'name' => 'User 1'
|
||||
],
|
||||
];
|
||||
$actual = $this->invokePrivate($this->provider, 'parseParamForAssignedUser', [$subjectParams, $params]);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testParseParamForLabel() {
|
||||
$params = [];
|
||||
$subjectParams = [
|
||||
'label' => [
|
||||
'id' => 1,
|
||||
'title' => 'Label title',
|
||||
],
|
||||
];
|
||||
$expected = [
|
||||
'label' => [
|
||||
'type' => 'highlight',
|
||||
'id' => 1,
|
||||
'name' => 'Label title'
|
||||
],
|
||||
];
|
||||
$actual = $this->invokePrivate($this->provider, 'parseParamForLabel', [$subjectParams, $params]);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testParseParamForAclUser() {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->expects($this->once())
|
||||
->method('getDisplayName')
|
||||
->willReturn('User 1');
|
||||
$this->userManager->expects($this->once())
|
||||
->method('get')
|
||||
->willReturn($user);
|
||||
$params = [];
|
||||
$subjectParams = [
|
||||
'acl' => [
|
||||
'id' => 1,
|
||||
'type' => Acl::PERMISSION_TYPE_USER,
|
||||
'participant' => 'user1'
|
||||
],
|
||||
];
|
||||
$expected = [
|
||||
'acl' => [
|
||||
'type' => 'user',
|
||||
'id' => 'user1',
|
||||
'name' => 'User 1'
|
||||
],
|
||||
];
|
||||
$actual = $this->invokePrivate($this->provider, 'parseParamForAcl', [$subjectParams, $params]);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testParseParamForAclGroup() {
|
||||
$params = [];
|
||||
$subjectParams = [
|
||||
'acl' => [
|
||||
'id' => 1,
|
||||
'type' => Acl::PERMISSION_TYPE_GROUP,
|
||||
'participant' => 'group'
|
||||
],
|
||||
];
|
||||
$expected = [
|
||||
'acl' => [
|
||||
'type' => 'highlight',
|
||||
'id' => 'group',
|
||||
'name' => 'group'
|
||||
],
|
||||
];
|
||||
$actual = $this->invokePrivate($this->provider, 'parseParamForAcl', [$subjectParams, $params]);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testParseParamForChanges() {
|
||||
$event = $this->createMock(IEvent::class);
|
||||
$params = [];
|
||||
$subjectParams = [
|
||||
'before' => 'ABC',
|
||||
'after' => 'BCD'
|
||||
];
|
||||
$expected = [
|
||||
'before' => [
|
||||
'type' => 'highlight',
|
||||
'id' => 'ABC',
|
||||
'name' => 'ABC'
|
||||
],
|
||||
'after' => [
|
||||
'type' => 'highlight',
|
||||
'id' => 'BCD',
|
||||
'name' => 'BCD'
|
||||
],
|
||||
];
|
||||
$actual = $this->invokePrivate($this->provider, 'parseParamForChanges', [$subjectParams, $params, $event]);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function invokePrivate(&$object, $methodName, array $parameters = array())
|
||||
{
|
||||
$reflection = new \ReflectionClass(get_class($object));
|
||||
$method = $reflection->getMethod($methodName);
|
||||
$method->setAccessible(true);
|
||||
return $method->invokeArgs($object, $parameters);
|
||||
}
|
||||
}
|
||||
81
tests/unit/Activity/FilterTest.php
Normal file
81
tests/unit/Activity/FilterTest.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Activity;
|
||||
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit_Framework_MockObject_MockObject as MockObject;
|
||||
|
||||
class FilterTest extends TestCase {
|
||||
|
||||
/** @var Filter */
|
||||
private $filter;
|
||||
|
||||
/** @var IL10N|MockObject */
|
||||
private $l10n;
|
||||
|
||||
/** @var IURLGenerator|MockObject */
|
||||
private $urlGenerator;
|
||||
|
||||
public function setUp() {
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
||||
$this->filter = new Filter($this->l10n, $this->urlGenerator);
|
||||
}
|
||||
|
||||
public function testGetIdentifier() {
|
||||
$this->assertEquals('deck', $this->filter->getIdentifier());
|
||||
}
|
||||
|
||||
public function testGetName() {
|
||||
$this->l10n->expects($this->once())
|
||||
->method('t')
|
||||
->with('Deck')
|
||||
->willReturn('Deck');
|
||||
$this->assertEquals('Deck', $this->filter->getName());
|
||||
}
|
||||
|
||||
public function testGetPriority() {
|
||||
$this->assertEquals(90, $this->filter->getPriority());
|
||||
}
|
||||
|
||||
public function testGetIcon() {
|
||||
$this->urlGenerator->expects($this->once())
|
||||
->method('imagePath')
|
||||
->with('deck', 'deck-dark.svg')
|
||||
->willReturn('http://localhost/apps/deck/img/deck-dark.svg');
|
||||
$this->assertEquals('http://localhost/apps/deck/img/deck-dark.svg', $this->filter->getIcon());
|
||||
}
|
||||
|
||||
public function testFilterTypes() {
|
||||
$data = ['deck_board', 'deck_card'];
|
||||
$this->assertEquals($data, $this->filter->filterTypes($data));
|
||||
}
|
||||
|
||||
public function testAllowedApps() {
|
||||
$this->assertEquals(['deck'], $this->filter->allowedApps());
|
||||
}
|
||||
|
||||
}
|
||||
65
tests/unit/Activity/SettingTest.php
Normal file
65
tests/unit/Activity/SettingTest.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Activity;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class SettingTest extends TestCase {
|
||||
|
||||
/** @var Setting */
|
||||
private $setting;
|
||||
|
||||
public function setUp() {
|
||||
$this->setting = new Setting();
|
||||
}
|
||||
|
||||
public function testGetIdentifier() {
|
||||
$this->assertEquals('deck', $this->setting->getIdentifier());
|
||||
}
|
||||
|
||||
public function testGetName() {
|
||||
$this->assertEquals('Deck', $this->setting->getName());
|
||||
}
|
||||
|
||||
public function testGetPriority() {
|
||||
$this->assertEquals(90, $this->setting->getPriority());
|
||||
}
|
||||
|
||||
public function testCanChangeStream() {
|
||||
$this->assertTrue($this->setting->canChangeStream());
|
||||
}
|
||||
|
||||
public function testIsDefaultEnabledStream() {
|
||||
$this->assertTrue($this->setting->isDefaultEnabledStream());
|
||||
}
|
||||
|
||||
public function testCanChangeMail() {
|
||||
$this->assertTrue($this->setting->canChangeMail());
|
||||
}
|
||||
|
||||
public function testIsDefaultEnabledMail() {
|
||||
$this->assertFalse($this->setting->isDefaultEnabledMail());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
namespace OCA\Deck\Service;
|
||||
|
||||
|
||||
use OCA\Deck\Activity\ActivityManager;
|
||||
use OCA\Deck\AppInfo\Application;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\Attachment;
|
||||
@@ -65,6 +66,8 @@ class AttachmentServiceTest extends TestCase {
|
||||
private $attachmentService;
|
||||
/** @var MockObject */
|
||||
private $attachmentServiceImpl;
|
||||
/** @var ActivityManager */
|
||||
private $activityManager;
|
||||
private $appContainer;
|
||||
/** ICache */
|
||||
private $cache;
|
||||
@@ -85,7 +88,8 @@ class AttachmentServiceTest extends TestCase {
|
||||
$this->permissionService = $this->createMock(PermissionService::class);
|
||||
$this->application = $this->createMock(Application::class);
|
||||
$this->cacheFactory = $this->createMock(ICacheFactory::class);
|
||||
|
||||
$this->activityManager = $this->createMock(ActivityManager::class);
|
||||
|
||||
$this->cache = $this->createMock(ICache::class);
|
||||
$this->cacheFactory->expects($this->any())->method('createDistributed')->willReturn($this->cache);
|
||||
|
||||
@@ -96,7 +100,7 @@ class AttachmentServiceTest extends TestCase {
|
||||
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
|
||||
$this->attachmentService = new AttachmentService($this->attachmentMapper, $this->cardMapper, $this->permissionService, $this->application, $this->cacheFactory, $this->userId, $this->l10n);
|
||||
$this->attachmentService = new AttachmentService($this->attachmentMapper, $this->cardMapper, $this->permissionService, $this->application, $this->cacheFactory, $this->userId, $this->l10n, $this->activityManager);
|
||||
}
|
||||
|
||||
public function testRegisterAttachmentService() {
|
||||
@@ -108,7 +112,7 @@ class AttachmentServiceTest extends TestCase {
|
||||
$application->expects($this->any())
|
||||
->method('getContainer')
|
||||
->willReturn($appContainer);
|
||||
$attachmentService = new AttachmentService($this->attachmentMapper, $this->cardMapper, $this->permissionService, $application, $this->cacheFactory, $this->userId, $this->l10n);
|
||||
$attachmentService = new AttachmentService($this->attachmentMapper, $this->cardMapper, $this->permissionService, $application, $this->cacheFactory, $this->userId, $this->l10n, $this->activityManager);
|
||||
$attachmentService->registerAttachmentService('custom', MyAttachmentService::class);
|
||||
$this->assertEquals($fileServiceMock, $attachmentService->getService('deck_file'));
|
||||
$this->assertEquals(MyAttachmentService::class, get_class($attachmentService->getService('custom')));
|
||||
@@ -126,7 +130,7 @@ class AttachmentServiceTest extends TestCase {
|
||||
$application->expects($this->any())
|
||||
->method('getContainer')
|
||||
->willReturn($appContainer);
|
||||
$attachmentService = new AttachmentService($this->attachmentMapper, $this->cardMapper, $this->permissionService, $application, $this->cacheFactory, $this->userId, $this->l10n);
|
||||
$attachmentService = new AttachmentService($this->attachmentMapper, $this->cardMapper, $this->permissionService, $application, $this->cacheFactory, $this->userId, $this->l10n, $this->activityManager);
|
||||
$attachmentService->registerAttachmentService('custom', MyAttachmentService::class);
|
||||
$attachmentService->getService('deck_file_invalid');
|
||||
}
|
||||
@@ -367,4 +371,4 @@ class AttachmentServiceTest extends TestCase {
|
||||
$actual = $this->attachmentService->restore(123, 1);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
namespace OCA\Deck\Service;
|
||||
|
||||
use OC\L10N\L10N;
|
||||
use OCA\Deck\Activity\ActivityManager;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\AclMapper;
|
||||
use OCA\Deck\Db\AssignedUsers;
|
||||
@@ -59,8 +60,10 @@ class BoardServiceTest extends TestCase {
|
||||
private $userManager;
|
||||
/** @var IUserManager */
|
||||
private $groupManager;
|
||||
/** @var ActivityManager */
|
||||
private $activityManager;
|
||||
|
||||
private $userId = 'admin';
|
||||
private $userId = 'admin';
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
@@ -72,7 +75,8 @@ class BoardServiceTest extends TestCase {
|
||||
$this->notificationHelper = $this->createMock(NotificationHelper::class);
|
||||
$this->assignedUsersMapper = $this->createMock(AssignedUsersMapper::class);
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->groupManager = $this->createMock(IGroupManager::class);
|
||||
$this->groupManager = $this->createMock(IGroupManager::class);
|
||||
$this->activityManager = $this->createMock(ActivityManager::class);
|
||||
|
||||
$this->service = new BoardService(
|
||||
$this->boardMapper,
|
||||
@@ -84,11 +88,12 @@ class BoardServiceTest extends TestCase {
|
||||
$this->assignedUsersMapper,
|
||||
$this->userManager,
|
||||
$this->groupManager,
|
||||
$this->activityManager,
|
||||
$this->userId
|
||||
);
|
||||
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('admin');
|
||||
$user->method('getUID')->willReturn('admin');
|
||||
}
|
||||
|
||||
public function testFindAll() {
|
||||
@@ -186,7 +191,12 @@ class BoardServiceTest extends TestCase {
|
||||
->willReturn([
|
||||
'admin' => 'admin',
|
||||
]);
|
||||
$this->assertEquals($board, $this->service->delete(123));
|
||||
$boardDeleted = clone $board;
|
||||
$board->setDeletedAt(1);
|
||||
$this->boardMapper->expects($this->once())
|
||||
->method('update')
|
||||
->willReturn($boardDeleted);
|
||||
$this->assertEquals($boardDeleted, $this->service->delete(123));
|
||||
}
|
||||
|
||||
public function testAddAcl() {
|
||||
@@ -268,4 +278,4 @@ class BoardServiceTest extends TestCase {
|
||||
->willReturn(true);
|
||||
$this->assertTrue($this->service->deleteAcl(123));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
namespace OCA\Deck\Service;
|
||||
|
||||
|
||||
use OCA\Deck\Activity\ActivityManager;
|
||||
use OCA\Deck\Db\AssignedUsers;
|
||||
use OCA\Deck\Db\AssignedUsersMapper;
|
||||
use OCA\Deck\Db\Card;
|
||||
@@ -34,6 +35,7 @@ use OCA\Deck\Db\LabelMapper;
|
||||
use OCA\Deck\NotFoundException;
|
||||
use OCA\Deck\Notification\NotificationHelper;
|
||||
use OCA\Deck\StatusException;
|
||||
use OCP\Activity\IEvent;
|
||||
use Test\TestCase;
|
||||
|
||||
class CardServiceTest extends TestCase {
|
||||
@@ -55,7 +57,10 @@ class CardServiceTest extends TestCase {
|
||||
/** @var LabelMapper|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $labelMapper;
|
||||
private $boardMapper;
|
||||
/** @var AttachmentService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $attachmentService;
|
||||
/** @var ActivityManager|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $activityManager;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
@@ -68,7 +73,8 @@ class CardServiceTest extends TestCase {
|
||||
$this->notificationHelper = $this->createMock(NotificationHelper::class);
|
||||
$this->assignedUsersMapper = $this->createMock(AssignedUsersMapper::class);
|
||||
$this->attachmentService = $this->createMock(AttachmentService::class);
|
||||
$this->cardService = new CardService(
|
||||
$this->activityManager = $this->createMock(ActivityManager::class);
|
||||
$this->cardService = new CardService(
|
||||
$this->cardMapper,
|
||||
$this->stackMapper,
|
||||
$this->boardMapper,
|
||||
@@ -78,10 +84,23 @@ class CardServiceTest extends TestCase {
|
||||
$this->notificationHelper,
|
||||
$this->assignedUsersMapper,
|
||||
$this->attachmentService,
|
||||
$this->activityManager,
|
||||
'user1'
|
||||
);
|
||||
}
|
||||
|
||||
public function mockActivity($type, $object, $subject) {
|
||||
// ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE
|
||||
$event = $this->createMock(IEvent::class);
|
||||
$this->activityManager->expects($this->once())
|
||||
->method('createEvent')
|
||||
->with($type, $object, $subject)
|
||||
->willReturn($event);
|
||||
$this->activityManager->expects($this->once())
|
||||
->method('sendToUsers')
|
||||
->with($event);
|
||||
}
|
||||
|
||||
public function testFind() {
|
||||
$card = new Card();
|
||||
$card->setId(1337);
|
||||
@@ -263,7 +282,7 @@ class CardServiceTest extends TestCase {
|
||||
$card = new Card();
|
||||
$card->setArchived(true);
|
||||
$this->cardMapper->expects($this->once())->method('find')->willReturn($card);
|
||||
$this->cardMapper->expects($this->never())->method('removeLabel');
|
||||
$this->cardMapper->expects($this->never())->method('removeLabel');
|
||||
$this->expectException(StatusException::class);
|
||||
$this->cardService->removeLabel(123, 999);
|
||||
}
|
||||
@@ -321,9 +340,9 @@ class CardServiceTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @expectException \OCA\Deck\NotFoundException
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public function testUnassignUserNotExisting() {
|
||||
$assignment = new AssignedUsers();
|
||||
@@ -337,7 +356,7 @@ class CardServiceTest extends TestCase {
|
||||
->with(123)
|
||||
->willReturn($assignments);
|
||||
$this->expectException(NotFoundException::class);
|
||||
$actual = $this->cardService->unassignUser(123, 'user');
|
||||
$actual = $this->cardService->unassignUser(123, 'user');
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace OCA\Deck\Service;
|
||||
|
||||
|
||||
|
||||
use OCA\Deck\Activity\ActivityManager;
|
||||
use OCA\Deck\Db\AssignedUsersMapper;
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
@@ -63,6 +64,8 @@ class StackServiceTest extends TestCase {
|
||||
private $boardService;
|
||||
/** @var CardService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $cardService;
|
||||
/** @var ActivityManager|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $activityManager;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
@@ -75,18 +78,19 @@ class StackServiceTest extends TestCase {
|
||||
$this->assignedUsersMapper = $this->createMock(AssignedUsersMapper::class);
|
||||
$this->attachmentService = $this->createMock(AttachmentService::class);
|
||||
$this->labelMapper = $this->createMock(LabelMapper::class);
|
||||
$this->activityManager = $this->createMock(ActivityManager::class);
|
||||
|
||||
$this->stackService = new StackService(
|
||||
$this->stackMapper,
|
||||
$this->boardMapper,
|
||||
$this->cardMapper,
|
||||
$this->labelMapper,
|
||||
|
||||
$this->boardMapper,
|
||||
$this->cardMapper,
|
||||
$this->labelMapper,
|
||||
$this->permissionService,
|
||||
$this->boardService,
|
||||
$this->cardService,
|
||||
$this->assignedUsersMapper,
|
||||
$this->attachmentService
|
||||
$this->attachmentService,
|
||||
$this->activityManager
|
||||
);
|
||||
}
|
||||
|
||||
@@ -176,14 +180,16 @@ class StackServiceTest extends TestCase {
|
||||
}
|
||||
|
||||
public function testDelete() {
|
||||
$this->permissionService->expects($this->once())->method('checkPermission');
|
||||
$stackToBeDeleted = new Stack();
|
||||
$stackToBeDeleted->setId(1);
|
||||
$this->stackMapper->expects($this->once())->method('find')->willReturn($stackToBeDeleted);
|
||||
$this->stackMapper->expects($this->once())->method('update');
|
||||
$this->stackService->delete(123);
|
||||
$this->assertTrue($stackToBeDeleted->getDeletedAt() <= time(), "deletedAt is in the past");
|
||||
}
|
||||
$this->permissionService->expects($this->once())->method('checkPermission');
|
||||
$stackToBeDeleted = new Stack();
|
||||
$stackToBeDeleted->setId(1);
|
||||
$this->stackMapper->expects($this->once())->method('find')->willReturn($stackToBeDeleted);
|
||||
$this->stackMapper->expects($this->once())->method('update')->willReturn($stackToBeDeleted);
|
||||
$this->stackService->delete(123);
|
||||
$this->assertTrue($stackToBeDeleted->getDeletedAt() <= time(), "deletedAt is in the past");
|
||||
$this->assertTrue($stackToBeDeleted->getDeletedAt() > 0, "deletedAt is set");
|
||||
|
||||
}
|
||||
|
||||
public function testUpdate() {
|
||||
$this->permissionService->expects($this->once())->method('checkPermission');
|
||||
|
||||
Reference in New Issue
Block a user