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 561642ddeb93b..fdb8c66559d39 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 c6f0cbfb4e987..ce412a93ad2a9 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 b0af3a08dc1c0..28d7ebbc40468 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 5f1e6c18fb7ee..c0ba59241c162 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 655235271dc10..b7ca1f7782f36 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 0000000000000..0b5fca9828df6 --- /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 ce412a93ad2a9..0ae27fc3ac2eb 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 28d7ebbc40468..fec22e24d67ab 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 fdb8c66559d39..e80709e213c59 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 0ae27fc3ac2eb..8693cda5969f2 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 fec22e24d67ab..ecb556ec14985 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 a34a038149213..bef5e3264eca1 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 a2b7ce8015d8b..a696f9b2f819d 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 9e75d0f82c90f..f8f0553f2e8bb 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 fc8f05c89410f..338adc1fb34ca 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 bef5e3264eca1..cc934b6c390f9 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 0b5fca9828df6..bccdd50e2a4f7 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 a696f9b2f819d..241520acb65f7 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