Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Telemetry] Add telemetry.sendUsageTo config #107396

Merged
merged 10 commits into from
Aug 5, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ kibana_vars=(
telemetry.enabled
telemetry.optIn
telemetry.optInStatusUrl
telemetry.sendUsageTo
telemetry.sendUsageFrom
tilemap.options.attribution
tilemap.options.maxZoom
Expand Down
14 changes: 14 additions & 0 deletions src/plugins/telemetry/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,17 @@ export const PRIVACY_STATEMENT_URL = `https://www.elastic.co/legal/privacy-state
* The endpoint version when hitting the remote telemetry service
*/
export const ENDPOINT_VERSION = 'v2';

/**
* The telemetry endpoints for the remote telemetry service.
*/
export const TELEMETRY_ENDPOINT = {
MAIN_CHANNEL: {
PROD: `https://telemetry.elastic.co/xpack/${ENDPOINT_VERSION}/send`,
STAGING: `https://telemetry-staging.elastic.co/xpack/${ENDPOINT_VERSION}/send`,
},
OPT_IN_STATUS_CHANNEL: {
PROD: `https://telemetry.elastic.co/opt_in_status/${ENDPOINT_VERSION}/send`,
STAGING: `https://telemetry-staging.elastic.co/opt_in_status/${ENDPOINT_VERSION}/send`,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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 { getTelemetryChannelEndpoint } from './get_telemetry_channel_endpoint';
import { TELEMETRY_ENDPOINT } from '../constants';

describe('getTelemetryChannelEndpoint', () => {
it('throws on unknown env', () => {
expect(() =>
// @ts-expect-error
getTelemetryChannelEndpoint({ env: 'ANY', channelName: 'main' })
).toThrowErrorMatchingInlineSnapshot(`"Unknown telemetry endpoint env ANY."`);
});

it('throws on unknown channelName', () => {
expect(() =>
// @ts-expect-error
getTelemetryChannelEndpoint({ env: 'prod', channelName: 'ANY' })
).toThrowErrorMatchingInlineSnapshot(`"Unknown telemetry channel ANY."`);
});

describe('main channel', () => {
it('returns correct prod endpoint', () => {
const endpoint = getTelemetryChannelEndpoint({ env: 'prod', channelName: 'main' });
expect(endpoint).toBe(TELEMETRY_ENDPOINT.MAIN_CHANNEL.PROD);
});
it('returns correct staging endpoint', () => {
const endpoint = getTelemetryChannelEndpoint({ env: 'staging', channelName: 'main' });
expect(endpoint).toBe(TELEMETRY_ENDPOINT.MAIN_CHANNEL.STAGING);
});
});

describe('optInStatus channel', () => {
it('returns correct prod endpoint', () => {
const endpoint = getTelemetryChannelEndpoint({ env: 'prod', channelName: 'optInStatus' });
expect(endpoint).toBe(TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.PROD);
});
it('returns correct staging endpoint', () => {
const endpoint = getTelemetryChannelEndpoint({ env: 'staging', channelName: 'optInStatus' });
expect(endpoint).toBe(TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.STAGING);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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 { TELEMETRY_ENDPOINT } from '../constants';

export interface GetTelemetryChannelEndpointConfig {
channelName: 'main' | 'optInStatus';
env: 'staging' | 'prod';
}

export function getTelemetryChannelEndpoint({
channelName,
env,
}: GetTelemetryChannelEndpointConfig): string {
if (env !== 'staging' && env !== 'prod') {
throw new Error(`Unknown telemetry endpoint env ${env}.`);
}

const endpointEnv = env === 'staging' ? 'STAGING' : 'PROD';

switch (channelName) {
case 'main':
return TELEMETRY_ENDPOINT.MAIN_CHANNEL[endpointEnv];
case 'optInStatus':
return TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL[endpointEnv];
default:
throw new Error(`Unknown telemetry channel ${channelName}.`);
}
}
2 changes: 2 additions & 0 deletions src/plugins/telemetry/common/telemetry_config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ export { getTelemetrySendUsageFrom } from './get_telemetry_send_usage_from';
export { getTelemetryAllowChangingOptInStatus } from './get_telemetry_allow_changing_opt_in_status';
export { getTelemetryFailureDetails } from './get_telemetry_failure_details';
export type { TelemetryFailureDetails } from './get_telemetry_failure_details';
export { getTelemetryChannelEndpoint } from './get_telemetry_channel_endpoint';
export type { GetTelemetryChannelEndpointConfig } from './get_telemetry_channel_endpoint';
3 changes: 1 addition & 2 deletions src/plugins/telemetry/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ export function mockTelemetryService({
}: TelemetryServiceMockOptions = {}) {
const config = {
enabled: true,
url: 'http://localhost',
optInStatusUrl: 'http://localhost',
sendUsageTo: 'staging' as const,
sendUsageFrom: 'browser' as const,
optIn: true,
banner: true,
Expand Down
6 changes: 2 additions & 4 deletions src/plugins/telemetry/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,14 @@ interface TelemetryPluginSetupDependencies {
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;
/** Specify if telemetry should send usage to the prod or staging remote telemetry service **/
sendUsageTo: 'prod' | 'staging';
/** Should the telemetry payloads be sent from the server or the browser? **/
sendUsageFrom: 'browser' | 'server';
/** Should notify the user about the opt-in status? **/
Expand Down
39 changes: 34 additions & 5 deletions src/plugins/telemetry/public/services/telemetry_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/* eslint-disable dot-notation */

import { mockTelemetryService } from '../mocks';

import { TELEMETRY_ENDPOINT } from '../../common/constants';
describe('TelemetryService', () => {
describe('fetchTelemetry', () => {
it('calls expected URL with 20 minutes - now', async () => {
Expand Down Expand Up @@ -137,13 +137,42 @@ describe('TelemetryService', () => {
});

describe('getTelemetryUrl', () => {
it('should return the config.url parameter', async () => {
const url = 'http://test.com';
it('should return staging endpoint when sendUsageTo is set to staging', async () => {
const telemetryService = mockTelemetryService({
config: { sendUsageTo: 'staging' },
});

expect(telemetryService.getTelemetryUrl()).toBe(TELEMETRY_ENDPOINT.MAIN_CHANNEL.STAGING);
});

it('should return prod endpoint when sendUsageTo is set to prod', async () => {
const telemetryService = mockTelemetryService({
config: { sendUsageTo: 'prod' },
});

expect(telemetryService.getTelemetryUrl()).toBe(TELEMETRY_ENDPOINT.MAIN_CHANNEL.PROD);
});
});

describe('getOptInStatusUrl', () => {
it('should return staging endpoint when sendUsageTo is set to staging', async () => {
const telemetryService = mockTelemetryService({
config: { sendUsageTo: 'staging' },
});

expect(telemetryService.getOptInStatusUrl()).toBe(
TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.STAGING
);
});

it('should return prod endpoint when sendUsageTo is set to prod', async () => {
const telemetryService = mockTelemetryService({
config: { url },
config: { sendUsageTo: 'prod' },
});

expect(telemetryService.getTelemetryUrl()).toBe(url);
expect(telemetryService.getOptInStatusUrl()).toBe(
TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.PROD
);
});
});

Expand Down
9 changes: 5 additions & 4 deletions src/plugins/telemetry/public/services/telemetry_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import { i18n } from '@kbn/i18n';
import { CoreStart } from 'kibana/public';
import { TelemetryPluginConfig } from '../plugin';
import { getTelemetryChannelEndpoint } from '../../common/telemetry_config';

interface TelemetryServiceConstructor {
config: TelemetryPluginConfig;
Expand Down Expand Up @@ -93,14 +94,14 @@ export class TelemetryService {

/** Retrieve the opt-in/out notification URL **/
public getOptInStatusUrl = () => {
const telemetryOptInStatusUrl = this.config.optInStatusUrl;
return telemetryOptInStatusUrl;
const { sendUsageTo } = this.config;
return getTelemetryChannelEndpoint({ channelName: 'optInStatus', env: sendUsageTo });
};

/** Retrieve the URL to report telemetry **/
public getTelemetryUrl = () => {
const telemetryUrl = this.config.url;
return telemetryUrl;
const { sendUsageTo } = this.config;
return getTelemetryChannelEndpoint({ channelName: 'main', env: sendUsageTo });
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@
* Side Public License, v 1.
*/

import { schema, TypeOf } from '@kbn/config-schema';
import { schema, TypeOf, Type } from '@kbn/config-schema';
import { getConfigPath } from '@kbn/utils';
import { ENDPOINT_VERSION } from '../common/constants';
import { PluginConfigDescriptor } from 'kibana/server';
import { TELEMETRY_ENDPOINT } from '../../common/constants';
import { deprecateEndpointConfigs } from './deprecations';

export const configSchema = schema.object({
const clusterEnvSchema: [Type<'prod'>, Type<'staging'>] = [
schema.literal('prod'),
schema.literal('staging'),
];

const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: true }),
allowChangingOptInStatus: schema.boolean({ defaultValue: true }),
optIn: schema.conditional(
Expand All @@ -23,24 +30,38 @@ export const configSchema = schema.object({
// `config` is used internally and not intended to be set
config: schema.string({ defaultValue: getConfigPath() }),
banner: schema.boolean({ defaultValue: true }),
sendUsageTo: schema.conditional(
schema.contextRef('dist'),
schema.literal(false), // Point to staging if it's not a distributable release
schema.oneOf(clusterEnvSchema, { defaultValue: 'staging' }),
schema.oneOf(clusterEnvSchema, { defaultValue: 'prod' })
),
/**
* REMOVE IN 8.0 - INTERNAL CONFIG DEPRECATED IN 7.15
* REPLACED WITH `telemetry.sendUsageTo: staging | prod`
*/
url: schema.conditional(
schema.contextRef('dist'),
schema.literal(false), // Point to staging if it's not a distributable release
schema.string({
defaultValue: `https://telemetry-staging.elastic.co/xpack/${ENDPOINT_VERSION}/send`,
defaultValue: TELEMETRY_ENDPOINT.MAIN_CHANNEL.STAGING,
}),
schema.string({
defaultValue: `https://telemetry.elastic.co/xpack/${ENDPOINT_VERSION}/send`,
defaultValue: TELEMETRY_ENDPOINT.MAIN_CHANNEL.PROD,
})
),
/**
* REMOVE IN 8.0 - INTERNAL CONFIG DEPRECATED IN 7.15
* REPLACED WITH `telemetry.sendUsageTo: staging | prod`
*/
optInStatusUrl: schema.conditional(
schema.contextRef('dist'),
schema.literal(false), // Point to staging if it's not a distributable release
schema.string({
defaultValue: `https://telemetry-staging.elastic.co/opt_in_status/${ENDPOINT_VERSION}/send`,
defaultValue: TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.STAGING,
}),
schema.string({
defaultValue: `https://telemetry.elastic.co/opt_in_status/${ENDPOINT_VERSION}/send`,
defaultValue: TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.PROD,
})
),
sendUsageFrom: schema.oneOf([schema.literal('server'), schema.literal('browser')], {
Expand All @@ -49,3 +70,16 @@ export const configSchema = schema.object({
});

export type TelemetryConfigType = TypeOf<typeof configSchema>;

export const config: PluginConfigDescriptor<TelemetryConfigType> = {
schema: configSchema,
exposeToBrowser: {
enabled: true,
banner: true,
allowChangingOptInStatus: true,
optIn: true,
sendUsageFrom: true,
sendUsageTo: true,
},
deprecations: () => [deprecateEndpointConfigs],
};
Loading