Skip to content
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 API for Dashboards #10858

Merged
merged 33 commits into from
Jun 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
82b7967
Initial implimentation
simianhacker Mar 22, 2017
3bb4792
Deduping final data array. Adding index patterns from saved searches
simianhacker Mar 23, 2017
1f4dd16
Adding import
simianhacker Mar 28, 2017
58d3853
Finishing import implimentation
simianhacker Mar 28, 2017
b68133a
Adding tests for export
simianhacker Mar 28, 2017
a9ce41d
Adding tests for import
simianhacker Mar 28, 2017
e0cd1ac
Merge branch 'master' of github.com:elastic/kibana into import-export
simianhacker Apr 26, 2017
948cd72
Filtering out bad ids
simianhacker Apr 26, 2017
c63801f
Merge branch 'master' of github.com:elastic/kibana into import-export
simianhacker Apr 28, 2017
0ec17c6
Adding options for exclude and force
simianhacker Apr 28, 2017
0eb79e2
Merge branch 'master' of github.com:elastic/kibana into import-export
simianhacker May 24, 2017
5cef778
Fixes per request by PR reviewers
simianhacker May 26, 2017
abe62ac
Adding a check for empty ids
simianhacker May 31, 2017
741822d
Use SavedObject API
May 31, 2017
b763821
Omits missed objects, adds tests
May 31, 2017
ba2c164
Moves import to SavedObjectsClient
Jun 1, 2017
c3aaaa2
Merge pull request #9 from tylersmalley/import-export-use-saved-objects
simianhacker Jun 5, 2017
518918e
Fixing spacing issues
simianhacker Jun 5, 2017
a9c941d
Merge branch 'import-export' of github.com:simianhacker/kibana into i…
simianhacker Jun 5, 2017
0e6bda4
Fixing a bug with missing index patterns
simianhacker Jun 5, 2017
b57b8f4
Fixing spacing issues (because the format is weird on this array whic…
simianhacker Jun 5, 2017
e08e787
Fixing tests and renaming file
simianhacker Jun 5, 2017
17b87c7
Changing paths to /api/kibana/dashboards/(export|import); adding vali…
simianhacker Jun 5, 2017
fb9125d
Single call signature and use async/await
Jun 6, 2017
53fe0a6
Adding try/catch to JSON parses; Removing payload from export;
simianhacker Jun 6, 2017
c1f6541
removing redundent code
simianhacker Jun 6, 2017
b9ca38b
Changing test to only use query arguments
simianhacker Jun 6, 2017
50729fb
Removing bad panel from test data
simianhacker Jun 6, 2017
a01fdae
Merge pull request #10 from tylersmalley/import-export-so-feedback
simianhacker Jun 6, 2017
42e4ea0
Return errors for bulkCreate objects
Jun 6, 2017
6da496b
Merge pull request #11 from tylersmalley/import-export-surface-errors
simianhacker Jun 6, 2017
fecb801
Changing everything to named imports; removing deps from everything; …
simianhacker Jun 6, 2017
bcb2b7d
Refactoring to use async/await pattern
simianhacker Jun 6, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/core_plugins/kibana/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { mkdirp as mkdirpNode } from 'mkdirp';
import manageUuid from './server/lib/manage_uuid';
import search from './server/routes/api/search';
import settings from './server/routes/api/settings';
import { importApi } from './server/routes/api/import';
import { exportApi } from './server/routes/api/export';
import scripts from './server/routes/api/scripts';
import { registerSuggestionsApi } from './server/routes/api/suggestions';
import * as systemApi from './server/lib/system_api';
Expand Down Expand Up @@ -126,6 +128,8 @@ module.exports = function (kibana) {
search(server);
settings(server);
scripts(server);
importApi(server);
exportApi(server);
registerSuggestionsApi(server);

server.expose('systemApi', systemApi);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import sinon from 'sinon';
import * as deps from '../collect_panels';
import { collectDashboards } from '../collect_dashboards';
import { expect } from 'chai';

describe('collectDashboards(req, ids)', () => {

let collectPanelsStub;
const savedObjectsClient = { bulkGet: sinon.mock() };

const ids = ['dashboard-01', 'dashboard-02'];

beforeEach(() => {
collectPanelsStub = sinon.stub(deps, 'collectPanels');
collectPanelsStub.onFirstCall().returns(Promise.resolve([
{ id: 'dashboard-01' },
{ id: 'panel-01' },
{ id: 'index-*' }
]));
collectPanelsStub.onSecondCall().returns(Promise.resolve([
{ id: 'dashboard-02' },
{ id: 'panel-01' },
{ id: 'index-*' }
]));

savedObjectsClient.bulkGet.returns(Promise.resolve([
{ id: 'dashboard-01' }, { id: 'dashboard-02' }
]));
});

afterEach(() => {
collectPanelsStub.restore();
savedObjectsClient.bulkGet.reset();
});

it('should request all dashboards', async () => {
await collectDashboards(savedObjectsClient, ids);

expect(savedObjectsClient.bulkGet.calledOnce).to.equal(true);

const args = savedObjectsClient.bulkGet.getCall(0).args;
expect(args[0]).to.eql([{
id: 'dashboard-01',
type: 'dashboard'
}, {
id: 'dashboard-02',
type: 'dashboard'
}]);
});

it('should call collectPanels with dashboard docs', async () => {
await collectDashboards(savedObjectsClient, ids);

expect(collectPanelsStub.calledTwice).to.equal(true);
expect(collectPanelsStub.args[0][1]).to.eql({ id: 'dashboard-01' });
expect(collectPanelsStub.args[1][1]).to.eql({ id: 'dashboard-02' });
});

it('should return an unique list of objects', async () => {
const results = await collectDashboards(savedObjectsClient, ids);
expect(results).to.eql([
{ id: 'dashboard-01' },
{ id: 'panel-01' },
{ id: 'index-*' },
{ id: 'dashboard-02' },
]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import sinon from 'sinon';
import { collectIndexPatterns } from '../collect_index_patterns';
import { expect } from 'chai';

describe('collectIndexPatterns(req, panels)', () => {
const panels = [
{
attributes: {
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({ index: 'index-*' })
}
}
}, {
attributes: {
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({ index: 'logstash-*' })
}
}
}, {
attributes: {
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({ index: 'logstash-*' })
}
}
}, {
attributes: {
savedSearchId: 1,
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({ index: 'bad-*' })
}
}
}
];

const savedObjectsClient = { bulkGet: sinon.mock() };

beforeEach(() => {
savedObjectsClient.bulkGet.returns(Promise.resolve([
{ id: 'index-*' }, { id: 'logstash-*' }
]));
});

afterEach(() => {
savedObjectsClient.bulkGet.reset();
});

it('should request all index patterns', async () => {
await collectIndexPatterns(savedObjectsClient, panels);

expect(savedObjectsClient.bulkGet.calledOnce).to.equal(true);
expect(savedObjectsClient.bulkGet.getCall(0).args[0]).to.eql([{
id: 'index-*',
type: 'index-pattern'
}, {
id: 'logstash-*',
type: 'index-pattern'
}]);
});

it('should return the index pattern docs', async () => {
const results = await collectIndexPatterns(savedObjectsClient, panels);

expect(results).to.eql([
{ id: 'index-*' },
{ id: 'logstash-*' }
]);
});

it('should return an empty array if nothing is requested', async () => {
const input = [
{
attributes: {
savedSearchId: 1,
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({ index: 'bad-*' })
}
}
}
];

const results = await collectIndexPatterns(savedObjectsClient, input);
expect(results).to.eql([]);
expect(savedObjectsClient.bulkGet.calledOnce).to.eql(false);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import sinon from 'sinon';
import * as collectIndexPatternsDep from '../collect_index_patterns';
import * as collectSearchSourcesDep from '../collect_search_sources';
import { collectPanels } from '../collect_panels';
import { expect } from 'chai';

describe('collectPanels(req, dashboard)', () => {
let collectSearchSourcesStub;
let collectIndexPatternsStub;
let dashboard;

const savedObjectsClient = { bulkGet: sinon.mock() };

beforeEach(() => {
dashboard = {
attributes: {
panelsJSON: JSON.stringify([
{ id: 'panel-01', type: 'search' },
{ id: 'panel-02', type: 'visualization' }
])
}
};

savedObjectsClient.bulkGet.returns(Promise.resolve([
{ id: 'panel-01' }, { id: 'panel-02' }
]));

collectIndexPatternsStub = sinon.stub(collectIndexPatternsDep, 'collectIndexPatterns');
collectIndexPatternsStub.returns([{ id: 'logstash-*' }]);
collectSearchSourcesStub = sinon.stub(collectSearchSourcesDep, 'collectSearchSources');
collectSearchSourcesStub.returns([ { id: 'search-01' }]);
});

afterEach(() => {
collectSearchSourcesStub.restore();
collectIndexPatternsStub.restore();
savedObjectsClient.bulkGet.reset();
});

it('should request each panel in the panelJSON', async () => {
await collectPanels(savedObjectsClient, dashboard);

expect(savedObjectsClient.bulkGet.calledOnce).to.equal(true);
expect(savedObjectsClient.bulkGet.getCall(0).args[0]).to.eql([{
id: 'panel-01',
type: 'search'
}, {
id: 'panel-02',
type: 'visualization'
}]);
});

it('should call collectSearchSources()', async () => {
await collectPanels(savedObjectsClient, dashboard);
expect(collectSearchSourcesStub.calledOnce).to.equal(true);
expect(collectSearchSourcesStub.args[0][1]).to.eql([
{ id: 'panel-01' },
{ id: 'panel-02' }
]);
});

it('should call collectIndexPatterns()', async () => {
await collectPanels(savedObjectsClient, dashboard);

expect(collectIndexPatternsStub.calledOnce).to.equal(true);
expect(collectIndexPatternsStub.args[0][1]).to.eql([
{ id: 'panel-01' },
{ id: 'panel-02' }
]);
});

it('should return panels, index patterns, search sources, and dashboard', async () => {
const results = await collectPanels(savedObjectsClient, dashboard);

expect(results).to.eql([
{ id: 'panel-01' },
{ id: 'panel-02' },
{ id: 'logstash-*' },
{ id: 'search-01' },
dashboard
]);
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import sinon from 'sinon';
import * as deps from '../collect_index_patterns';
import { collectSearchSources } from '../collect_search_sources';
import { expect } from 'chai';
describe('collectSearchSources(req, panels)', () => {
const savedObjectsClient = { bulkGet: sinon.mock() };

let panels;
let collectIndexPatternsStub;

beforeEach(() => {
panels = [
{ attributes: { savedSearchId: 1 } },
{ attributes: { savedSearchId: 2 } }
];

collectIndexPatternsStub = sinon.stub(deps, 'collectIndexPatterns');
collectIndexPatternsStub.returns(Promise.resolve([{ id: 'logstash-*' }]));

savedObjectsClient.bulkGet.returns(Promise.resolve([
{ id: 1 }, { id: 2 }
]));
});

afterEach(() => {
collectIndexPatternsStub.restore();
savedObjectsClient.bulkGet.reset();
});

it('should request all search sources', async () => {
await collectSearchSources(savedObjectsClient, panels);

expect(savedObjectsClient.bulkGet.calledOnce).to.equal(true);
expect(savedObjectsClient.bulkGet.getCall(0).args[0]).to.eql([
{ type: 'search', id: 1 }, { type: 'search', id: 2 }
]);
});

it('should return the search source and index patterns', async () => {
const results = await collectSearchSources(savedObjectsClient, panels);

expect(results).to.eql([
{ id: 1 },
{ id: 2 },
{ id: 'logstash-*' }
]);
});

it('should return an empty array if nothing is requested', async () => {
const input = [
{
attributes: {
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({ index: 'bad-*' })
}
}
}
];

const results = await collectSearchSources(savedObjectsClient, input);
expect(results).to.eql([]);
expect(savedObjectsClient.bulkGet.calledOnce).to.eql(false);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as deps from '../collect_dashboards';
import { exportDashboards } from '../export_dashboards';
import sinon from 'sinon';
import { expect } from 'chai';

describe('exportDashboards(req)', () => {

let req;
let collectDashboardsStub;

beforeEach(() => {
req = {
query: { dashboard: 'dashboard-01' },
server: {
config: () => ({ get: () => '6.0.0' }),
plugins: {
elasticsearch: {
getCluster: () => ({ callWithRequest: sinon.stub() })
}
},
}
};

collectDashboardsStub = sinon.stub(deps, 'collectDashboards');
collectDashboardsStub.returns(Promise.resolve([
{ id: 'dasboard-01' },
{ id: 'logstash-*' },
{ id: 'panel-01' }
]));
});

afterEach(() => {
collectDashboardsStub.restore();
});

it('should return a response object with version', () => {
return exportDashboards(req).then((resp) => {
expect(resp).to.have.property('version', '6.0.0');
});
});

it('should return a response object with objects', () => {
return exportDashboards(req).then((resp) => {
expect(resp).to.have.property('objects');
expect(resp.objects).to.eql([
{ id: 'dasboard-01' },
{ id: 'logstash-*' },
{ id: 'panel-01' }
]);
});
});
});
Loading