legacy dashboards import/export API: deprecation logs and usage data (#111283)
* Move legacy dashboards API to core and adds usage data * More legacy_export plugin removal * Log a warning for deprecated dashboard import/export API * Review comments
This commit is contained in:
parent
dda09b6b39
commit
5464af6923
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -250,7 +250,6 @@
|
|||
/src/plugins/kibana_overview/ @elastic/kibana-core
|
||||
/x-pack/plugins/global_search_bar/ @elastic/kibana-core
|
||||
#CC# /src/core/server/csp/ @elastic/kibana-core
|
||||
#CC# /src/plugins/legacy_export/ @elastic/kibana-core
|
||||
#CC# /src/plugins/xpack_legacy/ @elastic/kibana-core
|
||||
#CC# /src/plugins/saved_objects/ @elastic/kibana-core
|
||||
#CC# /x-pack/plugins/cloud/ @elastic/kibana-core
|
||||
|
|
|
@ -172,10 +172,6 @@ in Kibana, e.g. visualizations. It has the form of a flyout panel.
|
|||
|Utilities for building Kibana plugins.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/src/plugins/legacy_export/README.md[legacyExport]
|
||||
|The legacyExport plugin adds support for the legacy saved objects export format.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/src/plugins/management/README.md[management]
|
||||
|This plugins contains the "Stack Management" page framework. It offers navigation and an API
|
||||
to link individual managment section into it. This plugin does not contain any individual
|
||||
|
|
|
@ -51,14 +51,6 @@ Calls to the API endpoints require different operations. To interact with the {k
|
|||
|
||||
* *DELETE* - Removes the information.
|
||||
|
||||
For example, the following `curl` command exports a dashboard:
|
||||
|
||||
[source,sh]
|
||||
--------------------------------------------
|
||||
curl -X POST api/kibana/dashboards/export?dashboard=942dcef0-b2cd-11e8-ad8e-85441f0c2e5c
|
||||
--------------------------------------------
|
||||
// KIBANA
|
||||
|
||||
[float]
|
||||
[[api-request-headers]]
|
||||
=== Request headers
|
||||
|
|
|
@ -23,6 +23,8 @@ const createUsageStatsClientMock = () =>
|
|||
incrementSavedObjectsImport: jest.fn().mockResolvedValue(null),
|
||||
incrementSavedObjectsResolveImportErrors: jest.fn().mockResolvedValue(null),
|
||||
incrementSavedObjectsExport: jest.fn().mockResolvedValue(null),
|
||||
incrementLegacyDashboardsImport: jest.fn().mockResolvedValue(null),
|
||||
incrementLegacyDashboardsExport: jest.fn().mockResolvedValue(null),
|
||||
} as unknown) as jest.Mocked<CoreUsageStatsClient>);
|
||||
|
||||
export const coreUsageStatsClientMock = {
|
||||
|
|
|
@ -25,6 +25,8 @@ import {
|
|||
IMPORT_STATS_PREFIX,
|
||||
RESOLVE_IMPORT_STATS_PREFIX,
|
||||
EXPORT_STATS_PREFIX,
|
||||
LEGACY_DASHBOARDS_IMPORT_STATS_PREFIX,
|
||||
LEGACY_DASHBOARDS_EXPORT_STATS_PREFIX,
|
||||
} from './core_usage_stats_client';
|
||||
import { CoreUsageStatsClient } from '.';
|
||||
import { DEFAULT_NAMESPACE_STRING } from '../saved_objects/service/lib/utils';
|
||||
|
@ -1007,4 +1009,114 @@ describe('CoreUsageStatsClient', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#incrementLegacyDashboardsImport', () => {
|
||||
it('does not throw an error if repository incrementCounter operation fails', async () => {
|
||||
const { usageStatsClient, repositoryMock } = setup();
|
||||
repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!'));
|
||||
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
await expect(
|
||||
usageStatsClient.incrementLegacyDashboardsImport({
|
||||
request,
|
||||
} as IncrementSavedObjectsExportOptions)
|
||||
).resolves.toBeUndefined();
|
||||
expect(repositoryMock.incrementCounter).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('handles the default namespace string and first party request appropriately', async () => {
|
||||
const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING);
|
||||
|
||||
const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders });
|
||||
await usageStatsClient.incrementLegacyDashboardsImport({
|
||||
request,
|
||||
} as IncrementSavedObjectsExportOptions);
|
||||
expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1);
|
||||
expect(repositoryMock.incrementCounter).toHaveBeenCalledWith(
|
||||
CORE_USAGE_STATS_TYPE,
|
||||
CORE_USAGE_STATS_ID,
|
||||
[
|
||||
`${LEGACY_DASHBOARDS_IMPORT_STATS_PREFIX}.total`,
|
||||
`${LEGACY_DASHBOARDS_IMPORT_STATS_PREFIX}.namespace.default.total`,
|
||||
`${LEGACY_DASHBOARDS_IMPORT_STATS_PREFIX}.namespace.default.kibanaRequest.yes`,
|
||||
],
|
||||
incrementOptions
|
||||
);
|
||||
});
|
||||
|
||||
it('handles a non-default space and and third party request appropriately', async () => {
|
||||
const { usageStatsClient, repositoryMock } = setup('foo');
|
||||
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
await usageStatsClient.incrementLegacyDashboardsImport({
|
||||
request,
|
||||
} as IncrementSavedObjectsExportOptions);
|
||||
expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1);
|
||||
expect(repositoryMock.incrementCounter).toHaveBeenCalledWith(
|
||||
CORE_USAGE_STATS_TYPE,
|
||||
CORE_USAGE_STATS_ID,
|
||||
[
|
||||
`${LEGACY_DASHBOARDS_IMPORT_STATS_PREFIX}.total`,
|
||||
`${LEGACY_DASHBOARDS_IMPORT_STATS_PREFIX}.namespace.custom.total`,
|
||||
`${LEGACY_DASHBOARDS_IMPORT_STATS_PREFIX}.namespace.custom.kibanaRequest.no`,
|
||||
],
|
||||
incrementOptions
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#incrementLegacyDashboardsExport', () => {
|
||||
it('does not throw an error if repository incrementCounter operation fails', async () => {
|
||||
const { usageStatsClient, repositoryMock } = setup();
|
||||
repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!'));
|
||||
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
await expect(
|
||||
usageStatsClient.incrementLegacyDashboardsExport({
|
||||
request,
|
||||
} as IncrementSavedObjectsExportOptions)
|
||||
).resolves.toBeUndefined();
|
||||
expect(repositoryMock.incrementCounter).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('handles the default namespace string and first party request appropriately', async () => {
|
||||
const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING);
|
||||
|
||||
const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders });
|
||||
await usageStatsClient.incrementLegacyDashboardsExport({
|
||||
request,
|
||||
} as IncrementSavedObjectsExportOptions);
|
||||
expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1);
|
||||
expect(repositoryMock.incrementCounter).toHaveBeenCalledWith(
|
||||
CORE_USAGE_STATS_TYPE,
|
||||
CORE_USAGE_STATS_ID,
|
||||
[
|
||||
`${LEGACY_DASHBOARDS_EXPORT_STATS_PREFIX}.total`,
|
||||
`${LEGACY_DASHBOARDS_EXPORT_STATS_PREFIX}.namespace.default.total`,
|
||||
`${LEGACY_DASHBOARDS_EXPORT_STATS_PREFIX}.namespace.default.kibanaRequest.yes`,
|
||||
],
|
||||
incrementOptions
|
||||
);
|
||||
});
|
||||
|
||||
it('handles a non-default space and and third party request appropriately', async () => {
|
||||
const { usageStatsClient, repositoryMock } = setup('foo');
|
||||
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
await usageStatsClient.incrementLegacyDashboardsExport({
|
||||
request,
|
||||
} as IncrementSavedObjectsExportOptions);
|
||||
expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1);
|
||||
expect(repositoryMock.incrementCounter).toHaveBeenCalledWith(
|
||||
CORE_USAGE_STATS_TYPE,
|
||||
CORE_USAGE_STATS_ID,
|
||||
[
|
||||
`${LEGACY_DASHBOARDS_EXPORT_STATS_PREFIX}.total`,
|
||||
`${LEGACY_DASHBOARDS_EXPORT_STATS_PREFIX}.namespace.custom.total`,
|
||||
`${LEGACY_DASHBOARDS_EXPORT_STATS_PREFIX}.namespace.custom.kibanaRequest.no`,
|
||||
],
|
||||
incrementOptions
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -45,6 +45,9 @@ export const UPDATE_STATS_PREFIX = 'apiCalls.savedObjectsUpdate';
|
|||
export const IMPORT_STATS_PREFIX = 'apiCalls.savedObjectsImport';
|
||||
export const RESOLVE_IMPORT_STATS_PREFIX = 'apiCalls.savedObjectsResolveImportErrors';
|
||||
export const EXPORT_STATS_PREFIX = 'apiCalls.savedObjectsExport';
|
||||
export const LEGACY_DASHBOARDS_IMPORT_STATS_PREFIX = 'apiCalls.legacyDashboardImport';
|
||||
export const LEGACY_DASHBOARDS_EXPORT_STATS_PREFIX = 'apiCalls.legacyDashboardExport';
|
||||
|
||||
export const REPOSITORY_RESOLVE_OUTCOME_STATS = {
|
||||
EXACT_MATCH: 'savedObjectsRepository.resolvedOutcome.exactMatch',
|
||||
ALIAS_MATCH: 'savedObjectsRepository.resolvedOutcome.aliasMatch',
|
||||
|
@ -73,6 +76,8 @@ const ALL_COUNTER_FIELDS = [
|
|||
`${RESOLVE_IMPORT_STATS_PREFIX}.createNewCopiesEnabled.yes`,
|
||||
`${RESOLVE_IMPORT_STATS_PREFIX}.createNewCopiesEnabled.no`,
|
||||
...getFieldsForCounter(EXPORT_STATS_PREFIX),
|
||||
...getFieldsForCounter(LEGACY_DASHBOARDS_IMPORT_STATS_PREFIX),
|
||||
...getFieldsForCounter(LEGACY_DASHBOARDS_EXPORT_STATS_PREFIX),
|
||||
`${EXPORT_STATS_PREFIX}.allTypesSelected.yes`,
|
||||
`${EXPORT_STATS_PREFIX}.allTypesSelected.no`,
|
||||
// Saved Objects Repository counters; these are included here for stats collection, but are incremented in the repository itself
|
||||
|
@ -170,6 +175,14 @@ export class CoreUsageStatsClient {
|
|||
await this.updateUsageStats(counterFieldNames, EXPORT_STATS_PREFIX, options);
|
||||
}
|
||||
|
||||
public async incrementLegacyDashboardsImport(options: BaseIncrementOptions) {
|
||||
await this.updateUsageStats([], LEGACY_DASHBOARDS_IMPORT_STATS_PREFIX, options);
|
||||
}
|
||||
|
||||
public async incrementLegacyDashboardsExport(options: BaseIncrementOptions) {
|
||||
await this.updateUsageStats([], LEGACY_DASHBOARDS_EXPORT_STATS_PREFIX, options);
|
||||
}
|
||||
|
||||
private async updateUsageStats(
|
||||
counterFieldNames: string[],
|
||||
prefix: string,
|
||||
|
|
|
@ -110,6 +110,21 @@ export interface CoreUsageStats {
|
|||
'apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.no'?: number;
|
||||
'apiCalls.savedObjectsExport.allTypesSelected.yes'?: number;
|
||||
'apiCalls.savedObjectsExport.allTypesSelected.no'?: number;
|
||||
// Legacy Dashboard Import/Export API
|
||||
'apiCalls.legacyDashboardExport.total'?: number;
|
||||
'apiCalls.legacyDashboardExport.namespace.default.total'?: number;
|
||||
'apiCalls.legacyDashboardExport.namespace.default.kibanaRequest.yes'?: number;
|
||||
'apiCalls.legacyDashboardExport.namespace.default.kibanaRequest.no'?: number;
|
||||
'apiCalls.legacyDashboardExport.namespace.custom.total'?: number;
|
||||
'apiCalls.legacyDashboardExport.namespace.custom.kibanaRequest.yes'?: number;
|
||||
'apiCalls.legacyDashboardExport.namespace.custom.kibanaRequest.no'?: number;
|
||||
'apiCalls.legacyDashboardImport.total'?: number;
|
||||
'apiCalls.legacyDashboardImport.namespace.default.total'?: number;
|
||||
'apiCalls.legacyDashboardImport.namespace.default.kibanaRequest.yes'?: number;
|
||||
'apiCalls.legacyDashboardImport.namespace.default.kibanaRequest.no'?: number;
|
||||
'apiCalls.legacyDashboardImport.namespace.custom.total'?: number;
|
||||
'apiCalls.legacyDashboardImport.namespace.custom.kibanaRequest.yes'?: number;
|
||||
'apiCalls.legacyDashboardImport.namespace.custom.kibanaRequest.no'?: number;
|
||||
// Saved Objects Repository counters
|
||||
'savedObjectsRepository.resolvedOutcome.exactMatch'?: number;
|
||||
'savedObjectsRepository.resolvedOutcome.aliasMatch'?: number;
|
||||
|
|
|
@ -24,6 +24,8 @@ 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';
|
||||
|
||||
export function registerRoutes({
|
||||
http,
|
||||
|
@ -31,12 +33,14 @@ export function registerRoutes({
|
|||
logger,
|
||||
config,
|
||||
migratorPromise,
|
||||
kibanaVersion,
|
||||
}: {
|
||||
http: InternalHttpServiceSetup;
|
||||
coreUsageData: InternalCoreUsageDataSetup;
|
||||
logger: Logger;
|
||||
config: SavedObjectConfig;
|
||||
migratorPromise: Promise<IKibanaMigrator>;
|
||||
kibanaVersion: string;
|
||||
}) {
|
||||
const router = http.createRouter('/api/saved_objects/');
|
||||
|
||||
|
@ -53,6 +57,14 @@ export function registerRoutes({
|
|||
registerImportRoute(router, { config, coreUsageData });
|
||||
registerResolveImportErrorsRoute(router, { config, coreUsageData });
|
||||
|
||||
const legacyRouter = http.createRouter('');
|
||||
registerLegacyImportRoute(legacyRouter, {
|
||||
maxImportPayloadBytes: config.maxImportPayloadBytes,
|
||||
coreUsageData,
|
||||
logger,
|
||||
});
|
||||
registerLegacyExportRoute(legacyRouter, { kibanaVersion, coreUsageData, logger });
|
||||
|
||||
const internalRouter = http.createRouter('/internal/saved_objects/');
|
||||
|
||||
registerMigrateRoute(internalRouter, migratorPromise);
|
||||
|
|
|
@ -8,10 +8,18 @@
|
|||
|
||||
import moment from 'moment';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { IRouter } from 'src/core/server';
|
||||
import { exportDashboards } from '../lib';
|
||||
import { InternalCoreUsageDataSetup } from 'src/core/server/core_usage_data';
|
||||
import { IRouter, Logger } from '../../..';
|
||||
import { exportDashboards } from './lib';
|
||||
|
||||
export const registerExportRoute = (router: IRouter, kibanaVersion: string) => {
|
||||
export const registerLegacyExportRoute = (
|
||||
router: IRouter,
|
||||
{
|
||||
kibanaVersion,
|
||||
coreUsageData,
|
||||
logger,
|
||||
}: { kibanaVersion: string; coreUsageData: InternalCoreUsageDataSetup; logger: Logger }
|
||||
) => {
|
||||
router.get(
|
||||
{
|
||||
path: '/api/kibana/dashboards/export',
|
||||
|
@ -25,9 +33,16 @@ export const registerExportRoute = (router: IRouter, kibanaVersion: string) => {
|
|||
},
|
||||
},
|
||||
async (ctx, req, res) => {
|
||||
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(req.query.dashboard) ? req.query.dashboard : [req.query.dashboard];
|
||||
const { client } = ctx.core.savedObjects;
|
||||
|
||||
const usageStatsClient = coreUsageData.getClient();
|
||||
usageStatsClient.incrementLegacyDashboardsExport({ request: req }).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, ' ');
|
|
@ -7,10 +7,18 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { IRouter, SavedObject } from 'src/core/server';
|
||||
import { importDashboards } from '../lib';
|
||||
import { IRouter, Logger, SavedObject } from '../../..';
|
||||
import { InternalCoreUsageDataSetup } from '../../../core_usage_data';
|
||||
import { importDashboards } from './lib';
|
||||
|
||||
export const registerImportRoute = (router: IRouter, maxImportPayloadBytes: number) => {
|
||||
export const registerLegacyImportRoute = (
|
||||
router: IRouter,
|
||||
{
|
||||
maxImportPayloadBytes,
|
||||
coreUsageData,
|
||||
logger,
|
||||
}: { maxImportPayloadBytes: number; coreUsageData: InternalCoreUsageDataSetup; logger: Logger }
|
||||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: '/api/kibana/dashboards/import',
|
||||
|
@ -34,9 +42,17 @@ export const registerImportRoute = (router: IRouter, maxImportPayloadBytes: numb
|
|||
},
|
||||
},
|
||||
async (ctx, req, res) => {
|
||||
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 } = ctx.core.savedObjects;
|
||||
const objects = req.body.objects as SavedObject[];
|
||||
const { force, exclude } = req.query;
|
||||
|
||||
const usageStatsClient = coreUsageData.getClient();
|
||||
usageStatsClient.incrementLegacyDashboardsImport({ request: req }).catch(() => {});
|
||||
|
||||
const result = await importDashboards(client, objects, {
|
||||
overwrite: force,
|
||||
exclude: Array.isArray(exclude) ? exclude : [exclude],
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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 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 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',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
jest.mock('../lib/export_dashboards', () => ({
|
||||
exportDashboards: jest.fn().mockResolvedValue({ version: 'mockversion', objects: exportObjects }),
|
||||
}));
|
||||
|
||||
import supertest from 'supertest';
|
||||
import type { UnwrapPromise } from '@kbn/utility-types';
|
||||
import { CoreUsageStatsClient } from '../../../../core_usage_data';
|
||||
import { coreUsageStatsClientMock } from '../../../../core_usage_data/core_usage_stats_client.mock';
|
||||
import { coreUsageDataServiceMock } from '../../../../core_usage_data/core_usage_data_service.mock';
|
||||
import { registerLegacyExportRoute } from '../export';
|
||||
import { setupServer } from '../../test_utils';
|
||||
import { loggerMock } from 'src/core/server/logging/logger.mock';
|
||||
|
||||
type SetupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;
|
||||
let coreUsageStatsClient: jest.Mocked<CoreUsageStatsClient>;
|
||||
|
||||
describe('POST /api/dashboards/export', () => {
|
||||
let server: SetupServerReturn['server'];
|
||||
let httpSetup: SetupServerReturn['httpSetup'];
|
||||
|
||||
beforeEach(async () => {
|
||||
({ server, httpSetup } = await setupServer());
|
||||
|
||||
const router = httpSetup.createRouter('');
|
||||
|
||||
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: '7.14.0',
|
||||
coreUsageData,
|
||||
logger: loggerMock.create(),
|
||||
});
|
||||
|
||||
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'
|
||||
);
|
||||
|
||||
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(),
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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 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 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',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
jest.mock('../lib/import_dashboards', () => ({
|
||||
importDashboards: jest.fn().mockResolvedValue({ objects: importObjects }),
|
||||
}));
|
||||
|
||||
import supertest from 'supertest';
|
||||
import type { UnwrapPromise } from '@kbn/utility-types';
|
||||
import { CoreUsageStatsClient } from '../../../../core_usage_data';
|
||||
import { coreUsageStatsClientMock } from '../../../../core_usage_data/core_usage_stats_client.mock';
|
||||
import { coreUsageDataServiceMock } from '../../../../core_usage_data/core_usage_data_service.mock';
|
||||
import { registerLegacyImportRoute } from '../import';
|
||||
import { setupServer } from '../../test_utils';
|
||||
import { loggerMock } from 'src/core/server/logging/logger.mock';
|
||||
|
||||
type SetupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;
|
||||
let coreUsageStatsClient: jest.Mocked<CoreUsageStatsClient>;
|
||||
|
||||
describe('POST /api/dashboards/import', () => {
|
||||
let server: SetupServerReturn['server'];
|
||||
let httpSetup: SetupServerReturn['httpSetup'];
|
||||
|
||||
beforeEach(async () => {
|
||||
({ server, httpSetup } = await setupServer());
|
||||
|
||||
const router = httpSetup.createRouter('');
|
||||
|
||||
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(),
|
||||
});
|
||||
|
||||
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')
|
||||
.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(),
|
||||
});
|
||||
});
|
||||
});
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import { SavedObject, SavedObjectAttributes } from 'src/core/server';
|
||||
import { savedObjectsClientMock } from '../../../../../core/server/mocks';
|
||||
import { savedObjectsClientMock } from '../../../../mocks';
|
||||
import { collectReferencesDeep } from './collect_references_deep';
|
||||
|
||||
const data: Array<SavedObject<SavedObjectAttributes>> = [
|
|
@ -6,8 +6,8 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { savedObjectsClientMock } from '../../../../../core/server/mocks';
|
||||
import { SavedObject } from '../../../../../core/server';
|
||||
import { savedObjectsClientMock } from '../../../../mocks';
|
||||
import { SavedObject } from '../../../..';
|
||||
import { importDashboards } from './import_dashboards';
|
||||
|
||||
describe('importDashboards(req)', () => {
|
|
@ -6,8 +6,5 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../..',
|
||||
roots: ['<rootDir>/src/plugins/legacy_export'],
|
||||
};
|
||||
export { exportDashboards } from './export_dashboards';
|
||||
export { importDashboards } from './import_dashboards';
|
|
@ -306,6 +306,7 @@ export class SavedObjectsService
|
|||
logger: this.logger,
|
||||
config: this.config,
|
||||
migratorPromise: this.migrator$.pipe(first()).toPromise(),
|
||||
kibanaVersion: this.coreContext.env.packageInfo.version,
|
||||
});
|
||||
|
||||
registerCoreObjectTypes(this.typeRegistry);
|
||||
|
|
|
@ -491,6 +491,34 @@ export interface CoreUsageDataStart {
|
|||
|
||||
// @internal
|
||||
export interface CoreUsageStats {
|
||||
// (undocumented)
|
||||
'apiCalls.legacyDashboardExport.namespace.custom.kibanaRequest.no'?: number;
|
||||
// (undocumented)
|
||||
'apiCalls.legacyDashboardExport.namespace.custom.kibanaRequest.yes'?: number;
|
||||
// (undocumented)
|
||||
'apiCalls.legacyDashboardExport.namespace.custom.total'?: number;
|
||||
// (undocumented)
|
||||
'apiCalls.legacyDashboardExport.namespace.default.kibanaRequest.no'?: number;
|
||||
// (undocumented)
|
||||
'apiCalls.legacyDashboardExport.namespace.default.kibanaRequest.yes'?: number;
|
||||
// (undocumented)
|
||||
'apiCalls.legacyDashboardExport.namespace.default.total'?: number;
|
||||
// (undocumented)
|
||||
'apiCalls.legacyDashboardExport.total'?: number;
|
||||
// (undocumented)
|
||||
'apiCalls.legacyDashboardImport.namespace.custom.kibanaRequest.no'?: number;
|
||||
// (undocumented)
|
||||
'apiCalls.legacyDashboardImport.namespace.custom.kibanaRequest.yes'?: number;
|
||||
// (undocumented)
|
||||
'apiCalls.legacyDashboardImport.namespace.custom.total'?: number;
|
||||
// (undocumented)
|
||||
'apiCalls.legacyDashboardImport.namespace.default.kibanaRequest.no'?: number;
|
||||
// (undocumented)
|
||||
'apiCalls.legacyDashboardImport.namespace.default.kibanaRequest.yes'?: number;
|
||||
// (undocumented)
|
||||
'apiCalls.legacyDashboardImport.namespace.default.total'?: number;
|
||||
// (undocumented)
|
||||
'apiCalls.legacyDashboardImport.total'?: number;
|
||||
// (undocumented)
|
||||
'apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.no'?: number;
|
||||
// (undocumented)
|
||||
|
|
|
@ -936,11 +936,51 @@ export function getCoreUsageCollector(
|
|||
'How many times this API has been called by a non-Kibana client in a custom space.',
|
||||
},
|
||||
},
|
||||
'apiCalls.savedObjectsExport.allTypesSelected.yes': {
|
||||
// Legacy dashboard import/export APIs
|
||||
'apiCalls.legacyDashboardExport.total': {
|
||||
type: 'long',
|
||||
_meta: { description: 'How many times this API has been called.' },
|
||||
},
|
||||
'apiCalls.legacyDashboardExport.namespace.default.total': {
|
||||
type: 'long',
|
||||
_meta: { description: 'How many times this API has been called in the Default space.' },
|
||||
},
|
||||
'apiCalls.legacyDashboardExport.namespace.default.kibanaRequest.yes': {
|
||||
type: 'long',
|
||||
_meta: {
|
||||
description:
|
||||
'How many times this API has been called with the `createNewCopiesEnabled` option.',
|
||||
'How many times this API has been called by the Kibana client in the Default space.',
|
||||
},
|
||||
},
|
||||
'apiCalls.legacyDashboardExport.namespace.default.kibanaRequest.no': {
|
||||
type: 'long',
|
||||
_meta: {
|
||||
description:
|
||||
'How many times this API has been called by a non-Kibana client in the Default space.',
|
||||
},
|
||||
},
|
||||
'apiCalls.legacyDashboardExport.namespace.custom.total': {
|
||||
type: 'long',
|
||||
_meta: { description: 'How many times this API has been called in a custom space.' },
|
||||
},
|
||||
'apiCalls.legacyDashboardExport.namespace.custom.kibanaRequest.yes': {
|
||||
type: 'long',
|
||||
_meta: {
|
||||
description:
|
||||
'How many times this API has been called by the Kibana client in a custom space.',
|
||||
},
|
||||
},
|
||||
'apiCalls.legacyDashboardExport.namespace.custom.kibanaRequest.no': {
|
||||
type: 'long',
|
||||
_meta: {
|
||||
description:
|
||||
'How many times this API has been called by a non-Kibana client in a custom space.',
|
||||
},
|
||||
},
|
||||
'apiCalls.savedObjectsExport.allTypesSelected.yes': {
|
||||
type: 'long',
|
||||
_meta: {
|
||||
description: 'How many times this API has been called with all types selected.',
|
||||
},
|
||||
},
|
||||
'apiCalls.savedObjectsExport.allTypesSelected.no': {
|
||||
|
@ -949,6 +989,46 @@ export function getCoreUsageCollector(
|
|||
description: 'How many times this API has been called without all types selected.',
|
||||
},
|
||||
},
|
||||
'apiCalls.legacyDashboardImport.total': {
|
||||
type: 'long',
|
||||
_meta: { description: 'How many times this API has been called.' },
|
||||
},
|
||||
'apiCalls.legacyDashboardImport.namespace.default.total': {
|
||||
type: 'long',
|
||||
_meta: { description: 'How many times this API has been called in the Default space.' },
|
||||
},
|
||||
'apiCalls.legacyDashboardImport.namespace.default.kibanaRequest.yes': {
|
||||
type: 'long',
|
||||
_meta: {
|
||||
description:
|
||||
'How many times this API has been called by the Kibana client in the Default space.',
|
||||
},
|
||||
},
|
||||
'apiCalls.legacyDashboardImport.namespace.default.kibanaRequest.no': {
|
||||
type: 'long',
|
||||
_meta: {
|
||||
description:
|
||||
'How many times this API has been called by a non-Kibana client in the Default space.',
|
||||
},
|
||||
},
|
||||
'apiCalls.legacyDashboardImport.namespace.custom.total': {
|
||||
type: 'long',
|
||||
_meta: { description: 'How many times this API has been called in a custom space.' },
|
||||
},
|
||||
'apiCalls.legacyDashboardImport.namespace.custom.kibanaRequest.yes': {
|
||||
type: 'long',
|
||||
_meta: {
|
||||
description:
|
||||
'How many times this API has been called by the Kibana client in a custom space.',
|
||||
},
|
||||
},
|
||||
'apiCalls.legacyDashboardImport.namespace.custom.kibanaRequest.no': {
|
||||
type: 'long',
|
||||
_meta: {
|
||||
description:
|
||||
'How many times this API has been called by a non-Kibana client in a custom space.',
|
||||
},
|
||||
},
|
||||
// Saved Objects Repository counters
|
||||
'savedObjectsRepository.resolvedOutcome.exactMatch': {
|
||||
type: 'long',
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
# `legacyExport` plugin [deprecated]
|
||||
|
||||
The `legacyExport` plugin adds support for the legacy saved objects export format.
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"id": "legacyExport",
|
||||
"owner": {
|
||||
"name": "Kibana Core",
|
||||
"githubTeam": "kibana-core"
|
||||
},
|
||||
"version": "kibana",
|
||||
"server": true,
|
||||
"ui": false
|
||||
}
|
|
@ -1,12 +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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PluginInitializer } from 'src/core/server';
|
||||
import { LegacyExportPlugin } from './plugin';
|
||||
|
||||
export const plugin: PluginInitializer<{}, {}> = (context) => new LegacyExportPlugin(context);
|
|
@ -1,10 +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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { exportDashboards } from './export/export_dashboards';
|
||||
export { importDashboards } from './import/import_dashboards';
|
|
@ -1,34 +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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Plugin, CoreSetup, PluginInitializerContext } from 'kibana/server';
|
||||
import { registerRoutes } from './routes';
|
||||
|
||||
/** @deprecated */
|
||||
export class LegacyExportPlugin implements Plugin<{}, {}> {
|
||||
constructor(private readonly initContext: PluginInitializerContext) {}
|
||||
|
||||
public setup({ http }: CoreSetup) {
|
||||
const globalConfig = this.initContext.config.legacy.get();
|
||||
|
||||
const router = http.createRouter();
|
||||
registerRoutes(
|
||||
router,
|
||||
this.initContext.env.packageInfo.version,
|
||||
globalConfig.savedObjects.maxImportPayloadBytes.getValueInBytes()
|
||||
);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
public start() {
|
||||
return {};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
|
@ -1,20 +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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { IRouter } from 'src/core/server';
|
||||
import { registerImportRoute } from './import';
|
||||
import { registerExportRoute } from './export';
|
||||
|
||||
export const registerRoutes = (
|
||||
router: IRouter,
|
||||
kibanaVersion: string,
|
||||
maxImportPayloadBytes: number
|
||||
) => {
|
||||
registerExportRoute(router, kibanaVersion);
|
||||
registerImportRoute(router, maxImportPayloadBytes);
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./target/types",
|
||||
"emitDeclarationOnly": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true
|
||||
},
|
||||
"include": [
|
||||
"server/**/*",
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../core/tsconfig.json" }
|
||||
]
|
||||
}
|
|
@ -6850,10 +6850,52 @@
|
|||
"description": "How many times this API has been called by a non-Kibana client in a custom space."
|
||||
}
|
||||
},
|
||||
"apiCalls.legacyDashboardExport.total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "How many times this API has been called."
|
||||
}
|
||||
},
|
||||
"apiCalls.legacyDashboardExport.namespace.default.total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "How many times this API has been called in the Default space."
|
||||
}
|
||||
},
|
||||
"apiCalls.legacyDashboardExport.namespace.default.kibanaRequest.yes": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "How many times this API has been called by the Kibana client in the Default space."
|
||||
}
|
||||
},
|
||||
"apiCalls.legacyDashboardExport.namespace.default.kibanaRequest.no": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "How many times this API has been called by a non-Kibana client in the Default space."
|
||||
}
|
||||
},
|
||||
"apiCalls.legacyDashboardExport.namespace.custom.total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "How many times this API has been called in a custom space."
|
||||
}
|
||||
},
|
||||
"apiCalls.legacyDashboardExport.namespace.custom.kibanaRequest.yes": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "How many times this API has been called by the Kibana client in a custom space."
|
||||
}
|
||||
},
|
||||
"apiCalls.legacyDashboardExport.namespace.custom.kibanaRequest.no": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "How many times this API has been called by a non-Kibana client in a custom space."
|
||||
}
|
||||
},
|
||||
"apiCalls.savedObjectsExport.allTypesSelected.yes": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "How many times this API has been called with the `createNewCopiesEnabled` option."
|
||||
"description": "How many times this API has been called with all types selected."
|
||||
}
|
||||
},
|
||||
"apiCalls.savedObjectsExport.allTypesSelected.no": {
|
||||
|
@ -6862,6 +6904,48 @@
|
|||
"description": "How many times this API has been called without all types selected."
|
||||
}
|
||||
},
|
||||
"apiCalls.legacyDashboardImport.total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "How many times this API has been called."
|
||||
}
|
||||
},
|
||||
"apiCalls.legacyDashboardImport.namespace.default.total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "How many times this API has been called in the Default space."
|
||||
}
|
||||
},
|
||||
"apiCalls.legacyDashboardImport.namespace.default.kibanaRequest.yes": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "How many times this API has been called by the Kibana client in the Default space."
|
||||
}
|
||||
},
|
||||
"apiCalls.legacyDashboardImport.namespace.default.kibanaRequest.no": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "How many times this API has been called by a non-Kibana client in the Default space."
|
||||
}
|
||||
},
|
||||
"apiCalls.legacyDashboardImport.namespace.custom.total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "How many times this API has been called in a custom space."
|
||||
}
|
||||
},
|
||||
"apiCalls.legacyDashboardImport.namespace.custom.kibanaRequest.yes": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "How many times this API has been called by the Kibana client in a custom space."
|
||||
}
|
||||
},
|
||||
"apiCalls.legacyDashboardImport.namespace.custom.kibanaRequest.no": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "How many times this API has been called by a non-Kibana client in a custom space."
|
||||
}
|
||||
},
|
||||
"savedObjectsRepository.resolvedOutcome.exactMatch": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
|
|
|
@ -52,7 +52,6 @@
|
|||
{ "path": "../src/plugins/url_forwarding/tsconfig.json" },
|
||||
{ "path": "../src/plugins/usage_collection/tsconfig.json" },
|
||||
{ "path": "../src/plugins/index_pattern_management/tsconfig.json" },
|
||||
{ "path": "../src/plugins/legacy_export/tsconfig.json" },
|
||||
{ "path": "../src/plugins/visualize/tsconfig.json" },
|
||||
{ "path": "plugin_functional/plugins/core_app_status/tsconfig.json" },
|
||||
{ "path": "plugin_functional/plugins/core_provider_plugin/tsconfig.json" },
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
{ "path": "../../src/plugins/kibana_react/tsconfig.json" },
|
||||
{ "path": "../../src/plugins/kibana_usage_collection/tsconfig.json" },
|
||||
{ "path": "../../src/plugins/kibana_utils/tsconfig.json" },
|
||||
{ "path": "../../src/plugins/legacy_export/tsconfig.json" },
|
||||
{ "path": "../../src/plugins/management/tsconfig.json" },
|
||||
{ "path": "../../src/plugins/navigation/tsconfig.json" },
|
||||
{ "path": "../../src/plugins/newsfeed/tsconfig.json" },
|
||||
|
|
Loading…
Reference in a new issue