Telemetry/opt in welcome screen (#42110)

* refactor opt_in_message

* welcome optin card

* finish optin in welcome screen

* add  steps

* disable  current step

* remove checkbox option

* add metrics

* fix  typescript checks

* hide in oss

* update snapshots

* only require TelemetryOptInProvider if telemetry is enabled

* pass telemetry description from service

* remove x-pack import

* remove x-pack import

* update image

* update per design feedback

* update props

* fix in oss

* await before moving to next screen

* utlize bannerId in telemtry provider

* keep export in 1 line

* ts-ignore banner import

* add test

* update tests

* update translations

* update home tests

* remove extra license header

* showTelemetryOptIn -> shouldShowTelemetryOptIn

* remote extra EuiTexts

* update jest snapshot

* unencrypted telemetry example
This commit is contained in:
Ahmad Bamieh 2019-08-27 13:34:56 +03:00 committed by GitHub
parent 1d0c1c2196
commit 5917cd375f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1099 additions and 779 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 KiB

View file

@ -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`] = `
<Welcome
fetchTelemetry={[MockFunction]}
getTelemetryBannerId={[MockFunction]}
onSkip={[Function]}
setOptIn={[MockFunction]}
urlBasePath="goober"
/>
`;

View file

@ -1,6 +1,5 @@
.homWelcome {
@include kibanaFullScreenGraphics;
@include kibanaFullScreenGraphics($euiZLevel6);
}
.homWelcome__header {

View file

@ -225,6 +225,10 @@ export class Home extends Component {
<Welcome
onSkip={this.skipWelcome}
urlBasePath={this.props.urlBasePath}
shouldShowTelemetryOptIn={this.props.shouldShowTelemetryOptIn}
fetchTelemetry={this.props.fetchTelemetry}
setOptIn={this.props.setOptIn}
getTelemetryBannerId={this.props.getTelemetryBannerId}
/>
);
}
@ -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,

View file

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

View file

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

View file

@ -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}
/>
</Route>
</Switch>

View file

@ -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 (
<EuiCard
image={`${urlBasePath}/plugins/kibana/assets/illo_dashboard.png`}
textAlign="left"
title={<FormattedMessage id="kbn.home.letsStartTitle" defaultMessage="Let's get started" />}
description={
<FormattedMessage
id="kbn.home.letsStartDescription"
defaultMessage="We noticed that you don't have any data in your cluster.
You can try our sample data and dashboards or jump in with your own data."
/>
}
footer={
<footer>
<EuiButton fill className="homWelcome__footerAction" onClick={onConfirm}>
<FormattedMessage id="kbn.home.tryButtonLabel" defaultMessage="Try our sample data" />
</EuiButton>
<EuiButtonEmpty
className="homWelcome__footerAction"
onClick={onDecline}
data-test-subj="skipWelcomeScreen"
>
<FormattedMessage id="kbn.home.exploreButtonLabel" defaultMessage="Explore on my own" />
</EuiButtonEmpty>
</footer>
}
/>
);
}

View file

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

View file

@ -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<any>;
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<Props, State> {
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 (
<EuiFlexGroup justifyContent="spaceAround">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="xl" />
</EuiFlexItem>
</EuiFlexGroup>
);
}
if (!hasPrivilegeToRead) {
return (
<EuiCallOut
title={
<FormattedMessage
id="kbn.home.telemetry.callout.errorUnprivilegedUserTitle"
defaultMessage="Error displaying cluster statistics"
/>
}
color="danger"
iconType="cross"
>
<FormattedMessage
id="kbn.home.telemetry.callout.errorUnprivilegedUserDescription"
defaultMessage="You do not have access to see unencrypted cluster statistics."
/>
</EuiCallOut>
);
}
if (data === null) {
return (
<EuiCallOut
title={
<FormattedMessage
id="kbn.home.telemetry.callout.errorLoadingClusterStatisticsTitle"
defaultMessage="Error loading cluster statistics"
/>
}
color="danger"
iconType="cross"
>
<FormattedMessage
id="kbn.home.telemetry.callout.errorLoadingClusterStatisticsDescription"
defaultMessage="An unexpected error occured while attempting to fetch the cluster statistics.
This can occur because Elasticsearch failed, Kibana failed, or there is a network error.
Check Kibana, then reload the page and try again."
/>
</EuiCallOut>
);
}
return <EuiCodeBlock language="js">{JSON.stringify(data, null, 2)}</EuiCodeBlock>;
}
render() {
return (
<EuiPortal>
<EuiFlyout ownFocus onClose={this.props.onClose} maxWidth={true}>
<EuiFlyoutHeader>
<EuiTitle>
<h2>
<FormattedMessage
id="kbn.home.telemetry.callout.clusterStatisticsTitle"
defaultMessage="Cluster statistics"
/>
</h2>
</EuiTitle>
<EuiTextColor color="subdued">
<EuiText>
<FormattedMessage
id="kbn.home.telemetry.callout.clusterStatisticsDescription"
defaultMessage="This is an example of the basic cluster statistics that we'll collect.
It includes the number of indices, shards, and nodes.
It also includes high-level usage statistics, such as whether monitoring is turned on."
/>
</EuiText>
</EuiTextColor>
</EuiFlyoutHeader>
<EuiFlyoutBody>{this.renderBody(this.state)}</EuiFlyoutBody>
</EuiFlyout>
</EuiPortal>
);
}
}

View file

@ -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<any[]>;
}
interface State {
showDetails: boolean;
showExample: boolean;
}
export class OptInMessage extends React.PureComponent<Props, State> {
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 = () => (
<FormattedMessage
id="kbn.home.telemetry.optInMessage.detailsDescription"
defaultMessage="No information about the data you process or store will be sent. This feature will periodically send basic feature usage statistics. See an {exampleLink} or read our {telemetryPrivacyStatementLink}. You can disable this feature at any time."
values={{
exampleLink: (
<EuiLink onClick={this.toggleShowExample}>
<FormattedMessage
id="kbn.home.telemetry.optInMessage.detailsExampleLinkText"
defaultMessage="example"
/>
</EuiLink>
),
telemetryPrivacyStatementLink: (
<EuiLink
href="https://www.elastic.co/legal/telemetry-privacy-statement"
target="_blank"
>
<FormattedMessage
id="kbn.home.telemetry.optInMessage.detailsTelemetryPrivacyStatementLinkText"
defaultMessage="telemetry privacy statement"
/>
</EuiLink>
),
}}
/>
);
const getFlyoutDetails = () => (
<OptInExampleFlyout
onClose={() => this.setState({ showExample: false })}
fetchTelemetry={fetchTelemetry}
/>
);
const getReadMore = () => (
<EuiLink onClick={() => this.setState({ showDetails: true })}>
<FormattedMessage
id="kbn.home.telemetry.optInMessage.readMoreLinkText"
defaultMessage="Read more"
/>
</EuiLink>
);
return (
<React.Fragment>
<FormattedMessage
id="kbn.home.telemetry.optInMessageDescription"
defaultMessage="Help us improve the Elastic Stack by providing usage statistics for basic features. We will not share this data outside of Elastic."
/>{' '}
{!showDetails && getReadMore()}
{showDetails && getDetails()}
{showDetails && showExample && getFlyoutDetails()}
</React.Fragment>
);
}
}

View file

@ -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<any[]>;
}
export function renderTelemetryOptInCard({
urlBasePath,
fetchTelemetry,
onConfirm,
onDecline,
}: Props) {
return (
<EuiCard
image={`${urlBasePath}/plugins/kibana/assets/illo_telemetry.png`}
textAlign="left"
title={
<FormattedMessage
id="kbn.home.telemtery.optInCardTitle"
defaultMessage="Help us improve the Elastic Stack"
/>
}
description={<OptInMessage fetchTelemetry={fetchTelemetry} />}
footer={
<footer>
<EuiButton
onClick={onConfirm}
className="homWelcome__footerAction"
data-test-subj="WelcomeScreenOptInConfirm"
fill
>
<FormattedMessage
id="kbn.home.telemtery.optInCardConfirmButtonLabel"
defaultMessage="Yes"
/>
</EuiButton>
<EuiButton
className="homWelcome__footerAction"
onClick={onDecline}
data-test-subj="WelcomeScreenOptInCancel"
fill
>
<FormattedMessage
id="kbn.home.telemtery.optInCardDeclineButtonLabel"
defaultMessage="No"
/>
</EuiButton>
</footer>
}
/>
);
}

View file

@ -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 (
<EuiPortal>
<div className="homWelcome">
<header className="homWelcome__header">
<div className="homWelcome__content eui-textCenter">
<EuiSpacer size="xxl" />
<span className="homWelcome__logo">
<EuiIcon type="logoKibana" size="xxl" />
</span>
<EuiTitle size="l" className="homWelcome__title">
<h1>
<FormattedMessage id="kbn.home.welcomeTitle" defaultMessage="Welcome to Kibana"/>
</h1>
</EuiTitle>
<EuiText size="s" color="subdued" className="homWelcome__subtitle">
<p>
<FormattedMessage id="kbn.home.welcomeDescription" defaultMessage="Your window into the Elastic Stack"/>
</p>
</EuiText>
<EuiSpacer size="xl" />
</div>
</header>
<div className="homWelcome__content homWelcome-body">
<EuiFlexGroup gutterSize="l">
<EuiFlexItem>
<EuiCard
image={`${urlBasePath}/plugins/kibana/assets/illo_dashboard.png`}
textAlign="left"
title={<FormattedMessage id="kbn.home.letsStartTitle" defaultMessage="Let's get started"/>}
description={
<FormattedMessage
id="kbn.home.letsStartDescription"
defaultMessage="We noticed that you don't have any data in your cluster.
You can try our sample data and dashboards or jump in with your own data."
/>}
footer={
<footer>
<EuiButton
fill
className="homWelcome__footerAction"
href="#/home/tutorial_directory/sampleData"
>
<FormattedMessage id="kbn.home.tryButtonLabel" defaultMessage="Try our sample data"/>
</EuiButton>
<EuiButtonEmpty
className="homWelcome__footerAction"
onClick={onSkip}
data-test-subj="skipWelcomeScreen"
>
<FormattedMessage id="kbn.home.exploreButtonLabel" defaultMessage="Explore on my own"/>
</EuiButtonEmpty>
</footer>
}
/>
</EuiFlexItem>
</EuiFlexGroup>
</div>
</div>
</EuiPortal>
);
}
}
Welcome.propTypes = {
urlBasePath: PropTypes.string.isRequired,
onSkip: PropTypes.func.isRequired,
};

View file

@ -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<any[]>;
setOptIn: (enabled: boolean) => Promise<boolean>;
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<Props, State> {
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 (
<EuiPortal>
<div className="homWelcome">
<header className="homWelcome__header">
<div className="homWelcome__content eui-textCenter">
<EuiSpacer size="xl" />
<span className="homWelcome__logo">
<EuiIcon type="logoKibana" size="xxl" />
</span>
<EuiTitle size="l" className="homWelcome__title">
<h1>
<FormattedMessage id="kbn.home.welcomeTitle" defaultMessage="Welcome to Kibana" />
</h1>
</EuiTitle>
<EuiText size="s" color="subdued" className="homWelcome__subtitle">
<p>
<FormattedMessage
id="kbn.home.welcomeDescription"
defaultMessage="Your window into the Elastic Stack"
/>
</p>
</EuiText>
<EuiSpacer size="m" />
</div>
</header>
<div className="homWelcome__content homWelcome-body">
<EuiFlexGroup gutterSize="l">
<EuiFlexItem>
{shouldShowTelemetryOptIn && step === 0 && (
<TelemetryOptInCard
urlBasePath={urlBasePath}
fetchTelemetry={fetchTelemetry}
onConfirm={this.handleTelemetrySelection.bind(this, true)}
onDecline={this.handleTelemetrySelection.bind(this, false)}
/>
)}
{(!shouldShowTelemetryOptIn || step === 1) && (
<SampleDataCard
urlBasePath={urlBasePath}
onConfirm={this.onSampleDataConfirm}
onDecline={this.onSampleDataDecline}
/>
)}
<EuiSpacer size="xs" />
</EuiFlexItem>
</EuiFlexGroup>
</div>
</div>
</EuiPortal>
);
}
}

View file

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

View file

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

View file

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

View file

@ -104,277 +104,7 @@ exports[`TelemetryOptIn should display when telemetry not opted in 1`] = `
"timeZone": null,
}
}
>
<EuiSpacer
size="s"
>
<div
className="euiSpacer euiSpacer--s"
/>
</EuiSpacer>
<EuiTitle
size="s"
>
<h4
className="euiTitle euiTitle--small"
>
<FormattedMessage
defaultMessage="Help Elastic support provide better service"
id="xpack.licenseMgmt.telemetryOptIn.customersHelpSupportDescription"
values={Object {}}
>
Help Elastic support provide better service
</FormattedMessage>
</h4>
</EuiTitle>
<EuiSpacer
size="s"
>
<div
className="euiSpacer euiSpacer--s"
/>
</EuiSpacer>
<EuiCheckbox
checked={false}
compressed={false}
disabled={false}
id="isOptingInToTelemetry"
indeterminate={false}
label={
<span>
<FormattedMessage
defaultMessage="Send basic feature usage statistics to Elastic periodically. {popover}"
id="xpack.licenseMgmt.telemetryOptIn.sendBasicFeatureStatisticsLabel"
values={
Object {
"popover": <EuiPopover
anchorPosition="downCenter"
button={
<EuiLink
color="primary"
onClick={[Function]}
type="button"
>
<FormattedMessage
defaultMessage="Read more"
id="xpack.licenseMgmt.telemetryOptIn.readMoreLinkText"
values={Object {}}
/>
</EuiLink>
}
className="eui-AlignBaseline"
closePopover={[Function]}
display="inlineBlock"
hasArrow={true}
id="readMorePopover"
isOpen={false}
ownFocus={true}
panelPaddingSize="m"
>
<EuiText
className="licManagement__narrowText"
>
<p>
<FormattedMessage
defaultMessage="This feature periodically sends basic feature usage statistics. This information will not be shared outside of Elastic. See an {exampleLink} or read our {telemetryPrivacyStatementLink}. You can disable this feature any time."
id="xpack.licenseMgmt.telemetryOptIn.featureUsageWarningMessage"
values={
Object {
"exampleLink": <EuiLink
color="primary"
onClick={[Function]}
type="button"
>
<FormattedMessage
defaultMessage="example"
id="xpack.licenseMgmt.telemetryOptIn.exampleLinkText"
values={Object {}}
/>
</EuiLink>,
"telemetryPrivacyStatementLink": <EuiLink
color="primary"
href="https://www.elastic.co/legal/telemetry-privacy-statement"
target="_blank"
type="button"
>
<FormattedMessage
defaultMessage="telemetry privacy statement"
id="xpack.licenseMgmt.telemetryOptIn.telemetryPrivacyStatementLinkText"
values={Object {}}
/>
</EuiLink>,
}
}
/>
</p>
</EuiText>
</EuiPopover>,
}
}
/>
</span>
}
onChange={[Function]}
>
<div
className="euiCheckbox"
>
<input
checked={false}
className="euiCheckbox__input"
disabled={false}
id="isOptingInToTelemetry"
onChange={[Function]}
type="checkbox"
/>
<div
className="euiCheckbox__square"
/>
<label
className="euiCheckbox__label"
htmlFor="isOptingInToTelemetry"
>
<span>
<FormattedMessage
defaultMessage="Send basic feature usage statistics to Elastic periodically. {popover}"
id="xpack.licenseMgmt.telemetryOptIn.sendBasicFeatureStatisticsLabel"
values={
Object {
"popover": <EuiPopover
anchorPosition="downCenter"
button={
<EuiLink
color="primary"
onClick={[Function]}
type="button"
>
<FormattedMessage
defaultMessage="Read more"
id="xpack.licenseMgmt.telemetryOptIn.readMoreLinkText"
values={Object {}}
/>
</EuiLink>
}
className="eui-AlignBaseline"
closePopover={[Function]}
display="inlineBlock"
hasArrow={true}
id="readMorePopover"
isOpen={false}
ownFocus={true}
panelPaddingSize="m"
>
<EuiText
className="licManagement__narrowText"
>
<p>
<FormattedMessage
defaultMessage="This feature periodically sends basic feature usage statistics. This information will not be shared outside of Elastic. See an {exampleLink} or read our {telemetryPrivacyStatementLink}. You can disable this feature any time."
id="xpack.licenseMgmt.telemetryOptIn.featureUsageWarningMessage"
values={
Object {
"exampleLink": <EuiLink
color="primary"
onClick={[Function]}
type="button"
>
<FormattedMessage
defaultMessage="example"
id="xpack.licenseMgmt.telemetryOptIn.exampleLinkText"
values={Object {}}
/>
</EuiLink>,
"telemetryPrivacyStatementLink": <EuiLink
color="primary"
href="https://www.elastic.co/legal/telemetry-privacy-statement"
target="_blank"
type="button"
>
<FormattedMessage
defaultMessage="telemetry privacy statement"
id="xpack.licenseMgmt.telemetryOptIn.telemetryPrivacyStatementLinkText"
values={Object {}}
/>
</EuiLink>,
}
}
/>
</p>
</EuiText>
</EuiPopover>,
}
}
>
Send basic feature usage statistics to Elastic periodically.
<EuiPopover
anchorPosition="downCenter"
button={
<EuiLink
color="primary"
onClick={[Function]}
type="button"
>
<FormattedMessage
defaultMessage="Read more"
id="xpack.licenseMgmt.telemetryOptIn.readMoreLinkText"
values={Object {}}
/>
</EuiLink>
}
className="eui-AlignBaseline"
closePopover={[Function]}
display="inlineBlock"
hasArrow={true}
id="readMorePopover"
isOpen={false}
ownFocus={true}
panelPaddingSize="m"
>
<EuiOutsideClickDetector
isDisabled={true}
onOutsideClick={[Function]}
>
<div
className="euiPopover euiPopover--anchorDownCenter eui-AlignBaseline"
id="readMorePopover"
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
>
<div
className="euiPopover__anchor"
>
<EuiLink
color="primary"
onClick={[Function]}
type="button"
>
<button
className="euiLink euiLink--primary"
onClick={[Function]}
type="button"
>
<FormattedMessage
defaultMessage="Read more"
id="xpack.licenseMgmt.telemetryOptIn.readMoreLinkText"
values={Object {}}
>
Read more
</FormattedMessage>
</button>
</EuiLink>
</div>
</div>
</EuiOutsideClickDetector>
</EuiPopover>
</FormattedMessage>
</span>
</label>
</div>
</EuiCheckbox>
</TelemetryOptIn>
/>
`;
exports[`TelemetryOptIn should not display when telemetry is opted in 1`] = `

