diff --git a/src/legacy/core_plugins/kibana/public/assets/illo_telemetry.png b/src/legacy/core_plugins/kibana/public/assets/illo_telemetry.png new file mode 100644 index 0000000000000..d0d6dac59ab24 Binary files /dev/null and b/src/legacy/core_plugins/kibana/public/assets/illo_telemetry.png differ diff --git a/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap b/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap index 2331fdd559c91..0e73c0cbc93a5 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap @@ -1135,7 +1135,10 @@ exports[`home welcome should show the normal home page if welcome screen is disa exports[`home welcome should show the welcome screen if enabled, and there are no index patterns defined 1`] = ` `; diff --git a/src/legacy/core_plugins/kibana/public/home/components/_welcome.scss b/src/legacy/core_plugins/kibana/public/home/components/_welcome.scss index 821fbe46b1404..d8c777534647b 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/_welcome.scss +++ b/src/legacy/core_plugins/kibana/public/home/components/_welcome.scss @@ -1,6 +1,5 @@ - .homWelcome { - @include kibanaFullScreenGraphics; + @include kibanaFullScreenGraphics($euiZLevel6); } .homWelcome__header { diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.js b/src/legacy/core_plugins/kibana/public/home/components/home.js index 3b7e2c0990c6f..76f15f797ed6c 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home.js @@ -225,6 +225,10 @@ export class Home extends Component { ); } @@ -247,6 +251,10 @@ export class Home extends Component { Home.propTypes = { addBasePath: PropTypes.func.isRequired, + fetchTelemetry: PropTypes.func.isRequired, + getTelemetryBannerId: PropTypes.func.isRequired, + setOptIn: PropTypes.func.isRequired, + shouldShowTelemetryOptIn: PropTypes.bool.isRequired, directories: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.test.js b/src/legacy/core_plugins/kibana/public/home/components/home.test.js index c10aa3f0b1e32..aa520ba2ed5f9 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.test.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home.test.js @@ -17,30 +17,14 @@ * under the License. */ +import './home.test.mocks'; + import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; import { Home } from './home'; import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -jest.mock( - 'ui/chrome', - () => ({ - getBasePath: jest.fn(() => 'path'), - getInjected: jest.fn(() => ''), - }), - { virtual: true } -); - -jest.mock( - 'ui/capabilities', - () => ({ - catalogue: {}, - management: {}, - navLinks: {} - }) -); - describe('home', () => { let defaultProps; @@ -50,6 +34,10 @@ describe('home', () => { apmUiEnabled: true, mlEnabled: true, kibanaVersion: '99.2.1', + fetchTelemetry: jest.fn(), + getTelemetryBannerId: jest.fn(), + setOptIn: jest.fn(), + showTelemetryOptIn: false, addBasePath(url) { return `base_path/${url}`; }, diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts b/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts new file mode 100644 index 0000000000000..1eed597a90a4b --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { notificationServiceMock } from '../../../../../../core/public/mocks'; + +jest.doMock('ui/new_platform', () => { + return { + npSetup: { + core: { + notifications: notificationServiceMock.createSetupContract(), + }, + }, + }; +}); + +jest.doMock( + 'ui/chrome', + () => ({ + getBasePath: jest.fn(() => 'path'), + getInjected: jest.fn(() => ''), + }), + { virtual: true } +); + +jest.doMock('ui/capabilities', () => ({ + catalogue: {}, + management: {}, + navLinks: {}, +})); diff --git a/src/legacy/core_plugins/kibana/public/home/components/home_app.js b/src/legacy/core_plugins/kibana/public/home/components/home_app.js index fc24eeaa0f451..9aa44863f6d70 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home_app.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home_app.js @@ -30,12 +30,10 @@ import { } from 'react-router-dom'; import { getTutorial } from '../load_tutorials'; import { replaceTemplateStrings } from './tutorial/replace_template_strings'; +import { telemetryOptInProvider, shouldShowTelemetryOptIn } from '../kibana_services'; import chrome from 'ui/chrome'; -export function HomeApp({ - directories, -}) { - +export function HomeApp({ directories }) { const isCloudEnabled = chrome.getInjected('isCloudEnabled', false); const apmUiEnabled = chrome.getInjected('apmUiEnabled', true); const mlEnabled = chrome.getInjected('mlEnabled', false); @@ -94,6 +92,10 @@ export function HomeApp({ find={savedObjectsClient.find} localStorage={localStorage} urlBasePath={chrome.getBasePath()} + shouldShowTelemetryOptIn={shouldShowTelemetryOptIn} + setOptIn={telemetryOptInProvider.setOptIn} + fetchTelemetry={telemetryOptInProvider.fetchExample} + getTelemetryBannerId={telemetryOptInProvider.getBannerId} /> diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data/index.tsx b/src/legacy/core_plugins/kibana/public/home/components/sample_data/index.tsx new file mode 100644 index 0000000000000..1bb8bd214d2cb --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/components/sample_data/index.tsx @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * The UI and related logic for the welcome screen that *should* show only + * when it is enabled (the default) and there is no Kibana-consumed data + * in Elasticsearch. + */ + +import React from 'react'; +import { + // @ts-ignore + EuiCard, + EuiButton, + EuiButtonEmpty, +} from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n/react'; + +interface Props { + urlBasePath: string; + onDecline: () => void; + onConfirm: () => void; +} + +export function SampleDataCard({ urlBasePath, onDecline, onConfirm }: Props) { + return ( + } + description={ + + } + footer={ +
+ + + + + + +
+ } + /> + ); +} diff --git a/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/index.ts b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/index.ts new file mode 100644 index 0000000000000..63636433bc00b --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/index.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { renderTelemetryOptInCard, Props } from './telemetry_opt_in_card'; + +export const TelemetryOptInCard = (props: Props) => { + return renderTelemetryOptInCard(props); +}; diff --git a/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_details_component.tsx b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_details_component.tsx new file mode 100644 index 0000000000000..d90f54b2bcb54 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_details_component.tsx @@ -0,0 +1,163 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; + +import { + EuiCallOut, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiLoadingSpinner, + EuiPortal, // EuiPortal is a temporary requirement to use EuiFlyout with "ownFocus" + EuiText, + EuiTextColor, + EuiTitle, +} from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n/react'; + +interface Props { + fetchTelemetry: () => Promise; + onClose: () => void; +} + +interface State { + isLoading: boolean; + hasPrivilegeToRead: boolean; + data: any[] | null; +} + +/** + * React component for displaying the example data associated with the Telemetry opt-in banner. + */ +export class OptInExampleFlyout extends React.PureComponent { + public readonly state: State = { + data: null, + isLoading: true, + hasPrivilegeToRead: false, + }; + + componentDidMount() { + this.props + .fetchTelemetry() + .then(response => + this.setState({ + data: Array.isArray(response.data) ? response.data : null, + isLoading: false, + hasPrivilegeToRead: true, + }) + ) + .catch(err => { + this.setState({ + isLoading: false, + hasPrivilegeToRead: err.status !== 403, + }); + }); + } + + renderBody({ data, isLoading, hasPrivilegeToRead }: State) { + if (isLoading) { + return ( + + + + + + ); + } + + if (!hasPrivilegeToRead) { + return ( + + } + color="danger" + iconType="cross" + > + + + ); + } + + if (data === null) { + return ( + + } + color="danger" + iconType="cross" + > + + + ); + } + + return {JSON.stringify(data, null, 2)}; + } + + render() { + return ( + + + + +

+ +

+
+ + + + + +
+ {this.renderBody(this.state)} +
+
+ ); + } +} diff --git a/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_message.tsx b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_message.tsx new file mode 100644 index 0000000000000..0f581e819b052 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_message.tsx @@ -0,0 +1,106 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import { EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { OptInExampleFlyout } from './opt_in_details_component'; + +interface Props { + fetchTelemetry: () => Promise; +} + +interface State { + showDetails: boolean; + showExample: boolean; +} + +export class OptInMessage extends React.PureComponent { + public readonly state: State = { + showDetails: false, + showExample: false, + }; + + toggleShowExample = () => { + this.setState(prevState => ({ + showExample: !prevState.showExample, + })); + }; + + render() { + const { fetchTelemetry } = this.props; + const { showDetails, showExample } = this.state; + + const getDetails = () => ( + + + + ), + telemetryPrivacyStatementLink: ( + + + + ), + }} + /> + ); + + const getFlyoutDetails = () => ( + this.setState({ showExample: false })} + fetchTelemetry={fetchTelemetry} + /> + ); + + const getReadMore = () => ( + this.setState({ showDetails: true })}> + + + ); + + return ( + + {' '} + {!showDetails && getReadMore()} + {showDetails && getDetails()} + {showDetails && showExample && getFlyoutDetails()} + + ); + } +} diff --git a/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/telemetry_opt_in_card.tsx b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/telemetry_opt_in_card.tsx new file mode 100644 index 0000000000000..5fa842291b028 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/telemetry_opt_in_card.tsx @@ -0,0 +1,81 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + // @ts-ignore + EuiCard, + EuiButton, +} from '@elastic/eui'; +import { OptInMessage } from './opt_in_message'; + +export interface Props { + urlBasePath: string; + onConfirm: () => void; + onDecline: () => void; + fetchTelemetry: () => Promise; +} + +export function renderTelemetryOptInCard({ + urlBasePath, + fetchTelemetry, + onConfirm, + onDecline, +}: Props) { + return ( + + } + description={} + footer={ +
+ + + + + + +
+ } + /> + ); +} diff --git a/src/legacy/core_plugins/kibana/public/home/components/welcome.js b/src/legacy/core_plugins/kibana/public/home/components/welcome.js deleted file mode 100644 index 98560b748ec0d..0000000000000 --- a/src/legacy/core_plugins/kibana/public/home/components/welcome.js +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* - * The UI and related logic for the welcome screen that *should* show only - * when it is enabled (the default) and there is no Kibana-consumed data - * in Elasticsearch. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { - EuiCard, - EuiTitle, - EuiSpacer, - EuiFlexGroup, - EuiFlexItem, - EuiText, - EuiIcon, - EuiButton, - EuiButtonEmpty, - EuiPortal, -} from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -/** - * Shows a full-screen welcome page that gives helpful quick links to beginners. - */ -export class Welcome extends React.Component { - hideOnEsc = e => { - if (e.key === 'Escape') { - this.props.onSkip(); - } - }; - - componentDidMount() { - document.addEventListener('keydown', this.hideOnEsc); - } - - componentWillUnmount() { - document.removeEventListener('keydown', this.hideOnEsc); - } - - render() { - const { urlBasePath, onSkip } = this.props; - - return ( - -
-
-
- - - - - -

- -

-
- -

- -

-
- -
-
-
- - - } - description={ - } - footer={ -
- - - - - - -
- } - /> -
-
-
-
-
- ); - } -} - -Welcome.propTypes = { - urlBasePath: PropTypes.string.isRequired, - onSkip: PropTypes.func.isRequired, -}; diff --git a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx new file mode 100644 index 0000000000000..8869819290263 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx @@ -0,0 +1,161 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * The UI and related logic for the welcome screen that *should* show only + * when it is enabled (the default) and there is no Kibana-consumed data + * in Elasticsearch. + */ + +import React from 'react'; +import { + EuiTitle, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiIcon, + EuiPortal, +} from '@elastic/eui'; +// @ts-ignore +import { banners } from 'ui/notify'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import chrome from 'ui/chrome'; +import { SampleDataCard } from './sample_data'; +import { TelemetryOptInCard } from './telemetry_opt_in'; +// @ts-ignore +import { trackUiMetric, METRIC_TYPE } from '../kibana_services'; + +interface Props { + urlBasePath: string; + onSkip: () => {}; + fetchTelemetry: () => Promise; + setOptIn: (enabled: boolean) => Promise; + getTelemetryBannerId: () => string; + shouldShowTelemetryOptIn: boolean; +} +interface State { + step: number; +} + +/** + * Shows a full-screen welcome page that gives helpful quick links to beginners. + */ +export class Welcome extends React.PureComponent { + public readonly state: State = { + step: 0, + }; + + private hideOnEsc = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + this.props.onSkip(); + } + }; + + private redirecToSampleData() { + const path = chrome.addBasePath('#/home/tutorial_directory/sampleData'); + window.location.href = path; + } + private async handleTelemetrySelection(confirm: boolean) { + const metricName = `telemetryOptIn${confirm ? 'Confirm' : 'Decline'}`; + trackUiMetric(METRIC_TYPE.CLICK, metricName); + await this.props.setOptIn(confirm); + const bannerId = this.props.getTelemetryBannerId(); + banners.remove(bannerId); + this.setState(() => ({ step: 1 })); + } + + private onSampleDataDecline = () => { + trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataDecline'); + this.props.onSkip(); + }; + private onSampleDataConfirm = () => { + trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataConfirm'); + this.redirecToSampleData(); + }; + + componentDidMount() { + trackUiMetric(METRIC_TYPE.LOADED, 'welcomeScreenMount'); + if (this.props.shouldShowTelemetryOptIn) { + trackUiMetric(METRIC_TYPE.COUNT, 'welcomeScreenWithTelemetryOptIn'); + } + document.addEventListener('keydown', this.hideOnEsc); + } + + componentWillUnmount() { + document.removeEventListener('keydown', this.hideOnEsc); + } + + render() { + const { urlBasePath, shouldShowTelemetryOptIn, fetchTelemetry } = this.props; + const { step } = this.state; + + return ( + +
+
+
+ + + + + +

+ +

+
+ +

+ +

+
+ +
+
+
+ + + {shouldShowTelemetryOptIn && step === 0 && ( + + )} + {(!shouldShowTelemetryOptIn || step === 1) && ( + + )} + + + +
+
+
+ ); + } +} diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.js b/src/legacy/core_plugins/kibana/public/home/kibana_services.js index fc06a61ae343d..c5480e16491a9 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.js +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.js @@ -18,9 +18,23 @@ */ import { uiModules } from 'ui/modules'; +import { npStart } from 'ui/new_platform'; +import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; +import { TelemetryOptInProvider } from './telemetry_opt_in'; export let indexPatternService; +export let shouldShowTelemetryOptIn; +export let telemetryOptInProvider; + +export const trackUiMetric = createUiStatsReporter('Kibana_home'); +export { METRIC_TYPE }; uiModules.get('kibana').run(($injector) => { + const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); + const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner'); + const Private = $injector.get('Private'); + + telemetryOptInProvider = Private(TelemetryOptInProvider); + shouldShowTelemetryOptIn = telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(); indexPatternService = $injector.get('indexPatterns'); }); diff --git a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.js b/src/legacy/core_plugins/kibana/public/home/telemetry_opt_in.js similarity index 52% rename from x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.js rename to src/legacy/core_plugins/kibana/public/home/telemetry_opt_in.js index 2fcd2012a1528..274820844da45 100644 --- a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.js +++ b/src/legacy/core_plugins/kibana/public/home/telemetry_opt_in.js @@ -1,20 +1,37 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import moment from 'moment'; import { setCanTrackUiMetrics } from 'ui/ui_metric'; import { toastNotifications } from 'ui/notify'; +import { npStart } from 'ui/new_platform'; import { i18n } from '@kbn/i18n'; export function TelemetryOptInProvider($injector, chrome) { - let currentOptInStatus = $injector.get('telemetryOptedIn'); - setCanTrackUiMetrics(currentOptInStatus); + let currentOptInStatus = npStart.core.injectedMetadata.getInjectedVar('telemetryOptedIn'); + let bannerId = null; + setCanTrackUiMetrics(currentOptInStatus); const provider = { + getBannerId: () => bannerId, getOptIn: () => currentOptInStatus, + setBannerId(id) { bannerId = id; }, setOptIn: async (enabled) => { setCanTrackUiMetrics(enabled); const $http = $injector.get('$http'); @@ -24,10 +41,10 @@ export function TelemetryOptInProvider($injector, chrome) { currentOptInStatus = enabled; } catch (error) { toastNotifications.addError(error, { - title: i18n.translate('xpack.telemetry.optInErrorToastTitle', { + title: i18n.translate('kbn.home.telemetry.optInErrorToastTitle', { defaultMessage: 'Error', }), - toastMessage: i18n.translate('xpack.telemetry.optInErrorToastText', { + toastMessage: i18n.translate('kbn.home.telemetry.optInErrorToastText', { defaultMessage: 'An error occured while trying to set the usage statistics preference.', }), }); diff --git a/src/legacy/ui/public/styles/_mixins.scss b/src/legacy/ui/public/styles/_mixins.scss index cb027ee684a17..ae529a4678d5d 100644 --- a/src/legacy/ui/public/styles/_mixins.scss +++ b/src/legacy/ui/public/styles/_mixins.scss @@ -55,13 +55,13 @@ } } -@mixin kibanaFullScreenGraphics() { +@mixin kibanaFullScreenGraphics($euiZLevel: $euiZLevel9) { position: fixed; top: 0; left: 0; right: 0; bottom: 0; - z-index: $euiZLevel9 + 1000; + z-index: $euiZLevel + 1000; background: inherit; background-image: linear-gradient(0deg, $euiColorLightestShade 0%, $euiColorEmptyShade 100%); opacity: 0; diff --git a/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/telemetry_opt_in.test.js.snap b/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/telemetry_opt_in.test.js.snap index e07d9144c01c5..642b8399ff6d1 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/telemetry_opt_in.test.js.snap +++ b/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/telemetry_opt_in.test.js.snap @@ -104,277 +104,7 @@ exports[`TelemetryOptIn should display when telemetry not opted in 1`] = ` "timeZone": null, } } -> - -
- - -

