From 246bbc4fedc55034a2ff609008b9e55ed145b076 Mon Sep 17 00:00:00 2001 From: lukasolson Date: Thu, 2 Apr 2015 15:26:11 -0700 Subject: [PATCH 1/5] Changes to select all inside advanced object editor, initial export functionality --- .../courier/saved_object/saved_object.js | 8 ++ .../settings/sections/objects/_objects.html | 19 ++-- .../settings/sections/objects/_objects.js | 97 +++++++++++-------- .../settings/sections/objects/_view.js | 2 +- src/kibana/plugins/settings/styles/main.less | 6 ++ 5 files changed, 85 insertions(+), 47 deletions(-) diff --git a/src/kibana/components/courier/saved_object/saved_object.js b/src/kibana/components/courier/saved_object/saved_object.js index 561642ddeb93..fdb8c66559d3 100644 --- a/src/kibana/components/courier/saved_object/saved_object.js +++ b/src/kibana/components/courier/saved_object/saved_object.js @@ -265,6 +265,14 @@ define(function (require) { }); }; + self.export = function () { + return { + _id: this.id, + _type: type, + _source: this._source + }; + }; + } return SavedObject; diff --git a/src/kibana/plugins/settings/sections/objects/_objects.html b/src/kibana/plugins/settings/sections/objects/_objects.html index c6f0cbfb4e98..ce412a93ad2a 100644 --- a/src/kibana/plugins/settings/sections/objects/_objects.html +++ b/src/kibana/plugins/settings/sections/objects/_objects.html @@ -1,6 +1,10 @@ -

Edit Saved Objects

+
+

Edit Saved Objects

+ Export + Import +

From here you can delete saved objects, such as saved searches. You can also edit the raw data of saved objects. Typically objects are only modified via their associated application, which is probably what you should use instead of this screen.

@@ -16,13 +20,16 @@

Edit Saved Objects

