diff --git a/src/core/server/core_usage_data/core_usage_data_service.test.ts b/src/core/server/core_usage_data/core_usage_data_service.test.ts index e22dfcb1e3a20c..737c851f03bc9b 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.test.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.test.ts @@ -29,6 +29,7 @@ import { config as RawHttpConfig } from '../http/http_config'; import { config as RawLoggingConfig } from '../logging/logging_config'; import { config as RawKibanaConfig } from '../kibana_config'; import { savedObjectsConfig as RawSavedObjectsConfig } from '../saved_objects/saved_objects_config'; +import { httpServiceMock } from '../http/http_service.mock'; import { metricsServiceMock } from '../metrics/metrics_service.mock'; import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock'; @@ -68,11 +69,12 @@ describe('CoreUsageDataService', () => { describe('setup', () => { it('creates internal repository', async () => { + const http = httpServiceMock.createInternalSetupContract(); const metrics = metricsServiceMock.createInternalSetupContract(); const savedObjectsStartPromise = Promise.resolve( savedObjectsServiceMock.createStartContract() ); - service.setup({ metrics, savedObjectsStartPromise }); + service.setup({ http, metrics, savedObjectsStartPromise }); const savedObjects = await savedObjectsStartPromise; expect(savedObjects.createInternalRepository).toHaveBeenCalledTimes(1); @@ -81,14 +83,12 @@ describe('CoreUsageDataService', () => { describe('#registerType', () => { it('registers core usage stats type', async () => { + const http = httpServiceMock.createInternalSetupContract(); const metrics = metricsServiceMock.createInternalSetupContract(); const savedObjectsStartPromise = Promise.resolve( savedObjectsServiceMock.createStartContract() ); - const coreUsageData = service.setup({ - metrics, - savedObjectsStartPromise, - }); + const coreUsageData = service.setup({ http, metrics, savedObjectsStartPromise }); const typeRegistry = typeRegistryMock.create(); coreUsageData.registerType(typeRegistry); @@ -104,14 +104,12 @@ describe('CoreUsageDataService', () => { describe('#getClient', () => { it('returns client', async () => { + const http = httpServiceMock.createInternalSetupContract(); const metrics = metricsServiceMock.createInternalSetupContract(); const savedObjectsStartPromise = Promise.resolve( savedObjectsServiceMock.createStartContract() ); - const coreUsageData = service.setup({ - metrics, - savedObjectsStartPromise, - }); + const coreUsageData = service.setup({ http, metrics, savedObjectsStartPromise }); const usageStatsClient = coreUsageData.getClient(); expect(usageStatsClient).toBeInstanceOf(CoreUsageStatsClient); @@ -122,11 +120,12 @@ describe('CoreUsageDataService', () => { describe('start', () => { describe('getCoreUsageData', () => { it('returns core metrics for default config', async () => { + const http = httpServiceMock.createInternalSetupContract(); const metrics = metricsServiceMock.createInternalSetupContract(); const savedObjectsStartPromise = Promise.resolve( savedObjectsServiceMock.createStartContract() ); - service.setup({ metrics, savedObjectsStartPromise }); + service.setup({ http, metrics, savedObjectsStartPromise }); const elasticsearch = elasticsearchServiceMock.createStart(); elasticsearch.client.asInternalUser.cat.indices.mockResolvedValueOnce({ body: [ @@ -296,6 +295,7 @@ describe('CoreUsageDataService', () => { observables.push(newObservable); return newObservable; }); + const http = httpServiceMock.createInternalSetupContract(); const metrics = metricsServiceMock.createInternalSetupContract(); metrics.getOpsMetrics$.mockImplementation(() => { const newObservable = hot('-a-------'); @@ -306,7 +306,7 @@ describe('CoreUsageDataService', () => { savedObjectsServiceMock.createStartContract() ); - service.setup({ metrics, savedObjectsStartPromise }); + service.setup({ http, metrics, savedObjectsStartPromise }); // Use the stopTimer$ to delay calling stop() until the third frame const stopTimer$ = cold('---a|'); diff --git a/src/core/server/core_usage_data/core_usage_data_service.ts b/src/core/server/core_usage_data/core_usage_data_service.ts index 02b4f2ac591332..07c583186b4536 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.ts @@ -24,7 +24,7 @@ import { CoreService } from 'src/core/types'; import { Logger, SavedObjectsServiceStart, SavedObjectTypeRegistry } from 'src/core/server'; import { CoreContext } from '../core_context'; import { ElasticsearchConfigType } from '../elasticsearch/elasticsearch_config'; -import { HttpConfigType } from '../http'; +import { HttpConfigType, InternalHttpServiceSetup } from '../http'; import { LoggingConfigType } from '../logging'; import { SavedObjectsConfigType } from '../saved_objects/saved_objects_config'; import { @@ -42,6 +42,7 @@ import { CoreUsageStatsClient } from './core_usage_stats_client'; import { MetricsServiceSetup, OpsMetrics } from '..'; export interface SetupDeps { + http: InternalHttpServiceSetup; metrics: MetricsServiceSetup; savedObjectsStartPromise: Promise; } @@ -248,7 +249,7 @@ export class CoreUsageDataService implements CoreService { const debugLogger = (message: string) => this.logger.debug(message); - return new CoreUsageStatsClient(debugLogger, internalRepositoryPromise); + return new CoreUsageStatsClient(debugLogger, http.basePath, internalRepositoryPromise); }; this.coreUsageStatsClient = getClient(); 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 3bfb411c9dd49e..ef350a9bb4c5cf 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 e4f47667fce6b1..6b6e83f168f77f 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 @@ -17,30 +17,43 @@ * under the License. */ -import { savedObjectsRepositoryMock } from '../mocks'; +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, } from './core_usage_stats_client'; import { CoreUsageStatsClient } from '.'; +import { DEFAULT_NAMESPACE_STRING } from '../saved_objects/service/lib/utils'; describe('CoreUsageStatsClient', () => { - const setup = () => { + const setup = (namespace?: string) => { const debugLoggerMock = jest.fn(); + const basePathMock = httpServiceMock.createBasePath(); + // we could mock a return value for basePathMock.get, but it isn't necessary for testing purposes + basePathMock.remove.mockReturnValue(namespace ? `/s/${namespace}` : '/'); const repositoryMock = savedObjectsRepositoryMock.create(); const usageStatsClient = new CoreUsageStatsClient( debugLoggerMock, + basePathMock, Promise.resolve(repositoryMock) ); - return { usageStatsClient, debugLoggerMock, repositoryMock }; + return { usageStatsClient, debugLoggerMock, basePathMock, repositoryMock }; }; - - const firstPartyRequestHeaders = { 'kbn-version': 'a', origin: 'b', referer: 'c' }; // as long as these three header fields are truthy, this will be treated like a first-party request + const firstPartyRequestHeaders = { 'kbn-version': 'a', referer: 'b' }; // as long as these two header fields are truthy, this will be treated like a first-party request const incrementOptions = { refresh: false }; describe('#getUsageStats', () => { @@ -67,13 +80,616 @@ 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(); repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + const request = httpServerMock.createKibanaRequest(); await expect( - usageStatsClient.incrementSavedObjectsImport({} as IncrementSavedObjectsImportOptions) + usageStatsClient.incrementSavedObjectsImport({ + request, + } as IncrementSavedObjectsImportOptions) ).resolves.toBeUndefined(); expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); }); @@ -81,14 +697,18 @@ describe('CoreUsageStatsClient', () => { it('handles falsy options appropriately', async () => { const { usageStatsClient, repositoryMock } = setup(); - await usageStatsClient.incrementSavedObjectsImport({} as IncrementSavedObjectsImportOptions); + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsImport({ + request, + } as IncrementSavedObjectsImportOptions); expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( CORE_USAGE_STATS_TYPE, CORE_USAGE_STATS_ID, [ `${IMPORT_STATS_PREFIX}.total`, - `${IMPORT_STATS_PREFIX}.kibanaRequest.no`, + `${IMPORT_STATS_PREFIX}.namespace.default.total`, + `${IMPORT_STATS_PREFIX}.namespace.default.kibanaRequest.no`, `${IMPORT_STATS_PREFIX}.createNewCopiesEnabled.no`, `${IMPORT_STATS_PREFIX}.overwriteEnabled.no`, ], @@ -96,11 +716,12 @@ describe('CoreUsageStatsClient', () => { ); }); - it('handles truthy options appropriately', async () => { - const { usageStatsClient, repositoryMock } = setup(); + 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.incrementSavedObjectsImport({ - headers: firstPartyRequestHeaders, + request, createNewCopies: true, overwrite: true, } as IncrementSavedObjectsImportOptions); @@ -110,13 +731,36 @@ describe('CoreUsageStatsClient', () => { CORE_USAGE_STATS_ID, [ `${IMPORT_STATS_PREFIX}.total`, - `${IMPORT_STATS_PREFIX}.kibanaRequest.yes`, + `${IMPORT_STATS_PREFIX}.namespace.default.total`, + `${IMPORT_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, `${IMPORT_STATS_PREFIX}.createNewCopiesEnabled.yes`, `${IMPORT_STATS_PREFIX}.overwriteEnabled.yes`, ], incrementOptions ); }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsImport({ + request, + } as IncrementSavedObjectsImportOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${IMPORT_STATS_PREFIX}.total`, + `${IMPORT_STATS_PREFIX}.namespace.custom.total`, + `${IMPORT_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + `${IMPORT_STATS_PREFIX}.createNewCopiesEnabled.no`, + `${IMPORT_STATS_PREFIX}.overwriteEnabled.no`, + ], + incrementOptions + ); + }); }); describe('#incrementSavedObjectsResolveImportErrors', () => { @@ -124,10 +768,11 @@ describe('CoreUsageStatsClient', () => { const { usageStatsClient, repositoryMock } = setup(); repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + const request = httpServerMock.createKibanaRequest(); await expect( - usageStatsClient.incrementSavedObjectsResolveImportErrors( - {} as IncrementSavedObjectsResolveImportErrorsOptions - ) + usageStatsClient.incrementSavedObjectsResolveImportErrors({ + request, + } as IncrementSavedObjectsResolveImportErrorsOptions) ).resolves.toBeUndefined(); expect(repositoryMock.incrementCounter).toHaveBeenCalled(); }); @@ -135,27 +780,30 @@ describe('CoreUsageStatsClient', () => { it('handles falsy options appropriately', async () => { const { usageStatsClient, repositoryMock } = setup(); - await usageStatsClient.incrementSavedObjectsResolveImportErrors( - {} as IncrementSavedObjectsResolveImportErrorsOptions - ); + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsResolveImportErrors({ + request, + } as IncrementSavedObjectsResolveImportErrorsOptions); expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( CORE_USAGE_STATS_TYPE, CORE_USAGE_STATS_ID, [ `${RESOLVE_IMPORT_STATS_PREFIX}.total`, - `${RESOLVE_IMPORT_STATS_PREFIX}.kibanaRequest.no`, + `${RESOLVE_IMPORT_STATS_PREFIX}.namespace.default.total`, + `${RESOLVE_IMPORT_STATS_PREFIX}.namespace.default.kibanaRequest.no`, `${RESOLVE_IMPORT_STATS_PREFIX}.createNewCopiesEnabled.no`, ], incrementOptions ); }); - it('handles truthy options appropriately', async () => { - const { usageStatsClient, repositoryMock } = setup(); + 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.incrementSavedObjectsResolveImportErrors({ - headers: firstPartyRequestHeaders, + request, createNewCopies: true, } as IncrementSavedObjectsResolveImportErrorsOptions); expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); @@ -164,12 +812,34 @@ describe('CoreUsageStatsClient', () => { CORE_USAGE_STATS_ID, [ `${RESOLVE_IMPORT_STATS_PREFIX}.total`, - `${RESOLVE_IMPORT_STATS_PREFIX}.kibanaRequest.yes`, + `${RESOLVE_IMPORT_STATS_PREFIX}.namespace.default.total`, + `${RESOLVE_IMPORT_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, `${RESOLVE_IMPORT_STATS_PREFIX}.createNewCopiesEnabled.yes`, ], incrementOptions ); }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsResolveImportErrors({ + request, + } as IncrementSavedObjectsResolveImportErrorsOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${RESOLVE_IMPORT_STATS_PREFIX}.total`, + `${RESOLVE_IMPORT_STATS_PREFIX}.namespace.custom.total`, + `${RESOLVE_IMPORT_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + `${RESOLVE_IMPORT_STATS_PREFIX}.createNewCopiesEnabled.no`, + ], + incrementOptions + ); + }); }); describe('#incrementSavedObjectsExport', () => { @@ -177,8 +847,11 @@ describe('CoreUsageStatsClient', () => { const { usageStatsClient, repositoryMock } = setup(); repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + const request = httpServerMock.createKibanaRequest(); await expect( - usageStatsClient.incrementSavedObjectsExport({} as IncrementSavedObjectsExportOptions) + usageStatsClient.incrementSavedObjectsExport({ + request, + } as IncrementSavedObjectsExportOptions) ).resolves.toBeUndefined(); expect(repositoryMock.incrementCounter).toHaveBeenCalled(); }); @@ -186,7 +859,9 @@ describe('CoreUsageStatsClient', () => { it('handles falsy options appropriately', async () => { const { usageStatsClient, repositoryMock } = setup(); + const request = httpServerMock.createKibanaRequest(); await usageStatsClient.incrementSavedObjectsExport({ + request, types: undefined, supportedTypes: ['foo', 'bar'], } as IncrementSavedObjectsExportOptions); @@ -196,18 +871,20 @@ describe('CoreUsageStatsClient', () => { CORE_USAGE_STATS_ID, [ `${EXPORT_STATS_PREFIX}.total`, - `${EXPORT_STATS_PREFIX}.kibanaRequest.no`, + `${EXPORT_STATS_PREFIX}.namespace.default.total`, + `${EXPORT_STATS_PREFIX}.namespace.default.kibanaRequest.no`, `${EXPORT_STATS_PREFIX}.allTypesSelected.no`, ], incrementOptions ); }); - it('handles truthy options appropriately', async () => { - const { usageStatsClient, repositoryMock } = setup(); + 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.incrementSavedObjectsExport({ - headers: firstPartyRequestHeaders, + request, types: ['foo', 'bar'], supportedTypes: ['foo', 'bar'], } as IncrementSavedObjectsExportOptions); @@ -217,11 +894,33 @@ describe('CoreUsageStatsClient', () => { CORE_USAGE_STATS_ID, [ `${EXPORT_STATS_PREFIX}.total`, - `${EXPORT_STATS_PREFIX}.kibanaRequest.yes`, + `${EXPORT_STATS_PREFIX}.namespace.default.total`, + `${EXPORT_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, `${EXPORT_STATS_PREFIX}.allTypesSelected.yes`, ], incrementOptions ); }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsExport({ + request, + } as IncrementSavedObjectsExportOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${EXPORT_STATS_PREFIX}.total`, + `${EXPORT_STATS_PREFIX}.namespace.custom.total`, + `${EXPORT_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + `${EXPORT_STATS_PREFIX}.allTypesSelected.no`, + ], + incrementOptions + ); + }); }); }); 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 58356832d8b8af..c8d48597fae884 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 @@ -19,16 +19,19 @@ import { CORE_USAGE_STATS_TYPE, CORE_USAGE_STATS_ID } from './constants'; import { CoreUsageStats } from './types'; +import { DEFAULT_NAMESPACE_STRING } from '../saved_objects/service/lib/utils'; import { - Headers, ISavedObjectsRepository, SavedObjectsImportOptions, SavedObjectsResolveImportErrorsOptions, SavedObjectsExportOptions, + KibanaRequest, + IBasePath, } from '..'; -interface BaseIncrementOptions { - headers?: Headers; +/** @internal */ +export interface BaseIncrementOptions { + request: KibanaRequest; } /** @internal */ export type IncrementSavedObjectsImportOptions = BaseIncrementOptions & @@ -40,33 +43,47 @@ 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 = [ - `${IMPORT_STATS_PREFIX}.total`, - `${IMPORT_STATS_PREFIX}.kibanaRequest.yes`, - `${IMPORT_STATS_PREFIX}.kibanaRequest.no`, + // Saved Objects Client APIs + ...getFieldsForCounter(BULK_CREATE_STATS_PREFIX), + ...getFieldsForCounter(BULK_GET_STATS_PREFIX), + ...getFieldsForCounter(BULK_UPDATE_STATS_PREFIX), + ...getFieldsForCounter(CREATE_STATS_PREFIX), + ...getFieldsForCounter(DELETE_STATS_PREFIX), + ...getFieldsForCounter(FIND_STATS_PREFIX), + ...getFieldsForCounter(GET_STATS_PREFIX), + ...getFieldsForCounter(UPDATE_STATS_PREFIX), + // Saved Objects Management APIs + ...getFieldsForCounter(IMPORT_STATS_PREFIX), `${IMPORT_STATS_PREFIX}.createNewCopiesEnabled.yes`, `${IMPORT_STATS_PREFIX}.createNewCopiesEnabled.no`, `${IMPORT_STATS_PREFIX}.overwriteEnabled.yes`, `${IMPORT_STATS_PREFIX}.overwriteEnabled.no`, - `${RESOLVE_IMPORT_STATS_PREFIX}.total`, - `${RESOLVE_IMPORT_STATS_PREFIX}.kibanaRequest.yes`, - `${RESOLVE_IMPORT_STATS_PREFIX}.kibanaRequest.no`, + ...getFieldsForCounter(RESOLVE_IMPORT_STATS_PREFIX), `${RESOLVE_IMPORT_STATS_PREFIX}.createNewCopiesEnabled.yes`, `${RESOLVE_IMPORT_STATS_PREFIX}.createNewCopiesEnabled.no`, - `${EXPORT_STATS_PREFIX}.total`, - `${EXPORT_STATS_PREFIX}.kibanaRequest.yes`, - `${EXPORT_STATS_PREFIX}.kibanaRequest.no`, + ...getFieldsForCounter(EXPORT_STATS_PREFIX), `${EXPORT_STATS_PREFIX}.allTypesSelected.yes`, `${EXPORT_STATS_PREFIX}.allTypesSelected.no`, ]; +const SPACE_CONTEXT_REGEX = /^\/s\/([a-z0-9_\-]+)/; /** @internal */ export class CoreUsageStatsClient { constructor( private readonly debugLogger: (message: string) => void, + private readonly basePath: IBasePath, private readonly repositoryPromise: Promise ) {} @@ -88,67 +105,128 @@ export class CoreUsageStatsClient { return coreUsageStats; } - public async incrementSavedObjectsImport({ - headers, - createNewCopies, - overwrite, - }: IncrementSavedObjectsImportOptions) { - const isKibanaRequest = getIsKibanaRequest(headers); + 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 = [ - 'total', - `kibanaRequest.${isKibanaRequest ? 'yes' : 'no'}`, `createNewCopiesEnabled.${createNewCopies ? 'yes' : 'no'}`, `overwriteEnabled.${overwrite ? 'yes' : 'no'}`, ]; - await this.updateUsageStats(counterFieldNames, IMPORT_STATS_PREFIX); + await this.updateUsageStats(counterFieldNames, IMPORT_STATS_PREFIX, options); } - public async incrementSavedObjectsResolveImportErrors({ - headers, - createNewCopies, - }: IncrementSavedObjectsResolveImportErrorsOptions) { - const isKibanaRequest = getIsKibanaRequest(headers); - const counterFieldNames = [ - 'total', - `kibanaRequest.${isKibanaRequest ? 'yes' : 'no'}`, - `createNewCopiesEnabled.${createNewCopies ? 'yes' : 'no'}`, - ]; - await this.updateUsageStats(counterFieldNames, RESOLVE_IMPORT_STATS_PREFIX); + public async incrementSavedObjectsResolveImportErrors( + options: IncrementSavedObjectsResolveImportErrorsOptions + ) { + const { createNewCopies } = options; + const counterFieldNames = [`createNewCopiesEnabled.${createNewCopies ? 'yes' : 'no'}`]; + await this.updateUsageStats(counterFieldNames, RESOLVE_IMPORT_STATS_PREFIX, options); } - public async incrementSavedObjectsExport({ - headers, - types, - supportedTypes, - }: IncrementSavedObjectsExportOptions) { - const isKibanaRequest = getIsKibanaRequest(headers); + public async incrementSavedObjectsExport(options: IncrementSavedObjectsExportOptions) { + const { types, supportedTypes } = options; const isAllTypesSelected = !!types && supportedTypes.every((x) => types.includes(x)); - const counterFieldNames = [ - 'total', - `kibanaRequest.${isKibanaRequest ? 'yes' : 'no'}`, - `allTypesSelected.${isAllTypesSelected ? 'yes' : 'no'}`, - ]; - await this.updateUsageStats(counterFieldNames, EXPORT_STATS_PREFIX); + const counterFieldNames = [`allTypesSelected.${isAllTypesSelected ? 'yes' : 'no'}`]; + await this.updateUsageStats(counterFieldNames, EXPORT_STATS_PREFIX, options); } - private async updateUsageStats(counterFieldNames: string[], prefix: string) { + private async updateUsageStats( + counterFieldNames: string[], + prefix: string, + { request }: BaseIncrementOptions + ) { const options = { refresh: false }; try { const repository = await this.repositoryPromise; + const fields = this.getFieldsToIncrement(counterFieldNames, prefix, request); await repository.incrementCounter( CORE_USAGE_STATS_TYPE, CORE_USAGE_STATS_ID, - counterFieldNames.map((x) => `${prefix}.${x}`), + fields, options ); } catch (err) { // do nothing } } + + private getIsDefaultNamespace(request: KibanaRequest) { + const requestBasePath = this.basePath.get(request); // obtain the original request basePath, as it may have been modified by a request interceptor + const pathToCheck = this.basePath.remove(requestBasePath); // remove the server basePath from the request basePath + const matchResult = pathToCheck.match(SPACE_CONTEXT_REGEX); // Look for `/s/space-url-context` in the base path + + if (!matchResult || matchResult.length === 0) { + return true; + } + + // Ignoring first result, we only want the capture group result at index 1 + const [, spaceId] = matchResult; + + return spaceId === DEFAULT_NAMESPACE_STRING; + } + + private getFieldsToIncrement( + counterFieldNames: string[], + prefix: string, + request: KibanaRequest + ) { + const isKibanaRequest = getIsKibanaRequest(request); + const isDefaultNamespace = this.getIsDefaultNamespace(request); + const namespaceField = isDefaultNamespace ? 'default' : 'custom'; + return [ + 'total', + `namespace.${namespaceField}.total`, + `namespace.${namespaceField}.kibanaRequest.${isKibanaRequest ? 'yes' : 'no'}`, + ...counterFieldNames, + ].map((x) => `${prefix}.${x}`); + } +} + +function getFieldsForCounter(prefix: string) { + return [ + 'total', + 'namespace.default.total', + 'namespace.default.kibanaRequest.yes', + 'namespace.default.kibanaRequest.no', + 'namespace.custom.total', + 'namespace.custom.kibanaRequest.yes', + 'namespace.custom.kibanaRequest.no', + ].map((x) => `${prefix}.${x}`); } -function getIsKibanaRequest(headers?: Headers) { - // The presence of these three request headers gives us a good indication that this is a first-party request from the Kibana client. +function getIsKibanaRequest({ headers }: KibanaRequest) { + // The presence of these two request headers gives us a good indication that this is a first-party request from the Kibana client. // We can't be 100% certain, but this is a reasonable attempt. - return headers && headers['kbn-version'] && headers.origin && headers.referer; + return headers && headers['kbn-version'] && headers.referer; } diff --git a/src/core/server/core_usage_data/types.ts b/src/core/server/core_usage_data/types.ts index aa41d75e6f2d4f..b7952334b4be44 100644 --- a/src/core/server/core_usage_data/types.ts +++ b/src/core/server/core_usage_data/types.ts @@ -27,21 +27,91 @@ 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.kibanaRequest.yes'?: number; - 'apiCalls.savedObjectsImport.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsImport.namespace.default.total'?: number; + 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsImport.namespace.custom.total'?: number; + 'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.no'?: number; 'apiCalls.savedObjectsImport.createNewCopiesEnabled.yes'?: number; 'apiCalls.savedObjectsImport.createNewCopiesEnabled.no'?: number; 'apiCalls.savedObjectsImport.overwriteEnabled.yes'?: number; 'apiCalls.savedObjectsImport.overwriteEnabled.no'?: number; 'apiCalls.savedObjectsResolveImportErrors.total'?: number; - 'apiCalls.savedObjectsResolveImportErrors.kibanaRequest.yes'?: number; - 'apiCalls.savedObjectsResolveImportErrors.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.total'?: number; + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.total'?: number; + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.no'?: number; 'apiCalls.savedObjectsResolveImportErrors.createNewCopiesEnabled.yes'?: number; 'apiCalls.savedObjectsResolveImportErrors.createNewCopiesEnabled.no'?: number; 'apiCalls.savedObjectsExport.total'?: number; - 'apiCalls.savedObjectsExport.kibanaRequest.yes'?: number; - 'apiCalls.savedObjectsExport.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsExport.namespace.default.total'?: number; + 'apiCalls.savedObjectsExport.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsExport.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsExport.namespace.custom.total'?: number; + 'apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.no'?: number; 'apiCalls.savedObjectsExport.allTypesSelected.yes'?: number; 'apiCalls.savedObjectsExport.allTypesSelected.no'?: number; } diff --git a/src/core/server/saved_objects/routes/bulk_create.ts b/src/core/server/saved_objects/routes/bulk_create.ts index b048c5d8f99bfc..b1286f3a1f06cb 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 067388dcf92208..41c77520b4fafd 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 882213644146a1..b4014b5422d5d9 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 816315705a375a..cb6a849be9f2d9 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 d99397d2a050c6..69d2290325a939 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/export.ts b/src/core/server/saved_objects/routes/export.ts index 387280d777eaae..8f5c19d927d400 100644 --- a/src/core/server/saved_objects/routes/export.ts +++ b/src/core/server/saved_objects/routes/export.ts @@ -104,10 +104,9 @@ export const registerExportRoute = ( } } - const { headers } = req; const usageStatsClient = coreUsageData.getClient(); usageStatsClient - .incrementSavedObjectsExport({ headers, types, supportedTypes }) + .incrementSavedObjectsExport({ request: req, types, supportedTypes }) .catch(() => {}); const exportStream = await exportSavedObjectsToStream({ diff --git a/src/core/server/saved_objects/routes/find.ts b/src/core/server/saved_objects/routes/find.ts index 915d0cccf7af90..7ddcfa91da22d3 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 f1b974c70b1a97..d29229eab33ff7 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/import.ts b/src/core/server/saved_objects/routes/import.ts index 27be710c0a92a5..ebc52c32e2c709 100644 --- a/src/core/server/saved_objects/routes/import.ts +++ b/src/core/server/saved_objects/routes/import.ts @@ -75,10 +75,9 @@ export const registerImportRoute = ( router.handleLegacyErrors(async (context, req, res) => { const { overwrite, createNewCopies } = req.query; - const { headers } = req; const usageStatsClient = coreUsageData.getClient(); usageStatsClient - .incrementSavedObjectsImport({ headers, createNewCopies, overwrite }) + .incrementSavedObjectsImport({ request: req, createNewCopies, overwrite }) .catch(() => {}); const file = req.body.file as FileStream; diff --git a/src/core/server/saved_objects/routes/index.ts b/src/core/server/saved_objects/routes/index.ts index 19154b85836546..0ffd1104d35e27 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 3d455ff9d594c9..186b21ef361a96 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 5deea94299d7d9..c6028f86fcc7ce 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 45f310ecc3fa2e..c038c5303dd696 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 9e69c3dbc64ec1..8c209a05f29481 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 ff8642a34929fb..c70754632980ab 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 c37ed2da976813..d5b1e492e573fc 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 }); @@ -118,7 +118,7 @@ describe('POST /api/saved_objects/_export', () => { }) ); expect(coreUsageStatsClient.incrementSavedObjectsExport).toHaveBeenCalledWith({ - headers: expect.anything(), + request: expect.anything(), types: ['search'], supportedTypes: ['index-pattern', 'search'], }); 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 9a426ef48c7da8..8e3de04648b834 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 1e3405d7a318f7..e05b6b09659fad 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 9dfb7f79a925d7..b80deb87725d48 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 }); @@ -106,7 +106,7 @@ describe(`POST ${URL}`, () => { expect(result.body).toEqual({ success: true, successCount: 0 }); expect(savedObjectsClient.bulkCreate).not.toHaveBeenCalled(); // no objects were created expect(coreUsageStatsClient.incrementSavedObjectsImport).toHaveBeenCalledWith({ - headers: expect.anything(), + request: expect.anything(), createNewCopies: false, overwrite: false, }); 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 46f4d2435bf67f..f135e34231cb66 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 }); @@ -117,7 +117,7 @@ describe(`POST ${URL}`, () => { expect(result.body).toEqual({ success: true, successCount: 0 }); expect(savedObjectsClient.bulkCreate).not.toHaveBeenCalled(); // no objects were created expect(coreUsageStatsClient.incrementSavedObjectsResolveImportErrors).toHaveBeenCalledWith({ - headers: expect.anything(), + request: expect.anything(), createNewCopies: false, }); }); 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 dfccb651d72d7a..433ffb49e05e48 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/resolve_import_errors.ts b/src/core/server/saved_objects/routes/resolve_import_errors.ts index 34c178a975304d..5db5454b224d7b 100644 --- a/src/core/server/saved_objects/routes/resolve_import_errors.ts +++ b/src/core/server/saved_objects/routes/resolve_import_errors.ts @@ -83,10 +83,9 @@ export const registerResolveImportErrorsRoute = ( router.handleLegacyErrors(async (context, req, res) => { const { createNewCopies } = req.query; - const { headers } = req; const usageStatsClient = coreUsageData.getClient(); usageStatsClient - .incrementSavedObjectsResolveImportErrors({ headers, createNewCopies }) + .incrementSavedObjectsResolveImportErrors({ request: req, createNewCopies }) .catch(() => {}); const file = req.body.file as FileStream; diff --git a/src/core/server/saved_objects/routes/update.ts b/src/core/server/saved_objects/routes/update.ts index c0d94d362e648f..95137cb6e77cfd 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 a39bbecd16ff52..1ab06b7912d1fe 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -541,24 +541,138 @@ 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) 'apiCalls.savedObjectsExport.allTypesSelected.yes'?: number; // (undocumented) - 'apiCalls.savedObjectsExport.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsExport.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsExport.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsExport.namespace.default.kibanaRequest.yes'?: number; // (undocumented) - 'apiCalls.savedObjectsExport.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsExport.namespace.default.total'?: number; // (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; // (undocumented) - 'apiCalls.savedObjectsImport.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsImport.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.no'?: number; // (undocumented) - 'apiCalls.savedObjectsImport.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsImport.namespace.default.total'?: number; // (undocumented) 'apiCalls.savedObjectsImport.overwriteEnabled.no'?: number; // (undocumented) @@ -570,11 +684,33 @@ export interface CoreUsageStats { // (undocumented) 'apiCalls.savedObjectsResolveImportErrors.createNewCopiesEnabled.yes'?: number; // (undocumented) - 'apiCalls.savedObjectsResolveImportErrors.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.no'?: number; // (undocumented) - 'apiCalls.savedObjectsResolveImportErrors.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + '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/core/server/server.ts b/src/core/server/server.ts index 75530e557de048..08f0a191151dd0 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -166,6 +166,7 @@ export class Server { const metricsSetup = await this.metrics.setup({ http: httpSetup }); const coreUsageDataSetup = this.coreUsageData.setup({ + http: httpSetup, metrics: metricsSetup, savedObjectsStartPromise: this.savedObjectsStartPromise, }); 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 d30a3c5ab68615..a0960b30a2e870 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,21 +115,99 @@ 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.kibanaRequest.yes': { type: 'long' }, - 'apiCalls.savedObjectsImport.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsImport.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsImport.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.no': { type: 'long' }, 'apiCalls.savedObjectsImport.createNewCopiesEnabled.yes': { type: 'long' }, 'apiCalls.savedObjectsImport.createNewCopiesEnabled.no': { type: 'long' }, 'apiCalls.savedObjectsImport.overwriteEnabled.yes': { type: 'long' }, 'apiCalls.savedObjectsImport.overwriteEnabled.no': { type: 'long' }, 'apiCalls.savedObjectsResolveImportErrors.total': { type: 'long' }, - 'apiCalls.savedObjectsResolveImportErrors.kibanaRequest.yes': { type: 'long' }, - 'apiCalls.savedObjectsResolveImportErrors.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.yes': { + type: 'long', + }, + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.no': { + type: 'long', + }, + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.yes': { + type: 'long', + }, + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.no': { + type: 'long', + }, 'apiCalls.savedObjectsResolveImportErrors.createNewCopiesEnabled.yes': { type: 'long' }, 'apiCalls.savedObjectsResolveImportErrors.createNewCopiesEnabled.no': { type: 'long' }, 'apiCalls.savedObjectsExport.total': { type: 'long' }, - 'apiCalls.savedObjectsExport.kibanaRequest.yes': { type: 'long' }, - 'apiCalls.savedObjectsExport.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsExport.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsExport.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsExport.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsExport.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.no': { type: 'long' }, 'apiCalls.savedObjectsExport.allTypesSelected.yes': { type: 'long' }, 'apiCalls.savedObjectsExport.allTypesSelected.no': { type: 'long' }, }, diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 55384329f9af76..d486c06568c1b7 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -1517,13 +1517,193 @@ } } }, + "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" }, - "apiCalls.savedObjectsImport.kibanaRequest.yes": { + "apiCalls.savedObjectsImport.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsImport.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsImport.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsImport.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.yes": { "type": "long" }, - "apiCalls.savedObjectsImport.kibanaRequest.no": { + "apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.no": { "type": "long" }, "apiCalls.savedObjectsImport.createNewCopiesEnabled.yes": { @@ -1541,10 +1721,22 @@ "apiCalls.savedObjectsResolveImportErrors.total": { "type": "long" }, - "apiCalls.savedObjectsResolveImportErrors.kibanaRequest.yes": { + "apiCalls.savedObjectsResolveImportErrors.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsResolveImportErrors.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.yes": { "type": "long" }, - "apiCalls.savedObjectsResolveImportErrors.kibanaRequest.no": { + "apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.no": { "type": "long" }, "apiCalls.savedObjectsResolveImportErrors.createNewCopiesEnabled.yes": { @@ -1556,10 +1748,22 @@ "apiCalls.savedObjectsExport.total": { "type": "long" }, - "apiCalls.savedObjectsExport.kibanaRequest.yes": { + "apiCalls.savedObjectsExport.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsExport.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsExport.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsExport.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.yes": { "type": "long" }, - "apiCalls.savedObjectsExport.kibanaRequest.no": { + "apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.no": { "type": "long" }, "apiCalls.savedObjectsExport.allTypesSelected.yes": { diff --git a/test/api_integration/apis/saved_objects/bulk_create.js b/test/api_integration/apis/saved_objects/bulk_create.js index 7db968df8357a9..a78acea1d02992 100644 --- a/test/api_integration/apis/saved_objects/bulk_create.js +++ b/test/api_integration/apis/saved_objects/bulk_create.js @@ -68,7 +68,7 @@ export default function ({ getService }) { type: 'dashboard', id: 'a01b2f57-fcfd-4864-b735-09e28f0d815e', updated_at: resp.body.saved_objects[1].updated_at, - version: 'WzgsMV0=', + version: resp.body.saved_objects[1].version, attributes: { title: 'A great new dashboard', }, @@ -117,7 +117,7 @@ export default function ({ getService }) { type: 'visualization', id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', updated_at: resp.body.saved_objects[0].updated_at, - version: 'WzAsMV0=', + version: resp.body.saved_objects[0].version, attributes: { title: 'An existing visualization', }, @@ -131,7 +131,7 @@ export default function ({ getService }) { type: 'dashboard', id: 'a01b2f57-fcfd-4864-b735-09e28f0d815e', updated_at: resp.body.saved_objects[1].updated_at, - version: 'WzEsMV0=', + version: resp.body.saved_objects[1].version, attributes: { title: 'A great new dashboard', }, diff --git a/test/api_integration/apis/saved_objects/bulk_update.js b/test/api_integration/apis/saved_objects/bulk_update.js index 973ce382ea8130..58c72575c04bba 100644 --- a/test/api_integration/apis/saved_objects/bulk_update.js +++ b/test/api_integration/apis/saved_objects/bulk_update.js @@ -61,7 +61,7 @@ export default function ({ getService }) { expect(_.omit(firstObject, ['updated_at'])).to.eql({ id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', type: 'visualization', - version: 'WzgsMV0=', + version: firstObject.version, attributes: { title: 'An existing visualization', }, @@ -74,7 +74,7 @@ export default function ({ getService }) { expect(_.omit(secondObject, ['updated_at'])).to.eql({ id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', type: 'dashboard', - version: 'WzksMV0=', + version: secondObject.version, attributes: { title: 'An existing dashboard', }, diff --git a/test/api_integration/apis/saved_objects/create.js b/test/api_integration/apis/saved_objects/create.js index c1300125441bcb..15aecb6e547a00 100644 --- a/test/api_integration/apis/saved_objects/create.js +++ b/test/api_integration/apis/saved_objects/create.js @@ -53,7 +53,7 @@ export default function ({ getService }) { type: 'visualization', migrationVersion: resp.body.migrationVersion, updated_at: resp.body.updated_at, - version: 'WzgsMV0=', + version: resp.body.version, attributes: { title: 'My favorite vis', }, @@ -100,7 +100,7 @@ export default function ({ getService }) { type: 'visualization', migrationVersion: resp.body.migrationVersion, updated_at: resp.body.updated_at, - version: 'WzAsMV0=', + version: resp.body.version, attributes: { title: 'My favorite vis', }, diff --git a/test/api_integration/apis/saved_objects/update.js b/test/api_integration/apis/saved_objects/update.js index 7803c39897f283..14b363a512ea18 100644 --- a/test/api_integration/apis/saved_objects/update.js +++ b/test/api_integration/apis/saved_objects/update.js @@ -52,7 +52,7 @@ export default function ({ getService }) { id: resp.body.id, type: 'visualization', updated_at: resp.body.updated_at, - version: 'WzgsMV0=', + version: resp.body.version, attributes: { title: 'My second favorite vis', }, diff --git a/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.test.ts b/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.test.ts index b313c0be32b95a..20d38d58091495 100644 --- a/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.test.ts +++ b/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.test.ts @@ -22,7 +22,7 @@ describe('UsageStatsClient', () => { return { usageStatsClient, debugLoggerMock, repositoryMock }; }; - const firstPartyRequestHeaders = { 'kbn-version': 'a', origin: 'b', referer: 'c' }; // as long as these three header fields are truthy, this will be treated like a first-party request + const firstPartyRequestHeaders = { 'kbn-version': 'a', referer: 'b' }; // as long as these two header fields are truthy, this will be treated like a first-party request const incrementOptions = { refresh: false }; describe('#getUsageStats', () => { diff --git a/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.ts b/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.ts index 4c9d11a11cccac..22e8bb5c4d39a7 100644 --- a/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.ts +++ b/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.ts @@ -102,7 +102,7 @@ export class UsageStatsClient { } function getIsKibanaRequest(headers?: Headers) { - // The presence of these three request headers gives us a good indication that this is a first-party request from the Kibana client. + // The presence of these two request headers gives us a good indication that this is a first-party request from the Kibana client. // We can't be 100% certain, but this is a reasonable attempt. - return headers && headers['kbn-version'] && headers.origin && headers.referer; + return headers && headers['kbn-version'] && headers.referer; }