Skip to content

Commit

Permalink
[Feature Request] Add task bulk actions (#1823)
Browse files Browse the repository at this point in the history
* WIP: Add task ulk actions

* #1823 Fix router order

Co-authored-by: To-om <[email protected]>
  • Loading branch information
nadouani and To-om authored Mar 12, 2021
1 parent d211849 commit 3332ea9
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 49 deletions.
79 changes: 77 additions & 2 deletions frontend/app/scripts/controllers/case/CaseTasksCtrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
$scope.taskResponders = null;
$scope.collapseOptions = {};

$scope.selection = [];
$scope.menu = {
selectAll: false
};

this.$onInit = function() {
$scope.filtering = new FilteringSrv('task', 'task.list', {
version: 'v1',
Expand Down Expand Up @@ -48,7 +53,7 @@
};

$scope.load = function() {
$scope.tasks = new PaginatedQuerySrv({
$scope.list = new PaginatedQuerySrv({
name: 'case-tasks',
root: $scope.caseId,
objectType: 'case_task',
Expand All @@ -71,11 +76,69 @@
extraData: ['shareCount', 'actionRequired'],
//extraData: ['isOwner', 'shareCount'],
onUpdate: function() {
$scope.buildTaskGroups($scope.tasks.values);
$scope.buildTaskGroups($scope.list.values);
$scope.resetSelection();
}
});
};

$scope.resetSelection = function() {
if ($scope.menu.selectAll) {
$scope.selectAll();
} else {
$scope.selection = [];
$scope.menu.selectAll = false;
$scope.updateMenu();
}
};

$scope.updateMenu = function() {
// Handle flag/unflag menu items
var temp = _.uniq(_.pluck($scope.selection, 'flag'));
$scope.menu.unflag = temp.length === 1 && temp[0] === true;
$scope.menu.flag = temp.length === 1 && temp[0] === false;

// Handle close menu item
// temp = _.uniq(_.pluck($scope.selection, 'status'));
// $scope.menu.close = temp.length === 1 && temp[0] === 'Open';
// $scope.menu.reopen = temp.length === 1 && temp[0] === 'Resolved';

// $scope.menu.delete = $scope.selection.length > 0;
};

$scope.select = function(task) {
if (task.selected) {
$scope.selection.push(task);
} else {
$scope.selection = _.reject($scope.selection, function(item) {
return item._id === task._id;
});
}
$scope.updateMenu();
};

$scope.selectAll = function() {
var selected = $scope.menu.selectAll;

_.each($scope.list.values, function(item) {
// if(SecuritySrv.checkPermissions(['manageCase'], item.extraData.permissions)) {
item.selected = selected;
// }
});

if (selected) {
$scope.selection = _.filter($scope.list.values, function(item) {
return !!item.selected;
});
} else {
$scope.selection = [];
}

$scope.updateMenu();
};

// ######################@@@

$scope.toggleStats = function () {
$scope.filtering.toggleStats();
};
Expand Down Expand Up @@ -215,6 +278,18 @@
});
};

$scope.bulkFlag = function(flag) {
var ids = _.pluck($scope.selection, '_id');

return CaseTaskSrv.bulkUpdate(ids, {flag: flag})
.then(function(/*responses*/) {
NotificationSrv.log('Selected tasks have been updated successfully', 'success');
})
.catch(function(err) {
NotificationSrv.error('Bulk flag tasks', err.data, err.status);
});
}

// open task tab with its details
$scope.startTask = function(task) {
var taskId = task._id;
Expand Down
4 changes: 4 additions & 0 deletions frontend/app/scripts/services/api/CaseTaskSrv.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
});
};

this.bulkUpdate = function(ids, update) {
return $http.patch('./api/v1/task/_bulk', _.extend({ids: ids}, update));
};

