Skip to content

Commit

Permalink
UI Metrics use findAll to retrieve all Saved Objects (elastic#59891)
Browse files Browse the repository at this point in the history
* UI Metrics use findAll to retrieve all Saved Objects

* Rename test description

Co-Authored-By: Christiane (Tina) Heiligers <[email protected]>

Co-authored-by: Elastic Machine <[email protected]>
Co-authored-by: Christiane (Tina) Heiligers <[email protected]>
  • Loading branch information
3 people committed Mar 12, 2020
1 parent e39fa3f commit 6426ed5
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,8 @@
import moment from 'moment';
import { APPLICATION_USAGE_TYPE } from '../../../common/constants';
import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server';
import {
ISavedObjectsRepository,
SavedObjectAttributes,
SavedObjectsFindOptions,
SavedObject,
} from '../../../../../../core/server';
import { ISavedObjectsRepository, SavedObjectAttributes } from '../../../../../../core/server';
import { findAll } from '../find_all';

/**
* Roll indices every 24h
Expand Down Expand Up @@ -63,22 +59,6 @@ interface ApplicationUsageTelemetryReport {
};
}

async function findAll<T extends SavedObjectAttributes>(
savedObjectsClient: ISavedObjectsRepository,
opts: SavedObjectsFindOptions
): Promise<Array<SavedObject<T>>> {
const { page = 1, perPage = 100, ...options } = opts;
const { saved_objects: savedObjects, total } = await savedObjectsClient.find<T>({
...options,
page,
perPage,
});
if (page * perPage >= total) {
return savedObjects;
}
return [...savedObjects, ...(await findAll<T>(savedObjectsClient, { ...opts, page: page + 1 }))];
}

export function registerApplicationUsageCollector(
usageCollection: UsageCollectionSetup,
getSavedObjectsClient: () => ISavedObjectsRepository | undefined
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { savedObjectsRepositoryMock } from '../../../../../core/server/mocks';

import { findAll } from './find_all';

describe('telemetry_application_usage', () => {
test('when savedObjectClient is initialised, return something', async () => {
const savedObjectClient = savedObjectsRepositoryMock.create();
savedObjectClient.find.mockImplementation(
async () =>
({
saved_objects: [],
total: 0,
} as any)
);

expect(await findAll(savedObjectClient, { type: 'test-type' })).toStrictEqual([]);
});

test('paging in findAll works', async () => {
const savedObjectClient = savedObjectsRepositoryMock.create();
let total = 201;
const doc = { id: 'test-id', attributes: { test: 1 } };
savedObjectClient.find.mockImplementation(async opts => {
if ((opts.page || 1) > 2) {
return { saved_objects: [], total } as any;
}
const savedObjects = new Array(opts.perPage).fill(doc);
total = savedObjects.length * 2 + 1;
return { saved_objects: savedObjects, total };
});

expect(await findAll(savedObjectClient, { type: 'test-type' })).toStrictEqual(
new Array(total - 1).fill(doc)
);
});
});
41 changes: 41 additions & 0 deletions src/legacy/core_plugins/telemetry/server/collectors/find_all.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import {
SavedObjectAttributes,
ISavedObjectsRepository,
SavedObjectsFindOptions,
SavedObject,
} from 'kibana/server';

export async function findAll<T extends SavedObjectAttributes>(
savedObjectsClient: ISavedObjectsRepository,
opts: SavedObjectsFindOptions
): Promise<Array<SavedObject<T>>> {
const { page = 1, perPage = 100, ...options } = opts;
const { saved_objects: savedObjects, total } = await savedObjectsClient.find<T>({
...options,
page,
perPage,
});
if (page * perPage >= total) {
return savedObjects;
}
return [...savedObjects, ...(await findAll<T>(savedObjectsClient, { ...opts, page: page + 1 }))];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server';
import { savedObjectsRepositoryMock } from '../../../../../../core/server/mocks';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { CollectorOptions } from '../../../../../../plugins/usage_collection/server/collector/collector';

import { registerUiMetricUsageCollector } from './';

describe('telemetry_ui_metric', () => {
let collector: CollectorOptions;

const usageCollectionMock: jest.Mocked<UsageCollectionSetup> = {
makeUsageCollector: jest.fn().mockImplementation(config => (collector = config)),
registerCollector: jest.fn(),
} as any;

const getUsageCollector = jest.fn();
const callCluster = jest.fn();

beforeAll(() => registerUiMetricUsageCollector(usageCollectionMock, getUsageCollector));

test('registered collector is set', () => {
expect(collector).not.toBeUndefined();
});

test('if no savedObjectClient initialised, return undefined', async () => {
expect(await collector.fetch(callCluster)).toBeUndefined();
});

test('when savedObjectClient is initialised, return something', async () => {
const savedObjectClient = savedObjectsRepositoryMock.create();
savedObjectClient.find.mockImplementation(
async () =>
({
saved_objects: [],
total: 0,
} as any)
);
getUsageCollector.mockImplementation(() => savedObjectClient);

expect(await collector.fetch(callCluster)).toStrictEqual({});
expect(savedObjectClient.bulkCreate).not.toHaveBeenCalled();
});

test('results grouped by appName', async () => {
const savedObjectClient = savedObjectsRepositoryMock.create();
savedObjectClient.find.mockImplementation(async () => {
return {
saved_objects: [
{ id: 'testAppName:testKeyName1', attributes: { count: 3 } },
{ id: 'testAppName:testKeyName2', attributes: { count: 5 } },
{ id: 'testAppName2:testKeyName3', attributes: { count: 1 } },
],
total: 3,
} as any;
});

getUsageCollector.mockImplementation(() => savedObjectClient);

expect(await collector.fetch(callCluster)).toStrictEqual({
testAppName: [
{ key: 'testKeyName1', value: 3 },
{ key: 'testKeyName2', value: 5 },
],
testAppName2: [{ key: 'testKeyName3', value: 1 }],
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,50 @@
* under the License.
*/

import { ISavedObjectsRepository, SavedObjectAttributes } from 'kibana/server';
import { UI_METRIC_USAGE_TYPE } from '../../../common/constants';
import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server';
import { findAll } from '../find_all';

export function registerUiMetricUsageCollector(usageCollection: UsageCollectionSetup, server: any) {
interface UIMetricsSavedObjects extends SavedObjectAttributes {
count: number;
}

export function registerUiMetricUsageCollector(
usageCollection: UsageCollectionSetup,
getSavedObjectsClient: () => ISavedObjectsRepository | undefined
) {
const collector = usageCollection.makeUsageCollector({
type: UI_METRIC_USAGE_TYPE,
fetch: async () => {
const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects;
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
const internalRepository = getSavedObjectsRepository(callWithInternalUser);
const savedObjectsClient = new SavedObjectsClient(internalRepository);
const savedObjectsClient = getSavedObjectsClient();
if (typeof savedObjectsClient === 'undefined') {
return;
}

const { saved_objects: rawUiMetrics } = await savedObjectsClient.find({
const rawUiMetrics = await findAll<UIMetricsSavedObjects>(savedObjectsClient, {
type: 'ui-metric',
fields: ['count'],
});

const uiMetricsByAppName = rawUiMetrics.reduce((accum: any, rawUiMetric: any) => {
const uiMetricsByAppName = rawUiMetrics.reduce((accum, rawUiMetric) => {
const {
id,
attributes: { count },
} = rawUiMetric;

const [appName, metricType] = id.split(':');

if (!accum[appName]) {
accum[appName] = [];
}

const pair = { key: metricType, value: count };
accum[appName].push(pair);
return accum;
}, {});
return {
...accum,
[appName]: [...(accum[appName] || []), pair],
};
}, {} as Record<string, Array<{ key: string; value: number }>>);

return uiMetricsByAppName;
},
isReady: () => true,
isReady: () => typeof getSavedObjectsClient() !== 'undefined',
});

usageCollection.registerCollector(collector);
Expand Down
2 changes: 1 addition & 1 deletion src/legacy/core_plugins/telemetry/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class TelemetryPlugin {
registerTelemetryPluginUsageCollector(usageCollection, server);
registerLocalizationUsageCollector(usageCollection, server);
registerTelemetryUsageCollector(usageCollection, server);
registerUiMetricUsageCollector(usageCollection, server);
registerUiMetricUsageCollector(usageCollection, getSavedObjectsClient);
registerManagementUsageCollector(usageCollection, server);
registerApplicationUsageCollector(usageCollection, getSavedObjectsClient);
}
Expand Down

0 comments on commit 6426ed5

Please sign in to comment.