-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
Import/export saved objects #3573
Changes from all commits
246bbc4
dfa0b6c
27f92ad
dbf7929
c327ba7
f866122
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = $('<input type="file" style="opacity: 0" id="testfile" />'); | ||
$elem.after($fileInput); | ||
|
||
$fileInput.on('change', function (e) { | ||
var reader = new FileReader(); | ||
reader.onload = function (e) { | ||
$scope.$apply(function () { | ||
onUpload($scope, {fileContents: 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'); | ||
}); | ||
} | ||
}; | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,12 @@ | ||
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'); | ||
|
||
require('directives/file_upload'); | ||
|
||
require('routes') | ||
.when('/settings/objects', { | ||
template: objectIndexHTML | ||
|
@@ -12,86 +16,156 @@ define(function (require) { | |
.directive('kbnSettingsObjects', function (config, Notifier, Private, kbnUrl) { | ||
return { | ||
restrict: 'E', | ||
controller: function ($scope, $injector, $q, AppState) { | ||
controller: function ($scope, $injector, $q, AppState, es) { | ||
var notify = new Notifier({ location: 'Saved Objects' }); | ||
|
||
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, total: data.total }; | ||
return { | ||
service: service, | ||
serviceName: obj.service, | ||
title: obj.title, | ||
type: service.type, | ||
data: data.hits, | ||
total: data.total | ||
}; | ||
}); | ||
}); | ||
|
||
$q.all(services).then(function (data) { | ||
$scope.services = _.sortBy(data, 'title'); | ||
if (!$state.tab) { | ||
$scope.changeTab($scope.services[0]); | ||
} | ||
var tab = $scope.services[0]; | ||
if ($state.tab) tab = _.find($scope.services, {title: $state.tab}); | ||
$scope.changeTab(tab); | ||
}); | ||
}; | ||
|
||
$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)); | ||
}; | ||
|
||
$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 () { | ||
$scope.currentTab.service.delete(_.pluck($scope.selectedItems, 'id')).then(refreshData); | ||
}; | ||
|
||
$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.bulkExport = function () { | ||
var objs = $scope.selectedItems.map(_.partialRight(_.extend, {type: $scope.currentTab.type})); | ||
retrieveAndExportDocs(objs); | ||
}; | ||
|
||
$scope.exportAll = function () { | ||
var objs = $scope.services.map(function (service) { | ||
return service.data.map(_.partialRight(_.extend, {type: service.type})); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Took me a minute to see what was going on here and on line 84. Maybe wrap this up like |
||
}); | ||
retrieveAndExportDocs(_.flatten(objs)); | ||
}; | ||
|
||
$scope.changeTab = function (obj) { | ||
$state.tab = obj.title; | ||
function retrieveAndExportDocs(objs) { | ||
es.mget({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should probably return the promise here. |
||
index: config.file.kibana_index, | ||
body: {docs: objs.map(transformToMget)} | ||
}) | ||
.then(function (response) { | ||
saveToFile(response.docs.map(_.partialRight(_.pick, '_id', '_type', '_source'))); | ||
}); | ||
} | ||
|
||
// Takes an object and returns the associated data needed for an mget API request | ||
function transformToMget(obj) { | ||
return {_id: obj.id, _type: obj.type}; | ||
} | ||
|
||
function saveToFile(results) { | ||
var blob = new Blob([angular.toJson(results, true)], {type: 'application/json'}); | ||
saveAs(blob, 'export.json'); | ||
} | ||
|
||
$scope.importAll = function (fileContents) { | ||
var docs; | ||
try { | ||
docs = JSON.parse(fileContents); | ||
} catch (e) { | ||
notify.error('The file could not be processed.'); | ||
} | ||
|
||
return es.mget({ | ||
index: config.file.kibana_index, | ||
body: {docs: docs.map(_.partialRight(_.pick, '_id', '_type'))} | ||
}) | ||
.then(function (response) { | ||
var existingDocs = _.where(response.docs, {found: true}); | ||
var confirmMessage = 'The following objects will be overwritten:\n\n'; | ||
if (existingDocs.length === 0 || window.confirm(confirmMessage + _.pluck(existingDocs, '_id').join('\n'))) { | ||
return es.bulk({ | ||
index: config.file.kibana_index, | ||
body: _.flatten(docs.map(transformToBulk)) | ||
}) | ||
.then(refreshIndex) | ||
.then(refreshData, notify.error); | ||
} | ||
}); | ||
}; | ||
|
||
// Takes a doc and returns the associated two entries for an index bulk API request | ||
function transformToBulk(doc) { | ||
return [ | ||
{index: _.pick(doc, '_id', '_type')}, | ||
doc._source | ||
]; | ||
} | ||
|
||
function refreshIndex() { | ||
return es.indices.refresh({ | ||
index: config.file.kibana_index | ||
}); | ||
} | ||
|
||
function refreshData() { | ||
return getData($scope.advancedFilter); | ||
} | ||
|
||
$scope.changeTab = function (tab) { | ||
$scope.currentTab = tab; | ||
$scope.selectedItems.length = 0; | ||
$state.tab = tab.title; | ||
$state.save(); | ||
resetCheckBoxes(); | ||
}; | ||
|
||
$scope.$watch('advancedFilter', function (filter) { | ||
getData(filter); | ||
}); | ||
|
||
} | ||
}; | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you have familiarized yourself with this code, would you mind giving some more context to what "data" is here? From what I can tell everywhere but here
data
refers tohits
, but here it looks likedata
is referring toSection
orTab
.Sidenote: Since the method is returning an object, perhaps we should give that object a definition (either in comments or a class).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right,
data
is almost always the hits that come back per registered service (i.e. dashboards, searches, visualizations).$q.all(services).then(function (data) {...
Here,
data
could better be renamed something like you've said, maybetab
orsection
(or, in other places in this same code, it's referred to asservice
, which is also confusing). It's what's being returned from the preceding function ({service: service, serviceName: ...}
).I believe that's the only place
data
is referring to something other thanhits
.