Removes deprecated telemetry.url and telemetry.optInStatusUrl from telemetry plugin config (#114737)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Christiane (Tina) Heiligers 2021-10-15 11:26:43 -07:00 committed by GitHub
parent 07777b9de1
commit 22d07ed3d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 63 additions and 414 deletions

View file

@ -26,6 +26,30 @@ async function removeLogFile() {
// ignore errors if it doesn't exist
await fs.unlink(logFilePath).catch(() => void 0);
}
function sortByTypeAndId(a: { type: string; id: string }, b: { type: string; id: string }) {
return a.type.localeCompare(b.type) || a.id.localeCompare(b.id);
}
async function fetchDocuments(esClient: ElasticsearchClient, index: string) {
const { body } = await esClient.search<any>({
index,
body: {
query: {
match_all: {},
},
_source: ['type', 'id'],
},
});
return body.hits.hits
.map((h) => ({
...h._source,
id: h._id,
}))
.sort(sortByTypeAndId);
}
const assertMigratedDocuments = (arr: any[], target: any[]) => target.every((v) => arr.includes(v));
describe('migration v2', () => {
let esServer: kbnTestServer.TestElasticsearchUtils;
@ -72,16 +96,11 @@ describe('migration v2', () => {
await new Promise((resolve) => setTimeout(resolve, 5000));
const esClient: ElasticsearchClient = esServer.es.getClient();
const migratedIndexResponse = await esClient.count({
index: targetIndex,
});
const oldIndexResponse = await esClient.count({
index: '.kibana_7.14.0_001',
});
// Use a >= comparison since once Kibana has started it might create new
// documents like telemetry tasks
expect(migratedIndexResponse.body.count).toBeGreaterThanOrEqual(oldIndexResponse.body.count);
// assert that the docs from the original index have been migrated rather than comparing a doc count after startup
const originalDocs = await fetchDocuments(esClient, '.kibana_7.14.0_001');
const migratedDocs = await fetchDocuments(esClient, targetIndex);
expect(assertMigratedDocuments(migratedDocs, originalDocs));
});
it('fails with a descriptive message when a single document exceeds maxBatchSizeBytes', async () => {

View file

@ -162,4 +162,28 @@ describe('ui_settings 8.0.0 migrations', () => {
migrationVersion: {},
});
});
test('removes telemetry:optIn and xPackMonitoring:allowReport from ui_settings', () => {
const doc = {
type: 'config',
id: '8.0.0',
attributes: {
buildNum: 9007199254740991,
'telemetry:optIn': false,
'xPackMonitoring:allowReport': false,
},
references: [],
updated_at: '2020-06-09T20:18:20.349Z',
migrationVersion: {},
};
expect(migration(doc)).toEqual({
type: 'config',
id: '8.0.0',
attributes: {
buildNum: 9007199254740991,
},
references: [],
updated_at: '2020-06-09T20:18:20.349Z',
migrationVersion: {},
});
});
});

View file

@ -78,13 +78,16 @@ export const migrations = {
'8.0.0': (doc: SavedObjectUnsanitizedDoc<any>): SavedObjectSanitizedDoc<any> => ({
...doc,
...(doc.attributes && {
// owner: Team:Geo
attributes: Object.keys(doc.attributes).reduce(
(acc, key) =>
[
// owner: Team:Geo
'visualization:regionmap:showWarnings',
'visualization:tileMap:WMSdefaults',
'visualization:tileMap:maxPrecision',
// owner: Team:Core
'telemetry:optIn',
'xPackMonitoring:allowReport',
].includes(key)
? {
...acc,

View file

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

View file

@ -6,24 +6,6 @@
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
/**
* config options opt into telemetry
*/
export const CONFIG_TELEMETRY = 'telemetry:optIn';
/**
* config description for opting into telemetry
*/
export const getConfigTelemetryDesc = () => {
// Can't find where it's used but copying it over from the legacy code just in case...
return i18n.translate('telemetry.telemetryConfigDescription', {
defaultMessage:
'Help us improve the Elastic Stack by providing usage statistics for basic features. We will not share this data outside of Elastic.',
});
};
/**
* The amount of time, in milliseconds, to wait between reports when enabled.
* Currently 24 hours.

View file

@ -9,8 +9,6 @@
import { schema, TypeOf, Type } from '@kbn/config-schema';
import { getConfigPath } from '@kbn/utils';
import { PluginConfigDescriptor } from 'kibana/server';
import { TELEMETRY_ENDPOINT } from '../../common/constants';
import { deprecateEndpointConfigs } from './deprecations';
const clusterEnvSchema: [Type<'prod'>, Type<'staging'>] = [
schema.literal('prod'),
@ -36,34 +34,6 @@ const configSchema = schema.object({
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: TELEMETRY_ENDPOINT.MAIN_CHANNEL.STAGING,
}),
schema.string({
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: TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.STAGING,
}),
schema.string({
defaultValue: TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.PROD,
})
),
sendUsageFrom: schema.oneOf([schema.literal('server'), schema.literal('browser')], {
defaultValue: 'server',
}),
@ -81,5 +51,4 @@ export const config: PluginConfigDescriptor<TelemetryConfigType> = {
sendUsageFrom: true,
sendUsageTo: true,
},
deprecations: () => [deprecateEndpointConfigs],
};

View file

@ -1,197 +0,0 @@
/*
* 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 { configDeprecationsMock } from '../../../../core/server/mocks';
import { deprecateEndpointConfigs } from './deprecations';
import type { TelemetryConfigType } from './config';
import { TELEMETRY_ENDPOINT } from '../../common/constants';
describe('deprecateEndpointConfigs', () => {
const fromPath = 'telemetry';
const mockAddDeprecation = jest.fn();
const deprecationContext = configDeprecationsMock.createContext();
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,
deprecationContext
);
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,
deprecationContext
);
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,
deprecationContext
);
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,
deprecationContext
);
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,
deprecationContext
);
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, deprecationContext);
expect(mockAddDeprecation).toBeCalledTimes(1);
expect(mockAddDeprecation.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"configPath": "telemetry.url",
"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.",
"title": "Setting \\"telemetry.url\\" is deprecated",
},
]
`);
});
it('registers deprecation when "telemetry.optInStatusUrl" is set', () => {
const rawConfig = createMockRawConfig({
optInStatusUrl: 'random-endpoint',
});
deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation, deprecationContext);
expect(mockAddDeprecation).toBeCalledTimes(1);
expect(mockAddDeprecation.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"configPath": "telemetry.optInStatusUrl",
"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.",
"title": "Setting \\"telemetry.optInStatusUrl\\" is deprecated",
},
]
`);
});
});

View file

@ -1,68 +0,0 @@
/*
* 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({
configPath: fullConfigPath,
title: i18n.translate('telemetry.endpointConfigs.deprecationTitle', {
defaultMessage: 'Setting "{configPath}" is deprecated',
values: { configPath: fullConfigPath },
}),
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

@ -1,46 +0,0 @@
/*
* 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.
*/
/**
* Clean up any old, deprecated settings and determine if we should continue.
*
* This <em>will</em> update the latest telemetry setting if necessary.
*
* @param {Object} config The advanced settings config object.
* @return {Boolean} {@code true} if the banner should still be displayed. {@code false} if the banner should not be displayed.
*/
import { IUiSettingsClient, SavedObjectsClientContract } from 'kibana/server';
import { CONFIG_TELEMETRY } from '../../common/constants';
import { updateTelemetrySavedObject } from '../telemetry_repository';
const CONFIG_ALLOW_REPORT = 'xPackMonitoring:allowReport';
export async function handleOldSettings(
savedObjectsClient: SavedObjectsClientContract,
uiSettingsClient: IUiSettingsClient
) {
const oldTelemetrySetting = await uiSettingsClient.get(CONFIG_TELEMETRY);
const oldAllowReportSetting = await uiSettingsClient.get(CONFIG_ALLOW_REPORT);
let legacyOptInValue = null;
if (typeof oldTelemetrySetting === 'boolean') {
legacyOptInValue = oldTelemetrySetting;
} else if (
typeof oldAllowReportSetting === 'boolean' &&
uiSettingsClient.isOverridden(CONFIG_ALLOW_REPORT)
) {
legacyOptInValue = oldAllowReportSetting;
}
if (legacyOptInValue !== null) {
await updateTelemetrySavedObject(savedObjectsClient, {
enabled: legacyOptInValue,
});
}
}

View file

@ -1,9 +0,0 @@
/*
* 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 { handleOldSettings } from './handle_old_settings';

View file

@ -7,7 +7,7 @@
*/
import { URL } from 'url';
import { AsyncSubject, Observable } from 'rxjs';
import { Observable } from 'rxjs';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import {
TelemetryCollectionManagerPluginSetup,
@ -22,7 +22,6 @@ import {
SavedObjectsClient,
Plugin,
Logger,
UiSettingsServiceStart,
} from '../../../core/server';
import { registerRoutes } from './routes';
import { registerCollection } from './telemetry_collection';
@ -32,7 +31,6 @@ import {
} from './collectors';
import type { TelemetryConfigType } from './config';
import { FetcherTask } from './fetcher';
import { handleOldSettings } from './handle_old_settings';
import { getTelemetrySavedObject } from './telemetry_repository';
import { getTelemetryOptIn, getTelemetryChannelEndpoint } from '../common/telemetry_config';
@ -79,7 +77,6 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
/**
* @private Used to mark the completion of the old UI Settings migration
*/
private readonly oldUiSettingsHandled$ = new AsyncSubject();
private savedObjectsClient?: ISavedObjectsRepository;
constructor(initializerContext: PluginInitializerContext<TelemetryConfigType>) {
@ -126,19 +123,16 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
}
public start(core: CoreStart, { telemetryCollectionManager }: TelemetryPluginsDepsStart) {
const { savedObjects, uiSettings } = core;
const { savedObjects } = core;
const savedObjectsInternalRepository = savedObjects.createInternalRepository();
this.savedObjectsClient = savedObjectsInternalRepository;
// Not catching nor awaiting these promises because they should never reject
this.handleOldUiSettings(uiSettings);
this.startFetcherWhenOldSettingsAreHandled(core, telemetryCollectionManager);
this.startFetcher(core, telemetryCollectionManager);
return {
getIsOptedIn: async () => {
await this.oldUiSettingsHandled$.pipe(take(1)).toPromise(); // Wait for the old settings to be handled
const internalRepository = new SavedObjectsClient(savedObjectsInternalRepository);
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;
@ -155,24 +149,11 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
};
}
private async handleOldUiSettings(uiSettings: UiSettingsServiceStart) {
const savedObjectsClient = new SavedObjectsClient(this.savedObjectsClient!);
const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient);
try {
await handleOldSettings(savedObjectsClient, uiSettingsClient);
} catch (error) {
this.logger.warn('Unable to update legacy telemetry configs.');
}
// Set the mark in the AsyncSubject as complete so all the methods that require this method to be completed before working, can move on
this.oldUiSettingsHandled$.complete();
}
private async startFetcherWhenOldSettingsAreHandled(
private startFetcher(
core: CoreStart,
telemetryCollectionManager: TelemetryCollectionManagerPluginStart
) {
await this.oldUiSettingsHandled$.pipe(take(1)).toPromise(); // Wait for the old settings to be handled
// We start the fetcher having updated everything we need to using the config settings
this.fetcherTask.start(core, { telemetryCollectionManager });
}

View file

@ -42,7 +42,6 @@ export default async function ({ readConfigFile }) {
defaults: {
'accessibility:disableAnimations': true,
'dateFormat:tz': 'UTC',
'telemetry:optIn': false,
},
},
pageObjects: functionalConfig.get('pageObjects'),
@ -62,6 +61,7 @@ export default async function ({ readConfigFile }) {
...functionalConfig.get('kbnTestServer.serverArgs'),
// Required to load new platform plugins via `--plugin-path` flag.
'--env.name=development',
'--telemetry.optIn=false',
...examples.map(
(exampleDir) => `--plugin-path=${resolve(KIBANA_ROOT, 'examples', exampleDir)}`
),

View file

@ -4509,9 +4509,6 @@
"telemetry.callout.errorUnprivilegedUserDescription": "暗号化されていないクラスター統計を表示するアクセス権がありません。",
"telemetry.callout.errorUnprivilegedUserTitle": "クラスター統計の表示エラー",
"telemetry.clusterData": "クラスターデータ",
"telemetry.endpointConfigs.deprecationManualStep1": "Kibana構成から\"{configPath}\"を削除します。",
"telemetry.endpointConfigs.deprecationManualStep2": "使用状況をステージングエンドポイントに送信するには、「telemetry.sendUsageTo: staging」をKibana構成に追加します。",
"telemetry.endpointConfigs.deprecationMessage": "\"{configPath}\"は廃止予定にされました。「telemetry.sendUsageTo: staging」をKibana構成に設定し、使用状況をステージングエンドポイントに送信します。",
"telemetry.optInErrorToastText": "使用状況統計設定の設定中にエラーが発生しました。",
"telemetry.optInErrorToastTitle": "エラー",
"telemetry.optInNoticeSeenErrorTitle": "エラー",
@ -4524,7 +4521,6 @@
"telemetry.securityData": "Endpoint Security データ",
"telemetry.telemetryBannerDescription": "Elastic Stackの改善にご協力ください使用状況データの収集は現在無効です。使用状況データの収集を有効にすると、製品とサービスを管理して改善することができます。詳細については、{privacyStatementLink}をご覧ください。",
"telemetry.telemetryConfigAndLinkDescription": "使用状況データの収集を有効にすると、製品とサービスを管理して改善することができます。詳細については、{privacyStatementLink}をご覧ください。",
"telemetry.telemetryConfigDescription": "基本的な機能の利用状況に関する統計情報を提供して、Elastic Stack の改善にご協力ください。このデータは Elastic 社外と共有されません。",
"telemetry.telemetryOptedInDisableUsage": "ここで使用状況データを無効にする",
"telemetry.telemetryOptedInDismissMessage": "閉じる",
"telemetry.telemetryOptedInNoticeDescription": "使用状況データがどのように製品とサービスの管理と改善につながるのかに関する詳細については、{privacyStatementLink}をご覧ください。収集を停止するには、{disableLink}。",

View file

@ -4554,9 +4554,6 @@
"telemetry.callout.errorUnprivilegedUserDescription": "您无权查看未加密的集群统计信息。",
"telemetry.callout.errorUnprivilegedUserTitle": "显示集群统计信息时出错",
"telemetry.clusterData": "集群数据",
"telemetry.endpointConfigs.deprecationManualStep1": "从 Kibana 配置中移除“{configPath}”。",
"telemetry.endpointConfigs.deprecationManualStep2": "要将使用数据发送给暂存终端请将“telemetry.sendUsageTo: staging”添加到 Kibana 配置中。",
"telemetry.endpointConfigs.deprecationMessage": "“{configPath}”已弃用。在 Kibana 配置中设置“telemetry.sendUsageTo: staging”可将使用数据发送到暂存终端。",
"telemetry.optInErrorToastText": "尝试设置使用情况统计信息首选项时发生错误。",
"telemetry.optInErrorToastTitle": "错误",
"telemetry.optInNoticeSeenErrorTitle": "错误",
@ -4569,7 +4566,6 @@
"telemetry.securityData": "终端安全数据",
"telemetry.telemetryBannerDescription": "想帮助我们改进 Elastic Stack数据使用情况收集当前已禁用。启用使用情况数据收集可帮助我们管理并改善产品和服务。有关更多详情请参阅我们的{privacyStatementLink}。",
"telemetry.telemetryConfigAndLinkDescription": "启用使用情况数据收集可帮助我们管理并改善产品和服务。有关更多详情,请参阅我们的{privacyStatementLink}。",
"telemetry.telemetryConfigDescription": "通过提供基本功能的使用情况统计信息,来帮助我们改进 Elastic Stack。我们不会在 Elastic 之外共享此数据。",
"telemetry.telemetryOptedInDisableUsage": "请在此禁用使用情况数据",
"telemetry.telemetryOptedInDismissMessage": "关闭",
"telemetry.telemetryOptedInNoticeDescription": "要了解使用情况数据如何帮助我们管理和改善产品和服务,请参阅我们的{privacyStatementLink}。要停止收集,{disableLink}。",