Initial activity log data for tasks
This commit is contained in:
parent
1c1f585d38
commit
ce63479976
10
README.md
10
README.md
@ -151,11 +151,11 @@ Because I like seeing the numbers.
|
|||||||
|
|
||||||
Language | Files | Blank | Comment | Code
|
Language | Files | Blank | Comment | Code
|
||||||
-------------|--------:|---------:|--------:|---------:
|
-------------|--------:|---------:|--------:|---------:
|
||||||
TypeScript | 61 | 853 | 38 | 3901
|
TypeScript | 61 | 855 | 38 | 3910
|
||||||
PHP | 18 | 599 | 26 | 1920
|
PHP | 19 | 624 | 27 | 1997
|
||||||
HTML | 19 | 151 | 0 | 1396
|
HTML | 19 | 151 | 0 | 1396
|
||||||
SASS | 14 | 263 | 12 | 1181
|
SASS | 14 | 263 | 12 | 1181
|
||||||
__SUM:__ | __112__ | __1866__ | __76__ | __8398__
|
__SUM:__ | __113__ | __1893__ | __77__ | __8484__
|
||||||
|
|
||||||
Command: `cloc --exclude-dir=vendor --exclude-ext=json src/`
|
Command: `cloc --exclude-dir=vendor --exclude-ext=json src/`
|
||||||
|
|
||||||
@ -164,8 +164,8 @@ Command: `cloc --exclude-dir=vendor --exclude-ext=json src/`
|
|||||||
Language | Files | Blank | Comment | Code
|
Language | Files | Blank | Comment | Code
|
||||||
-------------|-------:|---------:|--------:|---------:
|
-------------|-------:|---------:|--------:|---------:
|
||||||
JavaScript | 45 | 624 | 53 | 2478
|
JavaScript | 45 | 624 | 53 | 2478
|
||||||
PHP | 10 | 721 | 19 | 2128
|
PHP | 11 | 743 | 16 | 2205
|
||||||
__SUM:__ | __55__ | __1345__ | __72__ | __4606__
|
__SUM:__ | __56__ | __1367__ | __69__ | __4683__
|
||||||
|
|
||||||
Command: `cloc --exclude-ext=xml test/`
|
Command: `cloc --exclude-ext=xml test/`
|
||||||
|
|
||||||
|
@ -32,6 +32,17 @@ $container['errorHandler'] = function ($c) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$container['phpErrorHandler'] = function ($c) {
|
||||||
|
return function ($request, $response, $exception) use ($c) {
|
||||||
|
$c['logger']->addError('Server error', $exception->getTrace());
|
||||||
|
|
||||||
|
return $c['response']->withStatus(500)
|
||||||
|
->withHeader('Content-Type', 'application/json')
|
||||||
|
->write('{ message: "Internal Server Error", error: "' .
|
||||||
|
$exception->getMessage() . '" }');
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// Routes ending in '/' use route without '/'
|
// Routes ending in '/' use route without '/'
|
||||||
$app->add(function($request, $response, $next) {
|
$app->add(function($request, $response, $next) {
|
||||||
$uri = $request->getUri();
|
$uri = $request->getUri();
|
||||||
|
90
src/api/controllers/Activity.php
Normal file
90
src/api/controllers/Activity.php
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
use RedBeanPHP\R;
|
||||||
|
|
||||||
|
class Activity extends BaseController {
|
||||||
|
|
||||||
|
public function getActivity($request, $response, $args) {
|
||||||
|
$status = $this->secureRoute($request, $response,
|
||||||
|
SecurityLevel::BOARD_ADMIN);
|
||||||
|
if ($status !== 200) {
|
||||||
|
return $this->jsonResponse($response, $status);
|
||||||
|
}
|
||||||
|
|
||||||
|
$activity = [];
|
||||||
|
|
||||||
|
// TODO: More activity types
|
||||||
|
if ($args['type'] === 'task') {
|
||||||
|
if (!$this->checkBoardAccess($this->getBoardId((int)$args['id']),
|
||||||
|
$request)) {
|
||||||
|
return $this->jsonResponse($response, 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$activity = $this->getTaskActivity((int)$args['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->apiJson->setSuccess();
|
||||||
|
$this->apiJson->addData($activity);
|
||||||
|
|
||||||
|
return $this->jsonResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getBoardId($taskId) {
|
||||||
|
$task = R::load('task', $taskId);
|
||||||
|
$column = R::load('column', $task->column_id);
|
||||||
|
|
||||||
|
return $column->board_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sortLogs($a, $b) {
|
||||||
|
if ($a->timestamp === $b->timestamp) {
|
||||||
|
return 0; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
return $a->timestamp < $b->timestamp ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTaskActivity($taskId) {
|
||||||
|
$task = R::load('task', $taskId);
|
||||||
|
$logs = [];
|
||||||
|
$commentIds = [];
|
||||||
|
$attachmentIds = [];
|
||||||
|
|
||||||
|
foreach ($task->ownComment as $comment) {
|
||||||
|
$commentIds[] = (int)$comment->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($task->ownAttachment as $attachment) {
|
||||||
|
$attachmentIds[] = (int)$attachment->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$taskActivity = R::find('activity',
|
||||||
|
'item_type="task" AND item_id=?',
|
||||||
|
[$taskId]);
|
||||||
|
$this->addLogItems($logs, $taskActivity);
|
||||||
|
|
||||||
|
$commentActivity =
|
||||||
|
R::find('activity', 'item_type="comment" AND '.
|
||||||
|
'item_id IN(' . R::genSlots($commentIds) . ')',
|
||||||
|
$commentIds);
|
||||||
|
$this->addLogItems($logs, $commentActivity);
|
||||||
|
|
||||||
|
$attachmentActivity =
|
||||||
|
R::find('activity', 'item_type="attachment" AND '.
|
||||||
|
'item_id IN(' . R::genSlots($attachmentIds) . ')',
|
||||||
|
$attachmentIds);
|
||||||
|
$this->addLogItems($logs, $attachmentActivity);
|
||||||
|
|
||||||
|
usort($logs, array("Activity", "sortLogs"));
|
||||||
|
|
||||||
|
return $logs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addLogItems(&$logs, $items) {
|
||||||
|
foreach ($items as $logItem) {
|
||||||
|
$logs[] = (object)array('text'=>$logItem->log_text,
|
||||||
|
'timestamp'=>$logItem->timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -49,6 +49,8 @@ $app->post('/users/{id}/opts', 'Users:updateUserOptions'); // User (limited to s
|
|||||||
$app->post('/users/{id}/cols', 'Users:toggleCollapsed'); // User (limited to self)
|
$app->post('/users/{id}/cols', 'Users:toggleCollapsed'); // User (limited to self)
|
||||||
$app->delete('/users/{id}', 'Users:removeUser'); // Admin
|
$app->delete('/users/{id}', 'Users:removeUser'); // Admin
|
||||||
|
|
||||||
|
$app->get('/activity[/{type}[/{id}]]', 'Activity:getActivity'); // BoardAdmin (with board access)
|
||||||
|
|
||||||
$app->post('/login', 'Auth:login'); // Unsecured (creates JWT)
|
$app->post('/login', 'Auth:login'); // Unsecured (creates JWT)
|
||||||
$app->post('/logout', 'Auth:logout'); // Unsecured (clears JWT)
|
$app->post('/logout', 'Auth:logout'); // Unsecured (clears JWT)
|
||||||
$app->post('/authenticate', 'Auth:authenticate'); // Unsecured (checks JWT)
|
$app->post('/authenticate', 'Auth:authenticate'); // Unsecured (checks JWT)
|
||||||
|
@ -73,6 +73,12 @@ export class BoardService {
|
|||||||
.catch(this.errorHandler);
|
.catch(this.errorHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTaskActivity(taskId: number): Observable<ApiResponse> {
|
||||||
|
return this.http.get('api/activity/task/' + taskId)
|
||||||
|
.map(this.toApiResponse)
|
||||||
|
.catch(this.errorHandler);
|
||||||
|
}
|
||||||
|
|
||||||
updateComment(comment: Comment): Observable<ApiResponse> {
|
updateComment(comment: Comment): Observable<ApiResponse> {
|
||||||
return this.http.post('api/comments/' + comment.id, comment)
|
return this.http.post('api/comments/' + comment.id, comment)
|
||||||
.map(this.toApiResponse)
|
.map(this.toApiResponse)
|
||||||
|
99
test/api/controllers/ActivityTest.php
Normal file
99
test/api/controllers/ActivityTest.php
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../Mocks.php';
|
||||||
|
use RedBeanPHP\R;
|
||||||
|
|
||||||
|
class ActivityTest extends PHPUnit_Framework_TestCase {
|
||||||
|
private $activity;
|
||||||
|
|
||||||
|
public static function setupBeforeClass() {
|
||||||
|
try {
|
||||||
|
R::setup('sqlite:tests.db');
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
R::nuke();
|
||||||
|
Auth::CreateInitialAdmin(new ContainerMock());
|
||||||
|
|
||||||
|
$this->activity = new Activity(new ContainerMock());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetActivityInvalid() {
|
||||||
|
$request = new RequestMock();
|
||||||
|
$request->hasHeader = false;
|
||||||
|
|
||||||
|
$args = [];
|
||||||
|
$args['type'] = 'task';
|
||||||
|
$args['id'] = 1;
|
||||||
|
|
||||||
|
$actual = $this->activity->getActivity($request,
|
||||||
|
new ResponseMock(), $args);
|
||||||
|
$this->assertEquals('error', $actual->alerts[0]['type']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetActivityForbidden() {
|
||||||
|
$this->setupTaskActivity();
|
||||||
|
|
||||||
|
$args = [];
|
||||||
|
$args['type'] = 'task';
|
||||||
|
$args['id'] = 1;
|
||||||
|
|
||||||
|
DataMock::CreateBoardAdminUser();
|
||||||
|
|
||||||
|
$request = new RequestMock();
|
||||||
|
$request->header = [DataMock::GetJwt(2)];
|
||||||
|
|
||||||
|
$actual = $this->activity->getActivity($request,
|
||||||
|
new ResponseMock(), $args);
|
||||||
|
$this->assertEquals('Access restricted.', $actual->alerts[0]['text']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetActivityForTask() {
|
||||||
|
$this->setupTaskActivity();
|
||||||
|
|
||||||
|
$request = new RequestMock();
|
||||||
|
$request->header = [DataMock::GetJwt()];
|
||||||
|
|
||||||
|
$args = [];
|
||||||
|
$args['type'] = 'task';
|
||||||
|
$args['id'] = 1;
|
||||||
|
|
||||||
|
$actual = $this->activity->getActivity($request,
|
||||||
|
new ResponseMock(), $args);
|
||||||
|
$this->assertEquals('success', $actual->status);
|
||||||
|
$this->assertEquals(3, count($actual->data[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setupTaskActivity() {
|
||||||
|
$task = R::dispense('task');
|
||||||
|
$comment = R::dispense('comment');
|
||||||
|
$attachment = R::dispense('attachment');
|
||||||
|
$task->ownComment[] = $comment;
|
||||||
|
$task->ownAttachment[] = $attachment;
|
||||||
|
R::store($task);
|
||||||
|
|
||||||
|
|
||||||
|
$activity = R::dispense('activity');
|
||||||
|
$activity->item_type = 'task';
|
||||||
|
$activity->item_id = 1;
|
||||||
|
$activity->log_text = 'test change';
|
||||||
|
$activity->timestamp = time();
|
||||||
|
R::store($activity);
|
||||||
|
|
||||||
|
$activity = R::dispense('activity');
|
||||||
|
$activity->item_type = 'task';
|
||||||
|
$activity->item_id = 1;
|
||||||
|
$activity->log_text = 'test change';
|
||||||
|
$activity->timestamp = time();
|
||||||
|
R::store($activity);
|
||||||
|
|
||||||
|
$activity = R::dispense('activity');
|
||||||
|
$activity->item_type = 'task';
|
||||||
|
$activity->item_id = 1;
|
||||||
|
$activity->log_text = 'test change';
|
||||||
|
$activity->timestamp = time() + 10;
|
||||||
|
R::store($activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -169,9 +169,6 @@ class TasksTest extends PHPUnit_Framework_TestCase {
|
|||||||
$this->assertEquals('updated', $response->data[1][0]['title']);
|
$this->assertEquals('updated', $response->data[1][0]['title']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @group single
|
|
||||||
*/
|
|
||||||
public function testUpdateTaskWithActions() {
|
public function testUpdateTaskWithActions() {
|
||||||
$this->addActions();
|
$this->addActions();
|
||||||
$this->createTask();
|
$this->createTask();
|
||||||
|
Reference in New Issue
Block a user