From b46f5e641f76296ba483fcc5d9700b66606a9d5a Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Sat, 12 Dec 2020 00:11:29 -0500 Subject: [PATCH] Add usage stats for other saved objects APIs --- .../core_usage_stats_client.mock.ts | 8 + .../core_usage_stats_client.test.ts | 609 ++++++++++++++++++ .../core_usage_stats_client.ts | 52 +- src/core/server/core_usage_data/types.ts | 58 ++ .../saved_objects/routes/bulk_create.ts | 11 +- .../server/saved_objects/routes/bulk_get.ts | 10 +- .../saved_objects/routes/bulk_update.ts | 10 +- .../server/saved_objects/routes/create.ts | 10 +- .../server/saved_objects/routes/delete.ts | 11 +- src/core/server/saved_objects/routes/find.ts | 10 +- src/core/server/saved_objects/routes/get.ts | 11 +- src/core/server/saved_objects/routes/index.ts | 16 +- .../integration_tests/bulk_create.test.ts | 14 +- .../routes/integration_tests/bulk_get.test.ts | 14 +- .../integration_tests/bulk_update.test.ts | 14 +- .../routes/integration_tests/create.test.ts | 14 +- .../routes/integration_tests/delete.test.ts | 14 +- .../routes/integration_tests/export.test.ts | 2 +- .../routes/integration_tests/find.test.ts | 14 +- .../routes/integration_tests/get.test.ts | 14 +- .../routes/integration_tests/import.test.ts | 2 +- .../resolve_import_errors.test.ts | 2 +- .../routes/integration_tests/update.test.ts | 14 +- .../server/saved_objects/routes/update.ts | 10 +- src/core/server/server.api.md | 112 ++++ .../collectors/core/core_usage_collector.ts | 58 ++ src/plugins/telemetry/schema/oss_plugins.json | 168 +++++ 27 files changed, 1246 insertions(+), 36 deletions(-) diff --git a/src/core/server/core_usage_data/core_usage_stats_client.mock.ts b/src/core/server/core_usage_data/core_usage_stats_client.mock.ts index 3bfb411c9dd49e1..ef350a9bb4c5cf0 100644 --- a/src/core/server/core_usage_data/core_usage_stats_client.mock.ts +++ b/src/core/server/core_usage_data/core_usage_stats_client.mock.ts @@ -22,6 +22,14 @@ import { CoreUsageStatsClient } from '.'; const createUsageStatsClientMock = () => (({ getUsageStats: jest.fn().mockResolvedValue({}), + incrementSavedObjectsBulkCreate: jest.fn().mockResolvedValue(null), + incrementSavedObjectsBulkGet: jest.fn().mockResolvedValue(null), + incrementSavedObjectsBulkUpdate: jest.fn().mockResolvedValue(null), + incrementSavedObjectsCreate: jest.fn().mockResolvedValue(null), + incrementSavedObjectsDelete: jest.fn().mockResolvedValue(null), + incrementSavedObjectsFind: jest.fn().mockResolvedValue(null), + incrementSavedObjectsGet: jest.fn().mockResolvedValue(null), + incrementSavedObjectsUpdate: jest.fn().mockResolvedValue(null), incrementSavedObjectsImport: jest.fn().mockResolvedValue(null), incrementSavedObjectsResolveImportErrors: jest.fn().mockResolvedValue(null), incrementSavedObjectsExport: jest.fn().mockResolvedValue(null), diff --git a/src/core/server/core_usage_data/core_usage_stats_client.test.ts b/src/core/server/core_usage_data/core_usage_stats_client.test.ts index e2b79e7ae8a5e81..405e8f00ac4aa27 100644 --- a/src/core/server/core_usage_data/core_usage_stats_client.test.ts +++ b/src/core/server/core_usage_data/core_usage_stats_client.test.ts @@ -20,9 +20,18 @@ import { httpServerMock, httpServiceMock, savedObjectsRepositoryMock } from '../mocks'; import { CORE_USAGE_STATS_TYPE, CORE_USAGE_STATS_ID } from './constants'; import { + BaseIncrementOptions, IncrementSavedObjectsImportOptions, IncrementSavedObjectsResolveImportErrorsOptions, IncrementSavedObjectsExportOptions, + BULK_CREATE_STATS_PREFIX, + BULK_GET_STATS_PREFIX, + BULK_UPDATE_STATS_PREFIX, + CREATE_STATS_PREFIX, + DELETE_STATS_PREFIX, + FIND_STATS_PREFIX, + GET_STATS_PREFIX, + UPDATE_STATS_PREFIX, IMPORT_STATS_PREFIX, RESOLVE_IMPORT_STATS_PREFIX, EXPORT_STATS_PREFIX, @@ -71,6 +80,606 @@ describe('CoreUsageStatsClient', () => { }); }); + describe('#incrementSavedObjectsBulkCreate', () => { + 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.incrementSavedObjectsBulkCreate({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsBulkCreate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_CREATE_STATS_PREFIX}.total`, + `${BULK_CREATE_STATS_PREFIX}.namespace.default.total`, + `${BULK_CREATE_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsBulkCreate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_CREATE_STATS_PREFIX}.total`, + `${BULK_CREATE_STATS_PREFIX}.namespace.default.total`, + `${BULK_CREATE_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsBulkCreate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_CREATE_STATS_PREFIX}.total`, + `${BULK_CREATE_STATS_PREFIX}.namespace.custom.total`, + `${BULK_CREATE_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + + describe('#incrementSavedObjectsBulkGet', () => { + 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.incrementSavedObjectsBulkGet({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsBulkGet({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_GET_STATS_PREFIX}.total`, + `${BULK_GET_STATS_PREFIX}.namespace.default.total`, + `${BULK_GET_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsBulkGet({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_GET_STATS_PREFIX}.total`, + `${BULK_GET_STATS_PREFIX}.namespace.default.total`, + `${BULK_GET_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsBulkGet({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_GET_STATS_PREFIX}.total`, + `${BULK_GET_STATS_PREFIX}.namespace.custom.total`, + `${BULK_GET_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + + describe('#incrementSavedObjectsBulkUpdate', () => { + 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.incrementSavedObjectsBulkUpdate({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsBulkUpdate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_UPDATE_STATS_PREFIX}.total`, + `${BULK_UPDATE_STATS_PREFIX}.namespace.default.total`, + `${BULK_UPDATE_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsBulkUpdate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_UPDATE_STATS_PREFIX}.total`, + `${BULK_UPDATE_STATS_PREFIX}.namespace.default.total`, + `${BULK_UPDATE_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsBulkUpdate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_UPDATE_STATS_PREFIX}.total`, + `${BULK_UPDATE_STATS_PREFIX}.namespace.custom.total`, + `${BULK_UPDATE_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + + describe('#incrementSavedObjectsCreate', () => { + 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.incrementSavedObjectsCreate({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsCreate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${CREATE_STATS_PREFIX}.total`, + `${CREATE_STATS_PREFIX}.namespace.default.total`, + `${CREATE_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsCreate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${CREATE_STATS_PREFIX}.total`, + `${CREATE_STATS_PREFIX}.namespace.default.total`, + `${CREATE_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsCreate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${CREATE_STATS_PREFIX}.total`, + `${CREATE_STATS_PREFIX}.namespace.custom.total`, + `${CREATE_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + + describe('#incrementSavedObjectsDelete', () => { + 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.incrementSavedObjectsDelete({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsDelete({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${DELETE_STATS_PREFIX}.total`, + `${DELETE_STATS_PREFIX}.namespace.default.total`, + `${DELETE_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsDelete({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${DELETE_STATS_PREFIX}.total`, + `${DELETE_STATS_PREFIX}.namespace.default.total`, + `${DELETE_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsDelete({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${DELETE_STATS_PREFIX}.total`, + `${DELETE_STATS_PREFIX}.namespace.custom.total`, + `${DELETE_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + + describe('#incrementSavedObjectsFind', () => { + 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.incrementSavedObjectsFind({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsFind({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${FIND_STATS_PREFIX}.total`, + `${FIND_STATS_PREFIX}.namespace.default.total`, + `${FIND_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsFind({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${FIND_STATS_PREFIX}.total`, + `${FIND_STATS_PREFIX}.namespace.default.total`, + `${FIND_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsFind({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${FIND_STATS_PREFIX}.total`, + `${FIND_STATS_PREFIX}.namespace.custom.total`, + `${FIND_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + + describe('#incrementSavedObjectsGet', () => { + 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.incrementSavedObjectsGet({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsGet({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${GET_STATS_PREFIX}.total`, + `${GET_STATS_PREFIX}.namespace.default.total`, + `${GET_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsGet({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${GET_STATS_PREFIX}.total`, + `${GET_STATS_PREFIX}.namespace.default.total`, + `${GET_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsGet({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${GET_STATS_PREFIX}.total`, + `${GET_STATS_PREFIX}.namespace.custom.total`, + `${GET_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + + describe('#incrementSavedObjectsUpdate', () => { + 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.incrementSavedObjectsUpdate({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsUpdate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${UPDATE_STATS_PREFIX}.total`, + `${UPDATE_STATS_PREFIX}.namespace.default.total`, + `${UPDATE_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsUpdate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${UPDATE_STATS_PREFIX}.total`, + `${UPDATE_STATS_PREFIX}.namespace.default.total`, + `${UPDATE_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsUpdate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${UPDATE_STATS_PREFIX}.total`, + `${UPDATE_STATS_PREFIX}.namespace.custom.total`, + `${UPDATE_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + describe('#incrementSavedObjectsImport', () => { it('does not throw an error if repository incrementCounter operation fails', async () => { const { usageStatsClient, repositoryMock } = setup(); diff --git a/src/core/server/core_usage_data/core_usage_stats_client.ts b/src/core/server/core_usage_data/core_usage_stats_client.ts index 7e4d12b6a462c8c..199d264ca64fea7 100644 --- a/src/core/server/core_usage_data/core_usage_stats_client.ts +++ b/src/core/server/core_usage_data/core_usage_stats_client.ts @@ -29,7 +29,8 @@ import { IBasePath, } from '..'; -interface BaseIncrementOptions { +/** @internal */ +export interface BaseIncrementOptions { request: KibanaRequest; } /** @internal */ @@ -42,10 +43,27 @@ export type IncrementSavedObjectsResolveImportErrorsOptions = BaseIncrementOptio export type IncrementSavedObjectsExportOptions = BaseIncrementOptions & Pick & { supportedTypes: string[] }; +export const BULK_CREATE_STATS_PREFIX = 'apiCalls.savedObjectsBulkCreate'; +export const BULK_GET_STATS_PREFIX = 'apiCalls.savedObjectsBulkGet'; +export const BULK_UPDATE_STATS_PREFIX = 'apiCalls.savedObjectsBulkUpdate'; +export const CREATE_STATS_PREFIX = 'apiCalls.savedObjectsCreate'; +export const DELETE_STATS_PREFIX = 'apiCalls.savedObjectsDelete'; +export const FIND_STATS_PREFIX = 'apiCalls.savedObjectsFind'; +export const GET_STATS_PREFIX = 'apiCalls.savedObjectsGet'; +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'; const ALL_COUNTER_FIELDS = [ + // Saved Objects Client APIs + ...getAllCommonFields(BULK_CREATE_STATS_PREFIX), + ...getAllCommonFields(BULK_GET_STATS_PREFIX), + ...getAllCommonFields(BULK_UPDATE_STATS_PREFIX), + ...getAllCommonFields(CREATE_STATS_PREFIX), + ...getAllCommonFields(DELETE_STATS_PREFIX), + ...getAllCommonFields(FIND_STATS_PREFIX), + ...getAllCommonFields(GET_STATS_PREFIX), + ...getAllCommonFields(UPDATE_STATS_PREFIX), // Saved Objects Management APIs ...getAllCommonFields(IMPORT_STATS_PREFIX), `${IMPORT_STATS_PREFIX}.createNewCopiesEnabled.yes`, @@ -87,6 +105,38 @@ export class CoreUsageStatsClient { return coreUsageStats; } + public async incrementSavedObjectsBulkCreate(options: BaseIncrementOptions) { + await this.updateUsageStats([], BULK_CREATE_STATS_PREFIX, options); + } + + public async incrementSavedObjectsBulkGet(options: BaseIncrementOptions) { + await this.updateUsageStats([], BULK_GET_STATS_PREFIX, options); + } + + public async incrementSavedObjectsBulkUpdate(options: BaseIncrementOptions) { + await this.updateUsageStats([], BULK_UPDATE_STATS_PREFIX, options); + } + + public async incrementSavedObjectsCreate(options: BaseIncrementOptions) { + await this.updateUsageStats([], CREATE_STATS_PREFIX, options); + } + + public async incrementSavedObjectsDelete(options: BaseIncrementOptions) { + await this.updateUsageStats([], DELETE_STATS_PREFIX, options); + } + + public async incrementSavedObjectsFind(options: BaseIncrementOptions) { + await this.updateUsageStats([], FIND_STATS_PREFIX, options); + } + + public async incrementSavedObjectsGet(options: BaseIncrementOptions) { + await this.updateUsageStats([], GET_STATS_PREFIX, options); + } + + public async incrementSavedObjectsUpdate(options: BaseIncrementOptions) { + await this.updateUsageStats([], UPDATE_STATS_PREFIX, options); + } + public async incrementSavedObjectsImport(options: IncrementSavedObjectsImportOptions) { const { createNewCopies, overwrite } = options; const counterFieldNames = [ diff --git a/src/core/server/core_usage_data/types.ts b/src/core/server/core_usage_data/types.ts index 2976da8c1a9a733..b7952334b4be441 100644 --- a/src/core/server/core_usage_data/types.ts +++ b/src/core/server/core_usage_data/types.ts @@ -27,6 +27,64 @@ import { ISavedObjectTypeRegistry, SavedObjectTypeRegistry } from '..'; * includes point-in-time configuration information. * */ export interface CoreUsageStats { + // Saved Objects Client APIs + 'apiCalls.savedObjectsBulkCreate.total'?: number; + 'apiCalls.savedObjectsBulkCreate.namespace.default.total'?: number; + 'apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsBulkCreate.namespace.custom.total'?: number; + 'apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsBulkGet.total'?: number; + 'apiCalls.savedObjectsBulkGet.namespace.default.total'?: number; + 'apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsBulkGet.namespace.custom.total'?: number; + 'apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsBulkUpdate.total'?: number; + 'apiCalls.savedObjectsBulkUpdate.namespace.default.total'?: number; + 'apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.total'?: number; + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsCreate.total'?: number; + 'apiCalls.savedObjectsCreate.namespace.default.total'?: number; + 'apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsCreate.namespace.custom.total'?: number; + 'apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsDelete.total'?: number; + 'apiCalls.savedObjectsDelete.namespace.default.total'?: number; + 'apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsDelete.namespace.custom.total'?: number; + 'apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsFind.total'?: number; + 'apiCalls.savedObjectsFind.namespace.default.total'?: number; + 'apiCalls.savedObjectsFind.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsFind.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsFind.namespace.custom.total'?: number; + 'apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsGet.total'?: number; + 'apiCalls.savedObjectsGet.namespace.default.total'?: number; + 'apiCalls.savedObjectsGet.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsGet.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsGet.namespace.custom.total'?: number; + 'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsUpdate.total'?: number; + 'apiCalls.savedObjectsUpdate.namespace.default.total'?: number; + 'apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsUpdate.namespace.custom.total'?: number; + 'apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.no'?: number; + // Saved Objects Management APIs 'apiCalls.savedObjectsImport.total'?: number; 'apiCalls.savedObjectsImport.namespace.default.total'?: number; 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.yes'?: number; diff --git a/src/core/server/saved_objects/routes/bulk_create.ts b/src/core/server/saved_objects/routes/bulk_create.ts index b048c5d8f99bfc4..b1286f3a1f06cb0 100644 --- a/src/core/server/saved_objects/routes/bulk_create.ts +++ b/src/core/server/saved_objects/routes/bulk_create.ts @@ -19,8 +19,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; -export const registerBulkCreateRoute = (router: IRouter) => { +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerBulkCreateRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { router.post( { path: '/_bulk_create', @@ -51,6 +56,10 @@ export const registerBulkCreateRoute = (router: IRouter) => { }, router.handleLegacyErrors(async (context, req, res) => { const { overwrite } = req.query; + + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsBulkCreate({ request: req }).catch(() => {}); + const result = await context.core.savedObjects.client.bulkCreate(req.body, { overwrite }); return res.ok({ body: result }); }) diff --git a/src/core/server/saved_objects/routes/bulk_get.ts b/src/core/server/saved_objects/routes/bulk_get.ts index 067388dcf922080..41c77520b4fafd7 100644 --- a/src/core/server/saved_objects/routes/bulk_get.ts +++ b/src/core/server/saved_objects/routes/bulk_get.ts @@ -19,8 +19,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; -export const registerBulkGetRoute = (router: IRouter) => { +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerBulkGetRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { router.post( { path: '/_bulk_get', @@ -35,6 +40,9 @@ export const registerBulkGetRoute = (router: IRouter) => { }, }, router.handleLegacyErrors(async (context, req, res) => { + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsBulkGet({ request: req }).catch(() => {}); + const result = await context.core.savedObjects.client.bulkGet(req.body); return res.ok({ body: result }); }) diff --git a/src/core/server/saved_objects/routes/bulk_update.ts b/src/core/server/saved_objects/routes/bulk_update.ts index 882213644146a11..b4014b5422d5d91 100644 --- a/src/core/server/saved_objects/routes/bulk_update.ts +++ b/src/core/server/saved_objects/routes/bulk_update.ts @@ -19,8 +19,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; -export const registerBulkUpdateRoute = (router: IRouter) => { +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerBulkUpdateRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { router.put( { path: '/_bulk_update', @@ -46,6 +51,9 @@ export const registerBulkUpdateRoute = (router: IRouter) => { }, }, router.handleLegacyErrors(async (context, req, res) => { + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsBulkUpdate({ request: req }).catch(() => {}); + const savedObject = await context.core.savedObjects.client.bulkUpdate(req.body); return res.ok({ body: savedObject }); }) diff --git a/src/core/server/saved_objects/routes/create.ts b/src/core/server/saved_objects/routes/create.ts index 816315705a375a4..cb6a849be9f2d93 100644 --- a/src/core/server/saved_objects/routes/create.ts +++ b/src/core/server/saved_objects/routes/create.ts @@ -19,8 +19,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; -export const registerCreateRoute = (router: IRouter) => { +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerCreateRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { router.post( { path: '/{type}/{id?}', @@ -53,6 +58,9 @@ export const registerCreateRoute = (router: IRouter) => { const { overwrite } = req.query; const { attributes, migrationVersion, references, initialNamespaces } = req.body; + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsCreate({ request: req }).catch(() => {}); + const options = { id, overwrite, migrationVersion, references, initialNamespaces }; const result = await context.core.savedObjects.client.create(type, attributes, options); return res.ok({ body: result }); diff --git a/src/core/server/saved_objects/routes/delete.ts b/src/core/server/saved_objects/routes/delete.ts index d99397d2a050c6b..69d2290325a9393 100644 --- a/src/core/server/saved_objects/routes/delete.ts +++ b/src/core/server/saved_objects/routes/delete.ts @@ -19,8 +19,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; -export const registerDeleteRoute = (router: IRouter) => { +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerDeleteRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { router.delete( { path: '/{type}/{id}', @@ -37,6 +42,10 @@ export const registerDeleteRoute = (router: IRouter) => { router.handleLegacyErrors(async (context, req, res) => { const { type, id } = req.params; const { force } = req.query; + + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsDelete({ request: req }).catch(() => {}); + const result = await context.core.savedObjects.client.delete(type, id, { force }); return res.ok({ body: result }); }) diff --git a/src/core/server/saved_objects/routes/find.ts b/src/core/server/saved_objects/routes/find.ts index 915d0cccf7af909..7ddcfa91da22d38 100644 --- a/src/core/server/saved_objects/routes/find.ts +++ b/src/core/server/saved_objects/routes/find.ts @@ -19,8 +19,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; -export const registerFindRoute = (router: IRouter) => { +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerFindRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { const referenceSchema = schema.object({ type: schema.string(), id: schema.string(), @@ -61,6 +66,9 @@ export const registerFindRoute = (router: IRouter) => { const namespaces = typeof req.query.namespaces === 'string' ? [req.query.namespaces] : req.query.namespaces; + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsFind({ request: req }).catch(() => {}); + const result = await context.core.savedObjects.client.find({ perPage: query.per_page, page: query.page, diff --git a/src/core/server/saved_objects/routes/get.ts b/src/core/server/saved_objects/routes/get.ts index f1b974c70b1a970..d29229eab33ff74 100644 --- a/src/core/server/saved_objects/routes/get.ts +++ b/src/core/server/saved_objects/routes/get.ts @@ -19,8 +19,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; -export const registerGetRoute = (router: IRouter) => { +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerGetRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { router.get( { path: '/{type}/{id}', @@ -33,6 +38,10 @@ export const registerGetRoute = (router: IRouter) => { }, router.handleLegacyErrors(async (context, req, res) => { const { type, id } = req.params; + + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsGet({ request: req }).catch(() => {}); + const savedObject = await context.core.savedObjects.client.get(type, id); return res.ok({ body: savedObject }); }) diff --git a/src/core/server/saved_objects/routes/index.ts b/src/core/server/saved_objects/routes/index.ts index 19154b85836546d..0ffd1104d35e27d 100644 --- a/src/core/server/saved_objects/routes/index.ts +++ b/src/core/server/saved_objects/routes/index.ts @@ -51,14 +51,14 @@ export function registerRoutes({ }) { const router = http.createRouter('/api/saved_objects/'); - registerGetRoute(router); - registerCreateRoute(router); - registerDeleteRoute(router); - registerFindRoute(router); - registerUpdateRoute(router); - registerBulkGetRoute(router); - registerBulkCreateRoute(router); - registerBulkUpdateRoute(router); + registerGetRoute(router, { coreUsageData }); + registerCreateRoute(router, { coreUsageData }); + registerDeleteRoute(router, { coreUsageData }); + registerFindRoute(router, { coreUsageData }); + registerUpdateRoute(router, { coreUsageData }); + registerBulkGetRoute(router, { coreUsageData }); + registerBulkCreateRoute(router, { coreUsageData }); + registerBulkUpdateRoute(router, { coreUsageData }); registerLogLegacyImportRoute(router, logger); registerExportRoute(router, { config, coreUsageData }); registerImportRoute(router, { config, coreUsageData }); diff --git a/src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts b/src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts index 3d455ff9d594c9c..186b21ef361a967 100644 --- a/src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts @@ -21,6 +21,9 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerBulkCreateRoute } from '../bulk_create'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; +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 { setupServer } from '../test_utils'; type SetupServerReturn = UnwrapPromise>; @@ -30,6 +33,7 @@ describe('POST /api/saved_objects/_bulk_create', () => { let httpSetup: SetupServerReturn['httpSetup']; let handlerContext: SetupServerReturn['handlerContext']; let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; beforeEach(async () => { ({ server, httpSetup, handlerContext } = await setupServer()); @@ -37,7 +41,10 @@ describe('POST /api/saved_objects/_bulk_create', () => { savedObjectsClient.bulkCreate.mockResolvedValue({ saved_objects: [] }); const router = httpSetup.createRouter('/api/saved_objects/'); - registerBulkCreateRoute(router); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsBulkCreate.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); + registerBulkCreateRoute(router, { coreUsageData }); await server.start(); }); @@ -46,7 +53,7 @@ describe('POST /api/saved_objects/_bulk_create', () => { await server.stop(); }); - it('formats successful response', async () => { + it('formats successful response and records usage stats', async () => { const clientResponse = { saved_objects: [ { @@ -75,6 +82,9 @@ describe('POST /api/saved_objects/_bulk_create', () => { .expect(200); expect(result.body).toEqual(clientResponse); + expect(coreUsageStatsClient.incrementSavedObjectsBulkCreate).toHaveBeenCalledWith({ + request: expect.anything(), + }); }); it('calls upon savedObjectClient.bulkCreate', async () => { diff --git a/src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts b/src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts index 5deea94299d7d90..c6028f86fcc7ce3 100644 --- a/src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts @@ -21,6 +21,9 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerBulkGetRoute } from '../bulk_get'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; +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 { setupServer } from '../test_utils'; type SetupServerReturn = UnwrapPromise>; @@ -30,6 +33,7 @@ describe('POST /api/saved_objects/_bulk_get', () => { let httpSetup: SetupServerReturn['httpSetup']; let handlerContext: SetupServerReturn['handlerContext']; let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; beforeEach(async () => { ({ server, httpSetup, handlerContext } = await setupServer()); @@ -39,7 +43,10 @@ describe('POST /api/saved_objects/_bulk_get', () => { saved_objects: [], }); const router = httpSetup.createRouter('/api/saved_objects/'); - registerBulkGetRoute(router); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsBulkGet.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); + registerBulkGetRoute(router, { coreUsageData }); await server.start(); }); @@ -48,7 +55,7 @@ describe('POST /api/saved_objects/_bulk_get', () => { await server.stop(); }); - it('formats successful response', async () => { + it('formats successful response and records usage stats', async () => { const clientResponse = { saved_objects: [ { @@ -74,6 +81,9 @@ describe('POST /api/saved_objects/_bulk_get', () => { .expect(200); expect(result.body).toEqual(clientResponse); + expect(coreUsageStatsClient.incrementSavedObjectsBulkGet).toHaveBeenCalledWith({ + request: expect.anything(), + }); }); it('calls upon savedObjectClient.bulkGet', async () => { diff --git a/src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts b/src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts index 45f310ecc3fa2e5..c038c5303dd6963 100644 --- a/src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts @@ -21,6 +21,9 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerBulkUpdateRoute } from '../bulk_update'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; +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 { setupServer } from '../test_utils'; type SetupServerReturn = UnwrapPromise>; @@ -30,13 +33,17 @@ describe('PUT /api/saved_objects/_bulk_update', () => { let httpSetup: SetupServerReturn['httpSetup']; let handlerContext: SetupServerReturn['handlerContext']; let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; beforeEach(async () => { ({ server, httpSetup, handlerContext } = await setupServer()); savedObjectsClient = handlerContext.savedObjects.client; const router = httpSetup.createRouter('/api/saved_objects/'); - registerBulkUpdateRoute(router); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsBulkUpdate.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); + registerBulkUpdateRoute(router, { coreUsageData }); await server.start(); }); @@ -45,7 +52,7 @@ describe('PUT /api/saved_objects/_bulk_update', () => { await server.stop(); }); - it('formats successful response', async () => { + it('formats successful response and records usage stats', async () => { const time = Date.now().toLocaleString(); const clientResponse = [ { @@ -92,6 +99,9 @@ describe('PUT /api/saved_objects/_bulk_update', () => { .expect(200); expect(result.body).toEqual({ saved_objects: clientResponse }); + expect(coreUsageStatsClient.incrementSavedObjectsBulkUpdate).toHaveBeenCalledWith({ + request: expect.anything(), + }); }); it('calls upon savedObjectClient.bulkUpdate', async () => { diff --git a/src/core/server/saved_objects/routes/integration_tests/create.test.ts b/src/core/server/saved_objects/routes/integration_tests/create.test.ts index 9e69c3dbc64ec12..8c209a05f294810 100644 --- a/src/core/server/saved_objects/routes/integration_tests/create.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/create.test.ts @@ -21,6 +21,9 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerCreateRoute } from '../create'; import { savedObjectsClientMock } from '../../service/saved_objects_client.mock'; +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 { setupServer } from '../test_utils'; type SetupServerReturn = UnwrapPromise>; @@ -30,6 +33,7 @@ describe('POST /api/saved_objects/{type}', () => { let httpSetup: SetupServerReturn['httpSetup']; let handlerContext: SetupServerReturn['handlerContext']; let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; const clientResponse = { id: 'logstash-*', @@ -46,7 +50,10 @@ describe('POST /api/saved_objects/{type}', () => { savedObjectsClient.create.mockImplementation(() => Promise.resolve(clientResponse)); const router = httpSetup.createRouter('/api/saved_objects/'); - registerCreateRoute(router); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsCreate.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); + registerCreateRoute(router, { coreUsageData }); await server.start(); }); @@ -55,7 +62,7 @@ describe('POST /api/saved_objects/{type}', () => { await server.stop(); }); - it('formats successful response', async () => { + it('formats successful response and records usage stats', async () => { const result = await supertest(httpSetup.server.listener) .post('/api/saved_objects/index-pattern') .send({ @@ -66,6 +73,9 @@ describe('POST /api/saved_objects/{type}', () => { .expect(200); expect(result.body).toEqual(clientResponse); + expect(coreUsageStatsClient.incrementSavedObjectsCreate).toHaveBeenCalledWith({ + request: expect.anything(), + }); }); it('requires attributes', async () => { diff --git a/src/core/server/saved_objects/routes/integration_tests/delete.test.ts b/src/core/server/saved_objects/routes/integration_tests/delete.test.ts index ff8642a34929fbc..c70754632980abb 100644 --- a/src/core/server/saved_objects/routes/integration_tests/delete.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/delete.test.ts @@ -21,6 +21,9 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerDeleteRoute } from '../delete'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; +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 { setupServer } from '../test_utils'; type SetupServerReturn = UnwrapPromise>; @@ -30,13 +33,17 @@ describe('DELETE /api/saved_objects/{type}/{id}', () => { let httpSetup: SetupServerReturn['httpSetup']; let handlerContext: SetupServerReturn['handlerContext']; let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; beforeEach(async () => { ({ server, httpSetup, handlerContext } = await setupServer()); savedObjectsClient = handlerContext.savedObjects.client; const router = httpSetup.createRouter('/api/saved_objects/'); - registerDeleteRoute(router); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsDelete.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); + registerDeleteRoute(router, { coreUsageData }); await server.start(); }); @@ -45,12 +52,15 @@ describe('DELETE /api/saved_objects/{type}/{id}', () => { await server.stop(); }); - it('formats successful response', async () => { + it('formats successful response and records usage stats', async () => { const result = await supertest(httpSetup.server.listener) .delete('/api/saved_objects/index-pattern/logstash-*') .expect(200); expect(result.body).toEqual({}); + expect(coreUsageStatsClient.incrementSavedObjectsDelete).toHaveBeenCalledWith({ + request: expect.anything(), + }); }); it('calls upon savedObjectClient.delete', async () => { diff --git a/src/core/server/saved_objects/routes/integration_tests/export.test.ts b/src/core/server/saved_objects/routes/integration_tests/export.test.ts index 1d4c5b7cbd15d8e..d5b1e492e573fce 100644 --- a/src/core/server/saved_objects/routes/integration_tests/export.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/export.test.ts @@ -54,7 +54,7 @@ describe('POST /api/saved_objects/_export', () => { const router = httpSetup.createRouter('/api/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); - coreUsageStatsClient.incrementSavedObjectsExport.mockRejectedValue(new Error('Oh no!')); // this error is intentionally swallowed so the export does not fail + coreUsageStatsClient.incrementSavedObjectsExport.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); registerExportRoute(router, { config, coreUsageData }); diff --git a/src/core/server/saved_objects/routes/integration_tests/find.test.ts b/src/core/server/saved_objects/routes/integration_tests/find.test.ts index 9a426ef48c7da85..8e3de04648b8349 100644 --- a/src/core/server/saved_objects/routes/integration_tests/find.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/find.test.ts @@ -23,6 +23,9 @@ import querystring from 'querystring'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerFindRoute } from '../find'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; +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 { setupServer } from '../test_utils'; type SetupServerReturn = UnwrapPromise>; @@ -32,6 +35,7 @@ describe('GET /api/saved_objects/_find', () => { let httpSetup: SetupServerReturn['httpSetup']; let handlerContext: SetupServerReturn['handlerContext']; let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; const clientResponse = { total: 0, @@ -47,7 +51,10 @@ describe('GET /api/saved_objects/_find', () => { savedObjectsClient.find.mockResolvedValue(clientResponse); const router = httpSetup.createRouter('/api/saved_objects/'); - registerFindRoute(router); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsFind.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); + registerFindRoute(router, { coreUsageData }); await server.start(); }); @@ -66,7 +73,7 @@ describe('GET /api/saved_objects/_find', () => { ); }); - it('formats successful response', async () => { + it('formats successful response and records usage stats', async () => { const findResponse = { total: 2, per_page: 2, @@ -103,6 +110,9 @@ describe('GET /api/saved_objects/_find', () => { .expect(200); expect(result.body).toEqual(findResponse); + expect(coreUsageStatsClient.incrementSavedObjectsFind).toHaveBeenCalledWith({ + request: expect.anything(), + }); }); it('calls upon savedObjectClient.find with defaults', async () => { diff --git a/src/core/server/saved_objects/routes/integration_tests/get.test.ts b/src/core/server/saved_objects/routes/integration_tests/get.test.ts index 1e3405d7a318f7d..e05b6b09659fad5 100644 --- a/src/core/server/saved_objects/routes/integration_tests/get.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/get.test.ts @@ -21,6 +21,9 @@ import supertest from 'supertest'; import { registerGetRoute } from '../get'; import { ContextService } from '../../../context'; import { savedObjectsClientMock } from '../../service/saved_objects_client.mock'; +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 { HttpService, InternalHttpServiceSetup } from '../../../http'; import { createHttpServer, createCoreContext } from '../../../http/test_utils'; import { coreMock } from '../../../mocks'; @@ -32,6 +35,7 @@ describe('GET /api/saved_objects/{type}/{id}', () => { let httpSetup: InternalHttpServiceSetup; let handlerContext: ReturnType; let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; beforeEach(async () => { const coreContext = createCoreContext({ coreId }); @@ -50,7 +54,10 @@ describe('GET /api/saved_objects/{type}/{id}', () => { }); const router = httpSetup.createRouter('/api/saved_objects/'); - registerGetRoute(router); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsGet.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); + registerGetRoute(router, { coreUsageData }); await server.start(); }); @@ -59,7 +66,7 @@ describe('GET /api/saved_objects/{type}/{id}', () => { await server.stop(); }); - it('formats successful response', async () => { + it('formats successful response and records usage stats', async () => { const clientResponse = { id: 'logstash-*', title: 'logstash-*', @@ -77,6 +84,9 @@ describe('GET /api/saved_objects/{type}/{id}', () => { .expect(200); expect(result.body).toEqual(clientResponse); + expect(coreUsageStatsClient.incrementSavedObjectsGet).toHaveBeenCalledWith({ + request: expect.anything(), + }); }); it('calls upon savedObjectClient.get', async () => { diff --git a/src/core/server/saved_objects/routes/integration_tests/import.test.ts b/src/core/server/saved_objects/routes/integration_tests/import.test.ts index 33885eb72b7182d..b80deb87725d487 100644 --- a/src/core/server/saved_objects/routes/integration_tests/import.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/import.test.ts @@ -76,7 +76,7 @@ describe(`POST ${URL}`, () => { const router = httpSetup.createRouter('/internal/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); - coreUsageStatsClient.incrementSavedObjectsImport.mockRejectedValue(new Error('Oh no!')); // this error is intentionally swallowed so the import does not fail + coreUsageStatsClient.incrementSavedObjectsImport.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); registerImportRoute(router, { config, coreUsageData }); diff --git a/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts b/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts index 64762e341185d8e..f135e34231cb666 100644 --- a/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts @@ -82,7 +82,7 @@ describe(`POST ${URL}`, () => { const router = httpSetup.createRouter('/api/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsResolveImportErrors.mockRejectedValue( - new Error('Oh no!') // this error is intentionally swallowed so the export does not fail + 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); registerResolveImportErrorsRoute(router, { config, coreUsageData }); diff --git a/src/core/server/saved_objects/routes/integration_tests/update.test.ts b/src/core/server/saved_objects/routes/integration_tests/update.test.ts index dfccb651d72d7a3..433ffb49e05e48f 100644 --- a/src/core/server/saved_objects/routes/integration_tests/update.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/update.test.ts @@ -21,6 +21,9 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerUpdateRoute } from '../update'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; +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 { setupServer } from '../test_utils'; type SetupServerReturn = UnwrapPromise>; @@ -30,6 +33,7 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => { let httpSetup: SetupServerReturn['httpSetup']; let handlerContext: SetupServerReturn['handlerContext']; let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; beforeEach(async () => { const clientResponse = { @@ -47,7 +51,10 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => { savedObjectsClient.update.mockResolvedValue(clientResponse); const router = httpSetup.createRouter('/api/saved_objects/'); - registerUpdateRoute(router); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsUpdate.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); + registerUpdateRoute(router, { coreUsageData }); await server.start(); }); @@ -56,7 +63,7 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => { await server.stop(); }); - it('formats successful response', async () => { + it('formats successful response and records usage stats', async () => { const clientResponse = { id: 'logstash-*', title: 'logstash-*', @@ -79,6 +86,9 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => { .expect(200); expect(result.body).toEqual(clientResponse); + expect(coreUsageStatsClient.incrementSavedObjectsUpdate).toHaveBeenCalledWith({ + request: expect.anything(), + }); }); it('calls upon savedObjectClient.update', async () => { diff --git a/src/core/server/saved_objects/routes/update.ts b/src/core/server/saved_objects/routes/update.ts index c0d94d362e648f4..95137cb6e77cfd1 100644 --- a/src/core/server/saved_objects/routes/update.ts +++ b/src/core/server/saved_objects/routes/update.ts @@ -19,8 +19,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; -export const registerUpdateRoute = (router: IRouter) => { +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerUpdateRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { router.put( { path: '/{type}/{id}', @@ -49,6 +54,9 @@ export const registerUpdateRoute = (router: IRouter) => { const { attributes, version, references } = req.body; const options = { version, references }; + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsUpdate({ request: req }).catch(() => {}); + const result = await context.core.savedObjects.client.update(type, id, attributes, options); return res.ok({ body: result }); }) diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 5557a09197fa9d9..1ab06b7912d1fe8 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -541,6 +541,76 @@ export interface CoreUsageDataStart { // @internal export interface CoreUsageStats { + // (undocumented) + 'apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkCreate.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkCreate.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkCreate.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkGet.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkGet.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkGet.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkUpdate.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkUpdate.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsCreate.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsCreate.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsCreate.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsDelete.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsDelete.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsDelete.total'?: number; // (undocumented) 'apiCalls.savedObjectsExport.allTypesSelected.no'?: number; // (undocumented) @@ -560,6 +630,34 @@ export interface CoreUsageStats { // (undocumented) 'apiCalls.savedObjectsExport.total'?: number; // (undocumented) + 'apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsFind.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsFind.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsFind.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsFind.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsFind.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsGet.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsGet.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsGet.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsGet.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsGet.total'?: number; + // (undocumented) 'apiCalls.savedObjectsImport.createNewCopiesEnabled.no'?: number; // (undocumented) 'apiCalls.savedObjectsImport.createNewCopiesEnabled.yes'?: number; @@ -599,6 +697,20 @@ export interface CoreUsageStats { 'apiCalls.savedObjectsResolveImportErrors.namespace.default.total'?: number; // (undocumented) 'apiCalls.savedObjectsResolveImportErrors.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsUpdate.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsUpdate.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsUpdate.total'?: number; } // @public (undocumented) diff --git a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts index eaad760c922baf8..a0960b30a2e870c 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts @@ -115,6 +115,64 @@ export function getCoreUsageCollector( }, }, }, + // Saved Objects Client APIs + 'apiCalls.savedObjectsBulkCreate.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkCreate.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsBulkCreate.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsBulkGet.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkGet.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsBulkGet.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsBulkUpdate.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkUpdate.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsCreate.total': { type: 'long' }, + 'apiCalls.savedObjectsCreate.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsCreate.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsDelete.total': { type: 'long' }, + 'apiCalls.savedObjectsDelete.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsDelete.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsFind.total': { type: 'long' }, + 'apiCalls.savedObjectsFind.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsFind.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsFind.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsFind.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsGet.total': { type: 'long' }, + 'apiCalls.savedObjectsGet.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsGet.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsGet.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsGet.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsUpdate.total': { type: 'long' }, + 'apiCalls.savedObjectsUpdate.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsUpdate.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.no': { type: 'long' }, + // Saved Objects Management APIs 'apiCalls.savedObjectsImport.total': { type: 'long' }, 'apiCalls.savedObjectsImport.namespace.default.total': { type: 'long' }, 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.yes': { type: 'long' }, diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index b14a5907c7ab4a2..d486c06568c1b74 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -1517,6 +1517,174 @@ } } }, + "apiCalls.savedObjectsBulkCreate.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkCreate.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsBulkCreate.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsBulkGet.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkGet.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsBulkGet.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsBulkUpdate.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkUpdate.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsBulkUpdate.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsCreate.total": { + "type": "long" + }, + "apiCalls.savedObjectsCreate.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsCreate.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsDelete.total": { + "type": "long" + }, + "apiCalls.savedObjectsDelete.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsDelete.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsFind.total": { + "type": "long" + }, + "apiCalls.savedObjectsFind.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsFind.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsFind.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsFind.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsGet.total": { + "type": "long" + }, + "apiCalls.savedObjectsGet.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsGet.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsGet.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsGet.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsUpdate.total": { + "type": "long" + }, + "apiCalls.savedObjectsUpdate.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsUpdate.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.no": { + "type": "long" + }, "apiCalls.savedObjectsImport.total": { "type": "long" },