[API DOCS] Usage Collection (#98656)

This commit is contained in:
Alejandro Fernández Haro 2021-05-04 13:32:11 +02:00 committed by GitHub
parent 6fb2b4ad83
commit 6d22f6f552
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1033 additions and 463 deletions

File diff suppressed because it is too large Load diff

View file

@ -22,13 +22,13 @@ import usageCollectionObj from './usage_collection.json';
### Functions
<DocDefinitionList data={usageCollectionObj.client.functions}/>
### Enums
<DocDefinitionList data={usageCollectionObj.client.enums}/>
### Interfaces
<DocDefinitionList data={usageCollectionObj.client.interfaces}/>
## Server
### Classes
<DocDefinitionList data={usageCollectionObj.server.classes}/>
### Setup
<DocDefinitionList data={[usageCollectionObj.server.setup]}/>
### Interfaces
<DocDefinitionList data={usageCollectionObj.server.interfaces}/>

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { CollectorSet, UsageCollector } from '../../plugins/usage_collection/server/collector';
import { CollectorSet, Collector } from '../../plugins/usage_collection/server/collector';
import { loggerMock } from '../../core/server/logging/logger.mock';
const collectorSet = new CollectorSet({
@ -19,7 +19,7 @@ interface Usage {
}
export class NestedInside {
collector?: UsageCollector<Usage>;
collector?: Collector<Usage>;
createMyCollector() {
this.collector = collectorSet.makeUsageCollector<Usage>({
type: 'my_nested_collector',

View file

@ -14,7 +14,6 @@
"optionalPlugins": ["usageCollection"],
"extraPublicDirs": ["common"],
"requiredBundles": [
"usageCollection",
"kibanaUtils",
"kibanaReact",
"inspector"

View file

@ -7,8 +7,9 @@
*/
import { first } from 'rxjs/operators';
import { StartServicesAccessor } from '../../../../../core/public';
import { METRIC_TYPE, UsageCollectionSetup } from '../../../../usage_collection/public';
import { METRIC_TYPE } from '@kbn/analytics';
import type { StartServicesAccessor } from 'src/core/public';
import type { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
import { AUTOCOMPLETE_EVENT_TYPE, AutocompleteUsageCollector } from './types';
export const createUsageCollector = (

View file

@ -77,7 +77,6 @@ import { PublicUiSettingsParams } from 'src/core/server/types';
import React from 'react';
import * as React_3 from 'react';
import { RecursiveReadonly } from '@kbn/utility-types';
import { Reporter } from '@kbn/analytics';
import { Request as Request_2 } from '@hapi/hapi';
import { RequestAdapter } from 'src/plugins/inspector/common';
import { RequestStatistics as RequestStatistics_2 } from 'src/plugins/inspector/common';
@ -102,6 +101,7 @@ import { Type } from '@kbn/config-schema';
import { TypeOf } from '@kbn/config-schema';
import { UiActionsSetup } from 'src/plugins/ui_actions/public';
import { UiActionsStart } from 'src/plugins/ui_actions/public';
import { UiCounterMetricType } from '@kbn/analytics';
import { Unit } from '@elastic/datemath';
import { UnregisterCallback } from 'history';
import { URL } from 'url';

View file

@ -7,9 +7,10 @@
*/
import { first } from 'rxjs/operators';
import { UiCounterMetricType } from '@kbn/analytics';
import { StartServicesAccessor } from '../../../../../core/public';
import { METRIC_TYPE, UsageCollectionSetup } from '../../../../usage_collection/public';
import { METRIC_TYPE } from '@kbn/analytics';
import type { UiCounterMetricType } from '@kbn/analytics';
import type { StartServicesAccessor } from 'src/core/public';
import type { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
import { SEARCH_EVENT_TYPE, SearchUsageCollector } from './types';
export const createUsageCollector = (

View file

@ -55,7 +55,6 @@ import { PublicMethodsOf } from '@kbn/utility-types';
import { RecursiveReadonly } from '@kbn/utility-types';
import { RequestAdapter } from 'src/plugins/inspector/common';
import { RequestHandlerContext } from 'src/core/server';
import * as Rx from 'rxjs';
import { SavedObject } from 'kibana/server';
import { SavedObject as SavedObject_2 } from 'src/core/server';
import { SavedObjectsClientContract } from 'src/core/server';

View file

@ -7,3 +7,4 @@
*/
export { TrackApplicationView } from './track_application_view';
export type { TrackApplicationViewProps } from './track_application_view';

View file

@ -17,7 +17,7 @@ export const ApplicationUsageContext = createContext<IApplicationUsageTracker |
/**
* React component to track the number of clicks and minutes on screen of the children components.
* @param props {@Link TrackApplicationViewProps}
* @param props {@link TrackApplicationViewProps}
* @constructor
*/
export const TrackApplicationView: FC<TrackApplicationViewProps> = (props) => {

View file

@ -9,7 +9,7 @@
import { ReactNode } from 'react';
/**
* Props to provide to the {@Link TrackApplicationView} component.
* Props to provide to the {@link TrackApplicationView} component.
* @public
*/
export interface TrackApplicationViewProps {

View file

@ -6,12 +6,12 @@
* Side Public License, v 1.
*/
import { PluginInitializerContext } from '../../../core/public';
import type { PluginInitializerContext } from 'src/core/public';
import { UsageCollectionPlugin } from './plugin';
export { METRIC_TYPE } from '@kbn/analytics';
export type { UsageCollectionSetup, UsageCollectionStart } from './plugin';
export { TrackApplicationView } from './components';
export type { TrackApplicationViewProps } from './components';
export function plugin(initializerContext: PluginInitializerContext) {
return new UsageCollectionPlugin(initializerContext);

View file

@ -7,6 +7,7 @@
*/
import { Reporter, ApplicationUsageTracker } from '@kbn/analytics';
import type { UiCounterMetricType } from '@kbn/analytics';
import type { Subscription } from 'rxjs';
import React from 'react';
import type {
@ -31,15 +32,63 @@ export type IApplicationUsageTracker = Pick<
'trackApplicationViewUsage' | 'flushTrackedView' | 'updateViewClickCounter'
>;
/** Public's setup APIs exposed by the UsageCollection Service **/
export interface UsageCollectionSetup {
/** Component helpers to track usage collection in the UI **/
components: {
/**
* The context provider to wrap the application if planning to use
* {@link TrackApplicationView} somewhere inside the app.
*
* @example
* ```typescript jsx
* class MyPlugin implements Plugin {
* ...
* public setup(core: CoreSetup, plugins: { usageCollection?: UsageCollectionSetup }) {
* const ApplicationUsageTrackingProvider = plugins.usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment;
*
* core.application.register({
* id,
* title,
* ...,
* mount: async (params: AppMountParameters) => {
* ReactDOM.render(
* <ApplicationUsageTrackingProvider> // Set the tracking context provider at the App level
* <I18nProvider>
* <App />
* </I18nProvider>
* </ApplicationUsageTrackingProvider>,
* element
* );
* return () => ReactDOM.unmountComponentAtNode(element);
* },
* });
* }
* ...
* }
* ```
*/
ApplicationUsageTrackingProvider: React.FC;
};
reportUiCounter: Reporter['reportUiCounter'];
/** Report whenever a UI event occurs for UI counters to report it **/
reportUiCounter: (
appName: string,
type: UiCounterMetricType,
eventNames: string | string[],
count?: number
) => void;
}
/** Public's start APIs exposed by the UsageCollection Service **/
export interface UsageCollectionStart {
reportUiCounter: Reporter['reportUiCounter'];
/** Report whenever a UI event occurs for UI counters to report it **/
reportUiCounter: (
appName: string,
type: UiCounterMetricType,
eventNames: string | string[],
count?: number
) => void;
}
export function isUnauthenticated(http: HttpSetup) {

View file

@ -20,19 +20,6 @@ describe('collector', () => {
);
});
it('should fail if init is not a function', () => {
expect(
() =>
new Collector(logger, {
type: 'my_test_collector',
// @ts-expect-error
init: 1,
})
).toThrowError(
'If init property is passed, Collector must be instantiated with a options.init as a function property'
);
});
it('should fail if fetch is not defined', () => {
expect(
() =>

View file

@ -6,144 +6,18 @@
* Side Public License, v 1.
*/
import type { Logger } from 'src/core/server';
import type {
Logger,
ElasticsearchClient,
SavedObjectsClientContract,
KibanaRequest,
} from 'src/core/server';
CollectorFetchMethod,
CollectorOptions,
CollectorOptionsFetchExtendedContext,
ICollector,
} from './types';
export type AllowedSchemaNumberTypes =
| 'long'
| 'integer'
| 'short'
| 'byte'
| 'double'
| 'float'
| 'date';
export type AllowedSchemaStringTypes = 'keyword' | 'text' | 'date';
export type AllowedSchemaBooleanTypes = 'boolean';
export type AllowedSchemaTypes =
| AllowedSchemaNumberTypes
| AllowedSchemaStringTypes
| AllowedSchemaBooleanTypes;
export interface SchemaField {
type: string;
}
export type PossibleSchemaTypes<U> = U extends string
? AllowedSchemaStringTypes
: U extends number
? AllowedSchemaNumberTypes
: U extends boolean
? AllowedSchemaBooleanTypes
: // allow any schema type from the union if typescript is unable to resolve the exact U type
AllowedSchemaTypes;
export type RecursiveMakeSchemaFrom<U> = U extends object
? MakeSchemaFrom<U>
: { type: PossibleSchemaTypes<U>; _meta?: { description: string } };
// Using Required to enforce all optional keys in the object
export type MakeSchemaFrom<Base> = {
[Key in keyof Required<Base>]: Required<Base>[Key] extends Array<infer U>
? { type: 'array'; items: RecursiveMakeSchemaFrom<U> }
: RecursiveMakeSchemaFrom<Required<Base>[Key]>;
};
/**
* The context for the `fetch` method: It includes the most commonly used clients in the collectors (ES and SO clients).
* Both are scoped based on the request and the context:
* - When users are requesting a sample of data, it is scoped to their role to avoid exposing data they shouldn't read
* - When building the telemetry data payload to report to the remote cluster, the requests are scoped to the `kibana` internal user
*
* @remark Bear in mind when testing your collector that your user has the same privileges as the Kibana Internal user to ensure the expected data is sent to the remote cluster.
*/
export type CollectorFetchContext<WithKibanaRequest extends boolean | undefined = false> = {
/**
* Request-scoped Elasticsearch client
* @remark Bear in mind when testing your collector that your user has the same privileges as the Kibana Internal user to ensure the expected data is sent to the remote cluster (more info: {@link CollectorFetchContext})
*/
esClient: ElasticsearchClient;
/**
* Request-scoped Saved Objects client
* @remark Bear in mind when testing your collector that your user has the same privileges as the Kibana Internal user to ensure the expected data is sent to the remote cluster (more info: {@link CollectorFetchContext})
*/
soClient: SavedObjectsClientContract;
} & (WithKibanaRequest extends true
? {
/**
* The KibanaRequest that can be used to scope the requests:
* It is provided only when your custom clients need to be scoped. If not available, you should use the Internal Client.
* More information about when scoping is needed: {@link CollectorFetchContext}
* @remark You should only use this if you implement your collector to deal with both scenarios: when provided and, especially, when not provided. When telemetry payload is sent to the remote service the `kibanaRequest` will not be provided.
*/
kibanaRequest?: KibanaRequest;
}
: {});
export type CollectorFetchMethod<
WithKibanaRequest extends boolean | undefined,
TReturn,
ExtraOptions extends object = {}
> = (
this: Collector<TReturn> & ExtraOptions, // Specify the context of `this` for this.log and others to become available
context: CollectorFetchContext<WithKibanaRequest>
) => Promise<TReturn> | TReturn;
export interface ICollectorOptionsFetchExtendedContext<WithKibanaRequest extends boolean> {
/**
* Set to `true` if your `fetch` method requires the `KibanaRequest` object to be added in its context {@link CollectorFetchContextWithRequest}.
* @remark You should fully understand acknowledge that by using the `KibanaRequest` in your collector, you need to ensure it should specially work without it because it won't be provided when building the telemetry payload actually sent to the remote telemetry service.
*/
kibanaRequest?: WithKibanaRequest;
}
export type CollectorOptionsFetchExtendedContext<
WithKibanaRequest extends boolean
> = ICollectorOptionsFetchExtendedContext<WithKibanaRequest> &
(WithKibanaRequest extends true // If enforced to true via Types, the config must be expected
? Required<Pick<ICollectorOptionsFetchExtendedContext<WithKibanaRequest>, 'kibanaRequest'>>
: {});
export type CollectorOptions<
TFetchReturn = unknown,
WithKibanaRequest extends boolean = boolean,
ExtraOptions extends object = {}
> = {
/**
* Unique string identifier for the collector
*/
type: string;
init?: Function;
/**
* Method to return `true`/`false` or Promise(`true`/`false`) to confirm if the collector is ready for the `fetch` method to be called.
*/
isReady: () => Promise<boolean> | boolean;
/**
* Schema definition of the output of the `fetch` method.
*/
schema?: MakeSchemaFrom<TFetchReturn>;
/**
* The method that will collect and return the data in the final format.
* @param collectorFetchContext {@link CollectorFetchContext}
*/
fetch: CollectorFetchMethod<WithKibanaRequest, TFetchReturn, ExtraOptions>;
} & ExtraOptions &
(WithKibanaRequest extends true // If enforced to true via Types, the config must be enforced
? {
extendFetchContext: CollectorOptionsFetchExtendedContext<WithKibanaRequest>;
}
: {
extendFetchContext?: CollectorOptionsFetchExtendedContext<WithKibanaRequest>;
});
export class Collector<TFetchReturn, ExtraOptions extends object = {}> {
export class Collector<TFetchReturn, ExtraOptions extends object = {}>
implements ICollector<TFetchReturn, ExtraOptions> {
public readonly extendFetchContext: CollectorOptionsFetchExtendedContext<boolean>;
public readonly type: CollectorOptions<TFetchReturn, boolean>['type'];
public readonly init?: CollectorOptions<TFetchReturn, boolean>['init'];
public readonly fetch: CollectorFetchMethod<boolean, TFetchReturn, ExtraOptions>;
public readonly isReady: CollectorOptions<TFetchReturn, boolean>['isReady'];
/**
@ -155,7 +29,6 @@ export class Collector<TFetchReturn, ExtraOptions extends object = {}> {
public readonly log: Logger,
{
type,
init,
fetch,
isReady,
extendFetchContext = {},
@ -167,11 +40,6 @@ export class Collector<TFetchReturn, ExtraOptions extends object = {}> {
if (type === undefined) {
throw new Error('Collector must be instantiated with a options.type string property');
}
if (typeof init !== 'undefined' && typeof init !== 'function') {
throw new Error(
'If init property is passed, Collector must be instantiated with a options.init as a function property'
);
}
if (typeof fetch !== 'function') {
throw new Error('Collector must be instantiated with a options.fetch function property');
}
@ -179,7 +47,6 @@ export class Collector<TFetchReturn, ExtraOptions extends object = {}> {
Object.assign(this, options); // spread in other properties and mutate "this"
this.type = type;
this.init = init;
this.fetch = fetch;
this.isReady = typeof isReady === 'function' ? isReady : () => true;
this.extendFetchContext = extendFetchContext;

View file

@ -25,10 +25,8 @@ const loggerSpies = {
describe('CollectorSet', () => {
describe('registers a collector set and runs lifecycle events', () => {
let init: Function;
let fetch: Function;
beforeEach(() => {
init = noop;
fetch = noop;
loggerSpies.debug.mockRestore();
loggerSpies.warn.mockRestore();
@ -42,7 +40,6 @@ describe('CollectorSet', () => {
const registerPojo = () => {
collectors.registerCollector({
type: 'type_collector_test',
init,
// @ts-expect-error we are intentionally sending it wrong.
fetch,
});

View file

@ -13,12 +13,13 @@ import type {
SavedObjectsClientContract,
KibanaRequest,
} from 'src/core/server';
import { Collector, CollectorOptions } from './collector';
import { Collector } from './collector';
import type { ICollector, CollectorOptions } from './types';
import { UsageCollector, UsageCollectorOptions } from './usage_collector';
// Needed for the general array containing all the collectors. We don't really care about their types here
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyCollector = Collector<any, any>;
type AnyCollector = ICollector<any, any>;
interface CollectorSetConfig {
logger: Logger;
@ -85,11 +86,6 @@ export class CollectorSet {
}
this.collectors.set(collector.type, collector);
if (collector.init) {
this.logger.debug(`Initializing ${collector.type} collector`);
collector.init();
}
};
public getCollectorByType = (type: string) => {

View file

@ -7,14 +7,17 @@
*/
export { CollectorSet } from './collector_set';
export { Collector } from './collector';
export type {
AllowedSchemaTypes,
AllowedSchemaNumberTypes,
SchemaField,
AllowedSchemaBooleanTypes,
AllowedSchemaStringTypes,
RecursiveMakeSchemaFrom,
MakeSchemaFrom,
CollectorOptions,
CollectorFetchContext,
} from './collector';
export { UsageCollector } from './usage_collector';
CollectorFetchMethod,
CollectorOptionsFetchExtendedContext,
ICollector as Collector,
} from './types';
export type { UsageCollectorOptions } from './usage_collector';

View file

@ -0,0 +1,197 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type {
ElasticsearchClient,
KibanaRequest,
SavedObjectsClientContract,
Logger,
} from 'src/core/server';
/** Types matching number values **/
export type AllowedSchemaNumberTypes =
| 'long'
| 'integer'
| 'short'
| 'byte'
| 'double'
| 'float'
| 'date';
/** Types matching string values **/
export type AllowedSchemaStringTypes = 'keyword' | 'text' | 'date';
/** Types matching boolean values **/
export type AllowedSchemaBooleanTypes = 'boolean';
/**
* Possible type values in the schema
*/
export type AllowedSchemaTypes =
| AllowedSchemaNumberTypes
| AllowedSchemaStringTypes
| AllowedSchemaBooleanTypes;
/**
* Helper to ensure the declared types match the schema types
*/
export type PossibleSchemaTypes<U> = U extends string
? AllowedSchemaStringTypes
: U extends number
? AllowedSchemaNumberTypes
: U extends boolean
? AllowedSchemaBooleanTypes
: // allow any schema type from the union if typescript is unable to resolve the exact U type
AllowedSchemaTypes;
/**
* Helper to find out whether to keep recursively looking or if we are on an end value
*/
export type RecursiveMakeSchemaFrom<U> = U extends object
? MakeSchemaFrom<U>
: { type: PossibleSchemaTypes<U>; _meta?: { description: string } };
/**
* The `schema` property in {@link CollectorOptions} must match the output of
* the `fetch` method. This type helps ensure that is correct
*/
export type MakeSchemaFrom<Base> = {
// Using Required to enforce all optional keys in the object
[Key in keyof Required<Base>]: Required<Base>[Key] extends Array<infer U>
? { type: 'array'; items: RecursiveMakeSchemaFrom<U> }
: RecursiveMakeSchemaFrom<Required<Base>[Key]>;
};
/**
* The context for the `fetch` method: It includes the most commonly used clients in the collectors (ES and SO clients).
* Both are scoped based on the request and the context:
* - When users are requesting a sample of data, it is scoped to their role to avoid exposing data they shouldn't read
* - When building the telemetry data payload to report to the remote cluster, the requests are scoped to the `kibana` internal user
*
* @remark Bear in mind when testing your collector that your user has the same privileges as the Kibana Internal user to ensure the expected data is sent to the remote cluster.
*/
export type CollectorFetchContext<WithKibanaRequest extends boolean | undefined = false> = {
/**
* Request-scoped Elasticsearch client
* @remark Bear in mind when testing your collector that your user has the same privileges as the Kibana Internal user to ensure the expected data is sent to the remote cluster (more info: {@link CollectorFetchContext})
*/
esClient: ElasticsearchClient;
/**
* Request-scoped Saved Objects client
* @remark Bear in mind when testing your collector that your user has the same privileges as the Kibana Internal user to ensure the expected data is sent to the remote cluster (more info: {@link CollectorFetchContext})
*/
soClient: SavedObjectsClientContract;
} & (WithKibanaRequest extends true
? {
/**
* The KibanaRequest that can be used to scope the requests:
* It is provided only when your custom clients need to be scoped. If not available, you should use the Internal Client.
* More information about when scoping is needed: {@link CollectorFetchContext}
* @remark You should only use this if you implement your collector to deal with both scenarios: when provided and, especially, when not provided. When telemetry payload is sent to the remote service the `kibanaRequest` will not be provided.
*/
kibanaRequest?: KibanaRequest;
}
: {});
/**
* The fetch method has the context of the Collector itself
* (this has access to all the properties of the collector like the logger)
* and the the first parameter is {@link CollectorFetchContext}.
*/
export type CollectorFetchMethod<
WithKibanaRequest extends boolean | undefined,
TReturn,
ExtraOptions extends object = {}
> = (
this: ICollector<TReturn> & ExtraOptions, // Specify the context of `this` for this.log and others to become available
context: CollectorFetchContext<WithKibanaRequest>
) => Promise<TReturn> | TReturn;
export interface ICollectorOptionsFetchExtendedContext<WithKibanaRequest extends boolean> {
/**
* Set to `true` if your `fetch` method requires the `KibanaRequest` object to be added in its context {@link CollectorFetchContextWithRequest}.
* @remark You should fully acknowledge that by using the `KibanaRequest` in your collector, you need to ensure it should specially work without it because it won't be provided when building the telemetry payload actually sent to the remote telemetry service.
*/
kibanaRequest?: WithKibanaRequest;
}
/**
* The options to extend the context provided to the `fetch` method.
* @remark Only to be used in very rare scenarios when this is really needed.
*/
export type CollectorOptionsFetchExtendedContext<
WithKibanaRequest extends boolean
> = ICollectorOptionsFetchExtendedContext<WithKibanaRequest> &
(WithKibanaRequest extends true // If enforced to true via Types, the config must be expected
? Required<Pick<ICollectorOptionsFetchExtendedContext<WithKibanaRequest>, 'kibanaRequest'>>
: {});
/**
* Options to instantiate a collector
*/
export type CollectorOptions<
TFetchReturn = unknown,
WithKibanaRequest extends boolean = boolean,
ExtraOptions extends object = {}
> = {
/**
* Unique string identifier for the collector
*/
type: string;
/**
* Method to return `true`/`false` or Promise(`true`/`false`) to confirm if the collector is ready for the `fetch` method to be called.
*/
isReady: () => Promise<boolean> | boolean;
/**
* Schema definition of the output of the `fetch` method.
*/
schema?: MakeSchemaFrom<TFetchReturn>;
/**
* The method that will collect and return the data in the final format.
* @param collectorFetchContext {@link CollectorFetchContext}
*/
fetch: CollectorFetchMethod<WithKibanaRequest, TFetchReturn, ExtraOptions>;
} & ExtraOptions &
(WithKibanaRequest extends true // If enforced to true via Types, the config must be enforced
? {
/** {@link CollectorOptionsFetchExtendedContext} **/
extendFetchContext: CollectorOptionsFetchExtendedContext<WithKibanaRequest>;
}
: {
/** {@link CollectorOptionsFetchExtendedContext} **/
extendFetchContext?: CollectorOptionsFetchExtendedContext<WithKibanaRequest>;
});
/**
* Common interface for Usage and Stats Collectors
*/
export interface ICollector<TFetchReturn, ExtraOptions extends object = {}> {
/** Logger **/
readonly log: Logger;
/**
* The options to extend the context provided to the `fetch` method: {@link CollectorOptionsFetchExtendedContext}.
* @remark Only to be used in very rare scenarios when this is really needed.
*/
readonly extendFetchContext: CollectorOptionsFetchExtendedContext<boolean>;
/** The registered type (aka name) of the collector **/
readonly type: CollectorOptions<TFetchReturn, boolean>['type'];
/**
* The actual logic that reports the Usage collection.
* It will be called on every collection request.
* Whatever is returned in this method will be passed through as-is under
* the {@link ICollector.type} key.
*
* @example
* {
* [type]: await fetch(context)
* }
*/
readonly fetch: CollectorFetchMethod<boolean, TFetchReturn, ExtraOptions>;
/**
* Should return `true` when it's safe to call the `fetch` method.
*/
readonly isReady: CollectorOptions<TFetchReturn, boolean>['isReady'];
}

View file

@ -6,10 +6,13 @@
* Side Public License, v 1.
*/
import { Logger } from 'src/core/server';
import { Collector, CollectorOptions } from './collector';
import type { Logger } from 'src/core/server';
import type { CollectorOptions } from './types';
import { Collector } from './collector';
// Enforce the `schema` property for UsageCollectors
/**
* Same as {@link CollectorOptions} but with the `schema` property enforced
*/
export type UsageCollectorOptions<
TFetchReturn = unknown,
WithKibanaRequest extends boolean = false,
@ -17,6 +20,9 @@ export type UsageCollectorOptions<
> = CollectorOptions<TFetchReturn, WithKibanaRequest, ExtraOptions> &
Required<Pick<CollectorOptions<TFetchReturn, boolean>, 'schema'>>;
/**
* @private Only used in fixtures as a type
*/
export class UsageCollector<TFetchReturn, ExtraOptions extends object = {}> extends Collector<
TFetchReturn,
ExtraOptions

View file

@ -9,27 +9,26 @@
import { PluginInitializerContext } from 'src/core/server';
import { UsageCollectionPlugin } from './plugin';
export { Collector } from './collector';
export type {
Collector,
AllowedSchemaTypes,
MakeSchemaFrom,
SchemaField,
CollectorOptions,
UsageCollectorOptions,
CollectorFetchContext,
CollectorFetchMethod,
CollectorOptionsFetchExtendedContext,
} from './collector';
export type {
UsageCountersSavedObject,
UsageCountersSavedObjectAttributes,
IncrementCounterParams,
UsageCounter,
SerializeCounterParams,
} from './usage_counters';
export {
USAGE_COUNTERS_SAVED_OBJECT_TYPE,
serializeCounterKey,
UsageCounter,
} from './usage_counters';
export { USAGE_COUNTERS_SAVED_OBJECT_TYPE, serializeCounterKey } from './usage_counters';
export type { UsageCollectionSetup } from './plugin';
export { config } from './config';

View file

@ -13,7 +13,8 @@ import {
savedObjectsClientMock,
} from '../../../../src/core/server/mocks';
import { CollectorOptions, Collector, CollectorSet } from './collector';
import { CollectorOptions, CollectorSet } from './collector';
import { Collector } from './collector/collector';
import { UsageCollectionSetup, CollectorFetchContext } from './index';
export type { CollectorOptions };

View file

@ -6,54 +6,100 @@
* Side Public License, v 1.
*/
import {
import type {
PluginInitializerContext,
Logger,
CoreSetup,
CoreStart,
ISavedObjectsRepository,
Plugin,
ElasticsearchClient,
SavedObjectsClientContract,
KibanaRequest,
} from 'src/core/server';
import { ConfigType } from './config';
import type { ConfigType } from './config';
import { CollectorSet } from './collector';
import type { Collector, CollectorOptions, UsageCollectorOptions } from './collector';
import { setupRoutes } from './routes';
import { UsageCountersService } from './usage_counters';
import type { UsageCountersServiceSetup } from './usage_counters';
import type { UsageCounter } from './usage_counters';
/** Server's setup APIs exposed by the UsageCollection Service **/
export interface UsageCollectionSetup {
/**
* Creates and registers a usage counter to collect daily aggregated plugin counter events
*/
createUsageCounter: UsageCountersServiceSetup['createUsageCounter'];
createUsageCounter: (type: string) => UsageCounter;
/**
* Returns a usage counter by type
*/
getUsageCounterByType: UsageCountersServiceSetup['getUsageCounterByType'];
getUsageCounterByType: (type: string) => UsageCounter | undefined;
/**
* Creates a usage collector to collect plugin telemetry data.
* registerCollector must be called to connect the created collecter with the service.
* registerCollector must be called to connect the created collector with the service.
*/
makeUsageCollector: CollectorSet['makeUsageCollector'];
makeUsageCollector: <
TFetchReturn,
WithKibanaRequest extends boolean = false,
ExtraOptions extends object = {}
>(
options: UsageCollectorOptions<TFetchReturn, WithKibanaRequest, ExtraOptions>
) => Collector<TFetchReturn, ExtraOptions>;
/**
* Register a usage collector or a stats collector.
* Used to connect the created collector to telemetry.
*/
registerCollector: CollectorSet['registerCollector'];
registerCollector: <TFetchReturn, ExtraOptions extends object>(
collector: Collector<TFetchReturn, ExtraOptions>
) => void;
/**
* Returns a usage collector by type
*/
getCollectorByType: CollectorSet['getCollectorByType'];
/* internal: telemetry use */
areAllCollectorsReady: CollectorSet['areAllCollectorsReady'];
/* internal: telemetry use */
bulkFetch: CollectorSet['bulkFetch'];
/* internal: telemetry use */
toObject: CollectorSet['toObject'];
/* internal: monitoring use */
toApiFieldNames: CollectorSet['toApiFieldNames'];
/* internal: telemtery and monitoring use */
makeStatsCollector: CollectorSet['makeStatsCollector'];
getCollectorByType: <TFetchReturn, ExtraOptions extends object>(
type: string
) => Collector<TFetchReturn, ExtraOptions> | undefined;
/**
* Returns if all the collectors are ready to fetch their reported usage.
* @internal: telemetry use
*/
areAllCollectorsReady: () => Promise<boolean>;
/**
* Fetches the collection from all the registered collectors
* @internal: telemetry use
*/
bulkFetch: <TFetchReturn, ExtraOptions extends object>(
esClient: ElasticsearchClient,
soClient: SavedObjectsClientContract,
kibanaRequest: KibanaRequest | undefined, // intentionally `| undefined` to enforce providing the parameter
collectors?: Map<string, Collector<TFetchReturn, ExtraOptions>>
) => Promise<Array<{ type: string; result: unknown }>>;
/**
* Converts an array of fetched stats results into key/object
* @internal: telemetry use
*/
toObject: <Result extends Record<string, unknown>, T = unknown>(
statsData?: Array<{ type: string; result: T }>
) => Result;
/**
* Rename fields to use API conventions
* @internal: monitoring use
*/
toApiFieldNames: (
apiData: Record<string, unknown> | unknown[]
) => Record<string, unknown> | unknown[];
/**
* Creates a stats collector to collect plugin telemetry data.
* registerCollector must be called to connect the created collector with the service.
* @internal: telemetry and monitoring use
*/
makeStatsCollector: <
TFetchReturn,
WithKibanaRequest extends boolean,
ExtraOptions extends object = {}
>(
options: CollectorOptions<TFetchReturn, WithKibanaRequest, ExtraOptions>
) => Collector<TFetchReturn, ExtraOptions>;
}
export class UsageCollectionPlugin implements Plugin<UsageCollectionSetup> {

View file

@ -8,8 +8,8 @@
export type { UsageCountersServiceSetup } from './usage_counters_service';
export type { UsageCountersSavedObjectAttributes, UsageCountersSavedObject } from './saved_objects';
export type { IncrementCounterParams } from './usage_counter';
export type { IUsageCounter as UsageCounter, IncrementCounterParams } from './usage_counter';
export { UsageCountersService } from './usage_counters_service';
export { UsageCounter } from './usage_counter';
export type { SerializeCounterParams } from './saved_objects';
export { USAGE_COUNTERS_SAVED_OBJECT_TYPE, serializeCounterKey } from './saved_objects';

View file

@ -6,24 +6,35 @@
* Side Public License, v 1.
*/
import {
import type {
SavedObject,
SavedObjectsRepository,
SavedObjectAttributes,
SavedObjectsServiceSetup,
} from 'kibana/server';
import moment from 'moment';
import { CounterMetric } from './usage_counter';
import type { CounterMetric } from './usage_counter';
/**
* The attributes stored in the UsageCounters' SavedObjects
*/
export interface UsageCountersSavedObjectAttributes extends SavedObjectAttributes {
/** The domain ID registered in the Usage Counter **/
domainId: string;
/** The counter name **/
counterName: string;
/** The counter type **/
counterType: string;
/** Number of times the event has occurred **/
count: number;
}
/**
* The structure of the SavedObjects of type "usage-counters"
*/
export type UsageCountersSavedObject = SavedObject<UsageCountersSavedObjectAttributes>;
/** The Saved Objects type for Usage Counters **/
export const USAGE_COUNTERS_SAVED_OBJECT_TYPE = 'usage-counters';
export const registerUsageCountersSavedObjectType = (
@ -42,13 +53,26 @@ export const registerUsageCountersSavedObjectType = (
});
};
/**
* Parameters to the `serializeCounterKey` method
* @internal used in kibana_usage_collectors
*/
export interface SerializeCounterParams {
/** The domain ID registered in the UsageCounter **/
domainId: string;
/** The counter name **/
counterName: string;
/** The counter type **/
counterType: string;
/** The date to which serialize the key **/
date: moment.MomentInput;
}
/**
* Generates a key based on the UsageCounter details
* @internal used in kibana_usage_collectors
* @param opts {@link SerializeCounterParams}
*/
export const serializeCounterKey = ({
domainId,
counterName,

View file

@ -20,13 +20,32 @@ export interface UsageCounterDeps {
counter$: Rx.Subject<CounterMetric>;
}
/**
* Details about the counter to be incremented
*/
export interface IncrementCounterParams {
/** The name of the counter **/
counterName: string;
/** The counter type ("count" by default) **/
counterType?: string;
/** Increment the counter by this number (1 if not specified) **/
incrementBy?: number;
}
export class UsageCounter {
/**
* Usage Counter allows to keep track of any events that occur.
* By calling {@link IUsageCounter.incrementCounter} devs can notify this
* API whenever the event happens.
*/
export interface IUsageCounter {
/**
* Notifies the counter about a new event happening so it can increase the count internally.
* @param params {@link IncrementCounterParams}
*/
incrementCounter: (params: IncrementCounterParams) => void;
}
export class UsageCounter implements IUsageCounter {
private domainId: string;
private counter$: Rx.Subject<CounterMetric>;

View file

@ -35,7 +35,6 @@
"savedObjects",
"kibanaUtils",
"kibanaReact",
"embeddable",
"usageCollection"
"embeddable"
]
}

View file

@ -25,7 +25,7 @@ import { DefaultInspectorAdapters, RenderMode } from 'src/plugins/expressions';
import { map, distinctUntilChanged, skip } from 'rxjs/operators';
import isEqual from 'fast-deep-equal';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
import { METRIC_TYPE } from '../../../../../../src/plugins/usage_collection/public';
import { METRIC_TYPE } from '@kbn/analytics';
import {
ExpressionRendererEvent,
ReactExpressionRendererType,