- - Help Elastic support provide better service - -

-
- -
- - - - - - } - className="eui-AlignBaseline" - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - id="readMorePopover" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > - -

- - - , - "telemetryPrivacyStatementLink": - - , - } - } - /> -

-
- , - } - } - /> - - } - onChange={[Function]} - > -
- -
- -
- - +/> `; exports[`TelemetryOptIn should not display when telemetry is opted in 1`] = ` diff --git a/x-pack/legacy/plugins/license_management/public/components/telemetry_opt_in/telemetry_opt_in.js b/x-pack/legacy/plugins/license_management/public/components/telemetry_opt_in/telemetry_opt_in.js index 1bd5f075449e2..35403ccb672f3 100644 --- a/x-pack/legacy/plugins/license_management/public/components/telemetry_opt_in/telemetry_opt_in.js +++ b/x-pack/legacy/plugins/license_management/public/components/telemetry_opt_in/telemetry_opt_in.js @@ -13,7 +13,7 @@ import { EuiTitle, EuiPopover } from '@elastic/eui'; -import { showTelemetryOptIn, getTelemetryFetcher, PRIVACY_STATEMENT_URL, OptInExampleFlyout } from '../../lib/telemetry'; +import { shouldShowTelemetryOptIn, getTelemetryFetcher, PRIVACY_STATEMENT_URL, OptInExampleFlyout } from '../../lib/telemetry'; import { FormattedMessage } from '@kbn/i18n/react'; export class TelemetryOptIn extends React.Component { @@ -127,7 +127,7 @@ export class TelemetryOptIn extends React.Component { ); - return showTelemetryOptIn() ? ( + return shouldShowTelemetryOptIn() ? ( {example} {toCurrentCustomers} diff --git a/x-pack/legacy/plugins/license_management/public/lib/telemetry.js b/x-pack/legacy/plugins/license_management/public/lib/telemetry.js index 79c65618cb1a1..08928549916eb 100644 --- a/x-pack/legacy/plugins/license_management/public/lib/telemetry.js +++ b/x-pack/legacy/plugins/license_management/public/lib/telemetry.js @@ -24,9 +24,9 @@ export const setTelemetryOptInService = (aTelemetryOptInService) => { export const optInToTelemetry = async (enableTelemetry) => { await telemetryOptInService.setOptIn(enableTelemetry); }; -export const showTelemetryOptIn = () => { +export const shouldShowTelemetryOptIn = () => { return telemetryEnabled && !telemetryOptInService.getOptIn(); }; export const getTelemetryFetcher = () => { - return fetchTelemetry(httpClient); + return fetchTelemetry(httpClient, { unencrypted: true }); }; diff --git a/x-pack/legacy/plugins/telemetry/public/components/__snapshots__/opt_in_details_component.test.js.snap b/x-pack/legacy/plugins/telemetry/public/components/__snapshots__/opt_in_details_component.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/telemetry/public/components/__snapshots__/opt_in_details_component.test.js.snap rename to x-pack/legacy/plugins/telemetry/public/components/__snapshots__/opt_in_details_component.test.tsx.snap diff --git a/x-pack/legacy/plugins/telemetry/public/components/index.js b/x-pack/legacy/plugins/telemetry/public/components/index.ts similarity index 74% rename from x-pack/legacy/plugins/telemetry/public/components/index.js rename to x-pack/legacy/plugins/telemetry/public/components/index.ts index 3461da4fdcaca..9675bd997b183 100644 --- a/x-pack/legacy/plugins/telemetry/public/components/index.js +++ b/x-pack/legacy/plugins/telemetry/public/components/index.ts @@ -4,5 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +// @ts-ignore export { TelemetryForm } from './telemetry_form'; export { OptInExampleFlyout } from './opt_in_details_component'; +export { OptInBanner } from './opt_in_banner_component'; +export { OptInMessage } from './opt_in_message'; diff --git a/x-pack/legacy/plugins/telemetry/public/components/opt_in_banner_component.tsx b/x-pack/legacy/plugins/telemetry/public/components/opt_in_banner_component.tsx new file mode 100644 index 0000000000000..19754504c081e --- /dev/null +++ b/x-pack/legacy/plugins/telemetry/public/components/opt_in_banner_component.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; + * you may not use this file except in compliance with the Elastic License. + */ + +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'; + +interface Props { + fetchTelemetry: () => Promise; + optInClick: (optIn: boolean) => void; +} + +/** + * React component for displaying the Telemetry opt-in banner. + */ +export class OptInBanner extends React.PureComponent { + render() { + const title = ( + + ); + return ( + + + + + + this.props.optInClick(true)}> + + + + + this.props.optInClick(false)}> + + + + + + ); + } +} diff --git a/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.test.js b/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.test.tsx similarity index 74% rename from x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.test.js rename to x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.test.tsx index 358735ff95c8f..c58927c66756b 100644 --- a/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.test.js +++ b/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.test.tsx @@ -9,8 +9,13 @@ import { OptInExampleFlyout } from './opt_in_details_component'; describe('OptInDetailsComponent', () => { it('renders as expected', () => { - expect(shallowWithIntl( - ({ data: [] }))} onClose={jest.fn()} />) + expect( + shallowWithIntl( + ({ data: [] }))} + onClose={jest.fn()} + /> + ) ).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.js b/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.tsx similarity index 65% rename from x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.js rename to x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.tsx index aac7ebf1b625b..9cfb55af1dab3 100644 --- a/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.js +++ b/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.tsx @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import * as React from 'react'; import { EuiCallOut, @@ -24,39 +23,37 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; +interface Props { + fetchTelemetry: () => Promise; + onClose: () => void; +} + +interface State { + isLoading: boolean; + hasPrivilegeToRead: boolean; + data: any[] | null; +} + /** * React component for displaying the example data associated with the Telemetry opt-in banner. */ -export class OptInExampleFlyout extends Component { - - static propTypes = { - /** - * Callback function with no parameters that returns a {@code Promise} containing the - * telemetry data (expected to be an array). - */ - fetchTelemetry: PropTypes.func.isRequired, - /** - * Callback function with no parameters that closes this flyout. - */ - onClose: PropTypes.func.isRequired, - } - - constructor(props) { - super(props); - this.state = { - data: null, - isLoading: true, - hasPrivilegeToRead: false, - }; - } +export class OptInExampleFlyout extends React.PureComponent { + public readonly state: State = { + data: null, + isLoading: true, + hasPrivilegeToRead: false, + }; componentDidMount() { - this.props.fetchTelemetry() - .then(response => this.setState({ - data: Array.isArray(response.data) ? response.data : null, - isLoading: false, - hasPrivilegeToRead: true, - })) + this.props + .fetchTelemetry() + .then(response => + this.setState({ + data: Array.isArray(response.data) ? response.data : null, + isLoading: false, + hasPrivilegeToRead: true, + }) + ) .catch(err => { this.setState({ isLoading: false, @@ -65,7 +62,7 @@ export class OptInExampleFlyout extends Component { }); } - renderBody({ data, isLoading, hasPrivilegeToRead }) { + renderBody({ data, isLoading, hasPrivilegeToRead }: State) { if (isLoading) { return ( @@ -79,10 +76,12 @@ export class OptInExampleFlyout extends Component { if (!hasPrivilegeToRead) { return ( } + title={ + + } color="danger" iconType="cross" > @@ -97,10 +96,12 @@ export class OptInExampleFlyout extends Component { if (data === null) { return ( } + title={ + + } color="danger" iconType="cross" > @@ -114,21 +115,13 @@ export class OptInExampleFlyout extends Component { ); } - return ( - - {JSON.stringify(data, null, 2)} - - ); + return {JSON.stringify(data, null, 2)}; } render() { return ( - +

@@ -149,12 +142,9 @@ export class OptInExampleFlyout extends Component { - - {this.renderBody(this.state)} - + {this.renderBody(this.state)} ); } - } diff --git a/x-pack/legacy/plugins/telemetry/public/components/opt_in_message.tsx b/x-pack/legacy/plugins/telemetry/public/components/opt_in_message.tsx new file mode 100644 index 0000000000000..5be20f8de32c1 --- /dev/null +++ b/x-pack/legacy/plugins/telemetry/public/components/opt_in_message.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import { EuiLink, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { getConfigTelemetryDesc, PRIVACY_STATEMENT_URL } from '../../common/constants'; +import { OptInExampleFlyout } from './opt_in_details_component'; + +interface Props { + fetchTelemetry: () => Promise; +} + +interface State { + showDetails: boolean; + showExample: boolean; +} + +export class OptInMessage extends React.PureComponent { + public readonly state: State = { + showDetails: false, + showExample: false, + }; + + toggleShowExample = () => { + this.setState(prevState => ({ + showExample: !prevState.showExample, + })); + }; + + render() { + const { showDetails, showExample } = this.state; + + const getDetails = () => ( + +

+ + + + ), + telemetryPrivacyStatementLink: ( + + + + ), + }} + /> +

+
+ ); + + const getFlyoutDetails = () => ( + this.setState({ showExample: false })} + fetchTelemetry={this.props.fetchTelemetry} + /> + ); + + const getReadMore = () => ( + this.setState({ showDetails: true })}> + + + ); + + return ( + + +

+ {getConfigTelemetryDesc()} {!showDetails && getReadMore()} +

+
+ {showDetails && getDetails()} + {showDetails && showExample && getFlyoutDetails()} +
+ ); + } +} diff --git a/x-pack/legacy/plugins/telemetry/public/components/telemetry_form.test.js b/x-pack/legacy/plugins/telemetry/public/components/telemetry_form.test.js index ee9418f8da2db..4df9cc0da1695 100644 --- a/x-pack/legacy/plugins/telemetry/public/components/telemetry_form.test.js +++ b/x-pack/legacy/plugins/telemetry/public/components/telemetry_form.test.js @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import '../services/telemetry_opt_in.test.mocks'; import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { TelemetryForm } from './telemetry_form'; diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/render_banner.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/render_banner.js deleted file mode 100644 index 0ba0a21b6413b..0000000000000 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/render_banner.js +++ /dev/null @@ -1,33 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -import { renderBanner } from '../render_banner'; - -describe('render_banner', () => { - - it('adds a banner to banners with priority of 10000', () => { - const config = { }; - const banners = { - add: sinon.stub() - }; - const fetchTelemetry = sinon.stub(); - banners.add.returns('brucer-banner'); - - renderBanner(config, fetchTelemetry, { _banners: banners }); - - expect(banners.add.calledOnce).to.be(true); - expect(fetchTelemetry.called).to.be(false); - - const bannerConfig = banners.add.getCall(0).args[0]; - - expect(bannerConfig.component).not.to.be(undefined); - expect(bannerConfig.priority).to.be(10000); - }); - -}); diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.js index 979eb635c5c5e..f337a8025e01d 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.js +++ b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.js @@ -16,18 +16,16 @@ import { FormattedMessage } from '@kbn/i18n/react'; /** * Handle clicks from the user on the opt-in banner. * - * @param {String} bannerId Banner ID to close upon success. * @param {Object} telemetryOptInProvider the telemetry opt-in provider * @param {Boolean} optIn {@code true} to opt into telemetry. * @param {Object} _banners Singleton banners. Can be overridden for tests. * @param {Object} _toastNotifications Singleton toast notifications. Can be overridden for tests. */ export async function clickBanner( - bannerId, telemetryOptInProvider, optIn, { _banners = banners, _toastNotifications = toastNotifications } = {}) { - + const bannerId = telemetryOptInProvider.getBannerId(); let set = false; try { diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/click_banner.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.test.js similarity index 64% rename from x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/click_banner.js rename to x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.test.js index ddc953b710658..751a8f5498ee5 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/click_banner.js +++ b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.test.js @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; +import { mockInjectedMetadata } from '../../services/telemetry_opt_in.test.mocks'; + import sinon from 'sinon'; import { uiModules } from 'ui/modules'; @@ -13,16 +14,12 @@ uiModules.get('kibana') // MockInjector used in these tests is not impacted .constant('telemetryOptedIn', null); -import { - clickBanner, -} from '../click_banner'; -import { TelemetryOptInProvider } from '../../../services/telemetry_opt_in'; +import { clickBanner } from './click_banner'; +import { TelemetryOptInProvider } from '../../services/telemetry_opt_in'; const getMockInjector = ({ simulateFailure }) => { const get = sinon.stub(); - get.withArgs('telemetryOptedIn').returns(null); - const mockHttp = { post: sinon.stub() }; @@ -60,16 +57,18 @@ describe('click_banner', () => { remove: sinon.spy() }; + const optIn = true; + const bannerId = 'bruce-banner'; + mockInjectedMetadata({ telemetryOptedIn: optIn }); const telemetryOptInProvider = getTelemetryOptInProvider(); - const bannerId = 'bruce-banner'; - const optIn = true; + telemetryOptInProvider.setBannerId(bannerId); - await clickBanner(bannerId, telemetryOptInProvider, optIn, { _banners: banners }); + await clickBanner(telemetryOptInProvider, optIn, { _banners: banners }); - expect(telemetryOptInProvider.getOptIn()).to.be(optIn); - expect(banners.remove.calledOnce).to.be(true); - expect(banners.remove.calledWith(bannerId)).to.be(true); + expect(telemetryOptInProvider.getOptIn()).toBe(optIn); + expect(banners.remove.calledOnce).toBe(true); + expect(banners.remove.calledWith(bannerId)).toBe(true); }); it('sets setting unsuccessfully, adds toast, and does not touch banner', async () => { @@ -79,15 +78,15 @@ describe('click_banner', () => { const banners = { remove: sinon.spy() }; - const telemetryOptInProvider = getTelemetryOptInProvider({ simulateFailure: true }); - const bannerId = 'bruce-banner'; const optIn = true; + mockInjectedMetadata({ telemetryOptedIn: null }); + const telemetryOptInProvider = getTelemetryOptInProvider({ simulateFailure: true }); - await clickBanner(bannerId, telemetryOptInProvider, optIn, { _banners: banners, _toastNotifications: toastNotifications }); + await clickBanner(telemetryOptInProvider, optIn, { _banners: banners, _toastNotifications: toastNotifications }); - expect(telemetryOptInProvider.getOptIn()).to.be(null); - expect(toastNotifications.addDanger.calledOnce).to.be(true); - expect(banners.remove.notCalled).to.be(true); + expect(telemetryOptInProvider.getOptIn()).toBe(null); + expect(toastNotifications.addDanger.calledOnce).toBe(true); + expect(banners.remove.notCalled).toBe(true); }); it('sets setting unsuccessfully with error, adds toast, and does not touch banner', async () => { @@ -97,15 +96,15 @@ describe('click_banner', () => { const banners = { remove: sinon.spy() }; - const telemetryOptInProvider = getTelemetryOptInProvider({ simulateError: true }); - const bannerId = 'bruce-banner'; const optIn = false; + mockInjectedMetadata({ telemetryOptedIn: null }); + const telemetryOptInProvider = getTelemetryOptInProvider({ simulateError: true }); - await clickBanner(bannerId, telemetryOptInProvider, optIn, { _banners: banners, _toastNotifications: toastNotifications }); + await clickBanner(telemetryOptInProvider, optIn, { _banners: banners, _toastNotifications: toastNotifications }); - expect(telemetryOptInProvider.getOptIn()).to.be(null); - expect(toastNotifications.addDanger.calledOnce).to.be(true); - expect(banners.remove.notCalled).to.be(true); + expect(telemetryOptInProvider.getOptIn()).toBe(null); + expect(toastNotifications.addDanger.calledOnce).toBe(true); + expect(banners.remove.notCalled).toBe(true); }); }); diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/handle_old_settings.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js similarity index 61% rename from x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/handle_old_settings.js rename to x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js index 9e61e08cc1b26..40e3bb042fa88 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/handle_old_settings.js +++ b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; +import { mockInjectedMetadata } from '../../services/telemetry_opt_in.test.mocks'; + import sinon from 'sinon'; -import { CONFIG_TELEMETRY } from '../../../../common/constants'; -import { handleOldSettings } from '../handle_old_settings'; -import { TelemetryOptInProvider } from '../../../services/telemetry_opt_in'; +import { CONFIG_TELEMETRY } from '../../../common/constants'; +import { handleOldSettings } from './handle_old_settings'; +import { TelemetryOptInProvider } from '../../services/telemetry_opt_in'; const getTelemetryOptInProvider = (enabled, { simulateFailure = false } = {}) => { const $http = { @@ -24,15 +25,13 @@ const getTelemetryOptInProvider = (enabled, { simulateFailure = false } = {}) => const chrome = { addBasePath: url => url }; + mockInjectedMetadata({ telemetryOptedIn: enabled }); const $injector = { get: (key) => { if (key === '$http') { return $http; } - if (key === 'telemetryOptedIn') { - return enabled; - } throw new Error(`unexpected mock injector usage for ${key}`); } }; @@ -50,22 +49,22 @@ describe('handle_old_settings', () => { }; const telemetryOptInProvider = getTelemetryOptInProvider(null); - expect(telemetryOptInProvider.getOptIn()).to.be(null); + expect(telemetryOptInProvider.getOptIn()).toBe(null); config.get.withArgs('xPackMonitoring:allowReport', null).returns(true); config.set.withArgs(CONFIG_TELEMETRY, true).returns(Promise.resolve(true)); - expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(false); + expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(false); - expect(config.get.calledTwice).to.be(true); - expect(config.set.called).to.be(false); + expect(config.get.calledTwice).toBe(true); + expect(config.set.called).toBe(false); - expect(config.remove.calledThrice).to.be(true); - expect(config.remove.getCall(0).args[0]).to.be('xPackMonitoring:allowReport'); - expect(config.remove.getCall(1).args[0]).to.be('xPackMonitoring:showBanner'); - expect(config.remove.getCall(2).args[0]).to.be(CONFIG_TELEMETRY); + expect(config.remove.calledThrice).toBe(true); + expect(config.remove.getCall(0).args[0]).toBe('xPackMonitoring:allowReport'); + expect(config.remove.getCall(1).args[0]).toBe('xPackMonitoring:showBanner'); + expect(config.remove.getCall(2).args[0]).toBe(CONFIG_TELEMETRY); - expect(telemetryOptInProvider.getOptIn()).to.be(true); + expect(telemetryOptInProvider.getOptIn()).toBe(true); }); it('re-uses old "telemetry:optIn" setting and stays opted in', async () => { @@ -76,22 +75,22 @@ describe('handle_old_settings', () => { }; const telemetryOptInProvider = getTelemetryOptInProvider(null); - expect(telemetryOptInProvider.getOptIn()).to.be(null); + expect(telemetryOptInProvider.getOptIn()).toBe(null); config.get.withArgs('xPackMonitoring:allowReport', null).returns(false); config.get.withArgs(CONFIG_TELEMETRY, null).returns(true); - expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(false); + expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(false); - expect(config.get.calledTwice).to.be(true); - expect(config.set.called).to.be(false); + expect(config.get.calledTwice).toBe(true); + expect(config.set.called).toBe(false); - expect(config.remove.calledThrice).to.be(true); - expect(config.remove.getCall(0).args[0]).to.be('xPackMonitoring:allowReport'); - expect(config.remove.getCall(1).args[0]).to.be('xPackMonitoring:showBanner'); - expect(config.remove.getCall(2).args[0]).to.be(CONFIG_TELEMETRY); + expect(config.remove.calledThrice).toBe(true); + expect(config.remove.getCall(0).args[0]).toBe('xPackMonitoring:allowReport'); + expect(config.remove.getCall(1).args[0]).toBe('xPackMonitoring:showBanner'); + expect(config.remove.getCall(2).args[0]).toBe(CONFIG_TELEMETRY); - expect(telemetryOptInProvider.getOptIn()).to.be(true); + expect(telemetryOptInProvider.getOptIn()).toBe(true); }); it('re-uses old "allowReport" setting and stays opted out', async () => { @@ -102,21 +101,21 @@ describe('handle_old_settings', () => { }; const telemetryOptInProvider = getTelemetryOptInProvider(null); - expect(telemetryOptInProvider.getOptIn()).to.be(null); + expect(telemetryOptInProvider.getOptIn()).toBe(null); config.get.withArgs('xPackMonitoring:allowReport', null).returns(false); config.set.withArgs(CONFIG_TELEMETRY, false).returns(Promise.resolve(true)); - expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(false); + expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(false); - expect(config.get.calledTwice).to.be(true); - expect(config.set.called).to.be(false); - expect(config.remove.calledThrice).to.be(true); - expect(config.remove.getCall(0).args[0]).to.be('xPackMonitoring:allowReport'); - expect(config.remove.getCall(1).args[0]).to.be('xPackMonitoring:showBanner'); - expect(config.remove.getCall(2).args[0]).to.be(CONFIG_TELEMETRY); + expect(config.get.calledTwice).toBe(true); + expect(config.set.called).toBe(false); + expect(config.remove.calledThrice).toBe(true); + expect(config.remove.getCall(0).args[0]).toBe('xPackMonitoring:allowReport'); + expect(config.remove.getCall(1).args[0]).toBe('xPackMonitoring:showBanner'); + expect(config.remove.getCall(2).args[0]).toBe(CONFIG_TELEMETRY); - expect(telemetryOptInProvider.getOptIn()).to.be(false); + expect(telemetryOptInProvider.getOptIn()).toBe(false); }); it('re-uses old "telemetry:optIn" setting and stays opted out', async () => { @@ -131,16 +130,16 @@ describe('handle_old_settings', () => { config.get.withArgs(CONFIG_TELEMETRY, null).returns(false); config.get.withArgs('xPackMonitoring:allowReport', null).returns(true); - expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(false); + expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(false); - expect(config.get.calledTwice).to.be(true); - expect(config.set.called).to.be(false); - expect(config.remove.calledThrice).to.be(true); - expect(config.remove.getCall(0).args[0]).to.be('xPackMonitoring:allowReport'); - expect(config.remove.getCall(1).args[0]).to.be('xPackMonitoring:showBanner'); - expect(config.remove.getCall(2).args[0]).to.be(CONFIG_TELEMETRY); + expect(config.get.calledTwice).toBe(true); + expect(config.set.called).toBe(false); + expect(config.remove.calledThrice).toBe(true); + expect(config.remove.getCall(0).args[0]).toBe('xPackMonitoring:allowReport'); + expect(config.remove.getCall(1).args[0]).toBe('xPackMonitoring:showBanner'); + expect(config.remove.getCall(2).args[0]).toBe(CONFIG_TELEMETRY); - expect(telemetryOptInProvider.getOptIn()).to.be(false); + expect(telemetryOptInProvider.getOptIn()).toBe(false); }); it('acknowledges users old setting even if re-setting fails', async () => { @@ -156,10 +155,10 @@ describe('handle_old_settings', () => { config.set.withArgs(CONFIG_TELEMETRY, false).returns(Promise.resolve(false)); // note: because it doesn't remove the old settings _and_ returns false, there's no risk of suddenly being opted in - expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(false); + expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(false); - expect(config.get.calledTwice).to.be(true); - expect(config.set.called).to.be(false); + expect(config.get.calledTwice).toBe(true); + expect(config.set.called).toBe(false); }); it('removes show banner setting and presents user with choice', async () => { @@ -173,11 +172,11 @@ describe('handle_old_settings', () => { config.get.withArgs('xPackMonitoring:allowReport', null).returns(null); config.get.withArgs('xPackMonitoring:showBanner', null).returns(false); - expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(true); + expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(true); - expect(config.get.calledThrice).to.be(true); - expect(config.remove.calledOnce).to.be(true); - expect(config.remove.getCall(0).args[0]).to.be('xPackMonitoring:showBanner'); + expect(config.get.calledThrice).toBe(true); + expect(config.remove.calledOnce).toBe(true); + expect(config.remove.getCall(0).args[0]).toBe('xPackMonitoring:showBanner'); }); it('is effectively ignored on fresh installs', async () => { @@ -190,9 +189,9 @@ describe('handle_old_settings', () => { config.get.withArgs('xPackMonitoring:allowReport', null).returns(null); config.get.withArgs('xPackMonitoring:showBanner', null).returns(null); - expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(true); + expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(true); - expect(config.get.calledThrice).to.be(true); + expect(config.get.calledThrice).toBe(true); }); }); diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/opt_in_banner_component.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/opt_in_banner_component.js deleted file mode 100644 index 75ec89b309dec..0000000000000 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/opt_in_banner_component.js +++ /dev/null @@ -1,147 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; - -import { - EuiButton, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { getConfigTelemetryDesc, PRIVACY_STATEMENT_URL } from '../../../common/constants'; -import { OptInExampleFlyout } from '../../components'; - -/** - * React component for displaying the Telemetry opt-in banner. - * - * TODO: When Jest tests become available in X-Pack, we should add one for this component. - */ -export class OptInBanner extends Component { - static propTypes = { - /** - * Callback function with no parameters that returns a {@code Promise} containing the - * telemetry data (expected to be an array). - */ - fetchTelemetry: PropTypes.func.isRequired, - /** - * Callback function passed a boolean to opt in ({@code true}) or out ({@code false}). - */ - optInClick: PropTypes.func.isRequired, - }; - - constructor(props) { - super(props); - - this.state = { - showDetails: false, - showExample: false, - }; - } - - render() { - let title = getConfigTelemetryDesc(); - let details; - let flyoutDetails; - - if (this.state.showDetails) { - details = ( - -

- this.setState({ showExample: !this.state.showExample })}> - - - ), - telemetryPrivacyStatementLink: ( - - - - ) - }} - /> -

