Skip to content

Commit

Permalink
legacy dashboards import/export API: deprecation logs and usage data (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
rudolf committed Sep 8, 2021
1 parent dda09b6 commit 5464af6
Show file tree
Hide file tree
Showing 31 changed files with 565 additions and 136 deletions.
1 change: 0 additions & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 0 additions & 4 deletions docs/developer/plugin-list.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 0 additions & 8 deletions docs/user/api.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
112 changes: 112 additions & 0 deletions src/core/server/core_usage_data/core_usage_stats_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
);
});
});
});
13 changes: 13 additions & 0 deletions src/core/server/core_usage_data/core_usage_stats_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
15 changes: 15 additions & 0 deletions src/core/server/core_usage_data/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions src/core/server/saved_objects/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,23 @@ 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,
coreUsageData,
logger,
config,
migratorPromise,
kibanaVersion,
}: {
http: InternalHttpServiceSetup;
coreUsageData: InternalCoreUsageDataSetup;
logger: Logger;
config: SavedObjectConfig;
migratorPromise: Promise<IKibanaMigrator>;
kibanaVersion: string;
}) {
const router = http.createRouter('/api/saved_objects/');

Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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, ' ');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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],
Expand Down
Loading

0 comments on commit 5464af6

Please sign in to comment.