[Telemetry] Add telemetry.sendUsageTo config (#107396) (#107805)

This commit is contained in:
Ahmad Bamieh 2021-08-06 00:43:13 +03:00 committed by GitHub
parent 34906a6f59
commit cfae29aeed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 492 additions and 94 deletions

View file

@ -191,6 +191,7 @@ kibana_vars=(
telemetry.enabled
telemetry.optIn
telemetry.optInStatusUrl
telemetry.sendUsageTo
telemetry.sendUsageFrom
tilemap.options.attribution
tilemap.options.maxZoom

View file

@ -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`,
},
};

View file

@ -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);
});
});
});

View file

@ -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}.`);
}
}

View file

@ -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';

View file

@ -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,

View file

@ -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? **/

View file

@ -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 () => {
@ -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: { url },
config: { sendUsageTo: 'staging' },
});
expect(telemetryService.getTelemetryUrl()).toBe(url);
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: { sendUsageTo: 'prod' },
});
expect(telemetryService.getOptInStatusUrl()).toBe(
TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.PROD
);
});
});

View file

@ -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;
@ -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 });
};
/**

View file

@ -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(
@ -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')], {
@ -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],
};

View file

@ -0,0 +1,164 @@
/*
* 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 { deprecateEndpointConfigs } from './deprecations';
import type { TelemetryConfigType } from './config';
import { TELEMETRY_ENDPOINT } from '../../common/constants';
describe('deprecateEndpointConfigs', () => {
const fromPath = 'telemetry';
const mockAddDeprecation = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
});
function createMockRawConfig(telemetryConfig?: Partial<TelemetryConfigType>) {
return {
elasticsearch: { username: 'kibana_system', password: 'changeme' },
plugins: { paths: [] },
server: { port: 5603, basePath: '/hln', rewriteBasePath: true },
logging: { json: false },
...(telemetryConfig ? { telemetry: telemetryConfig } : {}),
};
}
it('returns void if telemetry.* config is not set', () => {
const rawConfig = createMockRawConfig();
const result = deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation);
expect(result).toBe(undefined);
});
it('sets "telemetryConfig.sendUsageTo: staging" if "telemetry.url" uses the staging endpoint', () => {
const rawConfig = createMockRawConfig({
url: TELEMETRY_ENDPOINT.MAIN_CHANNEL.STAGING,
});
const result = deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation);
expect(result).toMatchInlineSnapshot(`
Object {
"set": Array [
Object {
"path": "telemetry.sendUsageTo",
"value": "staging",
},
],
"unset": Array [
Object {
"path": "telemetry.url",
},
],
}
`);
});
it('sets "telemetryConfig.sendUsageTo: prod" if "telemetry.url" uses the non-staging endpoint', () => {
const rawConfig = createMockRawConfig({
url: 'random-endpoint',
});
const result = deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation);
expect(result).toMatchInlineSnapshot(`
Object {
"set": Array [
Object {
"path": "telemetry.sendUsageTo",
"value": "prod",
},
],
"unset": Array [
Object {
"path": "telemetry.url",
},
],
}
`);
});
it('sets "telemetryConfig.sendUsageTo: staging" if "telemetry.optInStatusUrl" uses the staging endpoint', () => {
const rawConfig = createMockRawConfig({
optInStatusUrl: TELEMETRY_ENDPOINT.MAIN_CHANNEL.STAGING,
});
const result = deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation);
expect(result).toMatchInlineSnapshot(`
Object {
"set": Array [
Object {
"path": "telemetry.sendUsageTo",
"value": "staging",
},
],
"unset": Array [
Object {
"path": "telemetry.optInStatusUrl",
},
],
}
`);
});
it('sets "telemetryConfig.sendUsageTo: prod" if "telemetry.optInStatusUrl" uses the non-staging endpoint', () => {
const rawConfig = createMockRawConfig({
optInStatusUrl: 'random-endpoint',
});
const result = deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation);
expect(result).toMatchInlineSnapshot(`
Object {
"set": Array [
Object {
"path": "telemetry.sendUsageTo",
"value": "prod",
},
],
"unset": Array [
Object {
"path": "telemetry.optInStatusUrl",
},
],
}
`);
});
it('registers deprecation when "telemetry.url" is set', () => {
const rawConfig = createMockRawConfig({
url: TELEMETRY_ENDPOINT.MAIN_CHANNEL.PROD,
});
deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation);
expect(mockAddDeprecation).toBeCalledTimes(1);
expect(mockAddDeprecation.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"correctiveActions": Object {
"manualSteps": Array [
"Remove \\"telemetry.url\\" from the Kibana configuration.",
"To send usage to the staging endpoint add \\"telemetry.sendUsageTo: staging\\" to the Kibana configuration.",
],
},
"message": "\\"telemetry.url\\" has been deprecated. Set \\"telemetry.sendUsageTo: staging\\" to the Kibana configurations to send usage to the staging endpoint.",
},
]
`);
});
it('registers deprecation when "telemetry.optInStatusUrl" is set', () => {
const rawConfig = createMockRawConfig({
optInStatusUrl: 'random-endpoint',
});
deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation);
expect(mockAddDeprecation).toBeCalledTimes(1);
expect(mockAddDeprecation.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"correctiveActions": Object {
"manualSteps": Array [
"Remove \\"telemetry.optInStatusUrl\\" from the Kibana configuration.",
"To send usage to the staging endpoint add \\"telemetry.sendUsageTo: staging\\" to the Kibana configuration.",
],
},
"message": "\\"telemetry.optInStatusUrl\\" has been deprecated. Set \\"telemetry.sendUsageTo: staging\\" to the Kibana configurations to send usage to the staging endpoint.",
},
]
`);
});
});

View file

@ -0,0 +1,63 @@
/*
* 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 { i18n } from '@kbn/i18n';
import type { ConfigDeprecation } from 'kibana/server';
import type { TelemetryConfigType } from './config';
export const deprecateEndpointConfigs: ConfigDeprecation = (
rawConfig,
fromPath,
addDeprecation
) => {
const telemetryConfig: TelemetryConfigType = rawConfig[fromPath];
if (!telemetryConfig) {
return;
}
const unset: Array<{ path: string }> = [];
const endpointConfigPaths = ['url', 'optInStatusUrl'] as const;
let useStaging = telemetryConfig.sendUsageTo === 'staging' ? true : false;
for (const configPath of endpointConfigPaths) {
const configValue = telemetryConfig[configPath];
const fullConfigPath = `telemetry.${configPath}`;
if (typeof configValue !== 'undefined') {
unset.push({ path: fullConfigPath });
if (/telemetry-staging\.elastic\.co/i.test(configValue)) {
useStaging = true;
}
addDeprecation({
message: i18n.translate('telemetry.endpointConfigs.deprecationMessage', {
defaultMessage:
'"{configPath}" has been deprecated. Set "telemetry.sendUsageTo: staging" to the Kibana configurations to send usage to the staging endpoint.',
values: { configPath: fullConfigPath },
}),
correctiveActions: {
manualSteps: [
i18n.translate('telemetry.endpointConfigs.deprecationManualStep1', {
defaultMessage: 'Remove "{configPath}" from the Kibana configuration.',
values: { configPath: fullConfigPath },
}),
i18n.translate('telemetry.endpointConfigs.deprecationManualStep2', {
defaultMessage:
'To send usage to the staging endpoint add "telemetry.sendUsageTo: staging" to the Kibana configuration.',
}),
],
},
});
}
}
return {
set: [{ path: 'telemetry.sendUsageTo', value: useStaging ? 'staging' : 'prod' }],
unset,
};
};

View file

@ -0,0 +1,10 @@
/*
* 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.
*/
export { config } from './config';
export type { TelemetryConfigType } from './config';

