[Management] Saved objects UI should use the Saved Objects client (#19193)

* Ensure we always go through the saved objects client

* Fix a couple UI glitches

* Update these tests too

* Update snapshots
This commit is contained in:
Chris Roberson 2018-05-18 15:19:28 -04:00 committed by GitHub
parent 5edf2c0beb
commit fcec107a00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 252 additions and 425 deletions

View file

@ -42,7 +42,7 @@ function updateObjectsTable($scope, $injector) {
basePath={chrome.getBasePath()}
newIndexPatternUrl={kbnUrl.eval('#/management/kibana/index')}
getEditUrl={(id, type) => {
if (type === 'index-pattern') {
if (type === 'index-pattern' || type === 'indexPatterns') {
return kbnUrl.eval(`#/management/kibana/indices/${id}`);
}
const serviceName = typeToServiceName(type);

View file

@ -442,7 +442,7 @@ export class ObjectsTable extends Component {
const filterOptions = INCLUDED_TYPES.map(type => ({
value: type,
name: type,
view: `${type} (${savedObjectCounts[type]})`,
view: `${type} (${savedObjectCounts[type] || 0})`,
}));
return (

View file

@ -6,37 +6,6 @@ describe('findRelationships', () => {
const type = 'dashboard';
const id = 'foo';
const size = 10;
const callCluster = () => ({
docs: [
{
_id: 'visualization:1',
found: true,
_source: {
visualization: {
title: 'Foo',
},
},
},
{
_id: 'visualization:2',
found: true,
_source: {
visualization: {
title: 'Bar',
},
},
},
{
_id: 'visualization:3',
found: true,
_source: {
visualization: {
title: 'FooBar',
},
},
},
],
});
const savedObjectsClient = {
_index: '.kibana',
@ -45,12 +14,33 @@ describe('findRelationships', () => {
panelsJSON: JSON.stringify([{ id: '1' }, { id: '2' }, { id: '3' }]),
},
}),
bulkGet: () => ({
saved_objects: [
{
id: '1',
attributes: {
title: 'Foo',
},
},
{
id: '2',
attributes: {
title: 'Bar',
},
},
{
id: '3',
attributes: {
title: 'FooBar',
},
},
],
})
};
const result = await findRelationships(
type,
id,
size,
callCluster,
savedObjectsClient
);
expect(result).to.eql({
@ -66,60 +56,50 @@ describe('findRelationships', () => {
const type = 'visualization';
const id = 'foo';
const size = 10;
const callCluster = () => ({
hits: {
hits: [
{
_id: 'dashboard:1',
found: true,
_source: {
dashboard: {
title: 'My Dashboard',
panelsJSON: JSON.stringify([
{
type: 'visualization',
id,
},
{
type: 'visualization',
id: 'foobar',
},
]),
},
},
},
{
_id: 'dashboard:2',
found: true,
_source: {
dashboard: {
title: 'Your Dashboard',
panelsJSON: JSON.stringify([
{
type: 'visualization',
id,
},
{
type: 'visualization',
id: 'foobar',
},
]),
},
},
},
],
},
});
const savedObjectsClient = {
_index: '.kibana',
find: () => ({
saved_objects: [
{
id: '1',
attributes: {
title: 'My Dashboard',
panelsJSON: JSON.stringify([
{
type: 'visualization',
id,
},
{
type: 'visualization',
id: 'foobar',
},
]),
},
},
{
id: '2',
attributes: {
title: 'Your Dashboard',
panelsJSON: JSON.stringify([
{
type: 'visualization',
id,
},
{
type: 'visualization',
id: 'foobar',
},
]),
},
},
]
})
};
const result = await findRelationships(
type,
id,
size,
callCluster,
savedObjectsClient
);
expect(result).to.eql({
@ -134,46 +114,12 @@ describe('findRelationships', () => {
const type = 'search';
const id = 'foo';
const size = 10;
const callCluster = () => ({
hits: {
hits: [
{
_id: 'visualization:1',
found: true,
_source: {
visualization: {
title: 'Foo',
},
},
},
{
_id: 'visualization:2',
found: true,
_source: {
visualization: {
title: 'Bar',
},
},
},
{
_id: 'visualization:3',
found: true,
_source: {
visualization: {
title: 'FooBar',
},
},
},
],
},
});
const savedObjectsClient = {
_index: '.kibana',
get: type => {
if (type === 'search') {
return {
id: 'search:1',
id: '1',
attributes: {
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({
@ -191,13 +137,34 @@ describe('findRelationships', () => {
},
};
},
find: () => ({
saved_objects: [
{
id: '1',
attributes: {
title: 'Foo',
},
},
{
id: '2',
attributes: {
title: 'Bar',
},
},
{
id: '3',
attributes: {
title: 'FooBar',
},
},
]
})
};
const result = await findRelationships(
type,
id,
size,
callCluster,
savedObjectsClient
);
expect(result).to.eql({
@ -214,66 +181,16 @@ describe('findRelationships', () => {
const type = 'index-pattern';
const id = 'foo';
const size = 10;
const callCluster = (endpoint, options) => {
if (options._source[0] === 'visualization.title') {
return {
hits: {
hits: [
{
_id: 'visualization:1',
found: true,
_source: {
visualization: {
title: 'Foo',
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({
index: 'foo',
}),
},
},
},
},
{
_id: 'visualization:2',
found: true,
_source: {
visualization: {
title: 'Bar',
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({
index: 'foo',
}),
},
},
},
},
{
_id: 'visualization:3',
found: true,
_source: {
visualization: {
title: 'FooBar',
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({
index: 'foo2',
}),
},
},
},
},
],
},
};
}
return {
hits: {
hits: [
{
_id: 'search:1',
found: true,
_source: {
search: {
const savedObjectsClient = {
find: options => {
if (options.type === 'visualization') {
return {
saved_objects: [
{
id: '1',
found: true,
attributes: {
title: 'Foo',
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({
@ -282,12 +199,10 @@ describe('findRelationships', () => {
},
},
},
},
{
_id: 'search:2',
found: true,
_source: {
search: {
{
id: '2',
found: true,
attributes: {
title: 'Bar',
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({
@ -296,12 +211,10 @@ describe('findRelationships', () => {
},
},
},
},
{
_id: 'search:3',
found: true,
_source: {
search: {
{
id: '3',
found: true,
attributes: {
title: 'FooBar',
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({
@ -310,21 +223,54 @@ describe('findRelationships', () => {
},
},
},
},
],
},
};
};
]
};
}
const savedObjectsClient = {
_index: '.kibana',
return {
saved_objects: [
{
id: '1',
attributes: {
title: 'Foo',
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({
index: 'foo',
}),
},
},
},
{
id: '2',
attributes: {
title: 'Bar',
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({
index: 'foo',
}),
},
},
},
{
id: '3',
attributes: {
title: 'FooBar',
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({
index: 'foo2',
}),
},
},
},
]
};
}
};
const result = await findRelationships(
type,
id,
size,
callCluster,
savedObjectsClient
);
expect(result).to.eql({

View file

@ -1,9 +1,4 @@
function formatId(id) {
return id.split(':')[1];
}
async function findDashboardRelationships(id, size, callCluster, savedObjectsClient) {
const kibanaIndex = savedObjectsClient._index;
async function findDashboardRelationships(id, size, savedObjectsClient) {
const dashboard = await savedObjectsClient.get('dashboard', id);
const visualizations = [];
@ -11,22 +6,16 @@ async function findDashboardRelationships(id, size, callCluster, savedObjectsCli
const panelsJSON = JSON.parse(dashboard.attributes.panelsJSON);
if (panelsJSON) {
const visualizationIds = panelsJSON.map(panel => panel.id);
const visualizationResponse = await callCluster('mget', {
body: {
docs: visualizationIds.slice(0, size).map(id => ({
_index: kibanaIndex,
_type: 'doc',
_id: `visualization:${id}`,
_source: [`visualization.title`]
}))
}
});
const visualizationResponse = await savedObjectsClient.bulkGet(visualizationIds.slice(0, size).map(id => ({
id,
type: 'visualization',
})));
visualizations.push(...visualizationResponse.docs.reduce((accum, doc) => {
if (doc.found) {
visualizations.push(...visualizationResponse.saved_objects.reduce((accum, object) => {
if (!object.error) {
accum.push({
id: formatId(doc._id),
title: doc._source.visualization.title,
id: object.id,
title: object.attributes.title,
});
}
return accum;
@ -36,31 +25,24 @@ async function findDashboardRelationships(id, size, callCluster, savedObjectsCli
return { visualizations };
}
async function findVisualizationRelationships(id, size, callCluster, savedObjectsClient) {
const kibanaIndex = savedObjectsClient._index;
const allDashboardsResponse = await callCluster('search', {
index: kibanaIndex,
size: 10000,
ignore: [404],
_source: [`dashboard.title`, `dashboard.panelsJSON`],
body: {
query: {
term: {
type: 'dashboard'
}
}
}
async function findVisualizationRelationships(id, size, savedObjectsClient) {
const allDashboardsResponse = await savedObjectsClient.find({
type: 'dashboard',
fields: ['title', 'panelsJSON']
});
const dashboards = [];
for (const dashboard of allDashboardsResponse.hits.hits) {
const panelsJSON = JSON.parse(dashboard._source.dashboard.panelsJSON);
for (const dashboard of allDashboardsResponse.saved_objects) {
if (dashboard.error) {
continue;
}
const panelsJSON = JSON.parse(dashboard.attributes.panelsJSON);
if (panelsJSON) {
for (const panel of panelsJSON) {
if (panel.type === 'visualization' && panel.id === id) {
dashboards.push({
id: formatId(dashboard._id),
title: dashboard._source.dashboard.title,
id: dashboard.id,
title: dashboard.attributes.title,
});
}
}
@ -74,8 +56,7 @@ async function findVisualizationRelationships(id, size, callCluster, savedObject
return { dashboards };
}
async function findSavedSearchRelationships(id, size, callCluster, savedObjectsClient) {
const kibanaIndex = savedObjectsClient._index;
async function findSavedSearchRelationships(id, size, savedObjectsClient) {
const search = await savedObjectsClient.get('search', id);
const searchSourceJSON = JSON.parse(search.attributes.kibanaSavedObjectMeta.searchSourceJSON);
@ -88,93 +69,52 @@ async function findSavedSearchRelationships(id, size, callCluster, savedObjectsC
// Do nothing
}
const allVisualizationsResponse = await callCluster('search', {
index: kibanaIndex,
size,
ignore: [404],
_source: [`visualization.title`],
body: {
query: {
term: {
'visualization.savedSearchId': id,
}
}
}
const allVisualizationsResponse = await savedObjectsClient.find({
type: 'visualization',
searchFields: ['savedSearchId'],
search: id,
fields: ['title']
});
const visualizations = allVisualizationsResponse.hits.hits.map(response => ({
id: formatId(response._id),
title: response._source.visualization.title,
}));
const visualizations = allVisualizationsResponse.saved_objects.reduce((accum, object) => {
if (!object.error) {
accum.push({
id: object.id,
title: object.attributes.title,
});
}
return accum;
}, []);
return { visualizations, indexPatterns };
}
async function findIndexPatternRelationships(id, size, callCluster, savedObjectsClient) {
const kibanaIndex = savedObjectsClient._index;
async function findIndexPatternRelationships(id, size, savedObjectsClient) {
const [allVisualizationsResponse, savedSearchResponse] = await Promise.all([
callCluster('search', {
index: kibanaIndex,
size: 10000,
ignore: [404],
_source: [`visualization.title`, `visualization.kibanaSavedObjectMeta.searchSourceJSON`],
body: {
query: {
bool: {
filter: [
{
exists: {
field: 'visualization.kibanaSavedObjectMeta.searchSourceJSON',
}
},
{
term: {
type: {
value: 'visualization'
}
}
}
],
}
}
}
savedObjectsClient.find({
type: 'visualization',
searchFields: ['kibanaSavedObjectMeta.searchSourceJSON'],
search: '*',
fields: [`title`, `kibanaSavedObjectMeta.searchSourceJSON`],
}),
savedObjectsClient.find({
type: 'search',
searchFields: ['kibanaSavedObjectMeta.searchSourceJSON'],
search: '*',
fields: [`title`, `kibanaSavedObjectMeta.searchSourceJSON`],
}),
callCluster('search', {
index: kibanaIndex,
size: 10000,
ignore: [404],
_source: [`search.title`, `search.kibanaSavedObjectMeta.searchSourceJSON`],
body: {
query: {
bool: {
filter: [
{
exists: {
field: 'search.kibanaSavedObjectMeta.searchSourceJSON',
}
},
{
term: {
type: {
value: 'search'
}
}
}
]
}
}
}
})
]);
const visualizations = [];
for (const visualization of allVisualizationsResponse.hits.hits) {
const searchSourceJSON = JSON.parse(visualization._source.visualization.kibanaSavedObjectMeta.searchSourceJSON);
for (const visualization of allVisualizationsResponse.saved_objects) {
if (visualization.error) {
continue;
}
const searchSourceJSON = JSON.parse(visualization.attributes.kibanaSavedObjectMeta.searchSourceJSON);
if (searchSourceJSON && searchSourceJSON.index === id) {
visualizations.push({
id: formatId(visualization._id),
title: visualization._source.visualization.title,
id: visualization.id,
title: visualization.attributes.title,
});
}
@ -184,12 +124,15 @@ async function findIndexPatternRelationships(id, size, callCluster, savedObjects
}
const searches = [];
for (const search of savedSearchResponse.hits.hits) {
const searchSourceJSON = JSON.parse(search._source.search.kibanaSavedObjectMeta.searchSourceJSON);
for (const search of savedSearchResponse.saved_objects) {
if (search.error) {
continue;
}
const searchSourceJSON = JSON.parse(search.attributes.kibanaSavedObjectMeta.searchSourceJSON);
if (searchSourceJSON && searchSourceJSON.index === id) {
searches.push({
id: formatId(search._id),
title: search._source.search.title,
id: search.id,
title: search.attributes.title,
});
}
@ -201,16 +144,16 @@ async function findIndexPatternRelationships(id, size, callCluster, savedObjects
return { visualizations, searches };
}
export async function findRelationships(type, id, size, callCluster, savedObjectsClient) {
export async function findRelationships(type, id, size, savedObjectsClient) {
switch (type) {
case 'dashboard':
return await findDashboardRelationships(id, size, callCluster, savedObjectsClient);
return await findDashboardRelationships(id, size, savedObjectsClient);
case 'visualization':
return await findVisualizationRelationships(id, size, callCluster, savedObjectsClient);
return await findVisualizationRelationships(id, size, savedObjectsClient);
case 'search':
return await findSavedSearchRelationships(id, size, callCluster, savedObjectsClient);
return await findSavedSearchRelationships(id, size, savedObjectsClient);
case 'index-pattern':
return await findIndexPatternRelationships(id, size, callCluster, savedObjectsClient);
return await findIndexPatternRelationships(id, size, savedObjectsClient);
}
return {};
}

View file

@ -1,6 +1,5 @@
import Boom from 'boom';
import Joi from 'joi';
import _ from 'lodash';
import { findRelationships } from '../../../../lib/management/saved_objects/relationships';
export function registerRelationships(server) {
@ -20,9 +19,6 @@ export function registerRelationships(server) {
},
handler: async (req, reply) => {
const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');
const boundCallWithRequest = _.partial(callWithRequest, req);
const type = req.params.type;
const id = req.params.id;
const size = req.query.size || 10;
@ -32,7 +28,6 @@ export function registerRelationships(server) {
type,
id,
size,
boundCallWithRequest,
req.getSavedObjectsClient(),
);

View file

@ -1,17 +1,17 @@
import Boom from 'boom';
import Joi from 'joi';
import _ from 'lodash';
// import { findRelationships } from '../../../../lib/management/saved_objects/relationships';
async function fetchUntilDone(callCluster, response, results) {
results.push(...response.hits.hits);
if (response.hits.total > results.length) {
const nextResponse = await callCluster('scroll', {
scrollId: response._scroll_id,
scroll: '30s',
});
await fetchUntilDone(callCluster, nextResponse, results);
async function findAll(savedObjectsClient, findOptions, page = 1, allObjects = []) {
const objects = await savedObjectsClient.find({
...findOptions,
page
});
allObjects.push(...objects.saved_objects);
if (allObjects.length < objects.total) {
return findAll(savedObjectsClient, findOptions, page + 1, allObjects);
}
return allObjects;
}
export function registerScrollForExportRoute(server) {
@ -27,54 +27,24 @@ export function registerScrollForExportRoute(server) {
},
handler: async (req, reply) => {
const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin');
const callCluster = _.partial(callWithRequest, req);
const results = [];
const body = {
query: {
bool: {
should: req.payload.typesToInclude.map(type => ({
term: {
type: {
value: type,
}
}
})),
const savedObjectsClient = req.getSavedObjectsClient();
const objects = await findAll(savedObjectsClient, {
perPage: 1000,
typesToInclude: req.payload.typesToInclude
});
const response = objects.map(hit => {
const type = hit.type;
return {
_id: hit.id,
_type: type,
_source: hit.attributes,
_meta: {
savedObjectVersion: 2
}
}
};
};
});
try {
await fetchUntilDone(callCluster, await callCluster('search', {
index: server.config().get('kibana.index'),
scroll: '30s',
body,
}), results);
const response = results.map(hit => {
const type = hit._source.type;
if (hit._type === 'doc') {
return {
_id: hit._id.replace(`${type}:`, ''),
_type: type,
_source: hit._source[type],
_meta: {
savedObjectVersion: 2
}
};
}
return {
_id: hit._id,
_type: hit._type,
_source: hit._source,
};
});
reply(response);
}
catch (err) {
reply(Boom.boomify(err, { statusCode: 500 }));
}
reply(response);
}
});
}
@ -93,59 +63,32 @@ export function registerScrollForCountRoute(server) {
},
handler: async (req, reply) => {
const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin');
const callCluster = _.partial(callWithRequest, req);
const results = [];
const body = {
_source: 'type',
query: {
bool: {
should: req.payload.typesToInclude.map(type => ({
term: {
type: {
value: type,
}
}
})),
}
}
const savedObjectsClient = req.getSavedObjectsClient();
const findOptions = {
includeTypes: req.payload.typesToInclude,
perPage: 1000,
};
if (req.payload.searchString) {
body.query.bool.must = {
simple_query_string: {
query: `${req.payload.searchString}*`,
fields: req.payload.typesToInclude.map(type => `${type}.title`),
}
};
findOptions.search = `${req.payload.searchString}*`;
findOptions.searchFields = ['title'];
}
try {
await fetchUntilDone(callCluster, await callCluster('search', {
index: server.config().get('kibana.index'),
scroll: '30s',
body,
}), results);
const objects = await findAll(savedObjectsClient, findOptions);
const counts = objects.reduce((accum, result) => {
const type = result.type;
accum[type] = accum[type] || 0;
accum[type]++;
return accum;
}, {});
const counts = results.reduce((accum, result) => {
const type = result._source.type;
accum[type] = accum[type] || 0;
accum[type]++;
return accum;
}, {});
for (const type of req.payload.typesToInclude) {
if (!counts[type]) {
counts[type] = 0;
}
for (const type of req.payload.typesToInclude) {
if (!counts[type]) {
counts[type] = 0;
}
}
reply(counts);
}
catch (err) {
reply(Boom.boomify(err, { statusCode: 500 }));
}
reply(counts);
}
});
}