Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple custom fields functionality #687

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions thehive-backend/app/services/CaseMergeSrv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,28 @@ class CaseMergeSrv @Inject()(
customFieldsObject ← caze.customFields().asOpt[JsObject]
} yield customFieldsObject

val mergedCustomFieldsObject: Seq[(String, JsValue)] = customFields.flatMap(_.keys).distinct.flatMap { key ⇒
val customFieldsValues = customFields.flatMap(cf ⇒ (cf \ key).asOpt[JsObject]).distinct
if (customFieldsValues.lengthCompare(1) != 0)
None
else
Some(key → customFieldsValues.head)
val mergedCustomFieldsObject: Seq[(String, JsValue)] = customFields.flatMap(_.keys).distinct.zip(Stream from 1).flatMap {
case (key, index) ⇒
val customFieldsValues = customFields.flatMap(cf ⇒ (cf \ key).asOpt[JsObject]).distinct

val newCustomFieldsValues: Seq[(String, JsValue)] = customFieldsValues.flatMap(_.keys).distinct.flatMap { typeKey ⇒
val attribValues = customFieldsValues.flatMap(field ⇒ (field \ typeKey).asOpt[JsValue]).flatMap(f ⇒ f match {
case f: JsArray ⇒ f.value
case _ ⇒ IndexedSeq(f)
}).distinct

typeKey match {
case "order" ⇒ Some(typeKey -> JsNumber(index))
case "boolean" ⇒
if (attribValues.lengthCompare(1) == 0) Some(typeKey -> attribValues.head)
else if (attribValues.lengthCompare(2) == 0 && attribValues(0) == JsNull) Some(typeKey -> attribValues(1))
else if (attribValues.lengthCompare(2) == 0 && attribValues(1) == JsNull) Some(typeKey -> attribValues(0))
else Some(typeKey -> JsNull)
case _ ⇒ Some(typeKey -> JsArray(attribValues))
}
}

Some(key → JsObject(newCustomFieldsValues))
}

JsObject(mergedCustomFieldsObject)
Expand Down
5 changes: 5 additions & 0 deletions ui/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,17 @@
<script src="scripts/directives/updatableBoolean.js"></script>
<script src="scripts/directives/updatableDataDropdown.js"></script>
<script src="scripts/directives/updatableDate.js"></script>
<script src="scripts/directives/updatableDateMulti.js"></script>
<script src="scripts/directives/updatableSelect.js"></script>
<script src="scripts/directives/updatableSimpleText.js"></script>
<script src="scripts/directives/updatableNumber.js"></script>
<script src="scripts/directives/updatableString.js"></script>
<script src="scripts/directives/updatableTags.js"></script>
<script src="scripts/directives/updatableText.js"></script>
<script src="scripts/directives/updatableUser.js"></script>
<script src="scripts/directives/user.js"></script>
<script src="scripts/directives/multiRowAreaFormatterString.js"></script>
<script src="scripts/directives/multiRowAreaFormatterNumber.js"></script>
<script src="scripts/directives/utils/autofocus.js"></script>
<script src="scripts/filters/ellipsis.js"></script>
<script src="scripts/filters/fang.js"></script>
Expand Down
70 changes: 63 additions & 7 deletions ui/app/scripts/controllers/admin/AdminCaseTemplatesCtrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
self.templateMetrics = [];
self.templateIndex = -1;
self.getUserInfo = UserInfoSrv;
self.MAX_INPUT_SIZE = 5; //custom size of maxium input area size

/**
* Convert the template custom fields definition to a list of ordered field names
Expand All @@ -50,12 +51,43 @@
}
}

return {
name: name,
order: definition.order,
value: fieldDef ? definition[type] : null,
type: type
};
if(type === "boolean") {
var valueName = null;
if(definition[type] === true)
valueName = 'True';
else if(definition[type] === false)
valueName = 'False';
return {
name: name,
order: definition.order,
value: fieldDef ? valueName : null,
type: type
};
}
else if(fieldDef.options.length === 0 && type === "string") {
return {
name: name,
order: definition.order,
value: fieldDef ? (definition[type] ? definition[type].join('\n') : null) : null,
type: type
};
}
else if(fieldDef.options.length === 0 && type === "number") {
return {
name: name,
order: definition.order,
value: fieldDef ? (definition[type] ? definition[type].map(String).join('\n') : null) : null,
type: type
};
}
else {
return {
name: name,
order: definition.order,
value: fieldDef ? definition[type] : null,
type: type
};
}
}),
function(item) {
return item.order;
Expand Down Expand Up @@ -290,7 +322,23 @@
}

self.template.customFields[cf.name] = {};
self.template.customFields[cf.name][fieldDef ? fieldDef.type : cf.type] = value;
if(self.fields[cf.name].options.length === 0 && self.fields[cf.name].type === "string") {
self.template.customFields[cf.name][fieldDef ? fieldDef.type : cf.type] = value ? value.split("\n") : null;
}
else if(self.fields[cf.name].options.length === 0 && self.fields[cf.name].type === "number") {
self.template.customFields[cf.name][fieldDef ? fieldDef.type : cf.type] = value ? value.split("\n").map(Number) : null;
}
else if(self.fields[cf.name].type === "boolean") {
if(value === 'True')
self.template.customFields[cf.name][fieldDef ? fieldDef.type : cf.type] = true;
else if(value === 'False')
self.template.customFields[cf.name][fieldDef ? fieldDef.type : cf.type] = false;
else if(value === null)
self.template.customFields[cf.name][fieldDef ? fieldDef.type : cf.type] = null;
}
else {
self.template.customFields[cf.name][fieldDef ? fieldDef.type : cf.type] = value;
}
self.template.customFields[cf.name].order = index + 1;
});

Expand Down Expand Up @@ -370,6 +418,14 @@
}
});
};

self.setInputSize = function(cf) {
if(self.fields[cf.name].options.length <= self.MAX_INPUT_SIZE)
return self.fields[cf.name].options.length;
else
return self.MAX_INPUT_SIZE;
};

})
.controller('AdminCaseTemplateTasksCtrl', function($scope, $uibModalInstance, action, task, users, groups) {
$scope.task = task || {};
Expand Down
16 changes: 16 additions & 0 deletions ui/app/scripts/directives/multiRowAreaFormatterNumber.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
(function() {
angular.module('theHiveDirectives')
.directive('multiRowAreaFormatterNumber', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, controller) {
controller.$formatters.push(function(value) {
return value ? value.map(String).join('\n') : null;
});
controller.$parsers.push(function(value) {
return value ? value.split('\n').map(Number) : [null];
});
}
};
});
})();
16 changes: 16 additions & 0 deletions ui/app/scripts/directives/multiRowAreaFormatterString.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
(function() {
angular.module('theHiveDirectives')
.directive('multiRowAreaFormatterString', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, controller) {
controller.$formatters.push(function(value) {
return value ? value.join('\n') : null;
});
controller.$parsers.push(function(value) {
return value ? value.split('\n') : [null];
});
}
};
});
})();
60 changes: 60 additions & 0 deletions ui/app/scripts/directives/updatableDateMulti.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
(function() {
'use strict';
angular.module('theHiveDirectives')
.directive('updatableDateMulti', function($interval, UtilsSrv) {
function updateTime(scope) {
if (scope.dateNow) {
var now = moment();
// now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
scope.humanDate = now.format('DD-MM-YYYY HH:mm');
if (!angular.isDefined(scope.timeUpdater)) {
scope.timeUpdater = $interval(function() {
updateTime(scope);
}, 60000);
}
} else if (angular.isDefined(scope.timeUpdater)) {
$interval.cancel(scope.timeUpdater);
scope.timeUpdater = undefined;
}
}

return {
'restrict': 'E',
'link': function(scope, element, attrs, ctrl, transclude) {
UtilsSrv.updatableLink(scope, element, attrs, ctrl, transclude);
$(element).find('.input-datetime').datetimepicker({
format: 'dd-mm-yyyy hh:ii',
weekStart: 1,
startView: 1,
todayBtn: true,
autoclose: true
});
scope.dateNow = false;
scope.timeUpdater = undefined;
if (angular.isNumber(scope.value)) {
var m = moment(scope.value);
if (m.isValid()) {
scope.humanDate = m.format('DD-MM-YYYY HH:mm');
}
}
scope.$watch('dateNow', function() {
updateTime(scope);
});
scope.$watch('humanDate', function() {
if (angular.isString(scope.humanDate)) {
var m = moment(scope.humanDate, 'DD-MM-YYYY HH:mm');
if (m.isValid()) {
scope.value = m.valueOf();
}
}
});
},
'templateUrl': 'views/directives/updatable-date-multi.html',
'scope': {
'value': '=?',
'onUpdate': '&',
'active': '=?'
}
};
});
})();
17 changes: 17 additions & 0 deletions ui/app/scripts/directives/updatableNumber.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
(function() {
'use strict';
angular.module('theHiveDirectives')
.directive('updatableNumber', function(UtilsSrv) {
return {
'restrict': 'E',
'link': UtilsSrv.updatableLink,
'templateUrl': 'views/directives/updatable-number.html',
'scope': {
'value': '=?',
'onUpdate': '&',
'active': '=?',
'placeholder': '@'
}
};
});
})();
17 changes: 17 additions & 0 deletions ui/app/scripts/directives/updatableString.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
(function() {
'use strict';
angular.module('theHiveDirectives')
.directive('updatableString', function(UtilsSrv) {
return {
'restrict': 'E',
'link': UtilsSrv.updatableLink,
'templateUrl': 'views/directives/updatable-string.html',
'scope': {
'value': '=?',
'onUpdate': '&',
'active': '=?',
'placeholder': '@'
}
};
});
})();
8 changes: 8 additions & 0 deletions ui/app/scripts/services/UtilsSrv.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
},

updatableLink: function(scope, element, attrs) {
scope.MAX_INPUT_SIZE = 5;

scope.updatable = {
'updating': false
};
Expand Down Expand Up @@ -101,6 +103,12 @@
scope.value = scope.oldValue;
scope.updatable.updating = false;
};
scope.setFieldSize = function(options) {
if(options.length <= scope.MAX_INPUT_SIZE)
return options.length;
else
return scope.MAX_INPUT_SIZE;
};
},

extractQueryParam: function(paramName, queryString) {
Expand Down
30 changes: 30 additions & 0 deletions ui/app/views/directives/updatable-date-multi.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<span ng-hide="updatable.updating" ng-init="active = false" ng-mouseenter="active = true" ng-mouseleave="active = false">
<span ng-if="value!==null && value !==''" style="vertical-align:top" class="updatable-value" >{{value | showDate}}</span>
<span ng-if="value===null || value ===''" style="vertical-align: top; white-space: pre-wrap" class="updatable-value text-warning"><em>Not Specified</em></span>
<span ng-if="value!==null && value !=='' && value.length > 1" style="vertical-align: top; white-space: pre-wrap" class="updatable-value text-warning"><em>Multiple dates after merging. Not supported yet.</em></span>
<small ng-show="active">
<a style="cursor: pointer;" target="_self" tooltip-popup-delay='500' uib-tooltip="edit">
<i class="glyphicon glyphicon-pencil" ng-click="edit()"></i>
</a>
</small>
<small>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</small>
</span>
<span ng-init="updatable.dropdownOpen=false" ng-show="updatable.updating">

<form ng-submit="update()">
<div class="input-group input-group-sm">
<input class="input-datetime form-control input-sm" ng-click="dateNow=false" ng-model="humanDate" type="datetime"/>
<span class="input-group-btn">
<span uib-btn-checkbox="" class="btn btn-sm btn-primary" ng-model="dateNow">
Now
</span>
<button class="btn btn-sm btn-default" type="submit">
<i class="text-success glyphicon glyphicon-ok"></i>
</button>
<button class="btn btn-sm btn-default" type="button" ng-click="cancel()">
<i class="text-danger glyphicon glyphicon-remove"></i>
</button>
</span>
</div>
</form>
</span>
25 changes: 25 additions & 0 deletions ui/app/views/directives/updatable-number.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<span ng-hide="updatable.updating" ng-init="active = false" ng-mouseenter="active = true" ng-mouseleave="active = false">
<span ng-if="value!==null && value !=='' && value.length > 0" style="vertical-align: top; white-space: pre-wrap" class="updatable-value">{{value.join("\n")}}</span>
<span ng-if="value===null || value ==='' || value.length === 0 || (value.length === 1 && value[0] === null)" style="vertical-align: top; white-space: pre-wrap" class="updatable-value text-warning"><em>Not Specified</em></span>
<small ng-show="active">
<a tooltip-popup-delay='500' uib-tooltip="edit" target="_self" style="cursor: pointer;">
<i class="glyphicon glyphicon-pencil" ng-click="edit()"></i>
</a>
</small>
<small ng-hide="active">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</small>
</span>
<span ng-show="updatable.updating" ng-init="updatable.dropdownOpen=false">
<form ng-submit="update()">
<div class="input-group input-group-sm">
<textarea ng-model="value" rows="5" placeholder="Possible values, one per line" type="text" multi-row-area-formatter-number></textarea>
<span class="input-group-btn">
<button class="btn btn-sm btn-default" type="submit">
<i class="text-success glyphicon glyphicon-ok"></i>
</button>
<button class="btn btn-sm btn-default" type="button" ng-click="cancel()">
<i class="text-danger glyphicon glyphicon-remove"></i>
</button>
</span>
</div>
</form>
</span>
7 changes: 4 additions & 3 deletions ui/app/views/directives/updatable-select.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<span ng-hide="updatable.updating" ng-init="active = false" ng-mouseenter="active = true" ng-mouseleave="active = false">
<span ng-if="value!==null && value !==''" style="vertical-align: top; white-space: pre-wrap" class="updatable-value">{{value}}</span>
<span ng-if="value === null || value === undefined || value === ''" style="vertical-align: top;" class="updatable-value text-warning"><em>Not Specified</em></span>
<span ng-if="value!==null && value !=='' && value.length > 0 && value[0]!==null && value[0]!==''" style="vertical-align: top; white-space: pre-wrap" class="updatable-value">{{value.join("\n")}}</span>
<span ng-if="value === null || value === undefined || value.length === 0 || (value.length === 1 && value[0] === null) || (value.length === 1 && value[0] === '')" style="vertical-align: top;" class="updatable-value text-warning"><em>Not Specified</em></span>
<small ng-show="active">
<a href class="clickable" tooltip-popup-delay='500' uib-tooltip="edit">
<i class="glyphicon glyphicon-pencil" ng-click="edit()"></i>
Expand All @@ -11,7 +11,8 @@
<span ng-show="updatable.updating" ng-init="updatable.dropdownOpen=false">
<form ng-submit="update()">
<div class="input-group input-group-sm">
<select class="form-control input-sm" ng-model="value" ng-options="v for v in options" ng-change="update()"></select>
<!-- <select class="form-control input-sm" ng-model="value" ng-options="v for v in options" ng-change="update()"></select> -->
<select class="form-control input-sm" ng-model="value" ng-options="v for v in options" multiple required size="{{setFieldSize(options)}}"></select>
<span class="input-group-btn">
<button class="btn btn-sm btn-default" type="submit">
<i class="text-success glyphicon glyphicon-ok"></i>
Expand Down
Loading