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
|
||||
-------------|--------:|---------:|--------:|---------:
|
||||
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/`
|
||||
|
||||
|
@ -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();
|
||||
|
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->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)
|
||||
|
@ -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)
|
||||
|
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']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group single
|
||||
*/
|
||||
public function testUpdateTaskWithActions() {
|
||||
$this->addActions();
|
||||
$this->createTask();
|
||||
|
Reference in New Issue
Block a user