View file

@ -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 {
</EuiPopover>
);
return showTelemetryOptIn() ? (
return shouldShowTelemetryOptIn() ? (
<Fragment>
{example}
{toCurrentCustomers}

View file

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

View file

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

View file

@ -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<any[]>;
optInClick: (optIn: boolean) => void;
}
/**
* React component for displaying the Telemetry opt-in banner.
*/
export class OptInBanner extends React.PureComponent<Props> {
render() {
const title = (
<FormattedMessage
id="xpack.telemetry.welcomeBanner.title"
defaultMessage="Help us improve the Elastic Stack!"
/>
);
return (
<EuiCallOut iconType="questionInCircle" title={title}>
<OptInMessage fetchTelemetry={this.props.fetchTelemetry} />
<EuiSpacer size="s" />
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiButton size="s" onClick={() => this.props.optInClick(true)}>
<FormattedMessage
id="xpack.telemetry.welcomeBanner.yesButtonLabel"
defaultMessage="Yes"
/>
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton size="s" onClick={() => this.props.optInClick(false)}>
<FormattedMessage
id="xpack.telemetry.welcomeBanner.noButtonLabel"
defaultMessage="No"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiCallOut>
);
}
}

View file

@ -9,8 +9,13 @@ import { OptInExampleFlyout } from './opt_in_details_component';
describe('OptInDetailsComponent', () => {
it('renders as expected', () => {
expect(shallowWithIntl(
<OptInExampleFlyout fetchTelemetry={jest.fn(async () => ({ data: [] }))} onClose={jest.fn()} />)
expect(
shallowWithIntl(
<OptInExampleFlyout
fetchTelemetry={jest.fn(async () => ({ data: [] }))}
onClose={jest.fn()}
/>
)
).toMatchSnapshot();
});
});

View file

@ -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<any>;
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<Props, State> {
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 (
<EuiFlexGroup justifyContent="spaceAround">
@ -79,10 +76,12 @@ export class OptInExampleFlyout extends Component {
if (!hasPrivilegeToRead) {
return (
<EuiCallOut
title={<FormattedMessage
id="xpack.telemetry.callout.errorUnprivilegedUserTitle"
defaultMessage="Error displaying cluster statistics"
/>}
title={
<FormattedMessage
id="xpack.telemetry.callout.errorUnprivilegedUserTitle"
defaultMessage="Error displaying cluster statistics"
/>
}
color="danger"
iconType="cross"
>
@ -97,10 +96,12 @@ export class OptInExampleFlyout extends Component {
if (data === null) {
return (
<EuiCallOut
title={<FormattedMessage
id="xpack.telemetry.callout.errorLoadingClusterStatisticsTitle"
defaultMessage="Error loading cluster statistics"
/>}
title={
<FormattedMessage
id="xpack.telemetry.callout.errorLoadingClusterStatisticsTitle"
defaultMessage="Error loading cluster statistics"
/>
}
color="danger"
iconType="cross"
>
@ -114,21 +115,13 @@ export class OptInExampleFlyout extends Component {
);
}
return (
<EuiCodeBlock language="js">
{JSON.stringify(data, null, 2)}
</EuiCodeBlock>
);
return <EuiCodeBlock language="js">{JSON.stringify(data, null, 2)}</EuiCodeBlock>;
}
render() {
return (
<EuiPortal>
<EuiFlyout
ownFocus
onClose={this.props.onClose}
maxWidth={true}
>
<EuiFlyout ownFocus onClose={this.props.onClose} maxWidth={true}>
<EuiFlyoutHeader>
<EuiTitle>
<h2>
@ -149,12 +142,9 @@ export class OptInExampleFlyout extends Component {
</EuiText>
</EuiTextColor>
</EuiFlyoutHeader>
<EuiFlyoutBody>
{this.renderBody(this.state)}
</EuiFlyoutBody>
<EuiFlyoutBody>{this.renderBody(this.state)}</EuiFlyoutBody>
</EuiFlyout>
</EuiPortal>
);
}
}

View file

@ -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<any[]>;
}
interface State {
showDetails: boolean;
showExample: boolean;
}
export class OptInMessage extends React.PureComponent<Props, State> {
public readonly state: State = {
showDetails: false,
showExample: false,
};
toggleShowExample = () => {
this.setState(prevState => ({
showExample: !prevState.showExample,
}));
};
render() {
const { showDetails, showExample } = this.state;
const getDetails = () => (
<EuiText size="s">
<p tab-index="0">
<FormattedMessage
id="xpack.telemetry.welcomeBanner.telemetryConfigDetailsDescription"
defaultMessage="No information about the data you process or store will be sent. This feature
will periodically send basic feature usage statistics. See an {exampleLink} or read our {telemetryPrivacyStatementLink}.
You can disable this feature at any time."
values={{
exampleLink: (
<EuiLink onClick={this.toggleShowExample}>
<FormattedMessage
id="xpack.telemetry.welcomeBanner.telemetryConfigDetailsDescription.exampleLinkText"
defaultMessage="example"
/>
</EuiLink>
),
telemetryPrivacyStatementLink: (
<EuiLink href={PRIVACY_STATEMENT_URL} target="_blank">
<FormattedMessage
id="xpack.telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText"
defaultMessage="telemetry privacy statement"
/>
</EuiLink>
),
}}
/>
</p>
</EuiText>
);
const getFlyoutDetails = () => (
<OptInExampleFlyout
onClose={() => this.setState({ showExample: false })}
fetchTelemetry={this.props.fetchTelemetry}
/>
);
const getReadMore = () => (
<EuiLink onClick={() => this.setState({ showDetails: true })}>
<FormattedMessage
id="xpack.telemetry.welcomeBanner.telemetryConfigDescription.readMoreLinkText"
defaultMessage="Read more"
/>
</EuiLink>
);
return (
<React.Fragment>
<EuiText>
<p tab-index="0">
{getConfigTelemetryDesc()} {!showDetails && getReadMore()}
</p>
</EuiText>
{showDetails && getDetails()}
{showDetails && showExample && getFlyoutDetails()}
</React.Fragment>
);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 = (
<EuiText>
<p tabIndex="0">
<FormattedMessage
id="xpack.telemetry.welcomeBanner.telemetryConfigDetailsDescription"
defaultMessage="No information about the data you process or store will be sent. This feature
will periodically send basic feature usage statistics. See an {exampleLink} or read our {telemetryPrivacyStatementLink}.
You can disable this feature at any time."
values={{
exampleLink: (
<EuiLink onClick={() => this.setState({ showExample: !this.state.showExample })}>
<FormattedMessage
id="xpack.telemetry.welcomeBanner.telemetryConfigDetailsDescription.exampleLinkText"
defaultMessage="example"
/>
</EuiLink>
),
telemetryPrivacyStatementLink: (
<EuiLink href={PRIVACY_STATEMENT_URL} target="_blank" >
<FormattedMessage
id="xpack.telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText"
defaultMessage="telemetry privacy statement"
/>
</EuiLink>
)
}}
/>
</p>
</EuiText>
);
if (this.state.showExample) {
flyoutDetails = (
<OptInExampleFlyout
onClose={() => this.setState({ showExample: false })}
fetchTelemetry={this.props.fetchTelemetry}
/>
);
}
} else {
title = (
<Fragment>
{getConfigTelemetryDesc()} {(
<EuiLink onClick={() => this.setState({ showDetails: true })}>
<FormattedMessage
id="xpack.telemetry.welcomeBanner.telemetryConfigDescription.readMoreLinkText"
defaultMessage="Read more"
/>
</EuiLink>
)}
</Fragment>
);
}
const titleNode = (
<span tabIndex="0">{title}</span>
);
return (
<EuiCallOut iconType="questionInCircle" title={titleNode}>
{ details }
{ flyoutDetails }
<EuiSpacer size="s" />
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiButton
size="s"
onClick={() => this.props.optInClick(true)}
>
<FormattedMessage
id="xpack.telemetry.welcomeBanner.yesButtonLabel"
defaultMessage="Yes"
/>
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
size="s"
onClick={() => this.props.optInClick(false)}
>
<FormattedMessage
id="xpack.telemetry.welcomeBanner.noButtonLabel"
defaultMessage="No"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiCallOut>
);
}
}

View file

@ -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: (
<OptInBanner
optInClick={optIn => clickBanner(bannerId, telemetryOptInProvider, optIn)}
optInClick={optIn => clickBanner(telemetryOptInProvider, optIn)}
fetchTelemetry={fetchTelemetry}
/>
),
priority: 10000
});
telemetryOptInProvider.setBannerId(bannerId);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2432,6 +2432,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": "現在のフィールドのフィルター",
@ -10125,8 +10127,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 社外と共有されません。",

View file

@ -2432,6 +2432,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": "筛留存在的字段",
@ -10267,8 +10269,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 之外共享此数据。",