Skip to content

Commit

Permalink
[API DOCS] Telemetry (#98610)
Browse files Browse the repository at this point in the history
  • Loading branch information
afharo committed Apr 29, 2021
1 parent 7eb733f commit ed2e544
Show file tree
Hide file tree
Showing 15 changed files with 613 additions and 1,177 deletions.
1,505 changes: 404 additions & 1,101 deletions api_docs/telemetry.json

Large diffs are not rendered by default.

6 changes: 0 additions & 6 deletions api_docs/telemetry.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ import telemetryObj from './telemetry.json';
### Start
<DocDefinitionList data={[telemetryObj.client.start]}/>

### Classes
<DocDefinitionList data={telemetryObj.client.classes}/>

### Interfaces
<DocDefinitionList data={telemetryObj.client.interfaces}/>

Expand All @@ -33,9 +30,6 @@ import telemetryObj from './telemetry.json';
### Start
<DocDefinitionList data={[telemetryObj.server.start]}/>

### Functions
<DocDefinitionList data={telemetryObj.server.functions}/>

### Interfaces
<DocDefinitionList data={telemetryObj.server.interfaces}/>

Expand Down
13 changes: 9 additions & 4 deletions src/plugins/telemetry/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
* Side Public License, v 1.
*/

import { PluginInitializerContext } from 'kibana/public';
import { TelemetryPlugin, TelemetryPluginConfig } from './plugin';
export type { TelemetryPluginStart, TelemetryPluginSetup, TelemetryPluginConfig } from './plugin';
export type { TelemetryNotifications, TelemetryService } from './services';
import type { PluginInitializerContext } from 'src/core/public';
import type { TelemetryPluginConfig } from './plugin';
import { TelemetryPlugin } from './plugin';
export type {
TelemetryPluginStart,
TelemetryPluginSetup,
TelemetryPluginConfig,
TelemetryServicePublicApis,
} from './plugin';

export function plugin(initializerContext: PluginInitializerContext<TelemetryPluginConfig>) {
return new TelemetryPlugin(initializerContext);
Expand Down
80 changes: 70 additions & 10 deletions src/plugins/telemetry/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import {
import type {
Plugin,
CoreStart,
CoreSetup,
Expand All @@ -15,10 +15,10 @@ import {
SavedObjectsClientContract,
SavedObjectsBatchResponse,
ApplicationStart,
} from '../../../core/public';
} from 'src/core/public';

import { TelemetrySender, TelemetryService, TelemetryNotifications } from './services';
import {
import type {
TelemetrySavedObjectAttributes,
TelemetrySavedObject,
} from '../common/telemetry_config/types';
Expand All @@ -30,27 +30,73 @@ import {
import { getNotifyUserAboutOptInDefault } from '../common/telemetry_config/get_telemetry_notify_user_about_optin_default';
import { PRIVACY_STATEMENT_URL } from '../common/constants';

/**
* Publicly exposed APIs from the Telemetry Service
*/
export interface TelemetryServicePublicApis {
/** Is the cluster opted-in to telemetry? **/
getIsOptedIn: () => boolean | null;
/** Is the user allowed to change the opt-in/out status? **/
userCanChangeSettings: boolean;
/** Is the cluster allowed to change the opt-in/out status? **/
getCanChangeOptInStatus: () => boolean;
/** Fetches an unencrypted telemetry payload so we can show it to the user **/
fetchExample: () => Promise<unknown[]>;
/**
* Overwrite the opt-in status.
* It will send a final request to the remote telemetry cluster to report about the opt-in/out change.
* @param optedIn Whether the user is opting-in (`true`) or out (`false`).
*/
setOptIn: (optedIn: boolean) => Promise<boolean>;
}

/**
* Public's setup exposed APIs by the telemetry plugin
*/
export interface TelemetryPluginSetup {
telemetryService: TelemetryService;
/** {@link TelemetryService} **/
telemetryService: TelemetryServicePublicApis;
}

/**
* Public's start exposed APIs by the telemetry plugin
*/
export interface TelemetryPluginStart {
telemetryService: TelemetryService;
telemetryNotifications: TelemetryNotifications;
/** {@link TelemetryServicePublicApis} **/
telemetryService: TelemetryServicePublicApis;
/** Notification helpers **/
telemetryNotifications: {
/** Notify that the user has been presented with the opt-in/out notice. */
setOptedInNoticeSeen: () => Promise<void>;
};
/** Set of publicly exposed telemetry constants **/
telemetryConstants: {
/** Elastic's privacy statement url **/
getPrivacyStatementUrl: () => string;
};
}

/**
* Public-exposed configuration
*/
export interface TelemetryPluginConfig {
/** Is the plugin enabled? **/
enabled: boolean;
/** Remote telemetry service's URL **/
url: string;
/** The banner is expected to be shown when needed **/
banner: boolean;
/** Does the cluster allow changing the opt-in/out status via the UI? **/
allowChangingOptInStatus: boolean;
/** Is the cluster opted-in? **/
optIn: boolean | null;
/** Opt-in/out notification URL **/
optInStatusUrl: string;
/** Should the telemetry payloads be sent from the server or the browser? **/
sendUsageFrom: 'browser' | 'server';
/** Should notify the user about the opt-in status? **/
telemetryNotifyUserAboutOptInDefault?: boolean;
/** Does the user have enough privileges to change the settings? **/
userCanChangeSettings?: boolean;
}

Expand Down Expand Up @@ -80,7 +126,7 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
this.telemetrySender = new TelemetrySender(this.telemetryService);

return {
telemetryService: this.telemetryService,
telemetryService: this.getTelemetryServicePublicApis(),
};
}

Expand All @@ -92,11 +138,12 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
this.canUserChangeSettings = this.getCanUserChangeSettings(application);
this.telemetryService.userCanChangeSettings = this.canUserChangeSettings;

this.telemetryNotifications = new TelemetryNotifications({
const telemetryNotifications = new TelemetryNotifications({
http,
overlays,
telemetryService: this.telemetryService,
});
this.telemetryNotifications = telemetryNotifications;

application.currentAppId$.subscribe(async () => {
const isUnauthenticated = this.getIsUnauthenticated(http);
Expand All @@ -119,14 +166,27 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
});

return {
telemetryService: this.telemetryService,
telemetryNotifications: this.telemetryNotifications,
telemetryService: this.getTelemetryServicePublicApis(),
telemetryNotifications: {
setOptedInNoticeSeen: () => telemetryNotifications.setOptedInNoticeSeen(),
},
telemetryConstants: {
getPrivacyStatementUrl: () => PRIVACY_STATEMENT_URL,
},
};
}

private getTelemetryServicePublicApis(): TelemetryServicePublicApis {
const telemetryService = this.telemetryService!;
return {
getIsOptedIn: () => telemetryService.getIsOptedIn(),
setOptIn: (optedIn) => telemetryService.setOptIn(optedIn),
userCanChangeSettings: telemetryService.userCanChangeSettings,
getCanChangeOptInStatus: () => telemetryService.getCanChangeOptInStatus(),
fetchExample: () => telemetryService.fetchExample(),
};
}

/**
* Can the user edit the saved objects?
* This is a security feature, not included in the OSS build, so we need to fallback to `true`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ interface TelemetryNotificationsConstructor {
telemetryService: TelemetryService;
}

/**
* Helpers to the Telemetry banners spread through the code base in Welcome and Home landing pages.
*/
export class TelemetryNotifications {
private readonly http: CoreStart['http'];
private readonly overlays: CoreStart['overlays'];
Expand All @@ -30,12 +33,18 @@ export class TelemetryNotifications {
this.overlays = overlays;
}

/**
* Should the opted-in banner be shown to the user?
*/
public shouldShowOptedInNoticeBanner = (): boolean => {
const userShouldSeeOptInNotice = this.telemetryService.getUserShouldSeeOptInNotice();
const bannerOnScreen = typeof this.optedInNoticeBannerId !== 'undefined';
return !bannerOnScreen && userShouldSeeOptInNotice;
};

/**
* Renders the banner that claims the cluster is opted-in, and gives the option to opt-out.
*/
public renderOptedInNoticeBanner = (): void => {
const bannerId = renderOptedInNoticeBanner({
http: this.http,
Expand All @@ -46,12 +55,18 @@ export class TelemetryNotifications {
this.optedInNoticeBannerId = bannerId;
};

/**
* Should the banner to opt-in be shown to the user?
*/
public shouldShowOptInBanner = (): boolean => {
const isOptedIn = this.telemetryService.getIsOptedIn();
const bannerOnScreen = typeof this.optInBannerId !== 'undefined';
return !bannerOnScreen && isOptedIn === null;
};

/**
* Renders the banner that claims the cluster is opted-out, and gives the option to opt-in.
*/
public renderOptInBanner = (): void => {
const bannerId = renderOptInBanner({
setOptIn: this.onSetOptInClick,
Expand All @@ -61,6 +76,10 @@ export class TelemetryNotifications {
this.optInBannerId = bannerId;
};

/**
* Opt-in/out button handler
* @param isOptIn true/false whether the user opts-in/out
*/
private onSetOptInClick = async (isOptIn: boolean) => {
if (this.optInBannerId) {
this.overlays.banners.remove(this.optInBannerId);
Expand All @@ -70,6 +89,9 @@ export class TelemetryNotifications {
await this.telemetryService.setOptIn(isOptIn);
};

/**
* Clears the banner and stores the user's dismissal of the banner.
*/
public setOptedInNoticeSeen = async (): Promise<void> => {
if (this.optedInNoticeBannerId) {
this.overlays.banners.remove(this.optedInNoticeBannerId);
Expand Down
20 changes: 10 additions & 10 deletions src/plugins/telemetry/public/services/telemetry_sender.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,20 @@ describe('TelemetrySender', () => {
const telemetryService = mockTelemetryService();
telemetryService.getIsOptedIn = jest.fn().mockReturnValue(false);
const telemetrySender = new TelemetrySender(telemetryService);
const shouldSendRerpot = telemetrySender['shouldSendReport']();
const shouldSendReport = telemetrySender['shouldSendReport']();

expect(telemetryService.getIsOptedIn).toBeCalledTimes(1);
expect(shouldSendRerpot).toBe(false);
expect(shouldSendReport).toBe(false);
});

it('returns true if lastReported is undefined', () => {
const telemetryService = mockTelemetryService();
telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true);
const telemetrySender = new TelemetrySender(telemetryService);
const shouldSendRerpot = telemetrySender['shouldSendReport']();
const shouldSendReport = telemetrySender['shouldSendReport']();

expect(telemetrySender['lastReported']).toBeUndefined();
expect(shouldSendRerpot).toBe(true);
expect(shouldSendReport).toBe(true);
});

it('returns true if lastReported passed REPORT_INTERVAL_MS', () => {
Expand All @@ -94,8 +94,8 @@ describe('TelemetrySender', () => {
telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true);
const telemetrySender = new TelemetrySender(telemetryService);
telemetrySender['lastReported'] = `${lastReported}`;
const shouldSendRerpot = telemetrySender['shouldSendReport']();
expect(shouldSendRerpot).toBe(true);
const shouldSendReport = telemetrySender['shouldSendReport']();
expect(shouldSendReport).toBe(true);
});

it('returns false if lastReported is within REPORT_INTERVAL_MS', () => {
Expand All @@ -105,17 +105,17 @@ describe('TelemetrySender', () => {
telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true);
const telemetrySender = new TelemetrySender(telemetryService);
telemetrySender['lastReported'] = `${lastReported}`;
const shouldSendRerpot = telemetrySender['shouldSendReport']();
expect(shouldSendRerpot).toBe(false);
const shouldSendReport = telemetrySender['shouldSendReport']();
expect(shouldSendReport).toBe(false);
});

it('returns true if lastReported is malformed', () => {
const telemetryService = mockTelemetryService();
telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true);
const telemetrySender = new TelemetrySender(telemetryService);
telemetrySender['lastReported'] = `random_malformed_string`;
const shouldSendRerpot = telemetrySender['shouldSendReport']();
expect(shouldSendRerpot).toBe(true);
const shouldSendReport = telemetrySender['shouldSendReport']();
expect(shouldSendReport).toBe(true);
});

describe('sendIfDue', () => {
Expand Down
Loading

0 comments on commit ed2e544

Please sign in to comment.