Skip to content

Commit

Permalink
[telemetry] expose getIsOptedIn function in plugin start contract (#7…
Browse files Browse the repository at this point in the history
…5143)

Co-authored-by: Alejandro Fernández Haro <afharo@gmail.com>
  • Loading branch information
Bamieh and afharo authored Sep 7, 2020
1 parent a7d742a commit 992f5cc
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 7 deletions.
58 changes: 58 additions & 0 deletions src/plugins/telemetry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,61 @@ Telemetry allows Kibana features to have usage tracked in the wild. The general
3. Viewing usage data in the Kibana instance of the telemetry cluster (Viewing).

This plugin is responsible for sending usage data to the telemetry cluster. For collecting usage data, use the [`usageCollection` plugin](../usage_collection/README.md)

## Telemetry Plugin public API

### Setup

The `setup` function exposes the following interface:

- `getTelemetryUrl: () => Promise<URL>`:
An async function that resolves into the telemetry Url used to send telemetry. The url is wrapped with node's [URL constructor](https://nodejs.org/api/url.html). Here is an example on how to grab the url origin:
```
const telemetryUrl = await getTelemetryUrl();
> telemetryUrl.origin; // 'https://telemetry.elastic.co'
```
Note that the telemetry URL is a kibana.yml configuration hence it is recommended to call the `getTelemetryUrl` everytime before using the actual url.

### Start

The `start` function exposes the following interface:

- `async getIsOptedIn(): Promise<boolean>`:
An async function that resolves into `true` if the user has opted into send Elastic usage data.
Resolves to `false` if the user explicitly opted out of sending usage data to Elastic or did not choose
to opt-in or out yet after a minor or major upgrade (only when previously opted out).

### Usage

To use the exposed plugin start and setup contracts:

1. Make sure `telemetry` is in your `optionalPlugins` in the `kibana.json` file:

```json5
// <plugin>/kibana.json
{
"id": "...",
"optionalPlugins": ["telemetry"]
}
```

2. Use the exposed contracts:
```ts
// <plugin>/server/plugin.ts

import { TelemetryPluginsStart } from '../telemetry/server`;

interface MyPlyginStartDeps {
telemetry?: TelemetryPluginsStart;
}

