mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
# Backport This will backport the following commits from `main` to `8.8`: - [[Telemetry] Update notice message (#158669)](https://github.com/elastic/kibana/pull/158669) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Alejandro Fernández Haro","email":"alejandro.haro@elastic.co"},"sourceCommit":{"committedDate":"2023-06-01T15:53:02Z","message":"[Telemetry] Update notice message (#158669)\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Jean-Louis Leysens <jloleysens@gmail.com>","sha":"312ba3a758a05cea2d490a5174838cd84ac2cc8d","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:Docs","Team:Core","Feature:Telemetry","release_note:skip","backport:prev-minor","v8.9.0"],"number":158669,"url":"https://github.com/elastic/kibana/pull/158669","mergeCommit":{"message":"[Telemetry] Update notice message (#158669)\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Jean-Louis Leysens <jloleysens@gmail.com>","sha":"312ba3a758a05cea2d490a5174838cd84ac2cc8d"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/158669","number":158669,"mergeCommit":{"message":"[Telemetry] Update notice message (#158669)\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Jean-Louis Leysens <jloleysens@gmail.com>","sha":"312ba3a758a05cea2d490a5174838cd84ac2cc8d"}}]}] BACKPORT--> Co-authored-by: Alejandro Fernández Haro <alejandro.haro@elastic.co>
This commit is contained in:
parent
d841880951
commit
045c4bcb10
33 changed files with 391 additions and 696 deletions
|
@ -9,7 +9,7 @@ Usage Collection (also known as Telemetry) is enabled by default. This allows us
|
|||
Refer to our https://www.elastic.co/legal/product-privacy-statement[Privacy Statement] to learn more.
|
||||
|
||||
You can control whether this data is sent from the {kib} servers, or if it should be sent
|
||||
from the user's browser, in case a firewall is blocking the connections from the server. Additionally, you can decide to completely disable this feature either in the config file or in {kib} via *Management > Kibana > Advanced Settings > Usage Data*.
|
||||
from the user's browser, in case a firewall is blocking the connections from the server. Additionally, you can disable this feature either in *Stack Management > {kib} > Advanced Settings > Global Settings > Usage collection* or the config file with the following settings.
|
||||
|
||||
[float]
|
||||
[[telemetry-general-settings]]
|
||||
|
|
|
@ -348,7 +348,7 @@ export interface ISavedObjectsRepository {
|
|||
* It will not create a nested structure like:
|
||||
* `{attributes: {stats: {api: {counter: 1}}}}`
|
||||
*
|
||||
* When using incrementCounter for collecting usage data, you need to ensure
|
||||
* When using incrementCounter you need to ensure
|
||||
* that usage collection happens on a best-effort basis and doesn't
|
||||
* negatively affect your plugin or users. See https://github.com/elastic/kibana/blob/main/src/plugins/usage_collection/README.mdx#tracking-interactions-with-incrementcounter)
|
||||
*
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`OptInDetailsComponent renders as expected 1`] = `
|
||||
<EuiCallOut
|
||||
iconType="questionInCircle"
|
||||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="Help us improve the Elastic Stack"
|
||||
id="telemetry.welcomeBanner.title"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<OptInMessage
|
||||
telemetryConstants={
|
||||
Object {
|
||||
"getPrivacyStatementUrl": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="s"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="enable"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Enable"
|
||||
id="telemetry.welcomeBanner.enableButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="disable"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Disable"
|
||||
id="telemetry.welcomeBanner.disableButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiCallOut>
|
||||
`;
|
|
@ -1,25 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`OptInMessage renders as expected 1`] = `
|
||||
<Fragment>
|
||||
<FormattedMessage
|
||||
defaultMessage="Want to help us improve the Elastic Stack? Data usage collection is currently disabled. Enabling data usage collection helps us manage and improve our products and services. See our {privacyStatementLink} for more details."
|
||||
id="telemetry.telemetryBannerDescription"
|
||||
values={
|
||||
Object {
|
||||
"privacyStatementLink": <EuiLink
|
||||
href="https://some-host/some-url"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Privacy Statement"
|
||||
id="telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiLink>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
|
@ -1,52 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`OptInDetailsComponent renders as expected 1`] = `
|
||||
<EuiCallOut
|
||||
title="Help us improve the Elastic Stack"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="To learn about how usage data helps us manage and improve our products and services, see our {privacyStatementLink}. To stop collection, {disableLink}."
|
||||
id="telemetry.telemetryOptedInNoticeDescription"
|
||||
values={
|
||||
Object {
|
||||
"disableLink": <EuiLink
|
||||
href="/app/management/kibana/settings"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="disable usage data here"
|
||||
id="telemetry.telemetryOptedInDisableUsage"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiLink>,
|
||||
"privacyStatementLink": <EuiLink
|
||||
href="https://some-host/some-url"
|
||||
onClick={[Function]}
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Privacy Statement"
|
||||
id="telemetry.telemetryOptedInPrivacyStatement"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiLink>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Dismiss"
|
||||
id="telemetry.telemetryOptedInDismissMessage"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
`;
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import { shallowWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { OptInBanner } from './opt_in_banner';
|
||||
import { mockTelemetryConstants } from '../mocks';
|
||||
|
||||
describe('OptInDetailsComponent', () => {
|
||||
const telemetryConstants = mockTelemetryConstants();
|
||||
|
||||
it('renders as expected', () => {
|
||||
expect(
|
||||
shallowWithIntl(
|
||||
<OptInBanner onChangeOptInClick={() => {}} telemetryConstants={telemetryConstants} />
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('fires the "onChangeOptInClick" prop with true when a enable is clicked', () => {
|
||||
const onClick = jest.fn();
|
||||
const component = shallowWithIntl(
|
||||
<OptInBanner onChangeOptInClick={onClick} telemetryConstants={telemetryConstants} />
|
||||
);
|
||||
|
||||
const enableButton = component.findWhere((n) => {
|
||||
const props = n.props();
|
||||
return n.type() === EuiButton && props['data-test-subj'] === 'enable';
|
||||
});
|
||||
|
||||
if (!enableButton) {
|
||||
throw new Error(`Couldn't find any opt in enable button.`);
|
||||
}
|
||||
|
||||
enableButton.simulate('click');
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
expect(onClick).toBeCalledWith(true);
|
||||
});
|
||||
|
||||
it('fires the "onChangeOptInClick" with false when a disable is clicked', () => {
|
||||
const onClick = jest.fn();
|
||||
const component = shallowWithIntl(
|
||||
<OptInBanner onChangeOptInClick={onClick} telemetryConstants={telemetryConstants} />
|
||||
);
|
||||
|
||||
const disableButton = component.findWhere((n) => {
|
||||
const props = n.props();
|
||||
return n.type() === EuiButton && props['data-test-subj'] === 'disable';
|
||||
});
|
||||
|
||||
if (!disableButton) {
|
||||
throw new Error(`Couldn't find any opt in disable button.`);
|
||||
}
|
||||
|
||||
disableButton.simulate('click');
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
expect(onClick).toBeCalledWith(false);
|
||||
});
|
||||
});
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { EuiButton, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { OptInMessage } from './opt_in_message';
|
||||
import { TelemetryConstants } from '..';
|
||||
|
||||
interface Props {
|
||||
onChangeOptInClick: (isOptIn: boolean) => void;
|
||||
telemetryConstants: TelemetryConstants;
|
||||
}
|
||||
|
||||
export class OptInBanner extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const { onChangeOptInClick, telemetryConstants } = this.props;
|
||||
const title = (
|
||||
<FormattedMessage
|
||||
id="telemetry.welcomeBanner.title"
|
||||
defaultMessage="Help us improve the Elastic Stack"
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<EuiCallOut iconType="questionInCircle" title={title}>
|
||||
<OptInMessage telemetryConstants={telemetryConstants} />
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton size="s" data-test-subj="enable" onClick={() => onChangeOptInClick(true)}>
|
||||
<FormattedMessage
|
||||
id="telemetry.welcomeBanner.enableButtonLabel"
|
||||
defaultMessage="Enable"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton size="s" data-test-subj="disable" onClick={() => onChangeOptInClick(false)}>
|
||||
<FormattedMessage
|
||||
id="telemetry.welcomeBanner.disableButtonLabel"
|
||||
defaultMessage="Disable"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -7,16 +7,97 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { shallowWithIntl } from '@kbn/test-jest-helpers';
|
||||
import type { ReactWrapper } from 'enzyme';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
import { mockTelemetryConstants, mockTelemetryService } from '../mocks';
|
||||
import { OptInMessage } from './opt_in_message';
|
||||
import { mockTelemetryConstants } from '../mocks';
|
||||
|
||||
const telemetryConstants = mockTelemetryConstants();
|
||||
|
||||
describe('OptInMessage', () => {
|
||||
it('renders as expected', () => {
|
||||
expect(
|
||||
shallowWithIntl(<OptInMessage telemetryConstants={telemetryConstants} />)
|
||||
).toMatchSnapshot();
|
||||
const addBasePath = httpServiceMock.createBasePath().prepend;
|
||||
const telemetryConstants = mockTelemetryConstants();
|
||||
|
||||
describe('when opted-in', () => {
|
||||
const telemetryService = mockTelemetryService({ config: { optIn: true } });
|
||||
|
||||
let dom: ReactWrapper;
|
||||
|
||||
beforeAll(() => {
|
||||
dom = mountWithIntl(
|
||||
<OptInMessage
|
||||
telemetryConstants={telemetryConstants}
|
||||
telemetryService={telemetryService}
|
||||
addBasePath={addBasePath}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
dom.unmount();
|
||||
});
|
||||
|
||||
it('claims that telemetry is enabled', () => {
|
||||
expect(dom.text()).toContain('Usage collection (also known as Telemetry) is enabled.');
|
||||
});
|
||||
|
||||
it('offers the link to disable it', () => {
|
||||
expect(dom.text()).toContain('Disable usage collection.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when opted-out', () => {
|
||||
const telemetryService = mockTelemetryService({ config: { optIn: false } });
|
||||
|
||||
let dom: ReactWrapper;
|
||||
|
||||
beforeAll(() => {
|
||||
dom = mountWithIntl(
|
||||
<OptInMessage
|
||||
telemetryConstants={telemetryConstants}
|
||||
telemetryService={telemetryService}
|
||||
addBasePath={addBasePath}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
dom.unmount();
|
||||
});
|
||||
|
||||
it('claims that telemetry is disabled', () => {
|
||||
expect(dom.text()).toContain('Usage collection (also known as Telemetry) is disabled.');
|
||||
});
|
||||
|
||||
it('offers the link to enable it', () => {
|
||||
expect(dom.text()).toContain('Enable usage collection.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when null', () => {
|
||||
const telemetryService = mockTelemetryService({ config: { optIn: null } });
|
||||
|
||||
let dom: ReactWrapper;
|
||||
|
||||
beforeAll(() => {
|
||||
dom = mountWithIntl(
|
||||
<OptInMessage
|
||||
telemetryConstants={telemetryConstants}
|
||||
telemetryService={telemetryService}
|
||||
addBasePath={addBasePath}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
dom.unmount();
|
||||
});
|
||||
|
||||
it('claims that telemetry is disabled', () => {
|
||||
expect(dom.text()).toContain('Usage collection (also known as Telemetry) is disabled.');
|
||||
});
|
||||
|
||||
it('offers the link to enable it', () => {
|
||||
expect(dom.text()).toContain('Enable usage collection.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,35 +9,87 @@
|
|||
import * as React from 'react';
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { TelemetryConstants } from '..';
|
||||
import type { IBasePath } from '@kbn/core-http-browser';
|
||||
import type { TelemetryService } from '../services';
|
||||
import type { TelemetryConstants } from '..';
|
||||
|
||||
interface Props {
|
||||
export interface OptInMessageProps {
|
||||
telemetryConstants: TelemetryConstants;
|
||||
telemetryService: TelemetryService;
|
||||
addBasePath: IBasePath['prepend'];
|
||||
onClick?: () => unknown;
|
||||
}
|
||||
|
||||
export class OptInMessage extends React.PureComponent<Props> {
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<FormattedMessage
|
||||
id="telemetry.telemetryBannerDescription"
|
||||
defaultMessage="Want to help us improve the Elastic Stack? Data usage collection is currently disabled. Enabling data usage collection helps us manage and improve our products and services. See our {privacyStatementLink} for more details."
|
||||
values={{
|
||||
privacyStatementLink: (
|
||||
<EuiLink
|
||||
href={this.props.telemetryConstants.getPrivacyStatementUrl()}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText"
|
||||
defaultMessage="Privacy Statement"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
export const OptInMessage: React.FC<OptInMessageProps> = ({
|
||||
addBasePath,
|
||||
telemetryService,
|
||||
telemetryConstants,
|
||||
onClick,
|
||||
}) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<FormattedMessage
|
||||
id="telemetry.dataManagementDisclaimerPrivacy"
|
||||
defaultMessage="Usage collection (also known as Telemetry) is {optInStatus}.
|
||||
This allows us to learn what our users are most interested in, so we can improve our products and services.
|
||||
Refer to our {privacyStatementLink}."
|
||||
values={{
|
||||
optInStatus: (
|
||||
<em>
|
||||
{telemetryService.isOptedIn ? (
|
||||
<FormattedMessage id="telemetry.enabledStatus" defaultMessage="enabled" />
|
||||
) : (
|
||||
<FormattedMessage id="telemetry.disabledStatus" defaultMessage="disabled" />
|
||||
)}
|
||||
</em>
|
||||
),
|
||||
privacyStatementLink: (
|
||||
/* eslint-disable-next-line @elastic/eui/href-or-on-click */
|
||||
<EuiLink
|
||||
onClick={onClick}
|
||||
href={telemetryConstants.getPrivacyStatementUrl()}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="telemetry.dataManagementDisclaimerPrivacyLink"
|
||||
defaultMessage="Privacy Statement"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>{' '}
|
||||
{renderTelemetryEnabledOrDisabledText(telemetryService, addBasePath, onClick)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
function renderTelemetryEnabledOrDisabledText(
|
||||
telemetryService: TelemetryService,
|
||||
addBasePath: (url: string) => string,
|
||||
onClick?: () => unknown
|
||||
) {
|
||||
if (!telemetryService.userCanChangeSettings || !telemetryService.getCanChangeOptInStatus()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isOptedIn = telemetryService.getIsOptedIn();
|
||||
const actionMessage = isOptedIn ? (
|
||||
<FormattedMessage
|
||||
id="telemetry.dataManagementDisableCollectionLink"
|
||||
defaultMessage="Disable usage collection."
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="telemetry.dataManagementEnableCollectionLink"
|
||||
defaultMessage="Enable usage collection."
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
/* eslint-disable-next-line @elastic/eui/href-or-on-click */
|
||||
<EuiLink href={addBasePath('management/kibana/settings')} onClick={onClick}>
|
||||
{actionMessage}
|
||||
</EuiLink>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,34 +8,47 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import { shallowWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { OptedInNoticeBanner } from './opted_in_notice_banner';
|
||||
import { httpServiceMock } from '@kbn/core/public/mocks';
|
||||
import { mockTelemetryConstants } from '../mocks';
|
||||
import { shallowWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { mockTelemetryConstants, mockTelemetryService } from '../mocks';
|
||||
import { OptInStatusNoticeBanner } from './opt_in_status_notice_banner';
|
||||
import { OptInMessage } from './opt_in_message';
|
||||
|
||||
const mockHttp = httpServiceMock.createStartContract();
|
||||
const telemetryConstants = mockTelemetryConstants();
|
||||
const telemetryService = mockTelemetryService();
|
||||
|
||||
describe('OptInDetailsComponent', () => {
|
||||
describe('OptInStatusNoticeBanner', () => {
|
||||
it('renders as expected', () => {
|
||||
const onSeenBanner = () => {};
|
||||
const dom = shallowWithIntl(
|
||||
<OptInStatusNoticeBanner
|
||||
onSeenBanner={onSeenBanner}
|
||||
http={mockHttp}
|
||||
telemetryConstants={telemetryConstants}
|
||||
telemetryService={telemetryService}
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
shallowWithIntl(
|
||||
<OptedInNoticeBanner
|
||||
onSeenBanner={() => {}}
|
||||
http={mockHttp}
|
||||
dom.containsMatchingElement(
|
||||
<OptInMessage
|
||||
telemetryConstants={telemetryConstants}
|
||||
telemetryService={telemetryService}
|
||||
addBasePath={mockHttp.basePath.prepend}
|
||||
onClick={onSeenBanner}
|
||||
/>
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('fires the "onSeenBanner" prop when a link is clicked', () => {
|
||||
const onLinkClick = jest.fn();
|
||||
const component = shallowWithIntl(
|
||||
<OptedInNoticeBanner
|
||||
<OptInStatusNoticeBanner
|
||||
onSeenBanner={onLinkClick}
|
||||
http={mockHttp}
|
||||
telemetryConstants={telemetryConstants}
|
||||
telemetryService={telemetryService}
|
||||
/>
|
||||
);
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
/* eslint @elastic/eui/href-or-on-click:0 */
|
||||
|
||||
import * as React from 'react';
|
||||
import { EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { HttpSetup } from '@kbn/core/public';
|
||||
import { OptInMessage } from './opt_in_message';
|
||||
import { TelemetryService } from '../services';
|
||||
import { TelemetryConstants } from '..';
|
||||
|
||||
interface Props {
|
||||
http: HttpSetup;
|
||||
onSeenBanner: () => unknown;
|
||||
telemetryConstants: TelemetryConstants;
|
||||
telemetryService: TelemetryService;
|
||||
}
|
||||
|
||||
export const OptInStatusNoticeBanner: React.FC<Props> = ({
|
||||
onSeenBanner,
|
||||
http,
|
||||
telemetryConstants,
|
||||
telemetryService,
|
||||
}) => {
|
||||
const addBasePath = http.basePath.prepend;
|
||||
|
||||
const bannerTitle = i18n.translate('telemetry.telemetryOptedInNoticeTitle', {
|
||||
defaultMessage: 'Help us improve the Elastic Stack',
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiCallOut title={bannerTitle}>
|
||||
<OptInMessage
|
||||
telemetryConstants={telemetryConstants}
|
||||
telemetryService={telemetryService}
|
||||
addBasePath={addBasePath}
|
||||
onClick={onSeenBanner}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiButton size="s" onClick={onSeenBanner}>
|
||||
<FormattedMessage id="telemetry.telemetryOptedInDismissMessage" defaultMessage="Dismiss" />
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
/* eslint @elastic/eui/href-or-on-click:0 */
|
||||
|
||||
import * as React from 'react';
|
||||
import { EuiButton, EuiLink, EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { HttpSetup } from '@kbn/core/public';
|
||||
import { PATH_TO_ADVANCED_SETTINGS } from '../../common/constants';
|
||||
import { TelemetryConstants } from '..';
|
||||
|
||||
interface Props {
|
||||
http: HttpSetup;
|
||||
onSeenBanner: () => unknown;
|
||||
telemetryConstants: TelemetryConstants;
|
||||
}
|
||||
|
||||
export class OptedInNoticeBanner extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const { onSeenBanner, http, telemetryConstants } = this.props;
|
||||
const basePath = http.basePath.get();
|
||||
|
||||
const bannerTitle = i18n.translate('telemetry.telemetryOptedInNoticeTitle', {
|
||||
defaultMessage: 'Help us improve the Elastic Stack',
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiCallOut title={bannerTitle}>
|
||||
<FormattedMessage
|
||||
id="telemetry.telemetryOptedInNoticeDescription"
|
||||
defaultMessage="To learn about how usage data helps us manage and improve our products and services, see our {privacyStatementLink}. To stop collection, {disableLink}."
|
||||
values={{
|
||||
privacyStatementLink: (
|
||||
<EuiLink
|
||||
onClick={onSeenBanner}
|
||||
href={telemetryConstants.getPrivacyStatementUrl()}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="telemetry.telemetryOptedInPrivacyStatement"
|
||||
defaultMessage="Privacy Statement"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
disableLink: (
|
||||
<EuiLink href={`${basePath}${PATH_TO_ADVANCED_SETTINGS}`} onClick={onSeenBanner}>
|
||||
<FormattedMessage
|
||||
id="telemetry.telemetryOptedInDisableUsage"
|
||||
defaultMessage="disable usage data here"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiButton size="s" onClick={onSeenBanner}>
|
||||
<FormattedMessage
|
||||
id="telemetry.telemetryOptedInDismissMessage"
|
||||
defaultMessage="Dismiss"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -7,81 +7,16 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiLink, EuiSpacer, EuiTextColor } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { TelemetryConstants } from '../plugin';
|
||||
import type { TelemetryService } from '../services';
|
||||
import { EuiSpacer, EuiTextColor } from '@elastic/eui';
|
||||
import { OptInMessage, type OptInMessageProps } from './opt_in_message';
|
||||
|
||||
interface Props {
|
||||
telemetryService: TelemetryService;
|
||||
addBasePath: (url: string) => string;
|
||||
telemetryConstants: TelemetryConstants;
|
||||
}
|
||||
|
||||
export const WelcomeTelemetryNotice: React.FC<Props> = ({
|
||||
telemetryService,
|
||||
addBasePath,
|
||||
telemetryConstants,
|
||||
}: Props) => {
|
||||
export const WelcomeTelemetryNotice: React.FC<OptInMessageProps> = (props) => {
|
||||
return (
|
||||
<>
|
||||
<EuiTextColor className="euiText--small" color="subdued">
|
||||
<FormattedMessage
|
||||
id="telemetry.dataManagementDisclaimerPrivacy"
|
||||
defaultMessage="To learn about how usage data helps us manage and improve our products and services, see our "
|
||||
/>
|
||||
<EuiLink href={telemetryConstants.getPrivacyStatementUrl()} target="_blank" rel="noopener">
|
||||
<FormattedMessage
|
||||
id="telemetry.dataManagementDisclaimerPrivacyLink"
|
||||
defaultMessage="Privacy Statement."
|
||||
/>
|
||||
</EuiLink>
|
||||
{renderTelemetryEnabledOrDisabledText(telemetryService, addBasePath)}
|
||||
<OptInMessage {...props} />
|
||||
</EuiTextColor>
|
||||
<EuiSpacer size="xs" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function renderTelemetryEnabledOrDisabledText(
|
||||
telemetryService: TelemetryService,
|
||||
addBasePath: (url: string) => string
|
||||
) {
|
||||
if (!telemetryService.userCanChangeSettings || !telemetryService.getCanChangeOptInStatus()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isOptedIn = telemetryService.getIsOptedIn();
|
||||
|
||||
if (isOptedIn) {
|
||||
return (
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="telemetry.dataManagementDisableCollection"
|
||||
defaultMessage=" To stop collection, "
|
||||
/>
|
||||
<EuiLink href={addBasePath('management/kibana/settings')}>
|
||||
<FormattedMessage
|
||||
id="telemetry.dataManagementDisableCollectionLink"
|
||||
defaultMessage="disable usage data here."
|
||||
/>
|
||||
</EuiLink>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="telemetry.dataManagementEnableCollection"
|
||||
defaultMessage=" To start collection, "
|
||||
/>
|
||||
<EuiLink href={addBasePath('management/kibana/settings')}>
|
||||
<FormattedMessage
|
||||
id="telemetry.dataManagementEnableCollectionLink"
|
||||
defaultMessage="enable usage data here."
|
||||
/>
|
||||
</EuiLink>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,10 +12,9 @@ import {
|
|||
notificationServiceMock,
|
||||
themeServiceMock,
|
||||
} from '@kbn/core/public/mocks';
|
||||
import { TelemetryService } from './services/telemetry_service';
|
||||
import { TelemetryNotifications } from './services/telemetry_notifications/telemetry_notifications';
|
||||
import { TelemetryPluginStart, TelemetryPluginSetup, TelemetryPluginConfig } from './plugin';
|
||||
import { TelemetryConstants } from '.';
|
||||
import type { TelemetryConstants } from '.';
|
||||
import type { TelemetryPluginStart, TelemetryPluginSetup, TelemetryPluginConfig } from './plugin';
|
||||
import { TelemetryService, TelemetryNotifications } from './services';
|
||||
|
||||
// The following is to be able to access private methods
|
||||
/* eslint-disable dot-notation */
|
||||
|
@ -102,12 +101,13 @@ function createSetupContract(): Setup {
|
|||
|
||||
function createStartContract(): Start {
|
||||
const telemetryService = mockTelemetryService();
|
||||
const telemetryNotifications = mockTelemetryNotifications({ telemetryService });
|
||||
const telemetryConstants = mockTelemetryConstants();
|
||||
|
||||
const startContract: Start = {
|
||||
telemetryService,
|
||||
telemetryNotifications,
|
||||
telemetryNotifications: {
|
||||
setOptedInNoticeSeen: jest.fn(),
|
||||
},
|
||||
telemetryConstants,
|
||||
};
|
||||
|
||||
|
|
|
@ -171,7 +171,7 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
if (home && !this.config.hidePrivacyStatement) {
|
||||
home.welcomeScreen.registerOnRendered(() => {
|
||||
if (this.telemetryService?.userCanChangeSettings) {
|
||||
this.telemetryNotifications?.setOptedInNoticeSeen();
|
||||
this.telemetryNotifications?.setOptInStatusNoticeSeen();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -230,14 +230,13 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
this.maybeStartTelemetryPoller();
|
||||
if (telemetryBanner) {
|
||||
this.maybeShowOptedInNotificationBanner();
|
||||
this.maybeShowOptInBanner();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
telemetryService: this.getTelemetryServicePublicApis(),
|
||||
telemetryNotifications: {
|
||||
setOptedInNoticeSeen: () => telemetryNotifications.setOptedInNoticeSeen(),
|
||||
setOptedInNoticeSeen: () => telemetryNotifications.setOptInStatusNoticeSeen(),
|
||||
},
|
||||
telemetryConstants,
|
||||
};
|
||||
|
@ -300,19 +299,9 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
if (!this.telemetryNotifications) {
|
||||
return;
|
||||
}
|
||||
const shouldShowBanner = this.telemetryNotifications.shouldShowOptedInNoticeBanner();
|
||||
const shouldShowBanner = this.telemetryNotifications.shouldShowOptInStatusNoticeBanner();
|
||||
if (shouldShowBanner) {
|
||||
this.telemetryNotifications.renderOptedInNoticeBanner();
|
||||
}
|
||||
}
|
||||
|
||||
private maybeShowOptInBanner() {
|
||||
if (!this.telemetryNotifications) {
|
||||
return;
|
||||
}
|
||||
const shouldShowBanner = this.telemetryNotifications.shouldShowOptInBanner();
|
||||
if (shouldShowBanner) {
|
||||
this.telemetryNotifications.renderOptInBanner();
|
||||
this.telemetryNotifications.renderOptInStatusNoticeBanner();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { renderOptInBanner } from './render_opt_in_banner';
|
||||
import { overlayServiceMock } from '@kbn/core/public/mocks';
|
||||
import { mockTelemetryConstants } from '../../mocks';
|
||||
|
||||
describe('renderOptInBanner', () => {
|
||||
it('adds a banner to banners with priority of 10000', () => {
|
||||
const bannerID = 'brucer-wayne';
|
||||
const overlays = overlayServiceMock.createStartContract();
|
||||
const telemetryConstants = mockTelemetryConstants();
|
||||
overlays.banners.add.mockReturnValue(bannerID);
|
||||
|
||||
const returnedBannerId = renderOptInBanner({
|
||||
setOptIn: jest.fn(),
|
||||
overlays,
|
||||
telemetryConstants,
|
||||
});
|
||||
|
||||
expect(overlays.banners.add).toBeCalledTimes(1);
|
||||
|
||||
expect(returnedBannerId).toBe(bannerID);
|
||||
const bannerConfig = overlays.banners.add.mock.calls[0];
|
||||
|
||||
expect(bannerConfig[0]).not.toBe(undefined);
|
||||
expect(bannerConfig[1]).toBe(10000);
|
||||
});
|
||||
});
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { OverlayStart } from '@kbn/core/public';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import { withSuspense } from '@kbn/shared-ux-utility';
|
||||
import type { TelemetryConstants } from '../..';
|
||||
|
||||
interface RenderBannerConfig {
|
||||
overlays: OverlayStart;
|
||||
setOptIn: (isOptIn: boolean) => Promise<unknown>;
|
||||
telemetryConstants: TelemetryConstants;
|
||||
}
|
||||
|
||||
export function renderOptInBanner({ setOptIn, overlays, telemetryConstants }: RenderBannerConfig) {
|
||||
const OptInBannerLazy = withSuspense(
|
||||
React.lazy(() =>
|
||||
import('../../components/opt_in_banner').then(({ OptInBanner }) => ({ default: OptInBanner }))
|
||||
)
|
||||
);
|
||||
|
||||
const mount = toMountPoint(
|
||||
<OptInBannerLazy onChangeOptInClick={setOptIn} telemetryConstants={telemetryConstants} />
|
||||
);
|
||||
const bannerId = overlays.banners.add(mount, 10000);
|
||||
|
||||
return bannerId;
|
||||
}
|
|
@ -6,25 +6,27 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { renderOptedInNoticeBanner } from './render_opted_in_notice_banner';
|
||||
import { renderOptInStatusNoticeBanner } from './render_opt_in_status_notice_banner';
|
||||
import { overlayServiceMock, httpServiceMock, themeServiceMock } from '@kbn/core/public/mocks';
|
||||
import { mockTelemetryConstants } from '../../mocks';
|
||||
import { mockTelemetryConstants, mockTelemetryService } from '../../mocks';
|
||||
|
||||
describe('renderOptedInNoticeBanner', () => {
|
||||
describe('renderOptInStatusNoticeBanner', () => {
|
||||
it('adds a banner to banners with priority of 10000', () => {
|
||||
const bannerID = 'brucer-wayne';
|
||||
const overlays = overlayServiceMock.createStartContract();
|
||||
const mockHttp = httpServiceMock.createStartContract();
|
||||
const theme = themeServiceMock.createStartContract();
|
||||
const telemetryConstants = mockTelemetryConstants();
|
||||
const telemetryService = mockTelemetryService();
|
||||
overlays.banners.add.mockReturnValue(bannerID);
|
||||
|
||||
const returnedBannerId = renderOptedInNoticeBanner({
|
||||
const returnedBannerId = renderOptInStatusNoticeBanner({
|
||||
http: mockHttp,
|
||||
onSeen: jest.fn(),
|
||||
overlays,
|
||||
theme,
|
||||
telemetryConstants,
|
||||
telemetryService,
|
||||
});
|
||||
|
||||
expect(overlays.banners.add).toBeCalledTimes(1);
|
|
@ -10,6 +10,7 @@ import React from 'react';
|
|||
import type { HttpStart, OverlayStart, ThemeServiceStart } from '@kbn/core/public';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import { withSuspense } from '@kbn/shared-ux-utility';
|
||||
import { TelemetryService } from '..';
|
||||
import type { TelemetryConstants } from '../..';
|
||||
|
||||
interface RenderBannerConfig {
|
||||
|
@ -18,31 +19,37 @@ interface RenderBannerConfig {
|
|||
theme: ThemeServiceStart;
|
||||
onSeen: () => void;
|
||||
telemetryConstants: TelemetryConstants;
|
||||
telemetryService: TelemetryService;
|
||||
}
|
||||
|
||||
export function renderOptedInNoticeBanner({
|
||||
export function renderOptInStatusNoticeBanner({
|
||||
onSeen,
|
||||
overlays,
|
||||
http,
|
||||
theme,
|
||||
telemetryConstants,
|
||||
telemetryService,
|
||||
}: RenderBannerConfig) {
|
||||
const OptedInNoticeBannerLazy = withSuspense(
|
||||
React.lazy(() =>
|
||||
import('../../components/opted_in_notice_banner').then(({ OptedInNoticeBanner }) => ({
|
||||
default: OptedInNoticeBanner,
|
||||
}))
|
||||
import('../../components/opt_in_status_notice_banner').then(
|
||||
({ OptInStatusNoticeBanner }) => ({
|
||||
default: OptInStatusNoticeBanner,
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const mount = toMountPoint(
|
||||
<OptedInNoticeBannerLazy
|
||||
onSeenBanner={onSeen}
|
||||
http={http}
|
||||
telemetryConstants={telemetryConstants}
|
||||
telemetryService={telemetryService}
|
||||
/>,
|
||||
{ theme$: theme.theme$ }
|
||||
);
|
||||
const bannerId = overlays.banners.add(mount, 10000);
|
||||
|
||||
const bannerId = overlays.banners.add(mount, 10000);
|
||||
return bannerId;
|
||||
}
|
|
@ -9,24 +9,6 @@
|
|||
/* eslint-disable dot-notation */
|
||||
import { mockTelemetryNotifications, mockTelemetryService } from '../../mocks';
|
||||
|
||||
describe('onSetOptInClick', () => {
|
||||
it('sets setting successfully and removes banner', async () => {
|
||||
const optIn = true;
|
||||
const bannerId = 'bruce-banner';
|
||||
|
||||
const telemetryService = mockTelemetryService();
|
||||
telemetryService.setOptIn = jest.fn();
|
||||
const telemetryNotifications = mockTelemetryNotifications({ telemetryService });
|
||||
telemetryNotifications['optInBannerId'] = bannerId;
|
||||
|
||||
await telemetryNotifications['onSetOptInClick'](optIn);
|
||||
expect(telemetryNotifications['overlays'].banners.remove).toBeCalledTimes(1);
|
||||
expect(telemetryNotifications['overlays'].banners.remove).toBeCalledWith(bannerId);
|
||||
expect(telemetryService.setOptIn).toBeCalledTimes(1);
|
||||
expect(telemetryService.setOptIn).toBeCalledWith(optIn);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setOptedInNoticeSeen', () => {
|
||||
it('sets setting successfully and removes banner', async () => {
|
||||
const bannerId = 'bruce-banner';
|
||||
|
@ -34,8 +16,8 @@ describe('setOptedInNoticeSeen', () => {
|
|||
const telemetryService = mockTelemetryService();
|
||||
telemetryService.setUserHasSeenNotice = jest.fn();
|
||||
const telemetryNotifications = mockTelemetryNotifications({ telemetryService });
|
||||
telemetryNotifications['optedInNoticeBannerId'] = bannerId;
|
||||
await telemetryNotifications.setOptedInNoticeSeen();
|
||||
telemetryNotifications['optInStatusNoticeBannerId'] = bannerId;
|
||||
await telemetryNotifications.setOptInStatusNoticeSeen();
|
||||
|
||||
expect(telemetryNotifications['overlays'].banners.remove).toBeCalledTimes(1);
|
||||
expect(telemetryNotifications['overlays'].banners.remove).toBeCalledWith(bannerId);
|
||||
|
@ -44,25 +26,38 @@ describe('setOptedInNoticeSeen', () => {
|
|||
});
|
||||
|
||||
describe('shouldShowOptedInNoticeBanner', () => {
|
||||
it("should return true because a banner hasn't been shown, the notice hasn't been seen and the user has privileges to edit saved objects", () => {
|
||||
describe(`when the banner isn't visible yet`, () => {
|
||||
const telemetryService = mockTelemetryService();
|
||||
telemetryService.getUserShouldSeeOptInNotice = jest.fn().mockReturnValue(true);
|
||||
const getUserShouldSeeOptInNotice = jest.fn();
|
||||
telemetryService.getUserShouldSeeOptInNotice = getUserShouldSeeOptInNotice;
|
||||
const telemetryNotifications = mockTelemetryNotifications({ telemetryService });
|
||||
expect(telemetryNotifications.shouldShowOptedInNoticeBanner()).toBe(true);
|
||||
|
||||
it('should return true when `telemetryService.getUserShouldSeeOptInNotice returns true', () => {
|
||||
getUserShouldSeeOptInNotice.mockReturnValue(true);
|
||||
expect(telemetryNotifications.shouldShowOptInStatusNoticeBanner()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when `telemetryService.getUserShouldSeeOptInNotice returns false', () => {
|
||||
getUserShouldSeeOptInNotice.mockReturnValue(false);
|
||||
expect(telemetryNotifications.shouldShowOptInStatusNoticeBanner()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return false because the banner is already on screen', () => {
|
||||
describe(`when the banner is already visible`, () => {
|
||||
const telemetryService = mockTelemetryService();
|
||||
telemetryService.getUserShouldSeeOptInNotice = jest.fn().mockReturnValue(true);
|
||||
const getUserShouldSeeOptInNotice = jest.fn();
|
||||
telemetryService.getUserShouldSeeOptInNotice = getUserShouldSeeOptInNotice;
|
||||
const telemetryNotifications = mockTelemetryNotifications({ telemetryService });
|
||||
telemetryNotifications['optedInNoticeBannerId'] = 'bruce-banner';
|
||||
expect(telemetryNotifications.shouldShowOptedInNoticeBanner()).toBe(false);
|
||||
});
|
||||
telemetryNotifications['optInStatusNoticeBannerId'] = 'bruce-banner';
|
||||
|
||||
it("should return false because the banner has already been seen or the user doesn't have privileges to change saved objects", () => {
|
||||
const telemetryService = mockTelemetryService();
|
||||
telemetryService.getUserShouldSeeOptInNotice = jest.fn().mockReturnValue(false);
|
||||
const telemetryNotifications = mockTelemetryNotifications({ telemetryService });
|
||||
expect(telemetryNotifications.shouldShowOptedInNoticeBanner()).toBe(false);
|
||||
it('should return false when `telemetryService.getUserShouldSeeOptInNotice` returns true', () => {
|
||||
getUserShouldSeeOptInNotice.mockReturnValue(true);
|
||||
expect(telemetryNotifications.shouldShowOptInStatusNoticeBanner()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when `telemetryService.getUserShouldSeeOptInNotice returns false', () => {
|
||||
getUserShouldSeeOptInNotice.mockReturnValue(false);
|
||||
expect(telemetryNotifications.shouldShowOptInStatusNoticeBanner()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,11 +6,10 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { HttpStart, OverlayStart, ThemeServiceStart } from '@kbn/core/public';
|
||||
import { renderOptedInNoticeBanner } from './render_opted_in_notice_banner';
|
||||
import { renderOptInBanner } from './render_opt_in_banner';
|
||||
import { TelemetryService } from '../telemetry_service';
|
||||
import { TelemetryConstants } from '../..';
|
||||
import type { HttpStart, OverlayStart, ThemeServiceStart } from '@kbn/core/public';
|
||||
import type { TelemetryService } from '../telemetry_service';
|
||||
import type { TelemetryConstants } from '../..';
|
||||
import { renderOptInStatusNoticeBanner } from './render_opt_in_status_notice_banner';
|
||||
|
||||
interface TelemetryNotificationsConstructor {
|
||||
http: HttpStart;
|
||||
|
@ -29,8 +28,7 @@ export class TelemetryNotifications {
|
|||
private readonly theme: ThemeServiceStart;
|
||||
private readonly telemetryConstants: TelemetryConstants;
|
||||
private readonly telemetryService: TelemetryService;
|
||||
private optedInNoticeBannerId?: string;
|
||||
private optInBannerId?: string;
|
||||
private optInStatusNoticeBannerId?: string;
|
||||
|
||||
constructor({
|
||||
http,
|
||||
|
@ -49,70 +47,35 @@ export class TelemetryNotifications {
|
|||
/**
|
||||
* Should the opted-in banner be shown to the user?
|
||||
*/
|
||||
public shouldShowOptedInNoticeBanner = (): boolean => {
|
||||
public shouldShowOptInStatusNoticeBanner = (): boolean => {
|
||||
const userShouldSeeOptInNotice = this.telemetryService.getUserShouldSeeOptInNotice();
|
||||
const bannerOnScreen = typeof this.optedInNoticeBannerId !== 'undefined';
|
||||
const bannerOnScreen = typeof this.optInStatusNoticeBannerId !== 'undefined';
|
||||
return !bannerOnScreen && userShouldSeeOptInNotice;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the banner that claims the cluster is opted-in, and gives the option to opt-out.
|
||||
*/
|
||||
public renderOptedInNoticeBanner = (): void => {
|
||||
const bannerId = renderOptedInNoticeBanner({
|
||||
public renderOptInStatusNoticeBanner = (): void => {
|
||||
const bannerId = renderOptInStatusNoticeBanner({
|
||||
http: this.http,
|
||||
onSeen: this.setOptedInNoticeSeen,
|
||||
onSeen: this.setOptInStatusNoticeSeen,
|
||||
overlays: this.overlays,
|
||||
theme: this.theme,
|
||||
telemetryConstants: this.telemetryConstants,
|
||||
telemetryService: this.telemetryService,
|
||||
});
|
||||
|
||||
this.optedInNoticeBannerId = bannerId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Should the banner to opt-in be shown to the user?
|
||||
*/
|
||||
public shouldShowOptInBanner = (): boolean => {
|
||||
// Using `config.optIn` instead of the getter `getIsOptedIn()` because the latter only returns boolean, and we want to compare it against `null`.
|
||||
const isOptedIn = this.telemetryService.config.optIn;
|
||||
const bannerOnScreen = typeof this.optInBannerId !== 'undefined';
|
||||
return !bannerOnScreen && isOptedIn === null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the banner that claims the cluster is opted-out, and gives the option to opt-in.
|
||||
*/
|
||||
public renderOptInBanner = (): void => {
|
||||
const bannerId = renderOptInBanner({
|
||||
setOptIn: this.onSetOptInClick,
|
||||
overlays: this.overlays,
|
||||
telemetryConstants: this.telemetryConstants,
|
||||
});
|
||||
|
||||
this.optInBannerId = bannerId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Opt-in/out button handler
|
||||
* @param isOptIn true/false whether the user opts-in/out
|
||||
*/
|
||||
private onSetOptInClick = async (isOptIn: boolean) => {
|
||||
if (this.optInBannerId) {
|
||||
this.overlays.banners.remove(this.optInBannerId);
|
||||
this.optInBannerId = undefined;
|
||||
}
|
||||
|
||||
await this.telemetryService.setOptIn(isOptIn);
|
||||
this.optInStatusNoticeBannerId = bannerId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the banner and stores the user's dismissal of the banner.
|
||||
*/
|
||||
public setOptedInNoticeSeen = async (): Promise<void> => {
|
||||
if (this.optedInNoticeBannerId) {
|
||||
this.overlays.banners.remove(this.optedInNoticeBannerId);
|
||||
this.optedInNoticeBannerId = undefined;
|
||||
public setOptInStatusNoticeSeen = async (): Promise<void> => {
|
||||
if (this.optInStatusNoticeBannerId) {
|
||||
this.overlays.banners.remove(this.optInStatusNoticeBannerId);
|
||||
this.optInStatusNoticeBannerId = undefined;
|
||||
}
|
||||
|
||||
await this.telemetryService.setUserHasSeenNotice();
|
||||
|
|
|
@ -232,6 +232,19 @@ describe('TelemetryService', () => {
|
|||
expect(telemetryService.getUserShouldSeeOptInNotice()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when optIn: null even when previously seen', () => {
|
||||
const telemetryService = mockTelemetryService({
|
||||
config: {
|
||||
userCanChangeSettings: true,
|
||||
telemetryNotifyUserAboutOptInDefault: false,
|
||||
optIn: null,
|
||||
},
|
||||
});
|
||||
expect(telemetryService.config.userCanChangeSettings).toBe(true);
|
||||
expect(telemetryService.userCanChangeSettings).toBe(true);
|
||||
expect(telemetryService.getUserShouldSeeOptInNotice()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns whether the user can update the telemetry config (has SavedObjects access)', () => {
|
||||
const telemetryService = mockTelemetryService({
|
||||
config: { userCanChangeSettings: undefined },
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CoreSetup, CoreStart } from '@kbn/core/public';
|
||||
import { TelemetryPluginConfig } from '../plugin';
|
||||
import type { CoreSetup, CoreStart } from '@kbn/core/public';
|
||||
import type { TelemetryPluginConfig } from '../plugin';
|
||||
import { getTelemetryChannelEndpoint } from '../../common/telemetry_config/get_telemetry_channel_endpoint';
|
||||
import type {
|
||||
UnencryptedTelemetryPayload,
|
||||
|
@ -110,15 +110,19 @@ export class TelemetryService {
|
|||
};
|
||||
|
||||
/**
|
||||
* Returns if an user should be shown the notice about Opt-In/Out telemetry.
|
||||
* The decision is made based on whether any user has already dismissed the message or
|
||||
* the user can't actually change the settings (in which case, there's no point on bothering them)
|
||||
* Returns whether a user should be shown the notice about Opt-In/Out telemetry.
|
||||
* The decision is made based on:
|
||||
* 1. The config hidePrivacyStatement is unset
|
||||
* 2. The user has enough privileges to change the settings
|
||||
* 3. At least one of the following:
|
||||
* * It is opted-in, and the user has already been notified at any given point in the deployment's life.
|
||||
* * It is opted-out, and the user has been notified for this version (excluding patch updates)
|
||||
*/
|
||||
public getUserShouldSeeOptInNotice(): boolean {
|
||||
return (
|
||||
(!this.config.hidePrivacyStatement &&
|
||||
this.config.telemetryNotifyUserAboutOptInDefault &&
|
||||
this.config.userCanChangeSettings) ??
|
||||
this.config.userCanChangeSettings &&
|
||||
(this.config.telemetryNotifyUserAboutOptInDefault || this.config.optIn === null)) ??
|
||||
false
|
||||
);
|
||||
}
|
||||
|
|
|
@ -190,7 +190,7 @@ export class FetcherTask {
|
|||
} catch (err) {
|
||||
await this.updateReportFailure(telemetryConfig);
|
||||
|
||||
this.logger.warn(`Error sending telemetry usage data. (${err})`);
|
||||
this.logger.warn(`Error sending usage to Elastic. (${err})`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ export interface TelemetryPluginSetup {
|
|||
*/
|
||||
export interface TelemetryPluginStart {
|
||||
/**
|
||||
* Resolves `true` if the user has opted into send Elastic usage data.
|
||||
* Resolves `true` if sending usage to Elastic is enabled.
|
||||
* Resolves `false` if the user explicitly opted out of sending usage data to Elastic
|
||||
* or did not choose to opt-in or out -yet- after a minor or major upgrade (only when previously opted-out).
|
||||
*
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
"@kbn/core-saved-objects-server",
|
||||
"@kbn/core-saved-objects-api-server",
|
||||
"@kbn/std",
|
||||
"@kbn/core-http-browser-mocks",
|
||||
"@kbn/core-http-browser",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -12,7 +12,7 @@ exports[`TelemetryManagementSectionComponent renders as expected 1`] = `
|
|||
<EuiTitle>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
defaultMessage="Usage Data"
|
||||
defaultMessage="Usage collection"
|
||||
id="telemetry.usageDataTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
|
@ -57,13 +57,13 @@ exports[`TelemetryManagementSectionComponent renders as expected 1`] = `
|
|||
loading={false}
|
||||
setting={
|
||||
Object {
|
||||
"ariaName": "Provide usage data",
|
||||
"ariaName": "Share usage with Elastic",
|
||||
"category": Array [],
|
||||
"defVal": true,
|
||||
"description": <React.Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Enabling data usage collection helps us manage and improve our products and services. See our {privacyStatementLink} for more details."
|
||||
defaultMessage="Enabling usage collection (also known as Telemetry) allows us to learn what our users are most interested in, so we can improve our products and services. Refer to our {privacyStatementLink}."
|
||||
id="telemetry.telemetryConfigAndLinkDescription"
|
||||
values={
|
||||
Object {
|
||||
|
@ -113,7 +113,7 @@ exports[`TelemetryManagementSectionComponent renders as expected 1`] = `
|
|||
/>
|
||||
</p>
|
||||
</React.Fragment>,
|
||||
"displayName": "Provide usage data",
|
||||
"displayName": "Share usage with Elastic",
|
||||
"isCustom": true,
|
||||
"isOverridden": false,
|
||||
"name": "telemetry:enabled",
|
||||
|
|
|
@ -27,7 +27,21 @@ import { OptInExampleFlyout } from './opt_in_example_flyout';
|
|||
|
||||
type TelemetryService = TelemetryPluginSetup['telemetryService'];
|
||||
|
||||
const SEARCH_TERMS = ['telemetry', 'usage', 'data', 'usage data'];
|
||||
const SEARCH_TERMS: string[] = [
|
||||
'telemetry',
|
||||
'usage data', // Keeping this term for BWC
|
||||
'usage collection',
|
||||
i18n.translate('telemetry.telemetryConstant', {
|
||||
defaultMessage: 'telemetry',
|
||||
}),
|
||||
i18n.translate('telemetry.usageCollectionConstant', {
|
||||
defaultMessage: 'usage collection',
|
||||
}),
|
||||
].flatMap((term) => {
|
||||
// Automatically lower-case and split by space the terms from above
|
||||
const lowerCased = term.toLowerCase();
|
||||
return [lowerCased, ...lowerCased.split(' ')];
|
||||
});
|
||||
|
||||
interface Props {
|
||||
telemetryService: TelemetryService;
|
||||
|
@ -113,7 +127,10 @@ export class TelemetryManagementSection extends Component<Props, State> {
|
|||
<EuiSplitPanel.Inner color="subdued">
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
<FormattedMessage id="telemetry.usageDataTitle" defaultMessage="Usage Data" />
|
||||
<FormattedMessage
|
||||
id="telemetry.usageDataTitle"
|
||||
defaultMessage="Usage collection"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiSplitPanel.Inner>
|
||||
|
@ -126,13 +143,13 @@ export class TelemetryManagementSection extends Component<Props, State> {
|
|||
type: 'boolean',
|
||||
name: 'telemetry:enabled',
|
||||
displayName: i18n.translate('telemetry.provideUsageDataTitle', {
|
||||
defaultMessage: 'Provide usage data',
|
||||
defaultMessage: 'Share usage with Elastic',
|
||||
}),
|
||||
value: enabled,
|
||||
description: this.renderDescription(),
|
||||
defVal: true,
|
||||
ariaName: i18n.translate('telemetry.provideUsageDataAriaName', {
|
||||
defaultMessage: 'Provide usage data',
|
||||
defaultMessage: 'Share usage with Elastic',
|
||||
}),
|
||||
requiresPageReload: false,
|
||||
category: [],
|
||||
|
@ -204,8 +221,9 @@ export class TelemetryManagementSection extends Component<Props, State> {
|
|||
<p>
|
||||
<FormattedMessage
|
||||
id="telemetry.telemetryConfigAndLinkDescription"
|
||||
defaultMessage="Enabling data usage collection helps us manage and improve our products and services.
|
||||
See our {privacyStatementLink} for more details."
|
||||
defaultMessage="Enabling usage collection (also known as Telemetry) allows us to learn
|
||||
what our users are most interested in, so we can improve our products and services.
|
||||
Refer to our {privacyStatementLink}."
|
||||
values={{
|
||||
privacyStatementLink: (
|
||||
<EuiLink href={docLinks.legal.privacyStatement} target="_blank">
|
||||
|
@ -249,10 +267,10 @@ export class TelemetryManagementSection extends Component<Props, State> {
|
|||
toasts.addSuccess(
|
||||
newOptInValue
|
||||
? i18n.translate('telemetry.optInSuccessOn', {
|
||||
defaultMessage: 'Usage data collection turned on.',
|
||||
defaultMessage: 'Sharing usage with Elastic is enabled.',
|
||||
})
|
||||
: i18n.translate('telemetry.optInSuccessOff', {
|
||||
defaultMessage: 'Usage data collection turned off.',
|
||||
defaultMessage: 'No longer sharing usage with Elastic.',
|
||||
})
|
||||
);
|
||||
resolve(true);
|
||||
|
|
|
@ -392,14 +392,14 @@ document. Examples of interactions include tracking:
|
|||
- the number of API calls
|
||||
- the number of times users installed and uninstalled the sample datasets
|
||||
|
||||
When using `incrementCounter` for collecting usage data, you need to ensure
|
||||
When using `incrementCounter` for collecting usage, you need to ensure
|
||||
that usage collection happens on a best-effort basis and doesn't
|
||||
negatively affect your plugin or users (see the example):
|
||||
- Swallow any exceptions thrown from the incrementCounter method and log
|
||||
a message in development.
|
||||
- Don't block your application on the incrementCounter method (e.g.
|
||||
don't use `await`)
|
||||
- Set the `refresh` option to false to prevent unecessary index refreshes
|
||||
- Set the `refresh` option to false to prevent unnecessary index refreshes
|
||||
which slows down Elasticsearch performance
|
||||
|
||||
|
||||
|
|
|
@ -74,8 +74,8 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
|
|||
|
||||
it('shows the banner in the default configuration', async () => {
|
||||
await PageObjects.common.navigateToApp('home');
|
||||
expect(await find.existsByCssSelector('[data-test-subj="enable"]')).to.eql(true);
|
||||
expect(await find.existsByCssSelector('[data-test-subj="disable"]')).to.eql(true);
|
||||
expect(await find.existsByLinkText('Enable usage collection.')).to.eql(true);
|
||||
expect(await find.existsByLinkText('Disable usage collection.')).to.eql(false);
|
||||
});
|
||||
|
||||
it('does not show the banner if opted-in', async () => {
|
||||
|
@ -86,8 +86,8 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
|
|||
.expect(200);
|
||||
|
||||
await PageObjects.common.navigateToApp('home');
|
||||
expect(await find.existsByCssSelector('[data-test-subj="enable"]')).to.eql(false);
|
||||
expect(await find.existsByCssSelector('[data-test-subj="disable"]')).to.eql(false);
|
||||
expect(await find.existsByLinkText('Enable usage collection.')).to.eql(false);
|
||||
expect(await find.existsByLinkText('Disable usage collection.')).to.eql(false);
|
||||
});
|
||||
|
||||
it('does not show the banner if opted-out in this version', async () => {
|
||||
|
@ -98,8 +98,8 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
|
|||
.expect(200);
|
||||
|
||||
await PageObjects.common.navigateToApp('home');
|
||||
expect(await find.existsByCssSelector('[data-test-subj="enable"]')).to.eql(false);
|
||||
expect(await find.existsByCssSelector('[data-test-subj="disable"]')).to.eql(false);
|
||||
expect(await find.existsByLinkText('Enable usage collection.')).to.eql(false);
|
||||
expect(await find.existsByLinkText('Disable usage collection.')).to.eql(false);
|
||||
});
|
||||
|
||||
it('shows the banner if opted-out in a previous version', async () => {
|
||||
|
@ -111,8 +111,8 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
|
|||
});
|
||||
|
||||
await PageObjects.common.navigateToApp('home');
|
||||
expect(await find.existsByCssSelector('[data-test-subj="enable"]')).to.eql(true);
|
||||
expect(await find.existsByCssSelector('[data-test-subj="disable"]')).to.eql(true);
|
||||
expect(await find.existsByLinkText('Enable usage collection.')).to.eql(true);
|
||||
expect(await find.existsByLinkText('Disable usage collection.')).to.eql(false);
|
||||
});
|
||||
|
||||
it('does not show the banner if opted-in in a previous version', async () => {
|
||||
|
@ -124,8 +124,8 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
|
|||
});
|
||||
|
||||
await PageObjects.common.navigateToApp('home');
|
||||
expect(await find.existsByCssSelector('[data-test-subj="enable"]')).to.eql(false);
|
||||
expect(await find.existsByCssSelector('[data-test-subj="disable"]')).to.eql(false);
|
||||
expect(await find.existsByLinkText('Enable usage collection.')).to.eql(false);
|
||||
expect(await find.existsByLinkText('Disable usage collection.')).to.eql(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5307,9 +5307,7 @@
|
|||
"sharedUXPackages.userProfileComponents.userProfilesSelectable.suggestedLabel": "Suggérée",
|
||||
"telemetry.callout.appliesSettingTitle": "Les modifications apportées à ce paramètre s'appliquent dans {allOfKibanaText} et sont enregistrées automatiquement.",
|
||||
"telemetry.seeExampleOfClusterDataAndEndpointSecuity": "Découvrez des exemples des {clusterData} et {securityData} que nous collectons.",
|
||||
"telemetry.telemetryBannerDescription": "Vous souhaitez nous aider à améliorer la Suite Elastic ? La collecte de données d'utilisation est actuellement désactivée. En activant la collecte de données d'utilisation, vous nous aidez à gérer et à améliorer nos produits et nos services. Pour en savoir plus, consultez notre {privacyStatementLink}.",
|
||||
"telemetry.telemetryConfigAndLinkDescription": "En activant la collecte de données d'utilisation, vous nous aidez à gérer et à améliorer nos produits et nos services. Pour en savoir plus, consultez notre {privacyStatementLink}.",
|
||||
"telemetry.telemetryOptedInNoticeDescription": "Pour en savoir plus sur la manière dont les données d'utilisation nous aident à gérer et à améliorer nos produits et nos services, consultez notre {privacyStatementLink}. Pour mettre fin à la collecte, {disableLink}.",
|
||||
"telemetry.callout.appliesSettingTitle.allOfKibanaText": "tout Kibana",
|
||||
"telemetry.callout.clusterStatisticsDescription": "Voici un exemple des statistiques de cluster de base que nous collecterons. Cela comprend le nombre d'index, de partitions et de nœuds. Cela comprend également des statistiques d'utilisation de niveau élevé, comme l'état d'activation du monitoring.",
|
||||
"telemetry.callout.clusterStatisticsTitle": "Statistiques du cluster",
|
||||
|
@ -5318,11 +5316,8 @@
|
|||
"telemetry.callout.errorUnprivilegedUserDescription": "Vous ne disposez pas de l'accès requis pour voir les statistiques non chiffrées du cluster.",
|
||||
"telemetry.callout.errorUnprivilegedUserTitle": "Erreur lors de l'affichage des statistiques du cluster",
|
||||
"telemetry.clusterData": "données du cluster",
|
||||
"telemetry.dataManagementDisableCollection": " Pour mettre fin à la collecte, ",
|
||||
"telemetry.dataManagementDisableCollectionLink": "désactivez les données d'utilisation ici.",
|
||||
"telemetry.dataManagementDisclaimerPrivacy": "Pour en savoir plus sur la manière dont les données d'utilisation nous aident à gérer et à améliorer nos produits et nos services, consultez notre ",
|
||||
"telemetry.dataManagementDisclaimerPrivacyLink": "Déclaration de confidentialité.",
|
||||
"telemetry.dataManagementEnableCollection": " Pour démarrer la collecte, ",
|
||||
"telemetry.dataManagementEnableCollectionLink": "activez les données d'utilisation ici.",
|
||||
"telemetry.optInErrorToastText": "Une erreur s'est produite lors de la définition des préférences relatives aux statistiques d'utilisation.",
|
||||
"telemetry.optInErrorToastTitle": "Erreur",
|
||||
|
@ -5334,15 +5329,9 @@
|
|||
"telemetry.provideUsageDataTitle": "Fournir les données d'utilisation",
|
||||
"telemetry.readOurUsageDataPrivacyStatementLinkText": "Déclaration de confidentialité",
|
||||
"telemetry.securityData": "données de sécurité",
|
||||
"telemetry.telemetryOptedInDisableUsage": "désactivez les données d'utilisation ici",
|
||||
"telemetry.telemetryOptedInDismissMessage": "Rejeter",
|
||||
"telemetry.telemetryOptedInNoticeTitle": "Aidez-nous à améliorer la Suite Elastic.",
|
||||
"telemetry.telemetryOptedInPrivacyStatement": "Déclaration de confidentialité",
|
||||
"telemetry.usageDataTitle": "Données d'utilisation",
|
||||
"telemetry.welcomeBanner.disableButtonLabel": "Désactiver",
|
||||
"telemetry.welcomeBanner.enableButtonLabel": "Activer",
|
||||
"telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "Déclaration de confidentialité",
|
||||
"telemetry.welcomeBanner.title": "Aidez-nous à améliorer la Suite Elastic.",
|
||||
"timelion.help.functions.aggregate.args.functionHelpText": "L'une des {functions}",
|
||||
"timelion.help.functions.aggregateHelpText": "Crée une ligne statique sur la base du résultat du traitement de tous les points de la série. Fonctions disponibles : {functions}",
|
||||
"timelion.help.functions.common.args.fitHelpText": "Algorithme à utiliser pour adapter les séries à l'intervalle et à la période cible. Disponible : {fitFunctions}",
|
||||
|
|
|
@ -5308,9 +5308,7 @@
|
|||
"sharedUXPackages.userProfileComponents.userProfilesSelectable.suggestedLabel": "候補",
|
||||
"telemetry.callout.appliesSettingTitle": "この設定に加えた変更は{allOfKibanaText}に適用され、自動的に保存されます。",
|
||||
"telemetry.seeExampleOfClusterDataAndEndpointSecuity": "収集される{clusterData}および{securityData}の例を参照してください。",
|
||||
"telemetry.telemetryBannerDescription": "Elastic Stackの改善にご協力ください使用状況データの収集は現在無効です。使用状況データの収集を有効にすると、製品とサービスを管理して改善することができます。詳細は{privacyStatementLink}をご覧ください。",
|
||||
"telemetry.telemetryConfigAndLinkDescription": "使用状況データの収集を有効にすると、製品とサービスを管理して改善することができます。詳細は{privacyStatementLink}をご覧ください。",
|
||||
"telemetry.telemetryOptedInNoticeDescription": "使用状況データがどのように製品とサービスの管理と改善につながるのかに関する詳細については、{privacyStatementLink}を参照してください。収集を停止するには、{disableLink}。",
|
||||
"telemetry.callout.appliesSettingTitle.allOfKibanaText": "Kibana のすべて",
|
||||
"telemetry.callout.clusterStatisticsDescription": "これは収集される基本的なクラスター統計の例です。インデックス、シャード、ノードの数が含まれます。監視がオンになっているかどうかなどのハイレベルの使用統計も含まれます。",
|
||||
"telemetry.callout.clusterStatisticsTitle": "クラスター統計",
|
||||
|
@ -5319,11 +5317,8 @@
|
|||
"telemetry.callout.errorUnprivilegedUserDescription": "暗号化されていないクラスター統計を表示するアクセス権がありません。",
|
||||
"telemetry.callout.errorUnprivilegedUserTitle": "クラスター統計の表示エラー",
|
||||
"telemetry.clusterData": "クラスターデータ",
|
||||
"telemetry.dataManagementDisableCollection": " 収集を停止するには、",
|
||||
"telemetry.dataManagementDisableCollectionLink": "ここで使用状況データを無効にします。",
|
||||
"telemetry.dataManagementDisclaimerPrivacy": "使用状況データがどのように製品とサービスの管理と改善につながるのかに関する詳細については ",
|
||||
"telemetry.dataManagementDisclaimerPrivacyLink": "プライバシーポリシーをご覧ください。",
|
||||
"telemetry.dataManagementEnableCollection": " 収集を開始するには、",
|
||||
"telemetry.dataManagementEnableCollectionLink": "ここで使用状況データを有効にします。",
|
||||
"telemetry.optInErrorToastText": "使用状況統計設定の設定中にエラーが発生しました。",
|
||||
"telemetry.optInErrorToastTitle": "エラー",
|
||||
|
@ -5335,15 +5330,9 @@
|
|||
"telemetry.provideUsageDataTitle": "使用状況データを提供",
|
||||
"telemetry.readOurUsageDataPrivacyStatementLinkText": "プライバシーポリシー",
|
||||
"telemetry.securityData": "セキュリティデータ",
|
||||
"telemetry.telemetryOptedInDisableUsage": "ここで使用状況データを無効にする",
|
||||
"telemetry.telemetryOptedInDismissMessage": "閉じる",
|
||||
"telemetry.telemetryOptedInNoticeTitle": "Elastic Stack の改善にご協力ください",
|
||||
"telemetry.telemetryOptedInPrivacyStatement": "プライバシーポリシー",
|
||||
"telemetry.usageDataTitle": "使用データ",
|
||||
"telemetry.welcomeBanner.disableButtonLabel": "無効にする",
|
||||
"telemetry.welcomeBanner.enableButtonLabel": "有効にする",
|
||||
"telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "プライバシーポリシー",
|
||||
"telemetry.welcomeBanner.title": "Elastic Stack の改善にご協力ください",
|
||||
"timelion.help.functions.aggregate.args.functionHelpText": "{functions}の1つ",
|
||||
"timelion.help.functions.aggregateHelpText": "数列のすべての点の処理結果に基づく線を作成します。利用可能な関数:{functions}",
|
||||
"timelion.help.functions.common.args.fitHelpText": "ターゲットの期間と間隔に数列を合わせるためのアルゴリズムです。利用可能:{fitFunctions}",
|
||||
|
|
|
@ -5307,9 +5307,7 @@
|
|||
"sharedUXPackages.userProfileComponents.userProfilesSelectable.suggestedLabel": "已建议",
|
||||
"telemetry.callout.appliesSettingTitle": "对此设置的更改将应用到{allOfKibanaText} 且会自动保存。",
|
||||
"telemetry.seeExampleOfClusterDataAndEndpointSecuity": "查看我们收集的{clusterData}和{securityData}示例。",
|
||||
"telemetry.telemetryBannerDescription": "想帮助我们改进 Elastic Stack?数据使用情况收集当前已禁用。启用使用情况数据收集可帮助我们管理并改善产品和服务。有关详情,请参阅我们的{privacyStatementLink}。",
|
||||
"telemetry.telemetryConfigAndLinkDescription": "启用使用情况数据收集可帮助我们管理并改善产品和服务。有关详情,请参阅我们的{privacyStatementLink}。",
|
||||
"telemetry.telemetryOptedInNoticeDescription": "要了解使用情况数据如何帮助我们管理和改善产品和服务,请参阅我们的{privacyStatementLink}。要停止收集,{disableLink}。",
|
||||
"telemetry.callout.appliesSettingTitle.allOfKibanaText": "整个 Kibana",
|
||||
"telemetry.callout.clusterStatisticsDescription": "这是我们将收集的基本集群统计信息的示例。其包括索引、分片和节点的数目。还包括概括性的使用情况统计信息,例如监测是否打开。",
|
||||
"telemetry.callout.clusterStatisticsTitle": "集群统计信息",
|
||||
|
@ -5318,11 +5316,8 @@
|
|||
"telemetry.callout.errorUnprivilegedUserDescription": "您无权查看未加密的集群统计信息。",
|
||||
"telemetry.callout.errorUnprivilegedUserTitle": "显示集群统计信息时出错",
|
||||
"telemetry.clusterData": "集群数据",
|
||||
"telemetry.dataManagementDisableCollection": " 要停止收集,",
|
||||
"telemetry.dataManagementDisableCollectionLink": "请在此禁用使用情况数据。",
|
||||
"telemetry.dataManagementDisclaimerPrivacy": "要了解使用情况数据如何帮助我们管理和改善产品和服务,请参阅我们的 ",
|
||||
"telemetry.dataManagementDisclaimerPrivacyLink": "隐私声明。",
|
||||
"telemetry.dataManagementEnableCollection": " 要启动收集,",
|
||||
"telemetry.dataManagementEnableCollectionLink": "请在此处启用使用情况数据。",
|
||||
"telemetry.optInErrorToastText": "尝试设置使用情况统计信息首选项时发生错误。",
|
||||
"telemetry.optInErrorToastTitle": "错误",
|
||||
|
@ -5334,15 +5329,9 @@
|
|||
"telemetry.provideUsageDataTitle": "提供使用情况数据",
|
||||
"telemetry.readOurUsageDataPrivacyStatementLinkText": "隐私声明",
|
||||
"telemetry.securityData": "安全数据",
|
||||
"telemetry.telemetryOptedInDisableUsage": "请在此禁用使用情况数据",
|
||||
"telemetry.telemetryOptedInDismissMessage": "关闭",
|
||||
"telemetry.telemetryOptedInNoticeTitle": "帮助我们改进 Elastic Stack",
|
||||
"telemetry.telemetryOptedInPrivacyStatement": "隐私声明",
|
||||
"telemetry.usageDataTitle": "使用情况数据",
|
||||
"telemetry.welcomeBanner.disableButtonLabel": "禁用",
|
||||
"telemetry.welcomeBanner.enableButtonLabel": "启用",
|
||||
"telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "隐私声明",
|
||||
"telemetry.welcomeBanner.title": "帮助我们改进 Elastic Stack",
|
||||
"timelion.help.functions.aggregate.args.functionHelpText": "以下选项之一:{functions}",
|
||||
"timelion.help.functions.aggregateHelpText": "基于对序列中所有点的处理结果创建静态线。可用函数:{functions}",
|
||||
"timelion.help.functions.common.args.fitHelpText": "用于将序列拟合到目标时间跨度和时间间隔的算法。可用:{fitFunctions}",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue