From ce14d99f3b748dd5e5a7d3275f7ad7437624bbc3 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Fri, 12 Apr 2024 16:52:07 +0100 Subject: [PATCH 1/4] [ML] Hides filebeat configuration card for Serverless Search file upload (#178987) Updates the text on the landing page **Before** ![image](https://github.com/elastic/kibana/assets/22172091/003a582f-4fe0-4d2a-a0dd-d40bd4ed57f5) **After** ![image](https://github.com/elastic/kibana/assets/22172091/75afae43-1855-4012-a728-488b6a3d3823) Adds a new feature flag to allow the `Create filebeat configuration` card to be hidden. This has been [requested](https://github.com/elastic/enterprise-search-team/issues/7075) for the serverless search project and so the `serverless.es.yml` has been updated to include this flag. **Before** ![image](https://github.com/elastic/kibana/assets/22172091/add1aa39-186d-4816-9f92-27fa2f6f0378) **After** ![image](https://github.com/elastic/kibana/assets/22172091/a9ab7243-2fae-48fc-a3a0-0b2b347630be) --- config/serverless.es.yml | 2 + .../components/tutorial_directory.js | 4 +- .../add_data/add_data_service.test.tsx | 12 +++--- .../services/add_data/add_data_service.ts | 4 +- .../test_suites/core_plugins/rendering.ts | 1 + x-pack/plugins/data_visualizer/common/app.ts | 17 +++++++++ .../data_visualizer/public/api/index.ts | 35 +++++++++++++----- .../results_links/results_links.tsx | 37 +++++++++++-------- .../about_panel/welcome_content.tsx | 2 +- .../file_contents/file_contents.tsx | 4 +- .../file_contents/use_text_parser.tsx | 10 +++-- .../file_data_visualizer_view.js | 1 + .../components/import_view/import_view.js | 1 + .../file_data_visualizer.tsx | 8 +++- .../index_data_visualizer.tsx | 2 +- .../plugins/data_visualizer/public/index.ts | 5 ++- .../lazy_load_bundle/component_wrapper.tsx | 9 ++++- .../public/lazy_load_bundle/index.ts | 9 ++--- .../plugins/data_visualizer/public/plugin.ts | 34 +++++++++++++---- .../data_visualizer/public/register_home.ts | 7 ++-- .../data_visualizer/server/config_schema.ts | 13 +++++++ .../plugins/data_visualizer/server/index.ts | 13 ++++++- .../plugins/data_visualizer/server/plugin.ts | 4 +- .../file_based/file_datavisualizer.tsx | 14 ++++++- 24 files changed, 178 insertions(+), 70 deletions(-) create mode 100644 x-pack/plugins/data_visualizer/common/app.ts create mode 100644 x-pack/plugins/data_visualizer/server/config_schema.ts diff --git a/config/serverless.es.yml b/config/serverless.es.yml index d60b55e8cf463..73b8c09e29125 100644 --- a/config/serverless.es.yml +++ b/config/serverless.es.yml @@ -41,3 +41,5 @@ xpack.ml.ad.enabled: false xpack.ml.dfa.enabled: false xpack.ml.nlp.enabled: true xpack.ml.compatibleModuleType: 'search' + +data_visualizer.resultLinks.fileBeat.enabled: false diff --git a/src/plugins/home/public/application/components/tutorial_directory.js b/src/plugins/home/public/application/components/tutorial_directory.js index c5a4c1b27fbab..3d2fd55d8bd54 100644 --- a/src/plugins/home/public/application/components/tutorial_directory.js +++ b/src/plugins/home/public/application/components/tutorial_directory.js @@ -40,10 +40,10 @@ class TutorialDirectoryUi extends React.Component { }), content: , }, - ...extraTabs.map(({ id, name, component: Component }) => ({ + ...extraTabs.map(({ id, name, getComponent }) => ({ id, name, - content: , + content: getComponent(), })), ]; diff --git a/src/plugins/home/public/services/add_data/add_data_service.test.tsx b/src/plugins/home/public/services/add_data/add_data_service.test.tsx index b04b80ac19eec..3792c21e11c29 100644 --- a/src/plugins/home/public/services/add_data/add_data_service.test.tsx +++ b/src/plugins/home/public/services/add_data/add_data_service.test.tsx @@ -14,16 +14,16 @@ describe('AddDataService', () => { test('allows multiple register directory header link calls', () => { const setup = new AddDataService().setup(); expect(() => { - setup.registerAddDataTab({ id: 'abc', name: 'a b c', component: () => 123 }); - setup.registerAddDataTab({ id: 'def', name: 'a b c', component: () => 456 }); + setup.registerAddDataTab({ id: 'abc', name: 'a b c', getComponent: () => 123 }); + setup.registerAddDataTab({ id: 'def', name: 'a b c', getComponent: () => 456 }); }).not.toThrow(); }); test('throws when same directory header link is registered twice', () => { const setup = new AddDataService().setup(); expect(() => { - setup.registerAddDataTab({ id: 'abc', name: 'a b c', component: () => 123 }); - setup.registerAddDataTab({ id: 'abc', name: 'a b c', component: () => 456 }); + setup.registerAddDataTab({ id: 'abc', name: 'a b c', getComponent: () => 123 }); + setup.registerAddDataTab({ id: 'abc', name: 'a b c', getComponent: () => 456 }); }).toThrow(); }); }); @@ -38,8 +38,8 @@ describe('AddDataService', () => { const service = new AddDataService(); const setup = service.setup(); const links = [ - { id: 'abc', name: 'a b c', component: () => 123 }, - { id: 'def', name: 'a b c', component: () => 456 }, + { id: 'abc', name: 'a b c', getComponent: () => 123 }, + { id: 'def', name: 'a b c', getComponent: () => 456 }, ]; setup.registerAddDataTab(links[0]); setup.registerAddDataTab(links[1]); diff --git a/src/plugins/home/public/services/add_data/add_data_service.ts b/src/plugins/home/public/services/add_data/add_data_service.ts index 668c373f8314d..951d41f4abd07 100644 --- a/src/plugins/home/public/services/add_data/add_data_service.ts +++ b/src/plugins/home/public/services/add_data/add_data_service.ts @@ -6,13 +6,11 @@ * Side Public License, v 1. */ -import React from 'react'; - /** @public */ export interface AddDataTab { id: string; name: string; - component: React.FC; + getComponent: () => JSX.Element; } export class AddDataService { diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 22066abf17d20..eaa0099213b49 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -124,6 +124,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'data.search.sessions.maxUpdateRetries (number)', 'data.search.sessions.notTouchedTimeout (duration)', 'data_views.scriptedFieldsEnabled (any)', // It's a boolean (any because schema.conditional) + 'data_visualizer.resultLinks.fileBeat.enabled (boolean)', 'dev_tools.deeplinks.navLinkStatus (string)', 'enterpriseSearch.canDeployEntSearch (boolean)', 'enterpriseSearch.host (string)', diff --git a/x-pack/plugins/data_visualizer/common/app.ts b/x-pack/plugins/data_visualizer/common/app.ts new file mode 100644 index 0000000000000..6dfd9085568af --- /dev/null +++ b/x-pack/plugins/data_visualizer/common/app.ts @@ -0,0 +1,17 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface ResultLinks { + fileBeat?: { + enabled: boolean; + }; +} +export type ResultLink = keyof ResultLinks; + +export interface ConfigSchema { + resultLinks?: ResultLinks; +} diff --git a/x-pack/plugins/data_visualizer/public/api/index.ts b/x-pack/plugins/data_visualizer/public/api/index.ts index 68c265e200f12..b7b9bcb59112b 100644 --- a/x-pack/plugins/data_visualizer/public/api/index.ts +++ b/x-pack/plugins/data_visualizer/public/api/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { ResultLinks } from '../../common/app'; import { lazyLoadModules } from '../lazy_load_bundle'; import type { DataDriftSpec, @@ -12,17 +13,31 @@ import type { IndexDataVisualizerSpec, } from '../application'; -export async function getFileDataVisualizerComponent(): Promise<() => FileDataVisualizerSpec> { - const modules = await lazyLoadModules(); - return () => modules.FileDataVisualizer; +export interface SpecWithLinks { + resultLinks: ResultLinks; + component: T; } -export async function getIndexDataVisualizerComponent(): Promise<() => IndexDataVisualizerSpec> { - const modules = await lazyLoadModules(); - return () => modules.IndexDataVisualizer; -} +export function getComponents(resultLinks: ResultLinks) { + async function getFileDataVisualizerComponent(): Promise< + () => SpecWithLinks + > { + const modules = await lazyLoadModules(resultLinks); + return () => ({ component: modules.FileDataVisualizer, resultLinks }); + } + + async function getIndexDataVisualizerComponent(): Promise<() => IndexDataVisualizerSpec> { + const modules = await lazyLoadModules(resultLinks); + return () => modules.IndexDataVisualizer; + } -export async function getDataDriftComponent(): Promise<() => DataDriftSpec> { - const modules = await lazyLoadModules(); - return () => modules.DataDrift; + async function getDataDriftComponent(): Promise<() => DataDriftSpec> { + const modules = await lazyLoadModules(resultLinks); + return () => modules.DataDrift; + } + return { + getFileDataVisualizerComponent, + getIndexDataVisualizerComponent, + getDataDriftComponent, + }; } diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/results_links/results_links.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/results_links/results_links.tsx index 74b7b5cde4c06..1cb916007e3c5 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/results_links/results_links.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/results_links/results_links.tsx @@ -16,6 +16,7 @@ import type { FindFileStructureResponse } from '@kbn/file-upload-plugin/common'; import type { FileUploadPluginStart } from '@kbn/file-upload-plugin/public'; import { flatten } from 'lodash'; import { isDefined } from '@kbn/ml-is-defined'; +import type { ResultLinks } from '../../../../../common/app'; import type { LinkCardProps } from '../link_card/link_card'; import { useDataVisualizerKibana } from '../../../kibana_context'; @@ -50,6 +51,7 @@ interface Props { createDataView: boolean; showFilebeatFlyout(): void; getAdditionalLinks?: GetAdditionalLinks; + resultLinks?: ResultLinks; } interface GlobalState { @@ -67,6 +69,7 @@ export const ResultsLinks: FC = ({ createDataView, showFilebeatFlyout, getAdditionalLinks, + resultLinks, }) => { const { services: { @@ -257,21 +260,25 @@ export const ResultsLinks: FC = ({ /> )} - - } - data-test-subj="fileDataVisFilebeatConfigLink" - title={ - - } - description="" - onClick={showFilebeatFlyout} - /> - + + {resultLinks?.fileBeat?.enabled === false ? null : ( + + } + data-test-subj="fileDataVisFilebeatConfigLink" + title={ + + } + description="" + onClick={showFilebeatFlyout} + /> + + )} + {Array.isArray(asyncHrefCards) && asyncHrefCards.map((link) => ( diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/welcome_content.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/welcome_content.tsx index cc7a66d138925..61681db6b3ef5 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/welcome_content.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/welcome_content.tsx @@ -49,7 +49,7 @@ export const WelcomeContent: FC = ({ hasPermissionToImport }) => {

diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_contents/file_contents.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_contents/file_contents.tsx index ca2ae25ce2c1b..ef57ab6204c6d 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_contents/file_contents.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_contents/file_contents.tsx @@ -142,10 +142,10 @@ export const FileContents: FC = ({ data, format, numberOfLines, semiStruc ) : ( <> {highlightedLines.map((line, i) => ( - <> + {line} {i === highlightedLines.length - 1 ? null : } - + ))} )} diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_contents/use_text_parser.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_contents/use_text_parser.tsx index 183f3ca727d3a..64684c7589499 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_contents/use_text_parser.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_contents/use_text_parser.tsx @@ -42,11 +42,15 @@ export function useGrokHighlighter() { return lines.map((line) => { const formattedWords: JSX.Element[] = []; - for (const { word, field } of line) { + for (let j = 0; j < line.length; j++) { + const { word, field } = line[j]; + const key = `word-${j}`; if (field) { - formattedWords.push(); + formattedWords.push( + + ); } else { - formattedWords.push({word}); + formattedWords.push({word}); } } return ( diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js index 6e8b6640d854d..e6435c554fc63 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js @@ -345,6 +345,7 @@ export class FileDataVisualizerView extends Component { dataStart={this.props.dataStart} fileUpload={this.props.fileUpload} getAdditionalLinks={this.props.getAdditionalLinks} + resultLinks={this.props.resultLinks} capabilities={this.props.capabilities} mode={mode} onChangeMode={this.changeMode} diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js index 51050f8e940bb..728b9fcbc6d75 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js @@ -620,6 +620,7 @@ export class ImportView extends Component { createDataView={createDataView} showFilebeatFlyout={this.showFilebeatFlyout} getAdditionalLinks={this.props.getAdditionalLinks ?? []} + resultLinks={this.props.resultLinks} /> {isFilebeatFlyoutVisible && ( diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx index 96dd80a7dbab0..03406ddb8045b 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx @@ -8,18 +8,21 @@ import '../_index.scss'; import type { FC } from 'react'; import React from 'react'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import type { ResultLinks } from '../../../common/app'; import { getCoreStart, getPluginsStart } from '../../kibana_services'; // @ts-ignore import { FileDataVisualizerView } from './components/file_data_visualizer_view'; import type { GetAdditionalLinks } from '../common/components/results_links'; -interface Props { +export interface Props { + resultLinks?: ResultLinks; getAdditionalLinks?: GetAdditionalLinks; } export type FileDataVisualizerSpec = typeof FileDataVisualizer; -export const FileDataVisualizer: FC = ({ getAdditionalLinks }) => { + +export const FileDataVisualizer: FC = ({ getAdditionalLinks, resultLinks }) => { const coreStart = getCoreStart(); const { data, maps, embeddable, discover, share, security, fileUpload, cloud, fieldFormats } = getPluginsStart(); @@ -48,6 +51,7 @@ export const FileDataVisualizer: FC = ({ getAdditionalLinks }) => { http={coreStart.http} fileUpload={fileUpload} getAdditionalLinks={getAdditionalLinks} + resultLinks={resultLinks} capabilities={coreStart.application.capabilities} /> diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx index c8c112baf94ae..f49e6e4e00768 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx @@ -273,7 +273,7 @@ const DataVisualizerStateContextProvider: FC ( @@ -20,14 +21,18 @@ const FileDataVisualizerComponent = React.lazy( () => import('../application/file_data_visualizer/file_data_visualizer') ); -export const FileDataVisualizerWrapper: FC = () => { +export const FileDataVisualizerWrapper: FC<{ resultLinks?: ResultLinks }> = ({ resultLinks }) => { return ( }> - + ); }; +export function getFileDataVisualizerWrapper(resultLinks?: ResultLinks) { + return ; +} + const DataDriftLazy = React.lazy(() => import('../application/data_drift')); /** diff --git a/x-pack/plugins/data_visualizer/public/lazy_load_bundle/index.ts b/x-pack/plugins/data_visualizer/public/lazy_load_bundle/index.ts index e018216bcf9e7..3396eb8d7ed90 100644 --- a/x-pack/plugins/data_visualizer/public/lazy_load_bundle/index.ts +++ b/x-pack/plugins/data_visualizer/public/lazy_load_bundle/index.ts @@ -5,13 +5,12 @@ * 2.0. */ -import type { HttpSetup } from '@kbn/core/public'; +import type { ResultLinks } from '../../common/app'; import type { DataDriftSpec, FileDataVisualizerSpec, IndexDataVisualizerSpec, } from '../application'; -import { getCoreStart } from '../kibana_services'; let loadModulesPromise: Promise; @@ -19,10 +18,10 @@ interface LazyLoadedModules { FileDataVisualizer: FileDataVisualizerSpec; IndexDataVisualizer: IndexDataVisualizerSpec; DataDrift: DataDriftSpec; - getHttp: () => HttpSetup; + resultsLinks: ResultLinks; } -export async function lazyLoadModules(): Promise { +export async function lazyLoadModules(resultsLinks: ResultLinks): Promise { if (typeof loadModulesPromise !== 'undefined') { return loadModulesPromise; } @@ -30,7 +29,7 @@ export async function lazyLoadModules(): Promise { loadModulesPromise = new Promise(async (resolve, reject) => { try { const lazyImports = await import('./lazy'); - resolve({ ...lazyImports, getHttp: () => getCoreStart().http }); + resolve({ ...lazyImports, resultsLinks }); } catch (error) { reject(error); } diff --git a/x-pack/plugins/data_visualizer/public/plugin.ts b/x-pack/plugins/data_visualizer/public/plugin.ts index d26c01272e7ef..98225774e2120 100644 --- a/x-pack/plugins/data_visualizer/public/plugin.ts +++ b/x-pack/plugins/data_visualizer/public/plugin.ts @@ -5,7 +5,12 @@ * 2.0. */ -import type { AnalyticsServiceStart, CoreSetup, CoreStart } from '@kbn/core/public'; +import type { + AnalyticsServiceStart, + CoreSetup, + CoreStart, + PluginInitializerContext, +} from '@kbn/core/public'; import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import type { CloudStart } from '@kbn/cloud-plugin/public'; import type { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; @@ -24,16 +29,13 @@ import type { LensPublicStart } from '@kbn/lens-plugin/public'; import type { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import { - getDataDriftComponent, - getFileDataVisualizerComponent, - getIndexDataVisualizerComponent, -} from './api'; +import { getComponents } from './api'; import { getMaxBytesFormatted } from './application/common/util/get_max_bytes'; import { registerHomeAddData, registerHomeFeatureCatalogue } from './register_home'; import { registerEmbeddables } from './application/index_data_visualizer/embeddables'; import { setStartServices } from './kibana_services'; import { IndexDataVisualizerLocatorDefinition } from './application/index_data_visualizer/locator'; +import type { ConfigSchema } from '../common/app'; export interface DataVisualizerSetupDependencies { home?: HomePublicPluginSetup; @@ -72,12 +74,25 @@ export class DataVisualizerPlugin DataVisualizerStartDependencies > { + private resultsLinks = { + fileBeat: { + enabled: true, + }, + }; + + constructor(initializerContext: PluginInitializerContext) { + const resultsLinks = initializerContext.config.get().resultLinks; + if (resultsLinks !== undefined) { + this.resultsLinks.fileBeat.enabled = resultsLinks.fileBeat?.enabled ?? true; + } + } + public setup( core: CoreSetup, plugins: DataVisualizerSetupDependencies ) { if (plugins.home) { - registerHomeAddData(plugins.home); + registerHomeAddData(plugins.home, this.resultsLinks); registerHomeFeatureCatalogue(plugins.home); } @@ -87,6 +102,11 @@ export class DataVisualizerPlugin public start(core: CoreStart, plugins: DataVisualizerStartDependencies) { setStartServices(core, plugins); + const { + getFileDataVisualizerComponent, + getIndexDataVisualizerComponent, + getDataDriftComponent, + } = getComponents(this.resultsLinks); return { getFileDataVisualizerComponent, getIndexDataVisualizerComponent, diff --git a/x-pack/plugins/data_visualizer/public/register_home.ts b/x-pack/plugins/data_visualizer/public/register_home.ts index ec7fd39762d08..c098d46607a61 100644 --- a/x-pack/plugins/data_visualizer/public/register_home.ts +++ b/x-pack/plugins/data_visualizer/public/register_home.ts @@ -7,21 +7,22 @@ import { i18n } from '@kbn/i18n'; import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; -import { FileDataVisualizerWrapper } from './lazy_load_bundle/component_wrapper'; +import { getFileDataVisualizerWrapper } from './lazy_load_bundle/component_wrapper'; import { featureTitle, FILE_DATA_VIS_TAB_ID, applicationPath, featureId, } from '../common/constants'; +import type { ResultLinks } from '../common/app'; -export function registerHomeAddData(home: HomePublicPluginSetup) { +export function registerHomeAddData(home: HomePublicPluginSetup, resultsLinks: ResultLinks) { home.addData.registerAddDataTab({ id: FILE_DATA_VIS_TAB_ID, name: i18n.translate('xpack.dataVisualizer.file.embeddedTabTitle', { defaultMessage: 'Upload file', }), - component: FileDataVisualizerWrapper, + getComponent: () => getFileDataVisualizerWrapper(resultsLinks), }); } diff --git a/x-pack/plugins/data_visualizer/server/config_schema.ts b/x-pack/plugins/data_visualizer/server/config_schema.ts new file mode 100644 index 0000000000000..d2b9bbc0d39bb --- /dev/null +++ b/x-pack/plugins/data_visualizer/server/config_schema.ts @@ -0,0 +1,13 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { schema } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + resultLinks: schema.maybe( + schema.object({ fileBeat: schema.maybe(schema.object({ enabled: schema.boolean() })) }) + ), +}); diff --git a/x-pack/plugins/data_visualizer/server/index.ts b/x-pack/plugins/data_visualizer/server/index.ts index 14d7868f0949b..67da3121900f4 100644 --- a/x-pack/plugins/data_visualizer/server/index.ts +++ b/x-pack/plugins/data_visualizer/server/index.ts @@ -5,9 +5,18 @@ * 2.0. */ -import type { PluginInitializerContext } from '@kbn/core/server'; +import type { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server'; +import type { ConfigSchema } from '../common/app'; +import { configSchema } from './config_schema'; -export const plugin = async (initializerContext: PluginInitializerContext) => { +export const plugin = async (initializerContext: PluginInitializerContext) => { const { DataVisualizerPlugin } = await import('./plugin'); return new DataVisualizerPlugin(initializerContext); }; + +export const config: PluginConfigDescriptor = { + schema: configSchema, + exposeToBrowser: { + resultLinks: true, + }, +}; diff --git a/x-pack/plugins/data_visualizer/server/plugin.ts b/x-pack/plugins/data_visualizer/server/plugin.ts index 0e70f756f9b21..7a279d6c01ef8 100644 --- a/x-pack/plugins/data_visualizer/server/plugin.ts +++ b/x-pack/plugins/data_visualizer/server/plugin.ts @@ -15,16 +15,16 @@ import type { import type { StartDeps, SetupDeps } from './types'; import { registerWithCustomIntegrations } from './register_custom_integration'; import { routes } from './routes'; +import type { ConfigSchema } from '../common/app'; export class DataVisualizerPlugin implements Plugin { private readonly _logger: Logger; - constructor(initializerContext: PluginInitializerContext) { + constructor(initializerContext: PluginInitializerContext) { this._logger = initializerContext.logger.get(); } setup(coreSetup: CoreSetup, plugins: SetupDeps) { - // home-plugin required if (plugins.home && plugins.customIntegrations) { registerWithCustomIntegrations(plugins.customIntegrations); } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx index 39b4d2328db98..168ae4a44656d 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx @@ -15,6 +15,7 @@ import type { GetAdditionalLinks, } from '@kbn/data-visualizer-plugin/public'; import { useTimefilter } from '@kbn/ml-date-picker'; +import type { ResultLinks } from '@kbn/data-visualizer-plugin/common/app'; import { HelpMenu } from '../../components/help_menu'; import { useMlKibana, useMlLocator } from '../../contexts/kibana'; @@ -39,6 +40,7 @@ export const FileDataVisualizerPage: FC = () => { getMlNodeCount(); const [FileDataVisualizer, setFileDataVisualizer] = useState(null); + const [resultLinks, setResultLinks] = useState(null); const getAdditionalLinks: GetAdditionalLinks = useMemo( () => [ @@ -100,10 +102,15 @@ export const FileDataVisualizerPage: FC = () => { ); useEffect(() => { + // ML uses this function if (dataVisualizer !== undefined) { getMlNodeCount(); const { getFileDataVisualizerComponent } = dataVisualizer; - getFileDataVisualizerComponent().then(setFileDataVisualizer); + getFileDataVisualizerComponent().then((resp) => { + const items = resp(); + setFileDataVisualizer(() => items.component); + setResultLinks(items.resultLinks); + }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -118,7 +125,10 @@ export const FileDataVisualizerPage: FC = () => { defaultMessage="Data Visualizer" /> - + ) : null} From 891358ff80795b58278391189944e20cf6904d2c Mon Sep 17 00:00:00 2001 From: Mykola Harmash Date: Fri, 12 Apr 2024 18:11:04 +0200 Subject: [PATCH 2/4] [Observability Onboarding] Add telemetry events for logs onboarding flows (#180439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/elastic/kibana/issues/179555 Closes https://github.com/elastic/kibana/issues/179786 Depends on: https://github.com/elastic/kibana/pull/180301 > [!NOTE] > The current flow in `main` is a bit broken and does not report the final completed status. Make sure to wait until the above PR is merged or cherry pick its commit before testing. ## Summary * Adds a new schema for the `observability_onboarding` event * Adds logic to trigger the event on the onboarding landing pages (old and new) * Adds logic to trigger the event during the system/custom logs flows when: user has downloaded the agent, when agent has reported it's status, in case of warning/errors and finally when the flow has been completed. ## How to test * Run run serverless Kibana localy * Set the new onboarding feature flag on or off depending on which one you want to test: ``` # kibana.dev.yml xpack.cloud_integrations.experiments.enabled: true xpack.cloud_integrations.experiments.flag_overrides: "observability_onboarding.experimental_onboarding_flow_enabled": true ``` * (Annoying workaround 🙈) In order to make Elastic Agent to communicate with ES over https, modify `outputs.default` configuration in `x-pack/plugins/observability_solution/observability_onboarding/common/elastic_agent_logs/system_logs/generate_system_logs_yml.ts` and `x-pack/plugins/observability_solution/observability_onboarding/common/elastic_agent_logs/custom_logs/generate_custom_logs_yml.ts` to use your local Kiabana SSL certificate: ``` outputs: { default: { ... ssl: { enabled: true, certificate_authorities: [ // Replace with you local path to Kibana repo '/Users/mykolaharmash/Developer/kibana/packages/kbn-dev-utils/certs/ca.crt', ], }, } } ``` * Go trough the onboarding flow and make sure you see `/kibana-browser` requests in the "Network" with the correct payload. --------- Co-authored-by: Thom Heymann <190132+thomheymann@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/logs_flow_progress_step_id.ts | 15 ++++ .../common/telemetry_events.ts | 57 ++++++++++++ .../public/application/app.tsx | 23 +++-- .../experimental_onboarding_flow.tsx | 1 + .../custom_logs/install_elastic_agent.tsx | 3 + .../system_logs/install_elastic_agent.tsx | 3 + .../app/custom_logs/install_elastic_agent.tsx | 7 +- .../app/system_logs/install_elastic_agent.tsx | 7 +- .../shared/install_elastic_agent_steps.tsx | 14 +-- .../experimental_onboarding_enabled.ts | 10 +++ .../hooks/use_experimental_onboarding_flag.ts | 13 +++ .../use_flow_progress_telemetry.test.tsx | 87 +++++++++++++++++++ .../hooks/use_flow_progress_telemetry.ts | 73 ++++++++++++++++ .../observability_onboarding/public/plugin.ts | 3 + .../observability_onboarding/tsconfig.json | 3 +- 15 files changed, 299 insertions(+), 20 deletions(-) create mode 100644 x-pack/plugins/observability_solution/observability_onboarding/common/logs_flow_progress_step_id.ts create mode 100644 x-pack/plugins/observability_solution/observability_onboarding/common/telemetry_events.ts create mode 100644 x-pack/plugins/observability_solution/observability_onboarding/public/context/experimental_onboarding_enabled.ts create mode 100644 x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_experimental_onboarding_flag.ts create mode 100644 x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_flow_progress_telemetry.test.tsx create mode 100644 x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_flow_progress_telemetry.ts diff --git a/x-pack/plugins/observability_solution/observability_onboarding/common/logs_flow_progress_step_id.ts b/x-pack/plugins/observability_solution/observability_onboarding/common/logs_flow_progress_step_id.ts new file mode 100644 index 0000000000000..d0ebd89f395eb --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/common/logs_flow_progress_step_id.ts @@ -0,0 +1,15 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type EaInstallProgressStepId = + | 'ea-download' + | 'ea-extract' + | 'ea-install' + | 'ea-status' + | 'ea-config'; + +export type LogsFlowProgressStepId = EaInstallProgressStepId | 'logs-ingest'; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/common/telemetry_events.ts b/x-pack/plugins/observability_solution/observability_onboarding/common/telemetry_events.ts new file mode 100644 index 0000000000000..3cf24603c2bd1 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/common/telemetry_events.ts @@ -0,0 +1,57 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { type EventTypeOpts } from '@kbn/analytics-client'; + +export const OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT: EventTypeOpts<{ + flow?: string; + step?: string; + step_status?: string; + step_message?: string; + uses_legacy_onboarding_page: boolean; +}> = { + eventType: 'observability_onboarding', + schema: { + flow: { + type: 'keyword', + _meta: { + description: + "The current onboarding flow user is going through (e.g. 'system_logs', 'nginx'). If not present, user is on the landing screen.", + optional: true, + }, + }, + step: { + type: 'keyword', + _meta: { + description: 'The current step in the onboarding flow.', + optional: true, + }, + }, + step_status: { + type: 'keyword', + _meta: { + description: 'The status of the step in the onboarding flow.', + optional: true, + }, + }, + step_message: { + type: 'keyword', + _meta: { + description: + 'Error or warning message of the current step in the onboarding flow', + optional: true, + }, + }, + uses_legacy_onboarding_page: { + type: 'boolean', + _meta: { + description: + 'Whether the user is using the legacy onboarding page or the new one', + }, + }, + }, +}; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/app.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/app.tsx index 0301f2b33c2cc..d02dd2f06cc3b 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/app.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/app.tsx @@ -24,6 +24,7 @@ import { Router, Routes, Route } from '@kbn/shared-ux-router'; import { euiDarkVars, euiLightVars } from '@kbn/ui-theme'; import React from 'react'; import ReactDOM from 'react-dom'; +import { OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT } from '../../common/telemetry_events'; import { ConfigSchema } from '..'; import { customLogsRoutes } from '../components/app/custom_logs'; import { systemLogsRoutes } from '../components/app/system_logs'; @@ -36,6 +37,7 @@ import { baseRoutes, routes } from '../routes'; import { CustomLogs } from '../routes/templates/custom_logs'; import { SystemLogs } from '../routes/templates/system_logs'; import { ExperimentalOnboardingFlow } from './experimental_onboarding_flow'; +import { ExperimentalOnboardingFeatureFlag } from '../context/experimental_onboarding_enabled'; export const onBoardingTitle = i18n.translate( 'xpack.observability_onboarding.breadcrumbs.onboarding', @@ -145,6 +147,13 @@ export function ObservabilityOnboardingAppRoot({ const renderFeedbackLinkAsPortal = !config.serverless.enabled; + core.analytics.reportEvent( + OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT.eventType, + { + uses_legacy_onboarding_page: !experimentalOnboardingFlowEnabled, + } + ); + return (
)} - {experimentalOnboardingFlowEnabled ? ( - - ) : ( - - )} + + {experimentalOnboardingFlowEnabled ? ( + + ) : ( + + )} + diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/experimental_onboarding_flow.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/experimental_onboarding_flow.tsx index 973641f93cc19..13d0783c27fac 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/experimental_onboarding_flow.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/experimental_onboarding_flow.tsx @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import { i18n } from '@kbn/i18n'; import React from 'react'; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/custom_logs/install_elastic_agent.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/custom_logs/install_elastic_agent.tsx index 768fe8e1e6337..4dbe37201651f 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/custom_logs/install_elastic_agent.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/custom_logs/install_elastic_agent.tsx @@ -35,6 +35,7 @@ import { StepModal } from '../shared/step_panel'; import { ApiKeyBanner } from './api_key_banner'; import { WindowsInstallStep } from '../shared/windows_install_step'; import { TroubleshootingLink } from '../shared/troubleshooting_link'; +import { useFlowProgressTelemetry } from '../../../hooks/use_flow_progress_telemetry'; const defaultDatasetName = ''; @@ -215,6 +216,8 @@ export function InstallElasticAgent() { } }, [progressSucceded, refetchProgress]); + useFlowProgressTelemetry(progressData?.progress, 'custom_logs'); + const getCheckLogsStep = useCallback(() => { const progress = progressData?.progress; if (progress) { diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/system_logs/install_elastic_agent.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/system_logs/install_elastic_agent.tsx index 24f8c2c25e7df..7674206c77a34 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/system_logs/install_elastic_agent.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/system_logs/install_elastic_agent.tsx @@ -35,6 +35,7 @@ import { SystemIntegrationBanner, SystemIntegrationBannerState, } from './system_integration_banner'; +import { useFlowProgressTelemetry } from '../../../hooks/use_flow_progress_telemetry'; export function InstallElasticAgent() { const { @@ -174,6 +175,8 @@ export function InstallElasticAgent() { } }, [progressSucceded, refetchProgress]); + useFlowProgressTelemetry(progressData?.progress, 'system_logs'); + const getCheckLogsStep = useCallback(() => { const progress = progressData?.progress; if (progress) { diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/components/app/custom_logs/install_elastic_agent.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/components/app/custom_logs/install_elastic_agent.tsx index 1ce1611f6b128..1ea70146eee64 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/components/app/custom_logs/install_elastic_agent.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/components/app/custom_logs/install_elastic_agent.tsx @@ -19,6 +19,8 @@ import { SingleDatasetLocatorParams, SINGLE_DATASET_LOCATOR_ID, } from '@kbn/deeplinks-observability/locators'; +import { EaInstallProgressStepId } from '../../../../common/logs_flow_progress_step_id'; +import { useFlowProgressTelemetry } from '../../../hooks/use_flow_progress_telemetry'; import { ObservabilityOnboardingPluginSetupDeps } from '../../../plugin'; import { useWizard } from '.'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; @@ -28,7 +30,6 @@ import { } from '../../shared/get_elastic_agent_setup_command'; import { InstallElasticAgentSteps, - ProgressStepId, EuiStepStatus, } from '../../shared/install_elastic_agent_steps'; import { @@ -220,6 +221,8 @@ export function InstallElasticAgent() { } }, [progressSucceded, refetchProgress]); + useFlowProgressTelemetry(progressData?.progress, 'custom_logs'); + const getCheckLogsStep = useCallback(() => { const progress = progressData?.progress; if (progress) { @@ -372,7 +375,7 @@ export function InstallElasticAgent() { installProgressSteps={ (progressData?.progress ?? {}) as Partial< Record< - ProgressStepId, + EaInstallProgressStepId, { status: EuiStepStatus; message?: string } > > diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/components/app/system_logs/install_elastic_agent.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/components/app/system_logs/install_elastic_agent.tsx index 1112272ad5918..b694e8242b04b 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/components/app/system_logs/install_elastic_agent.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/components/app/system_logs/install_elastic_agent.tsx @@ -22,6 +22,8 @@ import { import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { default as React, useCallback, useEffect, useState } from 'react'; +import { EaInstallProgressStepId } from '../../../../common/logs_flow_progress_step_id'; +import { useFlowProgressTelemetry } from '../../../hooks/use_flow_progress_telemetry'; import { useWizard } from '.'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { ObservabilityOnboardingPluginSetupDeps } from '../../../plugin'; @@ -33,7 +35,6 @@ import { import { EuiStepStatus, InstallElasticAgentSteps, - ProgressStepId, } from '../../shared/install_elastic_agent_steps'; import { StepPanel, @@ -186,6 +187,8 @@ export function InstallElasticAgent() { } }, [progressSucceded, refetchProgress]); + useFlowProgressTelemetry(progressData?.progress, 'system_logs'); + const getCheckLogsStep = useCallback(() => { const progress = progressData?.progress; if (progress) { @@ -326,7 +329,7 @@ export function InstallElasticAgent() { installProgressSteps={ (progressData?.progress ?? {}) as Partial< Record< - ProgressStepId, + EaInstallProgressStepId, { status: EuiStepStatus; message?: string } > > diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/components/shared/install_elastic_agent_steps.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/components/shared/install_elastic_agent_steps.tsx index a0847f34fc1a8..70fe6ad322ed9 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/components/shared/install_elastic_agent_steps.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/components/shared/install_elastic_agent_steps.tsx @@ -26,17 +26,11 @@ import { Buffer } from 'buffer'; import React, { ReactNode } from 'react'; import { intersection } from 'lodash'; import { FormattedMessage } from '@kbn/i18n-react'; +import { EaInstallProgressStepId } from '../../../common/logs_flow_progress_step_id'; import { StepStatus } from './step_status'; export type EuiStepStatus = EuiStepsProps['steps'][number]['status']; -export type ProgressStepId = - | 'ea-download' - | 'ea-extract' - | 'ea-install' - | 'ea-status' - | 'ea-config'; - interface Props { installAgentPlatformOptions: Array<{ label: string; @@ -53,7 +47,7 @@ interface Props { installAgentStatus: EuiStepStatus; showInstallProgressSteps: boolean; installProgressSteps: Partial< - Record + Record >; configureAgentStatus: EuiStepStatus; configureAgentYaml: string; @@ -343,7 +337,7 @@ export function InstallElasticAgentSteps({ } function getStep( - id: ProgressStepId, + id: EaInstallProgressStepId, installProgressSteps: Props['installProgressSteps'], configPath: string ): { title: string; status: EuiStepStatus; message?: string } { @@ -374,7 +368,7 @@ function getStep( const PROGRESS_STEP_TITLES: ( configPath: string ) => Record< - ProgressStepId, + EaInstallProgressStepId, Record<'incompleteTitle' | 'loadingTitle' | 'completedTitle', string> > = (configPath: string) => ({ 'ea-download': { diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/context/experimental_onboarding_enabled.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/context/experimental_onboarding_enabled.ts new file mode 100644 index 0000000000000..8e817d992ab25 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/context/experimental_onboarding_enabled.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createContext } from 'react'; + +export const ExperimentalOnboardingFeatureFlag = createContext(false); diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_experimental_onboarding_flag.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_experimental_onboarding_flag.ts new file mode 100644 index 0000000000000..ac0451b996d68 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_experimental_onboarding_flag.ts @@ -0,0 +1,13 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useContext } from 'react'; +import { ExperimentalOnboardingFeatureFlag } from '../context/experimental_onboarding_enabled'; + +export const useExperimentalOnboardingFlag = () => { + return useContext(ExperimentalOnboardingFeatureFlag); +}; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_flow_progress_telemetry.test.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_flow_progress_telemetry.test.tsx new file mode 100644 index 0000000000000..716e36d0b548a --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_flow_progress_telemetry.test.tsx @@ -0,0 +1,87 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { useFlowProgressTelemetry } from './use_flow_progress_telemetry'; +import { useKibana } from './use_kibana'; + +jest.mock('./use_kibana', () => { + return { + useKibana: jest.fn().mockReturnValue({ + ...jest.requireActual('./use_kibana'), + services: { + analytics: { reportEvent: jest.fn() }, + }, + }), + }; +}); + +describe('useFlowProgressTelemetry', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('does not trigger an event if there is no progress', () => { + const render = renderHook(() => ({ + analytics: useKibana().services.analytics, + flowProgress: useFlowProgressTelemetry(undefined, 'test-flow'), + })); + + expect(render.result.current.analytics.reportEvent).not.toHaveBeenCalled(); + }); + + it('triggers an event when there is a progress change', () => { + const render = renderHook(() => ({ + analytics: useKibana().services.analytics, + flowProgress: useFlowProgressTelemetry( + { 'ea-download': { status: 'complete' } }, + 'test-flow' + ), + })); + + expect(render.result.current.analytics.reportEvent).toHaveBeenCalledTimes( + 1 + ); + expect(render.result.current.analytics.reportEvent).toHaveBeenCalledWith( + 'observability_onboarding', + { + uses_legacy_onboarding_page: true, + flow: 'test-flow', + step: 'ea-download', + step_status: 'complete', + } + ); + }); + + it('does not trigger an event for unsupported steps', () => { + const render = renderHook(() => ({ + analytics: useKibana().services.analytics, + flowProgress: useFlowProgressTelemetry( + { 'ea-extract': { status: 'complete' } }, + 'test-flow' + ), + })); + + expect(render.result.current.analytics.reportEvent).not.toHaveBeenCalled(); + }); + + it('does not trigger an event if the status of a step has not changed', () => { + const render = renderHook(() => ({ + analytics: useKibana().services.analytics, + flowProgress: useFlowProgressTelemetry( + { 'ea-download': { status: 'complete' } }, + 'test-flow' + ), + })); + + render.rerender(); + + expect(render.result.current.analytics.reportEvent).toHaveBeenCalledTimes( + 1 + ); + }); +}); diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_flow_progress_telemetry.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_flow_progress_telemetry.ts new file mode 100644 index 0000000000000..e9018ee01e3d4 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_flow_progress_telemetry.ts @@ -0,0 +1,73 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect, useState } from 'react'; +import { type LogsFlowProgressStepId } from '../../common/logs_flow_progress_step_id'; +import { OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT } from '../../common/telemetry_events'; +import { type EuiStepStatus } from '../components/shared/install_elastic_agent_steps'; +import { useExperimentalOnboardingFlag } from './use_experimental_onboarding_flag'; +import { useKibana } from './use_kibana'; + +type StepsProgress = Partial< + Record +>; + +const TRACKED_STEPS: LogsFlowProgressStepId[] = [ + 'ea-download', + 'ea-status', + 'logs-ingest', +]; +const TRACKED_STATUSES: EuiStepStatus[] = ['danger', 'warning', 'complete']; + +export function useFlowProgressTelemetry( + progress: StepsProgress | undefined, + flowId: string +) { + const { + services: { analytics }, + } = useKibana(); + const experimentalOnboardingFlowEnabled = useExperimentalOnboardingFlag(); + const [previousReportedSteps] = useState< + Map + >(new Map()); + + useEffect(() => { + if (!progress) { + return; + } + + TRACKED_STEPS.forEach((stepId) => { + const step = progress[stepId]; + + if ( + !step || + !TRACKED_STATUSES.includes(step.status) || + previousReportedSteps.get(stepId) === step.status + ) { + return; + } + + analytics.reportEvent( + OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT.eventType, + { + uses_legacy_onboarding_page: !experimentalOnboardingFlowEnabled, + flow: flowId, + step: stepId, + step_status: step.status, + step_message: step.message, + } + ); + previousReportedSteps.set(stepId, step.status); + }); + }, [ + analytics, + experimentalOnboardingFlowEnabled, + flowId, + progress, + previousReportedSteps, + ]); +} diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/plugin.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/plugin.ts index 1206d5087129a..dfdab0f3a5e44 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/plugin.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/plugin.ts @@ -29,6 +29,7 @@ import { PLUGIN_ID } from '../common'; import { ObservabilityOnboardingLocatorDefinition } from './locators/onboarding_locator/locator_definition'; import { ObservabilityOnboardingPluginLocators } from './locators'; import { ConfigSchema } from '.'; +import { OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT } from '../common/telemetry_events'; export type ObservabilityOnboardingPluginSetup = void; export type ObservabilityOnboardingPluginStart = void; @@ -164,6 +165,8 @@ export class ObservabilityOnboardingPlugin ), }; + core.analytics.registerEventType(OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT); + return { locators: this.locators, }; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/tsconfig.json b/x-pack/plugins/observability_solution/observability_onboarding/tsconfig.json index e88042defc687..52bf9a32fd8e1 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_onboarding/tsconfig.json @@ -35,7 +35,8 @@ "@kbn/deeplinks-observability", "@kbn/fleet-plugin", "@kbn/shared-ux-link-redirect-app", - "@kbn/cloud-experiments-plugin" + "@kbn/cloud-experiments-plugin", + "@kbn/analytics-client" ], "exclude": ["target/**/*"] } From d719c89190c37458bc54946af21a36a2a658ccb5 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 12 Apr 2024 11:43:34 -0500 Subject: [PATCH 3/4] [timelion] Fix load_functions on Windows (#169587) Fixes https://github.com/elastic/kibana/issues/168605 Testing - see https://github.com/elastic/kibana/issues/168605#issuecomment-1769347395 1) Install Kibana under `C:\Program Files (x86)`, or a path spaces and parentheses. Test builds are available in Buildkite on the artifacts tab on the `Build Kibana Distribution and Plugins` step. 2) Create a Timelion visualization 3) `.es(*)` should load --- .../vis_types/timelion/server/lib/load_functions.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/vis_types/timelion/server/lib/load_functions.js b/src/plugins/vis_types/timelion/server/lib/load_functions.js index 8329623261d20..70014be1816f4 100644 --- a/src/plugins/vis_types/timelion/server/lib/load_functions.js +++ b/src/plugins/vis_types/timelion/server/lib/load_functions.js @@ -8,8 +8,6 @@ import _ from 'lodash'; import globby from 'globby'; -import path from 'path'; -import normalizePath from 'normalize-path'; import processFunctionDefinition from './process_function_definition'; export default function (directory) { @@ -20,7 +18,7 @@ export default function (directory) { // Get a list of all files and use the filename as the object key const files = _.map( globby - .sync(normalizePath(path.resolve(__dirname, '../' + directory + '/*.js'))) + .sync('../' + directory + '/*.js', { cwd: __dirname }) .filter((filename) => !filename.includes('.test')), function (file) { const name = file.substring(file.lastIndexOf('/') + 1, file.lastIndexOf('.')); @@ -30,7 +28,9 @@ export default function (directory) { // Get a list of all directories with an index.js, use the directory name as the key in the object const directories = _.chain( - globby.sync(normalizePath(path.resolve(__dirname, '../' + directory + '/*/index.js'))) + globby.sync('../' + directory + '/*/index.js', { + cwd: __dirname, + }) ) .map(function (file) { const parts = file.split('/'); From 73d853300feb64894e908a11cd16e3159a941f20 Mon Sep 17 00:00:00 2001 From: Maxim Kholod Date: Fri, 12 Apr 2024 18:58:44 +0200 Subject: [PATCH 4/4] [Cloud Security] use as const for const dicts for TS to infer correct types (#180566) ## Summary using `as const` on exported const objects to allow TS infer the narrowest possible types Follow up on the CR comment - https://github.com/elastic/kibana/pull/179031#discussion_r1549592678 --- .../aws_credentials_form/aws_credentials_form.tsx | 3 ++- .../azure_credentials_form/azure_credentials_form.tsx | 2 +- .../gcp_credentials_form/gcp_credential_form.tsx | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form.tsx index 009d7fc7a1473..745540d5a4b0b 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form.tsx @@ -44,7 +44,7 @@ export type SetupFormat = typeof AWS_SETUP_FORMAT.CLOUD_FORMATION | typeof AWS_S export const AWS_SETUP_FORMAT = { CLOUD_FORMATION: 'cloud_formation', MANUAL: 'manual', -}; +} as const; export const AWS_CREDENTIALS_TYPE = { ASSUME_ROLE: 'assume_role', @@ -53,6 +53,7 @@ export const AWS_CREDENTIALS_TYPE = { SHARED_CREDENTIALS: 'shared_credentials', CLOUD_FORMATION: 'cloud_formation', } as const; + export const AWSSetupInfoContent = ({ info }: AWSSetupInfoContentProps) => { return ( <> diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form.tsx index 18d76c9e7b90f..1bf6b72cf1417 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form.tsx @@ -45,7 +45,7 @@ export type SetupFormat = typeof AZURE_SETUP_FORMAT.ARM_TEMPLATE | typeof AZURE_ export const AZURE_SETUP_FORMAT = { ARM_TEMPLATE: 'arm_template', MANUAL: 'manual', -}; +} as const; export const AZURE_CREDENTIALS_TYPE = { ARM_TEMPLATE: 'arm_template', diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credential_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credential_form.tsx index 8e78a606aa71c..b31f00c21c867 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credential_form.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credential_form.tsx @@ -46,15 +46,16 @@ import { export const GCP_SETUP_ACCESS = { CLOUD_SHELL: 'google_cloud_shell', MANUAL: 'manual', -}; +} as const; export const GCP_CREDENTIALS_TYPE = { CREDENTIALS_FILE: 'credentials-file', CREDENTIALS_JSON: 'credentials-json', CREDENTIALS_NONE: 'credentials-none', -}; +} as const; type SetupFormatGCP = typeof GCP_SETUP_ACCESS.CLOUD_SHELL | typeof GCP_SETUP_ACCESS.MANUAL; + export const GCPSetupInfoContent = () => ( <>