From 31e4d1b5566c89616f65c5a726093b030c6e4c98 Mon Sep 17 00:00:00 2001 From: Grzegorz Nowacki Date: Tue, 17 Jul 2018 13:00:50 +0200 Subject: [PATCH 1/2] Multiple custom fields functionality --- .../app/services/CaseMergeSrv.scala | 28 ++++++-- ui/app/index.html | 6 ++ .../admin/AdminCaseTemplatesCtrl.js | 70 +++++++++++++++++-- .../directives/multiRowAreaFormatterNumber.js | 16 +++++ .../directives/multiRowAreaFormatterString.js | 16 +++++ .../scripts/directives/updatableDateMulti.js | 60 ++++++++++++++++ ui/app/scripts/directives/updatableNumber.js | 17 +++++ ui/app/scripts/directives/updatableString.js | 17 +++++ ui/app/scripts/services/UtilsSrv.js | 8 +++ .../directives/updatable-date-multi.html | 30 ++++++++ ui/app/views/directives/updatable-number.html | 25 +++++++ ui/app/views/directives/updatable-select.html | 7 +- ui/app/views/directives/updatable-string.html | 25 +++++++ .../admin/case-template/custom-fields.html | 14 ++-- .../partials/case/details/custom.fields.html | 14 ++-- 15 files changed, 324 insertions(+), 29 deletions(-) create mode 100644 ui/app/scripts/directives/multiRowAreaFormatterNumber.js create mode 100644 ui/app/scripts/directives/multiRowAreaFormatterString.js create mode 100644 ui/app/scripts/directives/updatableDateMulti.js create mode 100644 ui/app/scripts/directives/updatableNumber.js create mode 100644 ui/app/scripts/directives/updatableString.js create mode 100644 ui/app/views/directives/updatable-date-multi.html create mode 100644 ui/app/views/directives/updatable-number.html create mode 100644 ui/app/views/directives/updatable-string.html diff --git a/thehive-backend/app/services/CaseMergeSrv.scala b/thehive-backend/app/services/CaseMergeSrv.scala index 0a7099cf76..91e3d2352f 100644 --- a/thehive-backend/app/services/CaseMergeSrv.scala +++ b/thehive-backend/app/services/CaseMergeSrv.scala @@ -123,12 +123,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) diff --git a/ui/app/index.html b/ui/app/index.html index b50b9cafe9..50bd86cc19 100644 --- a/ui/app/index.html +++ b/ui/app/index.html @@ -115,6 +115,7 @@ + @@ -210,12 +211,17 @@ + + + + + diff --git a/ui/app/scripts/controllers/admin/AdminCaseTemplatesCtrl.js b/ui/app/scripts/controllers/admin/AdminCaseTemplatesCtrl.js index 35cc1134aa..bbb8d9d6ee 100644 --- a/ui/app/scripts/controllers/admin/AdminCaseTemplatesCtrl.js +++ b/ui/app/scripts/controllers/admin/AdminCaseTemplatesCtrl.js @@ -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 @@ -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; @@ -285,7 +317,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; }); @@ -365,6 +413,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) { $scope.task = task || {}; diff --git a/ui/app/scripts/directives/multiRowAreaFormatterNumber.js b/ui/app/scripts/directives/multiRowAreaFormatterNumber.js new file mode 100644 index 0000000000..ee57e517b1 --- /dev/null +++ b/ui/app/scripts/directives/multiRowAreaFormatterNumber.js @@ -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]; + }); + } + }; + }); +})(); \ No newline at end of file diff --git a/ui/app/scripts/directives/multiRowAreaFormatterString.js b/ui/app/scripts/directives/multiRowAreaFormatterString.js new file mode 100644 index 0000000000..8ce16e1960 --- /dev/null +++ b/ui/app/scripts/directives/multiRowAreaFormatterString.js @@ -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]; + }); + } + }; + }); +})(); \ No newline at end of file diff --git a/ui/app/scripts/directives/updatableDateMulti.js b/ui/app/scripts/directives/updatableDateMulti.js new file mode 100644 index 0000000000..82dace681e --- /dev/null +++ b/ui/app/scripts/directives/updatableDateMulti.js @@ -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': '=?' + } + }; + }); +})(); diff --git a/ui/app/scripts/directives/updatableNumber.js b/ui/app/scripts/directives/updatableNumber.js new file mode 100644 index 0000000000..adfa06f496 --- /dev/null +++ b/ui/app/scripts/directives/updatableNumber.js @@ -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': '@' + } + }; + }); +})(); diff --git a/ui/app/scripts/directives/updatableString.js b/ui/app/scripts/directives/updatableString.js new file mode 100644 index 0000000000..7ae3dfaa51 --- /dev/null +++ b/ui/app/scripts/directives/updatableString.js @@ -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': '@' + } + }; + }); +})(); \ No newline at end of file diff --git a/ui/app/scripts/services/UtilsSrv.js b/ui/app/scripts/services/UtilsSrv.js index f48d53db1d..be4563ec2a 100644 --- a/ui/app/scripts/services/UtilsSrv.js +++ b/ui/app/scripts/services/UtilsSrv.js @@ -60,6 +60,8 @@ }, updatableLink: function(scope, element, attrs) { + scope.MAX_INPUT_SIZE = 5; + scope.updatable = { 'updating': false }; @@ -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) { diff --git a/ui/app/views/directives/updatable-date-multi.html b/ui/app/views/directives/updatable-date-multi.html new file mode 100644 index 0000000000..97220cc651 --- /dev/null +++ b/ui/app/views/directives/updatable-date-multi.html @@ -0,0 +1,30 @@ + + {{value | showDate}} + Not Specified + Multiple dates after merging. Not supported yet. + + + + + +       + + + +
+
+ + + + Now + + + + +
+
+
diff --git a/ui/app/views/directives/updatable-number.html b/ui/app/views/directives/updatable-number.html new file mode 100644 index 0000000000..56ded0e90c --- /dev/null +++ b/ui/app/views/directives/updatable-number.html @@ -0,0 +1,25 @@ + + {{value.join("\n")}} + Not Specified + + + + + +       + + +
+
+ + + + + +
+
+
\ No newline at end of file diff --git a/ui/app/views/directives/updatable-select.html b/ui/app/views/directives/updatable-select.html index 995118da1a..7480d56b72 100644 --- a/ui/app/views/directives/updatable-select.html +++ b/ui/app/views/directives/updatable-select.html @@ -1,6 +1,6 @@ - {{value}} - Not Specified + {{value.join("\n")}} + Not Specified @@ -11,7 +11,8 @@
+
+
\ No newline at end of file diff --git a/ui/app/views/partials/admin/case-template/custom-fields.html b/ui/app/views/partials/admin/case-template/custom-fields.html index b1a0ef0531..8bc7be2a92 100644 --- a/ui/app/views/partials/admin/case-template/custom-fields.html +++ b/ui/app/views/partials/admin/case-template/custom-fields.html @@ -23,14 +23,18 @@

- +
- - - + + + + + diff --git a/ui/app/views/partials/case/details/custom.fields.html b/ui/app/views/partials/case/details/custom.fields.html index 05bfaade34..ee49d5d03e 100644 --- a/ui/app/views/partials/case/details/custom.fields.html +++ b/ui/app/views/partials/case/details/custom.fields.html @@ -29,19 +29,17 @@

- + value="caze.customFields[fieldDef.reference][fieldDef.type]"> - + value="caze.customFields[fieldDef.reference][fieldDef.type]"> - + value="caze.customFields[fieldDef.reference][fieldDef.type]"> Date: Thu, 6 Sep 2018 12:46:49 +0200 Subject: [PATCH 2/2] Add missing frontend info --- ui/app/views/directives/updatable-select.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/views/directives/updatable-select.html b/ui/app/views/directives/updatable-select.html index 7480d56b72..66b3ea8a3c 100644 --- a/ui/app/views/directives/updatable-select.html +++ b/ui/app/views/directives/updatable-select.html @@ -1,6 +1,6 @@ - {{value.join("\n")}} - Not Specified + {{value.join("\n")}} + Not Specified