mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Remove legacy dashboard import/export API (#217485)
## Summary Fixes https://github.com/elastic/kibana/issues/41439 ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ...
This commit is contained in:
parent
aa9be6a621
commit
32c66acc4a
12 changed files with 0 additions and 784 deletions
|
@ -22,8 +22,6 @@ export type {
|
|||
|
||||
// only used by integration tests
|
||||
export { registerDeleteUnknownTypesRoute } from './src/routes/deprecations';
|
||||
export { registerLegacyExportRoute } from './src/routes/legacy_import_export/export';
|
||||
export { registerLegacyImportRoute } from './src/routes/legacy_import_export/import';
|
||||
export { registerBulkCreateRoute } from './src/routes/bulk_create';
|
||||
export { registerBulkGetRoute } from './src/routes/bulk_get';
|
||||
export { registerBulkResolveRoute } from './src/routes/bulk_resolve';
|
||||
|
|
|
@ -31,8 +31,6 @@ import { registerExportRoute } from './export';
|
|||
import { registerImportRoute } from './import';
|
||||
import { registerResolveImportErrorsRoute } from './resolve_import_errors';
|
||||
import { registerMigrateRoute } from './migrate';
|
||||
import { registerLegacyImportRoute } from './legacy_import_export/import';
|
||||
import { registerLegacyExportRoute } from './legacy_import_export/export';
|
||||
import { registerBulkResolveRoute } from './bulk_resolve';
|
||||
import { registerDeleteUnknownTypesRoute } from './deprecations';
|
||||
|
||||
|
@ -69,14 +67,6 @@ export function registerRoutes({
|
|||
},
|
||||
};
|
||||
|
||||
const legacyDeprecationInfo = {
|
||||
documentationUrl: `${docLinks.links.kibana.dashboardImportExport}`,
|
||||
severity: 'warning' as const, // will not break deployment upon upgrade
|
||||
reason: {
|
||||
type: 'remove' as const, // no alternative for posting `.json`, requires file format change to `.ndjson`
|
||||
},
|
||||
};
|
||||
|
||||
registerGetRoute(router, {
|
||||
config,
|
||||
coreUsageData,
|
||||
|
@ -159,22 +149,6 @@ export function registerRoutes({
|
|||
registerImportRoute(router, { config, coreUsageData });
|
||||
registerResolveImportErrorsRoute(router, { config, coreUsageData });
|
||||
|
||||
const legacyRouter = http.createRouter<InternalSavedObjectsRequestHandlerContext>('');
|
||||
registerLegacyImportRoute(legacyRouter, {
|
||||
maxImportPayloadBytes: config.maxImportPayloadBytes,
|
||||
coreUsageData,
|
||||
logger,
|
||||
access: internalOnServerless,
|
||||
legacyDeprecationInfo,
|
||||
});
|
||||
registerLegacyExportRoute(legacyRouter, {
|
||||
kibanaVersion,
|
||||
coreUsageData,
|
||||
logger,
|
||||
access: internalOnServerless,
|
||||
legacyDeprecationInfo,
|
||||
});
|
||||
|
||||
const internalRouter = http.createRouter<InternalSavedObjectsRequestHandlerContext>(
|
||||
'/internal/saved_objects/'
|
||||
);
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { Logger } from '@kbn/logging';
|
||||
import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal';
|
||||
import type { RouteAccess, RouteDeprecationInfo } from '@kbn/core-http-server';
|
||||
import type { InternalSavedObjectRouter } from '../../internal_types';
|
||||
import { exportDashboards } from './lib';
|
||||
|
||||
export const registerLegacyExportRoute = (
|
||||
router: InternalSavedObjectRouter,
|
||||
{
|
||||
kibanaVersion,
|
||||
coreUsageData,
|
||||
logger,
|
||||
access,
|
||||
legacyDeprecationInfo,
|
||||
}: {
|
||||
kibanaVersion: string;
|
||||
coreUsageData: InternalCoreUsageDataSetup;
|
||||
logger: Logger;
|
||||
access: RouteAccess;
|
||||
legacyDeprecationInfo: RouteDeprecationInfo;
|
||||
}
|
||||
) => {
|
||||
router.get(
|
||||
{
|
||||
path: '/api/kibana/dashboards/export',
|
||||
validate: {
|
||||
query: schema.object({
|
||||
dashboard: schema.oneOf([schema.string(), schema.arrayOf(schema.string())]),
|
||||
}),
|
||||
},
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'This route delegates authorization to the Saved Objects Client',
|
||||
},
|
||||
},
|
||||
options: {
|
||||
access,
|
||||
deprecated: legacyDeprecationInfo,
|
||||
tags: ['api'],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
logger.warn(
|
||||
"The export dashboard API '/api/kibana/dashboards/export' is deprecated. Use the saved objects export objects API '/api/saved_objects/_export' instead."
|
||||
);
|
||||
|
||||
const ids = Array.isArray(request.query.dashboard)
|
||||
? request.query.dashboard
|
||||
: [request.query.dashboard];
|
||||
const { client } = (await context.core).savedObjects;
|
||||
|
||||
const usageStatsClient = coreUsageData.getClient();
|
||||
usageStatsClient.incrementLegacyDashboardsExport({ request }).catch(() => {});
|
||||
|
||||
const exported = await exportDashboards(ids, client, kibanaVersion);
|
||||
const filename = `kibana-dashboards.${moment.utc().format('YYYY-MM-DD-HH-mm-ss')}.json`;
|
||||
const body = JSON.stringify(exported, null, ' ');
|
||||
|
||||
return response.ok({
|
||||
body,
|
||||
headers: {
|
||||
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': `${Buffer.byteLength(body, 'utf8')}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { Logger } from '@kbn/logging';
|
||||
import type { SavedObject } from '@kbn/core-saved-objects-server';
|
||||
import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal';
|
||||
import type { RouteAccess, RouteDeprecationInfo } from '@kbn/core-http-server';
|
||||
import type { InternalSavedObjectRouter } from '../../internal_types';
|
||||
import { importDashboards } from './lib';
|
||||
|
||||
export const registerLegacyImportRoute = (
|
||||
router: InternalSavedObjectRouter,
|
||||
{
|
||||
maxImportPayloadBytes,
|
||||
coreUsageData,
|
||||
logger,
|
||||
access,
|
||||
legacyDeprecationInfo,
|
||||
}: {
|
||||
maxImportPayloadBytes: number;
|
||||
coreUsageData: InternalCoreUsageDataSetup;
|
||||
logger: Logger;
|
||||
access: RouteAccess;
|
||||
legacyDeprecationInfo: RouteDeprecationInfo;
|
||||
}
|
||||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: '/api/kibana/dashboards/import',
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'This route delegates authorization to the Saved Objects Client',
|
||||
},
|
||||
},
|
||||
validate: {
|
||||
body: schema.object({
|
||||
objects: schema.arrayOf(schema.recordOf(schema.string(), schema.any())),
|
||||
version: schema.maybe(schema.string()),
|
||||
}),
|
||||
query: schema.object({
|
||||
force: schema.boolean({ defaultValue: false }),
|
||||
exclude: schema.oneOf([schema.string(), schema.arrayOf(schema.string())], {
|
||||
defaultValue: [],
|
||||
}),
|
||||
}),
|
||||
},
|
||||
options: {
|
||||
access,
|
||||
tags: ['api'],
|
||||
body: {
|
||||
maxBytes: maxImportPayloadBytes,
|
||||
},
|
||||
deprecated: legacyDeprecationInfo,
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
logger.warn(
|
||||
"The import dashboard API '/api/kibana/dashboards/import' is deprecated. Use the saved objects import objects API '/api/saved_objects/_import' instead."
|
||||
);
|
||||
|
||||
const { client } = (await context.core).savedObjects;
|
||||
const objects = request.body.objects as SavedObject[];
|
||||
const { force, exclude } = request.query;
|
||||
|
||||
const usageStatsClient = coreUsageData.getClient();
|
||||
usageStatsClient.incrementLegacyDashboardsImport({ request }).catch(() => {});
|
||||
|
||||
const result = await importDashboards(client, objects, {
|
||||
overwrite: force,
|
||||
exclude: Array.isArray(exclude) ? exclude : [exclude],
|
||||
});
|
||||
return response.ok({
|
||||
body: result,
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
|
@ -1,184 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { SavedObject, SavedObjectAttributes } from '@kbn/core-saved-objects-server';
|
||||
import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks';
|
||||
import { collectReferencesDeep } from './collect_references_deep';
|
||||
|
||||
const data: Array<SavedObject<SavedObjectAttributes>> = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'dashboard',
|
||||
attributes: {
|
||||
panelsJSON: JSON.stringify([{ panelRefName: 'panel_0' }, { panelRefName: 'panel_1' }]),
|
||||
},
|
||||
references: [
|
||||
{
|
||||
name: 'panel_0',
|
||||
type: 'visualization',
|
||||
id: '2',
|
||||
},
|
||||
{
|
||||
name: 'panel_1',
|
||||
type: 'visualization',
|
||||
id: '3',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'visualization',
|
||||
attributes: {
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON: JSON.stringify({
|
||||
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
}),
|
||||
},
|
||||
},
|
||||
references: [
|
||||
{
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
type: 'index-pattern',
|
||||
id: '4',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'visualization',
|
||||
attributes: {
|
||||
savedSearchRefName: 'search_0',
|
||||
},
|
||||
references: [
|
||||
{
|
||||
name: 'search_0',
|
||||
type: 'search',
|
||||
id: '5',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'index-pattern',
|
||||
attributes: {
|
||||
title: 'pattern*',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
type: 'search',
|
||||
attributes: {
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON: JSON.stringify({
|
||||
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
}),
|
||||
},
|
||||
},
|
||||
references: [
|
||||
{
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
type: 'index-pattern',
|
||||
id: '4',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
test('collects dashboard and all dependencies', async () => {
|
||||
const savedObjectClient = savedObjectsClientMock.create();
|
||||
savedObjectClient.bulkGet.mockImplementation((objects) => {
|
||||
if (!objects) {
|
||||
throw new Error('Invalid test data');
|
||||
}
|
||||
return Promise.resolve({
|
||||
saved_objects: objects.map(
|
||||
(obj: any) => data.find((row) => row.id === obj.id && row.type === obj.type)!
|
||||
),
|
||||
});
|
||||
});
|
||||
const objects = await collectReferencesDeep(savedObjectClient, [{ type: 'dashboard', id: '1' }]);
|
||||
expect(objects).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"panelsJSON": "[{\\"panelRefName\\":\\"panel_0\\"},{\\"panelRefName\\":\\"panel_1\\"}]",
|
||||
},
|
||||
"id": "1",
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "2",
|
||||
"name": "panel_0",
|
||||
"type": "visualization",
|
||||
},
|
||||
Object {
|
||||
"id": "3",
|
||||
"name": "panel_1",
|
||||
"type": "visualization",
|
||||
},
|
||||
],
|
||||
"type": "dashboard",
|
||||
},
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"kibanaSavedObjectMeta": Object {
|
||||
"searchSourceJSON": "{\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}",
|
||||
},
|
||||
},
|
||||
"id": "2",
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "4",
|
||||
"name": "kibanaSavedObjectMeta.searchSourceJSON.index",
|
||||
"type": "index-pattern",
|
||||
},
|
||||
],
|
||||
"type": "visualization",
|
||||
},
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"savedSearchRefName": "search_0",
|
||||
},
|
||||
"id": "3",
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "5",
|
||||
"name": "search_0",
|
||||
"type": "search",
|
||||
},
|
||||
],
|
||||
"type": "visualization",
|
||||
},
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"title": "pattern*",
|
||||
},
|
||||
"id": "4",
|
||||
"references": Array [],
|
||||
"type": "index-pattern",
|
||||
},
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"kibanaSavedObjectMeta": Object {
|
||||
"searchSourceJSON": "{\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}",
|
||||
},
|
||||
},
|
||||
"id": "5",
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "4",
|
||||
"name": "kibanaSavedObjectMeta.searchSourceJSON.index",
|
||||
"type": "index-pattern",
|
||||
},
|
||||
],
|
||||
"type": "search",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { SavedObject } from '@kbn/core-saved-objects-server';
|
||||
import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
||||
|
||||
const MAX_BULK_GET_SIZE = 10000;
|
||||
|
||||
interface ObjectsToCollect {
|
||||
id: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export async function collectReferencesDeep(
|
||||
savedObjectClient: SavedObjectsClientContract,
|
||||
objects: ObjectsToCollect[]
|
||||
): Promise<SavedObject[]> {
|
||||
let result: SavedObject[] = [];
|
||||
const queue = [...objects];
|
||||
while (queue.length !== 0) {
|
||||
const itemsToGet = queue.splice(0, MAX_BULK_GET_SIZE);
|
||||
const { saved_objects: savedObjects } = await savedObjectClient.bulkGet(itemsToGet);
|
||||
result = result.concat(savedObjects);
|
||||
for (const { references = [] } of savedObjects) {
|
||||
for (const reference of references) {
|
||||
const isDuplicate = queue
|
||||
.concat(result)
|
||||
.some((obj) => obj.type === reference.type && obj.id === reference.id);
|
||||
if (isDuplicate) {
|
||||
continue;
|
||||
}
|
||||
queue.push({ type: reference.type, id: reference.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
||||
import { collectReferencesDeep } from './collect_references_deep';
|
||||
|
||||
export async function exportDashboards(
|
||||
ids: string[],
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
kibanaVersion: string
|
||||
) {
|
||||
const objectsToExport = ids.map((id) => ({ id, type: 'dashboard' }));
|
||||
|
||||
const objects = await collectReferencesDeep(savedObjectsClient, objectsToExport);
|
||||
return {
|
||||
version: kibanaVersion,
|
||||
objects,
|
||||
};
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { SavedObject } from '@kbn/core-saved-objects-server';
|
||||
import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks';
|
||||
import { importDashboards } from './import_dashboards';
|
||||
|
||||
describe('importDashboards(req)', () => {
|
||||
let savedObjectClient: ReturnType<typeof savedObjectsClientMock.create>;
|
||||
let importedObjects: SavedObject[];
|
||||
|
||||
beforeEach(() => {
|
||||
savedObjectClient = savedObjectsClientMock.create();
|
||||
savedObjectClient.bulkCreate.mockResolvedValue({ saved_objects: [] });
|
||||
|
||||
importedObjects = [
|
||||
{
|
||||
id: 'dashboard-01',
|
||||
type: 'dashboard',
|
||||
attributes: { panelJSON: '{}' },
|
||||
references: [],
|
||||
version: 'foo',
|
||||
},
|
||||
{
|
||||
id: 'panel-01',
|
||||
type: 'visualization',
|
||||
attributes: { visState: '{}' },
|
||||
references: [],
|
||||
managed: true,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
test('should call bulkCreate with each asset, filtering out any version and managed if present', async () => {
|
||||
await importDashboards(savedObjectClient, importedObjects, { overwrite: false, exclude: [] });
|
||||
|
||||
expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(1);
|
||||
expect(savedObjectClient.bulkCreate).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
id: 'dashboard-01',
|
||||
type: 'dashboard',
|
||||
attributes: { panelJSON: '{}' },
|
||||
references: [],
|
||||
typeMigrationVersion: '',
|
||||
},
|
||||
{
|
||||
id: 'panel-01',
|
||||
type: 'visualization',
|
||||
attributes: { visState: '{}' },
|
||||
references: [],
|
||||
typeMigrationVersion: '',
|
||||
},
|
||||
],
|
||||
{ overwrite: false }
|
||||
);
|
||||
});
|
||||
|
||||
test('should call bulkCreate with overwrite true if force is truthy', async () => {
|
||||
await importDashboards(savedObjectClient, importedObjects, { overwrite: true, exclude: [] });
|
||||
|
||||
expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(1);
|
||||
expect(savedObjectClient.bulkCreate).toHaveBeenCalledWith(expect.any(Array), {
|
||||
overwrite: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('should exclude types based on exclude argument', async () => {
|
||||
await importDashboards(savedObjectClient, importedObjects, {
|
||||
overwrite: false,
|
||||
exclude: ['visualization'],
|
||||
});
|
||||
|
||||
expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(1);
|
||||
expect(savedObjectClient.bulkCreate).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
id: 'dashboard-01',
|
||||
type: 'dashboard',
|
||||
attributes: { panelJSON: '{}' },
|
||||
references: [],
|
||||
typeMigrationVersion: '',
|
||||
},
|
||||
],
|
||||
{ overwrite: false }
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { SavedObject } from '@kbn/core-saved-objects-server';
|
||||
import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
||||
|
||||
export async function importDashboards(
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
objects: SavedObject[],
|
||||
{ overwrite, exclude }: { overwrite: boolean; exclude: string[] }
|
||||
) {
|
||||
// The server assumes that documents with no `typeMigrationVersion` are up to date.
|
||||
// That assumption enables Kibana and other API consumers to not have to determine
|
||||
// `typeMigrationVersion` prior to creating new objects. But it means that imports
|
||||
// need to set `typeMigrationVersion` to something other than undefined, so that imported
|
||||
// docs are not seen as automatically up-to-date.
|
||||
const docs = objects
|
||||
.filter((item) => !exclude.includes(item.type))
|
||||
// filter out any document version and managed, if present
|
||||
.map(({ version, managed, ...doc }) => ({
|
||||
...doc,
|
||||
...(!doc.migrationVersion && !doc.typeMigrationVersion ? { typeMigrationVersion: '' } : {}),
|
||||
}));
|
||||
|
||||
const results = await savedObjectsClient.bulkCreate(docs, { overwrite });
|
||||
return { objects: results.saved_objects };
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export { exportDashboards } from './export_dashboards';
|
||||
export { importDashboards } from './import_dashboards';
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
const exportObjects = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'index-pattern',
|
||||
attributes: {},
|
||||
references: [],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'search',
|
||||
attributes: {},
|
||||
references: [
|
||||
{
|
||||
name: 'ref_0',
|
||||
type: 'index-pattern',
|
||||
id: '1',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
import supertest from 'supertest';
|
||||
import type { ICoreUsageStatsClient } from '@kbn/core-usage-data-base-server-internal';
|
||||
import {
|
||||
coreUsageStatsClientMock,
|
||||
coreUsageDataServiceMock,
|
||||
} from '@kbn/core-usage-data-server-mocks';
|
||||
import { setupServer } from '@kbn/core-test-helpers-test-utils';
|
||||
import { loggerMock } from '@kbn/logging-mocks';
|
||||
import { SavedObjectsBulkResponse } from '@kbn/core-saved-objects-api-server';
|
||||
import {
|
||||
registerLegacyExportRoute,
|
||||
type InternalSavedObjectsRequestHandlerContext,
|
||||
} from '@kbn/core-saved-objects-server-internal';
|
||||
import { legacyDeprecationMock } from '../routes_test_utils';
|
||||
|
||||
type SetupServerReturn = Awaited<ReturnType<typeof setupServer>>;
|
||||
let coreUsageStatsClient: jest.Mocked<ICoreUsageStatsClient>;
|
||||
|
||||
describe('POST /api/dashboards/export', () => {
|
||||
let server: SetupServerReturn['server'];
|
||||
let httpSetup: SetupServerReturn['httpSetup'];
|
||||
let handlerContext: SetupServerReturn['handlerContext'];
|
||||
|
||||
beforeEach(async () => {
|
||||
({ server, httpSetup, handlerContext } = await setupServer());
|
||||
|
||||
const router = httpSetup.createRouter<InternalSavedObjectsRequestHandlerContext>('');
|
||||
|
||||
coreUsageStatsClient = coreUsageStatsClientMock.create();
|
||||
coreUsageStatsClient.incrementLegacyDashboardsExport.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail
|
||||
const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient);
|
||||
|
||||
registerLegacyExportRoute(router, {
|
||||
kibanaVersion: 'mockversion',
|
||||
coreUsageData,
|
||||
logger: loggerMock.create(),
|
||||
access: 'public',
|
||||
legacyDeprecationInfo: legacyDeprecationMock,
|
||||
});
|
||||
|
||||
handlerContext.savedObjects.client.bulkGet
|
||||
.mockResolvedValueOnce({
|
||||
saved_objects: exportObjects,
|
||||
} as SavedObjectsBulkResponse)
|
||||
.mockResolvedValueOnce({
|
||||
saved_objects: [],
|
||||
} as SavedObjectsBulkResponse);
|
||||
|
||||
await server.start();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it('calls exportDashboards and records usage stats', async () => {
|
||||
const result = await supertest(httpSetup.server.listener)
|
||||
.get('/api/kibana/dashboards/export?dashboard=942dcef0-b2cd-11e8-ad8e-85441f0c2e5c')
|
||||
.set('x-elastic-internal-origin', 'kibana');
|
||||
|
||||
expect(result.status).toBe(200);
|
||||
expect(result.header['content-type']).toEqual('application/json; charset=utf-8');
|
||||
expect(result.header['content-disposition']).toMatch(
|
||||
/attachment; filename="kibana-dashboards.*\.json/
|
||||
);
|
||||
|
||||
expect(result.body.objects).toEqual(exportObjects);
|
||||
expect(result.body.version).toEqual('mockversion');
|
||||
expect(coreUsageStatsClient.incrementLegacyDashboardsExport).toHaveBeenCalledWith({
|
||||
request: expect.anything(),
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
const importObjects = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'index-pattern',
|
||||
attributes: {},
|
||||
references: [],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'search',
|
||||
attributes: {},
|
||||
references: [
|
||||
{
|
||||
name: 'ref_0',
|
||||
type: 'index-pattern',
|
||||
id: '1',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
import supertest from 'supertest';
|
||||
import type { ICoreUsageStatsClient } from '@kbn/core-usage-data-base-server-internal';
|
||||
import {
|
||||
coreUsageStatsClientMock,
|
||||
coreUsageDataServiceMock,
|
||||
} from '@kbn/core-usage-data-server-mocks';
|
||||
import { setupServer } from '@kbn/core-test-helpers-test-utils';
|
||||
import { loggerMock } from '@kbn/logging-mocks';
|
||||
import { SavedObjectsBulkResponse } from '@kbn/core-saved-objects-api-server';
|
||||
import {
|
||||
registerLegacyImportRoute,
|
||||
type InternalSavedObjectsRequestHandlerContext,
|
||||
} from '@kbn/core-saved-objects-server-internal';
|
||||
import { legacyDeprecationMock } from '../routes_test_utils';
|
||||
|
||||
type SetupServerReturn = Awaited<ReturnType<typeof setupServer>>;
|
||||
let coreUsageStatsClient: jest.Mocked<ICoreUsageStatsClient>;
|
||||
|
||||
describe('POST /api/dashboards/import', () => {
|
||||
let server: SetupServerReturn['server'];
|
||||
let httpSetup: SetupServerReturn['httpSetup'];
|
||||
let handlerContext: SetupServerReturn['handlerContext'];
|
||||
|
||||
beforeEach(async () => {
|
||||
({ server, httpSetup, handlerContext } = await setupServer());
|
||||
|
||||
const router = httpSetup.createRouter<InternalSavedObjectsRequestHandlerContext>('');
|
||||
|
||||
coreUsageStatsClient = coreUsageStatsClientMock.create();
|
||||
coreUsageStatsClient.incrementLegacyDashboardsImport.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail
|
||||
const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient);
|
||||
|
||||
registerLegacyImportRoute(router, {
|
||||
maxImportPayloadBytes: 26214400,
|
||||
coreUsageData,
|
||||
logger: loggerMock.create(),
|
||||
access: 'public',
|
||||
legacyDeprecationInfo: legacyDeprecationMock,
|
||||
});
|
||||
|
||||
handlerContext.savedObjects.client.bulkCreate.mockResolvedValueOnce({
|
||||
saved_objects: importObjects,
|
||||
} as SavedObjectsBulkResponse);
|
||||
|
||||
await server.start();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it('calls importDashboards and records usage stats', async () => {
|
||||
const result = await supertest(httpSetup.server.listener)
|
||||
.post('/api/kibana/dashboards/import')
|
||||
.set('x-elastic-internal-origin', 'kibana')
|
||||
.send({ version: '7.14.0', objects: importObjects });
|
||||
|
||||
expect(result.status).toBe(200);
|
||||
|
||||
expect(result.body.objects).toEqual(importObjects);
|
||||
expect(coreUsageStatsClient.incrementLegacyDashboardsImport).toHaveBeenCalledWith({
|
||||
request: expect.anything(),
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue