From 1acff4f49667f7347b7e63f474647da586c14a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Thu, 15 Dec 2022 13:50:21 +0100 Subject: [PATCH] [Telemetry] Set `telemetry` SO as `hidden` --- src/plugins/telemetry/common/routes.ts | 18 ++++ .../common/telemetry_config/index.ts | 5 - src/plugins/telemetry/public/plugin.ts | 102 ++++-------------- .../telemetry_plugin_collector.ts | 10 +- src/plugins/telemetry/server/fetcher.ts | 24 +++-- src/plugins/telemetry/server/plugin.ts | 65 +++-------- src/plugins/telemetry/server/routes/index.ts | 6 +- .../server/routes/telemetry_config.ts | 77 +++++++++++++ .../server/routes/telemetry_last_reported.ts | 2 +- .../server/routes/telemetry_opt_in.ts | 42 ++++---- .../server/routes/telemetry_opt_in_stats.ts | 8 +- .../server/routes/telemetry_usage_stats.ts | 4 +- .../routes/telemetry_user_has_seen_notice.ts | 18 ++-- .../server/saved_objects/constants.ts | 10 ++ .../get_telemetry_saved_object.test.ts | 22 ++-- .../get_telemetry_saved_object.ts | 23 ++-- .../index.ts | 7 +- .../register_telemetry_saved_object.ts | 48 +++++++++ .../saved_objects}/types.ts | 2 +- .../update_telemetry_saved_object.ts | 13 ++- ..._telemetry_allow_changing_opt_in_status.ts | 14 +-- .../get_telemetry_failure_details.test.ts | 2 +- .../get_telemetry_failure_details.ts | 2 +- ...ry_notify_user_about_optin_default.test.ts | 4 +- ...lemetry_notify_user_about_optin_default.ts | 2 +- .../get_telemetry_opt_in.test.ts | 20 +--- .../telemetry_config/get_telemetry_opt_in.ts | 10 +- .../get_telemetry_send_usage_from.test.ts | 27 ++--- .../get_telemetry_send_usage_from.ts | 2 +- .../server/telemetry_config/index.ts | 16 +++ .../apis/telemetry/{index.js => index.ts} | 5 +- test/api_integration/apis/telemetry/opt_in.ts | 81 ++++++++------ .../apis/telemetry/telemetry_config.ts | 52 +++++++++ .../apis/telemetry/telemetry_last_reported.ts | 5 +- .../api_integration/apis/telemetry/opt_in.ts | 84 ++++++++------- 35 files changed, 474 insertions(+), 358 deletions(-) create mode 100644 src/plugins/telemetry/common/routes.ts create mode 100644 src/plugins/telemetry/server/routes/telemetry_config.ts create mode 100644 src/plugins/telemetry/server/saved_objects/constants.ts rename src/plugins/telemetry/server/{telemetry_repository => saved_objects}/get_telemetry_saved_object.test.ts (84%) rename src/plugins/telemetry/server/{telemetry_repository => saved_objects}/get_telemetry_saved_object.ts (51%) rename src/plugins/telemetry/server/{telemetry_repository => saved_objects}/index.ts (66%) create mode 100644 src/plugins/telemetry/server/saved_objects/register_telemetry_saved_object.ts rename src/plugins/telemetry/{common/telemetry_config => server/saved_objects}/types.ts (97%) rename src/plugins/telemetry/server/{telemetry_repository => saved_objects}/update_telemetry_saved_object.ts (63%) rename src/plugins/telemetry/{common => server}/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts (62%) rename src/plugins/telemetry/{common => server}/telemetry_config/get_telemetry_failure_details.test.ts (98%) rename src/plugins/telemetry/{common => server}/telemetry_config/get_telemetry_failure_details.ts (94%) rename src/plugins/telemetry/{common => server}/telemetry_config/get_telemetry_notify_user_about_optin_default.test.ts (97%) rename src/plugins/telemetry/{common => server}/telemetry_config/get_telemetry_notify_user_about_optin_default.ts (94%) rename src/plugins/telemetry/{common => server}/telemetry_config/get_telemetry_opt_in.test.ts (91%) rename src/plugins/telemetry/{common => server}/telemetry_config/get_telemetry_opt_in.ts (92%) rename src/plugins/telemetry/{common => server}/telemetry_config/get_telemetry_send_usage_from.test.ts (72%) rename src/plugins/telemetry/{common => server}/telemetry_config/get_telemetry_send_usage_from.ts (93%) create mode 100644 src/plugins/telemetry/server/telemetry_config/index.ts rename test/api_integration/apis/telemetry/{index.js => index.ts} (75%) create mode 100644 test/api_integration/apis/telemetry/telemetry_config.ts diff --git a/src/plugins/telemetry/common/routes.ts b/src/plugins/telemetry/common/routes.ts new file mode 100644 index 0000000000000..2161cb7dd5651 --- /dev/null +++ b/src/plugins/telemetry/common/routes.ts @@ -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; +} diff --git a/src/plugins/telemetry/common/telemetry_config/index.ts b/src/plugins/telemetry/common/telemetry_config/index.ts index b15475280fe85..8c87b828ae2b0 100644 --- a/src/plugins/telemetry/common/telemetry_config/index.ts +++ b/src/plugins/telemetry/common/telemetry_config/index.ts @@ -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, diff --git a/src/plugins/telemetry/public/plugin.ts b/src/plugins/telemetry/public/plugin.ts index c164f500155a1..d28868a6dd286 100644 --- a/src/plugins/telemetry/public/plugin.ts +++ b/src/plugins/telemetry/public/plugin.ts @@ -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) { this.currentKibanaVersion = initializerContext.env.packageInfo.version; @@ -169,7 +163,7 @@ export class TelemetryPlugin implements Plugin { - await this.refreshConfig(); + await this.refreshConfig(http); analytics.optIn({ global: { enabled: this.telemetryService!.isOptedIn } }); }); @@ -200,7 +194,6 @@ export class TelemetryPlugin implements Plugin { const isUnauthenticated = this.getIsUnauthenticated(http); if (isUnauthenticated) { @@ -229,7 +220,7 @@ export class TelemetryPlugin implements Plugin { - 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 { + 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 { - 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 { + const { allowChangingOptInStatus, optIn, sendUsageFrom, telemetryNotifyUserAboutOptInDefault } = + await http.get(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; - return attributes; - } catch (error) { - const errorCode = error[Symbol('SavedObjectsClientErrorCode')]; - if (errorCode === 'SavedObjectsClient/notFound') { - return null; - } - - if (errorCode === 'SavedObjectsClient/forbidden') { - return false; - } - - throw error; - } - } } diff --git a/src/plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts b/src/plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts index 185ac8be49b59..b5840a7b76a44 100644 --- a/src/plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts +++ b/src/plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts @@ -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 } diff --git a/src/plugins/telemetry/server/fetcher.ts b/src/plugins/telemetry/server/fetcher.ts index 210e0a37d6c1c..65f367c095998 100644 --- a/src/plugins/telemetry/server/fetcher.ts +++ b/src/plugins/telemetry/server/fetcher.ts @@ -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()); diff --git a/src/plugins/telemetry/server/plugin.ts b/src/plugins/telemetry/server/plugin.ts index c9354f5c5ebab..0660e69c7b6cb 100644 --- a/src/plugins/telemetry/server/plugin.ts +++ b/src/plugins/telemetry/server/plugin.ts @@ -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; } -type SavedObjectsRegisterType = CoreSetup['savedObjects']['registerType']; - export class TelemetryPlugin implements Plugin { private readonly logger: Logger; private readonly currentKibanaVersion: string; @@ -111,9 +115,9 @@ export class TelemetryPlugin implements Plugin(1); + private readonly savedObjectsInternalClient$ = new ReplaySubject(1); - private pluginStop$ = new ReplaySubject(1); + private readonly pluginStop$ = new ReplaySubject(1); private security?: SecurityPluginStart; @@ -189,7 +193,7 @@ export class TelemetryPlugin implements Plugin 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 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 this.savedObjectsInternalRepository; diff --git a/src/plugins/telemetry/server/routes/index.ts b/src/plugins/telemetry/server/routes/index.ts index da752fd4c836a..8b73c8d76c4ec 100644 --- a/src/plugins/telemetry/server/routes/index.ts +++ b/src/plugins/telemetry/server/routes/index.ts @@ -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); diff --git a/src/plugins/telemetry/server/routes/telemetry_config.ts b/src/plugins/telemetry/server/routes/telemetry_config.ts new file mode 100644 index 0000000000000..4bcf1d1f4c811 --- /dev/null +++ b/src/plugins/telemetry/server/routes/telemetry_config.ts @@ -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; + currentKibanaVersion: string; + savedObjectsInternalClient$: Observable; +} +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 }); + } + ); +} diff --git a/src/plugins/telemetry/server/routes/telemetry_last_reported.ts b/src/plugins/telemetry/server/routes/telemetry_last_reported.ts index 2b5ffbd7ec4d6..80037761895b2 100644 --- a/src/plugins/telemetry/server/routes/telemetry_last_reported.ts +++ b/src/plugins/telemetry/server/routes/telemetry_last_reported.ts @@ -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, diff --git a/src/plugins/telemetry/server/routes/telemetry_opt_in.ts b/src/plugins/telemetry/server/routes/telemetry_opt_in.ts index 06e443670518d..e26e8c596b53a 100644 --- a/src/plugins/telemetry/server/routes/telemetry_opt_in.ts +++ b/src/plugins/telemetry/server/routes/telemetry_opt_in.ts @@ -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({ diff --git a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts index 414835fcc0a0d..d8ec9d4922fc1 100644 --- a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts +++ b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts @@ -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( diff --git a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts index 345b1e72dce34..fd613e1318966 100644 --- a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts +++ b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts @@ -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'; diff --git a/src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts b/src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts index cb990b68fd9b5..7686aa4100755 100644 --- a/src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts +++ b/src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts @@ -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 }); } diff --git a/src/plugins/telemetry/server/saved_objects/constants.ts b/src/plugins/telemetry/server/saved_objects/constants.ts new file mode 100644 index 0000000000000..f31de462a885a --- /dev/null +++ b/src/plugins/telemetry/server/saved_objects/constants.ts @@ -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'; diff --git a/src/plugins/telemetry/server/telemetry_repository/get_telemetry_saved_object.test.ts b/src/plugins/telemetry/server/saved_objects/get_telemetry_saved_object.test.ts similarity index 84% rename from src/plugins/telemetry/server/telemetry_repository/get_telemetry_saved_object.test.ts rename to src/plugins/telemetry/server/saved_objects/get_telemetry_saved_object.test.ts index 379232e7571ea..f2beff7b88119 100644 --- a/src/plugins/telemetry/server/telemetry_repository/get_telemetry_saved_object.test.ts +++ b/src/plugins/telemetry/server/saved_objects/get_telemetry_saved_object.test.ts @@ -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); }); }); diff --git a/src/plugins/telemetry/server/telemetry_repository/get_telemetry_saved_object.ts b/src/plugins/telemetry/server/saved_objects/get_telemetry_saved_object.ts similarity index 51% rename from src/plugins/telemetry/server/telemetry_repository/get_telemetry_saved_object.ts rename to src/plugins/telemetry/server/saved_objects/get_telemetry_saved_object.ts index 7d22758b5ccae..5a1509a028f90 100644 --- a/src/plugins/telemetry/server/telemetry_repository/get_telemetry_saved_object.ts +++ b/src/plugins/telemetry/server/saved_objects/get_telemetry_saved_object.ts @@ -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; export const getTelemetrySavedObject: GetTelemetrySavedObject = async ( - repository: SavedObjectsClientContract + soClient: SavedObjectsClientContract ) => { try { - const { attributes } = await repository.get('telemetry', 'telemetry'); + const { attributes } = await soClient.get( + 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; diff --git a/src/plugins/telemetry/server/telemetry_repository/index.ts b/src/plugins/telemetry/server/saved_objects/index.ts similarity index 66% rename from src/plugins/telemetry/server/telemetry_repository/index.ts rename to src/plugins/telemetry/server/saved_objects/index.ts index 594b53259a65f..5638e7a2d194b 100644 --- a/src/plugins/telemetry/server/telemetry_repository/index.ts +++ b/src/plugins/telemetry/server/saved_objects/index.ts @@ -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'; diff --git a/src/plugins/telemetry/server/saved_objects/register_telemetry_saved_object.ts b/src/plugins/telemetry/server/saved_objects/register_telemetry_saved_object.ts new file mode 100644 index 0000000000000..20b2ab6093514 --- /dev/null +++ b/src/plugins/telemetry/server/saved_objects/register_telemetry_saved_object.ts @@ -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', + }, + }, + }, + }); +} diff --git a/src/plugins/telemetry/common/telemetry_config/types.ts b/src/plugins/telemetry/server/saved_objects/types.ts similarity index 97% rename from src/plugins/telemetry/common/telemetry_config/types.ts rename to src/plugins/telemetry/server/saved_objects/types.ts index 9dc59226b7091..8603804463b46 100644 --- a/src/plugins/telemetry/common/telemetry_config/types.ts +++ b/src/plugins/telemetry/server/saved_objects/types.ts @@ -17,4 +17,4 @@ export interface TelemetrySavedObjectAttributes { reportFailureVersion?: string; } -export type TelemetrySavedObject = TelemetrySavedObjectAttributes | null | false; +export type TelemetrySavedObject = TelemetrySavedObjectAttributes; diff --git a/src/plugins/telemetry/server/telemetry_repository/update_telemetry_saved_object.ts b/src/plugins/telemetry/server/saved_objects/update_telemetry_saved_object.ts similarity index 63% rename from src/plugins/telemetry/server/telemetry_repository/update_telemetry_saved_object.ts rename to src/plugins/telemetry/server/saved_objects/update_telemetry_saved_object.ts index 8ecb10d741bb9..dddb752484cd2 100644 --- a/src/plugins/telemetry/server/telemetry_repository/update_telemetry_saved_object.ts +++ b/src/plugins/telemetry/server/saved_objects/update_telemetry_saved_object.ts @@ -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, }); } diff --git a/src/plugins/telemetry/common/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts b/src/plugins/telemetry/server/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts similarity index 62% rename from src/plugins/telemetry/common/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts rename to src/plugins/telemetry/server/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts index ea465a168d500..f1716d566829e 100644 --- a/src/plugins/telemetry/common/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts +++ b/src/plugins/telemetry/server/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts @@ -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; } diff --git a/src/plugins/telemetry/common/telemetry_config/get_telemetry_failure_details.test.ts b/src/plugins/telemetry/server/telemetry_config/get_telemetry_failure_details.test.ts similarity index 98% rename from src/plugins/telemetry/common/telemetry_config/get_telemetry_failure_details.test.ts rename to src/plugins/telemetry/server/telemetry_config/get_telemetry_failure_details.test.ts index c93ba53230954..7acaa2a816318 100644 --- a/src/plugins/telemetry/common/telemetry_config/get_telemetry_failure_details.test.ts +++ b/src/plugins/telemetry/server/telemetry_config/get_telemetry_failure_details.test.ts @@ -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, diff --git a/src/plugins/telemetry/common/telemetry_config/get_telemetry_failure_details.ts b/src/plugins/telemetry/server/telemetry_config/get_telemetry_failure_details.ts similarity index 94% rename from src/plugins/telemetry/common/telemetry_config/get_telemetry_failure_details.ts rename to src/plugins/telemetry/server/telemetry_config/get_telemetry_failure_details.ts index 8cb8a643d10e5..d820171a9c667 100644 --- a/src/plugins/telemetry/common/telemetry_config/get_telemetry_failure_details.ts +++ b/src/plugins/telemetry/server/telemetry_config/get_telemetry_failure_details.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { TelemetrySavedObject } from './types'; +import type { TelemetrySavedObject } from '../saved_objects'; interface GetTelemetryFailureDetailsConfig { telemetrySavedObject: TelemetrySavedObject; diff --git a/src/plugins/telemetry/common/telemetry_config/get_telemetry_notify_user_about_optin_default.test.ts b/src/plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.test.ts similarity index 97% rename from src/plugins/telemetry/common/telemetry_config/get_telemetry_notify_user_about_optin_default.test.ts rename to src/plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.test.ts index 3701380d22d1f..db8f0a01646fb 100644 --- a/src/plugins/telemetry/common/telemetry_config/get_telemetry_notify_user_about_optin_default.test.ts +++ b/src/plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.test.ts @@ -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, }) diff --git a/src/plugins/telemetry/common/telemetry_config/get_telemetry_notify_user_about_optin_default.ts b/src/plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.ts similarity index 94% rename from src/plugins/telemetry/common/telemetry_config/get_telemetry_notify_user_about_optin_default.ts rename to src/plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.ts index 6a32b8b75720e..f59b5790bbf3d 100644 --- a/src/plugins/telemetry/common/telemetry_config/get_telemetry_notify_user_about_optin_default.ts +++ b/src/plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { TelemetrySavedObject } from './types'; +import type { TelemetrySavedObject } from '../saved_objects'; interface NotifyOpts { allowChangingOptInStatus: boolean; diff --git a/src/plugins/telemetry/common/telemetry_config/get_telemetry_opt_in.test.ts b/src/plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.test.ts similarity index 91% rename from src/plugins/telemetry/common/telemetry_config/get_telemetry_opt_in.test.ts rename to src/plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.test.ts index ede56688e0449..fc76c9fc0e3ec 100644 --- a/src/plugins/telemetry/common/telemetry_config/get_telemetry_opt_in.test.ts +++ b/src/plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.test.ts @@ -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 { diff --git a/src/plugins/telemetry/common/telemetry_config/get_telemetry_opt_in.ts b/src/plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.ts similarity index 92% rename from src/plugins/telemetry/common/telemetry_config/get_telemetry_opt_in.ts rename to src/plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.ts index cb2292af8f5c8..2cc02c15820e1 100644 --- a/src/plugins/telemetry/common/telemetry_config/get_telemetry_opt_in.ts +++ b/src/plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.ts @@ -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. diff --git a/src/plugins/telemetry/common/telemetry_config/get_telemetry_send_usage_from.test.ts b/src/plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.test.ts similarity index 72% rename from src/plugins/telemetry/common/telemetry_config/get_telemetry_send_usage_from.test.ts rename to src/plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.test.ts index d9c8b3b7df958..9dd78b972998b 100644 --- a/src/plugins/telemetry/common/telemetry_config/get_telemetry_send_usage_from.test.ts +++ b/src/plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.test.ts @@ -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, }; } diff --git a/src/plugins/telemetry/common/telemetry_config/get_telemetry_send_usage_from.ts b/src/plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.ts similarity index 93% rename from src/plugins/telemetry/common/telemetry_config/get_telemetry_send_usage_from.ts rename to src/plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.ts index 0886ace8b0a1e..032b0d59a04b1 100644 --- a/src/plugins/telemetry/common/telemetry_config/get_telemetry_send_usage_from.ts +++ b/src/plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { TelemetrySavedObject } from './types'; +import type { TelemetrySavedObject } from '../saved_objects'; interface GetTelemetryUsageFetcherConfig { configTelemetrySendUsageFrom: 'browser' | 'server'; diff --git a/src/plugins/telemetry/server/telemetry_config/index.ts b/src/plugins/telemetry/server/telemetry_config/index.ts new file mode 100644 index 0000000000000..74a1116c9d083 --- /dev/null +++ b/src/plugins/telemetry/server/telemetry_config/index.ts @@ -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'; diff --git a/test/api_integration/apis/telemetry/index.js b/test/api_integration/apis/telemetry/index.ts similarity index 75% rename from test/api_integration/apis/telemetry/index.js rename to test/api_integration/apis/telemetry/index.ts index 94ada69b93322..3afe1ef304b27 100644 --- a/test/api_integration/apis/telemetry/index.js +++ b/test/api_integration/apis/telemetry/index.ts @@ -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')); }); diff --git a/test/api_integration/apis/telemetry/opt_in.ts b/test/api_integration/apis/telemetry/opt_in.ts index cbd2745976c6a..8d0fb6725bacf 100644 --- a/test/api_integration/apis/telemetry/opt_in.ts +++ b/test/api_integration/apis/telemetry/opt_in.ts @@ -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 { +async function postTelemetryV2OptIn( + supertest: SuperTest.SuperTest, + value: unknown, + statusCode: number +): Promise { 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 { - return await supertest - .post('/api/saved_objects/telemetry/telemetry') - .query({ overwrite: true }) - .set('kbn-xsrf', 'xxx') - .send({ attributes }) - .expect(200); +): Promise { + // 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 { - const { body } = await supertest.get('/api/saved_objects/telemetry/telemetry').expect(200); - return body.attributes; +async function getSavedObjectAttributes(es: Client): Promise { + // 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 || {}; } diff --git a/test/api_integration/apis/telemetry/telemetry_config.ts b/test/api_integration/apis/telemetry/telemetry_config.ts new file mode 100644 index 0000000000000..57feb849c3468 --- /dev/null +++ b/test/api_integration/apis/telemetry/telemetry_config.ts @@ -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, + }); + }); + }); +} diff --git a/test/api_integration/apis/telemetry/telemetry_last_reported.ts b/test/api_integration/apis/telemetry/telemetry_last_reported.ts index d190726e6db0b..37097c4cbf2f5 100644 --- a/test/api_integration/apis/telemetry/telemetry_last_reported.ts +++ b/test/api_integration/apis/telemetry/telemetry_last_reported.ts @@ -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 () => { diff --git a/x-pack/test/api_integration/apis/telemetry/opt_in.ts b/x-pack/test/api_integration/apis/telemetry/opt_in.ts index dafdac7e3f219..33e977b118100 100644 --- a/x-pack/test/api_integration/apis/telemetry/opt_in.ts +++ b/x-pack/test/api_integration/apis/telemetry/opt_in.ts @@ -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 { +async function postTelemetryV2OptIn( + supertest: SuperTest.SuperTest, + value: unknown, + statusCode: number +): Promise { 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 { - return await supertest - .post('/api/saved_objects/telemetry/telemetry') - .query({ overwrite: true }) - .set('kbn-xsrf', 'xxx') - .send({ attributes }) - .expect(200); +): Promise { + // 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 { - const { body } = await supertest.get('/api/saved_objects/telemetry/telemetry').expect(200); - return body.attributes; +async function getSavedObjectAttributes(es: Client): Promise { + // 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 || {}; }