class MyPlugin {
public async start(
core: CoreStart,
{ telemetry }: MyPlyginStartDeps
) {
const isOptedIn = await telemetry?.getIsOptedIn();
...
}
}
```
2 changes: 1 addition & 1 deletion src/plugins/telemetry/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { configSchema, TelemetryConfigType } from './config';

export { FetcherTask } from './fetcher';
export { handleOldSettings } from './handle_old_settings';
export { TelemetryPluginsSetup } from './plugin';
export { TelemetryPluginSetup, TelemetryPluginStart } from './plugin';

export const config: PluginConfigDescriptor<TelemetryConfigType> = {
schema: configSchema,
Expand Down
46 changes: 46 additions & 0 deletions src/plugins/telemetry/server/mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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 { URL } from 'url';
import { TelemetryPluginStart, TelemetryPluginSetup } from './plugin';

export type Setup = jest.Mocked<TelemetryPluginSetup>;
export type Start = jest.Mocked<TelemetryPluginStart>;

export const telemetryPluginMock = {
createSetupContract,
createStartContract,
};

function createSetupContract(): Setup {
const telemetryUrl = new URL('https://telemetry-staging.elastic.co/xpack/MOCK_URL/send');
const setupContract: Setup = {
getTelemetryUrl: jest.fn().mockResolvedValue(telemetryUrl),
};

return setupContract;
}

function createStartContract(): Start {
const startContract: Start = {
getIsOptedIn: jest.fn(),
};

return startContract;
}
62 changes: 56 additions & 6 deletions src/plugins/telemetry/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
* under the License.
*/

import { URL } from 'url';
import { Observable } from 'rxjs';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import {
TelemetryCollectionManagerPluginSetup,
TelemetryCollectionManagerPluginStart,
} from 'src/plugins/telemetry_collection_manager/server';
import { take } from 'rxjs/operators';
import {
CoreSetup,
PluginInitializerContext,
Expand All @@ -42,19 +44,38 @@ import {
import { TelemetryConfigType } from './config';
import { FetcherTask } from './fetcher';
import { handleOldSettings } from './handle_old_settings';
import { getTelemetrySavedObject } from './telemetry_repository';
import { getTelemetryOptIn } from '../common/telemetry_config';

export interface TelemetryPluginsSetup {
interface TelemetryPluginsDepsSetup {
usageCollection: UsageCollectionSetup;
telemetryCollectionManager: TelemetryCollectionManagerPluginSetup;
}

export interface TelemetryPluginsStart {
interface TelemetryPluginsDepsStart {
telemetryCollectionManager: TelemetryCollectionManagerPluginStart;
}

export interface TelemetryPluginSetup {
/**
* Resolves into the telemetry Url used to send telemetry.
* The url is wrapped with node's [URL constructor](https://nodejs.org/api/url.html).
*/
getTelemetryUrl: () => Promise<URL>;
}

export interface TelemetryPluginStart {
/**
* Resolves `true` if the user has opted into send Elastic usage data.
* Resolves `false` if the user explicitly opted out of sending usage data to Elastic
* or did not choose to opt-in or out -yet- after a minor or major upgrade (only when previously opted-out).
*/
getIsOptedIn: () => Promise<boolean>;
}

type SavedObjectsRegisterType = CoreSetup['savedObjects']['registerType'];

export class TelemetryPlugin implements Plugin {
export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPluginStart> {
private readonly logger: Logger;
private readonly currentKibanaVersion: string;
private readonly config$: Observable<TelemetryConfigType>;
Expand All @@ -76,8 +97,8 @@ export class TelemetryPlugin implements Plugin {

public async setup(
{ elasticsearch, http, savedObjects }: CoreSetup,
{ usageCollection, telemetryCollectionManager }: TelemetryPluginsSetup
) {
{ usageCollection, telemetryCollectionManager }: TelemetryPluginsDepsSetup
): Promise<TelemetryPluginSetup> {
const currentKibanaVersion = this.currentKibanaVersion;
const config$ = this.config$;
const isDev = this.isDev;
Expand All @@ -96,9 +117,19 @@ export class TelemetryPlugin implements Plugin {

this.registerMappings((opts) => savedObjects.registerType(opts));
this.registerUsageCollectors(usageCollection);

return {
getTelemetryUrl: async () => {
const config = await config$.pipe(take(1)).toPromise();
return new URL(config.url);
},
};
}

public async start(core: CoreStart, { telemetryCollectionManager }: TelemetryPluginsStart) {
public async start(
core: CoreStart,
{ telemetryCollectionManager }: TelemetryPluginsDepsStart
): Promise<TelemetryPluginStart> {
const { savedObjects, uiSettings } = core;
this.savedObjectsClient = savedObjects.createInternalRepository();
const savedObjectsClient = new SavedObjectsClient(this.savedObjectsClient);
Expand All @@ -111,6 +142,25 @@ export class TelemetryPlugin implements Plugin {
}

this.fetcherTask.start(core, { telemetryCollectionManager });

return {
getIsOptedIn: async () => {
const internalRepository = new SavedObjectsClient(savedObjects.createInternalRepository());
const telemetrySavedObject = await getTelemetrySavedObject(internalRepository!);
const config = await this.config$.pipe(take(1)).toPromise();
const allowChangingOptInStatus = config.allowChangingOptInStatus;
const configTelemetryOptIn = typeof config.optIn === 'undefined' ? null : config.optIn;
const currentKibanaVersion = this.currentKibanaVersion;
const isOptedIn = getTelemetryOptIn({
currentKibanaVersion,
telemetrySavedObject,
allowChangingOptInStatus,
configTelemetryOptIn,
});

return isOptedIn === true;
},
};
}

private registerMappings(registerType: SavedObjectsRegisterType) {
Expand Down

0 comments on commit 992f5cc

Please sign in to comment.