- Delete Selected + class="delete-all btn btn-xs btn-danger" aria-label="Delete"> Delete + Export
    @@ -47,8 +54,8 @@

    Edit Saved Objects

    diff --git a/src/kibana/plugins/settings/sections/objects/_objects.js b/src/kibana/plugins/settings/sections/objects/_objects.js index b0af3a08dc1c..28d7ebbc4046 100644 --- a/src/kibana/plugins/settings/sections/objects/_objects.js +++ b/src/kibana/plugins/settings/sections/objects/_objects.js @@ -1,5 +1,7 @@ define(function (require) { var _ = require('lodash'); + var angular = require('angular'); + var saveAs = require('file_saver'); var registry = require('plugins/settings/saved_object_registry'); var objectIndexHTML = require('text!plugins/settings/sections/objects/_objects.html'); @@ -15,39 +17,39 @@ define(function (require) { controller: function ($scope, $injector, $q, AppState) { var $state = $scope.state = new AppState(); - - var resetCheckBoxes = function () { - $scope.deleteAll = false; - _.each($scope.services, function (service) { - _.each(service.data, function (item) { - item.checked = false; - }); - }); - }; + $scope.currentTab = null; + $scope.selectedItems = []; var getData = function (filter) { var services = registry.all().map(function (obj) { var service = $injector.get(obj.service); return service.find(filter).then(function (data) { - return { service: obj.service, title: obj.title, data: data.hits }; + return { service: service, serviceName: obj.service, title: obj.title, data: data.hits }; }); }); + $q.all(services).then(function (data) { $scope.services = _.sortBy(data, 'title'); - if (!$state.tab) { - $scope.changeTab($scope.services[0]); - } + $scope.changeTab($state.tab ? {title: $state.tab} : $scope.services[0]); }); }; - $scope.$watch('deleteAll', function (checked) { - var service = _.find($scope.services, { title: $state.tab }); - if (!service) return; - _.each(service.data, function (item) { - item.checked = checked; - }); - $scope.toggleDeleteBtn(service); - }); + $scope.toggleAll = function () { + if ($scope.selectedItems.length === $scope.currentTab.data.length) { + $scope.selectedItems.length = 0; + } else { + $scope.selectedItems = [].concat($scope.currentTab.data); + } + }; + + $scope.toggleItem = function (item) { + var i = $scope.selectedItems.indexOf(item); + if (i >= 0) { + $scope.selectedItems.splice(i, 1); + } else { + $scope.selectedItems.push(item); + } + }; $scope.open = function (item) { kbnUrl.change(item.url.substr(1)); @@ -55,43 +57,58 @@ define(function (require) { $scope.edit = function (service, item) { var params = { - service: service.service, + service: service.serviceName, id: item.id }; kbnUrl.change('/settings/objects/{{ service }}/{{ id }}', params); }; - $scope.toggleDeleteBtn = function (service) { - $scope.deleteAllBtn = _.some(service.data, { checked: true}); - }; - $scope.bulkDelete = function () { - var serviceObj = _.find($scope.services, { title: $state.tab }); - if (!serviceObj) return; - var service = $injector.get(serviceObj.service); - var ids = _(serviceObj.data) - .filter({ checked: true}) - .pluck('id') - .value(); - service.delete(ids).then(function (resp) { - serviceObj.data = _.filter(serviceObj.data, function (obj) { - return !obj.checked; - }); - resetCheckBoxes(); + $scope.currentTab.service.delete(_.pluck($scope.selectedItems, 'id')).then(function (resp) { + $scope.currentTab.data = _.difference($scope.currentTab.data, $scope.selectedItems); + $scope.selectedItems.length = 0; }); }; + $scope.bulkExport = function () { + var promises = $scope.selectedItems.map(getExportedItems($scope.currentTab.service)); + $q.all(promises).then(saveToFile); + }; + + $scope.exportAll = function () { + var promises = $scope.services.reduce(function (promises, service) { + return promises.concat(service.data.map(getExportedItems(service.service))); + }, []); + + $q.all(promises).then(saveToFile); + }; + + function getExportedItems(service) { + return function (item) { + return service.get(item.id).then(function (obj) { + return obj.export(); + }); + }; + } + + function saveToFile(results) { + var blob = new Blob([angular.toJson(results, true)], {type: 'application/json'}); + saveAs(blob, 'export.json'); + } + + $scope.importAll = function () {}; + $scope.changeTab = function (obj) { + $scope.currentTab = _.find($scope.services, {title: obj.title}); + $scope.selectedItems.length = 0; $state.tab = obj.title; $state.save(); - resetCheckBoxes(); }; $scope.$watch('advancedFilter', function (filter) { getData(filter); }); - } }; }); diff --git a/src/kibana/plugins/settings/sections/objects/_view.js b/src/kibana/plugins/settings/sections/objects/_view.js index 5f1e6c18fb7e..c0ba59241c16 100644 --- a/src/kibana/plugins/settings/sections/objects/_view.js +++ b/src/kibana/plugins/settings/sections/objects/_view.js @@ -29,7 +29,7 @@ define(function (require) { * * @param {array} memo The stack of fields * @param {mixed} value The value of the field - * @param {stirng} key The key of the field + * @param {string} key The key of the field * @param {object} collection This is a reference the collection being reduced * @param {array} parents The parent keys to the field * @returns {array} diff --git a/src/kibana/plugins/settings/styles/main.less b/src/kibana/plugins/settings/styles/main.less index 655235271dc1..b7ca1f7782f3 100644 --- a/src/kibana/plugins/settings/styles/main.less +++ b/src/kibana/plugins/settings/styles/main.less @@ -54,6 +54,12 @@ kbn-settings-objects { } } + .header { + .title, .controls { + padding-right: 1em; + display: inline-block; + } + } } kbn-settings-advanced { From dfa0b6cc52956757d08325464b72e2d24481d113 Mon Sep 17 00:00:00 2001 From: lukasolson Date: Tue, 7 Apr 2015 08:01:42 -0700 Subject: [PATCH 2/5] Add file upload directive --- src/kibana/directives/file_upload.js | 32 +++++++++++++++++++ .../settings/sections/objects/_objects.html | 2 +- .../settings/sections/objects/_objects.js | 6 +++- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/kibana/directives/file_upload.js diff --git a/src/kibana/directives/file_upload.js b/src/kibana/directives/file_upload.js new file mode 100644 index 000000000000..0b5fca9828df --- /dev/null +++ b/src/kibana/directives/file_upload.js @@ -0,0 +1,32 @@ +define(function (require) { + var module = require('modules').get('kibana'); + var $ = require('jquery'); + + module.directive('fileUpload', function ($parse) { + return { + restrict: 'A', + link: function ($scope, $elem, attrs) { + var onUpload = $parse(attrs.fileUpload); + + var $fileInput = $(''); + $elem.after($fileInput); + + $fileInput.on('change', function (e) { + var reader = new FileReader(); + reader.onload = function (e) { + $scope.$apply(function () { + onUpload($scope, {result: e.target.result}); + }); + }; + + var target = e.srcElement || e.target; + if (target && target.files && target.files.length) reader.readAsText(target.files[0]); + }); + + $elem.on('click', function (e) { + $fileInput.trigger('click'); + }); + } + }; + }); +}); \ No newline at end of file diff --git a/src/kibana/plugins/settings/sections/objects/_objects.html b/src/kibana/plugins/settings/sections/objects/_objects.html index ce412a93ad2a..0ae27fc3ac2e 100644 --- a/src/kibana/plugins/settings/sections/objects/_objects.html +++ b/src/kibana/plugins/settings/sections/objects/_objects.html @@ -3,7 +3,7 @@

    Edit Saved Objects

    Export - Import + Import

    From here you can delete saved objects, such as saved searches. You can also edit the raw data of saved objects. Typically objects are only modified via their associated application, which is probably what you should use instead of this screen. diff --git a/src/kibana/plugins/settings/sections/objects/_objects.js b/src/kibana/plugins/settings/sections/objects/_objects.js index 28d7ebbc4046..fec22e24d67a 100644 --- a/src/kibana/plugins/settings/sections/objects/_objects.js +++ b/src/kibana/plugins/settings/sections/objects/_objects.js @@ -5,6 +5,8 @@ define(function (require) { var registry = require('plugins/settings/saved_object_registry'); var objectIndexHTML = require('text!plugins/settings/sections/objects/_objects.html'); + require('directives/file_upload'); + require('routes') .when('/settings/objects', { template: objectIndexHTML @@ -97,7 +99,9 @@ define(function (require) { saveAs(blob, 'export.json'); } - $scope.importAll = function () {}; + $scope.importAll = function (result) { + console.log(JSON.parse(result)); + }; $scope.changeTab = function (obj) { $scope.currentTab = _.find($scope.services, {title: obj.title}); From 27f92ad667f3ac953f824e02ff3cd8fd93e134c7 Mon Sep 17 00:00:00 2001 From: lukasolson Date: Tue, 7 Apr 2015 15:22:29 -0700 Subject: [PATCH 3/5] Initial import functionality --- .../courier/saved_object/saved_object.js | 44 ++++++++++++------- .../settings/sections/objects/_objects.html | 2 +- .../settings/sections/objects/_objects.js | 29 ++++++++++-- 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/kibana/components/courier/saved_object/saved_object.js b/src/kibana/components/courier/saved_object/saved_object.js index fdb8c66559d3..e80709e213c5 100644 --- a/src/kibana/components/courier/saved_object/saved_object.js +++ b/src/kibana/components/courier/saved_object/saved_object.js @@ -119,20 +119,7 @@ define(function (require) { _.assign(self, self._source); return Promise.try(function () { - // if we have a searchSource, set it's state based on the searchSourceJSON field - if (self.searchSource) { - var state = {}; - try { - state = JSON.parse(meta.searchSourceJSON); - } catch (e) {} - - var oldState = self.searchSource.toJSON(); - var fnProps = _.transform(oldState, function (dynamic, val, name) { - if (_.isFunction(val)) dynamic[name] = val; - }, {}); - - self.searchSource.set(_.defaults(state, fnProps)); - } + parseSearchSource(meta.searchSourceJSON); }) .then(hydrateIndexPattern) .then(function () { @@ -153,6 +140,23 @@ define(function (require) { }); }); + function parseSearchSource(searchSourceJson) { + // if we have a searchSource, set its state based on the searchSourceJSON field + if (self.searchSource) { + var state = {}; + try { + state = JSON.parse(searchSourceJson); + } catch (e) {} + + var oldState = self.searchSource.toJSON(); + var fnProps = _.transform(oldState, function (dynamic, val, name) { + if (_.isFunction(val)) dynamic[name] = val; + }, {}); + + self.searchSource.set(_.defaults(state, fnProps)); + } + } + /** * After creation or fetching from ES, ensure that the searchSources index indexPattern * is an bonafide IndexPattern object. @@ -229,7 +233,7 @@ define(function (require) { return docSource.doCreate(source) .then(finish) .catch(function (err) { - var confirmMessage = 'Are you sure you want to overwrite this?'; + var confirmMessage = 'Are you sure you want to overwrite ' + self.title + '?'; if (_.deepGet(err, 'origError.status') === 409 && window.confirm(confirmMessage)) { return docSource.doIndex(source).then(finish); } @@ -273,6 +277,16 @@ define(function (require) { }; }; + self.import = function (result) { + self.id = result._id; + Object.keys(result._source).forEach(function (key) { + self[key] = result._source[key]; + }); + + parseSearchSource(self.kibanaSavedObjectMeta && self.kibanaSavedObjectMeta.searchSourceJSON); + + return self.save(); + }; } return SavedObject; diff --git a/src/kibana/plugins/settings/sections/objects/_objects.html b/src/kibana/plugins/settings/sections/objects/_objects.html index 0ae27fc3ac2e..8693cda5969f 100644 --- a/src/kibana/plugins/settings/sections/objects/_objects.html +++ b/src/kibana/plugins/settings/sections/objects/_objects.html @@ -3,7 +3,7 @@

    Edit Saved Objects

    Export - Import + Import

    From here you can delete saved objects, such as saved searches. You can also edit the raw data of saved objects. Typically objects are only modified via their associated application, which is probably what you should use instead of this screen. diff --git a/src/kibana/plugins/settings/sections/objects/_objects.js b/src/kibana/plugins/settings/sections/objects/_objects.js index fec22e24d67a..ecb556ec1498 100644 --- a/src/kibana/plugins/settings/sections/objects/_objects.js +++ b/src/kibana/plugins/settings/sections/objects/_objects.js @@ -13,10 +13,11 @@ define(function (require) { }); require('modules').get('apps/settings') - .directive('kbnSettingsObjects', function (config, Notifier, Private, kbnUrl) { + .directive('kbnSettingsObjects', function (config, Notifier, Private, kbnUrl, $route) { return { restrict: 'E', controller: function ($scope, $injector, $q, AppState) { + var notify = new Notifier({ location: 'Saved Objects' }); var $state = $scope.state = new AppState(); $scope.currentTab = null; @@ -26,7 +27,7 @@ define(function (require) { var services = registry.all().map(function (obj) { var service = $injector.get(obj.service); return service.find(filter).then(function (data) { - return { service: service, serviceName: obj.service, title: obj.title, data: data.hits }; + return { service: service, serviceName: obj.service, title: obj.title, type: service.type, data: data.hits }; }); }); @@ -100,9 +101,31 @@ define(function (require) { } $scope.importAll = function (result) { - console.log(JSON.parse(result)); + var results; + try { + results = JSON.parse(result); + } catch (e) { + return importError(); + } + + var promises = results.map(function (result) { + var service = _.find($scope.services, {type: result._type}); + if (service == null) return importError(); + + return service.service.get().then(function (obj) { + return obj.import(result); + }); + }); + + $q.all(promises).then(function () { + $route.reload(); + }); }; + function importError() { + notify.error('The file could not be processed.'); + } + $scope.changeTab = function (obj) { $scope.currentTab = _.find($scope.services, {title: obj.title}); $scope.selectedItems.length = 0; From c327ba72e0ff0883da49c99e90c74257ac6eb4ef Mon Sep 17 00:00:00 2001 From: lukasolson Date: Tue, 14 Apr 2015 13:39:08 -0700 Subject: [PATCH 4/5] Make changes as per spalger's suggestions --- .../courier/saved_object/saved_object.js | 30 +++++++++---------- .../settings/sections/objects/_objects.html | 8 ++--- .../settings/sections/objects/_objects.js | 10 ++++--- src/kibana/plugins/settings/styles/main.less | 2 +- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/kibana/components/courier/saved_object/saved_object.js b/src/kibana/components/courier/saved_object/saved_object.js index a34a03814921..bef5e3264eca 100644 --- a/src/kibana/components/courier/saved_object/saved_object.js +++ b/src/kibana/components/courier/saved_object/saved_object.js @@ -141,20 +141,20 @@ define(function (require) { }); function parseSearchSource(searchSourceJson) { + if (!self.searchSource) return; + // if we have a searchSource, set its state based on the searchSourceJSON field - if (self.searchSource) { - var state = {}; - try { - state = JSON.parse(searchSourceJson); - } catch (e) {} + var state = {}; + try { + state = JSON.parse(searchSourceJson); + } catch (e) {} - var oldState = self.searchSource.toJSON(); - var fnProps = _.transform(oldState, function (dynamic, val, name) { - if (_.isFunction(val)) dynamic[name] = val; - }, {}); + var oldState = self.searchSource.toJSON(); + var fnProps = _.transform(oldState, function (dynamic, val, name) { + if (_.isFunction(val)) dynamic[name] = val; + }, {}); - self.searchSource.set(_.defaults(state, fnProps)); - } + self.searchSource.set(_.defaults(state, fnProps)); } /** @@ -278,10 +278,10 @@ define(function (require) { }; self.import = function (result) { - self.id = result._id; - Object.keys(result._source).forEach(function (key) { - self[key] = result._source[key]; - }); + _.assign(self, { + id: result._id, + _source: result._source + }, result._source); parseSearchSource(self.kibanaSavedObjectMeta && self.kibanaSavedObjectMeta.searchSourceJSON); diff --git a/src/kibana/plugins/settings/sections/objects/_objects.html b/src/kibana/plugins/settings/sections/objects/_objects.html index a2b7ce8015d8..a696f9b2f819 100644 --- a/src/kibana/plugins/settings/sections/objects/_objects.html +++ b/src/kibana/plugins/settings/sections/objects/_objects.html @@ -2,8 +2,8 @@

    Edit Saved Objects

    - Export - Import + +

    From here you can delete saved objects, such as saved searches. You can also edit the raw data of saved objects. Typically objects are only modified via their associated application, which is probably what you should use instead of this screen. Each tab is limited to 100 results. You can use the filter to find objects not in the default list. @@ -30,10 +30,10 @@

    Edit Saved Objects

    Delete + class="btn btn-xs btn-danger" aria-label="Delete"> Delete Export + class="btn btn-xs btn-default" aria-label="Export"> Export
    diff --git a/src/kibana/plugins/settings/sections/objects/_objects.js b/src/kibana/plugins/settings/sections/objects/_objects.js index 9e75d0f82c90..f8f0553f2e8b 100644 --- a/src/kibana/plugins/settings/sections/objects/_objects.js +++ b/src/kibana/plugins/settings/sections/objects/_objects.js @@ -40,7 +40,9 @@ define(function (require) { $q.all(services).then(function (data) { $scope.services = _.sortBy(data, 'title'); - $scope.changeTab($state.tab ? {title: $state.tab} : $scope.services[0]); + var tab = $scope.services[0]; + if ($state.tab) tab = _.find($scope.services, {title: $state.tab}); + $scope.changeTab(tab); }); }; @@ -133,10 +135,10 @@ define(function (require) { notify.error('The file could not be processed.'); } - $scope.changeTab = function (obj) { - $scope.currentTab = _.find($scope.services, {title: obj.title}); + $scope.changeTab = function (tab) { + $scope.currentTab = tab; $scope.selectedItems.length = 0; - $state.tab = obj.title; + $state.tab = tab.title; $state.save(); }; diff --git a/src/kibana/plugins/settings/styles/main.less b/src/kibana/plugins/settings/styles/main.less index fc8f05c89410..338adc1fb34c 100644 --- a/src/kibana/plugins/settings/styles/main.less +++ b/src/kibana/plugins/settings/styles/main.less @@ -48,7 +48,7 @@ kbn-settings-objects { font-weight: normal; } - .delete-all { + .btn { font-size: 10px; margin-left: 20px; } From f8661221115b4b2c0deeff3cc51ff92b8060f027 Mon Sep 17 00:00:00 2001 From: lukasolson Date: Mon, 20 Apr 2015 08:02:09 -0700 Subject: [PATCH 5/5] Switch to mget/bulk requests for import, warn on all objects to be overwritten --- .../courier/saved_object/saved_object.js | 19 ---- src/kibana/directives/file_upload.js | 4 +- .../settings/sections/objects/_objects.html | 6 +- .../settings/sections/objects/_objects.js | 91 ++++++++++++------- 4 files changed, 61 insertions(+), 59 deletions(-) diff --git a/src/kibana/components/courier/saved_object/saved_object.js b/src/kibana/components/courier/saved_object/saved_object.js index bef5e3264eca..cc934b6c390f 100644 --- a/src/kibana/components/courier/saved_object/saved_object.js +++ b/src/kibana/components/courier/saved_object/saved_object.js @@ -268,25 +268,6 @@ define(function (require) { }); }); }; - - self.export = function () { - return { - _id: this.id, - _type: type, - _source: this._source - }; - }; - - self.import = function (result) { - _.assign(self, { - id: result._id, - _source: result._source - }, result._source); - - parseSearchSource(self.kibanaSavedObjectMeta && self.kibanaSavedObjectMeta.searchSourceJSON); - - return self.save(); - }; } return SavedObject; diff --git a/src/kibana/directives/file_upload.js b/src/kibana/directives/file_upload.js index 0b5fca9828df..bccdd50e2a4f 100644 --- a/src/kibana/directives/file_upload.js +++ b/src/kibana/directives/file_upload.js @@ -15,7 +15,7 @@ define(function (require) { var reader = new FileReader(); reader.onload = function (e) { $scope.$apply(function () { - onUpload($scope, {result: e.target.result}); + onUpload($scope, {fileContents: e.target.result}); }); }; @@ -29,4 +29,4 @@ define(function (require) { } }; }); -}); \ No newline at end of file +}); diff --git a/src/kibana/plugins/settings/sections/objects/_objects.html b/src/kibana/plugins/settings/sections/objects/_objects.html index a696f9b2f819..241520acb65f 100644 --- a/src/kibana/plugins/settings/sections/objects/_objects.html +++ b/src/kibana/plugins/settings/sections/objects/_objects.html @@ -3,7 +3,7 @@

    Edit Saved Objects

    - +

    From here you can delete saved objects, such as saved searches. You can also edit the raw data of saved objects. Typically objects are only modified via their associated application, which is probably what you should use instead of this screen. Each tab is limited to 100 results. You can use the filter to find objects not in the default list. @@ -24,12 +24,12 @@

    Edit Saved Objects

    Delete