View file

@ -19,6 +19,7 @@ import {
ICustomClusterClient,
} from '../../../core/server';
import {
getTelemetryChannelEndpoint,
getTelemetryOptIn,
getTelemetrySendUsageFrom,
getTelemetryFailureDetails,
@ -139,7 +140,10 @@ export class FetcherTask {
const configTelemetrySendUsageFrom = config.sendUsageFrom;
const allowChangingOptInStatus = config.allowChangingOptInStatus;
const configTelemetryOptIn = typeof config.optIn === 'undefined' ? null : config.optIn;
const telemetryUrl = config.url;
const telemetryUrl = getTelemetryChannelEndpoint({
channelName: 'main',
env: config.sendUsageTo,
});
const { failureCount, failureVersion } = getTelemetryFailureDetails({
telemetrySavedObject,
});
@ -208,17 +212,17 @@ export class FetcherTask {
});
}
private async sendTelemetry(url: string, cluster: string): Promise<void> {
private async sendTelemetry(telemetryUrl: string, cluster: string): Promise<void> {
this.logger.debug(`Sending usage stats.`);
/**
* send OPTIONS before sending usage data.
* OPTIONS is less intrusive as it does not contain any payload and is used here to check if the endpoint is reachable.
*/
await fetch(url, {
await fetch(telemetryUrl, {
method: 'options',
});
await fetch(url, {
await fetch(telemetryUrl, {
method: 'post',
body: cluster,
headers: { 'X-Elastic-Stack-Version': this.currentKibanaVersion },

View file

@ -6,25 +6,13 @@
* Side Public License, v 1.
*/
import { PluginInitializerContext, PluginConfigDescriptor } from 'kibana/server';
import type { PluginInitializerContext } from 'kibana/server';
import type { TelemetryConfigType } from './config';
import { TelemetryPlugin } from './plugin';
import { configSchema, TelemetryConfigType } from './config';
export { config } from './config';
export type { TelemetryPluginSetup, TelemetryPluginStart } from './plugin';
export const config: PluginConfigDescriptor<TelemetryConfigType> = {
schema: configSchema,
exposeToBrowser: {
enabled: true,
url: true,
banner: true,
allowChangingOptInStatus: true,
optIn: true,
optInStatusUrl: true,
sendUsageFrom: true,
},
};
export const plugin = (initializerContext: PluginInitializerContext<TelemetryConfigType>) =>
new TelemetryPlugin(initializerContext);
export { getClusterUuids, getLocalStats } from './telemetry_collection';

View file

@ -30,11 +30,11 @@ import {
registerTelemetryUsageCollector,
registerTelemetryPluginUsageCollector,
} from './collectors';
import { TelemetryConfigType } from './config';
import type { TelemetryConfigType } from './config';
import { FetcherTask } from './fetcher';
import { handleOldSettings } from './handle_old_settings';
import { getTelemetrySavedObject } from './telemetry_repository';
import { getTelemetryOptIn } from '../common/telemetry_config';
import { getTelemetryOptIn, getTelemetryChannelEndpoint } from '../common/telemetry_config';
interface TelemetryPluginsDepsSetup {
usageCollection: UsageCollectionSetup;
@ -117,8 +117,10 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
return {
getTelemetryUrl: async () => {
const config = await config$.pipe(take(1)).toPromise();
return new URL(config.url);
const { sendUsageTo } = await config$.pipe(take(1)).toPromise();
const telemetryUrl = getTelemetryChannelEndpoint({ env: sendUsageTo, channelName: 'main' });
return new URL(telemetryUrl);
},
};
}

View file

@ -83,15 +83,15 @@ export function registerTelemetryOptInRoutes({
);
if (config.sendUsageFrom === 'server') {
const optInStatusUrl = config.optInStatusUrl;
const { sendUsageTo } = config;
sendTelemetryOptInStatus(
telemetryCollectionManager,
{ optInStatusUrl, newOptInStatus, currentKibanaVersion },
{ sendUsageTo, newOptInStatus, currentKibanaVersion },
statsGetterConfig
).catch((err) => {
// The server is likely behind a firewall and can't reach the remote service
logger.warn(
`Failed to notify "${optInStatusUrl}" from the server about the opt-in selection. Possibly blocked by a firewall? - Error: ${err.message}`
`Failed to notify the telemetry endpoint about the opt-in selection. Possibly blocked by a firewall? - Error: ${err.message}`
);
});
}

View file

@ -10,30 +10,53 @@ jest.mock('node-fetch');
import fetch from 'node-fetch';
import { sendTelemetryOptInStatus } from './telemetry_opt_in_stats';
import { StatsGetterConfig } from 'src/plugins/telemetry_collection_manager/server';
import { TELEMETRY_ENDPOINT } from '../../common/constants';
describe('sendTelemetryOptInStatus', () => {
const mockStatsGetterConfig = { unencrypted: false } as StatsGetterConfig;
const mockTelemetryCollectionManager = {
getOptInStats: jest.fn().mockResolvedValue(['mock_opt_in_hashed_value']),
};
beforeEach(() => {
jest.clearAllMocks();
});
it('calls fetch with the opt in status returned from the telemetryCollectionManager', async () => {
const mockOptInStatus = ['mock_opt_in_hashed_value'];
const mockTelemetryCollectionManager = {
getOptInStats: jest.fn().mockResolvedValue(mockOptInStatus),
};
const mockConfig = {
optInStatusUrl: 'some_url',
sendUsageTo: 'prod' as const,
newOptInStatus: true,
currentKibanaVersion: 'mock_kibana_version',
};
const mockStatsGetterConfig = {
unencrypted: false,
};
const result = await sendTelemetryOptInStatus(
mockTelemetryCollectionManager,
mockConfig,
mockStatsGetterConfig as StatsGetterConfig
mockStatsGetterConfig
);
expect(result).toBeUndefined();
expect(fetch).toBeCalledTimes(1);
expect(fetch).toBeCalledWith(mockConfig.optInStatusUrl, {
expect(fetch).toBeCalledWith(TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.PROD, {
method: 'post',
body: '["mock_opt_in_hashed_value"]',
headers: { 'X-Elastic-Stack-Version': mockConfig.currentKibanaVersion },
});
});
it('sends to staging endpoint on "sendUsageTo: staging"', async () => {
const mockConfig = {
sendUsageTo: 'staging' as const,
newOptInStatus: true,
currentKibanaVersion: 'mock_kibana_version',
};
await sendTelemetryOptInStatus(
mockTelemetryCollectionManager,
mockConfig,
mockStatsGetterConfig
);
expect(fetch).toBeCalledTimes(1);
expect(fetch).toBeCalledWith(TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.STAGING, {
method: 'post',
body: '["mock_opt_in_hashed_value"]',
headers: { 'X-Elastic-Stack-Version': mockConfig.currentKibanaVersion },

View file

@ -14,9 +14,10 @@ import {
TelemetryCollectionManagerPluginSetup,
StatsGetterConfig,
} from 'src/plugins/telemetry_collection_manager/server';
import { getTelemetryChannelEndpoint } from '../../common/telemetry_config';
interface SendTelemetryOptInStatusConfig {
optInStatusUrl: string;
sendUsageTo: 'staging' | 'prod';
newOptInStatus: boolean;
currentKibanaVersion: string;
}
@ -26,7 +27,12 @@ export async function sendTelemetryOptInStatus(
config: SendTelemetryOptInStatusConfig,
statsGetterConfig: StatsGetterConfig
) {
const { optInStatusUrl, newOptInStatus, currentKibanaVersion } = config;
const { sendUsageTo, newOptInStatus, currentKibanaVersion } = config;
const optInStatusUrl = getTelemetryChannelEndpoint({
env: sendUsageTo,
channelName: 'optInStatus',
});
const optInStatus = await telemetryCollectionManager.getOptInStats(
newOptInStatus,
statsGetterConfig

View file

@ -311,9 +311,8 @@ exports[`TelemetryManagementSectionComponent renders null because allowChangingO
"banner": true,
"enabled": true,
"optIn": true,
"optInStatusUrl": "",
"sendUsageFrom": "browser",
"url": "",
"sendUsageTo": "staging",
},
"fetchExample": [Function],
"fetchTelemetry": [Function],

View file

@ -24,12 +24,11 @@ describe('TelemetryManagementSectionComponent', () => {
const isSecurityExampleEnabled = jest.fn().mockReturnValue(true);
const telemetryService = new TelemetryService({
config: {
sendUsageTo: 'staging',
enabled: true,
url: '',
banner: true,
allowChangingOptInStatus: true,
optIn: true,
optInStatusUrl: '',
sendUsageFrom: 'browser',
},
isScreenshotMode: false,
@ -60,12 +59,11 @@ describe('TelemetryManagementSectionComponent', () => {
const telemetryService = new TelemetryService({
config: {
enabled: true,
url: '',
banner: true,
allowChangingOptInStatus: true,
optIn: false,
optInStatusUrl: '',
sendUsageFrom: 'browser',
sendUsageTo: 'staging',
},
isScreenshotMode: false,
reportOptInStatusChange: false,
@ -116,11 +114,10 @@ describe('TelemetryManagementSectionComponent', () => {
const telemetryService = new TelemetryService({
config: {
enabled: true,
url: '',
banner: true,
allowChangingOptInStatus: true,
optIn: false,
optInStatusUrl: '',
sendUsageTo: 'staging',
sendUsageFrom: 'browser',
},
isScreenshotMode: false,
@ -165,11 +162,10 @@ describe('TelemetryManagementSectionComponent', () => {
const telemetryService = new TelemetryService({
config: {
enabled: true,
url: '',
banner: true,
allowChangingOptInStatus: false,
optIn: true,
optInStatusUrl: '',
sendUsageTo: 'staging',
sendUsageFrom: 'browser',
},
isScreenshotMode: false,
@ -205,11 +201,10 @@ describe('TelemetryManagementSectionComponent', () => {
const telemetryService = new TelemetryService({
config: {
enabled: true,
url: '',
banner: true,
allowChangingOptInStatus: true,
optIn: false,
optInStatusUrl: '',
sendUsageTo: 'staging',
sendUsageFrom: 'browser',
},
isScreenshotMode: false,
@ -246,11 +241,10 @@ describe('TelemetryManagementSectionComponent', () => {
const telemetryService = new TelemetryService({
config: {
enabled: true,
url: '',
banner: true,
allowChangingOptInStatus: true,
optIn: false,
optInStatusUrl: '',
sendUsageTo: 'staging',
sendUsageFrom: 'browser',
},
isScreenshotMode: false,
@ -287,11 +281,10 @@ describe('TelemetryManagementSectionComponent', () => {
const telemetryService = new TelemetryService({
config: {
enabled: true,
url: '',
banner: true,
allowChangingOptInStatus: true,
optIn: false,
optInStatusUrl: '',
sendUsageTo: 'staging',
sendUsageFrom: 'browser',
},
isScreenshotMode: false,
@ -328,11 +321,10 @@ describe('TelemetryManagementSectionComponent', () => {
const telemetryService = new TelemetryService({
config: {
enabled: true,
url: '',
banner: true,
allowChangingOptInStatus: true,
optIn: false,
optInStatusUrl: '',
sendUsageTo: 'staging',
sendUsageFrom: 'browser',
},
isScreenshotMode: false,
@ -379,11 +371,10 @@ describe('TelemetryManagementSectionComponent', () => {
const telemetryService = new TelemetryService({
config: {
enabled: true,
url: '',
banner: true,
allowChangingOptInStatus: false,
optIn: false,
optInStatusUrl: '',
sendUsageTo: 'staging',
sendUsageFrom: 'browser',
},
isScreenshotMode: false,

View file

@ -17,17 +17,6 @@ import {
TelemetryManagementSectionWrapperProps,
} from './components/telemetry_management_section_wrapper';
export interface TelemetryPluginConfig {
enabled: boolean;
url: string;
banner: boolean;
allowChangingOptInStatus: boolean;
optIn: boolean | null;
optInStatusUrl: string;
sendUsageFrom: 'browser' | 'server';
telemetryNotifyUserAboutOptInDefault?: boolean;
}
export interface TelemetryManagementSectionPluginDepsSetup {
telemetry: TelemetryPluginSetup;
advancedSettings: AdvancedSettingsSetup;

View file

@ -48,8 +48,7 @@ export default function () {
'--telemetry.banner=false',
'--telemetry.optIn=false',
// These are *very* important to have them pointing to staging
'--telemetry.url=https://telemetry-staging.elastic.co/xpack/v2/send',
'--telemetry.optInStatusUrl=https://telemetry-staging.elastic.co/opt_in_status/v2/send',
'--telemetry.sendUsageTo=staging',
`--server.maxPayload=1679958`,
// newsfeed mock service
`--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'newsfeed')}`,