Initial activity log data for tasks

This commit is contained in:
Matthew Ross 2017-10-07 12:11:56 -04:00
parent 1c1f585d38
commit ce63479976
7 changed files with 213 additions and 8 deletions

View File

@ -151,11 +151,11 @@ Because I like seeing the numbers.
Language | Files | Blank | Comment | Code
-------------|--------:|---------:|--------:|---------:
TypeScript | 61 | 853 | 38 | 3901
PHP | 18 | 599 | 26 | 1920
TypeScript | 61 | 855 | 38 | 3910
PHP | 19 | 624 | 27 | 1997
HTML | 19 | 151 | 0 | 1396
SASS | 14 | 263 | 12 | 1181
__SUM:__ | __112__ | __1866__ | __76__ | __8398__
__SUM:__ | __113__ | __1893__ | __77__ | __8484__
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
-------------|-------:|---------:|--------:|---------:
JavaScript | 45 | 624 | 53 | 2478
PHP | 10 | 721 | 19 | 2128
__SUM:__ | __55__ | __1345__ | __72__ | __4606__
PHP | 11 | 743 | 16 | 2205
__SUM:__ | __56__ | __1367__ | __69__ | __4683__
Command: `cloc --exclude-ext=xml test/`

View File

@ -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 '/'
$app->add(function($request, $response, $next) {
$uri = $request->getUri();

View 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);
}
}
}

View File

@ -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->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('/logout', 'Auth:logout'); // Unsecured (clears JWT)
$app->post('/authenticate', 'Auth:authenticate'); // Unsecured (checks JWT)

View File

@ -73,6 +73,12 @@ export class BoardService {
.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> {
return this.http.post('api/comments/' + comment.id, comment)
.map(this.toApiResponse)

View 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);
}
}

View File

@ -169,9 +169,6 @@ class TasksTest extends PHPUnit_Framework_TestCase {
$this->assertEquals('updated', $response->data[1][0]['title']);
}
/**
* @group single
*/
public function testUpdateTaskWithActions() {
$this->addActions();
$this->createTask();