-
- ); - - if (this.state.showExample) { - flyoutDetails = ( - this.setState({ showExample: false })} - fetchTelemetry={this.props.fetchTelemetry} - /> - ); - } - } else { - title = ( - - {getConfigTelemetryDesc()} {( - this.setState({ showDetails: true })}> - - - )} - - ); - } - - const titleNode = ( - {title} - ); - - return ( - - { details } - { flyoutDetails } - - - - this.props.optInClick(true)} - > - - - - - this.props.optInClick(false)} - > - - - - - - ); - } -} diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.js index 1fa1287cc2d98..9143d13069316 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.js +++ b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.js @@ -9,7 +9,7 @@ import React from 'react'; import { banners } from 'ui/notify'; import { clickBanner } from './click_banner'; -import { OptInBanner } from './opt_in_banner_component'; +import { OptInBanner } from '../../components/opt_in_banner_component'; /** * Render the Telemetry Opt-in banner. @@ -22,10 +22,12 @@ export function renderBanner(telemetryOptInProvider, fetchTelemetry, { _banners const bannerId = _banners.add({ component: ( clickBanner(bannerId, telemetryOptInProvider, optIn)} + optInClick={optIn => clickBanner(telemetryOptInProvider, optIn)} fetchTelemetry={fetchTelemetry} /> ), priority: 10000 }); + + telemetryOptInProvider.setBannerId(bannerId); } diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.test.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.test.js new file mode 100644 index 0000000000000..a55027703f951 --- /dev/null +++ b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.test.js @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderBanner } from './render_banner'; + +describe('render_banner', () => { + + it('adds a banner to banners with priority of 10000', () => { + const bannerID = 'brucer-banner'; + + const telemetryOptInProvider = { setBannerId: jest.fn() }; + const banners = { add: jest.fn().mockReturnValue(bannerID) }; + const fetchTelemetry = jest.fn(); + + renderBanner(telemetryOptInProvider, fetchTelemetry, { _banners: banners }); + + expect(banners.add).toBeCalledTimes(1); + expect(fetchTelemetry).toBeCalledTimes(0); + expect(telemetryOptInProvider.setBannerId).toBeCalledWith(bannerID); + + const bannerConfig = banners.add.mock.calls[0][0]; + + expect(bannerConfig.component).not.toBe(undefined); + expect(bannerConfig.priority).toBe(10000); + }); + +}); diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/should_show_banner.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/should_show_banner.test.js similarity index 69% rename from x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/should_show_banner.js rename to x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/should_show_banner.test.js index 8712fa55d3d79..1bfe7e954738e 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/should_show_banner.js +++ b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/should_show_banner.test.js @@ -4,18 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; +import { mockInjectedMetadata } from '../../services/telemetry_opt_in.test.mocks'; + import sinon from 'sinon'; -import { CONFIG_TELEMETRY } from '../../../../common/constants'; -import { shouldShowBanner } from '../should_show_banner'; -import { TelemetryOptInProvider } from '../../../services/telemetry_opt_in'; +import { CONFIG_TELEMETRY } from '../../../common/constants'; +import { shouldShowBanner } from './should_show_banner'; +import { TelemetryOptInProvider } from '../../services/telemetry_opt_in'; -const getMockInjector = ({ telemetryEnabled }) => { +const getMockInjector = () => { const get = sinon.stub(); - get.withArgs('telemetryOptedIn').returns(telemetryEnabled); - const mockHttp = { post: sinon.stub() }; @@ -25,8 +24,9 @@ const getMockInjector = ({ telemetryEnabled }) => { return { get }; }; -const getTelemetryOptInProvider = ({ telemetryEnabled = null } = {}) => { - const injector = getMockInjector({ telemetryEnabled }); +const getTelemetryOptInProvider = ({ telemetryOptedIn = null } = {}) => { + mockInjectedMetadata({ telemetryOptedIn }); + const injector = getMockInjector(); const chrome = { addBasePath: (url) => url }; @@ -49,28 +49,28 @@ describe('should_show_banner', () => { const showBannerTrue = await shouldShowBanner(telemetryOptInProvider, config, { _handleOldSettings: handleOldSettingsTrue }); const showBannerFalse = await shouldShowBanner(telemetryOptInProvider, config, { _handleOldSettings: handleOldSettingsFalse }); - expect(showBannerTrue).to.be(true); - expect(showBannerFalse).to.be(false); + expect(showBannerTrue).toBe(true); + expect(showBannerFalse).toBe(false); - expect(config.get.callCount).to.be(0); - expect(handleOldSettingsTrue.calledOnce).to.be(true); - expect(handleOldSettingsFalse.calledOnce).to.be(true); + expect(config.get.callCount).toBe(0); + expect(handleOldSettingsTrue.calledOnce).toBe(true); + expect(handleOldSettingsFalse.calledOnce).toBe(true); }); it('returns false if telemetry opt-in setting is set to true', async () => { const config = { get: sinon.stub() }; - const telemetryOptInProvider = getTelemetryOptInProvider({ telemetryEnabled: true }); + const telemetryOptInProvider = getTelemetryOptInProvider({ telemetryOptedIn: true }); - expect(await shouldShowBanner(telemetryOptInProvider, config)).to.be(false); + expect(await shouldShowBanner(telemetryOptInProvider, config)).toBe(false); }); it('returns false if telemetry opt-in setting is set to false', async () => { const config = { get: sinon.stub() }; - const telemetryOptInProvider = getTelemetryOptInProvider({ telemetryEnabled: false }); + const telemetryOptInProvider = getTelemetryOptInProvider({ telemetryOptedIn: false }); - expect(await shouldShowBanner(telemetryOptInProvider, config)).to.be(false); + expect(await shouldShowBanner(telemetryOptInProvider, config)).toBe(false); }); }); diff --git a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.js b/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.js index 9e032c27baa14..5b93f84eabf4a 100644 --- a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.js +++ b/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.js @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { mockInjectedMetadata } from './telemetry_opt_in.test.mocks'; import { TelemetryOptInProvider } from './telemetry_opt_in'; describe('TelemetryOptInProvider', () => { @@ -19,12 +20,11 @@ describe('TelemetryOptInProvider', () => { addBasePath: (url) => url }; + mockInjectedMetadata({ telemetryOptedIn: optedIn }); + const mockInjector = { get: (key) => { switch (key) { - case 'telemetryOptedIn': { - return optedIn; - } case '$http': { return mockHttp; } @@ -77,4 +77,11 @@ describe('TelemetryOptInProvider', () => { // opt-in change should not be reflected expect(provider.getOptIn()).toEqual(false); }); + + it('should return the current bannerId', () => { + const { provider } = setup({}); + const bannerId = 'bruce-banner'; + provider.setBannerId(bannerId); + expect(provider.getBannerId()).toEqual(bannerId); + }); }); diff --git a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.mocks.js b/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.mocks.js new file mode 100644 index 0000000000000..63bfcb4b2a327 --- /dev/null +++ b/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.mocks.js @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { injectedMetadataServiceMock } from '../../../../../../src/core/public/mocks'; +const injectedMetadataMock = injectedMetadataServiceMock.createStartContract(); + +export function mockInjectedMetadata({ telemetryOptedIn }) { + const mockGetInjectedVar = jest.fn().mockImplementation((key) => { + switch (key) { + case 'telemetryOptedIn': return telemetryOptedIn; + default: throw new Error(`unexpected injectedVar ${key}`); + } + }); + + injectedMetadataMock.getInjectedVar = mockGetInjectedVar; +} + +jest.doMock('ui/new_platform', () => ({ + npStart: { + core: { + injectedMetadata: injectedMetadataMock + }, + }, +})); diff --git a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.ts b/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.ts new file mode 100644 index 0000000000000..9c1bfde1d27b9 --- /dev/null +++ b/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// @ts-ignore +export { TelemetryOptInProvider } from '../../../../../../src/legacy/core_plugins/kibana/public/home/telemetry_opt_in'; // eslint-disable-line diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e938f64a3f4ca..b43df00186bf2 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2450,6 +2450,8 @@ "kbn.visualize.wizard.step1Breadcrumb": "作成", "kbn.visualize.wizard.step2Breadcrumb": "作成", "kbn.visualizeTitle": "可視化", + "kbn.home.telemetry.optInErrorToastText": "使用状況統計設定の設定中にエラーが発生しました。", + "kbn.home.telemetry.optInErrorToastTitle": "エラー", "kbnDocViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip": "{underscoreSign} で始まるフィールド名はサポートされていません", "kbnDocViews.table.filterForFieldPresentButtonAriaLabel": "現在のフィールドのフィルター", "kbnDocViews.table.filterForFieldPresentButtonTooltip": "現在のフィールドのフィルター", @@ -10155,8 +10157,6 @@ "xpack.telemetry.callout.errorLoadingClusterStatisticsTitle": "クラスター統計の読み込みエラー", "xpack.telemetry.callout.errorUnprivilegedUserDescription": "暗号化されていないクラスター統計を表示するアクセス権がありません。", "xpack.telemetry.callout.errorUnprivilegedUserTitle": "クラスター統計の表示エラー", - "xpack.telemetry.optInErrorToastText": "使用状況統計設定の設定中にエラーが発生しました。", - "xpack.telemetry.optInErrorToastTitle": "エラー", "xpack.telemetry.readOurUsageDataPrivacyStatementLinkText": "使用データのプライバシーステートメントをお読みください", "xpack.telemetry.seeExampleOfWhatWeCollectLinkText": "収集されるデータの例を見る", "xpack.telemetry.telemetryConfigDescription": "基本的な機能利用に関する統計情報を提供して Elastic Stack の改善にご協力ください。このデータは Elastic 社外と共有されません。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 95f6172898130..a1d580fb5ad88 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2451,6 +2451,8 @@ "kbn.visualize.wizard.step1Breadcrumb": "创建", "kbn.visualize.wizard.step2Breadcrumb": "创建", "kbn.visualizeTitle": "可视化", + "kbn.home.telemetry.optInErrorToastText": "尝试设置使用统计信息首选项时发生错误。", + "kbn.home.telemetry.optInErrorToastTitle": "错误", "kbnDocViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip": "不支持以 {underscoreSign} 开头的字段名称", "kbnDocViews.table.filterForFieldPresentButtonAriaLabel": "筛留存在的字段", "kbnDocViews.table.filterForFieldPresentButtonTooltip": "筛留存在的字段", @@ -10298,8 +10300,6 @@ "xpack.telemetry.callout.errorLoadingClusterStatisticsTitle": "加载集群统计信息时出错", "xpack.telemetry.callout.errorUnprivilegedUserDescription": "您无权查看未加密的集群统计信息。", "xpack.telemetry.callout.errorUnprivilegedUserTitle": "显示集群统计信息时出错", - "xpack.telemetry.optInErrorToastText": "尝试设置使用统计信息首选项时发生错误。", - "xpack.telemetry.optInErrorToastTitle": "错误", "xpack.telemetry.readOurUsageDataPrivacyStatementLinkText": "阅读我们的使用情况数据隐私声明", "xpack.telemetry.seeExampleOfWhatWeCollectLinkText": "查看我们收集的内容示例", "xpack.telemetry.telemetryConfigDescription": "通过提供基本功能的使用情况统计信息,来帮助我们改进 Elastic Stack。我们不会在 Elastic 之外共享此数据。",