mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
parent
f3104aa77e
commit
d02fb7a928
104 changed files with 1814 additions and 3098 deletions
|
@ -37,7 +37,10 @@
|
|||
"savedObjects": "src/plugins/saved_objects",
|
||||
"server": "src/legacy/server",
|
||||
"statusPage": "src/legacy/core_plugins/status_page",
|
||||
"telemetry": "src/legacy/core_plugins/telemetry",
|
||||
"telemetry": [
|
||||
"src/legacy/core_plugins/telemetry",
|
||||
"src/plugins/telemetry"
|
||||
],
|
||||
"tileMap": "src/legacy/core_plugins/tile_map",
|
||||
"timelion": ["src/legacy/core_plugins/timelion", "src/legacy/core_plugins/vis_type_timelion", "src/plugins/timelion"],
|
||||
"uiActions": "src/plugins/ui_actions",
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ApplicationStart](./kibana-plugin-public.applicationstart.md) > [currentAppId$](./kibana-plugin-public.applicationstart.currentappid_.md)
|
||||
|
||||
## ApplicationStart.currentAppId$ property
|
||||
|
||||
An observable that emits the current application id and each subsequent id update.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
currentAppId$: Observable<string | undefined>;
|
||||
```
|
|
@ -16,6 +16,7 @@ export interface ApplicationStart
|
|||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [capabilities](./kibana-plugin-public.applicationstart.capabilities.md) | <code>RecursiveReadonly<Capabilities></code> | Gets the read-only capabilities. |
|
||||
| [currentAppId$](./kibana-plugin-public.applicationstart.currentappid_.md) | <code>Observable<string | undefined></code> | An observable that emits the current application id and each subsequent id update. |
|
||||
|
||||
## Methods
|
||||
|
||||
|
|
|
@ -43,12 +43,17 @@ const createInternalSetupContractMock = (): jest.Mocked<InternalApplicationSetup
|
|||
registerMountContext: jest.fn(),
|
||||
});
|
||||
|
||||
const createStartContractMock = (): jest.Mocked<ApplicationStart> => ({
|
||||
capabilities: capabilitiesServiceMock.createStartContract().capabilities,
|
||||
navigateToApp: jest.fn(),
|
||||
getUrlForApp: jest.fn(),
|
||||
registerMountContext: jest.fn(),
|
||||
});
|
||||
const createStartContractMock = (): jest.Mocked<ApplicationStart> => {
|
||||
const currentAppId$ = new Subject<string | undefined>();
|
||||
|
||||
return {
|
||||
currentAppId$: currentAppId$.asObservable(),
|
||||
capabilities: capabilitiesServiceMock.createStartContract().capabilities,
|
||||
navigateToApp: jest.fn(),
|
||||
getUrlForApp: jest.fn(),
|
||||
registerMountContext: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
const createInternalStartContractMock = (): jest.Mocked<InternalApplicationStart> => {
|
||||
const currentAppId$ = new Subject<string | undefined>();
|
||||
|
|
|
@ -612,11 +612,19 @@ export interface ApplicationStart {
|
|||
contextName: T,
|
||||
provider: IContextProvider<AppMountDeprecated, T>
|
||||
): void;
|
||||
|
||||
/**
|
||||
* An observable that emits the current application id and each subsequent id update.
|
||||
*/
|
||||
currentAppId$: Observable<string | undefined>;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface InternalApplicationStart
|
||||
extends Pick<ApplicationStart, 'capabilities' | 'navigateToApp' | 'getUrlForApp'> {
|
||||
extends Pick<
|
||||
ApplicationStart,
|
||||
'capabilities' | 'navigateToApp' | 'getUrlForApp' | 'currentAppId$'
|
||||
> {
|
||||
/**
|
||||
* Apps available based on the current capabilities.
|
||||
* Should be used to show navigation links and make routing decisions.
|
||||
|
@ -640,7 +648,6 @@ export interface InternalApplicationStart
|
|||
): void;
|
||||
|
||||
// Internal APIs
|
||||
currentAppId$: Observable<string | undefined>;
|
||||
getComponent(): JSX.Element | null;
|
||||
}
|
||||
|
||||
|
|
|
@ -121,6 +121,7 @@ export class LegacyPlatformService {
|
|||
const legacyCore: LegacyCoreStart = {
|
||||
...core,
|
||||
application: {
|
||||
currentAppId$: core.application.currentAppId$,
|
||||
capabilities: core.application.capabilities,
|
||||
getUrlForApp: core.application.getUrlForApp,
|
||||
navigateToApp: core.application.navigateToApp,
|
||||
|
|
|
@ -134,6 +134,7 @@ export function createPluginStartContext<
|
|||
): CoreStart {
|
||||
return {
|
||||
application: {
|
||||
currentAppId$: deps.application.currentAppId$,
|
||||
capabilities: deps.application.capabilities,
|
||||
navigateToApp: deps.application.navigateToApp,
|
||||
getUrlForApp: deps.application.getUrlForApp,
|
||||
|
|
|
@ -98,6 +98,7 @@ export interface ApplicationSetup {
|
|||
// @public (undocumented)
|
||||
export interface ApplicationStart {
|
||||
capabilities: RecursiveReadonly<Capabilities>;
|
||||
currentAppId$: Observable<string | undefined>;
|
||||
getUrlForApp(appId: string, options?: {
|
||||
path?: string;
|
||||
}): string;
|
||||
|
|
|
@ -115,6 +115,9 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({
|
|||
renameFromRoot('optimize.lazyHost', 'optimize.watchHost'),
|
||||
renameFromRoot('optimize.lazyPrebuild', 'optimize.watchPrebuild'),
|
||||
renameFromRoot('optimize.lazyProxyTimeout', 'optimize.watchProxyTimeout'),
|
||||
renameFromRoot('xpack.xpack_main.telemetry.config', 'telemetry.config'),
|
||||
renameFromRoot('xpack.xpack_main.telemetry.url', 'telemetry.url'),
|
||||
renameFromRoot('xpack.xpack_main.telemetry.enabled', 'telemetry.enabled'),
|
||||
renameFromRoot('xpack.telemetry.enabled', 'telemetry.enabled'),
|
||||
renameFromRoot('xpack.telemetry.config', 'telemetry.config'),
|
||||
renameFromRoot('xpack.telemetry.banner', 'telemetry.banner'),
|
||||
|
|
|
@ -18,30 +18,7 @@
|
|||
*/
|
||||
|
||||
import { npSetup, npStart } from 'ui/new_platform';
|
||||
import chrome from 'ui/chrome';
|
||||
import { HomePlugin, LegacyAngularInjectedDependencies } from './plugin';
|
||||
import { TelemetryOptInProvider } from '../../../telemetry/public/services';
|
||||
import { IPrivate } from '../../../../../plugins/kibana_legacy/public';
|
||||
|
||||
/**
|
||||
* Get dependencies relying on the global angular context.
|
||||
* They also have to get resolved together with the legacy imports above
|
||||
*/
|
||||
async function getAngularDependencies(): Promise<LegacyAngularInjectedDependencies> {
|
||||
const injector = await chrome.dangerouslyGetActiveInjector();
|
||||
|
||||
const Private = injector.get<IPrivate>('Private');
|
||||
|
||||
const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled');
|
||||
const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner');
|
||||
const telemetryOptInProvider = Private(TelemetryOptInProvider);
|
||||
|
||||
return {
|
||||
telemetryOptInProvider,
|
||||
shouldShowTelemetryOptIn:
|
||||
telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(),
|
||||
};
|
||||
}
|
||||
import { HomePlugin } from './plugin';
|
||||
|
||||
(async () => {
|
||||
const instance = new HomePlugin();
|
||||
|
@ -49,10 +26,8 @@ async function getAngularDependencies(): Promise<LegacyAngularInjectedDependenci
|
|||
...npSetup.plugins,
|
||||
__LEGACY: {
|
||||
metadata: npStart.core.injectedMetadata.getLegacyMetadata(),
|
||||
getAngularDependencies,
|
||||
},
|
||||
});
|
||||
instance.start(npStart.core, {
|
||||
...npStart.plugins,
|
||||
});
|
||||
|
||||
instance.start(npStart.core, npStart.plugins);
|
||||
})();
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
UiSettingsState,
|
||||
} from 'kibana/public';
|
||||
import { UiStatsMetricType } from '@kbn/analytics';
|
||||
import { TelemetryPluginStart } from '../../../../../plugins/telemetry/public';
|
||||
import {
|
||||
Environment,
|
||||
HomePublicPluginSetup,
|
||||
|
@ -53,7 +54,6 @@ export interface HomeKibanaServices {
|
|||
};
|
||||
getInjected: (name: string, defaultValue?: any) => unknown;
|
||||
chrome: ChromeStart;
|
||||
telemetryOptInProvider: any;
|
||||
uiSettings: IUiSettingsClient;
|
||||
config: KibanaLegacySetup['config'];
|
||||
homeConfig: HomePublicPluginSetup['config'];
|
||||
|
@ -64,10 +64,10 @@ export interface HomeKibanaServices {
|
|||
banners: OverlayStart['banners'];
|
||||
trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void;
|
||||
getBasePath: () => string;
|
||||
shouldShowTelemetryOptIn: boolean;
|
||||
docLinks: DocLinksStart;
|
||||
addBasePath: (url: string) => string;
|
||||
environment: Environment;
|
||||
telemetry?: TelemetryPluginStart;
|
||||
}
|
||||
|
||||
let services: HomeKibanaServices | null = null;
|
||||
|
|
|
@ -1054,7 +1054,6 @@ 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
|
||||
onOptInSeen={[Function]}
|
||||
onSkip={[Function]}
|
||||
urlBasePath="goober"
|
||||
/>
|
||||
|
|
|
@ -67,44 +67,6 @@ exports[`should render a Welcome screen with no telemetry disclaimer 1`] = `
|
|||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiTextColor
|
||||
className="euiText--small"
|
||||
color="subdued"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="To learn about how usage data helps us manage and improve our products and services, see our "
|
||||
id="kbn.home.dataManagementDisclaimerPrivacy"
|
||||
values={Object {}}
|
||||
/>
|
||||
<EuiLink
|
||||
href="https://www.elastic.co/legal/privacy-statement"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Privacy Statement."
|
||||
id="kbn.home.dataManagementDisclaimerPrivacyLink"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiLink>
|
||||
<FormattedMessage
|
||||
defaultMessage=" To start collection, "
|
||||
id="kbn.home.dataManagementEnableCollection"
|
||||
values={Object {}}
|
||||
/>
|
||||
<EuiLink
|
||||
href="#/management/kibana/settings"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="enable usage data here."
|
||||
id="kbn.home.dataManagementEnableCollectionLink"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiTextColor>
|
||||
<EuiSpacer
|
||||
size="xs"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
|
@ -200,16 +162,16 @@ exports[`should render a Welcome screen with the telemetry disclaimer 1`] = `
|
|||
/>
|
||||
</EuiLink>
|
||||
<FormattedMessage
|
||||
defaultMessage=" To start collection, "
|
||||
id="kbn.home.dataManagementEnableCollection"
|
||||
defaultMessage=" To stop collection, "
|
||||
id="kbn.home.dataManagementDisableCollection"
|
||||
values={Object {}}
|
||||
/>
|
||||
<EuiLink
|
||||
href="#/management/kibana/settings"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="enable usage data here."
|
||||
id="kbn.home.dataManagementEnableCollectionLink"
|
||||
defaultMessage="disable usage data here."
|
||||
id="kbn.home.dataManagementDisableCollectionLink"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiLink>
|
||||
|
|
|
@ -51,7 +51,6 @@ export class Home extends Component {
|
|||
getServices().homeConfig.disableWelcomeScreen ||
|
||||
props.localStorage.getItem(KEY_ENABLE_WELCOME) === 'false'
|
||||
);
|
||||
const currentOptInStatus = this.props.getOptInStatus();
|
||||
this.state = {
|
||||
// If welcome is enabled, we wait for loading to complete
|
||||
// before rendering. This prevents an annoying flickering
|
||||
|
@ -60,7 +59,6 @@ export class Home extends Component {
|
|||
isLoading: isWelcomeEnabled,
|
||||
isNewKibanaInstance: false,
|
||||
isWelcomeEnabled,
|
||||
currentOptInStatus,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -224,8 +222,7 @@ export class Home extends Component {
|
|||
<Welcome
|
||||
onSkip={this.skipWelcome}
|
||||
urlBasePath={this.props.urlBasePath}
|
||||
onOptInSeen={this.props.onOptInSeen}
|
||||
currentOptInStatus={this.state.currentOptInStatus}
|
||||
telemetry={this.props.telemetry}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -264,6 +261,8 @@ Home.propTypes = {
|
|||
localStorage: PropTypes.object.isRequired,
|
||||
urlBasePath: PropTypes.string.isRequired,
|
||||
mlEnabled: PropTypes.bool.isRequired,
|
||||
onOptInSeen: PropTypes.func.isRequired,
|
||||
getOptInStatus: PropTypes.func.isRequired,
|
||||
telemetry: PropTypes.shape({
|
||||
telemetryService: PropTypes.any,
|
||||
telemetryNotifications: PropTypes.any,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -35,7 +35,7 @@ export function HomeApp({ directories }) {
|
|||
getBasePath,
|
||||
addBasePath,
|
||||
environment,
|
||||
telemetryOptInProvider: { setOptInNoticeSeen, getOptIn },
|
||||
telemetry,
|
||||
} = getServices();
|
||||
const isCloudEnabled = environment.cloud;
|
||||
const mlEnabled = environment.ml;
|
||||
|
@ -84,8 +84,7 @@ export function HomeApp({ directories }) {
|
|||
find={savedObjectsClient.find}
|
||||
localStorage={localStorage}
|
||||
urlBasePath={getBasePath()}
|
||||
onOptInSeen={setOptInNoticeSeen}
|
||||
getOptInStatus={getOptIn}
|
||||
telemetry={telemetry}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="/home">
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Welcome } from './welcome';
|
||||
import { telemetryPluginMock } from '../../../../../../../plugins/telemetry/public/mocks';
|
||||
|
||||
jest.mock('../../kibana_services', () => ({
|
||||
getServices: () => ({
|
||||
|
@ -29,27 +30,32 @@ jest.mock('../../kibana_services', () => ({
|
|||
}));
|
||||
|
||||
test('should render a Welcome screen with the telemetry disclaimer', () => {
|
||||
const telemetry = telemetryPluginMock.createSetupContract();
|
||||
const component = shallow(
|
||||
// @ts-ignore
|
||||
<Welcome urlBasePath="/" onSkip={() => {}} onOptInSeen={() => {}} />
|
||||
<Welcome urlBasePath="/" onSkip={() => {}} telemetry={telemetry} />
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render a Welcome screen with the telemetry disclaimer when optIn is true', () => {
|
||||
const telemetry = telemetryPluginMock.createSetupContract();
|
||||
telemetry.telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true);
|
||||
const component = shallow(
|
||||
// @ts-ignore
|
||||
<Welcome urlBasePath="/" onSkip={() => {}} onOptInSeen={() => {}} currentOptInStatus={true} />
|
||||
<Welcome urlBasePath="/" onSkip={() => {}} telemetry={telemetry} />
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render a Welcome screen with the telemetry disclaimer when optIn is false', () => {
|
||||
const telemetry = telemetryPluginMock.createSetupContract();
|
||||
telemetry.telemetryService.getIsOptedIn = jest.fn().mockReturnValue(false);
|
||||
const component = shallow(
|
||||
// @ts-ignore
|
||||
<Welcome urlBasePath="/" onSkip={() => {}} onOptInSeen={() => {}} currentOptInStatus={false} />
|
||||
<Welcome urlBasePath="/" onSkip={() => {}} telemetry={telemetry} />
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
|
@ -59,19 +65,21 @@ test('should render a Welcome screen with no telemetry disclaimer', () => {
|
|||
// @ts-ignore
|
||||
const component = shallow(
|
||||
// @ts-ignore
|
||||
<Welcome urlBasePath="/" onSkip={() => {}} onOptInSeen={() => {}} />
|
||||
<Welcome urlBasePath="/" onSkip={() => {}} telemetry={null} />
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('fires opt-in seen when mounted', () => {
|
||||
const seen = jest.fn();
|
||||
|
||||
const telemetry = telemetryPluginMock.createSetupContract();
|
||||
const mockSetOptedInNoticeSeen = jest.fn();
|
||||
// @ts-ignore
|
||||
telemetry.telemetryNotifications.setOptedInNoticeSeen = mockSetOptedInNoticeSeen;
|
||||
shallow(
|
||||
// @ts-ignore
|
||||
<Welcome urlBasePath="/" onSkip={() => {}} onOptInSeen={seen} />
|
||||
<Welcome urlBasePath="/" onSkip={() => {}} telemetry={telemetry} />
|
||||
);
|
||||
|
||||
expect(seen).toHaveBeenCalled();
|
||||
expect(mockSetOptedInNoticeSeen).toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
@ -38,13 +38,14 @@ import {
|
|||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { getServices } from '../../kibana_services';
|
||||
import { TelemetryPluginStart } from '../../../../../../../plugins/telemetry/public';
|
||||
import { PRIVACY_STATEMENT_URL } from '../../../../../../../plugins/telemetry/common/constants';
|
||||
|
||||
import { SampleDataCard } from './sample_data';
|
||||
interface Props {
|
||||
urlBasePath: string;
|
||||
onSkip: () => void;
|
||||
onOptInSeen: () => any;
|
||||
currentOptInStatus: boolean;
|
||||
telemetry?: TelemetryPluginStart;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,8 +76,11 @@ export class Welcome extends React.Component<Props> {
|
|||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { telemetry } = this.props;
|
||||
this.services.trackUiMetric(METRIC_TYPE.LOADED, 'welcomeScreenMount');
|
||||
this.props.onOptInSeen();
|
||||
if (telemetry) {
|
||||
telemetry.telemetryNotifications.setOptedInNoticeSeen();
|
||||
}
|
||||
document.addEventListener('keydown', this.hideOnEsc);
|
||||
}
|
||||
|
||||
|
@ -85,7 +89,13 @@ export class Welcome extends React.Component<Props> {
|
|||
}
|
||||
|
||||
private renderTelemetryEnabledOrDisabledText = () => {
|
||||
if (this.props.currentOptInStatus) {
|
||||
const { telemetry } = this.props;
|
||||
if (!telemetry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isOptedIn = telemetry.telemetryService.getIsOptedIn();
|
||||
if (isOptedIn) {
|
||||
return (
|
||||
<Fragment>
|
||||
<FormattedMessage
|
||||
|
@ -119,7 +129,7 @@ export class Welcome extends React.Component<Props> {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { urlBasePath } = this.props;
|
||||
const { urlBasePath, telemetry } = this.props;
|
||||
return (
|
||||
<EuiPortal>
|
||||
<div className="homWelcome">
|
||||
|
@ -154,24 +164,24 @@ export class Welcome extends React.Component<Props> {
|
|||
onDecline={this.onSampleDataDecline}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiTextColor className="euiText--small" color="subdued">
|
||||
<FormattedMessage
|
||||
id="kbn.home.dataManagementDisclaimerPrivacy"
|
||||
defaultMessage="To learn about how usage data helps us manage and improve our products and services, see our "
|
||||
/>
|
||||
<EuiLink
|
||||
href="https://www.elastic.co/legal/privacy-statement"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.home.dataManagementDisclaimerPrivacyLink"
|
||||
defaultMessage="Privacy Statement."
|
||||
/>
|
||||
</EuiLink>
|
||||
{this.renderTelemetryEnabledOrDisabledText()}
|
||||
</EuiTextColor>
|
||||
<EuiSpacer size="xs" />
|
||||
{!!telemetry && (
|
||||
<Fragment>
|
||||
<EuiTextColor className="euiText--small" color="subdued">
|
||||
<FormattedMessage
|
||||
id="kbn.home.dataManagementDisclaimerPrivacy"
|
||||
defaultMessage="To learn about how usage data helps us manage and improve our products and services, see our "
|
||||
/>
|
||||
<EuiLink href={PRIVACY_STATEMENT_URL} target="_blank" rel="noopener">
|
||||
<FormattedMessage
|
||||
id="kbn.home.dataManagementDisclaimerPrivacyLink"
|
||||
defaultMessage="Privacy Statement."
|
||||
/>
|
||||
</EuiLink>
|
||||
{this.renderTelemetryEnabledOrDisabledText()}
|
||||
</EuiTextColor>
|
||||
<EuiSpacer size="xs" />
|
||||
</Fragment>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
import { CoreSetup, CoreStart, LegacyNavLink, Plugin, UiSettingsState } from 'kibana/public';
|
||||
|
||||
import { DataPublicPluginStart } from 'src/plugins/data/public';
|
||||
import { TelemetryPluginStart } from 'src/plugins/telemetry/public';
|
||||
import { setServices } from './kibana_services';
|
||||
import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public';
|
||||
import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public';
|
||||
|
@ -30,14 +31,10 @@ import {
|
|||
FeatureCatalogueEntry,
|
||||
} from '../../../../../plugins/home/public';
|
||||
|
||||
export interface LegacyAngularInjectedDependencies {
|
||||
telemetryOptInProvider: any;
|
||||
shouldShowTelemetryOptIn: boolean;
|
||||
}
|
||||
|
||||
export interface HomePluginStartDependencies {
|
||||
data: DataPublicPluginStart;
|
||||
home: HomePublicPluginStart;
|
||||
telemetry?: TelemetryPluginStart;
|
||||
}
|
||||
|
||||
export interface HomePluginSetupDependencies {
|
||||
|
@ -55,7 +52,6 @@ export interface HomePluginSetupDependencies {
|
|||
devMode: boolean;
|
||||
uiSettings: { defaults: UiSettingsState; user?: UiSettingsState | undefined };
|
||||
};
|
||||
getAngularDependencies: () => Promise<LegacyAngularInjectedDependencies>;
|
||||
};
|
||||
usageCollection: UsageCollectionSetup;
|
||||
kibanaLegacy: KibanaLegacySetup;
|
||||
|
@ -67,6 +63,7 @@ export class HomePlugin implements Plugin {
|
|||
private savedObjectsClient: any = null;
|
||||
private environment: Environment | null = null;
|
||||
private directories: readonly FeatureCatalogueEntry[] | null = null;
|
||||
private telemetry?: TelemetryPluginStart;
|
||||
|
||||
setup(
|
||||
core: CoreSetup,
|
||||
|
@ -74,7 +71,7 @@ export class HomePlugin implements Plugin {
|
|||
home,
|
||||
kibanaLegacy,
|
||||
usageCollection,
|
||||
__LEGACY: { getAngularDependencies, ...legacyServices },
|
||||
__LEGACY: { ...legacyServices },
|
||||
}: HomePluginSetupDependencies
|
||||
) {
|
||||
kibanaLegacy.registerLegacyApp({
|
||||
|
@ -82,7 +79,6 @@ export class HomePlugin implements Plugin {
|
|||
title: 'Home',
|
||||
mount: async ({ core: contextCore }, params) => {
|
||||
const trackUiMetric = usageCollection.reportUiStats.bind(usageCollection, 'Kibana_home');
|
||||
const angularDependencies = await getAngularDependencies();
|
||||
setServices({
|
||||
...legacyServices,
|
||||
trackUiMetric,
|
||||
|
@ -92,6 +88,7 @@ export class HomePlugin implements Plugin {
|
|||
getInjected: core.injectedMetadata.getInjectedVar,
|
||||
docLinks: contextCore.docLinks,
|
||||
savedObjectsClient: this.savedObjectsClient!,
|
||||
telemetry: this.telemetry,
|
||||
chrome: contextCore.chrome,
|
||||
uiSettings: core.uiSettings,
|
||||
addBasePath: core.http.basePath.prepend,
|
||||
|
@ -101,7 +98,6 @@ export class HomePlugin implements Plugin {
|
|||
config: kibanaLegacy.config,
|
||||
homeConfig: home.config,
|
||||
directories: this.directories!,
|
||||
...angularDependencies,
|
||||
});
|
||||
const { renderApp } = await import('./np_ready/application');
|
||||
return await renderApp(params.element);
|
||||
|
@ -109,10 +105,11 @@ export class HomePlugin implements Plugin {
|
|||
});
|
||||
}
|
||||
|
||||
start(core: CoreStart, { data, home }: HomePluginStartDependencies) {
|
||||
start(core: CoreStart, { data, home, telemetry }: HomePluginStartDependencies) {
|
||||
this.environment = home.environment.get();
|
||||
this.directories = home.featureCatalogue.get();
|
||||
this.dataStart = data;
|
||||
this.telemetry = telemetry;
|
||||
this.savedObjectsClient = core.savedObjects.client;
|
||||
}
|
||||
|
||||
|
|
|
@ -43,11 +43,6 @@ export const getConfigTelemetryDesc = () => {
|
|||
*/
|
||||
export const REPORT_INTERVAL_MS = 86400000;
|
||||
|
||||
/*
|
||||
* Key for the localStorage service
|
||||
*/
|
||||
export const LOCALSTORAGE_KEY = 'telemetry.data';
|
||||
|
||||
/**
|
||||
* Link to the Elastic Telemetry privacy statement.
|
||||
*/
|
||||
|
|
|
@ -1,41 +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.
|
||||
*/
|
||||
|
||||
import { KibanaConfig } from 'src/legacy/server/kbn_server';
|
||||
|
||||
export function getXpackConfigWithDeprecated(config: KibanaConfig, configPath: string) {
|
||||
try {
|
||||
const deprecatedXpackmainConfig = config.get(`xpack.xpack_main.${configPath}`);
|
||||
if (typeof deprecatedXpackmainConfig !== 'undefined') {
|
||||
return deprecatedXpackmainConfig;
|
||||
}
|
||||
} catch (err) {
|
||||
// swallow error
|
||||
}
|
||||
try {
|
||||
const deprecatedXpackConfig = config.get(`xpack.${configPath}`);
|
||||
if (typeof deprecatedXpackConfig !== 'undefined') {
|
||||
return deprecatedXpackConfig;
|
||||
}
|
||||
} catch (err) {
|
||||
// swallow error
|
||||
}
|
||||
|
||||
return config.get(configPath);
|
||||
}
|
|
@ -22,14 +22,17 @@ import { resolve } from 'path';
|
|||
import JoiNamespace from 'joi';
|
||||
import { Server } from 'hapi';
|
||||
import { CoreSetup, PluginInitializerContext } from 'src/core/server';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { getConfigPath } from '../../../core/server/path';
|
||||
// @ts-ignore
|
||||
import mappings from './mappings.json';
|
||||
import { CONFIG_TELEMETRY, getConfigTelemetryDesc } from './common/constants';
|
||||
import { getXpackConfigWithDeprecated } from './common/get_xpack_config_with_deprecated';
|
||||
import { telemetryPlugin, replaceTelemetryInjectedVars, FetcherTask, PluginsSetup } from './server';
|
||||
import {
|
||||
telemetryPlugin,
|
||||
replaceTelemetryInjectedVars,
|
||||
FetcherTask,
|
||||
PluginsSetup,
|
||||
handleOldSettings,
|
||||
} from './server';
|
||||
|
||||
const ENDPOINT_VERSION = 'v2';
|
||||
|
||||
|
@ -76,16 +79,6 @@ const telemetry = (kibana: any) => {
|
|||
},
|
||||
uiExports: {
|
||||
managementSections: ['plugins/telemetry/views/management'],
|
||||
uiSettingDefaults: {
|
||||
[CONFIG_TELEMETRY]: {
|
||||
name: i18n.translate('telemetry.telemetryConfigTitle', {
|
||||
defaultMessage: 'Telemetry opt-in',
|
||||
}),
|
||||
description: getConfigTelemetryDesc(),
|
||||
value: false,
|
||||
readonly: true,
|
||||
},
|
||||
},
|
||||
savedObjectSchemas: {
|
||||
telemetry: {
|
||||
isNamespaceAgnostic: true,
|
||||
|
@ -98,11 +91,11 @@ const telemetry = (kibana: any) => {
|
|||
injectDefaultVars(server: Server) {
|
||||
const config = server.config();
|
||||
return {
|
||||
telemetryEnabled: getXpackConfigWithDeprecated(config, 'telemetry.enabled'),
|
||||
telemetryUrl: getXpackConfigWithDeprecated(config, 'telemetry.url'),
|
||||
telemetryEnabled: config.get('telemetry.enabled'),
|
||||
telemetryUrl: config.get('telemetry.url'),
|
||||
telemetryBanner:
|
||||
config.get('telemetry.allowChangingOptInStatus') !== false &&
|
||||
getXpackConfigWithDeprecated(config, 'telemetry.banner'),
|
||||
config.get('telemetry.banner'),
|
||||
telemetryOptedIn: config.get('telemetry.optIn'),
|
||||
telemetryOptInStatusUrl: config.get('telemetry.optInStatusUrl'),
|
||||
allowChangingOptInStatus: config.get('telemetry.allowChangingOptInStatus'),
|
||||
|
@ -110,14 +103,13 @@ const telemetry = (kibana: any) => {
|
|||
telemetryNotifyUserAboutOptInDefault: false,
|
||||
};
|
||||
},
|
||||
hacks: ['plugins/telemetry/hacks/telemetry_init', 'plugins/telemetry/hacks/telemetry_opt_in'],
|
||||
mappings,
|
||||
},
|
||||
postInit(server: Server) {
|
||||
const fetcherTask = new FetcherTask(server);
|
||||
fetcherTask.start();
|
||||
},
|
||||
init(server: Server) {
|
||||
async init(server: Server) {
|
||||
const { usageCollection } = server.newPlatform.setup.plugins;
|
||||
const initializerContext = {
|
||||
env: {
|
||||
|
@ -145,6 +137,12 @@ const telemetry = (kibana: any) => {
|
|||
log: server.log,
|
||||
} as any) as CoreSetup;
|
||||
|
||||
try {
|
||||
await handleOldSettings(server);
|
||||
} catch (err) {
|
||||
server.log(['warning', 'telemetry'], 'Unable to update legacy telemetry configs.');
|
||||
}
|
||||
|
||||
const pluginsSetup: PluginsSetup = {
|
||||
usageCollection,
|
||||
};
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TelemetryForm doesn't render form when not allowed to change optIn status 1`] = `""`;
|
||||
|
||||
exports[`TelemetryForm renders as expected when allows to change optIn status 1`] = `
|
||||
<Fragment>
|
||||
<EuiPanel
|
||||
paddingSize="l"
|
||||
>
|
||||
<EuiForm>
|
||||
<EuiText>
|
||||
<EuiFlexGroup
|
||||
alignItems="baseline"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
defaultMessage="Usage Data"
|
||||
id="telemetry.usageDataTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h2>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<Field
|
||||
clear={[Function]}
|
||||
enableSaving={true}
|
||||
save={[Function]}
|
||||
setting={
|
||||
Object {
|
||||
"ariaName": "Provide usage statistics",
|
||||
"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."
|
||||
id="telemetry.telemetryConfigAndLinkDescription"
|
||||
values={
|
||||
Object {
|
||||
"privacyStatementLink": <ForwardRef
|
||||
href="https://www.elastic.co/legal/privacy-statement"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Privacy Statement"
|
||||
id="telemetry.readOurUsageDataPrivacyStatementLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</ForwardRef>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<ForwardRef
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="See an example of what we collect"
|
||||
id="telemetry.seeExampleOfWhatWeCollectLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</ForwardRef>
|
||||
</p>
|
||||
</React.Fragment>,
|
||||
"type": "boolean",
|
||||
"value": false,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiForm>
|
||||
</EuiPanel>
|
||||
</Fragment>
|
||||
`;
|
|
@ -1,83 +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.
|
||||
*/
|
||||
|
||||
import { mockInjectedMetadata } from '../services/telemetry_opt_in.test.mocks';
|
||||
import React from 'react';
|
||||
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { TelemetryForm } from './telemetry_form';
|
||||
import { TelemetryOptInProvider } from '../services';
|
||||
|
||||
const buildTelemetryOptInProvider = () => {
|
||||
const mockHttp = {
|
||||
post: jest.fn(),
|
||||
};
|
||||
|
||||
const mockInjector = {
|
||||
get: key => {
|
||||
switch (key) {
|
||||
case '$http':
|
||||
return mockHttp;
|
||||
case 'allowChangingOptInStatus':
|
||||
return true;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const chrome = {
|
||||
addBasePath: url => url,
|
||||
};
|
||||
|
||||
return new TelemetryOptInProvider(mockInjector, chrome);
|
||||
};
|
||||
|
||||
describe('TelemetryForm', () => {
|
||||
it('renders as expected when allows to change optIn status', () => {
|
||||
mockInjectedMetadata({ telemetryOptedIn: null, allowChangingOptInStatus: true });
|
||||
|
||||
expect(
|
||||
shallowWithIntl(
|
||||
<TelemetryForm
|
||||
spacesEnabled={false}
|
||||
query={{ text: '' }}
|
||||
onQueryMatchChange={jest.fn()}
|
||||
telemetryOptInProvider={buildTelemetryOptInProvider()}
|
||||
enableSaving={true}
|
||||
/>
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`doesn't render form when not allowed to change optIn status`, () => {
|
||||
mockInjectedMetadata({ telemetryOptedIn: null, allowChangingOptInStatus: false });
|
||||
|
||||
expect(
|
||||
shallowWithIntl(
|
||||
<TelemetryForm
|
||||
spacesEnabled={false}
|
||||
query={{ text: '' }}
|
||||
onQueryMatchChange={jest.fn()}
|
||||
telemetryOptInProvider={buildTelemetryOptInProvider()}
|
||||
enableSaving={true}
|
||||
/>
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -1,55 +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.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { fetchTelemetry } from '../fetch_telemetry';
|
||||
|
||||
describe('fetch_telemetry', () => {
|
||||
it('fetchTelemetry calls expected URL with 20 minutes - now', () => {
|
||||
const response = Promise.resolve();
|
||||
const $http = {
|
||||
post: sinon.stub(),
|
||||
};
|
||||
const basePath = 'fake';
|
||||
const moment = {
|
||||
subtract: sinon.stub(),
|
||||
toISOString: () => 'max123',
|
||||
};
|
||||
|
||||
moment.subtract.withArgs(20, 'minutes').returns({
|
||||
toISOString: () => 'min456',
|
||||
});
|
||||
|
||||
$http.post
|
||||
.withArgs(`fake/api/telemetry/v2/clusters/_stats`, {
|
||||
unencrypted: true,
|
||||
timeRange: {
|
||||
min: 'min456',
|
||||
max: 'max123',
|
||||
},
|
||||
})
|
||||
.returns(response);
|
||||
|
||||
expect(fetchTelemetry($http, { basePath, _moment: () => moment, unencrypted: true })).to.be(
|
||||
response
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,29 +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.
|
||||
*/
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
|
||||
// This overrides settings for other UI tests
|
||||
uiModules
|
||||
.get('kibana')
|
||||
// disable stat reporting while running tests,
|
||||
// MockInjector used in these tests is not impacted
|
||||
.constant('telemetryEnabled', false)
|
||||
.constant('telemetryOptedIn', null)
|
||||
.constant('telemetryUrl', 'not.a.valid.url.0');
|
|
@ -1,44 +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.
|
||||
*/
|
||||
|
||||
import uiChrome from 'ui/chrome';
|
||||
import moment from 'moment';
|
||||
|
||||
/**
|
||||
* Fetch Telemetry data by calling the Kibana API.
|
||||
*
|
||||
* @param {Object} $http The HTTP handler
|
||||
* @param {String} basePath The base URI
|
||||
* @param {Function} _moment moment.js, but injectable for tests
|
||||
* @return {Promise} An array of cluster Telemetry objects.
|
||||
*/
|
||||
export function fetchTelemetry(
|
||||
$http,
|
||||
{ basePath = uiChrome.getBasePath(), _moment = moment, unencrypted = false } = {}
|
||||
) {
|
||||
return $http.post(`${basePath}/api/telemetry/v2/clusters/_stats`, {
|
||||
unencrypted,
|
||||
timeRange: {
|
||||
min: _moment()
|
||||
.subtract(20, 'minutes')
|
||||
.toISOString(),
|
||||
max: _moment().toISOString(),
|
||||
},
|
||||
});
|
||||
}
|
|
@ -1,120 +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.
|
||||
*/
|
||||
|
||||
import { REPORT_INTERVAL_MS, LOCALSTORAGE_KEY } from '../../common/constants';
|
||||
|
||||
export class Telemetry {
|
||||
/**
|
||||
* @param {Object} $injector - AngularJS injector service
|
||||
* @param {Function} fetchTelemetry Method used to fetch telemetry data (expects an array response)
|
||||
*/
|
||||
constructor($injector, fetchTelemetry) {
|
||||
this._storage = $injector.get('localStorage');
|
||||
this._$http = $injector.get('$http');
|
||||
this._telemetryUrl = $injector.get('telemetryUrl');
|
||||
this._telemetryOptedIn = $injector.get('telemetryOptedIn');
|
||||
this._fetchTelemetry = fetchTelemetry;
|
||||
this._sending = false;
|
||||
|
||||
// try to load the local storage data
|
||||
const attributes = this._storage.get(LOCALSTORAGE_KEY) || {};
|
||||
this._lastReport = attributes.lastReport;
|
||||
}
|
||||
|
||||
_saveToBrowser() {
|
||||
// we are the only code that manipulates this key, so it's safe to blindly overwrite the whole object
|
||||
this._storage.set(LOCALSTORAGE_KEY, { lastReport: this._lastReport });
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we are due to send a new report.
|
||||
*
|
||||
* @returns {Boolean} true if a new report should be sent. false otherwise.
|
||||
*/
|
||||
_checkReportStatus() {
|
||||
// check if opt-in for telemetry is enabled
|
||||
if (this._telemetryOptedIn) {
|
||||
// returns NaN for any malformed or unset (null/undefined) value
|
||||
const lastReport = parseInt(this._lastReport, 10);
|
||||
// If it's been a day since we last sent telemetry
|
||||
if (isNaN(lastReport) || Date.now() - lastReport > REPORT_INTERVAL_MS) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check report permission and if passes, send the report
|
||||
*
|
||||
* @returns {Promise} Always.
|
||||
*/
|
||||
_sendIfDue() {
|
||||
if (this._sending || !this._checkReportStatus()) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
// mark that we are working so future requests are ignored until we're done
|
||||
this._sending = true;
|
||||
|
||||
return (
|
||||
this._fetchTelemetry()
|
||||
.then(response => {
|
||||
const clusters = [].concat(response.data);
|
||||
return Promise.all(
|
||||
clusters.map(cluster => {
|
||||
const req = {
|
||||
method: 'POST',
|
||||
url: this._telemetryUrl,
|
||||
data: cluster,
|
||||
};
|
||||
// if passing data externally, then suppress kbnXsrfToken
|
||||
if (this._telemetryUrl.match(/^https/)) {
|
||||
req.kbnXsrfToken = false;
|
||||
}
|
||||
return this._$http(req);
|
||||
})
|
||||
);
|
||||
})
|
||||
// the response object is ignored because we do not check it
|
||||
.then(() => {
|
||||
// we sent a report, so we need to record and store the current timestamp
|
||||
this._lastReport = Date.now();
|
||||
this._saveToBrowser();
|
||||
})
|
||||
// no ajaxErrorHandlers for telemetry
|
||||
.catch(() => null)
|
||||
.then(() => {
|
||||
this._sending = false;
|
||||
return true; // sent, but not necessarilly successfully
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public method
|
||||
*
|
||||
* @returns {Number} `window.setInterval` response to allow cancelling the interval.
|
||||
*/
|
||||
start() {
|
||||
// continuously check if it's due time for a report
|
||||
return window.setInterval(() => this._sendIfDue(), 60000);
|
||||
}
|
||||
} // end class
|
|
@ -1,306 +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.
|
||||
*/
|
||||
|
||||
import { Telemetry } from './telemetry';
|
||||
import { REPORT_INTERVAL_MS, LOCALSTORAGE_KEY } from '../../common/constants';
|
||||
|
||||
describe('telemetry class', () => {
|
||||
const clusters = [{ cluster_uuid: 'fake-123' }, { cluster_uuid: 'fake-456' }];
|
||||
const telemetryUrl = 'https://not.a.valid.url.0';
|
||||
const mockFetchTelemetry = () => Promise.resolve({ data: clusters });
|
||||
// returns a function that behaves like the injector by fetching the requested key from the object directly
|
||||
// for example:
|
||||
// { '$http': jest.fn() } would be how to mock the '$http' injector value
|
||||
const mockInjectorFromObject = object => {
|
||||
return { get: key => object[key] };
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
test('defaults lastReport if unset', () => {
|
||||
const injector = {
|
||||
localStorage: {
|
||||
get: jest.fn().mockReturnValueOnce(undefined),
|
||||
},
|
||||
$http: jest.fn(),
|
||||
telemetryOptedIn: true,
|
||||
telemetryUrl,
|
||||
};
|
||||
const telemetry = new Telemetry(mockInjectorFromObject(injector), mockFetchTelemetry);
|
||||
|
||||
expect(telemetry._storage).toBe(injector.localStorage);
|
||||
expect(telemetry._$http).toBe(injector.$http);
|
||||
expect(telemetry._telemetryOptedIn).toBe(injector.telemetryOptedIn);
|
||||
expect(telemetry._telemetryUrl).toBe(injector.telemetryUrl);
|
||||
expect(telemetry._fetchTelemetry).toBe(mockFetchTelemetry);
|
||||
expect(telemetry._sending).toBe(false);
|
||||
expect(telemetry._lastReport).toBeUndefined();
|
||||
|
||||
expect(injector.localStorage.get).toHaveBeenCalledTimes(1);
|
||||
expect(injector.localStorage.get).toHaveBeenCalledWith(LOCALSTORAGE_KEY);
|
||||
});
|
||||
|
||||
test('uses lastReport if set', () => {
|
||||
const lastReport = Date.now();
|
||||
const injector = {
|
||||
localStorage: {
|
||||
get: jest.fn().mockReturnValueOnce({ lastReport }),
|
||||
},
|
||||
$http: jest.fn(),
|
||||
telemetryOptedIn: true,
|
||||
telemetryUrl,
|
||||
};
|
||||
const telemetry = new Telemetry(mockInjectorFromObject(injector), mockFetchTelemetry);
|
||||
|
||||
expect(telemetry._storage).toBe(injector.localStorage);
|
||||
expect(telemetry._$http).toBe(injector.$http);
|
||||
expect(telemetry._telemetryOptedIn).toBe(injector.telemetryOptedIn);
|
||||
expect(telemetry._telemetryUrl).toBe(injector.telemetryUrl);
|
||||
expect(telemetry._fetchTelemetry).toBe(mockFetchTelemetry);
|
||||
expect(telemetry._sending).toBe(false);
|
||||
expect(telemetry._lastReport).toBe(lastReport);
|
||||
|
||||
expect(injector.localStorage.get).toHaveBeenCalledTimes(1);
|
||||
expect(injector.localStorage.get).toHaveBeenCalledWith(LOCALSTORAGE_KEY);
|
||||
});
|
||||
});
|
||||
|
||||
test('_saveToBrowser uses _lastReport', () => {
|
||||
const injector = {
|
||||
localStorage: {
|
||||
get: jest.fn().mockReturnValueOnce({ random: 'junk', gets: 'thrown away' }),
|
||||
set: jest.fn(),
|
||||
},
|
||||
};
|
||||
const lastReport = Date.now();
|
||||
const telemetry = new Telemetry(mockInjectorFromObject(injector), mockFetchTelemetry);
|
||||
telemetry._lastReport = lastReport;
|
||||
|
||||
telemetry._saveToBrowser();
|
||||
|
||||
expect(injector.localStorage.set).toHaveBeenCalledTimes(1);
|
||||
expect(injector.localStorage.set).toHaveBeenCalledWith(LOCALSTORAGE_KEY, { lastReport });
|
||||
});
|
||||
|
||||
describe('_checkReportStatus', () => {
|
||||
// send the report if we get to check the time
|
||||
const lastReportShouldSendNow = Date.now() - REPORT_INTERVAL_MS - 1;
|
||||
|
||||
test('returns false whenever telemetryOptedIn is null', () => {
|
||||
const injector = {
|
||||
localStorage: {
|
||||
get: jest.fn().mockReturnValueOnce({ lastReport: lastReportShouldSendNow }),
|
||||
},
|
||||
telemetryOptedIn: null, // not yet opted in
|
||||
};
|
||||
const telemetry = new Telemetry(mockInjectorFromObject(injector), mockFetchTelemetry);
|
||||
|
||||
expect(telemetry._checkReportStatus()).toBe(false);
|
||||
});
|
||||
|
||||
test('returns false whenever telemetryOptedIn is false', () => {
|
||||
const injector = {
|
||||
localStorage: {
|
||||
get: jest.fn().mockReturnValueOnce({ lastReport: lastReportShouldSendNow }),
|
||||
},
|
||||
telemetryOptedIn: false, // opted out explicitly
|
||||
};
|
||||
const telemetry = new Telemetry(mockInjectorFromObject(injector), mockFetchTelemetry);
|
||||
|
||||
expect(telemetry._checkReportStatus()).toBe(false);
|
||||
});
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/27922
|
||||
test.skip('returns false if last report is too recent', () => {
|
||||
const injector = {
|
||||
localStorage: {
|
||||
// we expect '>', not '>='
|
||||
get: jest.fn().mockReturnValueOnce({ lastReport: Date.now() - REPORT_INTERVAL_MS }),
|
||||
},
|
||||
telemetryOptedIn: true,
|
||||
};
|
||||
const telemetry = new Telemetry(mockInjectorFromObject(injector), mockFetchTelemetry);
|
||||
|
||||
expect(telemetry._checkReportStatus()).toBe(false);
|
||||
});
|
||||
|
||||
test('returns true if last report is not defined', () => {
|
||||
const injector = {
|
||||
localStorage: {
|
||||
get: jest.fn().mockReturnValueOnce({}),
|
||||
},
|
||||
telemetryOptedIn: true,
|
||||
};
|
||||
const telemetry = new Telemetry(mockInjectorFromObject(injector), mockFetchTelemetry);
|
||||
|
||||
expect(telemetry._checkReportStatus()).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true if last report is defined and old enough', () => {
|
||||
const injector = {
|
||||
localStorage: {
|
||||
get: jest.fn().mockReturnValueOnce({ lastReport: lastReportShouldSendNow }),
|
||||
},
|
||||
telemetryOptedIn: true,
|
||||
};
|
||||
const telemetry = new Telemetry(mockInjectorFromObject(injector), mockFetchTelemetry);
|
||||
|
||||
expect(telemetry._checkReportStatus()).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true if last report is defined and old enough as a string', () => {
|
||||
const injector = {
|
||||
localStorage: {
|
||||
get: jest.fn().mockReturnValueOnce({ lastReport: lastReportShouldSendNow.toString() }),
|
||||
},
|
||||
telemetryOptedIn: true,
|
||||
};
|
||||
const telemetry = new Telemetry(mockInjectorFromObject(injector), mockFetchTelemetry);
|
||||
|
||||
expect(telemetry._checkReportStatus()).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true if last report is defined and malformed', () => {
|
||||
const injector = {
|
||||
localStorage: {
|
||||
get: jest.fn().mockReturnValueOnce({ lastReport: { not: { a: 'number' } } }),
|
||||
},
|
||||
telemetryOptedIn: true,
|
||||
};
|
||||
const telemetry = new Telemetry(mockInjectorFromObject(injector), mockFetchTelemetry);
|
||||
|
||||
expect(telemetry._checkReportStatus()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_sendIfDue', () => {
|
||||
test('ignores and returns false if already sending', () => {
|
||||
const injector = {
|
||||
localStorage: {
|
||||
get: jest.fn().mockReturnValueOnce(undefined), // never sent
|
||||
},
|
||||
telemetryOptedIn: true,
|
||||
};
|
||||
const telemetry = new Telemetry(mockInjectorFromObject(injector), mockFetchTelemetry);
|
||||
telemetry._sending = true;
|
||||
|
||||
return expect(telemetry._sendIfDue()).resolves.toBe(false);
|
||||
});
|
||||
|
||||
test('ignores and returns false if _checkReportStatus says so', () => {
|
||||
const injector = {
|
||||
localStorage: {
|
||||
get: jest.fn().mockReturnValueOnce(undefined), // never sent, so it would try if opted in
|
||||
},
|
||||
telemetryOptedIn: false, // opted out
|
||||
};
|
||||
const telemetry = new Telemetry(mockInjectorFromObject(injector), mockFetchTelemetry);
|
||||
|
||||
return expect(telemetry._sendIfDue()).resolves.toBe(false);
|
||||
});
|
||||
|
||||
test('sends telemetry when requested', () => {
|
||||
const now = Date.now();
|
||||
const injector = {
|
||||
$http: jest.fn().mockResolvedValue({}), // ignored response
|
||||
localStorage: {
|
||||
get: jest.fn().mockReturnValueOnce({ lastReport: now - REPORT_INTERVAL_MS - 1 }),
|
||||
set: jest.fn(),
|
||||
},
|
||||
telemetryOptedIn: true,
|
||||
telemetryUrl,
|
||||
};
|
||||
const telemetry = new Telemetry(mockInjectorFromObject(injector), mockFetchTelemetry);
|
||||
|
||||
expect.hasAssertions();
|
||||
|
||||
return telemetry._sendIfDue().then(result => {
|
||||
expect(result).toBe(true);
|
||||
expect(telemetry._sending).toBe(false);
|
||||
|
||||
// should be updated
|
||||
const lastReport = telemetry._lastReport;
|
||||
|
||||
// if the test runs fast enough it should be exactly equal, but probably a few ms greater
|
||||
expect(lastReport).toBeGreaterThanOrEqual(now);
|
||||
|
||||
expect(injector.$http).toHaveBeenCalledTimes(2);
|
||||
// assert that it sent every cluster's telemetry
|
||||
clusters.forEach(cluster => {
|
||||
expect(injector.$http).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
url: telemetryUrl,
|
||||
data: cluster,
|
||||
kbnXsrfToken: false,
|
||||
});
|
||||
});
|
||||
|
||||
expect(injector.localStorage.set).toHaveBeenCalledTimes(1);
|
||||
expect(injector.localStorage.set).toHaveBeenCalledWith(LOCALSTORAGE_KEY, { lastReport });
|
||||
});
|
||||
});
|
||||
|
||||
test('sends telemetry when requested and catches exceptions', () => {
|
||||
const lastReport = Date.now() - REPORT_INTERVAL_MS - 1;
|
||||
const injector = {
|
||||
$http: jest.fn().mockRejectedValue(new Error('TEST - expected')), // caught failure
|
||||
localStorage: {
|
||||
get: jest.fn().mockReturnValueOnce({ lastReport }),
|
||||
set: jest.fn(),
|
||||
},
|
||||
telemetryOptedIn: true,
|
||||
telemetryUrl,
|
||||
};
|
||||
const telemetry = new Telemetry(mockInjectorFromObject(injector), mockFetchTelemetry);
|
||||
|
||||
expect.hasAssertions();
|
||||
|
||||
return telemetry._sendIfDue().then(result => {
|
||||
expect(result).toBe(true); // attempted to send
|
||||
expect(telemetry._sending).toBe(false);
|
||||
|
||||
// should be unchanged
|
||||
expect(telemetry._lastReport).toBe(lastReport);
|
||||
expect(injector.localStorage.set).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(injector.$http).toHaveBeenCalledTimes(2);
|
||||
// assert that it sent every cluster's telemetry
|
||||
clusters.forEach(cluster => {
|
||||
expect(injector.$http).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
url: telemetryUrl,
|
||||
data: cluster,
|
||||
kbnXsrfToken: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('start', () => {
|
||||
const injector = {
|
||||
localStorage: {
|
||||
get: jest.fn().mockReturnValueOnce(undefined),
|
||||
},
|
||||
telemetryOptedIn: false, // opted out
|
||||
};
|
||||
const telemetry = new Telemetry(mockInjectorFromObject(injector), mockFetchTelemetry);
|
||||
|
||||
clearInterval(telemetry.start());
|
||||
});
|
||||
});
|
|
@ -1,53 +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.
|
||||
*/
|
||||
|
||||
import { npStart } from 'ui/new_platform';
|
||||
// @ts-ignore
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { isUnauthenticated } from '../services';
|
||||
// @ts-ignore
|
||||
import { Telemetry } from './telemetry';
|
||||
// @ts-ignore
|
||||
import { fetchTelemetry } from './fetch_telemetry';
|
||||
// @ts-ignore
|
||||
import { isOptInHandleOldSettings } from './welcome_banner/handle_old_settings';
|
||||
import { TelemetryOptInProvider } from '../services';
|
||||
|
||||
function telemetryInit($injector: any) {
|
||||
const $http = $injector.get('$http');
|
||||
const Private = $injector.get('Private');
|
||||
const config = $injector.get('config');
|
||||
const telemetryOptInProvider = Private(TelemetryOptInProvider);
|
||||
|
||||
const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled');
|
||||
const telemetryOptedIn = isOptInHandleOldSettings(config, telemetryOptInProvider);
|
||||
const sendUsageFrom = npStart.core.injectedMetadata.getInjectedVar('telemetrySendUsageFrom');
|
||||
|
||||
if (telemetryEnabled && telemetryOptedIn && sendUsageFrom === 'browser') {
|
||||
// no telemetry for non-logged in users
|
||||
if (isUnauthenticated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sender = new Telemetry($injector, () => fetchTelemetry($http));
|
||||
sender.start();
|
||||
}
|
||||
}
|
||||
|
||||
uiModules.get('telemetry/hacks').run(telemetryInit);
|
|
@ -1,77 +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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { banners, toastNotifications } from 'ui/notify';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
/**
|
||||
* Handle clicks from the user on the opt-in banner.
|
||||
*
|
||||
* @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(
|
||||
telemetryOptInProvider,
|
||||
optIn,
|
||||
{ _banners = banners, _toastNotifications = toastNotifications } = {}
|
||||
) {
|
||||
const bannerId = telemetryOptInProvider.getBannerId();
|
||||
let set = false;
|
||||
|
||||
try {
|
||||
set = await telemetryOptInProvider.setOptIn(optIn);
|
||||
} catch (err) {
|
||||
// set is already false
|
||||
console.log('Unexpected error while trying to save setting.', err);
|
||||
}
|
||||
|
||||
if (set) {
|
||||
_banners.remove(bannerId);
|
||||
} else {
|
||||
_toastNotifications.addDanger({
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="telemetry.telemetryErrorNotificationMessageTitle"
|
||||
defaultMessage="Telemetry Error"
|
||||
/>
|
||||
),
|
||||
text: (
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="telemetry.telemetryErrorNotificationMessageDescription.unableToSaveTelemetryPreferenceText"
|
||||
defaultMessage="Unable to save telemetry preference."
|
||||
/>
|
||||
</p>
|
||||
<EuiText size="xs">
|
||||
<FormattedMessage
|
||||
id="telemetry.telemetryErrorNotificationMessageDescription.tryAgainText"
|
||||
defaultMessage="Check that Kibana and Elasticsearch are still running, then try again."
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiText>
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,128 +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.
|
||||
*/
|
||||
|
||||
import { mockInjectedMetadata } from '../../services/telemetry_opt_in.test.mocks';
|
||||
|
||||
import sinon from 'sinon';
|
||||
import { uiModules } from 'ui/modules';
|
||||
|
||||
uiModules
|
||||
.get('kibana')
|
||||
// disable stat reporting while running tests,
|
||||
// MockInjector used in these tests is not impacted
|
||||
.constant('telemetryOptedIn', null);
|
||||
|
||||
import { clickBanner } from './click_banner';
|
||||
import { TelemetryOptInProvider } from '../../services/telemetry_opt_in';
|
||||
|
||||
const getMockInjector = ({ simulateFailure }) => {
|
||||
const get = sinon.stub();
|
||||
|
||||
const mockHttp = {
|
||||
post: sinon.stub(),
|
||||
};
|
||||
|
||||
if (simulateFailure) {
|
||||
mockHttp.post.returns(Promise.reject(new Error('something happened')));
|
||||
} else {
|
||||
mockHttp.post.returns(Promise.resolve({}));
|
||||
}
|
||||
|
||||
get.withArgs('$http').returns(mockHttp);
|
||||
|
||||
return { get };
|
||||
};
|
||||
|
||||
const getTelemetryOptInProvider = ({ simulateFailure = false, simulateError = false } = {}) => {
|
||||
const injector = getMockInjector({ simulateFailure });
|
||||
const chrome = {
|
||||
addBasePath: url => url,
|
||||
};
|
||||
|
||||
const provider = new TelemetryOptInProvider(injector, chrome, false);
|
||||
|
||||
if (simulateError) {
|
||||
provider.setOptIn = () => Promise.reject('unhandled error');
|
||||
}
|
||||
|
||||
return provider;
|
||||
};
|
||||
|
||||
describe('click_banner', () => {
|
||||
it('sets setting successfully and removes banner', async () => {
|
||||
const banners = {
|
||||
remove: sinon.spy(),
|
||||
};
|
||||
|
||||
const optIn = true;
|
||||
const bannerId = 'bruce-banner';
|
||||
mockInjectedMetadata({ telemetryOptedIn: optIn, allowChangingOptInStatus: true });
|
||||
const telemetryOptInProvider = getTelemetryOptInProvider();
|
||||
|
||||
telemetryOptInProvider.setBannerId(bannerId);
|
||||
|
||||
await clickBanner(telemetryOptInProvider, optIn, { _banners: banners });
|
||||
|
||||
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 () => {
|
||||
const toastNotifications = {
|
||||
addDanger: sinon.spy(),
|
||||
};
|
||||
const banners = {
|
||||
remove: sinon.spy(),
|
||||
};
|
||||
const optIn = true;
|
||||
mockInjectedMetadata({ telemetryOptedIn: null, allowChangingOptInStatus: true });
|
||||
const telemetryOptInProvider = getTelemetryOptInProvider({ simulateFailure: true });
|
||||
|
||||
await clickBanner(telemetryOptInProvider, optIn, {
|
||||
_banners: banners,
|
||||
_toastNotifications: toastNotifications,
|
||||
});
|
||||
|
||||
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 () => {
|
||||
const toastNotifications = {
|
||||
addDanger: sinon.spy(),
|
||||
};
|
||||
const banners = {
|
||||
remove: sinon.spy(),
|
||||
};
|
||||
const optIn = false;
|
||||
mockInjectedMetadata({ telemetryOptedIn: null, allowChangingOptInStatus: true });
|
||||
const telemetryOptInProvider = getTelemetryOptInProvider({ simulateError: true });
|
||||
|
||||
await clickBanner(telemetryOptInProvider, optIn, {
|
||||
_banners: banners,
|
||||
_toastNotifications: toastNotifications,
|
||||
});
|
||||
|
||||
expect(telemetryOptInProvider.getOptIn()).toBe(null);
|
||||
expect(toastNotifications.addDanger.calledOnce).toBe(true);
|
||||
expect(banners.remove.notCalled).toBe(true);
|
||||
});
|
||||
});
|
|
@ -1,85 +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.
|
||||
*/
|
||||
|
||||
import { CONFIG_TELEMETRY } from '../../../common/constants';
|
||||
|
||||
/**
|
||||
* Clean up any old, deprecated settings and determine if we should continue.
|
||||
*
|
||||
* This <em>will</em> update the latest telemetry setting if necessary.
|
||||
*
|
||||
* @param {Object} config The advanced settings config object.
|
||||
* @return {Boolean} {@code true} if the banner should still be displayed. {@code false} if the banner should not be displayed.
|
||||
*/
|
||||
const CONFIG_ALLOW_REPORT = 'xPackMonitoring:allowReport';
|
||||
|
||||
export async function handleOldSettings(config, telemetryOptInProvider) {
|
||||
const CONFIG_SHOW_BANNER = 'xPackMonitoring:showBanner';
|
||||
const oldAllowReportSetting = config.get(CONFIG_ALLOW_REPORT, null);
|
||||
const oldTelemetrySetting = config.get(CONFIG_TELEMETRY, null);
|
||||
|
||||
let legacyOptInValue = null;
|
||||
|
||||
if (typeof oldTelemetrySetting === 'boolean') {
|
||||
legacyOptInValue = oldTelemetrySetting;
|
||||
} else if (typeof oldAllowReportSetting === 'boolean') {
|
||||
legacyOptInValue = oldAllowReportSetting;
|
||||
}
|
||||
|
||||
if (legacyOptInValue !== null) {
|
||||
try {
|
||||
await telemetryOptInProvider.setOptIn(legacyOptInValue);
|
||||
|
||||
// delete old keys once we've successfully changed the setting (if it fails, we just wait until next time)
|
||||
config.remove(CONFIG_ALLOW_REPORT);
|
||||
config.remove(CONFIG_SHOW_BANNER);
|
||||
config.remove(CONFIG_TELEMETRY);
|
||||
} finally {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const oldShowSetting = config.get(CONFIG_SHOW_BANNER, null);
|
||||
|
||||
if (oldShowSetting !== null) {
|
||||
config.remove(CONFIG_SHOW_BANNER);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function isOptInHandleOldSettings(config, telemetryOptInProvider) {
|
||||
const currentOptInSettting = telemetryOptInProvider.getOptIn();
|
||||
|
||||
if (typeof currentOptInSettting === 'boolean') {
|
||||
return currentOptInSettting;
|
||||
}
|
||||
|
||||
const oldTelemetrySetting = config.get(CONFIG_TELEMETRY, null);
|
||||
if (typeof oldTelemetrySetting === 'boolean') {
|
||||
return oldTelemetrySetting;
|
||||
}
|
||||
|
||||
const oldAllowReportSetting = config.get(CONFIG_ALLOW_REPORT, null);
|
||||
if (typeof oldAllowReportSetting === 'boolean') {
|
||||
return oldAllowReportSetting;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
|
@ -1,208 +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.
|
||||
*/
|
||||
|
||||
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';
|
||||
|
||||
const getTelemetryOptInProvider = (enabled, { simulateFailure = false } = {}) => {
|
||||
const $http = {
|
||||
post: async () => {
|
||||
if (simulateFailure) {
|
||||
return Promise.reject(new Error('something happened'));
|
||||
}
|
||||
return {};
|
||||
},
|
||||
};
|
||||
|
||||
const chrome = {
|
||||
addBasePath: url => url,
|
||||
};
|
||||
mockInjectedMetadata({ telemetryOptedIn: enabled, allowChangingOptInStatus: true });
|
||||
|
||||
const $injector = {
|
||||
get: key => {
|
||||
if (key === '$http') {
|
||||
return $http;
|
||||
}
|
||||
throw new Error(`unexpected mock injector usage for ${key}`);
|
||||
},
|
||||
};
|
||||
|
||||
return new TelemetryOptInProvider($injector, chrome, false);
|
||||
};
|
||||
|
||||
describe('handle_old_settings', () => {
|
||||
it('re-uses old "allowReport" setting and stays opted in', async () => {
|
||||
const config = {
|
||||
get: sinon.stub(),
|
||||
remove: sinon.spy(),
|
||||
set: sinon.stub(),
|
||||
};
|
||||
|
||||
const telemetryOptInProvider = getTelemetryOptInProvider(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)).toBe(false);
|
||||
|
||||
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()).toBe(true);
|
||||
});
|
||||
|
||||
it('re-uses old "telemetry:optIn" setting and stays opted in', async () => {
|
||||
const config = {
|
||||
get: sinon.stub(),
|
||||
remove: sinon.spy(),
|
||||
set: sinon.stub(),
|
||||
};
|
||||
|
||||
const telemetryOptInProvider = getTelemetryOptInProvider(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)).toBe(false);
|
||||
|
||||
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()).toBe(true);
|
||||
});
|
||||
|
||||
it('re-uses old "allowReport" setting and stays opted out', async () => {
|
||||
const config = {
|
||||
get: sinon.stub(),
|
||||
remove: sinon.spy(),
|
||||
set: sinon.stub(),
|
||||
};
|
||||
|
||||
const telemetryOptInProvider = getTelemetryOptInProvider(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)).toBe(false);
|
||||
|
||||
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()).toBe(false);
|
||||
});
|
||||
|
||||
it('re-uses old "telemetry:optIn" setting and stays opted out', async () => {
|
||||
const config = {
|
||||
get: sinon.stub(),
|
||||
remove: sinon.spy(),
|
||||
set: sinon.stub(),
|
||||
};
|
||||
|
||||
const telemetryOptInProvider = getTelemetryOptInProvider(null);
|
||||
|
||||
config.get.withArgs(CONFIG_TELEMETRY, null).returns(false);
|
||||
config.get.withArgs('xPackMonitoring:allowReport', null).returns(true);
|
||||
|
||||
expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(false);
|
||||
|
||||
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()).toBe(false);
|
||||
});
|
||||
|
||||
it('acknowledges users old setting even if re-setting fails', async () => {
|
||||
const config = {
|
||||
get: sinon.stub(),
|
||||
set: sinon.stub(),
|
||||
};
|
||||
|
||||
const telemetryOptInProvider = getTelemetryOptInProvider(null, { simulateFailure: true });
|
||||
|
||||
config.get.withArgs('xPackMonitoring:allowReport', null).returns(false);
|
||||
//todo: make the new version of this fail!
|
||||
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)).toBe(false);
|
||||
|
||||
expect(config.get.calledTwice).toBe(true);
|
||||
expect(config.set.called).toBe(false);
|
||||
});
|
||||
|
||||
it('removes show banner setting and presents user with choice', async () => {
|
||||
const config = {
|
||||
get: sinon.stub(),
|
||||
remove: sinon.spy(),
|
||||
};
|
||||
|
||||
const telemetryOptInProvider = getTelemetryOptInProvider(null);
|
||||
|
||||
config.get.withArgs('xPackMonitoring:allowReport', null).returns(null);
|
||||
config.get.withArgs('xPackMonitoring:showBanner', null).returns(false);
|
||||
|
||||
expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(true);
|
||||
|
||||
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 () => {
|
||||
const config = {
|
||||
get: sinon.stub(),
|
||||
};
|
||||
|
||||
const telemetryOptInProvider = getTelemetryOptInProvider(null);
|
||||
|
||||
config.get.withArgs('xPackMonitoring:allowReport', null).returns(null);
|
||||
config.get.withArgs('xPackMonitoring:showBanner', null).returns(null);
|
||||
|
||||
expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(true);
|
||||
|
||||
expect(config.get.calledThrice).toBe(true);
|
||||
});
|
||||
});
|
|
@ -1,76 +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.
|
||||
*/
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
import { fetchTelemetry } from '../fetch_telemetry';
|
||||
import { renderBanner } from './render_banner';
|
||||
import { renderOptedInBanner } from './render_notice_banner';
|
||||
import { shouldShowBanner } from './should_show_banner';
|
||||
import { shouldShowOptInBanner } from './should_show_opt_in_banner';
|
||||
import { TelemetryOptInProvider, isUnauthenticated } from '../../services';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
|
||||
/**
|
||||
* Add the Telemetry opt-in banner if the user has not already made a decision.
|
||||
*
|
||||
* Note: this is an async function, but Angular fails to use it as one. Its usage does not need to be awaited,
|
||||
* and thus it can be wrapped in the run method to just be a normal, non-async function.
|
||||
*
|
||||
* @param {Object} $injector The Angular injector
|
||||
*/
|
||||
async function asyncInjectBanner($injector) {
|
||||
const Private = $injector.get('Private');
|
||||
const telemetryOptInProvider = Private(TelemetryOptInProvider);
|
||||
const config = $injector.get('config');
|
||||
|
||||
// and no banner for non-logged in users
|
||||
if (isUnauthenticated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// and no banner on status page
|
||||
if (chrome.getApp().id === 'status_page') {
|
||||
return;
|
||||
}
|
||||
|
||||
const $http = $injector.get('$http');
|
||||
|
||||
// determine if the banner should be displayed
|
||||
if (await shouldShowBanner(telemetryOptInProvider, config)) {
|
||||
renderBanner(telemetryOptInProvider, () => fetchTelemetry($http, { unencrypted: true }));
|
||||
}
|
||||
|
||||
if (await shouldShowOptInBanner(telemetryOptInProvider, config)) {
|
||||
renderOptedInBanner(telemetryOptInProvider, () => fetchTelemetry($http, { unencrypted: true }));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Telemetry opt-in banner when appropriate.
|
||||
*
|
||||
* @param {Object} $injector The Angular injector
|
||||
*/
|
||||
export function injectBanner($injector) {
|
||||
const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled');
|
||||
const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner');
|
||||
if (telemetryEnabled && telemetryBanner) {
|
||||
asyncInjectBanner($injector);
|
||||
}
|
||||
}
|
|
@ -1,46 +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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { banners } from 'ui/notify';
|
||||
|
||||
import { clickBanner } from './click_banner';
|
||||
import { OptInBanner } from '../../components/opt_in_banner_component';
|
||||
|
||||
/**
|
||||
* Render the Telemetry Opt-in banner.
|
||||
*
|
||||
* @param {Object} telemetryOptInProvider The telemetry opt-in provider.
|
||||
* @param {Function} fetchTelemetry Function to pull telemetry on demand.
|
||||
* @param {Object} _banners Banners singleton, which can be overridden for tests.
|
||||
*/
|
||||
export function renderBanner(telemetryOptInProvider, fetchTelemetry, { _banners = banners } = {}) {
|
||||
const bannerId = _banners.add({
|
||||
component: (
|
||||
<OptInBanner
|
||||
optInClick={optIn => clickBanner(telemetryOptInProvider, optIn)}
|
||||
fetchTelemetry={fetchTelemetry}
|
||||
/>
|
||||
),
|
||||
priority: 10000,
|
||||
});
|
||||
|
||||
telemetryOptInProvider.setBannerId(bannerId);
|
||||
}
|
|
@ -1,38 +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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { banners } from 'ui/notify';
|
||||
import { OptedInBanner } from '../../components/opted_in_notice_banner';
|
||||
|
||||
/**
|
||||
* Render the Telemetry Opt-in notice banner.
|
||||
*
|
||||
* @param {Object} telemetryOptInProvider The telemetry opt-in provider.
|
||||
* @param {Object} _banners Banners singleton, which can be overridden for tests.
|
||||
*/
|
||||
export function renderOptedInBanner(telemetryOptInProvider, { _banners = banners } = {}) {
|
||||
const bannerId = _banners.add({
|
||||
component: <OptedInBanner onSeenBanner={telemetryOptInProvider.setOptInNoticeSeen} />,
|
||||
priority: 10000,
|
||||
});
|
||||
|
||||
telemetryOptInProvider.setOptInBannerNoticeId(bannerId);
|
||||
}
|
|
@ -1,40 +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.
|
||||
*/
|
||||
|
||||
import { handleOldSettings } from './handle_old_settings';
|
||||
|
||||
/**
|
||||
* Determine if the banner should be displayed.
|
||||
*
|
||||
* This method can have side-effects related to deprecated config settings.
|
||||
*
|
||||
* @param {Object} config The advanced settings config object.
|
||||
* @param {Object} _handleOldSettings handleOldSettings function, but overridable for tests.
|
||||
* @return {Boolean} {@code true} if the banner should be displayed. {@code false} otherwise.
|
||||
*/
|
||||
export async function shouldShowBanner(
|
||||
telemetryOptInProvider,
|
||||
config,
|
||||
{ _handleOldSettings = handleOldSettings } = {}
|
||||
) {
|
||||
return (
|
||||
telemetryOptInProvider.getOptIn() === null &&
|
||||
(await _handleOldSettings(config, telemetryOptInProvider))
|
||||
);
|
||||
}
|
|
@ -1,91 +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.
|
||||
*/
|
||||
|
||||
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';
|
||||
|
||||
const getMockInjector = () => {
|
||||
const get = sinon.stub();
|
||||
|
||||
const mockHttp = {
|
||||
post: sinon.stub(),
|
||||
};
|
||||
|
||||
get.withArgs('$http').returns(mockHttp);
|
||||
|
||||
return { get };
|
||||
};
|
||||
|
||||
const getTelemetryOptInProvider = ({ telemetryOptedIn = null } = {}) => {
|
||||
mockInjectedMetadata({ telemetryOptedIn, allowChangingOptInStatus: true });
|
||||
const injector = getMockInjector();
|
||||
const chrome = {
|
||||
addBasePath: url => url,
|
||||
};
|
||||
|
||||
return new TelemetryOptInProvider(injector, chrome);
|
||||
};
|
||||
|
||||
describe('should_show_banner', () => {
|
||||
it('returns whatever handleOldSettings does when telemetry opt-in setting is unset', async () => {
|
||||
const config = { get: sinon.stub() };
|
||||
const telemetryOptInProvider = getTelemetryOptInProvider();
|
||||
const handleOldSettingsTrue = sinon.stub();
|
||||
const handleOldSettingsFalse = sinon.stub();
|
||||
|
||||
config.get.withArgs(CONFIG_TELEMETRY, null).returns(null);
|
||||
handleOldSettingsTrue.returns(Promise.resolve(true));
|
||||
handleOldSettingsFalse.returns(Promise.resolve(false));
|
||||
|
||||
const showBannerTrue = await shouldShowBanner(telemetryOptInProvider, config, {
|
||||
_handleOldSettings: handleOldSettingsTrue,
|
||||
});
|
||||
const showBannerFalse = await shouldShowBanner(telemetryOptInProvider, config, {
|
||||
_handleOldSettings: handleOldSettingsFalse,
|
||||
});
|
||||
|
||||
expect(showBannerTrue).toBe(true);
|
||||
expect(showBannerFalse).toBe(false);
|
||||
|
||||
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({ telemetryOptedIn: true });
|
||||
|
||||
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({ telemetryOptedIn: false });
|
||||
|
||||
expect(await shouldShowBanner(telemetryOptInProvider, config)).toBe(false);
|
||||
});
|
||||
});
|
|
@ -1,148 +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.
|
||||
*/
|
||||
|
||||
import { mockInjectedMetadata } from './telemetry_opt_in.test.mocks';
|
||||
import { TelemetryOptInProvider } from './telemetry_opt_in';
|
||||
|
||||
describe('TelemetryOptInProvider', () => {
|
||||
const setup = ({ optedIn, simulatePostError, simulatePutError }) => {
|
||||
const mockHttp = {
|
||||
post: jest.fn(async () => {
|
||||
if (simulatePostError) {
|
||||
return Promise.reject('Something happened');
|
||||
}
|
||||
}),
|
||||
put: jest.fn(async () => {
|
||||
if (simulatePutError) {
|
||||
return Promise.reject('Something happened');
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
const mockChrome = {
|
||||
addBasePath: url => url,
|
||||
};
|
||||
|
||||
mockInjectedMetadata({
|
||||
telemetryOptedIn: optedIn,
|
||||
allowChangingOptInStatus: true,
|
||||
telemetryNotifyUserAboutOptInDefault: true,
|
||||
});
|
||||
|
||||
const mockInjector = {
|
||||
get: key => {
|
||||
switch (key) {
|
||||
case '$http': {
|
||||
return mockHttp;
|
||||
}
|
||||
default:
|
||||
throw new Error('unexpected injector request: ' + key);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const provider = new TelemetryOptInProvider(mockInjector, mockChrome, false);
|
||||
return {
|
||||
provider,
|
||||
mockHttp,
|
||||
};
|
||||
};
|
||||
|
||||
it('should return the current opt-in status', () => {
|
||||
const { provider: optedInProvider } = setup({ optedIn: true });
|
||||
expect(optedInProvider.getOptIn()).toEqual(true);
|
||||
|
||||
const { provider: optedOutProvider } = setup({ optedIn: false });
|
||||
expect(optedOutProvider.getOptIn()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should allow an opt-out to take place', async () => {
|
||||
const { provider, mockHttp } = setup({ optedIn: true });
|
||||
await provider.setOptIn(false);
|
||||
|
||||
expect(mockHttp.post).toHaveBeenCalledWith(`/api/telemetry/v2/optIn`, { enabled: false });
|
||||
|
||||
expect(provider.getOptIn()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should allow an opt-in to take place', async () => {
|
||||
const { provider, mockHttp } = setup({ optedIn: false });
|
||||
await provider.setOptIn(true);
|
||||
|
||||
expect(mockHttp.post).toHaveBeenCalledWith(`/api/telemetry/v2/optIn`, { enabled: true });
|
||||
|
||||
expect(provider.getOptIn()).toEqual(true);
|
||||
});
|
||||
|
||||
it('should gracefully handle errors', async () => {
|
||||
const { provider, mockHttp } = setup({ optedIn: false, simulatePostError: true });
|
||||
await provider.setOptIn(true);
|
||||
|
||||
expect(mockHttp.post).toHaveBeenCalledWith(`/api/telemetry/v2/optIn`, { enabled: true });
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
describe('Notice Banner', () => {
|
||||
it('should return the current bannerId', () => {
|
||||
const { provider } = setup({});
|
||||
const bannerId = 'bruce-wayne';
|
||||
provider.setOptInBannerNoticeId(bannerId);
|
||||
|
||||
expect(provider.getOptInBannerNoticeId()).toEqual(bannerId);
|
||||
expect(provider.getBannerId()).not.toEqual(bannerId);
|
||||
});
|
||||
|
||||
it('should persist that a user has seen the notice', async () => {
|
||||
const { provider, mockHttp } = setup({});
|
||||
await provider.setOptInNoticeSeen();
|
||||
|
||||
expect(mockHttp.put).toHaveBeenCalledWith(`/api/telemetry/v2/userHasSeenNotice`);
|
||||
|
||||
expect(provider.notifyUserAboutOptInDefault()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should only call the API once', async () => {
|
||||
const { provider, mockHttp } = setup({});
|
||||
await provider.setOptInNoticeSeen();
|
||||
await provider.setOptInNoticeSeen();
|
||||
|
||||
expect(mockHttp.put).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(provider.notifyUserAboutOptInDefault()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should gracefully handle errors', async () => {
|
||||
const { provider } = setup({ simulatePutError: true });
|
||||
|
||||
await provider.setOptInNoticeSeen();
|
||||
|
||||
// opt-in change should not be reflected
|
||||
expect(provider.notifyUserAboutOptInDefault()).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,60 +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.
|
||||
*/
|
||||
|
||||
import {
|
||||
injectedMetadataServiceMock,
|
||||
notificationServiceMock,
|
||||
overlayServiceMock,
|
||||
} from '../../../../../core/public/mocks';
|
||||
const injectedMetadataMock = injectedMetadataServiceMock.createStartContract();
|
||||
|
||||
export function mockInjectedMetadata({
|
||||
telemetryOptedIn,
|
||||
allowChangingOptInStatus,
|
||||
telemetryNotifyUserAboutOptInDefault,
|
||||
}) {
|
||||
const mockGetInjectedVar = jest.fn().mockImplementation(key => {
|
||||
switch (key) {
|
||||
case 'telemetryOptedIn':
|
||||
return telemetryOptedIn;
|
||||
case 'allowChangingOptInStatus':
|
||||
return allowChangingOptInStatus;
|
||||
case 'telemetryNotifyUserAboutOptInDefault':
|
||||
return telemetryNotifyUserAboutOptInDefault;
|
||||
default:
|
||||
throw new Error(`unexpected injectedVar ${key}`);
|
||||
}
|
||||
});
|
||||
|
||||
injectedMetadataMock.getInjectedVar = mockGetInjectedVar;
|
||||
}
|
||||
|
||||
jest.doMock('ui/new_platform', () => ({
|
||||
npSetup: {
|
||||
core: {
|
||||
notifications: notificationServiceMock.createSetupContract(),
|
||||
},
|
||||
},
|
||||
npStart: {
|
||||
core: {
|
||||
injectedMetadata: injectedMetadataMock,
|
||||
overlays: overlayServiceMock.createStartContract(),
|
||||
},
|
||||
},
|
||||
}));
|
|
@ -1,154 +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.
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
// @ts-ignore
|
||||
import { banners, toastNotifications } from 'ui/notify';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
let bannerId: string | null = null;
|
||||
let optInBannerNoticeId: string | null = null;
|
||||
let currentOptInStatus = false;
|
||||
let telemetryNotifyUserAboutOptInDefault = true;
|
||||
|
||||
async function sendOptInStatus($injector: any, chrome: any, enabled: boolean) {
|
||||
const telemetryOptInStatusUrl = npStart.core.injectedMetadata.getInjectedVar(
|
||||
'telemetryOptInStatusUrl'
|
||||
) as string;
|
||||
const $http = $injector.get('$http');
|
||||
|
||||
try {
|
||||
const optInStatus = await $http.post(
|
||||
chrome.addBasePath('/api/telemetry/v2/clusters/_opt_in_stats'),
|
||||
{
|
||||
enabled,
|
||||
unencrypted: false,
|
||||
}
|
||||
);
|
||||
|
||||
if (optInStatus.data && optInStatus.data.length) {
|
||||
return await fetch(telemetryOptInStatusUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(optInStatus.data),
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
// Sending the ping is best-effort. Telemetry tries to send the ping once and discards it immediately if sending fails.
|
||||
// swallow any errors
|
||||
}
|
||||
}
|
||||
export function TelemetryOptInProvider($injector: any, chrome: any, sendOptInStatusChange = true) {
|
||||
currentOptInStatus = npStart.core.injectedMetadata.getInjectedVar('telemetryOptedIn') as boolean;
|
||||
|
||||
const allowChangingOptInStatus = npStart.core.injectedMetadata.getInjectedVar(
|
||||
'allowChangingOptInStatus'
|
||||
) as boolean;
|
||||
|
||||
telemetryNotifyUserAboutOptInDefault = npStart.core.injectedMetadata.getInjectedVar(
|
||||
'telemetryNotifyUserAboutOptInDefault'
|
||||
) as boolean;
|
||||
|
||||
const provider = {
|
||||
getBannerId: () => bannerId,
|
||||
getOptInBannerNoticeId: () => optInBannerNoticeId,
|
||||
getOptIn: () => currentOptInStatus,
|
||||
canChangeOptInStatus: () => allowChangingOptInStatus,
|
||||
notifyUserAboutOptInDefault: () => telemetryNotifyUserAboutOptInDefault,
|
||||
setBannerId(id: string) {
|
||||
bannerId = id;
|
||||
},
|
||||
setOptInBannerNoticeId(id: string) {
|
||||
optInBannerNoticeId = id;
|
||||
},
|
||||
setOptInNoticeSeen: async () => {
|
||||
const $http = $injector.get('$http');
|
||||
|
||||
// If they've seen the notice don't spam the API
|
||||
if (!telemetryNotifyUserAboutOptInDefault) {
|
||||
return telemetryNotifyUserAboutOptInDefault;
|
||||
}
|
||||
|
||||
if (optInBannerNoticeId) {
|
||||
banners.remove(optInBannerNoticeId);
|
||||
}
|
||||
|
||||
try {
|
||||
await $http.put(chrome.addBasePath('/api/telemetry/v2/userHasSeenNotice'));
|
||||
telemetryNotifyUserAboutOptInDefault = false;
|
||||
} catch (error) {
|
||||
toastNotifications.addError(error, {
|
||||
title: i18n.translate('telemetry.optInNoticeSeenErrorTitle', {
|
||||
defaultMessage: 'Error',
|
||||
}),
|
||||
toastMessage: i18n.translate('telemetry.optInNoticeSeenErrorToastText', {
|
||||
defaultMessage: 'An error occurred dismissing the notice',
|
||||
}),
|
||||
});
|
||||
telemetryNotifyUserAboutOptInDefault = true;
|
||||
}
|
||||
|
||||
return telemetryNotifyUserAboutOptInDefault;
|
||||
},
|
||||
setOptIn: async (enabled: boolean) => {
|
||||
if (!allowChangingOptInStatus) {
|
||||
return;
|
||||
}
|
||||
const $http = $injector.get('$http');
|
||||
|
||||
try {
|
||||
await $http.post(chrome.addBasePath('/api/telemetry/v2/optIn'), { enabled });
|
||||
if (sendOptInStatusChange) {
|
||||
await sendOptInStatus($injector, chrome, enabled);
|
||||
}
|
||||
currentOptInStatus = enabled;
|
||||
} catch (error) {
|
||||
toastNotifications.addError(error, {
|
||||
title: i18n.translate('telemetry.optInErrorToastTitle', {
|
||||
defaultMessage: 'Error',
|
||||
}),
|
||||
toastMessage: i18n.translate('telemetry.optInErrorToastText', {
|
||||
defaultMessage:
|
||||
'An error occurred while trying to set the usage statistics preference.',
|
||||
}),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
fetchExample: async () => {
|
||||
const $http = $injector.get('$http');
|
||||
return $http.post(chrome.addBasePath(`/api/telemetry/v2/clusters/_stats`), {
|
||||
unencrypted: true,
|
||||
timeRange: {
|
||||
min: moment()
|
||||
.subtract(20, 'minutes')
|
||||
.toISOString(),
|
||||
max: moment().toISOString(),
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
return provider;
|
||||
}
|
|
@ -18,30 +18,32 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import routes from 'ui/routes';
|
||||
|
||||
import { npSetup } from 'ui/new_platform';
|
||||
import { TelemetryOptInProvider } from '../../services';
|
||||
import { TelemetryForm } from '../../components';
|
||||
import { npStart, npSetup } from 'ui/new_platform';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { TelemetryManagementSection } from '../../../../../../plugins/telemetry/public/components';
|
||||
|
||||
routes.defaults(/\/management/, {
|
||||
resolve: {
|
||||
telemetryManagementSection: function(Private) {
|
||||
const telemetryOptInProvider = Private(TelemetryOptInProvider);
|
||||
const componentRegistry = npSetup.plugins.advancedSettings.component;
|
||||
telemetryManagementSection() {
|
||||
const { telemetry } = npStart.plugins as any;
|
||||
const { advancedSettings } = npSetup.plugins as any;
|
||||
|
||||
const Component = props => (
|
||||
<TelemetryForm
|
||||
showAppliesSettingMessage={true}
|
||||
telemetryOptInProvider={telemetryOptInProvider}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
if (telemetry && advancedSettings) {
|
||||
const componentRegistry = advancedSettings.component;
|
||||
const Component = (props: any) => (
|
||||
<TelemetryManagementSection
|
||||
showAppliesSettingMessage={true}
|
||||
telemetryService={telemetry.telemetryService}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
componentRegistry.register(
|
||||
componentRegistry.componentType.PAGE_FOOTER_COMPONENT,
|
||||
Component,
|
||||
true
|
||||
);
|
||||
componentRegistry.register(
|
||||
componentRegistry.componentType.PAGE_FOOTER_COMPONENT,
|
||||
Component,
|
||||
true
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
|
@ -24,7 +24,6 @@ import { dirname, join } from 'path';
|
|||
|
||||
// look for telemetry.yml in the same places we expect kibana.yml
|
||||
import { ensureDeepObject } from './ensure_deep_object';
|
||||
import { getXpackConfigWithDeprecated } from '../../../common/get_xpack_config_with_deprecated';
|
||||
import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server';
|
||||
|
||||
/**
|
||||
|
@ -85,7 +84,7 @@ export function createTelemetryUsageCollector(
|
|||
isReady: () => true,
|
||||
fetch: async () => {
|
||||
const config = server.config();
|
||||
const configPath = getXpackConfigWithDeprecated(config, 'telemetry.config') as string;
|
||||
const configPath = config.get('telemetry.config') as string;
|
||||
const telemetryPath = join(dirname(configPath), 'telemetry.yml');
|
||||
return await readTelemetryFile(telemetryPath);
|
||||
},
|
||||
|
|
|
@ -24,7 +24,6 @@ import { telemetryCollectionManager } from './collection_manager';
|
|||
import { getTelemetryOptIn, getTelemetrySendUsageFrom } from './telemetry_config';
|
||||
import { getTelemetrySavedObject, updateTelemetrySavedObject } from './telemetry_repository';
|
||||
import { REPORT_INTERVAL_MS } from '../common/constants';
|
||||
import { getXpackConfigWithDeprecated } from '../common/get_xpack_config_with_deprecated';
|
||||
|
||||
export class FetcherTask {
|
||||
private readonly checkDurationMs = 60 * 1000 * 5;
|
||||
|
@ -52,7 +51,7 @@ export class FetcherTask {
|
|||
const configTelemetrySendUsageFrom = config.get('telemetry.sendUsageFrom');
|
||||
const allowChangingOptInStatus = config.get('telemetry.allowChangingOptInStatus');
|
||||
const configTelemetryOptIn = config.get('telemetry.optIn');
|
||||
const telemetryUrl = getXpackConfigWithDeprecated(config, 'telemetry.url') as string;
|
||||
const telemetryUrl = config.get('telemetry.url') as string;
|
||||
|
||||
return {
|
||||
telemetryOptIn: getTelemetryOptIn({
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Clean up any old, deprecated settings and determine if we should continue.
|
||||
*
|
||||
* This <em>will</em> update the latest telemetry setting if necessary.
|
||||
*
|
||||
* @param {Object} config The advanced settings config object.
|
||||
* @return {Boolean} {@code true} if the banner should still be displayed. {@code false} if the banner should not be displayed.
|
||||
*/
|
||||
|
||||
import { Server } from 'hapi';
|
||||
import { CONFIG_TELEMETRY } from '../../common/constants';
|
||||
import { updateTelemetrySavedObject } from '../telemetry_repository';
|
||||
|
||||
const CONFIG_ALLOW_REPORT = 'xPackMonitoring:allowReport';
|
||||
|
||||
export async function handleOldSettings(server: Server) {
|
||||
const { getSavedObjectsRepository } = server.savedObjects;
|
||||
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
|
||||
const savedObjectsClient = getSavedObjectsRepository(callWithInternalUser);
|
||||
const uiSettings = server.uiSettingsServiceFactory({ savedObjectsClient });
|
||||
|
||||
const oldTelemetrySetting = await uiSettings.get(CONFIG_TELEMETRY);
|
||||
const oldAllowReportSetting = await uiSettings.get(CONFIG_ALLOW_REPORT);
|
||||
let legacyOptInValue = null;
|
||||
|
||||
if (typeof oldTelemetrySetting === 'boolean') {
|
||||
legacyOptInValue = oldTelemetrySetting;
|
||||
} else if (
|
||||
typeof oldAllowReportSetting === 'boolean' &&
|
||||
uiSettings.isOverridden(CONFIG_ALLOW_REPORT)
|
||||
) {
|
||||
legacyOptInValue = oldAllowReportSetting;
|
||||
}
|
||||
|
||||
if (legacyOptInValue !== null) {
|
||||
await updateTelemetrySavedObject(savedObjectsClient, {
|
||||
enabled: legacyOptInValue,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -17,4 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { injectBanner } from './inject_banner';
|
||||
export { handleOldSettings } from './handle_old_settings';
|
|
@ -23,6 +23,7 @@ import * as constants from '../common/constants';
|
|||
|
||||
export { FetcherTask } from './fetcher';
|
||||
export { replaceTelemetryInjectedVars } from './telemetry_config';
|
||||
export { handleOldSettings } from './handle_old_settings';
|
||||
export { telemetryCollectionManager } from './collection_manager';
|
||||
export { PluginsSetup } from './plugin';
|
||||
export const telemetryPlugin = (initializerContext: PluginInitializerContext) =>
|
||||
|
|
|
@ -39,6 +39,7 @@ import {
|
|||
import { ManagementSetup, ManagementStart } from '../../../../plugins/management/public';
|
||||
import { BfetchPublicSetup, BfetchPublicStart } from '../../../../plugins/bfetch/public';
|
||||
import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public';
|
||||
import { TelemetryPluginSetup, TelemetryPluginStart } from '../../../../plugins/telemetry/public';
|
||||
import {
|
||||
NavigationPublicPluginSetup,
|
||||
NavigationPublicPluginStart,
|
||||
|
@ -60,6 +61,7 @@ export interface PluginsSetup {
|
|||
usageCollection: UsageCollectionSetup;
|
||||
advancedSettings: AdvancedSettingsSetup;
|
||||
management: ManagementSetup;
|
||||
telemetry?: TelemetryPluginSetup;
|
||||
}
|
||||
|
||||
export interface PluginsStart {
|
||||
|
@ -77,6 +79,7 @@ export interface PluginsStart {
|
|||
share: SharePluginStart;
|
||||
management: ManagementStart;
|
||||
advancedSettings: AdvancedSettingsStart;
|
||||
telemetry?: TelemetryPluginStart;
|
||||
}
|
||||
|
||||
export const npSetup = {
|
||||
|
|
|
@ -97,6 +97,17 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA
|
|||
"management": Object {},
|
||||
"navLinks": Object {},
|
||||
},
|
||||
"currentAppId$": Observable {
|
||||
"_isScalar": false,
|
||||
"source": Subject {
|
||||
"_isScalar": false,
|
||||
"closed": false,
|
||||
"hasError": false,
|
||||
"isStopped": false,
|
||||
"observers": Array [],
|
||||
"thrownError": null,
|
||||
},
|
||||
},
|
||||
"getUrlForApp": [MockFunction],
|
||||
"navigateToApp": [MockFunction],
|
||||
"registerMountContext": [MockFunction],
|
||||
|
@ -738,6 +749,17 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA
|
|||
"management": Object {},
|
||||
"navLinks": Object {},
|
||||
},
|
||||
"currentAppId$": Observable {
|
||||
"_isScalar": false,
|
||||
"source": Subject {
|
||||
"_isScalar": false,
|
||||
"closed": false,
|
||||
"hasError": false,
|
||||
"isStopped": false,
|
||||
"observers": Array [],
|
||||
"thrownError": null,
|
||||
},
|
||||
},
|
||||
"getUrlForApp": [MockFunction],
|
||||
"navigateToApp": [MockFunction],
|
||||
"registerMountContext": [MockFunction],
|
||||
|
@ -1361,6 +1383,17 @@ exports[`QueryStringInput Should pass the query language to the language switche
|
|||
"management": Object {},
|
||||
"navLinks": Object {},
|
||||
},
|
||||
"currentAppId$": Observable {
|
||||
"_isScalar": false,
|
||||
"source": Subject {
|
||||
"_isScalar": false,
|
||||
"closed": false,
|
||||
"hasError": false,
|
||||
"isStopped": false,
|
||||
"observers": Array [],
|
||||
"thrownError": null,
|
||||
},
|
||||
},
|
||||
"getUrlForApp": [MockFunction],
|
||||
"navigateToApp": [MockFunction],
|
||||
"registerMountContext": [MockFunction],
|
||||
|
@ -1999,6 +2032,17 @@ exports[`QueryStringInput Should pass the query language to the language switche
|
|||
"management": Object {},
|
||||
"navLinks": Object {},
|
||||
},
|
||||
"currentAppId$": Observable {
|
||||
"_isScalar": false,
|
||||
"source": Subject {
|
||||
"_isScalar": false,
|
||||
"closed": false,
|
||||
"hasError": false,
|
||||
"isStopped": false,
|
||||
"observers": Array [],
|
||||
"thrownError": null,
|
||||
},
|
||||
},
|
||||
"getUrlForApp": [MockFunction],
|
||||
"navigateToApp": [MockFunction],
|
||||
"registerMountContext": [MockFunction],
|
||||
|
@ -2622,6 +2666,17 @@ exports[`QueryStringInput Should render the given query 1`] = `
|
|||
"management": Object {},
|
||||
"navLinks": Object {},
|
||||
},
|
||||
"currentAppId$": Observable {
|
||||
"_isScalar": false,
|
||||
"source": Subject {
|
||||
"_isScalar": false,
|
||||
"closed": false,
|
||||
"hasError": false,
|
||||
"isStopped": false,
|
||||
"observers": Array [],
|
||||
"thrownError": null,
|
||||
},
|
||||
},
|
||||
"getUrlForApp": [MockFunction],
|
||||
"navigateToApp": [MockFunction],
|
||||
"registerMountContext": [MockFunction],
|
||||
|
@ -3260,6 +3315,17 @@ exports[`QueryStringInput Should render the given query 1`] = `
|
|||
"management": Object {},
|
||||
"navLinks": Object {},
|
||||
},
|
||||
"currentAppId$": Observable {
|
||||
"_isScalar": false,
|
||||
"source": Subject {
|
||||
"_isScalar": false,
|
||||
"closed": false,
|
||||
"hasError": false,
|
||||
"isStopped": false,
|
||||
"observers": Array [],
|
||||
"thrownError": null,
|
||||
},
|
||||
},
|
||||
"getUrlForApp": [MockFunction],
|
||||
"navigateToApp": [MockFunction],
|
||||
"registerMountContext": [MockFunction],
|
||||
|
|
|
@ -18,13 +18,22 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* Determine if the notice banner should be displayed.
|
||||
*
|
||||
* This method can have side-effects related to deprecated config settings.
|
||||
*
|
||||
* @param {Object} telemetryOptInProvider The Telemetry opt-in provider singleton.
|
||||
* @return {Boolean} {@code true} if the banner should be displayed. {@code false} otherwise.
|
||||
* The amount of time, in milliseconds, to wait between reports when enabled.
|
||||
* Currently 24 hours.
|
||||
*/
|
||||
export async function shouldShowOptInBanner(telemetryOptInProvider) {
|
||||
return telemetryOptInProvider.notifyUserAboutOptInDefault();
|
||||
}
|
||||
export const REPORT_INTERVAL_MS = 86400000;
|
||||
|
||||
/*
|
||||
* Key for the localStorage service
|
||||
*/
|
||||
export const LOCALSTORAGE_KEY = 'telemetry.data';
|
||||
|
||||
/**
|
||||
* Link to Advanced Settings.
|
||||
*/
|
||||
export const PATH_TO_ADVANCED_SETTINGS = 'kibana#/management/kibana/settings';
|
||||
|
||||
/**
|
||||
* Link to the Elastic Telemetry privacy statement.
|
||||
*/
|
||||
export const PRIVACY_STATEMENT_URL = `https://www.elastic.co/legal/privacy-statement`;
|
6
src/plugins/telemetry/kibana.json
Normal file
6
src/plugins/telemetry/kibana.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"id": "telemetry",
|
||||
"version": "kibana",
|
||||
"server": false,
|
||||
"ui": true
|
||||
}
|
54
src/plugins/telemetry/public/components/__snapshots__/opt_in_banner.test.tsx.snap
generated
Normal file
54
src/plugins/telemetry/public/components/__snapshots__/opt_in_banner.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,54 @@
|
|||
// 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 />
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="s"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
data-test-subj="enable"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Enable"
|
||||
id="telemetry.welcomeBanner.enableButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
data-test-subj="disable"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Disable"
|
||||
id="telemetry.welcomeBanner.disableButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiCallOut>
|
||||
`;
|
|
@ -9,6 +9,7 @@ exports[`OptInMessage renders as expected 1`] = `
|
|||
Object {
|
||||
"privacyStatementLink": <ForwardRef
|
||||
href="https://www.elastic.co/legal/privacy-statement"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
|
@ -17,8 +17,6 @@
|
|||
* under the 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';
|
||||
export { OptInExampleFlyout } from './opt_in_example_flyout';
|
||||
export { TelemetryManagementSection } from './telemetry_management_section';
|
||||
export { OptedInNoticeBanner } from './opted_in_notice_banner';
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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 { EuiButton } from '@elastic/eui';
|
||||
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { OptInBanner } from './opt_in_banner';
|
||||
|
||||
describe('OptInDetailsComponent', () => {
|
||||
it('renders as expected', () => {
|
||||
expect(shallowWithIntl(<OptInBanner onChangeOptInClick={() => {}} />)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('fires the "onChangeOptInClick" prop with true when a enable is clicked', () => {
|
||||
const onClick = jest.fn();
|
||||
const component = shallowWithIntl(<OptInBanner onChangeOptInClick={onClick} />);
|
||||
|
||||
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} />);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
|
@ -23,15 +23,12 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
import { OptInMessage } from './opt_in_message';
|
||||
|
||||
interface Props {
|
||||
fetchTelemetry: () => Promise<any[]>;
|
||||
optInClick: (optIn: boolean) => void;
|
||||
onChangeOptInClick: (isOptIn: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* React component for displaying the Telemetry opt-in banner.
|
||||
*/
|
||||
export class OptInBanner extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const { onChangeOptInClick } = this.props;
|
||||
const title = (
|
||||
<FormattedMessage
|
||||
id="telemetry.welcomeBanner.title"
|
||||
|
@ -40,11 +37,11 @@ export class OptInBanner extends React.PureComponent<Props> {
|
|||
);
|
||||
return (
|
||||
<EuiCallOut iconType="questionInCircle" title={title}>
|
||||
<OptInMessage fetchTelemetry={this.props.fetchTelemetry} />
|
||||
<OptInMessage />
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton size="s" onClick={() => this.props.optInClick(true)}>
|
||||
<EuiButton size="s" data-test-subj="enable" onClick={() => onChangeOptInClick(true)}>
|
||||
<FormattedMessage
|
||||
id="telemetry.welcomeBanner.enableButtonLabel"
|
||||
defaultMessage="Enable"
|
||||
|
@ -52,7 +49,7 @@ export class OptInBanner extends React.PureComponent<Props> {
|
|||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton size="s" onClick={() => this.props.optInClick(false)}>
|
||||
<EuiButton size="s" data-test-subj="disable" onClick={() => onChangeOptInClick(false)}>
|
||||
<FormattedMessage
|
||||
id="telemetry.welcomeBanner.disableButtonLabel"
|
||||
defaultMessage="Disable"
|
|
@ -18,16 +18,13 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { OptInExampleFlyout } from './opt_in_details_component';
|
||||
import { OptInExampleFlyout } from './opt_in_example_flyout';
|
||||
|
||||
describe('OptInDetailsComponent', () => {
|
||||
it('renders as expected', () => {
|
||||
expect(
|
||||
shallowWithIntl(
|
||||
<OptInExampleFlyout
|
||||
fetchTelemetry={jest.fn(async () => ({ data: [] }))}
|
||||
onClose={jest.fn()}
|
||||
/>
|
||||
<OptInExampleFlyout fetchExample={jest.fn(async () => [])} onClose={jest.fn()} />
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
|
@ -37,7 +37,7 @@ import {
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
interface Props {
|
||||
fetchTelemetry: () => Promise<any>;
|
||||
fetchExample: () => Promise<any[]>;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
|
@ -57,22 +57,21 @@ export class OptInExampleFlyout extends React.PureComponent<Props, State> {
|
|||
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,
|
||||
});
|
||||
async componentDidMount() {
|
||||
try {
|
||||
const { fetchExample } = this.props;
|
||||
const clusters = await fetchExample();
|
||||
this.setState({
|
||||
data: Array.isArray(clusters) ? clusters : null,
|
||||
isLoading: false,
|
||||
hasPrivilegeToRead: true,
|
||||
});
|
||||
} catch (err) {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
hasPrivilegeToRead: err.status !== 403,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderBody({ data, isLoading, hasPrivilegeToRead }: State) {
|
|
@ -22,8 +22,6 @@ import { OptInMessage } from './opt_in_message';
|
|||
|
||||
describe('OptInMessage', () => {
|
||||
it('renders as expected', () => {
|
||||
expect(
|
||||
shallowWithIntl(<OptInMessage fetchTelemetry={jest.fn(async () => [])} />)
|
||||
).toMatchSnapshot();
|
||||
expect(shallowWithIntl(<OptInMessage />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -20,30 +20,9 @@
|
|||
import * as React from 'react';
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { PRIVACY_STATEMENT_URL } from '../../common/constants';
|
||||
|
||||
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,
|
||||
}));
|
||||
};
|
||||
|
||||
export class OptInMessage extends React.PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
@ -52,7 +31,7 @@ export class OptInMessage extends React.PureComponent<Props, State> {
|
|||
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={PRIVACY_STATEMENT_URL} target="_blank">
|
||||
<EuiLink href={PRIVACY_STATEMENT_URL} target="_blank" rel="noopener">
|
||||
<FormattedMessage
|
||||
id="telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText"
|
||||
defaultMessage="Privacy Statement"
|
|
@ -19,16 +19,16 @@
|
|||
import React from 'react';
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { OptedInBanner } from './opted_in_notice_banner';
|
||||
import { OptedInNoticeBanner } from './opted_in_notice_banner';
|
||||
|
||||
describe('OptInDetailsComponent', () => {
|
||||
it('renders as expected', () => {
|
||||
expect(shallowWithIntl(<OptedInBanner onSeenBanner={() => {}} />)).toMatchSnapshot();
|
||||
expect(shallowWithIntl(<OptedInNoticeBanner onSeenBanner={() => {}} />)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('fires the "onSeenBanner" prop when a link is clicked', () => {
|
||||
const onLinkClick = jest.fn();
|
||||
const component = shallowWithIntl(<OptedInBanner onSeenBanner={onLinkClick} />);
|
||||
const component = shallowWithIntl(<OptedInNoticeBanner onSeenBanner={onLinkClick} />);
|
||||
|
||||
const button = component.findWhere(n => n.type() === EuiButton);
|
||||
|
|
@ -20,35 +20,32 @@
|
|||
/* eslint @elastic/eui/href-or-on-click:0 */
|
||||
|
||||
import * as React from 'react';
|
||||
import chrome from 'ui/chrome';
|
||||
import { EuiButton, EuiLink, EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { PATH_TO_ADVANCED_SETTINGS } from '../../common/constants';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { PATH_TO_ADVANCED_SETTINGS, PRIVACY_STATEMENT_URL } from '../../common/constants';
|
||||
|
||||
interface Props {
|
||||
onSeenBanner: () => any;
|
||||
}
|
||||
|
||||
/**
|
||||
* React component for displaying the Telemetry opt-in notice.
|
||||
*/
|
||||
export class OptedInBanner extends React.PureComponent<Props> {
|
||||
onLinkClick = () => {
|
||||
this.props.onSeenBanner();
|
||||
return;
|
||||
};
|
||||
|
||||
export class OptedInNoticeBanner extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const { onSeenBanner } = this.props;
|
||||
const bannerTitle = i18n.translate('telemetry.telemetryOptedInNoticeTitle', {
|
||||
defaultMessage: 'Help us improve the Elastic Stack',
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiCallOut title="Help us improve the Elastic Stack">
|
||||
<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={this.onLinkClick}
|
||||
href="https://www.elastic.co/legal/privacy-statement"
|
||||
onClick={onSeenBanner}
|
||||
href={PRIVACY_STATEMENT_URL}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
|
@ -59,10 +56,7 @@ export class OptedInBanner extends React.PureComponent<Props> {
|
|||
</EuiLink>
|
||||
),
|
||||
disableLink: (
|
||||
<EuiLink
|
||||
href={chrome.addBasePath(PATH_TO_ADVANCED_SETTINGS)}
|
||||
onClick={this.onLinkClick}
|
||||
>
|
||||
<EuiLink href={PATH_TO_ADVANCED_SETTINGS} onClick={onSeenBanner}>
|
||||
<FormattedMessage
|
||||
id="telemetry.telemetryOptedInDisableUsage"
|
||||
defaultMessage="disable usage data here"
|
||||
|
@ -72,7 +66,7 @@ export class OptedInBanner extends React.PureComponent<Props> {
|
|||
}}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiButton size="s" onClick={this.props.onSeenBanner}>
|
||||
<EuiButton size="s" onClick={onSeenBanner}>
|
||||
<FormattedMessage
|
||||
id="telemetry.telemetryOptedInDismissMessage"
|
||||
defaultMessage="Dismiss"
|
|
@ -18,7 +18,6 @@
|
|||
*/
|
||||
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiPanel,
|
||||
|
@ -29,30 +28,38 @@ import {
|
|||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { PRIVACY_STATEMENT_URL } from '../../common/constants';
|
||||
import { OptInExampleFlyout } from './opt_in_details_component';
|
||||
import { Field } from '../../../../../plugins/advanced_settings/public';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { PRIVACY_STATEMENT_URL } from '../../common/constants';
|
||||
import { OptInExampleFlyout } from './opt_in_example_flyout';
|
||||
// @ts-ignore
|
||||
import { Field } from '../../../advanced_settings/public';
|
||||
import { TelemetryService } from '../services/telemetry_service';
|
||||
const SEARCH_TERMS = ['telemetry', 'usage', 'data', 'usage data'];
|
||||
|
||||
export class TelemetryForm extends Component {
|
||||
static propTypes = {
|
||||
telemetryOptInProvider: PropTypes.object.isRequired,
|
||||
query: PropTypes.object,
|
||||
onQueryMatchChange: PropTypes.func.isRequired,
|
||||
showAppliesSettingMessage: PropTypes.bool.isRequired,
|
||||
enableSaving: PropTypes.bool.isRequired,
|
||||
};
|
||||
interface Props {
|
||||
telemetryService: TelemetryService;
|
||||
onQueryMatchChange: (searchTermMatches: boolean) => void;
|
||||
showAppliesSettingMessage: boolean;
|
||||
enableSaving: boolean;
|
||||
query?: any;
|
||||
}
|
||||
|
||||
state = {
|
||||
interface State {
|
||||
processing: boolean;
|
||||
showExample: boolean;
|
||||
queryMatches: boolean | null;
|
||||
}
|
||||
|
||||
export class TelemetryManagementSection extends Component<Props, State> {
|
||||
state: State = {
|
||||
processing: false,
|
||||
showExample: false,
|
||||
queryMatches: null,
|
||||
};
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
||||
const { query } = nextProps;
|
||||
|
||||
const searchTerm = (query.text || '').toLowerCase();
|
||||
|
@ -71,11 +78,10 @@ export class TelemetryForm extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { telemetryOptInProvider } = this.props;
|
||||
|
||||
const { telemetryService } = this.props;
|
||||
const { showExample, queryMatches } = this.state;
|
||||
|
||||
if (!telemetryOptInProvider.canChangeOptInStatus()) {
|
||||
if (!telemetryService.getCanChangeOptInStatus()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -87,7 +93,7 @@ export class TelemetryForm extends Component {
|
|||
<Fragment>
|
||||
{showExample && (
|
||||
<OptInExampleFlyout
|
||||
fetchTelemetry={() => telemetryOptInProvider.fetchExample()}
|
||||
fetchExample={telemetryService.fetchExample}
|
||||
onClose={this.toggleExample}
|
||||
/>
|
||||
)}
|
||||
|
@ -106,15 +112,23 @@ export class TelemetryForm extends Component {
|
|||
{this.maybeGetAppliesSettingMessage()}
|
||||
<EuiSpacer size="s" />
|
||||
<Field
|
||||
setting={{
|
||||
type: 'boolean',
|
||||
value: telemetryOptInProvider.getOptIn() || false,
|
||||
description: this.renderDescription(),
|
||||
defVal: true,
|
||||
ariaName: i18n.translate('telemetry.provideUsageStatisticsLabel', {
|
||||
defaultMessage: 'Provide usage statistics',
|
||||
}),
|
||||
}}
|
||||
setting={
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'telemetry:enabled',
|
||||
displayName: i18n.translate('telemetry.provideUsageStatisticsTitle', {
|
||||
defaultMessage: 'Provide usage statistics',
|
||||
}),
|
||||
value: telemetryService.getIsOptedIn(),
|
||||
description: this.renderDescription(),
|
||||
defVal: true,
|
||||
ariaName: i18n.translate('telemetry.provideUsageStatisticsAriaName', {
|
||||
defaultMessage: 'Provide usage statistics',
|
||||
}),
|
||||
} as any
|
||||
}
|
||||
dockLinks={null as any}
|
||||
toasts={null as any}
|
||||
save={this.toggleOptIn}
|
||||
clear={this.toggleOptIn}
|
||||
enableSaving={this.props.enableSaving}
|
||||
|
@ -185,29 +199,21 @@ export class TelemetryForm extends Component {
|
|||
</Fragment>
|
||||
);
|
||||
|
||||
toggleOptIn = async () => {
|
||||
const newOptInValue = !this.props.telemetryOptInProvider.getOptIn();
|
||||
toggleOptIn = async (): Promise<boolean> => {
|
||||
const { telemetryService } = this.props;
|
||||
const newOptInValue = !telemetryService.getIsOptedIn();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.setState(
|
||||
{
|
||||
enabled: newOptInValue,
|
||||
processing: true,
|
||||
},
|
||||
() => {
|
||||
this.props.telemetryOptInProvider.setOptIn(newOptInValue).then(
|
||||
() => {
|
||||
this.setState({ processing: false });
|
||||
resolve();
|
||||
},
|
||||
e => {
|
||||
// something went wrong
|
||||
this.setState({ processing: false });
|
||||
reject(e);
|
||||
}
|
||||
);
|
||||
this.setState({ processing: true }, async () => {
|
||||
try {
|
||||
await telemetryService.setOptIn(newOptInValue);
|
||||
this.setState({ processing: false });
|
||||
resolve(true);
|
||||
} catch (err) {
|
||||
this.setState({ processing: false });
|
||||
reject(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -17,8 +17,9 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { TelemetryPlugin } from './plugin';
|
||||
export { TelemetryPluginStart, TelemetryPluginSetup } from './plugin';
|
||||
|
||||
import { injectBanner } from './welcome_banner';
|
||||
|
||||
uiModules.get('telemetry/hacks').run(injectBanner);
|
||||
export function plugin() {
|
||||
return new TelemetryPlugin();
|
||||
}
|
85
src/plugins/telemetry/public/mocks.ts
Normal file
85
src/plugins/telemetry/public/mocks.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { overlayServiceMock } from '../../../core/public/overlays/overlay_service.mock';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { httpServiceMock } from '../../../core/public/http/http_service.mock';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { notificationServiceMock } from '../../../core/public/notifications/notifications_service.mock';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { injectedMetadataServiceMock } from '../../../core/public/injected_metadata/injected_metadata_service.mock';
|
||||
import { TelemetryService } from './services/telemetry_service';
|
||||
import { TelemetryNotifications } from './services/telemetry_notifications/telemetry_notifications';
|
||||
import { TelemetryPluginStart } from './plugin';
|
||||
|
||||
export function mockTelemetryService({
|
||||
reportOptInStatusChange,
|
||||
}: { reportOptInStatusChange?: boolean } = {}) {
|
||||
const injectedMetadata = injectedMetadataServiceMock.createStartContract();
|
||||
injectedMetadata.getInjectedVar.mockImplementation((key: string) => {
|
||||
switch (key) {
|
||||
case 'telemetryNotifyUserAboutOptInDefault':
|
||||
return true;
|
||||
case 'allowChangingOptInStatus':
|
||||
return true;
|
||||
case 'telemetryOptedIn':
|
||||
return true;
|
||||
default: {
|
||||
throw Error(`Unhandled getInjectedVar key "${key}".`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return new TelemetryService({
|
||||
injectedMetadata,
|
||||
http: httpServiceMock.createStartContract(),
|
||||
notifications: notificationServiceMock.createStartContract(),
|
||||
reportOptInStatusChange,
|
||||
});
|
||||
}
|
||||
|
||||
export function mockTelemetryNotifications({
|
||||
telemetryService,
|
||||
}: {
|
||||
telemetryService: TelemetryService;
|
||||
}) {
|
||||
return new TelemetryNotifications({
|
||||
overlays: overlayServiceMock.createStartContract(),
|
||||
telemetryService,
|
||||
});
|
||||
}
|
||||
|
||||
export type Setup = jest.Mocked<TelemetryPluginStart>;
|
||||
|
||||
export const telemetryPluginMock = {
|
||||
createSetupContract,
|
||||
};
|
||||
|
||||
function createSetupContract(): Setup {
|
||||
const telemetryService = mockTelemetryService();
|
||||
const telemetryNotifications = mockTelemetryNotifications({ telemetryService });
|
||||
|
||||
const setupContract: Setup = {
|
||||
telemetryService,
|
||||
telemetryNotifications,
|
||||
};
|
||||
|
||||
return setupContract;
|
||||
}
|
118
src/plugins/telemetry/public/plugin.ts
Normal file
118
src/plugins/telemetry/public/plugin.ts
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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 { Plugin, CoreStart, CoreSetup, HttpStart } from '../../../core/public';
|
||||
|
||||
import { TelemetrySender, TelemetryService, TelemetryNotifications } from './services';
|
||||
|
||||
export interface TelemetryPluginSetup {
|
||||
telemetryService: TelemetryService;
|
||||
}
|
||||
|
||||
export interface TelemetryPluginStart {
|
||||
telemetryService: TelemetryService;
|
||||
telemetryNotifications: TelemetryNotifications;
|
||||
}
|
||||
|
||||
export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPluginStart> {
|
||||
private telemetrySender?: TelemetrySender;
|
||||
private telemetryNotifications?: TelemetryNotifications;
|
||||
private telemetryService?: TelemetryService;
|
||||
|
||||
public setup({ http, injectedMetadata, notifications }: CoreSetup): TelemetryPluginSetup {
|
||||
this.telemetryService = new TelemetryService({
|
||||
http,
|
||||
injectedMetadata,
|
||||
notifications,
|
||||
});
|
||||
|
||||
this.telemetrySender = new TelemetrySender(this.telemetryService);
|
||||
|
||||
return {
|
||||
telemetryService: this.telemetryService,
|
||||
};
|
||||
}
|
||||
|
||||
public start({ injectedMetadata, http, overlays, application }: CoreStart): TelemetryPluginStart {
|
||||
if (!this.telemetryService) {
|
||||
throw Error('Telemetry plugin failed to initialize properly.');
|
||||
}
|
||||
|
||||
const telemetryBanner = injectedMetadata.getInjectedVar('telemetryBanner') as boolean;
|
||||
const sendUsageFrom = injectedMetadata.getInjectedVar('telemetrySendUsageFrom') as
|
||||
| 'browser'
|
||||
| 'server';
|
||||
|
||||
this.telemetryNotifications = new TelemetryNotifications({
|
||||
overlays,
|
||||
telemetryService: this.telemetryService,
|
||||
});
|
||||
|
||||
application.currentAppId$.subscribe(appId => {
|
||||
const isUnauthenticated = this.getIsUnauthenticated(http);
|
||||
if (isUnauthenticated) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.maybeStartTelemetryPoller({ sendUsageFrom });
|
||||
if (telemetryBanner) {
|
||||
this.maybeShowOptedInNotificationBanner();
|
||||
this.maybeShowOptInBanner();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
telemetryService: this.telemetryService,
|
||||
telemetryNotifications: this.telemetryNotifications,
|
||||
};
|
||||
}
|
||||
|
||||
private getIsUnauthenticated(http: HttpStart) {
|
||||
const { anonymousPaths } = http;
|
||||
return anonymousPaths.isAnonymous(window.location.pathname);
|
||||
}
|
||||
|
||||
private maybeStartTelemetryPoller({ sendUsageFrom }: { sendUsageFrom: string }) {
|
||||
if (!this.telemetrySender) {
|
||||
return;
|
||||
}
|
||||
if (sendUsageFrom === 'browser') {
|
||||
this.telemetrySender.startChecking();
|
||||
}
|
||||
}
|
||||
|
||||
private maybeShowOptedInNotificationBanner() {
|
||||
if (!this.telemetryNotifications) {
|
||||
return;
|
||||
}
|
||||
const shouldShowBanner = this.telemetryNotifications.shouldShowOptedInNoticeBanner();
|
||||
if (shouldShowBanner) {
|
||||
this.telemetryNotifications.renderOptedInNoticeBanner();
|
||||
}
|
||||
}
|
||||
|
||||
private maybeShowOptInBanner() {
|
||||
if (!this.telemetryNotifications) {
|
||||
return;
|
||||
}
|
||||
const shouldShowBanner = this.telemetryNotifications.shouldShowOptInBanner();
|
||||
if (shouldShowBanner) {
|
||||
this.telemetryNotifications.renderOptInBanner();
|
||||
}
|
||||
}
|
||||
}
|
22
src/plugins/telemetry/public/services/index.ts
Normal file
22
src/plugins/telemetry/public/services/index.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { TelemetrySender } from './telemetry_sender';
|
||||
export { TelemetryService } from './telemetry_service';
|
||||
export { TelemetryNotifications } from './telemetry_notifications';
|
|
@ -17,5 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { TelemetryOptInProvider } from './telemetry_opt_in';
|
||||
export { isUnauthenticated } from './path';
|
||||
export { TelemetryNotifications } from './telemetry_notifications';
|
|
@ -17,24 +17,27 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import '../../services/telemetry_opt_in.test.mocks';
|
||||
import { renderOptedInBanner } from './render_notice_banner';
|
||||
import { renderOptInBanner } from './render_opt_in_banner';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { overlayServiceMock } from '../../../../../core/public/overlays/overlay_service.mock';
|
||||
|
||||
describe('render_notice_banner', () => {
|
||||
describe('renderOptInBanner', () => {
|
||||
it('adds a banner to banners with priority of 10000', () => {
|
||||
const bannerID = 'brucer-wayne';
|
||||
const overlays = overlayServiceMock.createStartContract();
|
||||
overlays.banners.add.mockReturnValue(bannerID);
|
||||
|
||||
const telemetryOptInProvider = { setOptInBannerNoticeId: jest.fn() };
|
||||
const banners = { add: jest.fn().mockReturnValue(bannerID) };
|
||||
const returnedBannerId = renderOptInBanner({
|
||||
setOptIn: jest.fn(),
|
||||
overlays,
|
||||
});
|
||||
|
||||
renderOptedInBanner(telemetryOptInProvider, { _banners: banners });
|
||||
expect(overlays.banners.add).toBeCalledTimes(1);
|
||||
|
||||
expect(banners.add).toBeCalledTimes(1);
|
||||
expect(telemetryOptInProvider.setOptInBannerNoticeId).toBeCalledWith(bannerID);
|
||||
expect(returnedBannerId).toBe(bannerID);
|
||||
const bannerConfig = overlays.banners.add.mock.calls[0];
|
||||
|
||||
const bannerConfig = banners.add.mock.calls[0][0];
|
||||
|
||||
expect(bannerConfig.component).not.toBe(undefined);
|
||||
expect(bannerConfig.priority).toBe(10000);
|
||||
expect(bannerConfig[0]).not.toBe(undefined);
|
||||
expect(bannerConfig[1]).toBe(10000);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 { CoreStart } from 'kibana/public';
|
||||
import { OptInBanner } from '../../components/opt_in_banner';
|
||||
import { toMountPoint } from '../../../../kibana_react/public';
|
||||
|
||||
interface RenderBannerConfig {
|
||||
overlays: CoreStart['overlays'];
|
||||
setOptIn: (isOptIn: boolean) => Promise<any>;
|
||||
}
|
||||
|
||||
export function renderOptInBanner({ setOptIn, overlays }: RenderBannerConfig) {
|
||||
const mount = toMountPoint(<OptInBanner onChangeOptInClick={setOptIn} />);
|
||||
const bannerId = overlays.banners.add(mount, 10000);
|
||||
|
||||
return bannerId;
|
||||
}
|
|
@ -17,26 +17,27 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import '../../services/telemetry_opt_in.test.mocks';
|
||||
import { renderBanner } from './render_banner';
|
||||
import { renderOptedInNoticeBanner } from './render_opted_in_notice_banner';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { overlayServiceMock } from '../../../../../core/public/overlays/overlay_service.mock';
|
||||
|
||||
describe('render_banner', () => {
|
||||
describe('renderOptedInNoticeBanner', () => {
|
||||
it('adds a banner to banners with priority of 10000', () => {
|
||||
const bannerID = 'brucer-banner';
|
||||
const bannerID = 'brucer-wayne';
|
||||
const overlays = overlayServiceMock.createStartContract();
|
||||
overlays.banners.add.mockReturnValue(bannerID);
|
||||
|
||||
const telemetryOptInProvider = { setBannerId: jest.fn() };
|
||||
const banners = { add: jest.fn().mockReturnValue(bannerID) };
|
||||
const fetchTelemetry = jest.fn();
|
||||
const returnedBannerId = renderOptedInNoticeBanner({
|
||||
onSeen: jest.fn(),
|
||||
overlays,
|
||||
});
|
||||
|
||||
renderBanner(telemetryOptInProvider, fetchTelemetry, { _banners: banners });
|
||||
expect(overlays.banners.add).toBeCalledTimes(1);
|
||||
|
||||
expect(banners.add).toBeCalledTimes(1);
|
||||
expect(fetchTelemetry).toBeCalledTimes(0);
|
||||
expect(telemetryOptInProvider.setBannerId).toBeCalledWith(bannerID);
|
||||
expect(returnedBannerId).toBe(bannerID);
|
||||
const bannerConfig = overlays.banners.add.mock.calls[0];
|
||||
|
||||
const bannerConfig = banners.add.mock.calls[0][0];
|
||||
|
||||
expect(bannerConfig.component).not.toBe(undefined);
|
||||
expect(bannerConfig.priority).toBe(10000);
|
||||
expect(bannerConfig[0]).not.toBe(undefined);
|
||||
expect(bannerConfig[1]).toBe(10000);
|
||||
});
|
||||
});
|
|
@ -17,9 +17,18 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
import React from 'react';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import { OptedInNoticeBanner } from '../../components/opted_in_notice_banner';
|
||||
import { toMountPoint } from '../../../../kibana_react/public';
|
||||
|
||||
export function isUnauthenticated() {
|
||||
const path = (chrome as any).removeBasePath(window.location.pathname);
|
||||
return path === '/login' || path === '/logout' || path === '/logged_out' || path === '/status';
|
||||
interface RenderBannerConfig {
|
||||
overlays: CoreStart['overlays'];
|
||||
onSeen: () => void;
|
||||
}
|
||||
export function renderOptedInNoticeBanner({ onSeen, overlays }: RenderBannerConfig) {
|
||||
const mount = toMountPoint(<OptedInNoticeBanner onSeenBanner={onSeen} />);
|
||||
const bannerId = overlays.banners.add(mount, 10000);
|
||||
|
||||
return bannerId;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* 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';
|
||||
|
||||
const telemetryService = mockTelemetryService();
|
||||
telemetryService.setUserHasSeenNotice = jest.fn();
|
||||
const telemetryNotifications = mockTelemetryNotifications({ telemetryService });
|
||||
telemetryNotifications['optedInNoticeBannerId'] = bannerId;
|
||||
await telemetryNotifications.setOptedInNoticeSeen();
|
||||
|
||||
expect(telemetryNotifications['overlays'].banners.remove).toBeCalledTimes(1);
|
||||
expect(telemetryNotifications['overlays'].banners.remove).toBeCalledWith(bannerId);
|
||||
expect(telemetryService.setUserHasSeenNotice).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 { CoreStart } from 'kibana/public';
|
||||
import { renderOptedInNoticeBanner } from './render_opted_in_notice_banner';
|
||||
import { renderOptInBanner } from './render_opt_in_banner';
|
||||
import { TelemetryService } from '../telemetry_service';
|
||||
|
||||
interface TelemetryNotificationsConstructor {
|
||||
overlays: CoreStart['overlays'];
|
||||
telemetryService: TelemetryService;
|
||||
}
|
||||
|
||||
export class TelemetryNotifications {
|
||||
private readonly overlays: CoreStart['overlays'];
|
||||
private readonly telemetryService: TelemetryService;
|
||||
private optedInNoticeBannerId?: string;
|
||||
private optInBannerId?: string;
|
||||
|
||||
constructor({ overlays, telemetryService }: TelemetryNotificationsConstructor) {
|
||||
this.telemetryService = telemetryService;
|
||||
this.overlays = overlays;
|
||||
}
|
||||
|
||||
public shouldShowOptedInNoticeBanner = (): boolean => {
|
||||
const userHasSeenOptedInNotice = this.telemetryService.getUserHasSeenOptedInNotice();
|
||||
const bannerOnScreen = typeof this.optedInNoticeBannerId !== 'undefined';
|
||||
return !bannerOnScreen && userHasSeenOptedInNotice;
|
||||
};
|
||||
|
||||
public renderOptedInNoticeBanner = (): void => {
|
||||
const bannerId = renderOptedInNoticeBanner({
|
||||
onSeen: this.setOptedInNoticeSeen,
|
||||
overlays: this.overlays,
|
||||
});
|
||||
|
||||
this.optedInNoticeBannerId = bannerId;
|
||||
};
|
||||
|
||||
public shouldShowOptInBanner = (): boolean => {
|
||||
const isOptedIn = this.telemetryService.getIsOptedIn();
|
||||
const bannerOnScreen = typeof this.optInBannerId !== 'undefined';
|
||||
return !bannerOnScreen && isOptedIn === null;
|
||||
};
|
||||
|
||||
public renderOptInBanner = (): void => {
|
||||
const bannerId = renderOptInBanner({
|
||||
setOptIn: this.onSetOptInClick,
|
||||
overlays: this.overlays,
|
||||
});
|
||||
|
||||
this.optInBannerId = bannerId;
|
||||
};
|
||||
|
||||
private onSetOptInClick = async (isOptIn: boolean) => {
|
||||
if (this.optInBannerId) {
|
||||
this.overlays.banners.remove(this.optInBannerId);
|
||||
this.optInBannerId = undefined;
|
||||
}
|
||||
|
||||
await this.telemetryService.setOptIn(isOptIn);
|
||||
};
|
||||
|
||||
public setOptedInNoticeSeen = async (): Promise<void> => {
|
||||
if (this.optedInNoticeBannerId) {
|
||||
this.overlays.banners.remove(this.optedInNoticeBannerId);
|
||||
this.optedInNoticeBannerId = undefined;
|
||||
}
|
||||
|
||||
await this.telemetryService.setUserHasSeenNotice();
|
||||
};
|
||||
}
|
272
src/plugins/telemetry/public/services/telemetry_sender.test.ts
Normal file
272
src/plugins/telemetry/public/services/telemetry_sender.test.ts
Normal file
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable dot-notation */
|
||||
import { TelemetrySender } from './telemetry_sender';
|
||||
import { mockTelemetryService } from '../mocks';
|
||||
import { REPORT_INTERVAL_MS, LOCALSTORAGE_KEY } from '../../common/constants';
|
||||
|
||||
class LocalStorageMock implements Partial<Storage> {
|
||||
getItem = jest.fn();
|
||||
setItem = jest.fn();
|
||||
}
|
||||
|
||||
describe('TelemetrySender', () => {
|
||||
let originalLocalStorage: Storage;
|
||||
let mockLocalStorage: LocalStorageMock;
|
||||
beforeAll(() => {
|
||||
originalLocalStorage = window.localStorage;
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
beforeEach(() => (window.localStorage = mockLocalStorage = new LocalStorageMock()));
|
||||
// @ts-ignore
|
||||
afterAll(() => (window.localStorage = originalLocalStorage));
|
||||
|
||||
describe('constructor', () => {
|
||||
it('defaults lastReport if unset', () => {
|
||||
const telemetryService = mockTelemetryService();
|
||||
const telemetrySender = new TelemetrySender(telemetryService);
|
||||
expect(telemetrySender['lastReported']).toBeUndefined();
|
||||
expect(mockLocalStorage.getItem).toBeCalledTimes(1);
|
||||
expect(mockLocalStorage.getItem).toHaveBeenCalledWith(LOCALSTORAGE_KEY);
|
||||
});
|
||||
|
||||
it('uses lastReport if set', () => {
|
||||
const lastReport = `${Date.now()}`;
|
||||
mockLocalStorage.getItem.mockReturnValueOnce(JSON.stringify({ lastReport }));
|
||||
const telemetryService = mockTelemetryService();
|
||||
const telemetrySender = new TelemetrySender(telemetryService);
|
||||
expect(telemetrySender['lastReported']).toBe(lastReport);
|
||||
});
|
||||
});
|
||||
|
||||
describe('saveToBrowser', () => {
|
||||
it('uses lastReport', () => {
|
||||
const lastReport = `${Date.now()}`;
|
||||
const telemetryService = mockTelemetryService();
|
||||
const telemetrySender = new TelemetrySender(telemetryService);
|
||||
telemetrySender['lastReported'] = lastReport;
|
||||
telemetrySender['saveToBrowser']();
|
||||
|
||||
expect(mockLocalStorage.setItem).toHaveBeenCalledTimes(1);
|
||||
expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
|
||||
LOCALSTORAGE_KEY,
|
||||
JSON.stringify({ lastReport })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldSendReport', () => {
|
||||
it('returns false whenever optIn is false', () => {
|
||||
const telemetryService = mockTelemetryService();
|
||||
telemetryService.getIsOptedIn = jest.fn().mockReturnValue(false);
|
||||
const telemetrySender = new TelemetrySender(telemetryService);
|
||||
const shouldSendRerpot = telemetrySender['shouldSendReport']();
|
||||
|
||||
expect(telemetryService.getIsOptedIn).toBeCalledTimes(1);
|
||||
expect(shouldSendRerpot).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true if lastReported is undefined', () => {
|
||||
const telemetryService = mockTelemetryService();
|
||||
telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true);
|
||||
const telemetrySender = new TelemetrySender(telemetryService);
|
||||
const shouldSendRerpot = telemetrySender['shouldSendReport']();
|
||||
|
||||
expect(telemetrySender['lastReported']).toBeUndefined();
|
||||
expect(shouldSendRerpot).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true if lastReported passed REPORT_INTERVAL_MS', () => {
|
||||
const lastReported = Date.now() - (REPORT_INTERVAL_MS + 1000);
|
||||
|
||||
const telemetryService = mockTelemetryService();
|
||||
telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true);
|
||||
const telemetrySender = new TelemetrySender(telemetryService);
|
||||
telemetrySender['lastReported'] = `${lastReported}`;
|
||||
const shouldSendRerpot = telemetrySender['shouldSendReport']();
|
||||
expect(shouldSendRerpot).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if lastReported is within REPORT_INTERVAL_MS', () => {
|
||||
const lastReported = Date.now() + 1000;
|
||||
|
||||
const telemetryService = mockTelemetryService();
|
||||
telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true);
|
||||
const telemetrySender = new TelemetrySender(telemetryService);
|
||||
telemetrySender['lastReported'] = `${lastReported}`;
|
||||
const shouldSendRerpot = telemetrySender['shouldSendReport']();
|
||||
expect(shouldSendRerpot).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true if lastReported is malformed', () => {
|
||||
const telemetryService = mockTelemetryService();
|
||||
telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true);
|
||||
const telemetrySender = new TelemetrySender(telemetryService);
|
||||
telemetrySender['lastReported'] = `random_malformed_string`;
|
||||
const shouldSendRerpot = telemetrySender['shouldSendReport']();
|
||||
expect(shouldSendRerpot).toBe(true);
|
||||
});
|
||||
|
||||
describe('sendIfDue', () => {
|
||||
let originalFetch: typeof window['fetch'];
|
||||
let mockFetch: jest.Mock<typeof window['fetch']>;
|
||||
|
||||
beforeAll(() => {
|
||||
originalFetch = window.fetch;
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
beforeEach(() => (window.fetch = mockFetch = jest.fn()));
|
||||
// @ts-ignore
|
||||
afterAll(() => (window.fetch = originalFetch));
|
||||
|
||||
it('does not send if already sending', async () => {
|
||||
const telemetryService = mockTelemetryService();
|
||||
const telemetrySender = new TelemetrySender(telemetryService);
|
||||
telemetrySender['shouldSendReport'] = jest.fn();
|
||||
telemetrySender['isSending'] = true;
|
||||
await telemetrySender['sendIfDue']();
|
||||
|
||||
expect(telemetrySender['shouldSendReport']).toBeCalledTimes(0);
|
||||
expect(mockFetch).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('does not send if shouldSendReport returns false', async () => {
|
||||
const telemetryService = mockTelemetryService();
|
||||
const telemetrySender = new TelemetrySender(telemetryService);
|
||||
telemetrySender['shouldSendReport'] = jest.fn().mockReturnValue(false);
|
||||
telemetrySender['isSending'] = false;
|
||||
await telemetrySender['sendIfDue']();
|
||||
|
||||
expect(telemetrySender['shouldSendReport']).toBeCalledTimes(1);
|
||||
expect(mockFetch).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('sends report if due', async () => {
|
||||
const mockTelemetryUrl = 'telemetry_cluster_url';
|
||||
const mockTelemetryPayload = ['hashed_cluster_usage_data1'];
|
||||
|
||||
const telemetryService = mockTelemetryService();
|
||||
const telemetrySender = new TelemetrySender(telemetryService);
|
||||
telemetryService.getTelemetryUrl = jest.fn().mockReturnValue(mockTelemetryUrl);
|
||||
telemetryService.fetchTelemetry = jest.fn().mockReturnValue(mockTelemetryPayload);
|
||||
telemetrySender['shouldSendReport'] = jest.fn().mockReturnValue(true);
|
||||
telemetrySender['isSending'] = false;
|
||||
await telemetrySender['sendIfDue']();
|
||||
|
||||
expect(telemetryService.fetchTelemetry).toBeCalledTimes(1);
|
||||
expect(mockFetch).toBeCalledTimes(1);
|
||||
expect(mockFetch).toBeCalledWith(mockTelemetryUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: mockTelemetryPayload[0],
|
||||
});
|
||||
});
|
||||
|
||||
it('sends report separately for every cluster', async () => {
|
||||
const mockTelemetryUrl = 'telemetry_cluster_url';
|
||||
const mockTelemetryPayload = ['hashed_cluster_usage_data1', 'hashed_cluster_usage_data2'];
|
||||
|
||||
const telemetryService = mockTelemetryService();
|
||||
const telemetrySender = new TelemetrySender(telemetryService);
|
||||
telemetryService.getTelemetryUrl = jest.fn().mockReturnValue(mockTelemetryUrl);
|
||||
telemetryService.fetchTelemetry = jest.fn().mockReturnValue(mockTelemetryPayload);
|
||||
telemetrySender['shouldSendReport'] = jest.fn().mockReturnValue(true);
|
||||
telemetrySender['isSending'] = false;
|
||||
await telemetrySender['sendIfDue']();
|
||||
|
||||
expect(telemetryService.fetchTelemetry).toBeCalledTimes(1);
|
||||
expect(mockFetch).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it('updates last lastReported and calls saveToBrowser', async () => {
|
||||
const mockTelemetryUrl = 'telemetry_cluster_url';
|
||||
const mockTelemetryPayload = ['hashed_cluster_usage_data1'];
|
||||
|
||||
const telemetryService = mockTelemetryService();
|
||||
const telemetrySender = new TelemetrySender(telemetryService);
|
||||
telemetryService.getTelemetryUrl = jest.fn().mockReturnValue(mockTelemetryUrl);
|
||||
telemetryService.fetchTelemetry = jest.fn().mockReturnValue(mockTelemetryPayload);
|
||||
telemetrySender['shouldSendReport'] = jest.fn().mockReturnValue(true);
|
||||
telemetrySender['saveToBrowser'] = jest.fn();
|
||||
|
||||
await telemetrySender['sendIfDue']();
|
||||
|
||||
expect(mockFetch).toBeCalledTimes(1);
|
||||
expect(telemetrySender['lastReported']).toBeDefined();
|
||||
expect(telemetrySender['saveToBrowser']).toBeCalledTimes(1);
|
||||
expect(telemetrySender['isSending']).toBe(false);
|
||||
});
|
||||
|
||||
it('catches fetchTelemetry errors and sets isSending to false', async () => {
|
||||
const telemetryService = mockTelemetryService();
|
||||
const telemetrySender = new TelemetrySender(telemetryService);
|
||||
telemetryService.getTelemetryUrl = jest.fn();
|
||||
telemetryService.fetchTelemetry = jest.fn().mockImplementation(() => {
|
||||
throw Error('Error fetching usage');
|
||||
});
|
||||
await telemetrySender['sendIfDue']();
|
||||
expect(telemetryService.fetchTelemetry).toBeCalledTimes(1);
|
||||
expect(telemetrySender['lastReported']).toBeUndefined();
|
||||
expect(telemetrySender['isSending']).toBe(false);
|
||||
});
|
||||
|
||||
it('catches fetch errors and sets isSending to false', async () => {
|
||||
const mockTelemetryPayload = ['hashed_cluster_usage_data1', 'hashed_cluster_usage_data2'];
|
||||
const telemetryService = mockTelemetryService();
|
||||
const telemetrySender = new TelemetrySender(telemetryService);
|
||||
telemetryService.getTelemetryUrl = jest.fn();
|
||||
telemetryService.fetchTelemetry = jest.fn().mockReturnValue(mockTelemetryPayload);
|
||||
mockFetch.mockImplementation(() => {
|
||||
throw Error('Error sending usage');
|
||||
});
|
||||
await telemetrySender['sendIfDue']();
|
||||
expect(telemetryService.fetchTelemetry).toBeCalledTimes(1);
|
||||
expect(mockFetch).toBeCalledTimes(2);
|
||||
expect(telemetrySender['lastReported']).toBeUndefined();
|
||||
expect(telemetrySender['isSending']).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('startChecking', () => {
|
||||
let originalSetInterval: typeof window['setInterval'];
|
||||
let mockSetInterval: jest.Mock<typeof window['setInterval']>;
|
||||
|
||||
beforeAll(() => {
|
||||
originalSetInterval = window.setInterval;
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
beforeEach(() => (window.setInterval = mockSetInterval = jest.fn()));
|
||||
// @ts-ignore
|
||||
afterAll(() => (window.setInterval = originalSetInterval));
|
||||
|
||||
it('calls sendIfDue every 60000 ms', () => {
|
||||
const telemetryService = mockTelemetryService();
|
||||
const telemetrySender = new TelemetrySender(telemetryService);
|
||||
telemetrySender.startChecking();
|
||||
expect(mockSetInterval).toBeCalledTimes(1);
|
||||
expect(mockSetInterval).toBeCalledWith(telemetrySender['sendIfDue'], 60000);
|
||||
});
|
||||
});
|
||||
});
|
100
src/plugins/telemetry/public/services/telemetry_sender.ts
Normal file
100
src/plugins/telemetry/public/services/telemetry_sender.ts
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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 { REPORT_INTERVAL_MS, LOCALSTORAGE_KEY } from '../../common/constants';
|
||||
import { TelemetryService } from './telemetry_service';
|
||||
import { Storage } from '../../../kibana_utils/public';
|
||||
|
||||
export class TelemetrySender {
|
||||
private readonly telemetryService: TelemetryService;
|
||||
private isSending: boolean = false;
|
||||
private lastReported?: string;
|
||||
private readonly storage: Storage;
|
||||
private intervalId?: number;
|
||||
|
||||
constructor(telemetryService: TelemetryService) {
|
||||
this.telemetryService = telemetryService;
|
||||
this.storage = new Storage(window.localStorage);
|
||||
|
||||
const attributes = this.storage.get(LOCALSTORAGE_KEY);
|
||||
if (attributes) {
|
||||
this.lastReported = attributes.lastReport;
|
||||
}
|
||||
}
|
||||
|
||||
private saveToBrowser = () => {
|
||||
// we are the only code that manipulates this key, so it's safe to blindly overwrite the whole object
|
||||
this.storage.set(LOCALSTORAGE_KEY, { lastReport: this.lastReported });
|
||||
};
|
||||
|
||||
private shouldSendReport = (): boolean => {
|
||||
// check if opt-in for telemetry is enabled
|
||||
if (this.telemetryService.getIsOptedIn()) {
|
||||
if (!this.lastReported) {
|
||||
return true;
|
||||
}
|
||||
// returns NaN for any malformed or unset (null/undefined) value
|
||||
const lastReported = parseInt(this.lastReported, 10);
|
||||
// If it's been a day since we last sent telemetry
|
||||
if (isNaN(lastReported) || Date.now() - lastReported > REPORT_INTERVAL_MS) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
private sendIfDue = async (): Promise<void> => {
|
||||
if (this.isSending || !this.shouldSendReport()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// mark that we are working so future requests are ignored until we're done
|
||||
this.isSending = true;
|
||||
try {
|
||||
const telemetryUrl = this.telemetryService.getTelemetryUrl();
|
||||
const telemetryData: any | any[] = await this.telemetryService.fetchTelemetry();
|
||||
const clusters: string[] = [].concat(telemetryData);
|
||||
await Promise.all(
|
||||
clusters.map(
|
||||
async cluster =>
|
||||
await fetch(telemetryUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: cluster,
|
||||
})
|
||||
)
|
||||
);
|
||||
this.lastReported = `${Date.now()}`;
|
||||
this.saveToBrowser();
|
||||
} catch (err) {
|
||||
// ignore err
|
||||
} finally {
|
||||
this.isSending = false;
|
||||
}
|
||||
};
|
||||
|
||||
public startChecking = () => {
|
||||
if (typeof this.intervalId === 'undefined') {
|
||||
this.intervalId = window.setInterval(this.sendIfDue, 60000);
|
||||
}
|
||||
};
|
||||
}
|
139
src/plugins/telemetry/public/services/telemetry_service.test.ts
Normal file
139
src/plugins/telemetry/public/services/telemetry_service.test.ts
Normal file
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable dot-notation */
|
||||
import { mockTelemetryService } from '../mocks';
|
||||
|
||||
const mockSubtract = jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
toISOString: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('moment', () => {
|
||||
return jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
subtract: mockSubtract,
|
||||
toISOString: jest.fn(),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
describe('TelemetryService', () => {
|
||||
describe('fetchTelemetry', () => {
|
||||
it('calls expected URL with 20 minutes - now', async () => {
|
||||
const telemetryService = mockTelemetryService();
|
||||
await telemetryService.fetchTelemetry();
|
||||
expect(telemetryService['http'].post).toBeCalledWith('/api/telemetry/v2/clusters/_stats', {
|
||||
body: JSON.stringify({ unencrypted: false, timeRange: {} }),
|
||||
});
|
||||
expect(mockSubtract).toBeCalledWith(20, 'minutes');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchExample', () => {
|
||||
it('calls fetchTelemetry with unencrupted: true', async () => {
|
||||
const telemetryService = mockTelemetryService();
|
||||
telemetryService.fetchTelemetry = jest.fn();
|
||||
await telemetryService.fetchExample();
|
||||
expect(telemetryService.fetchTelemetry).toBeCalledWith({ unencrypted: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('setOptIn', () => {
|
||||
it('calls api if canChangeOptInStatus', async () => {
|
||||
const telemetryService = mockTelemetryService({ reportOptInStatusChange: false });
|
||||
telemetryService.getCanChangeOptInStatus = jest.fn().mockReturnValue(true);
|
||||
await telemetryService.setOptIn(true);
|
||||
|
||||
expect(telemetryService['http'].post).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('sends enabled true if optedIn: true', async () => {
|
||||
const telemetryService = mockTelemetryService({ reportOptInStatusChange: false });
|
||||
telemetryService.getCanChangeOptInStatus = jest.fn().mockReturnValue(true);
|
||||
const optedIn = true;
|
||||
await telemetryService.setOptIn(optedIn);
|
||||
|
||||
expect(telemetryService['http'].post).toBeCalledWith('/api/telemetry/v2/optIn', {
|
||||
body: JSON.stringify({ enabled: optedIn }),
|
||||
});
|
||||
});
|
||||
|
||||
it('sends enabled false if optedIn: false', async () => {
|
||||
const telemetryService = mockTelemetryService({ reportOptInStatusChange: false });
|
||||
telemetryService.getCanChangeOptInStatus = jest.fn().mockReturnValue(true);
|
||||
const optedIn = false;
|
||||
await telemetryService.setOptIn(optedIn);
|
||||
|
||||
expect(telemetryService['http'].post).toBeCalledWith('/api/telemetry/v2/optIn', {
|
||||
body: JSON.stringify({ enabled: optedIn }),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not call reportOptInStatus if reportOptInStatusChange is false', async () => {
|
||||
const telemetryService = mockTelemetryService({ reportOptInStatusChange: false });
|
||||
telemetryService.getCanChangeOptInStatus = jest.fn().mockReturnValue(true);
|
||||
telemetryService['reportOptInStatus'] = jest.fn();
|
||||
await telemetryService.setOptIn(true);
|
||||
|
||||
expect(telemetryService['reportOptInStatus']).toBeCalledTimes(0);
|
||||
expect(telemetryService['http'].post).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls reportOptInStatus if reportOptInStatusChange is true', async () => {
|
||||
const telemetryService = mockTelemetryService({ reportOptInStatusChange: true });
|
||||
telemetryService.getCanChangeOptInStatus = jest.fn().mockReturnValue(true);
|
||||
telemetryService['reportOptInStatus'] = jest.fn();
|
||||
await telemetryService.setOptIn(true);
|
||||
|
||||
expect(telemetryService['reportOptInStatus']).toBeCalledTimes(1);
|
||||
expect(telemetryService['http'].post).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('adds an error toast on api error', async () => {
|
||||
const telemetryService = mockTelemetryService({ reportOptInStatusChange: false });
|
||||
telemetryService.getCanChangeOptInStatus = jest.fn().mockReturnValue(true);
|
||||
telemetryService['reportOptInStatus'] = jest.fn();
|
||||
telemetryService['http'].post = jest.fn().mockImplementation((url: string) => {
|
||||
if (url === '/api/telemetry/v2/optIn') {
|
||||
throw Error('failed to update opt in.');
|
||||
}
|
||||
});
|
||||
|
||||
await telemetryService.setOptIn(true);
|
||||
expect(telemetryService['http'].post).toBeCalledTimes(1);
|
||||
expect(telemetryService['reportOptInStatus']).toBeCalledTimes(0);
|
||||
expect(telemetryService['notifications'].toasts.addError).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('adds an error toast on reportOptInStatus error', async () => {
|
||||
const telemetryService = mockTelemetryService({ reportOptInStatusChange: true });
|
||||
telemetryService.getCanChangeOptInStatus = jest.fn().mockReturnValue(true);
|
||||
telemetryService['reportOptInStatus'] = jest.fn().mockImplementation(() => {
|
||||
throw Error('failed to report OptIn Status.');
|
||||
});
|
||||
|
||||
await telemetryService.setOptIn(true);
|
||||
expect(telemetryService['http'].post).toBeCalledTimes(1);
|
||||
expect(telemetryService['reportOptInStatus']).toBeCalledTimes(1);
|
||||
expect(telemetryService['notifications'].toasts.addError).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
165
src/plugins/telemetry/public/services/telemetry_service.ts
Normal file
165
src/plugins/telemetry/public/services/telemetry_service.ts
Normal file
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
|
||||
interface TelemetryServiceConstructor {
|
||||
http: CoreStart['http'];
|
||||
injectedMetadata: CoreStart['injectedMetadata'];
|
||||
notifications: CoreStart['notifications'];
|
||||
reportOptInStatusChange?: boolean;
|
||||
}
|
||||
|
||||
export class TelemetryService {
|
||||
private readonly http: CoreStart['http'];
|
||||
private readonly injectedMetadata: CoreStart['injectedMetadata'];
|
||||
private readonly reportOptInStatusChange: boolean;
|
||||
private readonly notifications: CoreStart['notifications'];
|
||||
private isOptedIn: boolean | null;
|
||||
private userHasSeenOptedInNotice: boolean;
|
||||
|
||||
constructor({
|
||||
http,
|
||||
injectedMetadata,
|
||||
notifications,
|
||||
reportOptInStatusChange = true,
|
||||
}: TelemetryServiceConstructor) {
|
||||
const isOptedIn = injectedMetadata.getInjectedVar('telemetryOptedIn') as boolean | null;
|
||||
const userHasSeenOptedInNotice = injectedMetadata.getInjectedVar(
|
||||
'telemetryNotifyUserAboutOptInDefault'
|
||||
) as boolean;
|
||||
this.reportOptInStatusChange = reportOptInStatusChange;
|
||||
this.injectedMetadata = injectedMetadata;
|
||||
this.notifications = notifications;
|
||||
this.http = http;
|
||||
|
||||
this.isOptedIn = isOptedIn;
|
||||
this.userHasSeenOptedInNotice = userHasSeenOptedInNotice;
|
||||
}
|
||||
|
||||
public getCanChangeOptInStatus = () => {
|
||||
const allowChangingOptInStatus = this.injectedMetadata.getInjectedVar(
|
||||
'allowChangingOptInStatus'
|
||||
) as boolean;
|
||||
return allowChangingOptInStatus;
|
||||
};
|
||||
|
||||
public getOptInStatusUrl = () => {
|
||||
const telemetryOptInStatusUrl = this.injectedMetadata.getInjectedVar(
|
||||
'telemetryOptInStatusUrl'
|
||||
) as string;
|
||||
return telemetryOptInStatusUrl;
|
||||
};
|
||||
|
||||
public getTelemetryUrl = () => {
|
||||
const telemetryUrl = this.injectedMetadata.getInjectedVar('telemetryUrl') as string;
|
||||
return telemetryUrl;
|
||||
};
|
||||
|
||||
public getUserHasSeenOptedInNotice = () => {
|
||||
return this.userHasSeenOptedInNotice;
|
||||
};
|
||||
|
||||
public getIsOptedIn = () => {
|
||||
return this.isOptedIn;
|
||||
};
|
||||
|
||||
public fetchExample = async () => {
|
||||
return await this.fetchTelemetry({ unencrypted: true });
|
||||
};
|
||||
|
||||
public fetchTelemetry = async ({ unencrypted = false } = {}) => {
|
||||
const now = moment();
|
||||
return this.http.post('/api/telemetry/v2/clusters/_stats', {
|
||||
body: JSON.stringify({
|
||||
unencrypted,
|
||||
timeRange: {
|
||||
min: now.subtract(20, 'minutes').toISOString(),
|
||||
max: now.toISOString(),
|
||||
},
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
public setOptIn = async (optedIn: boolean): Promise<boolean> => {
|
||||
const canChangeOptInStatus = this.getCanChangeOptInStatus();
|
||||
if (!canChangeOptInStatus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.http.post('/api/telemetry/v2/optIn', {
|
||||
body: JSON.stringify({ enabled: optedIn }),
|
||||
});
|
||||
if (this.reportOptInStatusChange) {
|
||||
await this.reportOptInStatus(optedIn);
|
||||
}
|
||||
this.isOptedIn = optedIn;
|
||||
} catch (err) {
|
||||
this.notifications.toasts.addError(err, {
|
||||
title: i18n.translate('telemetry.optInErrorToastTitle', {
|
||||
defaultMessage: 'Error',
|
||||
}),
|
||||
toastMessage: i18n.translate('telemetry.optInErrorToastText', {
|
||||
defaultMessage: 'An error occurred while trying to set the usage statistics preference.',
|
||||
}),
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public setUserHasSeenNotice = async (): Promise<void> => {
|
||||
try {
|
||||
await this.http.put('/api/telemetry/v2/userHasSeenNotice');
|
||||
this.userHasSeenOptedInNotice = true;
|
||||
} catch (error) {
|
||||
this.notifications.toasts.addError(error, {
|
||||
title: i18n.translate('telemetry.optInNoticeSeenErrorTitle', {
|
||||
defaultMessage: 'Error',
|
||||
}),
|
||||
toastMessage: i18n.translate('telemetry.optInNoticeSeenErrorToastText', {
|
||||
defaultMessage: 'An error occurred dismissing the notice',
|
||||
}),
|
||||
});
|
||||
this.userHasSeenOptedInNotice = false;
|
||||
}
|
||||
};
|
||||
|
||||
private reportOptInStatus = async (OptInStatus: boolean): Promise<void> => {
|
||||
const telemetryOptInStatusUrl = this.getOptInStatusUrl();
|
||||
|
||||
try {
|
||||
await fetch(telemetryOptInStatusUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ enabled: OptInStatus }),
|
||||
});
|
||||
} catch (err) {
|
||||
// Sending the ping is best-effort. Telemetry tries to send the ping once and discards it immediately if sending fails.
|
||||
// swallow any errors
|
||||
}
|
||||
};
|
||||
}
|
|
@ -35,7 +35,6 @@ export default async function({ readConfigFile }) {
|
|||
defaults: {
|
||||
'accessibility:disableAnimations': true,
|
||||
'dateFormat:tz': 'UTC',
|
||||
'telemetry:optIn': false,
|
||||
'state:storeInSessionStorage': true,
|
||||
'notifications:lifetime:info': 10000,
|
||||
},
|
||||
|
@ -43,7 +42,7 @@ export default async function({ readConfigFile }) {
|
|||
|
||||
kbnTestServer: {
|
||||
...defaultConfig.get('kbnTestServer'),
|
||||
serverArgs: [...defaultConfig.get('kbnTestServer.serverArgs')],
|
||||
serverArgs: [...defaultConfig.get('kbnTestServer.serverArgs'), '--telemetry.optIn=false'],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -44,14 +44,17 @@ export default async function({ readConfigFile }) {
|
|||
|
||||
kbnTestServer: {
|
||||
...commonConfig.get('kbnTestServer'),
|
||||
serverArgs: [...commonConfig.get('kbnTestServer.serverArgs'), '--oss'],
|
||||
serverArgs: [
|
||||
...commonConfig.get('kbnTestServer.serverArgs'),
|
||||
'--oss',
|
||||
'--telemetry.optIn=false',
|
||||
],
|
||||
},
|
||||
|
||||
uiSettings: {
|
||||
defaults: {
|
||||
'accessibility:disableAnimations': true,
|
||||
'dateFormat:tz': 'UTC',
|
||||
'telemetry:optIn': false,
|
||||
},
|
||||
},
|
||||
|
||||
|
|
1
x-pack/.gitignore
vendored
1
x-pack/.gitignore
vendored
|
@ -4,6 +4,7 @@
|
|||
/test/functional/failure_debug
|
||||
/test/functional/screenshots
|
||||
/test/functional/apps/reporting/reports/session
|
||||
/test/reporting/configs/failure_debug/
|
||||
/legacy/plugins/reporting/.chromium/
|
||||
/legacy/plugins/reporting/.phantom/
|
||||
/.aws-config.json
|
||||
|
|
|
@ -1,576 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TelemetryOptIn should display when telemetry not opted in 1`] = `
|
||||
<TelemetryOptIn
|
||||
intl={
|
||||
Object {
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"formatDate": [Function],
|
||||
"formatHTMLMessage": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {
|
||||
"date": Object {
|
||||
"full": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"weekday": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"long": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"medium": Object {
|
||||
"day": "numeric",
|
||||
"month": "short",
|
||||
"year": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"day": "numeric",
|
||||
"month": "numeric",
|
||||
"year": "2-digit",
|
||||
},
|
||||
},
|
||||
"number": Object {
|
||||
"currency": Object {
|
||||
"style": "currency",
|
||||
},
|
||||
"percent": Object {
|
||||
"style": "percent",
|
||||
},
|
||||
},
|
||||
"relative": Object {
|
||||
"days": Object {
|
||||
"units": "day",
|
||||
},
|
||||
"hours": Object {
|
||||
"units": "hour",
|
||||
},
|
||||
"minutes": Object {
|
||||
"units": "minute",
|
||||
},
|
||||
"months": Object {
|
||||
"units": "month",
|
||||
},
|
||||
"seconds": Object {
|
||||
"units": "second",
|
||||
},
|
||||
"years": Object {
|
||||
"units": "year",
|
||||
},
|
||||
},
|
||||
"time": Object {
|
||||
"full": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"long": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"medium": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
},
|
||||
},
|
||||
},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralFormat": [Function],
|
||||
"getRelativeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"now": [Function],
|
||||
"onError": [Function],
|
||||
"textComponent": Symbol(react.fragment),
|
||||
"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={
|
||||
<ForwardRef
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Read more"
|
||||
id="xpack.licenseMgmt.telemetryOptIn.readMoreLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</ForwardRef>
|
||||
}
|
||||
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": <ForwardRef
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="example"
|
||||
id="xpack.licenseMgmt.telemetryOptIn.exampleLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</ForwardRef>,
|
||||
"telemetryPrivacyStatementLink": <ForwardRef
|
||||
href="https://www.elastic.co/legal/privacy-statement"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="telemetry privacy statement"
|
||||
id="xpack.licenseMgmt.telemetryOptIn.telemetryPrivacyStatementLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</ForwardRef>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</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={
|
||||
<ForwardRef
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Read more"
|
||||
id="xpack.licenseMgmt.telemetryOptIn.readMoreLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</ForwardRef>
|
||||
}
|
||||
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": <ForwardRef
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="example"
|
||||
id="xpack.licenseMgmt.telemetryOptIn.exampleLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</ForwardRef>,
|
||||
"telemetryPrivacyStatementLink": <ForwardRef
|
||||
href="https://www.elastic.co/legal/privacy-statement"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="telemetry privacy statement"
|
||||
id="xpack.licenseMgmt.telemetryOptIn.telemetryPrivacyStatementLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</ForwardRef>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiPopover>,
|
||||
}
|
||||
}
|
||||
>
|
||||
Send basic feature usage statistics to Elastic periodically.
|
||||
<EuiPopover
|
||||
anchorPosition="downCenter"
|
||||
button={
|
||||
<ForwardRef
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Read more"
|
||||
id="xpack.licenseMgmt.telemetryOptIn.readMoreLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</ForwardRef>
|
||||
}
|
||||
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
|
||||
onClick={[Function]}
|
||||
>
|
||||
<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`] = `
|
||||
<TelemetryOptIn
|
||||
intl={
|
||||
Object {
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"formatDate": [Function],
|
||||
"formatHTMLMessage": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {
|
||||
"date": Object {
|
||||
"full": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"weekday": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"long": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"medium": Object {
|
||||
"day": "numeric",
|
||||
"month": "short",
|
||||
"year": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"day": "numeric",
|
||||
"month": "numeric",
|
||||
"year": "2-digit",
|
||||
},
|
||||
},
|
||||
"number": Object {
|
||||
"currency": Object {
|
||||
"style": "currency",
|
||||
},
|
||||
"percent": Object {
|
||||
"style": "percent",
|
||||
},
|
||||
},
|
||||
"relative": Object {
|
||||
"days": Object {
|
||||
"units": "day",
|
||||
},
|
||||
"hours": Object {
|
||||
"units": "hour",
|
||||
},
|
||||
"minutes": Object {
|
||||
"units": "minute",
|
||||
},
|
||||
"months": Object {
|
||||
"units": "month",
|
||||
},
|
||||
"seconds": Object {
|
||||
"units": "second",
|
||||
},
|
||||
"years": Object {
|
||||
"units": "year",
|
||||
},
|
||||
},
|
||||
"time": Object {
|
||||
"full": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"long": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"medium": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
},
|
||||
},
|
||||
},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralFormat": [Function],
|
||||
"getRelativeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"now": [Function],
|
||||
"onError": [Function],
|
||||
"textComponent": Symbol(react.fragment),
|
||||
"timeZone": null,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`TelemetryOptIn shouldn't display when telemetry optIn status can't change 1`] = `
|
||||
<TelemetryOptIn
|
||||
intl={
|
||||
Object {
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"formatDate": [Function],
|
||||
"formatHTMLMessage": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {
|
||||
"date": Object {
|
||||
"full": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"weekday": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"long": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"medium": Object {
|
||||
"day": "numeric",
|
||||
"month": "short",
|
||||
"year": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"day": "numeric",
|
||||
"month": "numeric",
|
||||
"year": "2-digit",
|
||||
},
|
||||
},
|
||||
"number": Object {
|
||||
"currency": Object {
|
||||
"style": "currency",
|
||||
},
|
||||
"percent": Object {
|
||||
"style": "percent",
|
||||
},
|
||||
},
|
||||
"relative": Object {
|
||||
"days": Object {
|
||||
"units": "day",
|
||||
},
|
||||
"hours": Object {
|
||||
"units": "hour",
|
||||
},
|
||||
"minutes": Object {
|
||||
"units": "minute",
|
||||
},
|
||||
"months": Object {
|
||||
"units": "month",
|
||||
},
|
||||
"seconds": Object {
|
||||
"units": "second",
|
||||
},
|
||||
"years": Object {
|
||||
"units": "year",
|
||||
},
|
||||
},
|
||||
"time": Object {
|
||||
"full": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"long": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"medium": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
},
|
||||
},
|
||||
},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralFormat": [Function],
|
||||
"getRelativeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"now": [Function],
|
||||
"onError": [Function],
|
||||
"textComponent": Symbol(react.fragment),
|
||||
"timeZone": null,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
|
@ -965,7 +965,6 @@ exports[`UploadLicense should display a modal when license requires acknowledgem
|
|||
className="euiSpacer euiSpacer--m"
|
||||
/>
|
||||
</EuiSpacer>
|
||||
<TelemetryOptIn />
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
>
|
||||
|
@ -1434,7 +1433,6 @@ exports[`UploadLicense should display an error when ES says license is expired 1
|
|||
className="euiSpacer euiSpacer--m"
|
||||
/>
|
||||
</EuiSpacer>
|
||||
<TelemetryOptIn />
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
>
|
||||
|
@ -1903,7 +1901,6 @@ exports[`UploadLicense should display an error when ES says license is invalid 1
|
|||
className="euiSpacer euiSpacer--m"
|
||||
/>
|
||||
</EuiSpacer>
|
||||
<TelemetryOptIn />
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
>
|
||||
|
@ -2368,7 +2365,6 @@ exports[`UploadLicense should display an error when submitting invalid JSON 1`]
|
|||
className="euiSpacer euiSpacer--m"
|
||||
/>
|
||||
</EuiSpacer>
|
||||
<TelemetryOptIn />
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
>
|
||||
|
@ -2837,7 +2833,6 @@ exports[`UploadLicense should display error when ES returns error 1`] = `
|
|||
className="euiSpacer euiSpacer--m"
|
||||
/>
|
||||
</EuiSpacer>
|
||||
<TelemetryOptIn />
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
>
|
||||
|
|
|
@ -1,43 +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 from 'react';
|
||||
import {
|
||||
setTelemetryEnabled,
|
||||
setTelemetryOptInService,
|
||||
} from '../public/np_ready/application/lib/telemetry';
|
||||
import { TelemetryOptIn } from '../public/np_ready/application/components/telemetry_opt_in';
|
||||
import { mountWithIntl } from '../../../../test_utils/enzyme_helpers';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
setTelemetryEnabled(true);
|
||||
|
||||
describe('TelemetryOptIn', () => {
|
||||
test('should display when telemetry not opted in', () => {
|
||||
setTelemetryOptInService({
|
||||
getOptIn: () => false,
|
||||
canChangeOptInStatus: () => true,
|
||||
});
|
||||
const rendered = mountWithIntl(<TelemetryOptIn />);
|
||||
expect(rendered).toMatchSnapshot();
|
||||
});
|
||||
test('should not display when telemetry is opted in', () => {
|
||||
setTelemetryOptInService({
|
||||
getOptIn: () => true,
|
||||
canChangeOptInStatus: () => true,
|
||||
});
|
||||
const rendered = mountWithIntl(<TelemetryOptIn />);
|
||||
expect(rendered).toMatchSnapshot();
|
||||
});
|
||||
test(`shouldn't display when telemetry optIn status can't change`, () => {
|
||||
setTelemetryOptInService({
|
||||
getOptIn: () => false,
|
||||
canChangeOptInStatus: () => false,
|
||||
});
|
||||
const rendered = mountWithIntl(<TelemetryOptIn />);
|
||||
expect(rendered).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -18,7 +18,7 @@ export class App extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { hasPermission, permissionsLoading, permissionsError } = this.props;
|
||||
const { hasPermission, permissionsLoading, permissionsError, telemetry } = this.props;
|
||||
|
||||
if (permissionsLoading) {
|
||||
return (
|
||||
|
@ -85,11 +85,12 @@ export class App extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
const withTelemetry = Component => props => <Component {...props} telemetry={telemetry} />;
|
||||
return (
|
||||
<EuiPageBody>
|
||||
<Switch>
|
||||
<Route path={`${BASE_PATH}upload_license`} component={UploadLicense} />
|
||||
<Route path={BASE_PATH} component={LicenseDashboard} />
|
||||
<Route path={`${BASE_PATH}upload_license`} component={withTelemetry(UploadLicense)} />
|
||||
<Route path={BASE_PATH} component={withTelemetry(LicenseDashboard)} />
|
||||
</Switch>
|
||||
</EuiPageBody>
|
||||
);
|
||||
|
|
|
@ -11,6 +11,7 @@ import { render, unmountComponentAtNode } from 'react-dom';
|
|||
import * as history from 'history';
|
||||
import { DocLinksStart, HttpSetup, ToastsSetup, ChromeStart } from 'src/core/public';
|
||||
|
||||
import { TelemetryPluginSetup } from 'src/plugins/telemetry/public';
|
||||
// @ts-ignore
|
||||
import { App } from './app.container';
|
||||
// @ts-ignore
|
||||
|
@ -34,10 +35,11 @@ interface AppDependencies {
|
|||
toasts: ToastsSetup;
|
||||
docLinks: DocLinksStart;
|
||||
http: HttpSetup;
|
||||
telemetry?: TelemetryPluginSetup;
|
||||
}
|
||||
|
||||
export const boot = (deps: AppDependencies) => {
|
||||
const { I18nContext, element, legacy, toasts, docLinks, http, chrome } = deps;
|
||||
const { I18nContext, element, legacy, toasts, docLinks, http, chrome, telemetry } = deps;
|
||||
const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks;
|
||||
const esBase = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}`;
|
||||
const securityDocumentationLink = `${esBase}/security-settings.html`;
|
||||
|
@ -56,15 +58,17 @@ export const boot = (deps: AppDependencies) => {
|
|||
toasts,
|
||||
http,
|
||||
chrome,
|
||||
telemetry,
|
||||
MANAGEMENT_BREADCRUMB: legacy.MANAGEMENT_BREADCRUMB,
|
||||
};
|
||||
|
||||
const store = licenseManagementStore(initialState, services);
|
||||
|
||||
render(
|
||||
<I18nContext>
|
||||
<Provider store={store}>
|
||||
<HashRouter>
|
||||
<App />
|
||||
<App telemetry={telemetry} />
|
||||
</HashRouter>
|
||||
</Provider>
|
||||
</I18nContext>,
|
||||
|
|
|
@ -6,26 +6,31 @@
|
|||
|
||||
import React, { Fragment } from 'react';
|
||||
import { EuiLink, EuiCheckbox, EuiSpacer, EuiText, EuiTitle, EuiPopover } from '@elastic/eui';
|
||||
import {
|
||||
shouldShowTelemetryOptIn,
|
||||
getTelemetryFetcher,
|
||||
PRIVACY_STATEMENT_URL,
|
||||
OptInExampleFlyout,
|
||||
} from '../../lib/telemetry';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
OptInExampleFlyout,
|
||||
PRIVACY_STATEMENT_URL,
|
||||
TelemetryPluginSetup,
|
||||
} from '../../lib/telemetry';
|
||||
|
||||
export class TelemetryOptIn extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
showMoreTelemetryInfo: false,
|
||||
isOptingInToTelemetry: false,
|
||||
showExample: false,
|
||||
};
|
||||
}
|
||||
isOptingInToTelemetry = () => {
|
||||
return this.state.isOptingInToTelemetry;
|
||||
interface State {
|
||||
showMoreTelemetryInfo: boolean;
|
||||
showExample: boolean;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onOptInChange: (isOptingInToTelemetry: boolean) => void;
|
||||
isOptingInToTelemetry: boolean;
|
||||
isStartTrial: boolean;
|
||||
telemetry: TelemetryPluginSetup;
|
||||
}
|
||||
|
||||
export class TelemetryOptIn extends React.Component<Props, State> {
|
||||
state: State = {
|
||||
showMoreTelemetryInfo: false,
|
||||
showExample: false,
|
||||
};
|
||||
|
||||
closeReadMorePopover = () => {
|
||||
this.setState({ showMoreTelemetryInfo: false });
|
||||
};
|
||||
|
@ -37,20 +42,22 @@ export class TelemetryOptIn extends React.Component {
|
|||
this.setState({ showExample: true });
|
||||
this.closeReadMorePopover();
|
||||
};
|
||||
onChangeOptIn = event => {
|
||||
onChangeOptIn = (event: any) => {
|
||||
const isOptingInToTelemetry = event.target.checked;
|
||||
this.setState({ isOptingInToTelemetry });
|
||||
const { onOptInChange } = this.props;
|
||||
onOptInChange(isOptingInToTelemetry);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { showMoreTelemetryInfo, isOptingInToTelemetry, showExample } = this.state;
|
||||
const { isStartTrial } = this.props;
|
||||
const { showMoreTelemetryInfo, showExample } = this.state;
|
||||
const { isStartTrial, isOptingInToTelemetry, telemetry } = this.props;
|
||||
|
||||
let example = null;
|
||||
if (showExample) {
|
||||
example = (
|
||||
<OptInExampleFlyout
|
||||
onClose={() => this.setState({ showExample: false })}
|
||||
fetchTelemetry={getTelemetryFetcher}
|
||||
fetchExample={telemetry.telemetryService.fetchExample}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -123,7 +130,7 @@ export class TelemetryOptIn extends React.Component {
|
|||
</EuiPopover>
|
||||
);
|
||||
|
||||
return shouldShowTelemetryOptIn() ? (
|
||||
return (
|
||||
<Fragment>
|
||||
{example}
|
||||
{toCurrentCustomers}
|
||||
|
@ -144,6 +151,6 @@ export class TelemetryOptIn extends React.Component {
|
|||
onChange={this.onChangeOptIn}
|
||||
/>
|
||||
</Fragment>
|
||||
) : null;
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,36 +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 { fetchTelemetry } from '../../../../../../../../src/legacy/core_plugins/telemetry/public/hacks/fetch_telemetry';
|
||||
export { PRIVACY_STATEMENT_URL } from '../../../../../../../../src/legacy/core_plugins/telemetry/common/constants';
|
||||
export { TelemetryOptInProvider } from '../../../../../../../../src/legacy/core_plugins/telemetry/public/services';
|
||||
export { OptInExampleFlyout } from '../../../../../../../../src/legacy/core_plugins/telemetry/public/components';
|
||||
|
||||
let telemetryEnabled;
|
||||
let httpClient;
|
||||
let telemetryOptInService;
|
||||
export const setTelemetryEnabled = isTelemetryEnabled => {
|
||||
telemetryEnabled = isTelemetryEnabled;
|
||||
};
|
||||
export const setHttpClient = anHttpClient => {
|
||||
httpClient = anHttpClient;
|
||||
};
|
||||
export const setTelemetryOptInService = aTelemetryOptInService => {
|
||||
telemetryOptInService = aTelemetryOptInService;
|
||||
};
|
||||
export const optInToTelemetry = async enableTelemetry => {
|
||||
await telemetryOptInService.setOptIn(enableTelemetry);
|
||||
};
|
||||
export const shouldShowTelemetryOptIn = () => {
|
||||
return (
|
||||
telemetryEnabled &&
|
||||
!telemetryOptInService.getOptIn() &&
|
||||
telemetryOptInService.canChangeOptInStatus()
|
||||
);
|
||||
};
|
||||
export const getTelemetryFetcher = () => {
|
||||
return fetchTelemetry(httpClient, { unencrypted: true });
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { TelemetryPluginSetup } from '../../../../../../../../src/plugins/telemetry/public';
|
||||
|
||||
export { OptInExampleFlyout } from '../../../../../../../../src/plugins/telemetry/public/components';
|
||||
export { PRIVACY_STATEMENT_URL } from '../../../../../../../../src/plugins/telemetry/common/constants';
|
||||
export { TelemetryPluginSetup, shouldShowTelemetryOptIn };
|
||||
|
||||
function shouldShowTelemetryOptIn(
|
||||
telemetry?: TelemetryPluginSetup
|
||||
): telemetry is TelemetryPluginSetup {
|
||||
if (telemetry) {
|
||||
const { telemetryService } = telemetry;
|
||||
const isOptedIn = telemetryService.getIsOptedIn();
|
||||
const canChangeOptInStatus = telemetryService.getCanChangeOptInStatus();
|
||||
return canChangeOptInStatus && !isOptedIn;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
|
@ -12,7 +12,7 @@ import { AddLicense } from './add_license';
|
|||
import { RequestTrialExtension } from './request_trial_extension';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
export const LicenseDashboard = ({ setBreadcrumb } = { setBreadcrumb: () => {} }) => {
|
||||
export const LicenseDashboard = ({ setBreadcrumb, telemetry } = { setBreadcrumb: () => {} }) => {
|
||||
useEffect(() => {
|
||||
setBreadcrumb('dashboard');
|
||||
});
|
||||
|
@ -25,7 +25,7 @@ export const LicenseDashboard = ({ setBreadcrumb } = { setBreadcrumb: () => {} }
|
|||
<EuiFlexItem>
|
||||
<AddLicense />
|
||||
</EuiFlexItem>
|
||||
<StartTrial />
|
||||
<StartTrial telemetry={telemetry} />
|
||||
<RequestTrialExtension />
|
||||
<RevertToBasic />
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -4,4 +4,5 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
export { StartTrial } from './start_trial.container';
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
|
@ -22,32 +22,56 @@ import {
|
|||
EuiModalHeaderTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { TelemetryOptIn } from '../../../components/telemetry_opt_in';
|
||||
import { optInToTelemetry } from '../../../lib/telemetry';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { TelemetryOptIn } from '../../../components/telemetry_opt_in';
|
||||
import { EXTERNAL_LINKS } from '../../../../../../common/constants';
|
||||
import { getDocLinks } from '../../../lib/docs_links';
|
||||
import { TelemetryPluginSetup, shouldShowTelemetryOptIn } from '../../../lib/telemetry';
|
||||
|
||||
interface Props {
|
||||
loadTrialStatus: () => void;
|
||||
startLicenseTrial: () => void;
|
||||
telemetry?: TelemetryPluginSetup;
|
||||
shouldShowStartTrial: boolean;
|
||||
}
|
||||
|
||||
interface State {
|
||||
showConfirmation: boolean;
|
||||
isOptingInToTelemetry: boolean;
|
||||
}
|
||||
|
||||
export class StartTrial extends Component<Props, State> {
|
||||
cancelRef: any;
|
||||
confirmRef: any;
|
||||
|
||||
state: State = {
|
||||
showConfirmation: false,
|
||||
isOptingInToTelemetry: false,
|
||||
};
|
||||
|
||||
export class StartTrial extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { showConfirmation: false };
|
||||
}
|
||||
UNSAFE_componentWillMount() {
|
||||
this.props.loadTrialStatus();
|
||||
}
|
||||
startLicenseTrial = () => {
|
||||
const { startLicenseTrial } = this.props;
|
||||
if (this.telemetryOptIn.isOptingInToTelemetry()) {
|
||||
optInToTelemetry(true);
|
||||
|
||||
onOptInChange = (isOptingInToTelemetry: boolean) => {
|
||||
this.setState({ isOptingInToTelemetry });
|
||||
};
|
||||
|
||||
onStartLicenseTrial = () => {
|
||||
const { telemetry, startLicenseTrial } = this.props;
|
||||
if (this.state.isOptingInToTelemetry && telemetry) {
|
||||
telemetry.telemetryService.setOptIn(true);
|
||||
}
|
||||
startLicenseTrial();
|
||||
};
|
||||
|
||||
cancel = () => {
|
||||
this.setState({ showConfirmation: false });
|
||||
};
|
||||
acknowledgeModal() {
|
||||
const { showConfirmation } = this.state;
|
||||
const { showConfirmation, isOptingInToTelemetry } = this.state;
|
||||
const { telemetry } = this.props;
|
||||
|
||||
if (!showConfirmation) {
|
||||
return null;
|
||||
}
|
||||
|
@ -158,12 +182,14 @@ export class StartTrial extends React.PureComponent {
|
|||
<EuiModalFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<TelemetryOptIn
|
||||
isStartTrial={true}
|
||||
ref={ref => {
|
||||
this.telemetryOptIn = ref;
|
||||
}}
|
||||
/>
|
||||
{shouldShowTelemetryOptIn(telemetry) && (
|
||||
<TelemetryOptIn
|
||||
telemetry={telemetry}
|
||||
isStartTrial={true}
|
||||
onOptInChange={this.onOptInChange}
|
||||
isOptingInToTelemetry={isOptingInToTelemetry}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} className="licManagement__ieFlex">
|
||||
<EuiFlexGroup responsive={false}>
|
||||
|
@ -182,7 +208,7 @@ export class StartTrial extends React.PureComponent {
|
|||
<EuiFlexItem grow={false} className="licManagement__ieFlex">
|
||||
<EuiButton
|
||||
data-test-subj="confirmModalConfirmButton"
|
||||
onClick={this.startLicenseTrial}
|
||||
onClick={this.onStartLicenseTrial}
|
||||
fill
|
||||
buttonRef={this.confirmRef}
|
||||
color="primary"
|
|
@ -22,20 +22,28 @@ import {
|
|||
EuiPageContentBody,
|
||||
} from '@elastic/eui';
|
||||
import { TelemetryOptIn } from '../../components/telemetry_opt_in';
|
||||
import { optInToTelemetry } from '../../lib/telemetry';
|
||||
import { shouldShowTelemetryOptIn } from '../../lib/telemetry';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
export class UploadLicense extends React.PureComponent {
|
||||
state = {
|
||||
isOptingInToTelemetry: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.setBreadcrumb('upload');
|
||||
this.props.addUploadErrorMessage('');
|
||||
}
|
||||
onOptInChange = isOptingInToTelemetry => {
|
||||
this.setState({ isOptingInToTelemetry });
|
||||
};
|
||||
send = acknowledge => {
|
||||
const file = this.file;
|
||||
const fr = new FileReader();
|
||||
|
||||
fr.onload = ({ target: { result } }) => {
|
||||
if (this.telemetryOptIn.isOptingInToTelemetry()) {
|
||||
optInToTelemetry(true);
|
||||
if (this.state.isOptingInToTelemetry) {
|
||||
this.props.telemetry?.telemetryService.setOptIn(true);
|
||||
}
|
||||
this.props.uploadLicense(result, this.props.currentLicenseType, acknowledge);
|
||||
};
|
||||
|
@ -116,7 +124,8 @@ export class UploadLicense extends React.PureComponent {
|
|||
}
|
||||
};
|
||||
render() {
|
||||
const { currentLicenseType, applying } = this.props;
|
||||
const { currentLicenseType, applying, telemetry } = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiPageContent horizontalPosition="center" verticalPosition="center">
|
||||
|
@ -170,11 +179,13 @@ export class UploadLicense extends React.PureComponent {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<TelemetryOptIn
|
||||
ref={ref => {
|
||||
this.telemetryOptIn = ref;
|
||||
}}
|
||||
/>
|
||||
{shouldShowTelemetryOptIn(telemetry) && (
|
||||
<TelemetryOptIn
|
||||
isOptingInToTelemetry={this.state.isOptingInToTelemetry}
|
||||
onOptInChange={this.onOptInChange}
|
||||
telemetry={telemetry}
|
||||
/>
|
||||
)}
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
*/
|
||||
|
||||
import { CoreSetup, CoreStart, Plugin } from 'src/core/public';
|
||||
import { TelemetryPluginSetup } from 'src/plugins/telemetry/public';
|
||||
import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main';
|
||||
import { PLUGIN } from '../../common/constants';
|
||||
import { Breadcrumb } from './application/breadcrumbs';
|
||||
|
||||
export interface Plugins {
|
||||
telemetry: TelemetryPluginSetup;
|
||||
__LEGACY: {
|
||||
xpackInfo: XPackMainPlugin;
|
||||
refreshXpack: () => void;
|
||||
|
@ -18,7 +19,7 @@ export interface Plugins {
|
|||
}
|
||||
|
||||
export class LicenseManagementUIPlugin implements Plugin<void, void, any, any> {
|
||||
setup({ application, notifications, http }: CoreSetup, { __LEGACY }: Plugins) {
|
||||
setup({ application, notifications, http }: CoreSetup, { __LEGACY, telemetry }: Plugins) {
|
||||
application.register({
|
||||
id: PLUGIN.ID,
|
||||
title: PLUGIN.TITLE,
|
||||
|
@ -41,6 +42,7 @@ export class LicenseManagementUIPlugin implements Plugin<void, void, any, any> {
|
|||
http,
|
||||
element,
|
||||
chrome,
|
||||
telemetry,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -15,15 +15,6 @@ import routes from 'ui/routes';
|
|||
import { xpackInfo } from 'plugins/xpack_main/services/xpack_info';
|
||||
|
||||
import { plugin } from './np_ready';
|
||||
|
||||
import {
|
||||
setTelemetryOptInService,
|
||||
setTelemetryEnabled,
|
||||
setHttpClient,
|
||||
TelemetryOptInProvider,
|
||||
// @ts-ignore
|
||||
} from './np_ready/application/lib/telemetry';
|
||||
|
||||
import { BASE_PATH } from '../common/constants';
|
||||
|
||||
const licenseManagementUiEnabled = chrome.getInjected('licenseManagementUiEnabled');
|
||||
|
@ -51,15 +42,6 @@ if (licenseManagementUiEnabled) {
|
|||
});
|
||||
};
|
||||
|
||||
const initializeTelemetry = ($injector: any) => {
|
||||
const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled');
|
||||
const Private = $injector.get('Private');
|
||||
const telemetryOptInProvider = Private(TelemetryOptInProvider);
|
||||
setTelemetryOptInService(telemetryOptInProvider);
|
||||
setTelemetryEnabled(telemetryEnabled);
|
||||
setHttpClient($injector.get('$http'));
|
||||
};
|
||||
|
||||
const template = `<kbn-management-app section="elasticsearch/license_management">
|
||||
<div id="licenseReactRoot"></div>
|
||||
</kbn-management-app>`;
|
||||
|
@ -69,8 +51,6 @@ if (licenseManagementUiEnabled) {
|
|||
controllerAs: 'licenseManagement',
|
||||
controller: class LicenseManagementController {
|
||||
constructor($injector: any, $rootScope: any, $scope: any, $route: any) {
|
||||
initializeTelemetry($injector);
|
||||
|
||||
$scope.$$postDigest(() => {
|
||||
const element = document.getElementById('licenseReactRoot')!;
|
||||
|
||||
|
@ -94,6 +74,7 @@ if (licenseManagementUiEnabled) {
|
|||
},
|
||||
},
|
||||
{
|
||||
telemetry: (npSetup.plugins as any).telemetry,
|
||||
__LEGACY: { xpackInfo, refreshXpack, MANAGEMENT_BREADCRUMB },
|
||||
}
|
||||
);
|
||||
|
|
|
@ -13,8 +13,6 @@ import { setupXPackMain } from './server/lib/setup_xpack_main';
|
|||
import { xpackInfoRoute, settingsRoute } from './server/routes/api/v1';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { has } from 'lodash';
|
||||
|
||||
export { callClusterFactory } from './server/lib/call_cluster_factory';
|
||||
import { registerMonitoringCollection } from './server/telemetry_collection';
|
||||
|
||||
|
@ -98,21 +96,5 @@ export const xpackMain = kibana => {
|
|||
xpackInfoRoute(server);
|
||||
settingsRoute(server, this.kbnServer);
|
||||
},
|
||||
deprecations: () => {
|
||||
function movedToTelemetry(configPath) {
|
||||
return (settings, log) => {
|
||||
if (has(settings, configPath)) {
|
||||
log(
|
||||
`Config key "xpack.xpack_main.${configPath}" is deprecated. Use "telemetry.${configPath}" instead.`
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
return [
|
||||
movedToTelemetry('telemetry.config'),
|
||||
movedToTelemetry('telemetry.url'),
|
||||
movedToTelemetry('telemetry.enabled'),
|
||||
];
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue