mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Telemetry] Set telemetry
SO as hidden
(#147631)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Resolves https://github.com/elastic/kibana/issues/145399
This commit is contained in:
parent
53e42eb633
commit
30469a427a
35 changed files with 474 additions and 358 deletions
18
src/plugins/telemetry/common/routes.ts
Normal file
18
src/plugins/telemetry/common/routes.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fetch Telemetry Config
|
||||
*/
|
||||
export const FetchTelemetryConfigRoute = '/api/telemetry/v2/config';
|
||||
export interface FetchTelemetryConfigResponse {
|
||||
allowChangingOptInStatus: boolean;
|
||||
optIn: boolean | null;
|
||||
sendUsageFrom: 'server' | 'browser';
|
||||
telemetryNotifyUserAboutOptInDefault: boolean;
|
||||
}
|
|
@ -6,11 +6,6 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { getTelemetryOptIn } from './get_telemetry_opt_in';
|
||||
export { getTelemetrySendUsageFrom } from './get_telemetry_send_usage_from';
|
||||
export { getTelemetryAllowChangingOptInStatus } from './get_telemetry_allow_changing_opt_in_status';
|
||||
export { getTelemetryFailureDetails } from './get_telemetry_failure_details';
|
||||
export type { TelemetryFailureDetails } from './get_telemetry_failure_details';
|
||||
export { getTelemetryChannelEndpoint } from './get_telemetry_channel_endpoint';
|
||||
export type {
|
||||
GetTelemetryChannelEndpointConfig,
|
||||
|
|
|
@ -12,22 +12,17 @@ import type {
|
|||
CoreSetup,
|
||||
HttpStart,
|
||||
PluginInitializerContext,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsBatchResponse,
|
||||
ApplicationStart,
|
||||
DocLinksStart,
|
||||
HttpSetup,
|
||||
} from '@kbn/core/public';
|
||||
import type { ScreenshotModePluginSetup } from '@kbn/screenshot-mode-plugin/public';
|
||||
import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
|
||||
import { ElasticV3BrowserShipper } from '@kbn/analytics-shippers-elastic-v3-browser';
|
||||
|
||||
import { of } from 'rxjs';
|
||||
import { FetchTelemetryConfigResponse, FetchTelemetryConfigRoute } from '../common/routes';
|
||||
import { TelemetrySender, TelemetryService, TelemetryNotifications } from './services';
|
||||
import type {
|
||||
TelemetrySavedObjectAttributes,
|
||||
TelemetrySavedObject,
|
||||
} from '../common/telemetry_config/types';
|
||||
import { getNotifyUserAboutOptInDefault } from '../common/telemetry_config/get_telemetry_notify_user_about_optin_default';
|
||||
import { renderWelcomeTelemetryNotice } from './render_welcome_telemetry_notice';
|
||||
|
||||
/**
|
||||
|
@ -122,7 +117,6 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
private telemetryNotifications?: TelemetryNotifications;
|
||||
private telemetryService?: TelemetryService;
|
||||
private canUserChangeSettings: boolean = true;
|
||||
private savedObjectsClient?: SavedObjectsClientContract;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext<TelemetryPluginConfig>) {
|
||||
this.currentKibanaVersion = initializerContext.env.packageInfo.version;
|
||||
|
@ -169,7 +163,7 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
});
|
||||
|
||||
this.telemetrySender = new TelemetrySender(this.telemetryService, async () => {
|
||||
await this.refreshConfig();
|
||||
await this.refreshConfig(http);
|
||||
analytics.optIn({ global: { enabled: this.telemetryService!.isOptedIn } });
|
||||
});
|
||||
|
||||
|
@ -200,7 +194,6 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
overlays,
|
||||
theme,
|
||||
application,
|
||||
savedObjects,
|
||||
docLinks,
|
||||
}: CoreStart): TelemetryPluginStart {
|
||||
if (!this.telemetryService) {
|
||||
|
@ -220,8 +213,6 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
});
|
||||
this.telemetryNotifications = telemetryNotifications;
|
||||
|
||||
this.savedObjectsClient = savedObjects.client;
|
||||
|
||||
application.currentAppId$.subscribe(async () => {
|
||||
const isUnauthenticated = this.getIsUnauthenticated(http);
|
||||
if (isUnauthenticated) {
|
||||
|
@ -229,7 +220,7 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
}
|
||||
|
||||
// Refresh and get telemetry config
|
||||
const updatedConfig = await this.refreshConfig();
|
||||
const updatedConfig = await this.refreshConfig(http);
|
||||
|
||||
analytics.optIn({ global: { enabled: this.telemetryService!.isOptedIn } });
|
||||
|
||||
|
@ -267,14 +258,17 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
};
|
||||
}
|
||||
|
||||
private async refreshConfig(): Promise<TelemetryPluginConfig | undefined> {
|
||||
if (this.savedObjectsClient && this.telemetryService) {
|
||||
// Update the telemetry config based as a mix of the config files and saved objects
|
||||
const telemetrySavedObject = await this.getTelemetrySavedObject(this.savedObjectsClient);
|
||||
const updatedConfig = await this.updateConfigsBasedOnSavedObjects(telemetrySavedObject);
|
||||
/**
|
||||
* Retrieve the up-to-date configuration
|
||||
* @param http HTTP helper to make requests to the server
|
||||
* @private
|
||||
*/
|
||||
private async refreshConfig(http: HttpStart | HttpSetup): Promise<TelemetryPluginConfig> {
|
||||
const updatedConfig = await this.fetchUpdatedConfig(http);
|
||||
if (this.telemetryService) {
|
||||
this.telemetryService.config = updatedConfig;
|
||||
return updatedConfig;
|
||||
}
|
||||
return updatedConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -321,74 +315,22 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
}
|
||||
}
|
||||
|
||||
private async updateConfigsBasedOnSavedObjects(
|
||||
telemetrySavedObject: TelemetrySavedObject
|
||||
): Promise<TelemetryPluginConfig> {
|
||||
const configTelemetrySendUsageFrom = this.config.sendUsageFrom;
|
||||
const configTelemetryOptIn = this.config.optIn as boolean;
|
||||
const configTelemetryAllowChangingOptInStatus = this.config.allowChangingOptInStatus;
|
||||
|
||||
const currentKibanaVersion = this.currentKibanaVersion;
|
||||
|
||||
const { getTelemetryAllowChangingOptInStatus, getTelemetryOptIn, getTelemetrySendUsageFrom } =
|
||||
await import('../common/telemetry_config');
|
||||
|
||||
const allowChangingOptInStatus = getTelemetryAllowChangingOptInStatus({
|
||||
configTelemetryAllowChangingOptInStatus,
|
||||
telemetrySavedObject,
|
||||
});
|
||||
|
||||
const optIn = getTelemetryOptIn({
|
||||
configTelemetryOptIn,
|
||||
allowChangingOptInStatus,
|
||||
telemetrySavedObject,
|
||||
currentKibanaVersion,
|
||||
});
|
||||
|
||||
const sendUsageFrom = getTelemetrySendUsageFrom({
|
||||
configTelemetrySendUsageFrom,
|
||||
telemetrySavedObject,
|
||||
});
|
||||
|
||||
const telemetryNotifyUserAboutOptInDefault = getNotifyUserAboutOptInDefault({
|
||||
telemetrySavedObject,
|
||||
allowChangingOptInStatus,
|
||||
configTelemetryOptIn,
|
||||
telemetryOptedIn: optIn,
|
||||
});
|
||||
/**
|
||||
* Fetch configuration from the server and merge it with the one the browser already knows
|
||||
* @param http The HTTP helper to make the requests
|
||||
* @private
|
||||
*/
|
||||
private async fetchUpdatedConfig(http: HttpStart | HttpSetup): Promise<TelemetryPluginConfig> {
|
||||
const { allowChangingOptInStatus, optIn, sendUsageFrom, telemetryNotifyUserAboutOptInDefault } =
|
||||
await http.get<FetchTelemetryConfigResponse>(FetchTelemetryConfigRoute);
|
||||
|
||||
return {
|
||||
...this.config,
|
||||
allowChangingOptInStatus,
|
||||
optIn,
|
||||
sendUsageFrom,
|
||||
telemetryNotifyUserAboutOptInDefault,
|
||||
userCanChangeSettings: this.canUserChangeSettings,
|
||||
};
|
||||
}
|
||||
|
||||
private async getTelemetrySavedObject(savedObjectsClient: SavedObjectsClientContract) {
|
||||
try {
|
||||
// Use bulk get API here to avoid the queue. This could fail independent requests if we don't have rights to access the telemetry object otherwise
|
||||
const {
|
||||
savedObjects: [{ attributes }],
|
||||
} = (await savedObjectsClient.bulkGet([
|
||||
{
|
||||
id: 'telemetry',
|
||||
type: 'telemetry',
|
||||
},
|
||||
])) as SavedObjectsBatchResponse<TelemetrySavedObjectAttributes>;
|
||||
return attributes;
|
||||
} catch (error) {
|
||||
const errorCode = error[Symbol('SavedObjectsClientErrorCode')];
|
||||
if (errorCode === 'SavedObjectsClient/notFound') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (errorCode === 'SavedObjectsClient/forbidden') {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
*/
|
||||
|
||||
import { Observable, firstValueFrom } from 'rxjs';
|
||||
import { ISavedObjectsRepository, SavedObjectsClient } from '@kbn/core/server';
|
||||
import { ISavedObjectsRepository } from '@kbn/core/server';
|
||||
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
|
||||
import { getTelemetrySavedObject, TelemetrySavedObject } from '../../telemetry_repository';
|
||||
import { getTelemetryOptIn, getTelemetrySendUsageFrom } from '../../../common/telemetry_config';
|
||||
import { getTelemetrySavedObject, TelemetrySavedObject } from '../../saved_objects';
|
||||
import { TelemetryConfigType } from '../../config';
|
||||
import { getTelemetryOptIn, getTelemetrySendUsageFrom } from '../../telemetry_config';
|
||||
|
||||
export interface TelemetryUsageStats {
|
||||
opt_in_status?: boolean | null;
|
||||
|
@ -39,9 +39,7 @@ export function createCollectorFetch({
|
|||
|
||||
try {
|
||||
const internalRepository = getSavedObjectsClient()!;
|
||||
telemetrySavedObject = await getTelemetrySavedObject(
|
||||
new SavedObjectsClient(internalRepository)
|
||||
);
|
||||
telemetrySavedObject = await getTelemetrySavedObject(internalRepository);
|
||||
} catch (err) {
|
||||
// no-op
|
||||
}
|
||||
|
|
|
@ -22,23 +22,27 @@ import {
|
|||
import fetch from 'node-fetch';
|
||||
import type { TelemetryCollectionManagerPluginStart } from '@kbn/telemetry-collection-manager-plugin/server';
|
||||
import {
|
||||
PluginInitializerContext,
|
||||
Logger,
|
||||
SavedObjectsClientContract,
|
||||
type PluginInitializerContext,
|
||||
type Logger,
|
||||
type SavedObjectsClientContract,
|
||||
SavedObjectsClient,
|
||||
CoreStart,
|
||||
type CoreStart,
|
||||
} from '@kbn/core/server';
|
||||
import { getTelemetryChannelEndpoint } from '../common/telemetry_config';
|
||||
import {
|
||||
TELEMETRY_SAVED_OBJECT_TYPE,
|
||||
getTelemetrySavedObject,
|
||||
updateTelemetrySavedObject,
|
||||
} from './saved_objects';
|
||||
import { getNextAttemptDate } from './get_next_attempt_date';
|
||||
import {
|
||||
getTelemetryChannelEndpoint,
|
||||
getTelemetryOptIn,
|
||||
getTelemetrySendUsageFrom,
|
||||
getTelemetryFailureDetails,
|
||||
} from '../common/telemetry_config';
|
||||
import { getTelemetrySavedObject, updateTelemetrySavedObject } from './telemetry_repository';
|
||||
} from './telemetry_config';
|
||||
import { PAYLOAD_CONTENT_ENCODING } from '../common/constants';
|
||||
import type { EncryptedTelemetryPayload } from '../common/types';
|
||||
import { TelemetryConfigType } from './config';
|
||||
import type { TelemetryConfigType } from './config';
|
||||
import { isReportIntervalExpired } from '../common/is_report_interval_expired';
|
||||
|
||||
export interface FetcherTaskDepsStart {
|
||||
|
@ -78,7 +82,9 @@ export class FetcherTask {
|
|||
}
|
||||
|
||||
public start({ savedObjects }: CoreStart, { telemetryCollectionManager }: FetcherTaskDepsStart) {
|
||||
this.internalRepository = new SavedObjectsClient(savedObjects.createInternalRepository());
|
||||
this.internalRepository = new SavedObjectsClient(
|
||||
savedObjects.createInternalRepository([TELEMETRY_SAVED_OBJECT_TYPE])
|
||||
);
|
||||
this.telemetryCollectionManager = telemetryCollectionManager;
|
||||
|
||||
this.subscriptions.add(this.validateConnectivity());
|
||||
|
|
|
@ -40,6 +40,12 @@ import type {
|
|||
import type { SecurityPluginStart } from '@kbn/security-plugin/server';
|
||||
import { SavedObjectsClient } from '@kbn/core/server';
|
||||
|
||||
import {
|
||||
type TelemetrySavedObject,
|
||||
getTelemetrySavedObject,
|
||||
registerTelemetrySavedObject,
|
||||
TELEMETRY_SAVED_OBJECT_TYPE,
|
||||
} from './saved_objects';
|
||||
import { registerRoutes } from './routes';
|
||||
import { registerCollection } from './telemetry_collection';
|
||||
import {
|
||||
|
@ -48,9 +54,9 @@ import {
|
|||
} from './collectors';
|
||||
import type { TelemetryConfigLabels, TelemetryConfigType } from './config';
|
||||
import { FetcherTask } from './fetcher';
|
||||
import { getTelemetrySavedObject, TelemetrySavedObject } from './telemetry_repository';
|
||||
import { OPT_IN_POLL_INTERVAL_MS } from '../common/constants';
|
||||
import { getTelemetryOptIn, getTelemetryChannelEndpoint } from '../common/telemetry_config';
|
||||
import { getTelemetryChannelEndpoint } from '../common/telemetry_config';
|
||||
import { getTelemetryOptIn } from './telemetry_config';
|
||||
|
||||
interface TelemetryPluginsDepsSetup {
|
||||
usageCollection: UsageCollectionSetup;
|
||||
|
@ -87,8 +93,6 @@ export interface TelemetryPluginStart {
|
|||
getIsOptedIn: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
type SavedObjectsRegisterType = CoreSetup['savedObjects']['registerType'];
|
||||
|
||||
export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPluginStart> {
|
||||
private readonly logger: Logger;
|
||||
private readonly currentKibanaVersion: string;
|
||||
|
@ -111,9 +115,9 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
*
|
||||
* Using the internal client in all cases ensures the permissions to interact the document.
|
||||
*/
|
||||
private savedObjectsInternalClient$ = new ReplaySubject<SavedObjectsClient>(1);
|
||||
private readonly savedObjectsInternalClient$ = new ReplaySubject<SavedObjectsClient>(1);
|
||||
|
||||
private pluginStop$ = new ReplaySubject<void>(1);
|
||||
private readonly pluginStop$ = new ReplaySubject<void>(1);
|
||||
|
||||
private security?: SecurityPluginStart;
|
||||
|
||||
|
@ -189,7 +193,7 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
getSecurity: () => this.security,
|
||||
});
|
||||
|
||||
this.registerMappings((opts) => savedObjects.registerType(opts));
|
||||
registerTelemetrySavedObject((opts) => savedObjects.registerType(opts));
|
||||
this.registerUsageCollectors(usageCollection);
|
||||
|
||||
return {
|
||||
|
@ -213,7 +217,9 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
|
||||
this.isOptedIn$.subscribe((enabled) => analytics.optIn({ global: { enabled } }));
|
||||
|
||||
const savedObjectsInternalRepository = savedObjects.createInternalRepository();
|
||||
const savedObjectsInternalRepository = savedObjects.createInternalRepository([
|
||||
TELEMETRY_SAVED_OBJECT_TYPE,
|
||||
]);
|
||||
this.savedObjectsInternalRepository = savedObjectsInternalRepository;
|
||||
this.savedObjectsInternalClient$.next(new SavedObjectsClient(savedObjectsInternalRepository));
|
||||
|
||||
|
@ -244,10 +250,7 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
telemetrySavedObject = await getTelemetrySavedObject(internalRepositoryClient);
|
||||
} catch (err) {
|
||||
this.logger.debug('Failed to check telemetry opt-in status: ' + err.message);
|
||||
}
|
||||
|
||||
// If we can't get the saved object due to permissions or other error other than 404, skip this round.
|
||||
if (typeof telemetrySavedObject === 'undefined' || telemetrySavedObject === false) {
|
||||
// If we can't get the saved object due to any error other than 404, skip this round.
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -271,46 +274,10 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
core: CoreStart,
|
||||
telemetryCollectionManager: TelemetryCollectionManagerPluginStart
|
||||
) {
|
||||
// We start the fetcher having updated everything we need to using the config settings
|
||||
// We start the fetcher having updated everything we need to use the config settings
|
||||
this.fetcherTask.start(core, { telemetryCollectionManager });
|
||||
}
|
||||
|
||||
private registerMappings(registerType: SavedObjectsRegisterType) {
|
||||
registerType({
|
||||
name: 'telemetry',
|
||||
hidden: false,
|
||||
namespaceType: 'agnostic',
|
||||
mappings: {
|
||||
properties: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
},
|
||||
sendUsageFrom: {
|
||||
type: 'keyword',
|
||||
},
|
||||
lastReported: {
|
||||
type: 'date',
|
||||
},
|
||||
lastVersionChecked: {
|
||||
type: 'keyword',
|
||||
},
|
||||
userHasSeenNotice: {
|
||||
type: 'boolean',
|
||||
},
|
||||
reportFailureCount: {
|
||||
type: 'integer',
|
||||
},
|
||||
reportFailureVersion: {
|
||||
type: 'keyword',
|
||||
},
|
||||
allowChangingOptInStatus: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private registerUsageCollectors(usageCollection: UsageCollectionSetup) {
|
||||
const getSavedObjectsClient = () => this.savedObjectsInternalRepository;
|
||||
|
||||
|
|
|
@ -9,11 +9,12 @@
|
|||
import type { Observable } from 'rxjs';
|
||||
import type { IRouter, Logger, SavedObjectsClient } from '@kbn/core/server';
|
||||
import type { TelemetryCollectionManagerPluginSetup } from '@kbn/telemetry-collection-manager-plugin/server';
|
||||
import type { TelemetryConfigType } from '../config';
|
||||
import { registerTelemetryConfigRoutes } from './telemetry_config';
|
||||
import { registerTelemetryOptInRoutes } from './telemetry_opt_in';
|
||||
import { registerTelemetryUsageStatsRoutes, SecurityGetter } from './telemetry_usage_stats';
|
||||
import { registerTelemetryUsageStatsRoutes, type SecurityGetter } from './telemetry_usage_stats';
|
||||
import { registerTelemetryOptInStatsRoutes } from './telemetry_opt_in_stats';
|
||||
import { registerTelemetryUserHasSeenNotice } from './telemetry_user_has_seen_notice';
|
||||
import type { TelemetryConfigType } from '../config';
|
||||
import { registerTelemetryLastReported } from './telemetry_last_reported';
|
||||
|
||||
interface RegisterRoutesParams {
|
||||
|
@ -31,6 +32,7 @@ export function registerRoutes(options: RegisterRoutesParams) {
|
|||
const { isDev, telemetryCollectionManager, router, savedObjectsInternalClient$, getSecurity } =
|
||||
options;
|
||||
registerTelemetryOptInRoutes(options);
|
||||
registerTelemetryConfigRoutes(options);
|
||||
registerTelemetryUsageStatsRoutes(router, telemetryCollectionManager, isDev, getSecurity);
|
||||
registerTelemetryOptInStatsRoutes(router, telemetryCollectionManager);
|
||||
registerTelemetryUserHasSeenNotice(router);
|
||||
|
|
77
src/plugins/telemetry/server/routes/telemetry_config.ts
Normal file
77
src/plugins/telemetry/server/routes/telemetry_config.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { type Observable, firstValueFrom } from 'rxjs';
|
||||
import type { IRouter, SavedObjectsClient } from '@kbn/core/server';
|
||||
import type { TelemetryConfigType } from '../config';
|
||||
import { FetchTelemetryConfigResponse, FetchTelemetryConfigRoute } from '../../common/routes';
|
||||
import { getTelemetrySavedObject } from '../saved_objects';
|
||||
import {
|
||||
getNotifyUserAboutOptInDefault,
|
||||
getTelemetryAllowChangingOptInStatus,
|
||||
getTelemetryOptIn,
|
||||
getTelemetrySendUsageFrom,
|
||||
} from '../telemetry_config';
|
||||
|
||||
interface RegisterTelemetryConfigRouteOptions {
|
||||
router: IRouter;
|
||||
config$: Observable<TelemetryConfigType>;
|
||||
currentKibanaVersion: string;
|
||||
savedObjectsInternalClient$: Observable<SavedObjectsClient>;
|
||||
}
|
||||
export function registerTelemetryConfigRoutes({
|
||||
router,
|
||||
config$,
|
||||
currentKibanaVersion,
|
||||
savedObjectsInternalClient$,
|
||||
}: RegisterTelemetryConfigRouteOptions) {
|
||||
// GET to retrieve
|
||||
router.get(
|
||||
{
|
||||
path: FetchTelemetryConfigRoute,
|
||||
validate: false,
|
||||
},
|
||||
async (context, req, res) => {
|
||||
const config = await firstValueFrom(config$);
|
||||
const savedObjectsInternalClient = await firstValueFrom(savedObjectsInternalClient$);
|
||||
const telemetrySavedObject = await getTelemetrySavedObject(savedObjectsInternalClient);
|
||||
const allowChangingOptInStatus = getTelemetryAllowChangingOptInStatus({
|
||||
configTelemetryAllowChangingOptInStatus: config.allowChangingOptInStatus,
|
||||
telemetrySavedObject,
|
||||
});
|
||||
|
||||
const optIn = getTelemetryOptIn({
|
||||
configTelemetryOptIn: config.optIn,
|
||||
allowChangingOptInStatus,
|
||||
telemetrySavedObject,
|
||||
currentKibanaVersion,
|
||||
});
|
||||
|
||||
const sendUsageFrom = getTelemetrySendUsageFrom({
|
||||
configTelemetrySendUsageFrom: config.sendUsageFrom,
|
||||
telemetrySavedObject,
|
||||
});
|
||||
|
||||
const telemetryNotifyUserAboutOptInDefault = getNotifyUserAboutOptInDefault({
|
||||
telemetrySavedObject,
|
||||
allowChangingOptInStatus,
|
||||
configTelemetryOptIn: config.optIn,
|
||||
telemetryOptedIn: optIn,
|
||||
});
|
||||
|
||||
const body: FetchTelemetryConfigResponse = {
|
||||
allowChangingOptInStatus,
|
||||
optIn,
|
||||
sendUsageFrom,
|
||||
telemetryNotifyUserAboutOptInDefault,
|
||||
};
|
||||
|
||||
return res.ok({ body });
|
||||
}
|
||||
);
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
import type { IRouter, SavedObjectsClient } from '@kbn/core/server';
|
||||
import type { Observable } from 'rxjs';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { getTelemetrySavedObject, updateTelemetrySavedObject } from '../telemetry_repository';
|
||||
import { getTelemetrySavedObject, updateTelemetrySavedObject } from '../saved_objects';
|
||||
|
||||
export function registerTelemetryLastReported(
|
||||
router: IRouter,
|
||||
|
|
|
@ -6,23 +6,24 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { firstValueFrom, type Observable } from 'rxjs';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { IRouter, Logger } from '@kbn/core/server';
|
||||
import {
|
||||
import type { IRouter, Logger } from '@kbn/core/server';
|
||||
import { SavedObjectsErrorHelpers } from '@kbn/core/server';
|
||||
import type {
|
||||
StatsGetterConfig,
|
||||
TelemetryCollectionManagerPluginSetup,
|
||||
} from '@kbn/telemetry-collection-manager-plugin/server';
|
||||
import { SavedObjectsErrorHelpers } from '@kbn/core/server';
|
||||
import { getTelemetryAllowChangingOptInStatus } from '../../common/telemetry_config';
|
||||
import { sendTelemetryOptInStatus } from './telemetry_opt_in_stats';
|
||||
|
||||
import {
|
||||
TelemetrySavedObjectAttributes,
|
||||
updateTelemetrySavedObject,
|
||||
getTelemetrySavedObject,
|
||||
} from '../telemetry_repository';
|
||||
TELEMETRY_SAVED_OBJECT_TYPE,
|
||||
type TelemetrySavedObject,
|
||||
updateTelemetrySavedObject,
|
||||
} from '../saved_objects';
|
||||
|
||||
import { TelemetryConfigType } from '../config';
|
||||
import { getTelemetryAllowChangingOptInStatus } from '../telemetry_config';
|
||||
|
||||
interface RegisterOptInRoutesParams {
|
||||
currentKibanaVersion: string;
|
||||
|
@ -48,24 +49,29 @@ export function registerTelemetryOptInRoutes({
|
|||
},
|
||||
async (context, req, res) => {
|
||||
const newOptInStatus = req.body.enabled;
|
||||
const soClient = (await context.core).savedObjects.client;
|
||||
const attributes: TelemetrySavedObjectAttributes = {
|
||||
const soClient = (await context.core).savedObjects.getClient({
|
||||
includedHiddenTypes: [TELEMETRY_SAVED_OBJECT_TYPE],
|
||||
});
|
||||
const attributes: TelemetrySavedObject = {
|
||||
enabled: newOptInStatus,
|
||||
lastVersionChecked: currentKibanaVersion,
|
||||
};
|
||||
const config = await firstValueFrom(config$);
|
||||
const telemetrySavedObject = await getTelemetrySavedObject(soClient);
|
||||
|
||||
if (telemetrySavedObject === false) {
|
||||
// If we get false, we couldn't get the saved object due to lack of permissions
|
||||
// so we can assume the user won't be able to update it either
|
||||
return res.forbidden();
|
||||
let telemetrySavedObject: TelemetrySavedObject | undefined;
|
||||
try {
|
||||
telemetrySavedObject = await getTelemetrySavedObject(soClient);
|
||||
} catch (err) {
|
||||
if (SavedObjectsErrorHelpers.isForbiddenError(err)) {
|
||||
// If we couldn't get the saved object due to lack of permissions,
|
||||
// we can assume the user won't be able to update it either
|
||||
return res.forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
const configTelemetryAllowChangingOptInStatus = config.allowChangingOptInStatus;
|
||||
const allowChangingOptInStatus = getTelemetryAllowChangingOptInStatus({
|
||||
configTelemetryAllowChangingOptInStatus: config.allowChangingOptInStatus,
|
||||
telemetrySavedObject,
|
||||
configTelemetryAllowChangingOptInStatus,
|
||||
});
|
||||
if (!allowChangingOptInStatus) {
|
||||
return res.badRequest({
|
||||
|
|
|
@ -8,15 +8,15 @@
|
|||
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
import { IRouter } from '@kbn/core/server';
|
||||
import type { IRouter } from '@kbn/core/server';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import {
|
||||
import type {
|
||||
TelemetryCollectionManagerPluginSetup,
|
||||
StatsGetterConfig,
|
||||
} from '@kbn/telemetry-collection-manager-plugin/server';
|
||||
import { EncryptedTelemetryPayload, UnencryptedTelemetryPayload } from '../../common/types';
|
||||
import { getTelemetryChannelEndpoint } from '../../common/telemetry_config';
|
||||
import { PAYLOAD_CONTENT_ENCODING } from '../../common/constants';
|
||||
import type { UnencryptedTelemetryPayload } from '../../common/types';
|
||||
|
||||
interface SendTelemetryOptInStatusConfig {
|
||||
sendUsageTo: 'staging' | 'prod';
|
||||
|
@ -35,7 +35,7 @@ export async function sendTelemetryOptInStatus(
|
|||
channelName: 'optInStatus',
|
||||
});
|
||||
|
||||
const optInStatusPayload: UnencryptedTelemetryPayload =
|
||||
const optInStatusPayload: UnencryptedTelemetryPayload | EncryptedTelemetryPayload =
|
||||
await telemetryCollectionManager.getOptInStats(newOptInStatus, statsGetterConfig);
|
||||
|
||||
await Promise.all(
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { IRouter } from '@kbn/core/server';
|
||||
import {
|
||||
import type { IRouter } from '@kbn/core/server';
|
||||
import type {
|
||||
TelemetryCollectionManagerPluginSetup,
|
||||
StatsGetterConfig,
|
||||
} from '@kbn/telemetry-collection-manager-plugin/server';
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { IRouter } from '@kbn/core/server';
|
||||
import type { IRouter } from '@kbn/core/server';
|
||||
import { TELEMETRY_SAVED_OBJECT_TYPE } from '../saved_objects';
|
||||
import {
|
||||
TelemetrySavedObject,
|
||||
TelemetrySavedObjectAttributes,
|
||||
type TelemetrySavedObjectAttributes,
|
||||
getTelemetrySavedObject,
|
||||
updateTelemetrySavedObject,
|
||||
} from '../telemetry_repository';
|
||||
} from '../saved_objects';
|
||||
|
||||
export function registerTelemetryUserHasSeenNotice(router: IRouter) {
|
||||
router.put(
|
||||
|
@ -21,17 +21,17 @@ export function registerTelemetryUserHasSeenNotice(router: IRouter) {
|
|||
validate: false,
|
||||
},
|
||||
async (context, req, res) => {
|
||||
const internalRepository = (await context.core).savedObjects.client;
|
||||
const telemetrySavedObject: TelemetrySavedObject = await getTelemetrySavedObject(
|
||||
internalRepository
|
||||
);
|
||||
const soClient = (await context.core).savedObjects.getClient({
|
||||
includedHiddenTypes: [TELEMETRY_SAVED_OBJECT_TYPE],
|
||||
});
|
||||
const telemetrySavedObject = await getTelemetrySavedObject(soClient);
|
||||
|
||||
// update the object with a flag stating that the opt-in notice has been seen
|
||||
const updatedAttributes: TelemetrySavedObjectAttributes = {
|
||||
...telemetrySavedObject,
|
||||
userHasSeenNotice: true,
|
||||
};
|
||||
await updateTelemetrySavedObject(internalRepository, updatedAttributes);
|
||||
await updateTelemetrySavedObject(soClient, updatedAttributes);
|
||||
|
||||
return res.ok({ body: updatedAttributes });
|
||||
}
|
||||
|
|
10
src/plugins/telemetry/server/saved_objects/constants.ts
Normal file
10
src/plugins/telemetry/server/saved_objects/constants.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export const TELEMETRY_SAVED_OBJECT_TYPE = 'telemetry';
|
||||
export const TELEMETRY_SAVED_OBJECT_ID = 'telemetry';
|
|
@ -11,24 +11,24 @@ import { SavedObjectsErrorHelpers } from '@kbn/core/server';
|
|||
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
|
||||
|
||||
describe('getTelemetrySavedObject', () => {
|
||||
it('returns null when saved object not found', async () => {
|
||||
it('returns {} when saved object not found', async () => {
|
||||
const params = getCallGetTelemetrySavedObjectParams({
|
||||
savedObjectNotFound: true,
|
||||
});
|
||||
|
||||
const result = await callGetTelemetrySavedObject(params);
|
||||
|
||||
expect(result).toBe(null);
|
||||
expect(result).toStrictEqual({});
|
||||
});
|
||||
|
||||
it('returns false when saved object forbidden', async () => {
|
||||
it('throws when saved object forbidden', async () => {
|
||||
const params = getCallGetTelemetrySavedObjectParams({
|
||||
savedObjectForbidden: true,
|
||||
});
|
||||
|
||||
const result = await callGetTelemetrySavedObject(params);
|
||||
|
||||
expect(result).toBe(false);
|
||||
await expect(callGetTelemetrySavedObject(params)).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"savedObjectForbidden"`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws an error on unexpected saved object error', async () => {
|
||||
|
@ -36,15 +36,7 @@ describe('getTelemetrySavedObject', () => {
|
|||
savedObjectOtherError: true,
|
||||
});
|
||||
|
||||
let threw = false;
|
||||
try {
|
||||
await callGetTelemetrySavedObject(params);
|
||||
} catch (err) {
|
||||
threw = true;
|
||||
expect(err.message).toBe(SavedObjectOtherErrorMessage);
|
||||
}
|
||||
|
||||
expect(threw).toBe(true);
|
||||
await expect(callGetTelemetrySavedObject(params)).rejects.toThrow(SavedObjectOtherErrorMessage);
|
||||
});
|
||||
});
|
||||
|
|
@ -6,28 +6,27 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SavedObjectsErrorHelpers, type SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import type { TelemetrySavedObject } from '.';
|
||||
import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
||||
import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server';
|
||||
import type { TelemetrySavedObject } from './types';
|
||||
import { TELEMETRY_SAVED_OBJECT_TYPE, TELEMETRY_SAVED_OBJECT_ID } from './constants';
|
||||
|
||||
type GetTelemetrySavedObject = (
|
||||
repository: SavedObjectsClientContract
|
||||
soClient: SavedObjectsClientContract
|
||||
) => Promise<TelemetrySavedObject>;
|
||||
|
||||
export const getTelemetrySavedObject: GetTelemetrySavedObject = async (
|
||||
repository: SavedObjectsClientContract
|
||||
soClient: SavedObjectsClientContract
|
||||
) => {
|
||||
try {
|
||||
const { attributes } = await repository.get<TelemetrySavedObject>('telemetry', 'telemetry');
|
||||
const { attributes } = await soClient.get<TelemetrySavedObject>(
|
||||
TELEMETRY_SAVED_OBJECT_TYPE,
|
||||
TELEMETRY_SAVED_OBJECT_ID
|
||||
);
|
||||
return attributes;
|
||||
} catch (error) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(error)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// if we aren't allowed to get the telemetry document, we can assume that we won't
|
||||
// be able to opt into telemetry either, so we're returning `false` here instead of null
|
||||
if (SavedObjectsErrorHelpers.isForbiddenError(error)) {
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
|
||||
throw error;
|
|
@ -6,9 +6,8 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { TELEMETRY_SAVED_OBJECT_TYPE, TELEMETRY_SAVED_OBJECT_ID } from './constants';
|
||||
export { getTelemetrySavedObject } from './get_telemetry_saved_object';
|
||||
export { registerTelemetrySavedObject } from './register_telemetry_saved_object';
|
||||
export { updateTelemetrySavedObject } from './update_telemetry_saved_object';
|
||||
export type {
|
||||
TelemetrySavedObject,
|
||||
TelemetrySavedObjectAttributes,
|
||||
} from '../../common/telemetry_config/types';
|
||||
export type { TelemetrySavedObject, TelemetrySavedObjectAttributes } from './types';
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { SavedObjectsServiceSetup } from '@kbn/core-saved-objects-server';
|
||||
import { TELEMETRY_SAVED_OBJECT_ID } from './constants';
|
||||
|
||||
export function registerTelemetrySavedObject(
|
||||
registerType: SavedObjectsServiceSetup['registerType']
|
||||
) {
|
||||
registerType({
|
||||
name: TELEMETRY_SAVED_OBJECT_ID,
|
||||
hidden: true,
|
||||
namespaceType: 'agnostic',
|
||||
mappings: {
|
||||
properties: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
},
|
||||
sendUsageFrom: {
|
||||
type: 'keyword',
|
||||
},
|
||||
lastReported: {
|
||||
type: 'date',
|
||||
},
|
||||
lastVersionChecked: {
|
||||
type: 'keyword',
|
||||
},
|
||||
userHasSeenNotice: {
|
||||
type: 'boolean',
|
||||
},
|
||||
reportFailureCount: {
|
||||
type: 'integer',
|
||||
},
|
||||
reportFailureVersion: {
|
||||
type: 'keyword',
|
||||
},
|
||||
allowChangingOptInStatus: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
|
@ -17,4 +17,4 @@ export interface TelemetrySavedObjectAttributes {
|
|||
reportFailureVersion?: string;
|
||||
}
|
||||
|
||||
export type TelemetrySavedObject = TelemetrySavedObjectAttributes | null | false;
|
||||
export type TelemetrySavedObject = TelemetrySavedObjectAttributes;
|
|
@ -7,18 +7,23 @@
|
|||
*/
|
||||
|
||||
import { SavedObjectsErrorHelpers, SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import { TelemetrySavedObjectAttributes } from '.';
|
||||
import { TELEMETRY_SAVED_OBJECT_TYPE, TELEMETRY_SAVED_OBJECT_ID } from './constants';
|
||||
import type { TelemetrySavedObjectAttributes } from './types';
|
||||
|
||||
export async function updateTelemetrySavedObject(
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
savedObjectAttributes: TelemetrySavedObjectAttributes
|
||||
) {
|
||||
try {
|
||||
return await savedObjectsClient.update('telemetry', 'telemetry', savedObjectAttributes);
|
||||
return await savedObjectsClient.update(
|
||||
TELEMETRY_SAVED_OBJECT_TYPE,
|
||||
TELEMETRY_SAVED_OBJECT_ID,
|
||||
savedObjectAttributes
|
||||
);
|
||||
} catch (err) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(err)) {
|
||||
return await savedObjectsClient.create('telemetry', savedObjectAttributes, {
|
||||
id: 'telemetry',
|
||||
return await savedObjectsClient.create(TELEMETRY_SAVED_OBJECT_TYPE, savedObjectAttributes, {
|
||||
id: TELEMETRY_SAVED_OBJECT_ID,
|
||||
overwrite: true,
|
||||
});
|
||||
}
|
|
@ -6,24 +6,16 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { TelemetrySavedObject } from './types';
|
||||
import type { TelemetrySavedObject } from '../saved_objects';
|
||||
|
||||
interface GetTelemetryAllowChangingOptInStatus {
|
||||
configTelemetryAllowChangingOptInStatus: boolean;
|
||||
telemetrySavedObject: TelemetrySavedObject;
|
||||
telemetrySavedObject?: TelemetrySavedObject;
|
||||
}
|
||||
|
||||
export function getTelemetryAllowChangingOptInStatus({
|
||||
telemetrySavedObject,
|
||||
configTelemetryAllowChangingOptInStatus,
|
||||
}: GetTelemetryAllowChangingOptInStatus) {
|
||||
if (!telemetrySavedObject) {
|
||||
return configTelemetryAllowChangingOptInStatus;
|
||||
}
|
||||
|
||||
if (typeof telemetrySavedObject.allowChangingOptInStatus === 'undefined') {
|
||||
return configTelemetryAllowChangingOptInStatus;
|
||||
}
|
||||
|
||||
return telemetrySavedObject.allowChangingOptInStatus;
|
||||
return telemetrySavedObject?.allowChangingOptInStatus ?? configTelemetryAllowChangingOptInStatus;
|
||||
}
|
|
@ -10,7 +10,7 @@ import { getTelemetryFailureDetails } from './get_telemetry_failure_details';
|
|||
|
||||
describe('getTelemetryFailureDetails: get details about server usage fetcher failures', () => {
|
||||
it('returns `failureCount: 0` and `failureVersion: undefined` when telemetry does not have any custom configs in saved Object', () => {
|
||||
const telemetrySavedObject = null;
|
||||
const telemetrySavedObject = {};
|
||||
const failureDetails = getTelemetryFailureDetails({ telemetrySavedObject });
|
||||
expect(failureDetails).toStrictEqual({
|
||||
failureVersion: undefined,
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { TelemetrySavedObject } from './types';
|
||||
import type { TelemetrySavedObject } from '../saved_objects';
|
||||
|
||||
interface GetTelemetryFailureDetailsConfig {
|
||||
telemetrySavedObject: TelemetrySavedObject;
|
|
@ -24,7 +24,7 @@ describe('getNotifyUserAboutOptInDefault: get a flag that describes if the user
|
|||
expect(
|
||||
getNotifyUserAboutOptInDefault({
|
||||
allowChangingOptInStatus: false,
|
||||
telemetrySavedObject: null,
|
||||
telemetrySavedObject: {},
|
||||
telemetryOptedIn: false,
|
||||
configTelemetryOptIn: false,
|
||||
})
|
||||
|
@ -33,7 +33,7 @@ describe('getNotifyUserAboutOptInDefault: get a flag that describes if the user
|
|||
expect(
|
||||
getNotifyUserAboutOptInDefault({
|
||||
allowChangingOptInStatus: false,
|
||||
telemetrySavedObject: null,
|
||||
telemetrySavedObject: {},
|
||||
telemetryOptedIn: true,
|
||||
configTelemetryOptIn: true,
|
||||
})
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { TelemetrySavedObject } from './types';
|
||||
import type { TelemetrySavedObject } from '../saved_objects';
|
||||
|
||||
interface NotifyOpts {
|
||||
allowChangingOptInStatus: boolean;
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import { getTelemetryOptIn } from './get_telemetry_opt_in';
|
||||
import { TelemetrySavedObject } from './types';
|
||||
import type { TelemetrySavedObject } from '../saved_objects';
|
||||
|
||||
describe('getTelemetryOptIn', () => {
|
||||
it('returns null when saved object not found', () => {
|
||||
|
@ -20,16 +20,6 @@ describe('getTelemetryOptIn', () => {
|
|||
expect(result).toBe(null);
|
||||
});
|
||||
|
||||
it('returns false when saved object forbidden', () => {
|
||||
const params = getCallGetTelemetryOptInParams({
|
||||
savedObjectForbidden: true,
|
||||
});
|
||||
|
||||
const result = callGetTelemetryOptIn(params);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('returns null if enabled is null or undefined', () => {
|
||||
for (const enabled of [null, undefined]) {
|
||||
const params = getCallGetTelemetryOptInParams({
|
||||
|
@ -112,7 +102,6 @@ describe('getTelemetryOptIn', () => {
|
|||
|
||||
interface CallGetTelemetryOptInParams {
|
||||
savedObjectNotFound: boolean;
|
||||
savedObjectForbidden: boolean;
|
||||
lastVersionChecked?: string; // should be a string, but test with non-strings
|
||||
currentKibanaVersion: string;
|
||||
result?: boolean | null;
|
||||
|
@ -149,12 +138,9 @@ function callGetTelemetryOptIn(params: CallGetTelemetryOptInParams) {
|
|||
}
|
||||
|
||||
function getMockTelemetrySavedObject(params: CallGetTelemetryOptInParams): TelemetrySavedObject {
|
||||
const { savedObjectNotFound, savedObjectForbidden } = params;
|
||||
if (savedObjectForbidden) {
|
||||
return false;
|
||||
}
|
||||
const { savedObjectNotFound } = params;
|
||||
if (savedObjectNotFound) {
|
||||
return null;
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import type SemVer from 'semver/classes/semver';
|
||||
import semverParse from 'semver/functions/parse';
|
||||
import { TelemetrySavedObject } from './types';
|
||||
import type { TelemetrySavedObject } from '../saved_objects';
|
||||
|
||||
interface GetTelemetryOptInConfig {
|
||||
telemetrySavedObject: TelemetrySavedObject;
|
||||
|
@ -29,11 +29,7 @@ export const getTelemetryOptIn: GetTelemetryOptIn = ({
|
|||
return configTelemetryOptIn;
|
||||
}
|
||||
|
||||
if (telemetrySavedObject === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (telemetrySavedObject === null || typeof telemetrySavedObject.enabled !== 'boolean') {
|
||||
if (typeof telemetrySavedObject.enabled !== 'boolean') {
|
||||
return configTelemetryOptIn;
|
||||
}
|
||||
|
||||
|
@ -42,6 +38,8 @@ export const getTelemetryOptIn: GetTelemetryOptIn = ({
|
|||
// if enabled is true, return it
|
||||
if (savedOptIn === true) return savedOptIn;
|
||||
|
||||
// TODO: Should we split the logic below into another OptIn getter?
|
||||
|
||||
// Additional check if they've already opted out (enabled: false):
|
||||
// - if the Kibana version has changed by at least a minor version,
|
||||
// return null to re-prompt.
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import { getTelemetrySendUsageFrom } from './get_telemetry_send_usage_from';
|
||||
import { TelemetrySavedObject } from './types';
|
||||
import type { TelemetrySavedObject } from '../saved_objects';
|
||||
|
||||
describe('getTelemetrySendUsageFrom', () => {
|
||||
it('returns kibana.yml config when saved object not found', () => {
|
||||
|
@ -21,20 +21,9 @@ describe('getTelemetrySendUsageFrom', () => {
|
|||
expect(result).toBe('browser');
|
||||
});
|
||||
|
||||
it('returns kibana.yml config when saved object forbidden', () => {
|
||||
const params: CallGetTelemetryUsageFetcherParams = {
|
||||
savedObjectForbidden: true,
|
||||
configSendUsageFrom: 'browser',
|
||||
};
|
||||
|
||||
const result = callGetTelemetryUsageFetcher(params);
|
||||
|
||||
expect(result).toBe('browser');
|
||||
});
|
||||
|
||||
it('returns kibana.yml config when saved object sendUsageFrom is undefined', () => {
|
||||
const params: CallGetTelemetryUsageFetcherParams = {
|
||||
savedSendUsagefrom: undefined,
|
||||
savedSendUsageFrom: undefined,
|
||||
configSendUsageFrom: 'server',
|
||||
};
|
||||
|
||||
|
@ -46,8 +35,7 @@ describe('getTelemetrySendUsageFrom', () => {
|
|||
|
||||
interface CallGetTelemetryUsageFetcherParams {
|
||||
savedObjectNotFound?: boolean;
|
||||
savedObjectForbidden?: boolean;
|
||||
savedSendUsagefrom?: 'browser' | 'server';
|
||||
savedSendUsageFrom?: 'browser' | 'server';
|
||||
configSendUsageFrom: 'browser' | 'server';
|
||||
}
|
||||
|
||||
|
@ -60,15 +48,12 @@ function callGetTelemetryUsageFetcher(params: CallGetTelemetryUsageFetcherParams
|
|||
function getMockTelemetrySavedObject(
|
||||
params: CallGetTelemetryUsageFetcherParams
|
||||
): TelemetrySavedObject {
|
||||
const { savedObjectNotFound, savedObjectForbidden } = params;
|
||||
if (savedObjectForbidden) {
|
||||
return false;
|
||||
}
|
||||
const { savedObjectNotFound } = params;
|
||||
if (savedObjectNotFound) {
|
||||
return null;
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
sendUsageFrom: params.savedSendUsagefrom,
|
||||
sendUsageFrom: params.savedSendUsageFrom,
|
||||
};
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { TelemetrySavedObject } from './types';
|
||||
import type { TelemetrySavedObject } from '../saved_objects';
|
||||
|
||||
interface GetTelemetryUsageFetcherConfig {
|
||||
configTelemetrySendUsageFrom: 'browser' | 'server';
|
16
src/plugins/telemetry/server/telemetry_config/index.ts
Normal file
16
src/plugins/telemetry/server/telemetry_config/index.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { getTelemetryAllowChangingOptInStatus } from './get_telemetry_allow_changing_opt_in_status';
|
||||
export {
|
||||
getTelemetryFailureDetails,
|
||||
type TelemetryFailureDetails,
|
||||
} from './get_telemetry_failure_details';
|
||||
export { getNotifyUserAboutOptInDefault } from './get_telemetry_notify_user_about_optin_default';
|
||||
export { getTelemetryOptIn } from './get_telemetry_opt_in';
|
||||
export { getTelemetrySendUsageFrom } from './get_telemetry_send_usage_from';
|
|
@ -6,9 +6,12 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export default function ({ loadTestFile }) {
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('Telemetry', () => {
|
||||
loadTestFile(require.resolve('./opt_in'));
|
||||
loadTestFile(require.resolve('./telemetry_config'));
|
||||
loadTestFile(require.resolve('./telemetry_last_reported'));
|
||||
loadTestFile(require.resolve('./telemetry_optin_notice_seen'));
|
||||
});
|
|
@ -7,87 +7,87 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { Client } from '@elastic/elasticsearch';
|
||||
|
||||
import { TelemetrySavedObjectAttributes } from '@kbn/telemetry-plugin/server/telemetry_repository';
|
||||
import { TelemetrySavedObjectAttributes } from '@kbn/telemetry-plugin/server/saved_objects';
|
||||
import SuperTest from 'supertest';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function optInTest({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const esClient: Client = getService('es');
|
||||
|
||||
describe('/api/telemetry/v2/optIn API', () => {
|
||||
let defaultAttributes: TelemetrySavedObjectAttributes;
|
||||
let kibanaVersion: any;
|
||||
let kibanaVersion: string;
|
||||
before(async () => {
|
||||
await esArchiver.emptyKibanaIndex();
|
||||
const kibanaVersionAccessor = kibanaServer.version;
|
||||
kibanaVersion = await kibanaVersionAccessor.get();
|
||||
defaultAttributes =
|
||||
(await getSavedObjectAttributes(supertest).catch((err) => {
|
||||
if (err.message === 'expected 200 "OK", got 404 "Not Found"') {
|
||||
return null;
|
||||
}
|
||||
throw err;
|
||||
})) || {};
|
||||
defaultAttributes = await getSavedObjectAttributes(esClient);
|
||||
|
||||
expect(typeof kibanaVersion).to.eql('string');
|
||||
expect(kibanaVersion.length).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await updateSavedObjectAttributes(supertest, defaultAttributes);
|
||||
await updateSavedObjectAttributes(esClient, defaultAttributes);
|
||||
});
|
||||
|
||||
it('should support sending false with allowChangingOptInStatus true', async () => {
|
||||
await updateSavedObjectAttributes(supertest, {
|
||||
...defaultAttributes,
|
||||
await updateSavedObjectAttributes(esClient, {
|
||||
allowChangingOptInStatus: true,
|
||||
});
|
||||
await postTelemetryV2Optin(supertest, false, 200);
|
||||
const { enabled, lastVersionChecked } = await getSavedObjectAttributes(supertest);
|
||||
await postTelemetryV2OptIn(supertest, false, 200);
|
||||
const { enabled, lastVersionChecked } = await getSavedObjectAttributes(esClient);
|
||||
expect(enabled).to.be(false);
|
||||
expect(lastVersionChecked).to.be(kibanaVersion);
|
||||
});
|
||||
|
||||
it('should support sending true with allowChangingOptInStatus true', async () => {
|
||||
await updateSavedObjectAttributes(supertest, {
|
||||
await updateSavedObjectAttributes(esClient, {
|
||||
...defaultAttributes,
|
||||
allowChangingOptInStatus: true,
|
||||
});
|
||||
await postTelemetryV2Optin(supertest, true, 200);
|
||||
const { enabled, lastVersionChecked } = await getSavedObjectAttributes(supertest);
|
||||
await postTelemetryV2OptIn(supertest, true, 200);
|
||||
const { enabled, lastVersionChecked } = await getSavedObjectAttributes(esClient);
|
||||
expect(enabled).to.be(true);
|
||||
expect(lastVersionChecked).to.be(kibanaVersion);
|
||||
});
|
||||
|
||||
it('should not support sending false with allowChangingOptInStatus false', async () => {
|
||||
await updateSavedObjectAttributes(supertest, {
|
||||
await updateSavedObjectAttributes(esClient, {
|
||||
...defaultAttributes,
|
||||
allowChangingOptInStatus: false,
|
||||
});
|
||||
await postTelemetryV2Optin(supertest, false, 400);
|
||||
await postTelemetryV2OptIn(supertest, false, 400);
|
||||
});
|
||||
|
||||
it('should not support sending true with allowChangingOptInStatus false', async () => {
|
||||
await updateSavedObjectAttributes(supertest, {
|
||||
await updateSavedObjectAttributes(esClient, {
|
||||
...defaultAttributes,
|
||||
allowChangingOptInStatus: false,
|
||||
});
|
||||
await postTelemetryV2Optin(supertest, true, 400);
|
||||
await postTelemetryV2OptIn(supertest, true, 400);
|
||||
});
|
||||
|
||||
it('should not support sending null', async () => {
|
||||
await postTelemetryV2Optin(supertest, null, 400);
|
||||
await postTelemetryV2OptIn(supertest, null, 400);
|
||||
});
|
||||
|
||||
it('should not support sending junk', async () => {
|
||||
await postTelemetryV2Optin(supertest, 42, 400);
|
||||
await postTelemetryV2OptIn(supertest, 42, 400);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function postTelemetryV2Optin(supertest: any, value: any, statusCode: number): Promise<any> {
|
||||
async function postTelemetryV2OptIn(
|
||||
supertest: SuperTest.SuperTest<SuperTest.Test>,
|
||||
value: unknown,
|
||||
statusCode: number
|
||||
): Promise<any> {
|
||||
const { body } = await supertest
|
||||
.post('/api/telemetry/v2/optIn')
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
|
@ -98,18 +98,29 @@ async function postTelemetryV2Optin(supertest: any, value: any, statusCode: numb
|
|||
}
|
||||
|
||||
async function updateSavedObjectAttributes(
|
||||
supertest: any,
|
||||
es: Client,
|
||||
attributes: TelemetrySavedObjectAttributes
|
||||
): Promise<any> {
|
||||
return await supertest
|
||||
.post('/api/saved_objects/telemetry/telemetry')
|
||||
.query({ overwrite: true })
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.send({ attributes })
|
||||
.expect(200);
|
||||
): Promise<void> {
|
||||
// Directly writing to the `.kibana` index because the SO type now is hidden, meaning it's not exposed via the SO HTTP API
|
||||
await es.update({
|
||||
index: '.kibana',
|
||||
id: 'telemetry:telemetry',
|
||||
doc: {
|
||||
telemetry: attributes,
|
||||
// there are many missing fields in the SO, hopefully it doesn't break Kibana
|
||||
},
|
||||
doc_as_upsert: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function getSavedObjectAttributes(supertest: any): Promise<TelemetrySavedObjectAttributes> {
|
||||
const { body } = await supertest.get('/api/saved_objects/telemetry/telemetry').expect(200);
|
||||
return body.attributes;
|
||||
async function getSavedObjectAttributes(es: Client): Promise<TelemetrySavedObjectAttributes> {
|
||||
// Directly writing to the `.kibana` index because the SO type now is hidden, meaning it's not exposed via the SO HTTP API
|
||||
const { _source: body } = await es.get<{ telemetry: TelemetrySavedObjectAttributes }>(
|
||||
{
|
||||
index: '.kibana',
|
||||
id: 'telemetry:telemetry',
|
||||
},
|
||||
{ ignore: [404] }
|
||||
);
|
||||
return body?.telemetry || {};
|
||||
}
|
||||
|
|
52
test/api_integration/apis/telemetry/telemetry_config.ts
Normal file
52
test/api_integration/apis/telemetry/telemetry_config.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function optInTest({ getService }: FtrProviderContext) {
|
||||
const client = getService('es');
|
||||
const supertest = getService('supertest');
|
||||
|
||||
describe('/api/telemetry/v2/config API Telemetry config', () => {
|
||||
before(async () => {
|
||||
await client.delete(
|
||||
{
|
||||
index: '.kibana',
|
||||
id: 'telemetry:telemetry',
|
||||
},
|
||||
{ ignore: [404] }
|
||||
);
|
||||
});
|
||||
|
||||
it('GET should get the default config', async () => {
|
||||
await supertest.get('/api/telemetry/v2/config').set('kbn-xsrf', 'xxx').expect(200, {
|
||||
allowChangingOptInStatus: true,
|
||||
optIn: false, // the config.js for this FTR sets it to `false`
|
||||
sendUsageFrom: 'server',
|
||||
telemetryNotifyUserAboutOptInDefault: false, // it's not opted-in, so we don't notify about opt-in??
|
||||
});
|
||||
});
|
||||
|
||||
it('GET should get when opted-in', async () => {
|
||||
// Opt-in
|
||||
await supertest
|
||||
.post('/api/telemetry/v2/optIn')
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.send({ enabled: true })
|
||||
.expect(200);
|
||||
|
||||
await supertest.get('/api/telemetry/v2/config').set('kbn-xsrf', 'xxx').expect(200, {
|
||||
allowChangingOptInStatus: true,
|
||||
optIn: true,
|
||||
sendUsageFrom: 'server',
|
||||
// it's not opted-in (in the YAML config) despite being opted-in via API/UI, and we still say false??
|
||||
telemetryNotifyUserAboutOptInDefault: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -25,10 +25,7 @@ export default function optInTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('GET should return undefined when there is no stored telemetry.lastReported value', async () => {
|
||||
await supertest
|
||||
.get('/api/telemetry/v2/last_reported')
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.expect(200, { lastReported: undefined });
|
||||
await supertest.get('/api/telemetry/v2/last_reported').set('kbn-xsrf', 'xxx').expect(200, {});
|
||||
});
|
||||
|
||||
it('PUT should update telemetry.lastReported to now', async () => {
|
||||
|
|
|
@ -6,86 +6,87 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { Client } from '@elastic/elasticsearch';
|
||||
|
||||
import { TelemetrySavedObjectAttributes } from '@kbn/telemetry-plugin/server/telemetry_repository';
|
||||
import { TelemetrySavedObjectAttributes } from '@kbn/telemetry-plugin/server/saved_objects';
|
||||
import SuperTest from 'supertest';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function optInTest({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const esClient: Client = getService('es');
|
||||
|
||||
describe('/api/telemetry/v2/optIn API', () => {
|
||||
let defaultAttributes: TelemetrySavedObjectAttributes;
|
||||
let kibanaVersion: any;
|
||||
|
||||
let kibanaVersion: string;
|
||||
before(async () => {
|
||||
await esArchiver.emptyKibanaIndex();
|
||||
const kibanaVersionAccessor = kibanaServer.version;
|
||||
kibanaVersion = await kibanaVersionAccessor.get();
|
||||
defaultAttributes =
|
||||
(await getSavedObjectAttributes(supertest).catch((err) => {
|
||||
if (err.message === 'expected 200 "OK", got 404 "Not Found"') {
|
||||
return null;
|
||||
}
|
||||
throw err;
|
||||
})) || {};
|
||||
defaultAttributes = await getSavedObjectAttributes(esClient);
|
||||
|
||||
expect(typeof kibanaVersion).to.eql('string');
|
||||
expect(kibanaVersion.length).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await updateSavedObjectAttributes(supertest, defaultAttributes);
|
||||
await updateSavedObjectAttributes(esClient, defaultAttributes);
|
||||
});
|
||||
|
||||
it('should support sending false with allowChangingOptInStatus true', async () => {
|
||||
await updateSavedObjectAttributes(supertest, {
|
||||
...defaultAttributes,
|
||||
await updateSavedObjectAttributes(esClient, {
|
||||
allowChangingOptInStatus: true,
|
||||
});
|
||||
await postTelemetryV2Optin(supertest, false, 200);
|
||||
const { enabled, lastVersionChecked } = await getSavedObjectAttributes(supertest);
|
||||
await postTelemetryV2OptIn(supertest, false, 200);
|
||||
const { enabled, lastVersionChecked } = await getSavedObjectAttributes(esClient);
|
||||
expect(enabled).to.be(false);
|
||||
expect(lastVersionChecked).to.be(kibanaVersion);
|
||||
});
|
||||
|
||||
it('should support sending true with allowChangingOptInStatus true', async () => {
|
||||
await updateSavedObjectAttributes(supertest, {
|
||||
await updateSavedObjectAttributes(esClient, {
|
||||
...defaultAttributes,
|
||||
allowChangingOptInStatus: true,
|
||||
});
|
||||
await postTelemetryV2Optin(supertest, true, 200);
|
||||
const { enabled, lastVersionChecked } = await getSavedObjectAttributes(supertest);
|
||||
await postTelemetryV2OptIn(supertest, true, 200);
|
||||
const { enabled, lastVersionChecked } = await getSavedObjectAttributes(esClient);
|
||||
expect(enabled).to.be(true);
|
||||
expect(lastVersionChecked).to.be(kibanaVersion);
|
||||
});
|
||||
|
||||
it('should not support sending false with allowChangingOptInStatus false', async () => {
|
||||
await updateSavedObjectAttributes(supertest, {
|
||||
await updateSavedObjectAttributes(esClient, {
|
||||
...defaultAttributes,
|
||||
allowChangingOptInStatus: false,
|
||||
});
|
||||
await postTelemetryV2Optin(supertest, false, 400);
|
||||
await postTelemetryV2OptIn(supertest, false, 400);
|
||||
});
|
||||
|
||||
it('should not support sending true with allowChangingOptInStatus false', async () => {
|
||||
await updateSavedObjectAttributes(supertest, {
|
||||
await updateSavedObjectAttributes(esClient, {
|
||||
...defaultAttributes,
|
||||
allowChangingOptInStatus: false,
|
||||
});
|
||||
await postTelemetryV2Optin(supertest, true, 400);
|
||||
await postTelemetryV2OptIn(supertest, true, 400);
|
||||
});
|
||||
|
||||
it('should not support sending null', async () => {
|
||||
await postTelemetryV2Optin(supertest, null, 400);
|
||||
await postTelemetryV2OptIn(supertest, null, 400);
|
||||
});
|
||||
|
||||
it('should not support sending junk', async () => {
|
||||
await postTelemetryV2Optin(supertest, 42, 400);
|
||||
await postTelemetryV2OptIn(supertest, 42, 400);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function postTelemetryV2Optin(supertest: any, value: any, statusCode: number): Promise<any> {
|
||||
async function postTelemetryV2OptIn(
|
||||
supertest: SuperTest.SuperTest<SuperTest.Test>,
|
||||
value: unknown,
|
||||
statusCode: number
|
||||
): Promise<any> {
|
||||
const { body } = await supertest
|
||||
.post('/api/telemetry/v2/optIn')
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
|
@ -96,18 +97,29 @@ async function postTelemetryV2Optin(supertest: any, value: any, statusCode: numb
|
|||
}
|
||||
|
||||
async function updateSavedObjectAttributes(
|
||||
supertest: any,
|
||||
es: Client,
|
||||
attributes: TelemetrySavedObjectAttributes
|
||||
): Promise<any> {
|
||||
return await supertest
|
||||
.post('/api/saved_objects/telemetry/telemetry')
|
||||
.query({ overwrite: true })
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.send({ attributes })
|
||||
.expect(200);
|
||||
): Promise<void> {
|
||||
// Directly writing to the `.kibana` index because the SO type now is hidden, meaning it's not exposed via the SO HTTP API
|
||||
await es.update({
|
||||
index: '.kibana',
|
||||
id: 'telemetry:telemetry',
|
||||
doc: {
|
||||
telemetry: attributes,
|
||||
// there are many missing fields in the SO, hopefully it doesn't break Kibana
|
||||
},
|
||||
doc_as_upsert: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function getSavedObjectAttributes(supertest: any): Promise<TelemetrySavedObjectAttributes> {
|
||||
const { body } = await supertest.get('/api/saved_objects/telemetry/telemetry').expect(200);
|
||||
return body.attributes;
|
||||
async function getSavedObjectAttributes(es: Client): Promise<TelemetrySavedObjectAttributes> {
|
||||
// Directly writing to the `.kibana` index because the SO type now is hidden, meaning it's not exposed via the SO HTTP API
|
||||
const { _source: body } = await es.get<{ telemetry: TelemetrySavedObjectAttributes }>(
|
||||
{
|
||||
index: '.kibana',
|
||||
id: 'telemetry:telemetry',
|
||||
},
|
||||
{ ignore: [404] }
|
||||
);
|
||||
return body?.telemetry || {};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue