diff --git a/docs/settings/telemetry-settings.asciidoc b/docs/settings/telemetry-settings.asciidoc index ae984cc6fd12b..74c85061cd713 100644 --- a/docs/settings/telemetry-settings.asciidoc +++ b/docs/settings/telemetry-settings.asciidoc @@ -9,7 +9,7 @@ Usage Collection (also known as Telemetry) is enabled by default. This allows us Refer to our https://www.elastic.co/legal/product-privacy-statement[Privacy Statement] to learn more. You can control whether this data is sent from the {kib} servers, or if it should be sent -from the user's browser, in case a firewall is blocking the connections from the server. Additionally, you can decide to completely disable this feature either in the config file or in {kib} via *Management > Kibana > Advanced Settings > Usage Data*. +from the user's browser, in case a firewall is blocking the connections from the server. Additionally, you can disable this feature either in *Stack Management > {kib} > Advanced Settings > Global Settings > Usage collection* or the config file with the following settings. [float] [[telemetry-general-settings]] diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts index 062729b71fc0f..dc748e48123ad 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts @@ -348,7 +348,7 @@ export interface ISavedObjectsRepository { * It will not create a nested structure like: * `{attributes: {stats: {api: {counter: 1}}}}` * - * When using incrementCounter for collecting usage data, you need to ensure + * When using incrementCounter you need to ensure * that usage collection happens on a best-effort basis and doesn't * negatively affect your plugin or users. See https://github.com/elastic/kibana/blob/main/src/plugins/usage_collection/README.mdx#tracking-interactions-with-incrementcounter) * diff --git a/src/plugins/telemetry/public/components/__snapshots__/opt_in_banner.test.tsx.snap b/src/plugins/telemetry/public/components/__snapshots__/opt_in_banner.test.tsx.snap deleted file mode 100644 index e243b83527eb6..0000000000000 --- a/src/plugins/telemetry/public/components/__snapshots__/opt_in_banner.test.tsx.snap +++ /dev/null @@ -1,62 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`OptInDetailsComponent renders as expected 1`] = ` - - } -> - - - - - - - - - - - - - - - -`; diff --git a/src/plugins/telemetry/public/components/__snapshots__/opt_in_message.test.tsx.snap b/src/plugins/telemetry/public/components/__snapshots__/opt_in_message.test.tsx.snap deleted file mode 100644 index ed0fbaa6407f0..0000000000000 --- a/src/plugins/telemetry/public/components/__snapshots__/opt_in_message.test.tsx.snap +++ /dev/null @@ -1,25 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`OptInMessage renders as expected 1`] = ` - - - - , - } - } - /> - -`; diff --git a/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap b/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap deleted file mode 100644 index 122764b27bd29..0000000000000 --- a/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap +++ /dev/null @@ -1,52 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`OptInDetailsComponent renders as expected 1`] = ` - - - - , - "privacyStatementLink": - - , - } - } - /> - - - - - -`; diff --git a/src/plugins/telemetry/public/components/opt_in_banner.test.tsx b/src/plugins/telemetry/public/components/opt_in_banner.test.tsx deleted file mode 100644 index 4ec057d8d268e..0000000000000 --- a/src/plugins/telemetry/public/components/opt_in_banner.test.tsx +++ /dev/null @@ -1,65 +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 React from 'react'; -import { EuiButton } from '@elastic/eui'; -import { shallowWithIntl } from '@kbn/test-jest-helpers'; -import { OptInBanner } from './opt_in_banner'; -import { mockTelemetryConstants } from '../mocks'; - -describe('OptInDetailsComponent', () => { - const telemetryConstants = mockTelemetryConstants(); - - it('renders as expected', () => { - expect( - shallowWithIntl( - {}} telemetryConstants={telemetryConstants} /> - ) - ).toMatchSnapshot(); - }); - - it('fires the "onChangeOptInClick" prop with true when a enable is clicked', () => { - const onClick = jest.fn(); - const component = shallowWithIntl( - - ); - - const enableButton = component.findWhere((n) => { - const props = n.props(); - return n.type() === EuiButton && props['data-test-subj'] === 'enable'; - }); - - if (!enableButton) { - throw new Error(`Couldn't find any opt in enable button.`); - } - - enableButton.simulate('click'); - expect(onClick).toHaveBeenCalled(); - expect(onClick).toBeCalledWith(true); - }); - - it('fires the "onChangeOptInClick" with false when a disable is clicked', () => { - const onClick = jest.fn(); - const component = shallowWithIntl( - - ); - - const disableButton = component.findWhere((n) => { - const props = n.props(); - return n.type() === EuiButton && props['data-test-subj'] === 'disable'; - }); - - if (!disableButton) { - throw new Error(`Couldn't find any opt in disable button.`); - } - - disableButton.simulate('click'); - expect(onClick).toHaveBeenCalled(); - expect(onClick).toBeCalledWith(false); - }); -}); diff --git a/src/plugins/telemetry/public/components/opt_in_banner.tsx b/src/plugins/telemetry/public/components/opt_in_banner.tsx deleted file mode 100644 index de45b4419a5ee..0000000000000 --- a/src/plugins/telemetry/public/components/opt_in_banner.tsx +++ /dev/null @@ -1,54 +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 * as React from 'react'; -import { EuiButton, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { OptInMessage } from './opt_in_message'; -import { TelemetryConstants } from '..'; - -interface Props { - onChangeOptInClick: (isOptIn: boolean) => void; - telemetryConstants: TelemetryConstants; -} - -export class OptInBanner extends React.PureComponent { - render() { - const { onChangeOptInClick, telemetryConstants } = this.props; - const title = ( - - ); - return ( - - - - - - onChangeOptInClick(true)}> - - - - - onChangeOptInClick(false)}> - - - - - - ); - } -} diff --git a/src/plugins/telemetry/public/components/opt_in_message.test.tsx b/src/plugins/telemetry/public/components/opt_in_message.test.tsx index ddba6cc517c2b..ddbf4b8473454 100644 --- a/src/plugins/telemetry/public/components/opt_in_message.test.tsx +++ b/src/plugins/telemetry/public/components/opt_in_message.test.tsx @@ -7,16 +7,97 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test-jest-helpers'; +import type { ReactWrapper } from 'enzyme'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { httpServiceMock } from '@kbn/core-http-browser-mocks'; +import { mockTelemetryConstants, mockTelemetryService } from '../mocks'; import { OptInMessage } from './opt_in_message'; -import { mockTelemetryConstants } from '../mocks'; - -const telemetryConstants = mockTelemetryConstants(); describe('OptInMessage', () => { - it('renders as expected', () => { - expect( - shallowWithIntl() - ).toMatchSnapshot(); + const addBasePath = httpServiceMock.createBasePath().prepend; + const telemetryConstants = mockTelemetryConstants(); + + describe('when opted-in', () => { + const telemetryService = mockTelemetryService({ config: { optIn: true } }); + + let dom: ReactWrapper; + + beforeAll(() => { + dom = mountWithIntl( + + ); + }); + + afterAll(() => { + dom.unmount(); + }); + + it('claims that telemetry is enabled', () => { + expect(dom.text()).toContain('Usage collection (also known as Telemetry) is enabled.'); + }); + + it('offers the link to disable it', () => { + expect(dom.text()).toContain('Disable usage collection.'); + }); + }); + + describe('when opted-out', () => { + const telemetryService = mockTelemetryService({ config: { optIn: false } }); + + let dom: ReactWrapper; + + beforeAll(() => { + dom = mountWithIntl( + + ); + }); + + afterAll(() => { + dom.unmount(); + }); + + it('claims that telemetry is disabled', () => { + expect(dom.text()).toContain('Usage collection (also known as Telemetry) is disabled.'); + }); + + it('offers the link to enable it', () => { + expect(dom.text()).toContain('Enable usage collection.'); + }); + }); + + describe('when null', () => { + const telemetryService = mockTelemetryService({ config: { optIn: null } }); + + let dom: ReactWrapper; + + beforeAll(() => { + dom = mountWithIntl( + + ); + }); + + afterAll(() => { + dom.unmount(); + }); + + it('claims that telemetry is disabled', () => { + expect(dom.text()).toContain('Usage collection (also known as Telemetry) is disabled.'); + }); + + it('offers the link to enable it', () => { + expect(dom.text()).toContain('Enable usage collection.'); + }); }); }); diff --git a/src/plugins/telemetry/public/components/opt_in_message.tsx b/src/plugins/telemetry/public/components/opt_in_message.tsx index 32e58e6a71ca2..e9e167c888e4b 100644 --- a/src/plugins/telemetry/public/components/opt_in_message.tsx +++ b/src/plugins/telemetry/public/components/opt_in_message.tsx @@ -9,35 +9,87 @@ import * as React from 'react'; import { EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { TelemetryConstants } from '..'; +import type { IBasePath } from '@kbn/core-http-browser'; +import type { TelemetryService } from '../services'; +import type { TelemetryConstants } from '..'; -interface Props { +export interface OptInMessageProps { telemetryConstants: TelemetryConstants; + telemetryService: TelemetryService; + addBasePath: IBasePath['prepend']; + onClick?: () => unknown; } -export class OptInMessage extends React.PureComponent { - render() { - return ( - - - - - ), - }} - /> - - ); +export const OptInMessage: React.FC = ({ + addBasePath, + telemetryService, + telemetryConstants, + onClick, +}) => { + return ( + + + {telemetryService.isOptedIn ? ( + + ) : ( + + )} + + ), + privacyStatementLink: ( + /* eslint-disable-next-line @elastic/eui/href-or-on-click */ + + + + ), + }} + />{' '} + {renderTelemetryEnabledOrDisabledText(telemetryService, addBasePath, onClick)} + + ); +}; + +function renderTelemetryEnabledOrDisabledText( + telemetryService: TelemetryService, + addBasePath: (url: string) => string, + onClick?: () => unknown +) { + if (!telemetryService.userCanChangeSettings || !telemetryService.getCanChangeOptInStatus()) { + return null; } + + const isOptedIn = telemetryService.getIsOptedIn(); + const actionMessage = isOptedIn ? ( + + ) : ( + + ); + + return ( + /* eslint-disable-next-line @elastic/eui/href-or-on-click */ + + {actionMessage} + + ); } diff --git a/src/plugins/telemetry/public/components/opted_in_notice_banner.test.tsx b/src/plugins/telemetry/public/components/opt_in_status_notice_banner.test.tsx similarity index 60% rename from src/plugins/telemetry/public/components/opted_in_notice_banner.test.tsx rename to src/plugins/telemetry/public/components/opt_in_status_notice_banner.test.tsx index 3e60534ce410a..7017d2795d587 100644 --- a/src/plugins/telemetry/public/components/opted_in_notice_banner.test.tsx +++ b/src/plugins/telemetry/public/components/opt_in_status_notice_banner.test.tsx @@ -8,34 +8,47 @@ import React from 'react'; import { EuiButton } from '@elastic/eui'; -import { shallowWithIntl } from '@kbn/test-jest-helpers'; -import { OptedInNoticeBanner } from './opted_in_notice_banner'; import { httpServiceMock } from '@kbn/core/public/mocks'; -import { mockTelemetryConstants } from '../mocks'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; +import { mockTelemetryConstants, mockTelemetryService } from '../mocks'; +import { OptInStatusNoticeBanner } from './opt_in_status_notice_banner'; +import { OptInMessage } from './opt_in_message'; const mockHttp = httpServiceMock.createStartContract(); const telemetryConstants = mockTelemetryConstants(); +const telemetryService = mockTelemetryService(); -describe('OptInDetailsComponent', () => { +describe('OptInStatusNoticeBanner', () => { it('renders as expected', () => { + const onSeenBanner = () => {}; + const dom = shallowWithIntl( + + ); expect( - shallowWithIntl( - {}} - http={mockHttp} + dom.containsMatchingElement( + ) - ).toMatchSnapshot(); + ).toBe(true); }); it('fires the "onSeenBanner" prop when a link is clicked', () => { const onLinkClick = jest.fn(); const component = shallowWithIntl( - ); diff --git a/src/plugins/telemetry/public/components/opt_in_status_notice_banner.tsx b/src/plugins/telemetry/public/components/opt_in_status_notice_banner.tsx new file mode 100644 index 0000000000000..3cc2172370447 --- /dev/null +++ b/src/plugins/telemetry/public/components/opt_in_status_notice_banner.tsx @@ -0,0 +1,53 @@ +/* + * 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. + */ + +/* eslint @elastic/eui/href-or-on-click:0 */ + +import * as React from 'react'; +import { EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import { HttpSetup } from '@kbn/core/public'; +import { OptInMessage } from './opt_in_message'; +import { TelemetryService } from '../services'; +import { TelemetryConstants } from '..'; + +interface Props { + http: HttpSetup; + onSeenBanner: () => unknown; + telemetryConstants: TelemetryConstants; + telemetryService: TelemetryService; +} + +export const OptInStatusNoticeBanner: React.FC = ({ + onSeenBanner, + http, + telemetryConstants, + telemetryService, +}) => { + const addBasePath = http.basePath.prepend; + + const bannerTitle = i18n.translate('telemetry.telemetryOptedInNoticeTitle', { + defaultMessage: 'Help us improve the Elastic Stack', + }); + + return ( + + + + + + + + ); +}; diff --git a/src/plugins/telemetry/public/components/opted_in_notice_banner.tsx b/src/plugins/telemetry/public/components/opted_in_notice_banner.tsx deleted file mode 100644 index da9db9eec7321..0000000000000 --- a/src/plugins/telemetry/public/components/opted_in_notice_banner.tsx +++ /dev/null @@ -1,73 +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. - */ - -/* eslint @elastic/eui/href-or-on-click:0 */ - -import * as React from 'react'; -import { EuiButton, EuiLink, EuiCallOut, EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { i18n } from '@kbn/i18n'; -import { HttpSetup } from '@kbn/core/public'; -import { PATH_TO_ADVANCED_SETTINGS } from '../../common/constants'; -import { TelemetryConstants } from '..'; - -interface Props { - http: HttpSetup; - onSeenBanner: () => unknown; - telemetryConstants: TelemetryConstants; -} - -export class OptedInNoticeBanner extends React.PureComponent { - render() { - const { onSeenBanner, http, telemetryConstants } = this.props; - const basePath = http.basePath.get(); - - const bannerTitle = i18n.translate('telemetry.telemetryOptedInNoticeTitle', { - defaultMessage: 'Help us improve the Elastic Stack', - }); - - return ( - - - - - ), - disableLink: ( - - - - ), - }} - /> - - - - - - ); - } -} diff --git a/src/plugins/telemetry/public/components/welcome_telemetry_notice.tsx b/src/plugins/telemetry/public/components/welcome_telemetry_notice.tsx index 3923016637cfb..e6b6dd411637b 100644 --- a/src/plugins/telemetry/public/components/welcome_telemetry_notice.tsx +++ b/src/plugins/telemetry/public/components/welcome_telemetry_notice.tsx @@ -7,81 +7,16 @@ */ import React from 'react'; -import { EuiLink, EuiSpacer, EuiTextColor } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import type { TelemetryConstants } from '../plugin'; -import type { TelemetryService } from '../services'; +import { EuiSpacer, EuiTextColor } from '@elastic/eui'; +import { OptInMessage, type OptInMessageProps } from './opt_in_message'; -interface Props { - telemetryService: TelemetryService; - addBasePath: (url: string) => string; - telemetryConstants: TelemetryConstants; -} - -export const WelcomeTelemetryNotice: React.FC = ({ - telemetryService, - addBasePath, - telemetryConstants, -}: Props) => { +export const WelcomeTelemetryNotice: React.FC = (props) => { return ( <> - - - - - {renderTelemetryEnabledOrDisabledText(telemetryService, addBasePath)} + ); }; - -function renderTelemetryEnabledOrDisabledText( - telemetryService: TelemetryService, - addBasePath: (url: string) => string -) { - if (!telemetryService.userCanChangeSettings || !telemetryService.getCanChangeOptInStatus()) { - return null; - } - - const isOptedIn = telemetryService.getIsOptedIn(); - - if (isOptedIn) { - return ( - <> - - - - - - ); - } else { - return ( - <> - - - - - - ); - } -} diff --git a/src/plugins/telemetry/public/mocks.ts b/src/plugins/telemetry/public/mocks.ts index 297563460e888..afe8f7037dca5 100644 --- a/src/plugins/telemetry/public/mocks.ts +++ b/src/plugins/telemetry/public/mocks.ts @@ -12,10 +12,9 @@ import { notificationServiceMock, themeServiceMock, } from '@kbn/core/public/mocks'; -import { TelemetryService } from './services/telemetry_service'; -import { TelemetryNotifications } from './services/telemetry_notifications/telemetry_notifications'; -import { TelemetryPluginStart, TelemetryPluginSetup, TelemetryPluginConfig } from './plugin'; -import { TelemetryConstants } from '.'; +import type { TelemetryConstants } from '.'; +import type { TelemetryPluginStart, TelemetryPluginSetup, TelemetryPluginConfig } from './plugin'; +import { TelemetryService, TelemetryNotifications } from './services'; // The following is to be able to access private methods /* eslint-disable dot-notation */ @@ -102,12 +101,13 @@ function createSetupContract(): Setup { function createStartContract(): Start { const telemetryService = mockTelemetryService(); - const telemetryNotifications = mockTelemetryNotifications({ telemetryService }); const telemetryConstants = mockTelemetryConstants(); const startContract: Start = { telemetryService, - telemetryNotifications, + telemetryNotifications: { + setOptedInNoticeSeen: jest.fn(), + }, telemetryConstants, }; diff --git a/src/plugins/telemetry/public/plugin.ts b/src/plugins/telemetry/public/plugin.ts index 15b581c498366..9411212abebe9 100644 --- a/src/plugins/telemetry/public/plugin.ts +++ b/src/plugins/telemetry/public/plugin.ts @@ -171,7 +171,7 @@ export class TelemetryPlugin implements Plugin { if (this.telemetryService?.userCanChangeSettings) { - this.telemetryNotifications?.setOptedInNoticeSeen(); + this.telemetryNotifications?.setOptInStatusNoticeSeen(); } }); @@ -230,14 +230,13 @@ export class TelemetryPlugin implements Plugin telemetryNotifications.setOptedInNoticeSeen(), + setOptedInNoticeSeen: () => telemetryNotifications.setOptInStatusNoticeSeen(), }, telemetryConstants, }; @@ -300,19 +299,9 @@ export class TelemetryPlugin implements Plugin { - it('adds a banner to banners with priority of 10000', () => { - const bannerID = 'brucer-wayne'; - const overlays = overlayServiceMock.createStartContract(); - const telemetryConstants = mockTelemetryConstants(); - overlays.banners.add.mockReturnValue(bannerID); - - const returnedBannerId = renderOptInBanner({ - setOptIn: jest.fn(), - overlays, - telemetryConstants, - }); - - expect(overlays.banners.add).toBeCalledTimes(1); - - expect(returnedBannerId).toBe(bannerID); - const bannerConfig = overlays.banners.add.mock.calls[0]; - - expect(bannerConfig[0]).not.toBe(undefined); - expect(bannerConfig[1]).toBe(10000); - }); -}); diff --git a/src/plugins/telemetry/public/services/telemetry_notifications/render_opt_in_banner.tsx b/src/plugins/telemetry/public/services/telemetry_notifications/render_opt_in_banner.tsx deleted file mode 100644 index e2aca2fafd2f9..0000000000000 --- a/src/plugins/telemetry/public/services/telemetry_notifications/render_opt_in_banner.tsx +++ /dev/null @@ -1,34 +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 React from 'react'; -import type { OverlayStart } from '@kbn/core/public'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; -import { withSuspense } from '@kbn/shared-ux-utility'; -import type { TelemetryConstants } from '../..'; - -interface RenderBannerConfig { - overlays: OverlayStart; - setOptIn: (isOptIn: boolean) => Promise; - telemetryConstants: TelemetryConstants; -} - -export function renderOptInBanner({ setOptIn, overlays, telemetryConstants }: RenderBannerConfig) { - const OptInBannerLazy = withSuspense( - React.lazy(() => - import('../../components/opt_in_banner').then(({ OptInBanner }) => ({ default: OptInBanner })) - ) - ); - - const mount = toMountPoint( - - ); - const bannerId = overlays.banners.add(mount, 10000); - - return bannerId; -} diff --git a/src/plugins/telemetry/public/services/telemetry_notifications/render_opted_in_notice_banner.test.ts b/src/plugins/telemetry/public/services/telemetry_notifications/render_opt_in_status_notice_banner.test.ts similarity index 77% rename from src/plugins/telemetry/public/services/telemetry_notifications/render_opted_in_notice_banner.test.ts rename to src/plugins/telemetry/public/services/telemetry_notifications/render_opt_in_status_notice_banner.test.ts index 3a631b02127bb..74e5d8f488495 100644 --- a/src/plugins/telemetry/public/services/telemetry_notifications/render_opted_in_notice_banner.test.ts +++ b/src/plugins/telemetry/public/services/telemetry_notifications/render_opt_in_status_notice_banner.test.ts @@ -6,25 +6,27 @@ * Side Public License, v 1. */ -import { renderOptedInNoticeBanner } from './render_opted_in_notice_banner'; +import { renderOptInStatusNoticeBanner } from './render_opt_in_status_notice_banner'; import { overlayServiceMock, httpServiceMock, themeServiceMock } from '@kbn/core/public/mocks'; -import { mockTelemetryConstants } from '../../mocks'; +import { mockTelemetryConstants, mockTelemetryService } from '../../mocks'; -describe('renderOptedInNoticeBanner', () => { +describe('renderOptInStatusNoticeBanner', () => { it('adds a banner to banners with priority of 10000', () => { const bannerID = 'brucer-wayne'; const overlays = overlayServiceMock.createStartContract(); const mockHttp = httpServiceMock.createStartContract(); const theme = themeServiceMock.createStartContract(); const telemetryConstants = mockTelemetryConstants(); + const telemetryService = mockTelemetryService(); overlays.banners.add.mockReturnValue(bannerID); - const returnedBannerId = renderOptedInNoticeBanner({ + const returnedBannerId = renderOptInStatusNoticeBanner({ http: mockHttp, onSeen: jest.fn(), overlays, theme, telemetryConstants, + telemetryService, }); expect(overlays.banners.add).toBeCalledTimes(1); diff --git a/src/plugins/telemetry/public/services/telemetry_notifications/render_opted_in_notice_banner.tsx b/src/plugins/telemetry/public/services/telemetry_notifications/render_opt_in_status_notice_banner.tsx similarity index 77% rename from src/plugins/telemetry/public/services/telemetry_notifications/render_opted_in_notice_banner.tsx rename to src/plugins/telemetry/public/services/telemetry_notifications/render_opt_in_status_notice_banner.tsx index b6693f8963587..7b48e0646d4e8 100644 --- a/src/plugins/telemetry/public/services/telemetry_notifications/render_opted_in_notice_banner.tsx +++ b/src/plugins/telemetry/public/services/telemetry_notifications/render_opt_in_status_notice_banner.tsx @@ -10,6 +10,7 @@ import React from 'react'; import type { HttpStart, OverlayStart, ThemeServiceStart } from '@kbn/core/public'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { withSuspense } from '@kbn/shared-ux-utility'; +import { TelemetryService } from '..'; import type { TelemetryConstants } from '../..'; interface RenderBannerConfig { @@ -18,31 +19,37 @@ interface RenderBannerConfig { theme: ThemeServiceStart; onSeen: () => void; telemetryConstants: TelemetryConstants; + telemetryService: TelemetryService; } -export function renderOptedInNoticeBanner({ +export function renderOptInStatusNoticeBanner({ onSeen, overlays, http, theme, telemetryConstants, + telemetryService, }: RenderBannerConfig) { const OptedInNoticeBannerLazy = withSuspense( React.lazy(() => - import('../../components/opted_in_notice_banner').then(({ OptedInNoticeBanner }) => ({ - default: OptedInNoticeBanner, - })) + import('../../components/opt_in_status_notice_banner').then( + ({ OptInStatusNoticeBanner }) => ({ + default: OptInStatusNoticeBanner, + }) + ) ) ); + const mount = toMountPoint( , { theme$: theme.theme$ } ); - const bannerId = overlays.banners.add(mount, 10000); + const bannerId = overlays.banners.add(mount, 10000); return bannerId; } diff --git a/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.test.ts b/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.test.ts index dfbc23281c2ce..4515e5757cb0f 100644 --- a/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.test.ts +++ b/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.test.ts @@ -9,24 +9,6 @@ /* eslint-disable dot-notation */ import { mockTelemetryNotifications, mockTelemetryService } from '../../mocks'; -describe('onSetOptInClick', () => { - it('sets setting successfully and removes banner', async () => { - const optIn = true; - const bannerId = 'bruce-banner'; - - const telemetryService = mockTelemetryService(); - telemetryService.setOptIn = jest.fn(); - const telemetryNotifications = mockTelemetryNotifications({ telemetryService }); - telemetryNotifications['optInBannerId'] = bannerId; - - await telemetryNotifications['onSetOptInClick'](optIn); - expect(telemetryNotifications['overlays'].banners.remove).toBeCalledTimes(1); - expect(telemetryNotifications['overlays'].banners.remove).toBeCalledWith(bannerId); - expect(telemetryService.setOptIn).toBeCalledTimes(1); - expect(telemetryService.setOptIn).toBeCalledWith(optIn); - }); -}); - describe('setOptedInNoticeSeen', () => { it('sets setting successfully and removes banner', async () => { const bannerId = 'bruce-banner'; @@ -34,8 +16,8 @@ describe('setOptedInNoticeSeen', () => { const telemetryService = mockTelemetryService(); telemetryService.setUserHasSeenNotice = jest.fn(); const telemetryNotifications = mockTelemetryNotifications({ telemetryService }); - telemetryNotifications['optedInNoticeBannerId'] = bannerId; - await telemetryNotifications.setOptedInNoticeSeen(); + telemetryNotifications['optInStatusNoticeBannerId'] = bannerId; + await telemetryNotifications.setOptInStatusNoticeSeen(); expect(telemetryNotifications['overlays'].banners.remove).toBeCalledTimes(1); expect(telemetryNotifications['overlays'].banners.remove).toBeCalledWith(bannerId); @@ -44,25 +26,38 @@ describe('setOptedInNoticeSeen', () => { }); describe('shouldShowOptedInNoticeBanner', () => { - it("should return true because a banner hasn't been shown, the notice hasn't been seen and the user has privileges to edit saved objects", () => { + describe(`when the banner isn't visible yet`, () => { const telemetryService = mockTelemetryService(); - telemetryService.getUserShouldSeeOptInNotice = jest.fn().mockReturnValue(true); + const getUserShouldSeeOptInNotice = jest.fn(); + telemetryService.getUserShouldSeeOptInNotice = getUserShouldSeeOptInNotice; const telemetryNotifications = mockTelemetryNotifications({ telemetryService }); - expect(telemetryNotifications.shouldShowOptedInNoticeBanner()).toBe(true); - }); - it('should return false because the banner is already on screen', () => { - const telemetryService = mockTelemetryService(); - telemetryService.getUserShouldSeeOptInNotice = jest.fn().mockReturnValue(true); - const telemetryNotifications = mockTelemetryNotifications({ telemetryService }); - telemetryNotifications['optedInNoticeBannerId'] = 'bruce-banner'; - expect(telemetryNotifications.shouldShowOptedInNoticeBanner()).toBe(false); + it('should return true when `telemetryService.getUserShouldSeeOptInNotice returns true', () => { + getUserShouldSeeOptInNotice.mockReturnValue(true); + expect(telemetryNotifications.shouldShowOptInStatusNoticeBanner()).toBe(true); + }); + + it('should return false when `telemetryService.getUserShouldSeeOptInNotice returns false', () => { + getUserShouldSeeOptInNotice.mockReturnValue(false); + expect(telemetryNotifications.shouldShowOptInStatusNoticeBanner()).toBe(false); + }); }); - it("should return false because the banner has already been seen or the user doesn't have privileges to change saved objects", () => { + describe(`when the banner is already visible`, () => { const telemetryService = mockTelemetryService(); - telemetryService.getUserShouldSeeOptInNotice = jest.fn().mockReturnValue(false); + const getUserShouldSeeOptInNotice = jest.fn(); + telemetryService.getUserShouldSeeOptInNotice = getUserShouldSeeOptInNotice; const telemetryNotifications = mockTelemetryNotifications({ telemetryService }); - expect(telemetryNotifications.shouldShowOptedInNoticeBanner()).toBe(false); + telemetryNotifications['optInStatusNoticeBannerId'] = 'bruce-banner'; + + it('should return false when `telemetryService.getUserShouldSeeOptInNotice` returns true', () => { + getUserShouldSeeOptInNotice.mockReturnValue(true); + expect(telemetryNotifications.shouldShowOptInStatusNoticeBanner()).toBe(false); + }); + + it('should return false when `telemetryService.getUserShouldSeeOptInNotice returns false', () => { + getUserShouldSeeOptInNotice.mockReturnValue(false); + expect(telemetryNotifications.shouldShowOptInStatusNoticeBanner()).toBe(false); + }); }); }); diff --git a/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts b/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts index 2a3daf14f8414..9259735170bf4 100644 --- a/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts +++ b/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts @@ -6,11 +6,10 @@ * Side Public License, v 1. */ -import { HttpStart, OverlayStart, ThemeServiceStart } from '@kbn/core/public'; -import { renderOptedInNoticeBanner } from './render_opted_in_notice_banner'; -import { renderOptInBanner } from './render_opt_in_banner'; -import { TelemetryService } from '../telemetry_service'; -import { TelemetryConstants } from '../..'; +import type { HttpStart, OverlayStart, ThemeServiceStart } from '@kbn/core/public'; +import type { TelemetryService } from '../telemetry_service'; +import type { TelemetryConstants } from '../..'; +import { renderOptInStatusNoticeBanner } from './render_opt_in_status_notice_banner'; interface TelemetryNotificationsConstructor { http: HttpStart; @@ -29,8 +28,7 @@ export class TelemetryNotifications { private readonly theme: ThemeServiceStart; private readonly telemetryConstants: TelemetryConstants; private readonly telemetryService: TelemetryService; - private optedInNoticeBannerId?: string; - private optInBannerId?: string; + private optInStatusNoticeBannerId?: string; constructor({ http, @@ -49,70 +47,35 @@ export class TelemetryNotifications { /** * Should the opted-in banner be shown to the user? */ - public shouldShowOptedInNoticeBanner = (): boolean => { + public shouldShowOptInStatusNoticeBanner = (): boolean => { const userShouldSeeOptInNotice = this.telemetryService.getUserShouldSeeOptInNotice(); - const bannerOnScreen = typeof this.optedInNoticeBannerId !== 'undefined'; + const bannerOnScreen = typeof this.optInStatusNoticeBannerId !== '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({ + public renderOptInStatusNoticeBanner = (): void => { + const bannerId = renderOptInStatusNoticeBanner({ http: this.http, - onSeen: this.setOptedInNoticeSeen, + onSeen: this.setOptInStatusNoticeSeen, overlays: this.overlays, theme: this.theme, telemetryConstants: this.telemetryConstants, + telemetryService: this.telemetryService, }); - this.optedInNoticeBannerId = bannerId; - }; - - /** - * Should the banner to opt-in be shown to the user? - */ - public shouldShowOptInBanner = (): boolean => { - // Using `config.optIn` instead of the getter `getIsOptedIn()` because the latter only returns boolean, and we want to compare it against `null`. - const isOptedIn = this.telemetryService.config.optIn; - 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, - overlays: this.overlays, - telemetryConstants: this.telemetryConstants, - }); - - 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); - this.optInBannerId = undefined; - } - - await this.telemetryService.setOptIn(isOptIn); + this.optInStatusNoticeBannerId = bannerId; }; /** * Clears the banner and stores the user's dismissal of the banner. */ - public setOptedInNoticeSeen = async (): Promise => { - if (this.optedInNoticeBannerId) { - this.overlays.banners.remove(this.optedInNoticeBannerId); - this.optedInNoticeBannerId = undefined; + public setOptInStatusNoticeSeen = async (): Promise => { + if (this.optInStatusNoticeBannerId) { + this.overlays.banners.remove(this.optInStatusNoticeBannerId); + this.optInStatusNoticeBannerId = undefined; } await this.telemetryService.setUserHasSeenNotice(); diff --git a/src/plugins/telemetry/public/services/telemetry_service.test.ts b/src/plugins/telemetry/public/services/telemetry_service.test.ts index 03564f5b6f632..a43df3d2bd654 100644 --- a/src/plugins/telemetry/public/services/telemetry_service.test.ts +++ b/src/plugins/telemetry/public/services/telemetry_service.test.ts @@ -232,6 +232,19 @@ describe('TelemetryService', () => { expect(telemetryService.getUserShouldSeeOptInNotice()).toBe(false); }); + it('should return true when optIn: null even when previously seen', () => { + const telemetryService = mockTelemetryService({ + config: { + userCanChangeSettings: true, + telemetryNotifyUserAboutOptInDefault: false, + optIn: null, + }, + }); + expect(telemetryService.config.userCanChangeSettings).toBe(true); + expect(telemetryService.userCanChangeSettings).toBe(true); + expect(telemetryService.getUserShouldSeeOptInNotice()).toBe(true); + }); + it('returns whether the user can update the telemetry config (has SavedObjects access)', () => { const telemetryService = mockTelemetryService({ config: { userCanChangeSettings: undefined }, diff --git a/src/plugins/telemetry/public/services/telemetry_service.ts b/src/plugins/telemetry/public/services/telemetry_service.ts index e7b3e69ab9e83..58e60d0db1f30 100644 --- a/src/plugins/telemetry/public/services/telemetry_service.ts +++ b/src/plugins/telemetry/public/services/telemetry_service.ts @@ -7,8 +7,8 @@ */ import { i18n } from '@kbn/i18n'; -import { CoreSetup, CoreStart } from '@kbn/core/public'; -import { TelemetryPluginConfig } from '../plugin'; +import type { CoreSetup, CoreStart } from '@kbn/core/public'; +import type { TelemetryPluginConfig } from '../plugin'; import { getTelemetryChannelEndpoint } from '../../common/telemetry_config/get_telemetry_channel_endpoint'; import type { UnencryptedTelemetryPayload, @@ -110,15 +110,19 @@ export class TelemetryService { }; /** - * Returns if an user should be shown the notice about Opt-In/Out telemetry. - * The decision is made based on whether any user has already dismissed the message or - * the user can't actually change the settings (in which case, there's no point on bothering them) + * Returns whether a user should be shown the notice about Opt-In/Out telemetry. + * The decision is made based on: + * 1. The config hidePrivacyStatement is unset + * 2. The user has enough privileges to change the settings + * 3. At least one of the following: + * * It is opted-in, and the user has already been notified at any given point in the deployment's life. + * * It is opted-out, and the user has been notified for this version (excluding patch updates) */ public getUserShouldSeeOptInNotice(): boolean { return ( (!this.config.hidePrivacyStatement && - this.config.telemetryNotifyUserAboutOptInDefault && - this.config.userCanChangeSettings) ?? + this.config.userCanChangeSettings && + (this.config.telemetryNotifyUserAboutOptInDefault || this.config.optIn === null)) ?? false ); } diff --git a/src/plugins/telemetry/server/fetcher.ts b/src/plugins/telemetry/server/fetcher.ts index 65f367c095998..66f75f8582ec3 100644 --- a/src/plugins/telemetry/server/fetcher.ts +++ b/src/plugins/telemetry/server/fetcher.ts @@ -190,7 +190,7 @@ export class FetcherTask { } catch (err) { await this.updateReportFailure(telemetryConfig); - this.logger.warn(`Error sending telemetry usage data. (${err})`); + this.logger.warn(`Error sending usage to Elastic. (${err})`); } } diff --git a/src/plugins/telemetry/server/plugin.ts b/src/plugins/telemetry/server/plugin.ts index 0660e69c7b6cb..5255e77587263 100644 --- a/src/plugins/telemetry/server/plugin.ts +++ b/src/plugins/telemetry/server/plugin.ts @@ -84,7 +84,7 @@ export interface TelemetryPluginSetup { */ export interface TelemetryPluginStart { /** - * Resolves `true` if the user has opted into send Elastic usage data. + * Resolves `true` if sending usage to Elastic is enabled. * 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). * diff --git a/src/plugins/telemetry/tsconfig.json b/src/plugins/telemetry/tsconfig.json index fd016d03f72c0..7da7e89bae02f 100644 --- a/src/plugins/telemetry/tsconfig.json +++ b/src/plugins/telemetry/tsconfig.json @@ -32,6 +32,8 @@ "@kbn/core-saved-objects-server", "@kbn/core-saved-objects-api-server", "@kbn/std", + "@kbn/core-http-browser-mocks", + "@kbn/core-http-browser", ], "exclude": [ "target/**/*", diff --git a/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap b/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap index 322a698e9bdaf..0babaac94cb48 100644 --- a/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap +++ b/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap @@ -12,7 +12,7 @@ exports[`TelemetryManagementSectionComponent renders as expected 1`] = `

@@ -57,13 +57,13 @@ exports[`TelemetryManagementSectionComponent renders as expected 1`] = ` loading={false} setting={ Object { - "ariaName": "Provide usage data", + "ariaName": "Share usage with Elastic", "category": Array [], "defVal": true, "description":

, - "displayName": "Provide usage data", + "displayName": "Share usage with Elastic", "isCustom": true, "isOverridden": false, "name": "telemetry:enabled", diff --git a/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx b/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx index b972d2c469406..511b52a43325f 100644 --- a/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx +++ b/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx @@ -27,7 +27,21 @@ import { OptInExampleFlyout } from './opt_in_example_flyout'; type TelemetryService = TelemetryPluginSetup['telemetryService']; -const SEARCH_TERMS = ['telemetry', 'usage', 'data', 'usage data']; +const SEARCH_TERMS: string[] = [ + 'telemetry', + 'usage data', // Keeping this term for BWC + 'usage collection', + i18n.translate('telemetry.telemetryConstant', { + defaultMessage: 'telemetry', + }), + i18n.translate('telemetry.usageCollectionConstant', { + defaultMessage: 'usage collection', + }), +].flatMap((term) => { + // Automatically lower-case and split by space the terms from above + const lowerCased = term.toLowerCase(); + return [lowerCased, ...lowerCased.split(' ')]; +}); interface Props { telemetryService: TelemetryService; @@ -113,7 +127,10 @@ export class TelemetryManagementSection extends Component {

- +

@@ -126,13 +143,13 @@ export class TelemetryManagementSection extends Component { type: 'boolean', name: 'telemetry:enabled', displayName: i18n.translate('telemetry.provideUsageDataTitle', { - defaultMessage: 'Provide usage data', + defaultMessage: 'Share usage with Elastic', }), value: enabled, description: this.renderDescription(), defVal: true, ariaName: i18n.translate('telemetry.provideUsageDataAriaName', { - defaultMessage: 'Provide usage data', + defaultMessage: 'Share usage with Elastic', }), requiresPageReload: false, category: [], @@ -204,8 +221,9 @@ export class TelemetryManagementSection extends Component {

@@ -249,10 +267,10 @@ export class TelemetryManagementSection extends Component { toasts.addSuccess( newOptInValue ? i18n.translate('telemetry.optInSuccessOn', { - defaultMessage: 'Usage data collection turned on.', + defaultMessage: 'Sharing usage with Elastic is enabled.', }) : i18n.translate('telemetry.optInSuccessOff', { - defaultMessage: 'Usage data collection turned off.', + defaultMessage: 'No longer sharing usage with Elastic.', }) ); resolve(true); diff --git a/src/plugins/usage_collection/README.mdx b/src/plugins/usage_collection/README.mdx index a703a3de00820..d755c9615327e 100644 --- a/src/plugins/usage_collection/README.mdx +++ b/src/plugins/usage_collection/README.mdx @@ -392,14 +392,14 @@ document. Examples of interactions include tracking: - the number of API calls - the number of times users installed and uninstalled the sample datasets -When using `incrementCounter` for collecting usage data, you need to ensure +When using `incrementCounter` for collecting usage, you need to ensure that usage collection happens on a best-effort basis and doesn't negatively affect your plugin or users (see the example): - Swallow any exceptions thrown from the incrementCounter method and log a message in development. - Don't block your application on the incrementCounter method (e.g. don't use `await`) - - Set the `refresh` option to false to prevent unecessary index refreshes + - Set the `refresh` option to false to prevent unnecessary index refreshes which slows down Elasticsearch performance diff --git a/test/plugin_functional/test_suites/telemetry/telemetry.ts b/test/plugin_functional/test_suites/telemetry/telemetry.ts index b866733f7f6ce..99208fcb7d6d6 100644 --- a/test/plugin_functional/test_suites/telemetry/telemetry.ts +++ b/test/plugin_functional/test_suites/telemetry/telemetry.ts @@ -74,8 +74,8 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide it('shows the banner in the default configuration', async () => { await PageObjects.common.navigateToApp('home'); - expect(await find.existsByCssSelector('[data-test-subj="enable"]')).to.eql(true); - expect(await find.existsByCssSelector('[data-test-subj="disable"]')).to.eql(true); + expect(await find.existsByLinkText('Enable usage collection.')).to.eql(true); + expect(await find.existsByLinkText('Disable usage collection.')).to.eql(false); }); it('does not show the banner if opted-in', async () => { @@ -86,8 +86,8 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide .expect(200); await PageObjects.common.navigateToApp('home'); - expect(await find.existsByCssSelector('[data-test-subj="enable"]')).to.eql(false); - expect(await find.existsByCssSelector('[data-test-subj="disable"]')).to.eql(false); + expect(await find.existsByLinkText('Enable usage collection.')).to.eql(false); + expect(await find.existsByLinkText('Disable usage collection.')).to.eql(false); }); it('does not show the banner if opted-out in this version', async () => { @@ -98,8 +98,8 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide .expect(200); await PageObjects.common.navigateToApp('home'); - expect(await find.existsByCssSelector('[data-test-subj="enable"]')).to.eql(false); - expect(await find.existsByCssSelector('[data-test-subj="disable"]')).to.eql(false); + expect(await find.existsByLinkText('Enable usage collection.')).to.eql(false); + expect(await find.existsByLinkText('Disable usage collection.')).to.eql(false); }); it('shows the banner if opted-out in a previous version', async () => { @@ -111,8 +111,8 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide }); await PageObjects.common.navigateToApp('home'); - expect(await find.existsByCssSelector('[data-test-subj="enable"]')).to.eql(true); - expect(await find.existsByCssSelector('[data-test-subj="disable"]')).to.eql(true); + expect(await find.existsByLinkText('Enable usage collection.')).to.eql(true); + expect(await find.existsByLinkText('Disable usage collection.')).to.eql(false); }); it('does not show the banner if opted-in in a previous version', async () => { @@ -124,8 +124,8 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide }); await PageObjects.common.navigateToApp('home'); - expect(await find.existsByCssSelector('[data-test-subj="enable"]')).to.eql(false); - expect(await find.existsByCssSelector('[data-test-subj="disable"]')).to.eql(false); + expect(await find.existsByLinkText('Enable usage collection.')).to.eql(false); + expect(await find.existsByLinkText('Disable usage collection.')).to.eql(false); }); }); }); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 5ebaa136691d3..c3351d331cca9 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -5278,9 +5278,7 @@ "sharedUXPackages.userProfileComponents.userProfilesSelectable.suggestedLabel": "Suggérée", "telemetry.callout.appliesSettingTitle": "Les modifications apportées à ce paramètre s'appliquent dans {allOfKibanaText} et sont enregistrées automatiquement.", "telemetry.seeExampleOfClusterDataAndEndpointSecuity": "Découvrez des exemples des {clusterData} et {securityData} que nous collectons.", - "telemetry.telemetryBannerDescription": "Vous souhaitez nous aider à améliorer la Suite Elastic ? La collecte de données d'utilisation est actuellement désactivée. En activant la collecte de données d'utilisation, vous nous aidez à gérer et à améliorer nos produits et nos services. Pour en savoir plus, consultez notre {privacyStatementLink}.", "telemetry.telemetryConfigAndLinkDescription": "En activant la collecte de données d'utilisation, vous nous aidez à gérer et à améliorer nos produits et nos services. Pour en savoir plus, consultez notre {privacyStatementLink}.", - "telemetry.telemetryOptedInNoticeDescription": "Pour en savoir plus sur la manière dont les données d'utilisation nous aident à gérer et à améliorer nos produits et nos services, consultez notre {privacyStatementLink}. Pour mettre fin à la collecte, {disableLink}.", "telemetry.callout.appliesSettingTitle.allOfKibanaText": "tout Kibana", "telemetry.callout.clusterStatisticsDescription": "Voici un exemple des statistiques de cluster de base que nous collecterons. Cela comprend le nombre d'index, de partitions et de nœuds. Cela comprend également des statistiques d'utilisation de niveau élevé, comme l'état d'activation du monitoring.", "telemetry.callout.clusterStatisticsTitle": "Statistiques du cluster", @@ -5289,11 +5287,8 @@ "telemetry.callout.errorUnprivilegedUserDescription": "Vous ne disposez pas de l'accès requis pour voir les statistiques non chiffrées du cluster.", "telemetry.callout.errorUnprivilegedUserTitle": "Erreur lors de l'affichage des statistiques du cluster", "telemetry.clusterData": "données du cluster", - "telemetry.dataManagementDisableCollection": " Pour mettre fin à la collecte, ", "telemetry.dataManagementDisableCollectionLink": "désactivez les données d'utilisation ici.", - "telemetry.dataManagementDisclaimerPrivacy": "Pour en savoir plus sur la manière dont les données d'utilisation nous aident à gérer et à améliorer nos produits et nos services, consultez notre ", "telemetry.dataManagementDisclaimerPrivacyLink": "Déclaration de confidentialité.", - "telemetry.dataManagementEnableCollection": " Pour démarrer la collecte, ", "telemetry.dataManagementEnableCollectionLink": "activez les données d'utilisation ici.", "telemetry.optInErrorToastText": "Une erreur s'est produite lors de la définition des préférences relatives aux statistiques d'utilisation.", "telemetry.optInErrorToastTitle": "Erreur", @@ -5305,15 +5300,9 @@ "telemetry.provideUsageDataTitle": "Fournir les données d'utilisation", "telemetry.readOurUsageDataPrivacyStatementLinkText": "Déclaration de confidentialité", "telemetry.securityData": "données de sécurité", - "telemetry.telemetryOptedInDisableUsage": "désactivez les données d'utilisation ici", "telemetry.telemetryOptedInDismissMessage": "Rejeter", "telemetry.telemetryOptedInNoticeTitle": "Aidez-nous à améliorer la Suite Elastic.", - "telemetry.telemetryOptedInPrivacyStatement": "Déclaration de confidentialité", "telemetry.usageDataTitle": "Données d'utilisation", - "telemetry.welcomeBanner.disableButtonLabel": "Désactiver", - "telemetry.welcomeBanner.enableButtonLabel": "Activer", - "telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "Déclaration de confidentialité", - "telemetry.welcomeBanner.title": "Aidez-nous à améliorer la Suite Elastic.", "timelion.help.functions.aggregate.args.functionHelpText": "L'une des {functions}", "timelion.help.functions.aggregateHelpText": "Crée une ligne statique sur la base du résultat du traitement de tous les points de la série. Fonctions disponibles : {functions}", "timelion.help.functions.common.args.fitHelpText": "Algorithme à utiliser pour adapter les séries à l'intervalle et à la période cible. Disponible : {fitFunctions}", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 407ccad60f283..c45b063820158 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5279,9 +5279,7 @@ "sharedUXPackages.userProfileComponents.userProfilesSelectable.suggestedLabel": "候補", "telemetry.callout.appliesSettingTitle": "この設定に加えた変更は{allOfKibanaText}に適用され、自動的に保存されます。", "telemetry.seeExampleOfClusterDataAndEndpointSecuity": "収集される{clusterData}および{securityData}の例を参照してください。", - "telemetry.telemetryBannerDescription": "Elastic Stackの改善にご協力ください使用状況データの収集は現在無効です。使用状況データの収集を有効にすると、製品とサービスを管理して改善することができます。詳細は{privacyStatementLink}をご覧ください。", "telemetry.telemetryConfigAndLinkDescription": "使用状況データの収集を有効にすると、製品とサービスを管理して改善することができます。詳細は{privacyStatementLink}をご覧ください。", - "telemetry.telemetryOptedInNoticeDescription": "使用状況データがどのように製品とサービスの管理と改善につながるのかに関する詳細については、{privacyStatementLink}を参照してください。収集を停止するには、{disableLink}。", "telemetry.callout.appliesSettingTitle.allOfKibanaText": "Kibana のすべて", "telemetry.callout.clusterStatisticsDescription": "これは収集される基本的なクラスター統計の例です。インデックス、シャード、ノードの数が含まれます。監視がオンになっているかどうかなどのハイレベルの使用統計も含まれます。", "telemetry.callout.clusterStatisticsTitle": "クラスター統計", @@ -5290,11 +5288,8 @@ "telemetry.callout.errorUnprivilegedUserDescription": "暗号化されていないクラスター統計を表示するアクセス権がありません。", "telemetry.callout.errorUnprivilegedUserTitle": "クラスター統計の表示エラー", "telemetry.clusterData": "クラスターデータ", - "telemetry.dataManagementDisableCollection": " 収集を停止するには、", "telemetry.dataManagementDisableCollectionLink": "ここで使用状況データを無効にします。", - "telemetry.dataManagementDisclaimerPrivacy": "使用状況データがどのように製品とサービスの管理と改善につながるのかに関する詳細については ", "telemetry.dataManagementDisclaimerPrivacyLink": "プライバシーポリシーをご覧ください。", - "telemetry.dataManagementEnableCollection": " 収集を開始するには、", "telemetry.dataManagementEnableCollectionLink": "ここで使用状況データを有効にします。", "telemetry.optInErrorToastText": "使用状況統計設定の設定中にエラーが発生しました。", "telemetry.optInErrorToastTitle": "エラー", @@ -5306,15 +5301,9 @@ "telemetry.provideUsageDataTitle": "使用状況データを提供", "telemetry.readOurUsageDataPrivacyStatementLinkText": "プライバシーポリシー", "telemetry.securityData": "セキュリティデータ", - "telemetry.telemetryOptedInDisableUsage": "ここで使用状況データを無効にする", "telemetry.telemetryOptedInDismissMessage": "閉じる", "telemetry.telemetryOptedInNoticeTitle": "Elastic Stack の改善にご協力ください", - "telemetry.telemetryOptedInPrivacyStatement": "プライバシーポリシー", "telemetry.usageDataTitle": "使用データ", - "telemetry.welcomeBanner.disableButtonLabel": "無効にする", - "telemetry.welcomeBanner.enableButtonLabel": "有効にする", - "telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "プライバシーポリシー", - "telemetry.welcomeBanner.title": "Elastic Stack の改善にご協力ください", "timelion.help.functions.aggregate.args.functionHelpText": "{functions}の1つ", "timelion.help.functions.aggregateHelpText": "数列のすべての点の処理結果に基づく線を作成します。利用可能な関数:{functions}", "timelion.help.functions.common.args.fitHelpText": "ターゲットの期間と間隔に数列を合わせるためのアルゴリズムです。利用可能:{fitFunctions}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3df0589530cff..85ba7c495ebdc 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5278,9 +5278,7 @@ "sharedUXPackages.userProfileComponents.userProfilesSelectable.suggestedLabel": "已建议", "telemetry.callout.appliesSettingTitle": "对此设置的更改将应用到{allOfKibanaText} 且会自动保存。", "telemetry.seeExampleOfClusterDataAndEndpointSecuity": "查看我们收集的{clusterData}和{securityData}示例。", - "telemetry.telemetryBannerDescription": "想帮助我们改进 Elastic Stack?数据使用情况收集当前已禁用。启用使用情况数据收集可帮助我们管理并改善产品和服务。有关详情,请参阅我们的{privacyStatementLink}。", "telemetry.telemetryConfigAndLinkDescription": "启用使用情况数据收集可帮助我们管理并改善产品和服务。有关详情,请参阅我们的{privacyStatementLink}。", - "telemetry.telemetryOptedInNoticeDescription": "要了解使用情况数据如何帮助我们管理和改善产品和服务,请参阅我们的{privacyStatementLink}。要停止收集,{disableLink}。", "telemetry.callout.appliesSettingTitle.allOfKibanaText": "整个 Kibana", "telemetry.callout.clusterStatisticsDescription": "这是我们将收集的基本集群统计信息的示例。其包括索引、分片和节点的数目。还包括概括性的使用情况统计信息,例如监测是否打开。", "telemetry.callout.clusterStatisticsTitle": "集群统计信息", @@ -5289,11 +5287,8 @@ "telemetry.callout.errorUnprivilegedUserDescription": "您无权查看未加密的集群统计信息。", "telemetry.callout.errorUnprivilegedUserTitle": "显示集群统计信息时出错", "telemetry.clusterData": "集群数据", - "telemetry.dataManagementDisableCollection": " 要停止收集,", "telemetry.dataManagementDisableCollectionLink": "请在此禁用使用情况数据。", - "telemetry.dataManagementDisclaimerPrivacy": "要了解使用情况数据如何帮助我们管理和改善产品和服务,请参阅我们的 ", "telemetry.dataManagementDisclaimerPrivacyLink": "隐私声明。", - "telemetry.dataManagementEnableCollection": " 要启动收集,", "telemetry.dataManagementEnableCollectionLink": "请在此处启用使用情况数据。", "telemetry.optInErrorToastText": "尝试设置使用情况统计信息首选项时发生错误。", "telemetry.optInErrorToastTitle": "错误", @@ -5305,15 +5300,9 @@ "telemetry.provideUsageDataTitle": "提供使用情况数据", "telemetry.readOurUsageDataPrivacyStatementLinkText": "隐私声明", "telemetry.securityData": "安全数据", - "telemetry.telemetryOptedInDisableUsage": "请在此禁用使用情况数据", "telemetry.telemetryOptedInDismissMessage": "关闭", "telemetry.telemetryOptedInNoticeTitle": "帮助我们改进 Elastic Stack", - "telemetry.telemetryOptedInPrivacyStatement": "隐私声明", "telemetry.usageDataTitle": "使用情况数据", - "telemetry.welcomeBanner.disableButtonLabel": "禁用", - "telemetry.welcomeBanner.enableButtonLabel": "启用", - "telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "隐私声明", - "telemetry.welcomeBanner.title": "帮助我们改进 Elastic Stack", "timelion.help.functions.aggregate.args.functionHelpText": "以下选项之一:{functions}", "timelion.help.functions.aggregateHelpText": "基于对序列中所有点的处理结果创建静态线。可用函数:{functions}", "timelion.help.functions.common.args.fitHelpText": "用于将序列拟合到目标时间跨度和时间间隔的算法。可用:{fitFunctions}",