[Usage Collection] Improves Collector fetch API (#79595)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Christiane (Tina) Heiligers 2020-10-13 09:55:22 -07:00 committed by GitHub
parent b5c5bf706d
commit 70a9164790
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 391 additions and 307 deletions

View file

@ -19,6 +19,8 @@
import { fetchProvider } from './fetch';
import { LegacyAPICaller } from 'kibana/server';
import { CollectorFetchContext } from 'src/plugins/usage_collection/server';
import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
jest.mock('../../../common', () => ({
DEFAULT_QUERY_LANGUAGE: 'lucene',
@ -29,6 +31,8 @@ jest.mock('../../../common', () => ({
let fetch: ReturnType<typeof fetchProvider>;
let callCluster: LegacyAPICaller;
let collectorFetchContext: CollectorFetchContext;
const collectorFetchContextMock = createCollectorFetchContextMock();
function setupMockCallCluster(
optCount: { optInCount?: number; optOutCount?: number } | null,
@ -89,40 +93,64 @@ describe('makeKQLUsageCollector', () => {
it('should return opt in data from the .kibana/kql-telemetry doc', async () => {
setupMockCallCluster({ optInCount: 1 }, 'kuery');
const fetchResponse = await fetch(callCluster);
collectorFetchContext = {
...collectorFetchContextMock,
callCluster,
};
const fetchResponse = await fetch(collectorFetchContext);
expect(fetchResponse.optInCount).toBe(1);
expect(fetchResponse.optOutCount).toBe(0);
});
it('should return the default query language set in advanced settings', async () => {
setupMockCallCluster({ optInCount: 1 }, 'kuery');
const fetchResponse = await fetch(callCluster);
collectorFetchContext = {
...collectorFetchContextMock,
callCluster,
};
const fetchResponse = await fetch(collectorFetchContext);
expect(fetchResponse.defaultQueryLanguage).toBe('kuery');
});
// Indicates the user has modified the setting at some point but the value is currently the default
it('should return the kibana default query language if the config value is null', async () => {
setupMockCallCluster({ optInCount: 1 }, null);
const fetchResponse = await fetch(callCluster);
collectorFetchContext = {
...collectorFetchContextMock,
callCluster,
};
const fetchResponse = await fetch(collectorFetchContext);
expect(fetchResponse.defaultQueryLanguage).toBe('lucene');
});
it('should indicate when the default language has never been modified by the user', async () => {
setupMockCallCluster({ optInCount: 1 }, undefined);
const fetchResponse = await fetch(callCluster);
collectorFetchContext = {
...collectorFetchContextMock,
callCluster,
};
const fetchResponse = await fetch(collectorFetchContext);
expect(fetchResponse.defaultQueryLanguage).toBe('default-lucene');
});
it('should default to 0 opt in counts if the .kibana/kql-telemetry doc does not exist', async () => {
setupMockCallCluster(null, 'kuery');
const fetchResponse = await fetch(callCluster);
collectorFetchContext = {
...collectorFetchContextMock,
callCluster,
};
const fetchResponse = await fetch(collectorFetchContext);
expect(fetchResponse.optInCount).toBe(0);
expect(fetchResponse.optOutCount).toBe(0);
});
it('should default to the kibana default language if the config document does not exist', async () => {
setupMockCallCluster(null, 'missingConfigDoc');
const fetchResponse = await fetch(callCluster);
collectorFetchContext = {
...collectorFetchContextMock,
callCluster,
};
const fetchResponse = await fetch(collectorFetchContext);
expect(fetchResponse.defaultQueryLanguage).toBe('default-lucene');
});
});

View file

@ -18,7 +18,7 @@
*/
import { get } from 'lodash';
import { LegacyAPICaller } from 'kibana/server';
import { CollectorFetchContext } from 'src/plugins/usage_collection/server';
import { DEFAULT_QUERY_LANGUAGE, UI_SETTINGS } from '../../../common';
const defaultSearchQueryLanguageSetting = DEFAULT_QUERY_LANGUAGE;
@ -30,7 +30,7 @@ export interface Usage {
}
export function fetchProvider(index: string) {
return async (callCluster: LegacyAPICaller): Promise<Usage> => {
return async ({ callCluster }: CollectorFetchContext): Promise<Usage> => {
const [response, config] = await Promise.all([
callCluster('get', {
index,

View file

@ -19,7 +19,8 @@
import { Observable } from 'rxjs';
import { first } from 'rxjs/operators';
import { LegacyAPICaller, SharedGlobalConfig } from 'kibana/server';
import { SharedGlobalConfig } from 'kibana/server';
import { CollectorFetchContext } from 'src/plugins/usage_collection/server';
import { Usage } from './register';
interface SearchTelemetrySavedObject {
@ -27,7 +28,7 @@ interface SearchTelemetrySavedObject {
}
export function fetchProvider(config$: Observable<SharedGlobalConfig>) {
return async (callCluster: LegacyAPICaller): Promise<Usage> => {
return async ({ callCluster }: CollectorFetchContext): Promise<Usage> => {
const config = await config$.pipe(first()).toPromise();
const response = await callCluster<SearchTelemetrySavedObject>('search', {

View file

@ -17,39 +17,39 @@
* under the License.
*/
import sinon from 'sinon';
import { CollectorFetchContext } from 'src/plugins/usage_collection/server';
import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
import { fetchProvider } from './collector_fetch';
describe('Sample Data Fetch', () => {
let callClusterMock: sinon.SinonStub;
const getMockFetchClients = (hits?: unknown[]) => {
const fetchParamsMock = createCollectorFetchContextMock();
fetchParamsMock.callCluster.mockResolvedValue({ hits: { hits } });
return fetchParamsMock;
};
beforeEach(() => {
callClusterMock = sinon.stub();
});
describe('Sample Data Fetch', () => {
let collectorFetchContext: CollectorFetchContext;
test('uninitialized .kibana', async () => {
const fetch = fetchProvider('index');
const telemetry = await fetch(callClusterMock);
collectorFetchContext = getMockFetchClients();
const telemetry = await fetch(collectorFetchContext);
expect(telemetry).toMatchInlineSnapshot(`undefined`);
});
test('installed data set', async () => {
const fetch = fetchProvider('index');
callClusterMock.returns({
hits: {
hits: [
{
_id: 'sample-data-telemetry:test1',
_source: {
updated_at: '2019-03-13T22:02:09Z',
'sample-data-telemetry': { installCount: 1 },
},
},
],
collectorFetchContext = getMockFetchClients([
{
_id: 'sample-data-telemetry:test1',
_source: {
updated_at: '2019-03-13T22:02:09Z',
'sample-data-telemetry': { installCount: 1 },
},
},
});
const telemetry = await fetch(callClusterMock);
]);
const telemetry = await fetch(collectorFetchContext);
expect(telemetry).toMatchInlineSnapshot(`
Object {
@ -67,27 +67,23 @@ Object {
test('multiple installed data sets', async () => {
const fetch = fetchProvider('index');
callClusterMock.returns({
hits: {
hits: [
{
_id: 'sample-data-telemetry:test1',
_source: {
updated_at: '2019-03-13T22:02:09Z',
'sample-data-telemetry': { installCount: 1 },
},
},
{
_id: 'sample-data-telemetry:test2',
_source: {
updated_at: '2019-03-13T22:13:17Z',
'sample-data-telemetry': { installCount: 1 },
},
},
],
collectorFetchContext = getMockFetchClients([
{
_id: 'sample-data-telemetry:test1',
_source: {
updated_at: '2019-03-13T22:02:09Z',
'sample-data-telemetry': { installCount: 1 },
},
},
});
const telemetry = await fetch(callClusterMock);
{
_id: 'sample-data-telemetry:test2',
_source: {
updated_at: '2019-03-13T22:13:17Z',
'sample-data-telemetry': { installCount: 1 },
},
},
]);
const telemetry = await fetch(collectorFetchContext);
expect(telemetry).toMatchInlineSnapshot(`
Object {
@ -106,17 +102,13 @@ Object {
test('installed data set, missing counts', async () => {
const fetch = fetchProvider('index');
callClusterMock.returns({
hits: {
hits: [
{
_id: 'sample-data-telemetry:test1',
_source: { updated_at: '2019-03-13T22:02:09Z', 'sample-data-telemetry': {} },
},
],
collectorFetchContext = getMockFetchClients([
{
_id: 'sample-data-telemetry:test1',
_source: { updated_at: '2019-03-13T22:02:09Z', 'sample-data-telemetry': {} },
},
});
const telemetry = await fetch(callClusterMock);
]);
const telemetry = await fetch(collectorFetchContext);
expect(telemetry).toMatchInlineSnapshot(`
Object {
@ -132,34 +124,30 @@ Object {
test('installed and uninstalled data sets', async () => {
const fetch = fetchProvider('index');
callClusterMock.returns({
hits: {
hits: [
{
_id: 'sample-data-telemetry:test0',
_source: {
updated_at: '2019-03-13T22:29:32Z',
'sample-data-telemetry': { installCount: 4, unInstallCount: 4 },
},
},
{
_id: 'sample-data-telemetry:test1',
_source: {
updated_at: '2019-03-13T22:02:09Z',
'sample-data-telemetry': { installCount: 1 },
},
},
{
_id: 'sample-data-telemetry:test2',
_source: {
updated_at: '2019-03-13T22:13:17Z',
'sample-data-telemetry': { installCount: 1 },
},
},
],
collectorFetchContext = getMockFetchClients([
{
_id: 'sample-data-telemetry:test0',
_source: {
updated_at: '2019-03-13T22:29:32Z',
'sample-data-telemetry': { installCount: 4, unInstallCount: 4 },
},
},
});
const telemetry = await fetch(callClusterMock);
{
_id: 'sample-data-telemetry:test1',
_source: {
updated_at: '2019-03-13T22:02:09Z',
'sample-data-telemetry': { installCount: 1 },
},
},
{
_id: 'sample-data-telemetry:test2',
_source: {
updated_at: '2019-03-13T22:13:17Z',
'sample-data-telemetry': { installCount: 1 },
},
},
]);
const telemetry = await fetch(collectorFetchContext);
expect(telemetry).toMatchInlineSnapshot(`
Object {

View file

@ -19,6 +19,7 @@
import { get } from 'lodash';
import moment from 'moment';
import { CollectorFetchContext } from '../../../../../usage_collection/server';
interface SearchHit {
_id: string;
@ -41,7 +42,7 @@ export interface TelemetryResponse {
}
export function fetchProvider(index: string) {
return async (callCluster: any) => {
return async ({ callCluster }: CollectorFetchContext) => {
const response = await callCluster('search', {
index,
body: {

View file

@ -17,16 +17,13 @@
* under the License.
*/
import {
savedObjectsRepositoryMock,
loggingSystemMock,
elasticsearchServiceMock,
} from '../../../../../core/server/mocks';
import { savedObjectsRepositoryMock, loggingSystemMock } from '../../../../../core/server/mocks';
import {
CollectorOptions,
createUsageCollectionSetupMock,
} from '../../../../usage_collection/server/usage_collection.mock';
import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
import {
ROLL_INDICES_START,
ROLL_TOTAL_INDICES_INTERVAL,
@ -53,8 +50,7 @@ describe('telemetry_application_usage', () => {
const getUsageCollector = jest.fn();
const registerType = jest.fn();
const callCluster = jest.fn();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const mockedFetchContext = createCollectorFetchContextMock();
beforeAll(() =>
registerApplicationUsageCollector(logger, usageCollectionMock, registerType, getUsageCollector)
@ -67,7 +63,7 @@ describe('telemetry_application_usage', () => {
test('if no savedObjectClient initialised, return undefined', async () => {
expect(collector.isReady()).toBe(false);
expect(await collector.fetch(callCluster, esClient)).toBeUndefined();
expect(await collector.fetch(mockedFetchContext)).toBeUndefined();
jest.runTimersToTime(ROLL_INDICES_START);
});
@ -85,7 +81,7 @@ describe('telemetry_application_usage', () => {
jest.runTimersToTime(ROLL_TOTAL_INDICES_INTERVAL); // Force rollTotals to run
expect(collector.isReady()).toBe(true);
expect(await collector.fetch(callCluster, esClient)).toStrictEqual({});
expect(await collector.fetch(mockedFetchContext)).toStrictEqual({});
expect(savedObjectClient.bulkCreate).not.toHaveBeenCalled();
});
@ -142,7 +138,7 @@ describe('telemetry_application_usage', () => {
jest.runTimersToTime(ROLL_TOTAL_INDICES_INTERVAL); // Force rollTotals to run
expect(await collector.fetch(callCluster, esClient)).toStrictEqual({
expect(await collector.fetch(mockedFetchContext)).toStrictEqual({
appId: {
clicks_total: total + 1 + 10,
clicks_7_days: total + 1,
@ -202,7 +198,7 @@ describe('telemetry_application_usage', () => {
getUsageCollector.mockImplementation(() => savedObjectClient);
expect(await collector.fetch(callCluster, esClient)).toStrictEqual({
expect(await collector.fetch(mockedFetchContext)).toStrictEqual({
appId: {
clicks_total: 1,
clicks_7_days: 0,

View file

@ -21,7 +21,7 @@ import {
CollectorOptions,
createUsageCollectionSetupMock,
} from '../../../../usage_collection/server/usage_collection.mock';
import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
import { registerCoreUsageCollector } from '.';
import { coreUsageDataServiceMock } from '../../../../../core/server/mocks';
import { CoreUsageData } from 'src/core/server/';
@ -35,7 +35,7 @@ describe('telemetry_core', () => {
return createUsageCollectionSetupMock().makeUsageCollector(config);
});
const callCluster = jest.fn().mockImplementation(() => ({}));
const collectorFetchContext = createCollectorFetchContextMock();
const coreUsageDataStart = coreUsageDataServiceMock.createStartContract();
const getCoreUsageDataReturnValue = (Symbol('core telemetry') as any) as CoreUsageData;
coreUsageDataStart.getCoreUsageData.mockResolvedValue(getCoreUsageDataReturnValue);
@ -48,6 +48,6 @@ describe('telemetry_core', () => {
});
test('fetch', async () => {
expect(await collector.fetch(callCluster)).toEqual(getCoreUsageDataReturnValue);
expect(await collector.fetch(collectorFetchContext)).toEqual(getCoreUsageDataReturnValue);
});
});

View file

@ -20,10 +20,12 @@
import { CspConfig, ICspConfig } from '../../../../../core/server';
import { createCspCollector } from './csp_collector';
import { httpServiceMock } from '../../../../../core/server/mocks';
import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
describe('csp collector', () => {
let httpMock: ReturnType<typeof httpServiceMock.createSetupContract>;
const mockCallCluster = null as any;
// changed for consistency with expected implementation
const mockedFetchContext = createCollectorFetchContextMock();
function updateCsp(config: Partial<ICspConfig>) {
httpMock.csp = new CspConfig(config);
@ -36,28 +38,28 @@ describe('csp collector', () => {
test('fetches whether strict mode is enabled', async () => {
const collector = createCspCollector(httpMock);
expect((await collector.fetch(mockCallCluster)).strict).toEqual(true);
expect((await collector.fetch(mockedFetchContext)).strict).toEqual(true);
updateCsp({ strict: false });
expect((await collector.fetch(mockCallCluster)).strict).toEqual(false);
expect((await collector.fetch(mockedFetchContext)).strict).toEqual(false);
});
test('fetches whether the legacy browser warning is enabled', async () => {
const collector = createCspCollector(httpMock);
expect((await collector.fetch(mockCallCluster)).warnLegacyBrowsers).toEqual(true);
expect((await collector.fetch(mockedFetchContext)).warnLegacyBrowsers).toEqual(true);
updateCsp({ warnLegacyBrowsers: false });
expect((await collector.fetch(mockCallCluster)).warnLegacyBrowsers).toEqual(false);
expect((await collector.fetch(mockedFetchContext)).warnLegacyBrowsers).toEqual(false);
});
test('fetches whether the csp rules have been changed or not', async () => {
const collector = createCspCollector(httpMock);
expect((await collector.fetch(mockCallCluster)).rulesChangedFromDefault).toEqual(false);
expect((await collector.fetch(mockedFetchContext)).rulesChangedFromDefault).toEqual(false);
updateCsp({ rules: ['not', 'default'] });
expect((await collector.fetch(mockCallCluster)).rulesChangedFromDefault).toEqual(true);
expect((await collector.fetch(mockedFetchContext)).rulesChangedFromDefault).toEqual(true);
});
test('does not include raw csp rules under any property names', async () => {
@ -69,7 +71,7 @@ describe('csp collector', () => {
//
// We use a snapshot here to ensure csp.rules isn't finding its way into the
// payload under some new and unexpected variable name (e.g. cspRules).
expect(await collector.fetch(mockCallCluster)).toMatchInlineSnapshot(`
expect(await collector.fetch(mockedFetchContext)).toMatchInlineSnapshot(`
Object {
"rulesChangedFromDefault": false,
"strict": true,

View file

@ -22,7 +22,7 @@ import {
CollectorOptions,
createUsageCollectionSetupMock,
} from '../../../../usage_collection/server/usage_collection.mock';
import { createCollectorFetchContextMock } from '../../../../usage_collection/server/mocks';
import { registerKibanaUsageCollector } from './';
describe('telemetry_kibana', () => {
@ -35,7 +35,12 @@ describe('telemetry_kibana', () => {
});
const legacyConfig$ = pluginInitializerContextConfigMock({}).legacy.globalConfig$;
const callCluster = jest.fn().mockImplementation(() => ({}));
const getMockFetchClients = (hits?: unknown[]) => {
const fetchParamsMock = createCollectorFetchContextMock();
fetchParamsMock.callCluster.mockResolvedValue({ hits: { hits } });
return fetchParamsMock;
};
beforeAll(() => registerKibanaUsageCollector(usageCollectionMock, legacyConfig$));
afterAll(() => jest.clearAllTimers());
@ -46,7 +51,7 @@ describe('telemetry_kibana', () => {
});
test('fetch', async () => {
expect(await collector.fetch(callCluster)).toStrictEqual({
expect(await collector.fetch(getMockFetchClients())).toStrictEqual({
index: '.kibana-tests',
dashboard: { total: 0 },
visualization: { total: 0 },

View file

@ -44,7 +44,7 @@ export function getKibanaUsageCollector(
graph_workspace: { total: { type: 'long' } },
timelion_sheet: { total: { type: 'long' } },
},
async fetch(callCluster) {
async fetch({ callCluster }) {
const {
kibana: { index },
} = await legacyConfig$.pipe(take(1)).toPromise();

View file

@ -21,6 +21,7 @@ import { uiSettingsServiceMock } from '../../../../../core/server/mocks';
import {
CollectorOptions,
createUsageCollectionSetupMock,
createCollectorFetchContextMock,
} from '../../../../usage_collection/server/usage_collection.mock';
import { registerManagementUsageCollector } from './';
@ -36,7 +37,7 @@ describe('telemetry_application_usage_collector', () => {
const uiSettingsClient = uiSettingsServiceMock.createClient();
const getUiSettingsClient = jest.fn(() => uiSettingsClient);
const callCluster = jest.fn();
const mockedFetchContext = createCollectorFetchContextMock();
beforeAll(() => {
registerManagementUsageCollector(usageCollectionMock, getUiSettingsClient);
@ -59,11 +60,11 @@ describe('telemetry_application_usage_collector', () => {
uiSettingsClient.getUserProvided.mockImplementationOnce(async () => ({
'my-key': { userValue: 'my-value' },
}));
await expect(collector.fetch(callCluster)).resolves.toMatchSnapshot();
await expect(collector.fetch(mockedFetchContext)).resolves.toMatchSnapshot();
});
test('fetch() should not fail if invoked when not ready', async () => {
getUiSettingsClient.mockImplementationOnce(() => undefined as any);
await expect(collector.fetch(callCluster)).resolves.toBe(undefined);
await expect(collector.fetch(mockedFetchContext)).resolves.toBe(undefined);
});
});

View file

@ -21,6 +21,7 @@ import { Subject } from 'rxjs';
import {
CollectorOptions,
createUsageCollectionSetupMock,
createCollectorFetchContextMock,
} from '../../../../usage_collection/server/usage_collection.mock';
import { registerOpsStatsCollector } from './';
@ -36,7 +37,7 @@ describe('telemetry_ops_stats', () => {
});
const metrics$ = new Subject<OpsMetrics>();
const callCluster = jest.fn();
const mockedFetchContext = createCollectorFetchContextMock();
const metric: OpsMetrics = {
collected_at: new Date('2020-01-01 01:00:00'),
@ -92,7 +93,7 @@ describe('telemetry_ops_stats', () => {
test('should return something when there is a metric', async () => {
metrics$.next(metric);
expect(collector.isReady()).toBe(true);
expect(await collector.fetch(callCluster)).toMatchSnapshot({
expect(await collector.fetch(mockedFetchContext)).toMatchSnapshot({
concurrent_connections: 20,
os: {
load: {

View file

@ -21,6 +21,7 @@ import { savedObjectsRepositoryMock } from '../../../../../core/server/mocks';
import {
CollectorOptions,
createUsageCollectionSetupMock,
createCollectorFetchContextMock,
} from '../../../../usage_collection/server/usage_collection.mock';
import { registerUiMetricUsageCollector } from './';
@ -36,7 +37,7 @@ describe('telemetry_ui_metric', () => {
const getUsageCollector = jest.fn();
const registerType = jest.fn();
const callCluster = jest.fn();
const mockedFetchContext = createCollectorFetchContextMock();
beforeAll(() =>
registerUiMetricUsageCollector(usageCollectionMock, registerType, getUsageCollector)
@ -47,7 +48,7 @@ describe('telemetry_ui_metric', () => {
});
test('if no savedObjectClient initialised, return undefined', async () => {
expect(await collector.fetch(callCluster)).toBeUndefined();
expect(await collector.fetch(mockedFetchContext)).toBeUndefined();
});
test('when savedObjectClient is initialised, return something', async () => {
@ -61,7 +62,7 @@ describe('telemetry_ui_metric', () => {
);
getUsageCollector.mockImplementation(() => savedObjectClient);
expect(await collector.fetch(callCluster)).toStrictEqual({});
expect(await collector.fetch(mockedFetchContext)).toStrictEqual({});
expect(savedObjectClient.bulkCreate).not.toHaveBeenCalled();
});
@ -85,7 +86,7 @@ describe('telemetry_ui_metric', () => {
getUsageCollector.mockImplementation(() => savedObjectClient);
expect(await collector.fetch(callCluster)).toStrictEqual({
expect(await collector.fetch(mockedFetchContext)).toStrictEqual({
testAppName: [
{ key: 'testKeyName1', value: 3 },
{ key: 'testKeyName2', value: 5 },

View file

@ -37,10 +37,9 @@ All you need to provide is a `type` for organizing your fields, `schema` field t
```
3. Creating and registering a Usage Collector. Ideally collectors would be defined in a separate directory `server/collectors/register.ts`.
```ts
// server/collectors/register.ts
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { UsageCollectionSetup, CollectorFetchContext } from 'src/plugins/usage_collection/server';
import { APICluster } from 'kibana/server';
interface Usage {
@ -63,7 +62,7 @@ All you need to provide is a `type` for organizing your fields, `schema` field t
total: 'long',
},
},
fetch: async (callCluster: APICluster, esClient: IClusterClient) => {
fetch: async (collectorFetchContext: CollectorFetchContext) => {
// query ES and get some data
// summarize the data into a model

View file

@ -45,11 +45,23 @@ export type MakeSchemaFrom<Base> = {
: RecursiveMakeSchemaFrom<Required<Base>[Key]>;
};
export interface CollectorFetchContext {
/**
* @depricated Scoped Legacy Elasticsearch client: use esClient instead
*/
callCluster: LegacyAPICaller;
/**
* Request-scoped Elasticsearch client:
* - When users are requesting a sample of data, it is scoped to their role to avoid exposing data they should't read
* - When building the telemetry data payload to report to the remote cluster, the requests are scoped to the `kibana` internal user
*/
esClient: ElasticsearchClient;
}
export interface CollectorOptions<T = unknown, U = T> {
type: string;
init?: Function;
schema?: MakeSchemaFrom<T>;
fetch: (callCluster: LegacyAPICaller, esClient?: ElasticsearchClient) => Promise<T> | T;
fetch: (collectorFetchContext: CollectorFetchContext) => Promise<T> | T;
/*
* A hook for allowing the fetched data payload to be organized into a typed
* data model for internal bulk upload. See defaultFormatterForBulkUpload for

View file

@ -81,7 +81,9 @@ describe('CollectorSet', () => {
collectors.registerCollector(
new Collector(logger, {
type: 'MY_TEST_COLLECTOR',
fetch: (caller: any) => caller(),
fetch: (collectorFetchContext: any) => {
return collectorFetchContext.callCluster();
},
isReady: () => true,
})
);

View file

@ -122,9 +122,6 @@ export class CollectorSet {
return allReady;
};
// all collections eventually pass through bulkFetch.
// the shape of the response is different when using the new ES client as is the error handling.
// We'll handle the refactor for using the new client in a follow up PR.
public bulkFetch = async (
callCluster: LegacyAPICaller,
esClient: ElasticsearchClient,
@ -136,7 +133,7 @@ export class CollectorSet {
try {
return {
type: collector.type,
result: await collector.fetch(callCluster, esClient), // each collector must ensure they handle the response appropriately.
result: await collector.fetch({ callCluster, esClient }),
};
} catch (err) {
this.logger.warn(err);

View file

@ -24,5 +24,6 @@ export {
SchemaField,
MakeSchemaFrom,
CollectorOptions,
CollectorFetchContext,
} from './collector';
export { UsageCollector } from './usage_collector';

View file

@ -26,6 +26,7 @@ export {
SchemaField,
CollectorOptions,
Collector,
CollectorFetchContext,
} from './collector';
export { UsageCollectionSetup } from './plugin';
export { config } from './config';

View file

@ -20,6 +20,7 @@
import { loggingSystemMock } from '../../../core/server/mocks';
import { UsageCollectionSetup } from './plugin';
import { CollectorSet } from './collector';
export { createCollectorFetchContextMock } from './usage_collection.mock';
const createSetupContract = () => {
return {

View file

@ -17,8 +17,10 @@
* under the License.
*/
import { elasticsearchServiceMock } from '../../../../src/core/server/mocks';
import { CollectorOptions } from './collector/collector';
import { UsageCollectionSetup } from './index';
import { UsageCollectionSetup, CollectorFetchContext } from './index';
export { CollectorOptions };
@ -45,3 +47,11 @@ export const createUsageCollectionSetupMock = () => {
usageCollectionSetupMock.areAllCollectorsReady.mockResolvedValue(true);
return usageCollectionSetupMock;
};
export function createCollectorFetchContextMock(): jest.Mocked<CollectorFetchContext> {
const collectorFetchClientsMock: jest.Mocked<CollectorFetchContext> = {
callCluster: elasticsearchServiceMock.createLegacyClusterClient().callAsInternalUser,
esClient: elasticsearchServiceMock.createClusterClient().asInternalUser,
};
return collectorFetchClientsMock;
}

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { LegacyAPICaller, CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server';
import { CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server';
import { UsageCollectionSetup } from '../../../usage_collection/server';
import { tsvbTelemetrySavedObjectType } from '../saved_objects';
@ -49,7 +49,7 @@ export class ValidationTelemetryService implements Plugin<ValidationTelemetrySer
usageCollection.makeUsageCollector<Usage>({
type: 'tsvb-validation',
isReady: () => this.kibanaIndex !== '',
fetch: async (callCluster: LegacyAPICaller) => {
fetch: async ({ callCluster }) => {
try {
const response = await callCluster('get', {
index: this.kibanaIndex,

View file

@ -17,9 +17,9 @@
* under the License.
*/
import { LegacyAPICaller } from 'src/core/server';
import { getStats } from './get_usage_collector';
import { HomeServerPluginSetup } from '../../../home/server';
import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
const mockedSavedObjects = [
// vega-lite lib spec
@ -70,8 +70,11 @@ const mockedSavedObjects = [
},
];
const getMockCallCluster = (hits?: unknown[]) =>
jest.fn().mockReturnValue(Promise.resolve({ hits: { hits } }) as unknown) as LegacyAPICaller;
const getMockCollectorFetchContext = (hits?: unknown[]) => {
const fetchParamsMock = createCollectorFetchContextMock();
fetchParamsMock.callCluster.mockResolvedValue({ hits: { hits } });
return fetchParamsMock;
};
describe('Vega visualization usage collector', () => {
const mockIndex = 'mock_index';
@ -101,19 +104,23 @@ describe('Vega visualization usage collector', () => {
};
test('Returns undefined when no results found (undefined)', async () => {
const result = await getStats(getMockCallCluster(), mockIndex, mockDeps);
const result = await getStats(getMockCollectorFetchContext().callCluster, mockIndex, mockDeps);
expect(result).toBeUndefined();
});
test('Returns undefined when no results found (0 results)', async () => {
const result = await getStats(getMockCallCluster([]), mockIndex, mockDeps);
const result = await getStats(
getMockCollectorFetchContext([]).callCluster,
mockIndex,
mockDeps
);
expect(result).toBeUndefined();
});
test('Returns undefined when no vega saved objects found', async () => {
const mockCallCluster = getMockCallCluster([
const mockCollectorFetchContext = getMockCollectorFetchContext([
{
_id: 'visualization:myvis-123',
_source: {
@ -122,13 +129,13 @@ describe('Vega visualization usage collector', () => {
},
},
]);
const result = await getStats(mockCallCluster, mockIndex, mockDeps);
const result = await getStats(mockCollectorFetchContext.callCluster, mockIndex, mockDeps);
expect(result).toBeUndefined();
});
test('Should ingnore sample data visualizations', async () => {
const mockCallCluster = getMockCallCluster([
const mockCollectorFetchContext = getMockCollectorFetchContext([
{
_id: 'visualization:sampledata-123',
_source: {
@ -146,14 +153,14 @@ describe('Vega visualization usage collector', () => {
},
]);
const result = await getStats(mockCallCluster, mockIndex, mockDeps);
const result = await getStats(mockCollectorFetchContext.callCluster, mockIndex, mockDeps);
expect(result).toBeUndefined();
});
test('Summarizes visualizations response data', async () => {
const mockCallCluster = getMockCallCluster(mockedSavedObjects);
const result = await getStats(mockCallCluster, mockIndex, mockDeps);
const mockCollectorFetchContext = getMockCollectorFetchContext(mockedSavedObjects);
const result = await getStats(mockCollectorFetchContext.callCluster, mockIndex, mockDeps);
expect(result).toMatchObject({
vega_lib_specs_total: 2,

View file

@ -20,6 +20,7 @@
import { of } from 'rxjs';
import { mockStats, mockGetStats } from './get_usage_collector.mock';
import { createUsageCollectionSetupMock } from 'src/plugins/usage_collection/server/usage_collection.mock';
import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
import { HomeServerPluginSetup } from '../../../home/server';
import { registerVegaUsageCollector } from './register_vega_collector';
@ -59,10 +60,14 @@ describe('registerVegaUsageCollector', () => {
const mockCollectorSet = createUsageCollectionSetupMock();
registerVegaUsageCollector(mockCollectorSet, mockConfig, mockDeps);
const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0];
const mockCallCluster = jest.fn();
const fetchResult = await usageCollectorConfig.fetch(mockCallCluster);
const mockedCollectorFetchContext = createCollectorFetchContextMock();
const fetchResult = await usageCollectorConfig.fetch(mockedCollectorFetchContext);
expect(mockGetStats).toBeCalledTimes(1);
expect(mockGetStats).toBeCalledWith(mockCallCluster, mockIndex, mockDeps);
expect(mockGetStats).toBeCalledWith(
mockedCollectorFetchContext.callCluster,
mockIndex,
mockDeps
);
expect(fetchResult).toBe(mockStats);
});
});

View file

@ -35,7 +35,7 @@ export function registerVegaUsageCollector(
vega_lite_lib_specs_total: { type: 'long' },
vega_use_map_total: { type: 'long' },
},
fetch: async (callCluster) => {
fetch: async ({ callCluster }) => {
const { index } = (await config.pipe(first()).toPromise()).kibana;
return await getStats(callCluster, index, dependencies);

View file

@ -20,6 +20,7 @@
import { of } from 'rxjs';
import { mockStats, mockGetStats } from './get_usage_collector.mock';
import { createUsageCollectionSetupMock } from 'src/plugins/usage_collection/server/usage_collection.mock';
import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
import { registerVisualizationsCollector } from './register_visualizations_collector';
@ -58,10 +59,10 @@ describe('registerVisualizationsCollector', () => {
const mockCollectorSet = createUsageCollectionSetupMock();
registerVisualizationsCollector(mockCollectorSet, mockConfig);
const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0];
const mockCallCluster = jest.fn();
const fetchResult = await usageCollectorConfig.fetch(mockCallCluster);
const mockCollectorFetchContext = createCollectorFetchContextMock();
const fetchResult = await usageCollectorConfig.fetch(mockCollectorFetchContext);
expect(mockGetStats).toBeCalledTimes(1);
expect(mockGetStats).toBeCalledWith(mockCallCluster, mockIndex);
expect(mockGetStats).toBeCalledWith(mockCollectorFetchContext.callCluster, mockIndex);
expect(fetchResult).toBe(mockStats);
});
});

View file

@ -41,7 +41,7 @@ export function registerVisualizationsCollector(
saved_90_days_total: { type: 'long' },
},
},
fetch: async (callCluster) => {
fetch: async ({ callCluster }) => {
const index = (await config.pipe(first()).toPromise()).kibana.index;
return await getStats(callCluster, index);
},

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { CollectorFetchContext, UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { TelemetryCollector } from '../../types';
import { workpadCollector, workpadSchema, WorkpadTelemetry } from './workpad_collector';
@ -37,7 +37,7 @@ export function registerCanvasUsageCollector(
const canvasCollector = usageCollection.makeUsageCollector<CanvasUsage>({
type: 'canvas',
isReady: () => true,
fetch: async (callCluster) => {
fetch: async ({ callCluster }: CollectorFetchContext) => {
const collectorResults = await Promise.all(
collectors.map((collector) => collector(kibanaIndex, callCluster))
);

View file

@ -5,6 +5,7 @@
*/
import { createCloudUsageCollector } from './cloud_usage_collector';
import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
const mockUsageCollection = () => ({
makeUsageCollector: jest.fn().mockImplementation((args: any) => ({ ...args })),
@ -25,9 +26,9 @@ describe('createCloudUsageCollector', () => {
const mockConfigs = getMockConfigs(true);
const usageCollection = mockUsageCollection() as any;
const collector = createCloudUsageCollector(usageCollection, mockConfigs);
const callCluster = {} as any; // Sending any as the callCluster client because it's not needed in this collector but TS requires it when calling it.
const collectorFetchContext = createCollectorFetchContextMock();
expect((await collector.fetch(callCluster)).isCloudEnabled).toBe(true); // Adding the await because the fetch can be a Promise or a synchronous method and TS complains in the test if not awaited
expect((await collector.fetch(collectorFetchContext)).isCloudEnabled).toBe(true); // Adding the await because the fetch can be a Promise or a synchronous method and TS complains in the test if not awaited
});
});
});

View file

@ -6,7 +6,8 @@
import * as Rx from 'rxjs';
import sinon from 'sinon';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { CollectorFetchContext, UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
import { ReportingConfig, ReportingCore } from '../';
import { getExportTypesRegistry } from '../lib/export_types_registry';
import { createMockConfig, createMockConfigSchema, createMockReportingCore } from '../test_helpers';
@ -56,6 +57,11 @@ function getPluginsMock(
const getResponseMock = (base = {}) => base;
const getMockFetchClients = (resp: any) => {
const fetchParamsMock = createCollectorFetchContextMock();
fetchParamsMock.callCluster.mockResolvedValue(resp);
return fetchParamsMock;
};
describe('license checks', () => {
let mockConfig: ReportingConfig;
let mockCore: ReportingCore;
@ -68,7 +74,6 @@ describe('license checks', () => {
let usageStats: any;
beforeAll(async () => {
const plugins = getPluginsMock({ license: 'basic' });
const callClusterMock = jest.fn(() => Promise.resolve(getResponseMock()));
const { fetch } = getReportingUsageCollector(
mockCore,
plugins.usageCollection,
@ -78,7 +83,7 @@ describe('license checks', () => {
return Promise.resolve(true);
}
);
usageStats = await fetch(callClusterMock as any);
usageStats = await fetch(getMockFetchClients(getResponseMock()));
});
test('sets enables to true', async () => {
@ -98,7 +103,6 @@ describe('license checks', () => {
let usageStats: any;
beforeAll(async () => {
const plugins = getPluginsMock({ license: 'none' });
const callClusterMock = jest.fn(() => Promise.resolve(getResponseMock()));
const { fetch } = getReportingUsageCollector(
mockCore,
plugins.usageCollection,
@ -108,7 +112,7 @@ describe('license checks', () => {
return Promise.resolve(true);
}
);
usageStats = await fetch(callClusterMock as any);
usageStats = await fetch(getMockFetchClients(getResponseMock()));
});
test('sets enables to true', async () => {
@ -128,7 +132,6 @@ describe('license checks', () => {
let usageStats: any;
beforeAll(async () => {
const plugins = getPluginsMock({ license: 'platinum' });
const callClusterMock = jest.fn(() => Promise.resolve(getResponseMock()));
const { fetch } = getReportingUsageCollector(
mockCore,
plugins.usageCollection,
@ -138,7 +141,7 @@ describe('license checks', () => {
return Promise.resolve(true);
}
);
usageStats = await fetch(callClusterMock as any);
usageStats = await fetch(getMockFetchClients(getResponseMock()));
});
test('sets enables to true', async () => {
@ -158,7 +161,6 @@ describe('license checks', () => {
let usageStats: any;
beforeAll(async () => {
const plugins = getPluginsMock({ license: 'basic' });
const callClusterMock = jest.fn(() => Promise.resolve({}));
const { fetch } = getReportingUsageCollector(
mockCore,
plugins.usageCollection,
@ -168,7 +170,7 @@ describe('license checks', () => {
return Promise.resolve(true);
}
);
usageStats = await fetch(callClusterMock as any);
usageStats = await fetch(getMockFetchClients({}));
});
test('sets enables to true', async () => {
@ -184,6 +186,7 @@ describe('license checks', () => {
describe('data modeling', () => {
let mockConfig: ReportingConfig;
let mockCore: ReportingCore;
let collectorFetchContext: CollectorFetchContext;
beforeAll(async () => {
mockConfig = createMockConfig(createMockConfigSchema());
mockCore = await createMockReportingCore(mockConfig);
@ -199,44 +202,42 @@ describe('data modeling', () => {
return Promise.resolve(true);
}
);
const callClusterMock = jest.fn(() =>
Promise.resolve(
getResponseMock({
aggregations: {
ranges: {
buckets: {
all: {
doc_count: 12,
jobTypes: { buckets: [ { doc_count: 9, key: 'printable_pdf' }, { doc_count: 3, key: 'PNG' }, ], },
layoutTypes: { doc_count: 9, pdf: { buckets: [{ doc_count: 9, key: 'preserve_layout' }] }, },
objectTypes: { doc_count: 9, pdf: { buckets: [ { doc_count: 6, key: 'canvas workpad' }, { doc_count: 3, key: 'visualization' }, ], }, },
statusByApp: { buckets: [ { doc_count: 10, jobTypes: { buckets: [ { appNames: { buckets: [ { doc_count: 6, key: 'canvas workpad' }, { doc_count: 3, key: 'visualization' }, ], }, doc_count: 9, key: 'printable_pdf', }, { appNames: { buckets: [{ doc_count: 1, key: 'visualization' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'completed', }, { doc_count: 1, jobTypes: { buckets: [ { appNames: { buckets: [{ doc_count: 1, key: 'dashboard' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'completed_with_warnings', }, { doc_count: 1, jobTypes: { buckets: [ { appNames: { buckets: [{ doc_count: 1, key: 'dashboard' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'failed', }, ], },
statusTypes: { buckets: [ { doc_count: 10, key: 'completed' }, { doc_count: 1, key: 'completed_with_warnings' }, { doc_count: 1, key: 'failed' }, ], },
},
last7Days: {
doc_count: 1,
jobTypes: { buckets: [{ doc_count: 1, key: 'PNG' }] },
layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
objectTypes: { doc_count: 0, pdf: { buckets: [] } },
statusByApp: { buckets: [ { doc_count: 1, jobTypes: { buckets: [ { appNames: { buckets: [{ doc_count: 1, key: 'dashboard' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'completed_with_warnings', }, ], },
statusTypes: { buckets: [{ doc_count: 1, key: 'completed_with_warnings' }] },
},
lastDay: {
doc_count: 1,
jobTypes: { buckets: [{ doc_count: 1, key: 'PNG' }] },
layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
objectTypes: { doc_count: 0, pdf: { buckets: [] } },
statusByApp: { buckets: [ { doc_count: 1, jobTypes: { buckets: [ { appNames: { buckets: [{ doc_count: 1, key: 'dashboard' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'completed_with_warnings', }, ], },
statusTypes: { buckets: [{ doc_count: 1, key: 'completed_with_warnings' }] },
},
collectorFetchContext = getMockFetchClients(
getResponseMock(
{
aggregations: {
ranges: {
buckets: {
all: {
doc_count: 12,
jobTypes: { buckets: [ { doc_count: 9, key: 'printable_pdf' }, { doc_count: 3, key: 'PNG' }, ], },
layoutTypes: { doc_count: 9, pdf: { buckets: [{ doc_count: 9, key: 'preserve_layout' }] }, },
objectTypes: { doc_count: 9, pdf: { buckets: [ { doc_count: 6, key: 'canvas workpad' }, { doc_count: 3, key: 'visualization' }, ], }, },
statusByApp: { buckets: [ { doc_count: 10, jobTypes: { buckets: [ { appNames: { buckets: [ { doc_count: 6, key: 'canvas workpad' }, { doc_count: 3, key: 'visualization' }, ], }, doc_count: 9, key: 'printable_pdf', }, { appNames: { buckets: [{ doc_count: 1, key: 'visualization' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'completed', }, { doc_count: 1, jobTypes: { buckets: [ { appNames: { buckets: [{ doc_count: 1, key: 'dashboard' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'completed_with_warnings', }, { doc_count: 1, jobTypes: { buckets: [ { appNames: { buckets: [{ doc_count: 1, key: 'dashboard' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'failed', }, ], },
statusTypes: { buckets: [ { doc_count: 10, key: 'completed' }, { doc_count: 1, key: 'completed_with_warnings' }, { doc_count: 1, key: 'failed' }, ], },
},
last7Days: {
doc_count: 1,
jobTypes: { buckets: [{ doc_count: 1, key: 'PNG' }] },
layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
objectTypes: { doc_count: 0, pdf: { buckets: [] } },
statusByApp: { buckets: [ { doc_count: 1, jobTypes: { buckets: [ { appNames: { buckets: [{ doc_count: 1, key: 'dashboard' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'completed_with_warnings', }, ], },
statusTypes: { buckets: [{ doc_count: 1, key: 'completed_with_warnings' }] },
},
lastDay: {
doc_count: 1,
jobTypes: { buckets: [{ doc_count: 1, key: 'PNG' }] },
layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
objectTypes: { doc_count: 0, pdf: { buckets: [] } },
statusByApp: { buckets: [ { doc_count: 1, jobTypes: { buckets: [ { appNames: { buckets: [{ doc_count: 1, key: 'dashboard' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'completed_with_warnings', }, ], },
statusTypes: { buckets: [{ doc_count: 1, key: 'completed_with_warnings' }] },
},
},
},
} as SearchResponse) // prettier-ignore
)
},
} as SearchResponse) // prettier-ignore
);
const usageStats = await fetch(callClusterMock as any);
const usageStats = await fetch(collectorFetchContext);
expect(usageStats).toMatchSnapshot();
});
@ -251,44 +252,42 @@ describe('data modeling', () => {
return Promise.resolve(true);
}
);
const callClusterMock = jest.fn(() =>
Promise.resolve(
getResponseMock({
aggregations: {
ranges: {
buckets: {
all: {
doc_count: 4,
layoutTypes: { doc_count: 2, pdf: { buckets: [{ key: 'preserve_layout', doc_count: 2 }] }, },
statusByApp: { buckets: [ { key: 'completed', doc_count: 4, jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2, appNames: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, }, { key: 'PNG', doc_count: 1, appNames: { buckets: [{ key: 'dashboard', doc_count: 1 }] }, }, { key: 'csv', doc_count: 1, appNames: { buckets: [] } }, ], }, }, ], },
objectTypes: { doc_count: 2, pdf: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, },
statusTypes: { buckets: [{ key: 'completed', doc_count: 4 }] },
jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2 }, { key: 'PNG', doc_count: 1 }, { key: 'csv', doc_count: 1 }, ], },
},
last7Days: {
doc_count: 4,
layoutTypes: { doc_count: 2, pdf: { buckets: [{ key: 'preserve_layout', doc_count: 2 }] }, },
statusByApp: { buckets: [ { key: 'completed', doc_count: 4, jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2, appNames: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, }, { key: 'PNG', doc_count: 1, appNames: { buckets: [{ key: 'dashboard', doc_count: 1 }] }, }, { key: 'csv', doc_count: 1, appNames: { buckets: [] } }, ], }, }, ], },
objectTypes: { doc_count: 2, pdf: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, },
statusTypes: { buckets: [{ key: 'completed', doc_count: 4 }] },
jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2 }, { key: 'PNG', doc_count: 1 }, { key: 'csv', doc_count: 1 }, ], },
},
lastDay: {
doc_count: 4,
layoutTypes: { doc_count: 2, pdf: { buckets: [{ key: 'preserve_layout', doc_count: 2 }] }, },
statusByApp: { buckets: [ { key: 'completed', doc_count: 4, jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2, appNames: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, }, { key: 'PNG', doc_count: 1, appNames: { buckets: [{ key: 'dashboard', doc_count: 1 }] }, }, { key: 'csv', doc_count: 1, appNames: { buckets: [] } }, ], }, }, ], },
objectTypes: { doc_count: 2, pdf: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, },
statusTypes: { buckets: [{ key: 'completed', doc_count: 4 }] },
jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2 }, { key: 'PNG', doc_count: 1 }, { key: 'csv', doc_count: 1 }, ], },
},
collectorFetchContext = getMockFetchClients(
getResponseMock(
{
aggregations: {
ranges: {
buckets: {
all: {
doc_count: 4,
layoutTypes: { doc_count: 2, pdf: { buckets: [{ key: 'preserve_layout', doc_count: 2 }] }, },
statusByApp: { buckets: [ { key: 'completed', doc_count: 4, jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2, appNames: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, }, { key: 'PNG', doc_count: 1, appNames: { buckets: [{ key: 'dashboard', doc_count: 1 }] }, }, { key: 'csv', doc_count: 1, appNames: { buckets: [] } }, ], }, }, ], },
objectTypes: { doc_count: 2, pdf: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, },
statusTypes: { buckets: [{ key: 'completed', doc_count: 4 }] },
jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2 }, { key: 'PNG', doc_count: 1 }, { key: 'csv', doc_count: 1 }, ], },
},
last7Days: {
doc_count: 4,
layoutTypes: { doc_count: 2, pdf: { buckets: [{ key: 'preserve_layout', doc_count: 2 }] }, },
statusByApp: { buckets: [ { key: 'completed', doc_count: 4, jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2, appNames: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, }, { key: 'PNG', doc_count: 1, appNames: { buckets: [{ key: 'dashboard', doc_count: 1 }] }, }, { key: 'csv', doc_count: 1, appNames: { buckets: [] } }, ], }, }, ], },
objectTypes: { doc_count: 2, pdf: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, },
statusTypes: { buckets: [{ key: 'completed', doc_count: 4 }] },
jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2 }, { key: 'PNG', doc_count: 1 }, { key: 'csv', doc_count: 1 }, ], },
},
lastDay: {
doc_count: 4,
layoutTypes: { doc_count: 2, pdf: { buckets: [{ key: 'preserve_layout', doc_count: 2 }] }, },
statusByApp: { buckets: [ { key: 'completed', doc_count: 4, jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2, appNames: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, }, { key: 'PNG', doc_count: 1, appNames: { buckets: [{ key: 'dashboard', doc_count: 1 }] }, }, { key: 'csv', doc_count: 1, appNames: { buckets: [] } }, ], }, }, ], },
objectTypes: { doc_count: 2, pdf: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, },
statusTypes: { buckets: [{ key: 'completed', doc_count: 4 }] },
jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2 }, { key: 'PNG', doc_count: 1 }, { key: 'csv', doc_count: 1 }, ], },
},
},
},
} as SearchResponse) // prettier-ignore
)
},
} as SearchResponse) // prettier-ignore
);
const usageStats = await fetch(callClusterMock as any);
const usageStats = await fetch(collectorFetchContext);
expect(usageStats).toMatchSnapshot();
});
@ -303,43 +302,42 @@ describe('data modeling', () => {
return Promise.resolve(true);
}
);
const callClusterMock = jest.fn(() =>
Promise.resolve(
getResponseMock({
aggregations: {
ranges: {
buckets: {
all: {
doc_count: 0,
jobTypes: { buckets: [] },
layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
objectTypes: { doc_count: 0, pdf: { buckets: [] } },
statusByApp: { buckets: [] },
statusTypes: { buckets: [] },
},
last7Days: {
doc_count: 0,
jobTypes: { buckets: [] },
layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
objectTypes: { doc_count: 0, pdf: { buckets: [] } },
statusByApp: { buckets: [] },
statusTypes: { buckets: [] },
},
lastDay: {
doc_count: 0,
jobTypes: { buckets: [] },
layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
objectTypes: { doc_count: 0, pdf: { buckets: [] } },
statusByApp: { buckets: [] },
statusTypes: { buckets: [] },
},
},
collectorFetchContext = getMockFetchClients(
getResponseMock({
aggregations: {
ranges: {
buckets: {
all: {
doc_count: 0,
jobTypes: { buckets: [] },
layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
objectTypes: { doc_count: 0, pdf: { buckets: [] } },
statusByApp: { buckets: [] },
statusTypes: { buckets: [] },
},
last7Days: {
doc_count: 0,
jobTypes: { buckets: [] },
layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
objectTypes: { doc_count: 0, pdf: { buckets: [] } },
statusByApp: { buckets: [] },
statusTypes: { buckets: [] },
},
lastDay: {
doc_count: 0,
jobTypes: { buckets: [] },
layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
objectTypes: { doc_count: 0, pdf: { buckets: [] } },
statusByApp: { buckets: [] },
statusTypes: { buckets: [] },
},
},
} as SearchResponse)
)
},
},
} as SearchResponse) // prettier-ignore
);
const usageStats = await fetch(callClusterMock as any);
const usageStats = await fetch(collectorFetchContext);
expect(usageStats).toMatchSnapshot();
});

View file

@ -5,8 +5,7 @@
*/
import { first, map } from 'rxjs/operators';
import { LegacyAPICaller } from 'kibana/server';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { CollectorFetchContext, UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { ReportingCore } from '../';
import { ExportTypesRegistry } from '../lib/export_types_registry';
import { ReportingSetupDeps } from '../types';
@ -37,7 +36,7 @@ export function getReportingUsageCollector(
) {
return usageCollection.makeUsageCollector<ReportingUsageType, XpackBulkUpload>({
type: 'reporting',
fetch: (callCluster: LegacyAPICaller) => {
fetch: ({ callCluster }: CollectorFetchContext) => {
const config = reporting.getConfig();
return getReportingUsage(config, getLicense, callCluster, exportTypesRegistry);
},

View file

@ -5,7 +5,7 @@
*/
import { get } from 'lodash';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { UsageCollectionSetup, CollectorFetchContext } from 'src/plugins/usage_collection/server';
import { LegacyAPICaller } from 'kibana/server';
interface IdToFlagMap {
@ -211,7 +211,7 @@ export function registerRollupUsageCollector(
total: { type: 'long' },
},
},
fetch: async (callCluster: LegacyAPICaller) => {
fetch: async ({ callCluster }: CollectorFetchContext) => {
const rollupIndexPatterns = await fetchRollupIndexPatterns(kibanaIndex, callCluster);
const rollupIndexPatternToFlagMap = createIdToFlagMap(rollupIndexPatterns);

View file

@ -7,9 +7,11 @@
import { createConfig, ConfigSchema } from '../config';
import { loggingSystemMock } from 'src/core/server/mocks';
import { TypeOf } from '@kbn/config-schema';
import { usageCollectionPluginMock } from 'src/plugins/usage_collection/server/mocks';
import {
usageCollectionPluginMock,
createCollectorFetchContextMock,
} from 'src/plugins/usage_collection/server/mocks';
import { registerSecurityUsageCollector } from './security_usage_collector';
import { elasticsearchServiceMock } from 'src/core/server/mocks';
import { licenseMock } from '../../common/licensing/index.mock';
import { SecurityLicenseFeatures } from '../../common/licensing';
@ -34,7 +36,7 @@ describe('Security UsageCollector', () => {
return license;
};
const clusterClient = elasticsearchServiceMock.createLegacyClusterClient();
const collectorFetchContext = createCollectorFetchContextMock();
describe('initialization', () => {
it('handles an undefined usage collector', () => {
@ -68,7 +70,7 @@ describe('Security UsageCollector', () => {
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(clusterClient.asScoped().callAsCurrentUser);
?.fetch(collectorFetchContext);
expect(usage).toEqual({
auditLoggingEnabled: false,
@ -89,7 +91,7 @@ describe('Security UsageCollector', () => {
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(clusterClient.asScoped().callAsCurrentUser);
?.fetch(collectorFetchContext);
expect(usage).toEqual({
auditLoggingEnabled: false,
@ -133,7 +135,7 @@ describe('Security UsageCollector', () => {
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(clusterClient.asScoped().callAsCurrentUser);
?.fetch(collectorFetchContext);
expect(usage).toEqual({
auditLoggingEnabled: false,
@ -182,7 +184,7 @@ describe('Security UsageCollector', () => {
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(clusterClient.asScoped().callAsCurrentUser);
?.fetch(collectorFetchContext);
expect(usage).toEqual({
auditLoggingEnabled: false,
@ -220,7 +222,7 @@ describe('Security UsageCollector', () => {
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(clusterClient.asScoped().callAsCurrentUser);
?.fetch(collectorFetchContext);
expect(usage).toEqual({
auditLoggingEnabled: false,
@ -258,7 +260,7 @@ describe('Security UsageCollector', () => {
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(clusterClient.asScoped().callAsCurrentUser);
?.fetch(collectorFetchContext);
expect(usage).toEqual({
auditLoggingEnabled: false,
@ -299,7 +301,7 @@ describe('Security UsageCollector', () => {
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(clusterClient.asScoped().callAsCurrentUser);
?.fetch(collectorFetchContext);
expect(usage).toEqual({
auditLoggingEnabled: false,
@ -338,7 +340,7 @@ describe('Security UsageCollector', () => {
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(clusterClient.asScoped().callAsCurrentUser);
?.fetch(collectorFetchContext);
expect(usage).toEqual({
auditLoggingEnabled: false,
@ -366,7 +368,7 @@ describe('Security UsageCollector', () => {
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(clusterClient.asScoped().callAsCurrentUser);
?.fetch(collectorFetchContext);
expect(usage).toEqual({
auditLoggingEnabled: true,
@ -392,7 +394,7 @@ describe('Security UsageCollector', () => {
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(clusterClient.asScoped().callAsCurrentUser);
?.fetch(collectorFetchContext);
expect(usage).toEqual({
auditLoggingEnabled: false,
@ -422,7 +424,7 @@ describe('Security UsageCollector', () => {
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(clusterClient.asScoped().callAsCurrentUser);
?.fetch(collectorFetchContext);
expect(usage).toEqual({
auditLoggingEnabled: false,
@ -450,7 +452,7 @@ describe('Security UsageCollector', () => {
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(clusterClient.asScoped().callAsCurrentUser);
?.fetch(collectorFetchContext);
expect(usage).toEqual({
auditLoggingEnabled: false,

View file

@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { LegacyAPICaller, CoreSetup } from '../../../../../src/core/server';
import { CoreSetup } from '../../../../../src/core/server';
import { CollectorFetchContext } from '../../../../../src/plugins/usage_collection/server';
import { CollectorDependencies } from './types';
import { DetectionsUsage, fetchDetectionsUsage, defaultDetectionsUsage } from './detections';
import { EndpointUsage, getEndpointTelemetryFromFleet } from './endpoints';
@ -77,7 +78,7 @@ export const registerCollector: RegisterCollector = ({
},
},
isReady: () => kibanaIndex.length > 0,
fetch: async (callCluster: LegacyAPICaller): Promise<UsageData> => {
fetch: async ({ callCluster }: CollectorFetchContext): Promise<UsageData> => {
const savedObjectsClient = await getInternalSavedObjectsClient(core);
const [detections, endpoints] = await Promise.allSettled([
fetchDetectionsUsage(kibanaIndex, callCluster, ml),

View file

@ -10,6 +10,7 @@ import { PluginsSetup } from '../plugin';
import { KibanaFeature } from '../../../features/server';
import { ILicense, LicensingPluginSetup } from '../../../licensing/server';
import { pluginInitializerContextConfigMock } from 'src/core/server/mocks';
import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
interface SetupOpts {
license?: Partial<ILicense>;
@ -67,6 +68,13 @@ const defaultCallClusterMock = jest.fn().mockResolvedValue({
},
});
const getMockFetchContext = (mockedCallCluster: jest.Mock) => {
return {
...createCollectorFetchContextMock(),
callCluster: mockedCallCluster,
};
};
describe('error handling', () => {
it('handles a 404 when searching for space usage', async () => {
const { features, licensing, usageCollecion } = setup({
@ -78,7 +86,7 @@ describe('error handling', () => {
licensing,
});
await getSpacesUsage(jest.fn().mockRejectedValue({ status: 404 }));
await getSpacesUsage(getMockFetchContext(jest.fn().mockRejectedValue({ status: 404 })));
});
it('throws error for a non-404', async () => {
@ -94,7 +102,9 @@ describe('error handling', () => {
const statusCodes = [401, 402, 403, 500];
for (const statusCode of statusCodes) {
const error = { status: statusCode };
await expect(getSpacesUsage(jest.fn().mockRejectedValue(error))).rejects.toBe(error);
await expect(
getSpacesUsage(getMockFetchContext(jest.fn().mockRejectedValue(error)))
).rejects.toBe(error);
}
});
});
@ -110,7 +120,7 @@ describe('with a basic license', () => {
features,
licensing,
});
usageStats = await getSpacesUsage(defaultCallClusterMock);
usageStats = await getSpacesUsage(getMockFetchContext(defaultCallClusterMock));
expect(defaultCallClusterMock).toHaveBeenCalledWith('search', {
body: {
@ -158,7 +168,7 @@ describe('with no license', () => {
features,
licensing,
});
usageStats = await getSpacesUsage(defaultCallClusterMock);
usageStats = await getSpacesUsage(getMockFetchContext(defaultCallClusterMock));
});
test('sets enabled to false', () => {
@ -189,7 +199,7 @@ describe('with platinum license', () => {
features,
licensing,
});
usageStats = await getSpacesUsage(defaultCallClusterMock);
usageStats = await getSpacesUsage(getMockFetchContext(defaultCallClusterMock));
});
test('sets enabled to true', () => {

View file

@ -6,7 +6,7 @@
import { LegacyCallAPIOptions } from 'src/core/server';
import { take } from 'rxjs/operators';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { CollectorFetchContext, UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { Observable } from 'rxjs';
import { KIBANA_STATS_TYPE_MONITORING } from '../../../monitoring/common/constants';
import { PluginsSetup } from '../plugin';
@ -188,7 +188,7 @@ export function getSpacesUsageCollector(
enabled: { type: 'boolean' },
count: { type: 'long' },
},
fetch: async (callCluster: CallCluster) => {
fetch: async ({ callCluster }: CollectorFetchContext) => {
const license = await deps.licensing.license$.pipe(take(1)).toPromise();
const available = license.isAvailable; // some form of spaces is available for all valid licenses

View file

@ -5,7 +5,7 @@
*/
import { KibanaTelemetryAdapter } from '../kibana_telemetry_adapter';
import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
jest
.spyOn(KibanaTelemetryAdapter, 'countNoOfUniqueMonitorAndLocations')
.mockResolvedValue(undefined as any);
@ -13,7 +13,12 @@ jest
describe('KibanaTelemetryAdapter', () => {
let usageCollection: any;
let getSavedObjectsClient: any;
let collector: { type: string; fetch: () => Promise<any>; isReady: () => boolean };
let collectorFetchContext: any;
let collector: {
type: string;
fetch: (collectorFetchParams: any) => Promise<any>;
isReady: () => boolean;
};
beforeEach(() => {
usageCollection = {
makeUsageCollector: (val: any) => {
@ -23,6 +28,7 @@ describe('KibanaTelemetryAdapter', () => {
getSavedObjectsClient = () => {
return {};
};
collectorFetchContext = createCollectorFetchContextMock();
});
it('collects monitor and overview data', async () => {
@ -49,7 +55,7 @@ describe('KibanaTelemetryAdapter', () => {
autoRefreshEnabled: true,
autorefreshInterval: 30,
});
const result = await collector.fetch();
const result = await collector.fetch(collectorFetchContext);
expect(result).toMatchSnapshot();
});
@ -87,7 +93,7 @@ describe('KibanaTelemetryAdapter', () => {
autoRefreshEnabled: true,
autorefreshInterval: 30,
});
const result = await collector.fetch();
const result = await collector.fetch(collectorFetchContext);
expect(result).toMatchSnapshot();
});

View file

@ -6,7 +6,7 @@
import moment from 'moment';
import { ISavedObjectsRepository, SavedObjectsClientContract } from 'kibana/server';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { CollectorFetchContext, UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { PageViewParams, UptimeTelemetry, Usage } from './types';
import { ESAPICaller } from '../framework';
import { savedObjectsAdapter } from '../../saved_objects';
@ -69,7 +69,7 @@ export class KibanaTelemetryAdapter {
},
},
},
fetch: async (callCluster: ESAPICaller) => {
fetch: async ({ callCluster }: CollectorFetchContext) => {
const savedObjectsClient = getSavedObjectsClient()!;
if (savedObjectsClient) {
await this.countNoOfUniqueMonitorAndLocations(callCluster, savedObjectsClient);

View file

@ -13,14 +13,14 @@ import {
ServiceStatus,
ServiceStatusLevels,
} from '../../../../../src/core/server';
import { contextServiceMock } from '../../../../../src/core/server/mocks';
import { contextServiceMock, elasticsearchServiceMock } from '../../../../../src/core/server/mocks';
import { createHttpServer } from '../../../../../src/core/server/test_utils';
import { registerSettingsRoute } from './settings';
type HttpService = ReturnType<typeof createHttpServer>;
type HttpSetup = UnwrapPromise<ReturnType<HttpService['setup']>>;
describe('/api/stats', () => {
describe('/api/settings', () => {
let server: HttpService;
let httpSetup: HttpSetup;
let overallStatus$: BehaviorSubject<ServiceStatus>;
@ -38,6 +38,9 @@ describe('/api/stats', () => {
callAsCurrentUser: mockApiCaller,
},
},
client: {
asCurrentUser: elasticsearchServiceMock.createScopedClusterClient().asCurrentUser,
},
},
},
}),

View file

@ -42,6 +42,10 @@ export function registerSettingsRoute({
},
async (context, req, res) => {
const { callAsCurrentUser } = context.core.elasticsearch.legacy.client;
const collectorFetchContext = {
callCluster: callAsCurrentUser,
esClient: context.core.elasticsearch.client.asCurrentUser,
};
const settingsCollector = usageCollection.getCollectorByType(KIBANA_SETTINGS_TYPE) as
| KibanaSettingsCollector
@ -51,7 +55,7 @@ export function registerSettingsRoute({
}
const settings =
(await settingsCollector.fetch(callAsCurrentUser)) ??
(await settingsCollector.fetch(collectorFetchContext)) ??
settingsCollector.getEmailValueStructure(null);
const { cluster_uuid: uuid } = await callAsCurrentUser('info', {
filterPath: 'cluster_uuid',