this.removeShare = function(id, share) {
return $http.delete('./api/task/'+id+'/shares', {
data: {
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/styles/case.css
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ table.case-list .case-tags .label,
font-size: 12px !important;
font-weight: normal;
}

table.data-list .btn-icon,
table.case-list .btn-icon {
padding: 6px;
padding-top: 0;
Expand Down
85 changes: 46 additions & 39 deletions frontend/app/views/partials/case/case.tasks.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<div class="col-md-12 clearfix">
<div class="pull-left">
<h4>
Tasks List ({{tasks.values.length || 0}} of {{tasks.total}})
Tasks List ({{list.values.length || 0}} of {{list.total}})
</h4>
</div>

Expand All @@ -17,7 +17,7 @@ <h4>
</div>
</div>

<div class="row mv-s" ng-show="tasks.total === 0">
<div class="row mv-s" ng-show="list.total === 0">
<div class="col-md-12">
<div class="empty-message">No task found for this case.</div>
</div>
Expand Down Expand Up @@ -52,26 +52,33 @@ <h4>
</div>
</div>

<div class="row" ng-if="!state.showGrouped && tasks.total !== 0">
<div class="row" ng-if="!state.showGrouped && list.total !== 0">
<div class="col-md-12">

<psearch control="tasks"></psearch>
<psearch control="list"></psearch>

<table class="table table-hover valigned tasks-table">
<thead>
<tr>
<th width="20px">
<input if-permission="manageTask" allowed="{{userPermissions}}" type="checkbox" ng-model="menu.selectAll" ng-change="selectAll()">
</th>
<th style="width: 20px"></th>
<th style="width: 40px"></th>
<th style="width: 250px">Group</th>
<th style="width: 150px">Group</th>
<th>Task</th>
<th style="width: 70px;"></th>
<th style="width: 150px">Date</th>
<th style="width: 150px">Assignee</th>
<th style="width: 250px" class="text-right" if-permission="manageTask" allowed="{{userPermissions}}">Actions</th>
</tr>
</thead>
<tbody ng-repeat="task in tasks.values">
<tbody ng-repeat="task in list.values">
<tr class="task-row" ng-class="{'warning': (task.status=== 'Waiting' || task.status=== 'InProgress') && task.flag == true}">
<td>
<input if-permission="manageTask" allowed="{{userPermissions}}"
type="checkbox" ng-model="task.selected" ng-change="select(task)">
</td>
<td>
<a href ng-click="collapseOptions[task._id] = !collapseOptions[task._id]" ng-show="task.description" uib-tooltip="Show/Hide description" tooltip-placement="right">
<i class="fa" ng-class="{
Expand All @@ -81,8 +88,8 @@ <h4>
</a>
</td>
<td class="task-status" align="center" ng-switch="task.status">
<i ng-switch-when="Completed" class="text-success glyphicon glyphicon-ok"></i>
<i ng-switch-when="InProgress" class="glyphicon" ng-class="{true:'text-yellow glyphicon-flag', false:'text-primary glyphicon-play'}[task.flag]"></i>
<i ng-switch-when="Completed" class="text-success glyphicon glyphicon-ok" uib-tooltip="Completed"></i>
<i ng-switch-when="InProgress" class="glyphicon" ng-class="{true:'text-yellow glyphicon-flag', false:'text-primary glyphicon-play'}[task.flag]" uib-tooltip="In Progress"></i>
<i ng-switch-when="Waiting" class="glyphicon" ng-class="{true:'text-yellow glyphicon-flag'}[task.flag]"></i>
</td>
<td ng-if="canEdit">
Expand Down Expand Up @@ -168,33 +175,36 @@ <h4>
</tr>

<tr ng-if="task.description && collapseOptions[task._id]">
<td colspan="7" class="wrap">
<td colspan="9" class="wrap">
<div marked="task.description" class="mt-xxs filter-panel markdown"></div>
</td>
</tr>
</tbody>
</table>

<psearch control="tasks"></psearch>
<psearch control="list"></psearch>
</div>
</div>

<div class="row" ng-if="state.showGrouped && tasks.total !== 0">
<div class="row" ng-if="state.showGrouped && list.total !== 0">
<div class="col-md-12">

<psearch control="tasks"></psearch>
<psearch control="list"></psearch>

<div class="panel panel-default" ng-repeat="group in groupedTasks">
<div class="panel-heading">
<strong>{{group.group || 'Not Specified'}}</strong> <small>({{group.tasks.length}} task(s))</small>
<strong>{{group.group || 'Not Specified'}}</strong> <small>({{group.list.length}} task(s))</small>
</div>
<div class="panel-body p-0">
<table class="table table-hover valigned tasks-table">
<table class="table table-hover valigned tasks-table data-list">
<thead>
<tr>
<th width="20px">
<input if-permission="manageTask" allowed="{{userPermissions}}" type="checkbox" ng-model="menu.selectAll" ng-change="selectAll()">
</th>
<th style="width: 20px"></th>
<th style="width: 40px"></th>
<th style="width: 250px">Group</th>
<th style="width: 150px">Group</th>
<th>Task</th>
<th style="width: 70px;"></th>
<th style="width: 150px">Date</th>
Expand All @@ -204,6 +214,9 @@ <h4>
</thead>
<tbody ng-repeat="task in group.tasks">
<tr class="task-row" ng-class="{'warning': (task.status=== 'Waiting' || task.status=== 'InProgress') && task.flag == true}">
<td>
<input if-permission="manageTask" allowed="{{userPermissions}}" type="checkbox" ng-model="task.selected" ng-change="select(task)">
</td>
<td>
<a href ng-click="collapseOptions[task._id] = !collapseOptions[task._id]" ng-show="task.description" uib-tooltip="Show/Hide description" tooltip-placement="right">
<i class="fa" ng-class="{
Expand All @@ -213,8 +226,8 @@ <h4>
</a>
</td>
<td class="task-status" align="center" ng-switch="task.status">
<i ng-switch-when="Completed" class="text-success glyphicon glyphicon-ok"></i>
<i ng-switch-when="InProgress" class="glyphicon" ng-class="{true:'text-yellow glyphicon-flag', false:'text-primary glyphicon-play'}[task.flag]"></i>
<i ng-switch-when="Completed" class="text-success glyphicon glyphicon-ok" uib-tooltip="Completed"></i>
<i ng-switch-when="InProgress" class="glyphicon" ng-class="{true:'text-yellow glyphicon-flag', false:'text-primary glyphicon-play'}[task.flag]" uib-tooltip="In Porgress"></i>
<i ng-switch-when="Waiting" class="glyphicon" ng-class="{true:'text-yellow glyphicon-flag'}[task.flag]"></i>
</td>
<td ng-if="canEdit">
Expand Down Expand Up @@ -270,28 +283,22 @@ <h4>
</td>
<!-- class="task-delete" -->
<td align="right" class="task-actions" if-permission="manageTask" allowed="{{userPermissions}}">
<span class="ml-xs clickable text-danger task-delete" ng-click="removeTask(task)">
<i class="fa fa-times"></i>
Delete
</span>
<span class="ml-xs clickable text-success action-button" ng-show="task.status == 'Completed'" ng-click="openTask(task)">
<i class="fa fa-check-circle"></i>
Reopen
</span>
<span class="ml-xs clickable text-muted action-button" ng-show="task.status == 'InProgress'" ng-click="closeTask(task)">
<i class="fa fa-check-circle-o"></i>
Close
</span>
<span class="ml-xs clickable text-primary action-button" ng-show="task.status == 'Waiting'" ng-click="startTask(task)">
<i class="fa fa-play"></i>
Start
</span>

<span class="ml-xs" uib-dropdown ng-if="appConfig.connectors.cortex.enabled" if-permission="manageAction" allowed="{{userPermissions}}">
<a href class="text-primary noline nowrap" ng-click="getTaskResponders(task, true)">
<i class="text-primary fa fa-cog"></i>
</a>
</span>
<a class="btn btn-icon btn-clear" ng-click="removeTask(task)" uib-tooltip="Delete">
<i class="fa fa-trash text-danger"></i>
</a>
<a class="btn btn-icon btn-clear" ng-show="task.status == 'Completed'" ng-click="openTask(task)" uib-tooltip="Reopen">
<i class="fa fa-check-circle text-success"></i>
</a>
<a class="btn btn-icon btn-clear" ng-show="task.status == 'InProgress'" ng-click="closeTask(task)" uib-tooltip="Close">
<i class="fa fa-check-circle-o text-muted"></i>
</a>
<a class="btn btn-icon btn-clear" ng-show="task.status == 'Waiting'" ng-click="startTask(task)" uib-toolip="Start">
<i class="fa fa-play text-primary"></i>
</a>
<a href class="btn btn-icon btn-clear" ng-click="getTaskResponders(task, true)" ng-if="appConfig.connectors.cortex.enabled" if-permission="manageAction" allowed="{{userPermissions}}">
<i class="text-primary fa fa-cog"></i>
</a>
</td>
</tr>
<tr ng-if="task.description && collapseOptions[task._id]">
Expand All @@ -304,7 +311,7 @@ <h4>
</div>
</div>

<psearch control="tasks"></psearch>
<psearch control="list"></psearch>

</div>
</div>
38 changes: 31 additions & 7 deletions frontend/app/views/partials/case/tasks/toolbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,31 @@
<div class="col-md-12">
<div class="btn-toolbar" role="toolbar">

<div class="btn-group" uib-dropdown if-permission="manageTask" allowed="{{userPermissions}}">
<button type="button" class="btn btn-sm btn-default" uib-dropdown-toggle ng-disabled="selection.length === 0">
<ng-pluralize count="selection.length" when="{'0': 'No tasks selected', 'one': '{} selected task', 'other': '{} selected tasks'}"></ng-pluralize>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" uib-dropdown-menu>
<li ng-if="menu.flag">
<a href ng-click="bulkFlag(true)"><i class="fa fa-flag"></i> Add flag</a>
</li>
<li ng-if="menu.unflag">
<a href ng-click="bulkFlag(false)"><i class="fa fa-flag-o"></i> Remove flag</a>
</li>
<!-- <li ng-if="$vm.menu.reopen">
<a href ng-click="$vm.bulkReopen()"><i class="fa fa-folder-open"></i> Reopen</a>
</li>
<li ng-if="$vm.menu.close">
<a href ng-click="$vm.bulkClose()"><i class="fa fa-folder"></i> Close</a>
</li>
<li ng-if="$vm.menu.delete" class="divider"></li>
<li>
<a href ng-click="$vm.bulkRemove()"><i class="fa fa-trash"></i> Delete</a>
</li> -->
</ul>
</div>

<div class="btn-group" if-permission="manageTask" allowed="{{userPermissions}}">
<button class="btn btn-sm btn-primary" ng-click="state.isNewTask = true">
<i class="glyphicon glyphicon-plus"></i>
Expand Down Expand Up @@ -36,13 +61,6 @@
</ul>
</div>

<div class="btn-group">
<button class="btn btn-sm btn-default" ng-click="toggleGroupedView()">
<i class="glyphicon glyphicon-th-list"></i>
Show {{state.showGrouped ? 'as a List' : 'Groups'}}
</button>
</div>

<div class="btn-group pull-right" role="group">
<page-sizer collection="tasks" sizes="[10, 15, 30, 100]"></page-sizer>
</div>
Expand All @@ -52,6 +70,12 @@
<i class="fa fa-search"></i> Filters
</button>
</div>
<div class="btn-group pull-right">
<button class="btn btn-sm btn-default" ng-click="toggleGroupedView()">
<i class="glyphicon glyphicon-th-list"></i>
Show {{state.showGrouped ? 'as a List' : 'Groups'}}
</button>
</div>
</div>
</div>
</div>
1 change: 1 addition & 0 deletions thehive/app/org/thp/thehive/controllers/v1/Router.scala
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class Router @Inject() (
case GET(p"/task") => taskCtrl.list
case POST(p"/task") => taskCtrl.create
case GET(p"/task/$taskId") => taskCtrl.get(taskId)
case PATCH(p"/task/_bulk") => taskCtrl.bulkUpdate
case PATCH(p"/task/$taskId") => taskCtrl.update(taskId)
case GET(p"/task/$taskId/actionRequired") => taskCtrl.isActionRequired(taskId)
case PUT(p"/task/$taskId/actionRequired/$orgaId") => taskCtrl.actionRequired(taskId, orgaId, required = true)
Expand Down
Loading

0 comments on commit 3332ea9

Please sign in to comment.