From e923dca1f4086baff2043791b925c3c2bb8d0bc8 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Mon, 7 Oct 2024 13:09:08 -0700 Subject: [PATCH 01/45] [OpenAPI][DOCS] Edit role and space tags (#194888) --- .../overlays/kibana.overlays.serverless.yaml | 5 +++++ oas_docs/overlays/kibana.overlays.yaml | 22 ++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/oas_docs/overlays/kibana.overlays.serverless.yaml b/oas_docs/overlays/kibana.overlays.serverless.yaml index b4a37c1aec846..64040383ae38c 100644 --- a/oas_docs/overlays/kibana.overlays.serverless.yaml +++ b/oas_docs/overlays/kibana.overlays.serverless.yaml @@ -54,6 +54,11 @@ actions: description: Change displayName update: x-displayName: "Service level objectives" + - target: '$.tags[?(@.name=="spaces")]' + description: Change displayName + update: + x-displayName: "Spaces" + description: Manage your Kibana spaces. - target: '$.tags[?(@.name=="system")]' description: Change displayName and description update: diff --git a/oas_docs/overlays/kibana.overlays.yaml b/oas_docs/overlays/kibana.overlays.yaml index 7464f1899d8f2..c4747d7d13a0a 100644 --- a/oas_docs/overlays/kibana.overlays.yaml +++ b/oas_docs/overlays/kibana.overlays.yaml @@ -23,7 +23,7 @@ actions: If you use the Kibana console to send API requests, it automatically adds the appropriate space identifier. - To learn more, check out [Spaces](https://www.elastic.co/guide/en/kibana/current/xpack-spaces.html). + To learn more, check out [Spaces](https://www.elastic.co/guide/en/kibana/master/xpack-spaces.html). # Add some tag descriptions and displayNames - target: '$.tags[?(@.name=="alerting")]' description: Change tag description and displayName @@ -34,7 +34,7 @@ actions: Actions typically involve the use of connectors to interact with Kibana services or third party integrations. externalDocs: description: Alerting documentation - url: https://www.elastic.co/guide/en/kibana/current/alerting-getting-started.html + url: https://www.elastic.co/guide/en/kibana/master/alerting-getting-started.html x-displayName: "Alerting" - target: '$.tags[?(@.name=="cases")]' description: Change tag description and displayName @@ -45,7 +45,7 @@ actions: You can also send cases to external incident management systems by configuring connectors. externalDocs: description: Cases documentation - url: https://www.elastic.co/guide/en/kibana/current/cases.html + url: https://www.elastic.co/guide/en/kibana/master/cases.html x-displayName: "Cases" - target: '$.tags[?(@.name=="connectors")]' description: Change tag description and displayName @@ -65,10 +65,26 @@ actions: description: Change displayName update: x-displayName: "Machine learning" + - target: '$.tags[?(@.name=="roles")]' + description: Change displayName and description + update: + x-displayName: "Roles" + description: Manage the roles that grant Elasticsearch and Kibana privileges. + externalDocs: + description: Kibana role management + url: https://www.elastic.co/guide/en/kibana/master/kibana-role-management.html - target: '$.tags[?(@.name=="slo")]' description: Change displayName update: x-displayName: "Service level objectives" + - target: '$.tags[?(@.name=="spaces")]' + description: Change displayName + update: + x-displayName: "Spaces" + description: Manage your Kibana spaces. + externalDocs: + url: https://www.elastic.co/guide/en/kibana/master/xpack-spaces.html + description: Space overview - target: '$.tags[?(@.name=="system")]' description: Change displayName and description update: From 94aa9151695ec4d7a001195aa10cc25ea6e2d282 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 7 Oct 2024 22:19:08 +0200 Subject: [PATCH 02/45] github-actions: notify github commands for all the observability code (#195219) --- .github/workflows/oblt-github-commands.yml | 30 +++++----------------- renovate.json | 1 + 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/.github/workflows/oblt-github-commands.yml b/.github/workflows/oblt-github-commands.yml index d3f4bd61b817e..443c0fa5f9071 100644 --- a/.github/workflows/oblt-github-commands.yml +++ b/.github/workflows/oblt-github-commands.yml @@ -8,39 +8,21 @@ name: oblt-github-commands on: - pull_request_target: + pull_request: types: - - opened + - labeled permissions: contents: read + pull-requests: write jobs: - comment-if-oblt-member: + comment: + if: ${{ github.event.label.name == 'ci:project-deploy-observability' }} runs-on: ubuntu-latest steps: - - uses: elastic/apm-pipeline-library/.github/actions/github-token@current + - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: - url: ${{ secrets.OBLT_VAULT_ADDR }} - roleId: ${{ secrets.OBLT_VAULT_ROLE_ID }} - secretId: ${{ secrets.OBLT_VAULT_SECRET_ID }} - - - id: is_team_member - name: Check if user is member of the Elastic org and Observability team - run: | - if gh api -H "Accept: application/vnd.github+json" \ - /orgs/elastic/teams/observability/memberships/${{ github.actor }} ; then - echo "result=true" >> $GITHUB_OUTPUT - else - echo "result=false" >> $GITHUB_OUTPUT - fi - env: - GH_TOKEN: ${{ env.GITHUB_TOKEN }} - - - if: ${{ steps.is_team_member.outputs.result == 'true' }} - uses: actions/github-script@v6 - with: - github-token: ${{ env.GITHUB_TOKEN }} script: | const body = ` ### :robot: GitHub comments diff --git a/renovate.json b/renovate.json index 029dab13f394f..b66d29c13ca0d 100644 --- a/renovate.json +++ b/renovate.json @@ -41,6 +41,7 @@ "matchManagers": ["github-actions"], "matchPackageNames": [ "actions/checkout", + "actions/github-script", "elastic/github-actions/project-assigner", "hmarr/auto-approve-action", "octokit/graphql-action", From afb671f5619b5439255233599a246a2714cbda25 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 7 Oct 2024 14:36:33 -0600 Subject: [PATCH 03/45] [embeddable] avoid uncaught error when embeddable factory is not found (#195306) Small clean-up of ReactEmbeddableRenderer 1. avoid uncaught error when embeddable factory is not found. Move `getReactEmbeddableFactory` into `buildEmbeddable` so that if factory is not found and `getReactEmbeddableFactory` throws, its caught by try/catch wrapping `buildEmbeddable`. 2. unsubscribe from `subscriptions` --- .../react_embeddable_renderer.tsx | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx index 054949650c343..0f9ae361bbf93 100644 --- a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx +++ b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx @@ -102,22 +102,10 @@ export const ReactEmbeddableRenderer = < */ return (async () => { const parentApi = getParentApi(); - const factory = await getReactEmbeddableFactory(type); const subscriptions = new Subscription(); - const setApi = ( - apiRegistration: SetReactEmbeddableApiRegistration - ) => { - return { - ...apiRegistration, - uuid, - phase$, - parentApi, - type: factory.type, - } as unknown as Api; - }; - const buildEmbeddable = async () => { + const factory = await getReactEmbeddableFactory(type); const serializedState = parentApi.getSerializedStateForChild(uuid); const lastSavedRuntimeState = serializedState ? await factory.deserializeState(serializedState) @@ -131,6 +119,18 @@ export const ReactEmbeddableRenderer = < const initialRuntimeState = { ...lastSavedRuntimeState, ...partialRuntimeState }; + const setApi = ( + apiRegistration: SetReactEmbeddableApiRegistration + ) => { + return { + ...apiRegistration, + uuid, + phase$, + parentApi, + type: factory.type, + } as unknown as Api; + }; + const buildApi = ( apiRegistration: BuildReactEmbeddableApiRegistration< SerializedState, @@ -179,7 +179,10 @@ export const ReactEmbeddableRenderer = < ...unsavedChanges.api, } as unknown as SetReactEmbeddableApiRegistration); - cleanupFunction.current = () => unsavedChanges.cleanup(); + cleanupFunction.current = () => { + subscriptions.unsubscribe(); + unsavedChanges.cleanup(); + }; return fullApi as Api & HasSnapshottableState; }; From ecc7d4e2e6099beca9028c6aacec978ad0e7e992 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 7 Oct 2024 14:43:12 -0600 Subject: [PATCH 04/45] [dashboard] decouple DashboardCreationOptions from DashboardContainer (#194875) PR decouples `DashboardCreationOptions` type from `DashboardContainer` and moves `DashboardCreationOptions` type into dashboard_api folder. `useControlGroupIntegration` removed from `DashboardCreationOptions` type since its no longer used. --------- Co-authored-by: Elastic Machine --- .../dashboard_with_controls_example.tsx | 1 - .../dashboard/public/dashboard_api/types.ts | 43 ++++++++++++++++++- .../public/dashboard_app/dashboard_app.tsx | 4 +- .../url/search_sessions_integration.ts | 33 +++++++------- .../create/create_dashboard.test.ts | 2 +- .../embeddable/create/create_dashboard.ts | 2 +- ...rt_dashboard_search_session_integration.ts | 23 +++++----- .../embeddable/dashboard_container.tsx | 2 +- .../dashboard_container_factory.tsx | 40 +---------------- .../external_api/dashboard_renderer.test.tsx | 4 +- .../external_api/dashboard_renderer.tsx | 2 +- .../public/dashboard_container/index.ts | 1 - .../diffing/dashboard_diffing_integration.ts | 3 +- src/plugins/dashboard/public/index.ts | 3 +- .../app/metrics/static_dashboard/index.tsx | 1 - .../app/service_dashboards/index.tsx | 1 - .../tabs/dashboards/dashboards.tsx | 1 - .../components/dashboard_renderer.test.tsx | 1 - .../components/dashboard_renderer.tsx | 1 - 19 files changed, 80 insertions(+), 88 deletions(-) diff --git a/examples/portable_dashboards_example/public/dashboard_with_controls_example.tsx b/examples/portable_dashboards_example/public/dashboard_with_controls_example.tsx index 316ed8e47fb28..ca6c21538f3dd 100644 --- a/examples/portable_dashboards_example/public/dashboard_with_controls_example.tsx +++ b/examples/portable_dashboards_example/public/dashboard_with_controls_example.tsx @@ -68,7 +68,6 @@ export const DashboardWithControlsExample = ({ dataView }: { dataView: DataView }); return { - useControlGroupIntegration: true, getInitialInput: () => ({ timeRange: { from: 'now-30d', to: 'now' }, viewMode: ViewMode.VIEW, diff --git a/src/plugins/dashboard/public/dashboard_api/types.ts b/src/plugins/dashboard/public/dashboard_api/types.ts index c874ff9e67241..e0b6b8d3e4824 100644 --- a/src/plugins/dashboard/public/dashboard_api/types.ts +++ b/src/plugins/dashboard/public/dashboard_api/types.ts @@ -16,6 +16,7 @@ import { TracksOverlays, } from '@kbn/presentation-containers'; import { + EmbeddableAppContext, HasAppContext, HasType, PublishesDataViews, @@ -29,11 +30,49 @@ import { } from '@kbn/presentation-publishing'; import { ControlGroupApi, ControlGroupSerializedState } from '@kbn/controls-plugin/public'; import { Filter, Query, TimeRange } from '@kbn/es-query'; -import { DefaultEmbeddableApi, ErrorEmbeddable, IEmbeddable } from '@kbn/embeddable-plugin/public'; +import { + DefaultEmbeddableApi, + EmbeddablePackageState, + ErrorEmbeddable, + IEmbeddable, +} from '@kbn/embeddable-plugin/public'; +import { Observable } from 'rxjs'; +import { SearchSessionInfoProvider } from '@kbn/data-plugin/public'; +import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import { DashboardPanelMap, DashboardPanelState } from '../../common'; -import { SaveDashboardReturn } from '../services/dashboard_content_management_service/types'; +import { + LoadDashboardReturn, + SaveDashboardReturn, + SavedDashboardInput, +} from '../services/dashboard_content_management_service/types'; import { DashboardStateFromSettingsFlyout, UnsavedPanelState } from '../dashboard_container/types'; +export interface DashboardCreationOptions { + getInitialInput?: () => Partial; + + getIncomingEmbeddable?: () => EmbeddablePackageState | undefined; + + useSearchSessionsIntegration?: boolean; + searchSessionSettings?: { + sessionIdToRestore?: string; + sessionIdUrlChangeObservable?: Observable; + getSearchSessionIdFromURL: () => string | undefined; + removeSessionIdFromUrl: () => void; + createSessionRestorationDataProvider: (dashboardApi: DashboardApi) => SearchSessionInfoProvider; + }; + + useSessionStorageIntegration?: boolean; + + useUnifiedSearchIntegration?: boolean; + unifiedSearchSettings?: { kbnUrlStateStorage: IKbnUrlStateStorage }; + + validateLoadedSavedObject?: (result: LoadDashboardReturn) => 'valid' | 'invalid' | 'redirected'; + + isEmbeddedExternally?: boolean; + + getEmbeddableAppContext?: (dashboardId?: string) => EmbeddableAppContext; +} + export type DashboardApi = CanExpandPanels & HasAppContext & HasRuntimeChildState & diff --git a/src/plugins/dashboard/public/dashboard_app/dashboard_app.tsx b/src/plugins/dashboard/public/dashboard_app/dashboard_app.tsx index ecb62bf8fc2b3..f7ca6b552893b 100644 --- a/src/plugins/dashboard/public/dashboard_app/dashboard_app.tsx +++ b/src/plugins/dashboard/public/dashboard_app/dashboard_app.tsx @@ -19,14 +19,13 @@ import { ViewMode } from '@kbn/embeddable-plugin/public'; import { useExecutionContext } from '@kbn/kibana-react-plugin/public'; import { createKbnUrlStateStorage, withNotifyOnErrors } from '@kbn/kibana-utils-plugin/public'; -import { DashboardApi, DashboardRenderer } from '..'; +import { DashboardApi, DashboardCreationOptions, DashboardRenderer } from '..'; import { SharedDashboardState } from '../../common'; import { DASHBOARD_APP_ID, DASHBOARD_STATE_STORAGE_KEY, createDashboardEditUrl, } from '../dashboard_constants'; -import type { DashboardCreationOptions } from '../dashboard_container/embeddable/dashboard_container_factory'; import { DashboardRedirect } from '../dashboard_container/types'; import { DashboardTopNav } from '../dashboard_top_nav'; import { @@ -143,7 +142,6 @@ export function DashboardApp({ embeddableService.getStateTransfer().getIncomingEmbeddablePackage(DASHBOARD_APP_ID, true), // integrations - useControlGroupIntegration: true, useSessionStorageIntegration: true, useUnifiedSearchIntegration: true, unifiedSearchSettings: { diff --git a/src/plugins/dashboard/public/dashboard_app/url/search_sessions_integration.ts b/src/plugins/dashboard/public/dashboard_app/url/search_sessions_integration.ts index 2845808f723c3..e9ae3d6a15050 100644 --- a/src/plugins/dashboard/public/dashboard_app/url/search_sessions_integration.ts +++ b/src/plugins/dashboard/public/dashboard_app/url/search_sessions_integration.ts @@ -18,12 +18,13 @@ import { import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/common'; import type { Query } from '@kbn/es-query'; import { SearchSessionInfoProvider } from '@kbn/data-plugin/public'; - +import type { ViewMode } from '@kbn/embeddable-plugin/common'; import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics'; import { SEARCH_SESSION_ID } from '../../dashboard_constants'; -import { DashboardContainer, DashboardLocatorParams } from '../../dashboard_container'; +import { DashboardLocatorParams } from '../../dashboard_container'; import { convertPanelMapToSavedPanels } from '../../../common'; import { dataService } from '../../services/kibana_services'; +import { DashboardApi } from '../../dashboard_api/types'; export const removeSearchSessionIdFromURL = (kbnUrlStateStorage: IKbnUrlStateStorage) => { kbnUrlStateStorage.kbnUrlControls.updateAsync((nextUrl) => { @@ -46,14 +47,15 @@ export const getSessionURLObservable = (history: History) => ); export function createSessionRestorationDataProvider( - container: DashboardContainer + dashboardApi: DashboardApi ): SearchSessionInfoProvider { return { - getName: async () => container.getTitle(), + getName: async () => + dashboardApi.panelTitle.value ?? dashboardApi.savedObjectId.value ?? dashboardApi.uuid$.value, getLocatorData: async () => ({ id: DASHBOARD_APP_LOCATOR, - initialState: getLocatorParams({ container, shouldRestoreSearchSession: false }), - restoreState: getLocatorParams({ container, shouldRestoreSearchSession: true }), + initialState: getLocatorParams({ dashboardApi, shouldRestoreSearchSession: false }), + restoreState: getLocatorParams({ dashboardApi, shouldRestoreSearchSession: true }), }), }; } @@ -63,24 +65,19 @@ export function createSessionRestorationDataProvider( * as it was. */ function getLocatorParams({ - container, + dashboardApi, shouldRestoreSearchSession, }: { - container: DashboardContainer; + dashboardApi: DashboardApi; shouldRestoreSearchSession: boolean; }): DashboardLocatorParams { - const { - explicitInput: { panels, query, viewMode }, - } = container.getState(); - - const savedObjectId = container.savedObjectId.value; - + const savedObjectId = dashboardApi.savedObjectId.value; return { - viewMode, + viewMode: (dashboardApi.viewMode.value as ViewMode) ?? 'view', useHash: false, preserveSavedFilters: false, filters: dataService.query.filterManager.getFilters(), - query: dataService.query.queryString.formatQuery(query) as Query, + query: dataService.query.queryString.formatQuery(dashboardApi.query$.value) as Query, dashboardId: savedObjectId, searchSessionId: shouldRestoreSearchSession ? dataService.search.session.getSessionId() @@ -96,6 +93,8 @@ function getLocatorParams({ : undefined, panels: savedObjectId ? undefined - : (convertPanelMapToSavedPanels(panels) as DashboardLocatorParams['panels']), + : (convertPanelMapToSavedPanels( + dashboardApi.panels$.value + ) as DashboardLocatorParams['panels']), }; } diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts index ea5508dff23da..ddcdeff25ea4e 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts @@ -21,7 +21,7 @@ import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import { DEFAULT_DASHBOARD_INPUT } from '../../../dashboard_constants'; import { getSampleDashboardPanel, mockControlGroupApi } from '../../../mocks'; import { dataService, embeddableService } from '../../../services/kibana_services'; -import { DashboardCreationOptions } from '../dashboard_container_factory'; +import { DashboardCreationOptions } from '../../..'; import { createDashboard } from './create_dashboard'; import { getDashboardContentManagementService } from '../../../services/dashboard_content_management_service'; import { getDashboardBackupService } from '../../../services/dashboard_backup_service'; diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts index 0264e834bbd07..3eab6c641ee87 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts @@ -44,7 +44,7 @@ import { runPanelPlacementStrategy } from '../../panel_placement/place_new_panel import { startDiffingDashboardState } from '../../state/diffing/dashboard_diffing_integration'; import { UnsavedPanelState } from '../../types'; import { DashboardContainer } from '../dashboard_container'; -import { DashboardCreationOptions } from '../dashboard_container_factory'; +import type { DashboardCreationOptions } from '../../..'; import { startSyncingDashboardDataViews } from './data_views/sync_dashboard_data_views'; import { startQueryPerformanceTracking } from './performance/query_performance_tracking'; import { startDashboardSearchSessionIntegration } from './search_sessions/start_dashboard_search_session_integration'; diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/search_sessions/start_dashboard_search_session_integration.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/search_sessions/start_dashboard_search_session_integration.ts index 8150ee7a72381..70f841db869a5 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/search_sessions/start_dashboard_search_session_integration.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/search_sessions/start_dashboard_search_session_integration.ts @@ -13,7 +13,7 @@ import { noSearchSessionStorageCapabilityMessage } from '@kbn/data-plugin/public import { dataService } from '../../../../services/kibana_services'; import { DashboardContainer } from '../../dashboard_container'; -import { DashboardCreationOptions } from '../../dashboard_container_factory'; +import type { DashboardApi, DashboardCreationOptions } from '../../../..'; import { newSession$ } from './new_session'; import { getDashboardCapabilities } from '../../../../utils/get_dashboard_capabilities'; @@ -33,15 +33,18 @@ export function startDashboardSearchSessionIntegration( createSessionRestorationDataProvider, } = searchSessionSettings; - dataService.search.session.enableStorage(createSessionRestorationDataProvider(this), { - isDisabled: () => - getDashboardCapabilities().storeSearchSession - ? { disabled: false } - : { - disabled: true, - reasonText: noSearchSessionStorageCapabilityMessage, - }, - }); + dataService.search.session.enableStorage( + createSessionRestorationDataProvider(this as DashboardApi), + { + isDisabled: () => + getDashboardCapabilities().storeSearchSession + ? { disabled: false } + : { + disabled: true, + reasonText: noSearchSessionStorageCapabilityMessage, + }, + } + ); // force refresh when the session id in the URL changes. This will also fire off the "handle search session change" below. const searchSessionIdChangeSubscription = sessionIdUrlChangeObservable diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx index c6fed0998cfde..e508e511d41cb 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx @@ -112,11 +112,11 @@ import { } from './create/controls/dashboard_control_group_integration'; import { initializeDashboard } from './create/create_dashboard'; import { - DashboardCreationOptions, dashboardTypeDisplayLowercase, dashboardTypeDisplayName, } from './dashboard_container_factory'; import { InitialComponentState, getDashboardApi } from '../../dashboard_api/get_dashboard_api'; +import type { DashboardCreationOptions } from '../..'; export interface InheritedChildInput { filters: Filter[]; diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx index ecccc49a60f12..52d7d84f67490 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx @@ -8,29 +8,20 @@ */ import { i18n } from '@kbn/i18n'; -import { Observable } from 'rxjs'; - -import { SearchSessionInfoProvider } from '@kbn/data-plugin/public'; import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common'; import { Container, ContainerOutput, EmbeddableFactory, EmbeddableFactoryDefinition, - EmbeddablePackageState, ErrorEmbeddable, } from '@kbn/embeddable-plugin/public'; -import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; -import { EmbeddableAppContext } from '@kbn/presentation-publishing'; import { DASHBOARD_CONTAINER_TYPE } from '..'; import { createExtract, createInject, DashboardContainerInput } from '../../../common'; import { DEFAULT_DASHBOARD_INPUT } from '../../dashboard_constants'; -import { - LoadDashboardReturn, - SavedDashboardInput, -} from '../../services/dashboard_content_management_service/types'; import type { DashboardContainer } from './dashboard_container'; +import type { DashboardCreationOptions } from '../..'; export type DashboardContainerFactory = EmbeddableFactory< DashboardContainerInput, @@ -38,35 +29,6 @@ export type DashboardContainerFactory = EmbeddableFactory< DashboardContainer >; -export interface DashboardCreationOptions { - getInitialInput?: () => Partial; - - getIncomingEmbeddable?: () => EmbeddablePackageState | undefined; - - useSearchSessionsIntegration?: boolean; - searchSessionSettings?: { - sessionIdToRestore?: string; - sessionIdUrlChangeObservable?: Observable; - getSearchSessionIdFromURL: () => string | undefined; - removeSessionIdFromUrl: () => void; - createSessionRestorationDataProvider: ( - container: DashboardContainer - ) => SearchSessionInfoProvider; - }; - - useControlGroupIntegration?: boolean; - useSessionStorageIntegration?: boolean; - - useUnifiedSearchIntegration?: boolean; - unifiedSearchSettings?: { kbnUrlStateStorage: IKbnUrlStateStorage }; - - validateLoadedSavedObject?: (result: LoadDashboardReturn) => 'valid' | 'invalid' | 'redirected'; - - isEmbeddedExternally?: boolean; - - getEmbeddableAppContext?: (dashboardId?: string) => EmbeddableAppContext; -} - export const dashboardTypeDisplayName = i18n.translate('dashboard.factory.displayName', { defaultMessage: 'Dashboard', }); diff --git a/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.test.tsx b/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.test.tsx index 153324f5a2bf3..fd41fdd5e764d 100644 --- a/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.test.tsx +++ b/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.test.tsx @@ -18,10 +18,9 @@ import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/common'; import { setStubKibanaServices as setPresentationPanelMocks } from '@kbn/presentation-panel-plugin/public/mocks'; import { BehaviorSubject } from 'rxjs'; import { DashboardContainerFactory } from '..'; -import { DASHBOARD_CONTAINER_TYPE } from '../..'; +import { DASHBOARD_CONTAINER_TYPE, DashboardCreationOptions } from '../..'; import { embeddableService } from '../../services/kibana_services'; import { DashboardContainer } from '../embeddable/dashboard_container'; -import { DashboardCreationOptions } from '../embeddable/dashboard_container_factory'; import { DashboardRenderer } from './dashboard_renderer'; describe('dashboard renderer', () => { @@ -53,7 +52,6 @@ describe('dashboard renderer', () => { test('saved object id & creation options are passed to dashboard factory', async () => { const options: DashboardCreationOptions = { - useControlGroupIntegration: true, useSessionStorageIntegration: true, useUnifiedSearchIntegration: true, }; diff --git a/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx b/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx index e8ef4faef724d..a43bd6ddbc75b 100644 --- a/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx +++ b/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx @@ -28,8 +28,8 @@ import type { DashboardContainer } from '../embeddable/dashboard_container'; import { DashboardContainerFactory, DashboardContainerFactoryDefinition, - DashboardCreationOptions, } from '../embeddable/dashboard_container_factory'; +import type { DashboardCreationOptions } from '../..'; import { DashboardLocatorParams, DashboardRedirect } from '../types'; import { Dashboard404Page } from './dashboard_404'; diff --git a/src/plugins/dashboard/public/dashboard_container/index.ts b/src/plugins/dashboard/public/dashboard_container/index.ts index d8103f1e3f9bc..16314f52d38f8 100644 --- a/src/plugins/dashboard/public/dashboard_container/index.ts +++ b/src/plugins/dashboard/public/dashboard_container/index.ts @@ -17,7 +17,6 @@ export const LATEST_DASHBOARD_CONTAINER_VERSION = convertNumberToDashboardVersio export type { DashboardContainer } from './embeddable/dashboard_container'; export { type DashboardContainerFactory, - type DashboardCreationOptions, DashboardContainerFactoryDefinition, } from './embeddable/dashboard_container_factory'; diff --git a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts index 8c014d0ec92c6..ad33b2e5fb117 100644 --- a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts +++ b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts @@ -10,7 +10,8 @@ import { childrenUnsavedChanges$ } from '@kbn/presentation-containers'; import { omit } from 'lodash'; import { AnyAction, Middleware } from 'redux'; import { combineLatest, debounceTime, skipWhile, startWith, switchMap } from 'rxjs'; -import { DashboardContainer, DashboardCreationOptions } from '../..'; +import { DashboardContainer } from '../..'; +import { DashboardCreationOptions } from '../../..'; import { DashboardContainerInput } from '../../../../common'; import { CHANGE_CHECK_DEBOUNCE } from '../../../dashboard_constants'; import { diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index 612cdc5517bbe..109be5bc0eaf0 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -17,11 +17,10 @@ export { DASHBOARD_GRID_COLUMN_COUNT, PanelPlacementStrategy, } from './dashboard_constants'; -export type { DashboardApi } from './dashboard_api/types'; +export type { DashboardApi, DashboardCreationOptions } from './dashboard_api/types'; export { LazyDashboardRenderer as DashboardRenderer, DASHBOARD_CONTAINER_TYPE, - type DashboardCreationOptions, type DashboardLocatorParams, type IProvidesLegacyPanelPlacementSettings, } from './dashboard_container'; diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/metrics/static_dashboard/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/metrics/static_dashboard/index.tsx index 15a4b62a7efe7..cc9c12b97a6d2 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/metrics/static_dashboard/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/metrics/static_dashboard/index.tsx @@ -82,7 +82,6 @@ async function getCreationOptions( } return { - useControlGroupIntegration: true, getInitialInput: () => ({ viewMode: ViewMode.VIEW, panels, diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_dashboards/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_dashboards/index.tsx index bf864e134a698..627c314bf72e6 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_dashboards/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_dashboards/index.tsx @@ -103,7 +103,6 @@ export function ServiceDashboards({ checkForEntities = false }: { checkForEntiti }); return Promise.resolve({ getInitialInput, - useControlGroupIntegration: true, }); }, [rangeFrom, rangeTo]); diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/dashboards/dashboards.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/dashboards/dashboards.tsx index ae5251a5c17b9..f90aa500c9266 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/dashboards/dashboards.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/dashboards/dashboards.tsx @@ -137,7 +137,6 @@ export function Dashboards() { }); return Promise.resolve({ getInitialInput, - useControlGroupIntegration: true, }); }, [dateRange.from, dateRange.to]); diff --git a/x-pack/plugins/security_solution/public/dashboards/components/dashboard_renderer.test.tsx b/x-pack/plugins/security_solution/public/dashboards/components/dashboard_renderer.test.tsx index 9c6df7bb6e395..6897f59af6ffd 100644 --- a/x-pack/plugins/security_solution/public/dashboards/components/dashboard_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/dashboards/components/dashboard_renderer.test.tsx @@ -62,7 +62,6 @@ describe('DashboardRenderer', () => { filters: undefined, }) ); - expect(options.useControlGroupIntegration).toEqual(true); }); it('does not render when No Read Permission', () => { diff --git a/x-pack/plugins/security_solution/public/dashboards/components/dashboard_renderer.tsx b/x-pack/plugins/security_solution/public/dashboards/components/dashboard_renderer.tsx index cad24fe3f494c..183f62f6cb923 100644 --- a/x-pack/plugins/security_solution/public/dashboards/components/dashboard_renderer.tsx +++ b/x-pack/plugins/security_solution/public/dashboards/components/dashboard_renderer.tsx @@ -108,7 +108,6 @@ const DashboardRendererComponent = ({ const getCreationOptions: () => Promise = useCallback(() => { return Promise.resolve({ useSessionStorageIntegration: true, - useControlGroupIntegration: true, getInitialInput: () => { return initialInput.value; }, From b4d52e440ef6f7c68675893cde4994a2b3d45247 Mon Sep 17 00:00:00 2001 From: Rachel Shen Date: Mon, 7 Oct 2024 14:43:40 -0600 Subject: [PATCH 05/45] [Dashboard] [Usability] Add scroll margin to panels (#193430) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Closes [[Bug] Upper part of the chart is hidden when user edits a chart inline · Issue #192437 · elastic/kibana · GitHub](https://github.com/elastic/kibana/issues/192437) This PR disables the overflow-y hidden css and the panel is set to the top. Users are also able to scroll to the top of the visualization in inline editing mode. ### Now: https://github.com/user-attachments/assets/a68a3bdc-5452-40f3-84a6-4950e2fc7893 --------- Co-authored-by: Hannah Mudge --- src/plugins/dashboard/public/dashboard_api/track_panel.ts | 3 +-- .../component/grid/_dashboard_panel.scss | 7 +++++++ .../component/grid/dashboard_grid_item.tsx | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/plugins/dashboard/public/dashboard_api/track_panel.ts b/src/plugins/dashboard/public/dashboard_api/track_panel.ts index 3f3a9b4aaad4b..42345f38d614f 100644 --- a/src/plugins/dashboard/public/dashboard_api/track_panel.ts +++ b/src/plugins/dashboard/public/dashboard_api/track_panel.ts @@ -73,8 +73,7 @@ export function initializeTrackPanel(untilEmbeddableLoaded: (id: string) => Prom }; return; } - - panelRef.scrollIntoView({ block: 'center' }); + panelRef.scrollIntoView({ block: 'nearest' }); }); }, scrollToTop: () => { diff --git a/src/plugins/dashboard/public/dashboard_container/component/grid/_dashboard_panel.scss b/src/plugins/dashboard/public/dashboard_container/component/grid/_dashboard_panel.scss index 5a252f9bf1630..d54f513a207a4 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/grid/_dashboard_panel.scss +++ b/src/plugins/dashboard/public/dashboard_container/component/grid/_dashboard_panel.scss @@ -4,6 +4,11 @@ * .embPanel--editing doesn't get updating without a hard refresh */ +.dshDashboardGrid__item { + scroll-margin-top: calc((var(--euiFixedHeadersOffset, 100) * 2) + $euiSizeS); + scroll-margin-bottom: $euiSizeS; +} + // LAYOUT MODES // Adjust borders/etc... for non-spaced out and expanded panels .dshLayout-withoutMargins { @@ -36,9 +41,11 @@ 0% { outline: solid $euiSizeXS transparentize($euiColorSuccess, 1); } + 25% { outline: solid $euiSizeXS transparentize($euiColorSuccess, .5); } + 100% { outline: solid $euiSizeXS transparentize($euiColorSuccess, 1); } diff --git a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx index 518b009571e27..7b21db4ea3f84 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx @@ -60,7 +60,7 @@ export const Item = React.forwardRef( const hidePanel = expandedPanelId !== undefined && expandedPanelId !== id; const focusPanel = focusedPanelId !== undefined && focusedPanelId === id; const blurPanel = focusedPanelId !== undefined && focusedPanelId !== id; - const classes = classNames({ + const classes = classNames('dshDashboardGrid__item', { 'dshDashboardGrid__item--expanded': expandPanel, 'dshDashboardGrid__item--hidden': hidePanel, 'dshDashboardGrid__item--focused': focusPanel, From 1ee648d672f0ed5322183d5abd4ebbe4e13f0a93 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Mon, 7 Oct 2024 23:41:20 +0200 Subject: [PATCH 06/45] [Security Assistant] AI Assistant - Better Solution for OSS models (#10416) (#194166) --- .../impl/assistant/use_send_message/index.tsx | 16 ++ .../use_send_message/translations.ts | 15 ++ .../server/lib/langchain/executors/types.ts | 1 + .../graphs/default_assistant_graph/graph.ts | 4 + .../graphs/default_assistant_graph/helpers.ts | 11 +- .../graphs/default_assistant_graph/index.ts | 7 +- .../nodes/model_input.ts | 4 +- .../nodes/translations.ts | 56 ++++++ .../graphs/default_assistant_graph/prompts.ts | 57 +----- .../graphs/default_assistant_graph/types.ts | 2 + .../server/routes/chat/chat_complete_route.ts | 6 +- .../server/routes/evaluate/post_evaluate.ts | 22 ++- .../server/routes/helpers.ts | 3 + .../routes/post_actions_connector_execute.ts | 5 + .../server/routes/utils.test.ts | 69 ++++++++ .../elastic_assistant/server/routes/utils.ts | 28 +++ .../plugins/elastic_assistant/server/types.ts | 1 + .../esql_language_knowledge_base/common.ts | 15 ++ .../esql_language_knowledge_base_tool.test.ts | 23 +++ .../esql_language_knowledge_base_tool.ts | 12 +- .../nl_to_esql_tool.test.ts | 162 ++++++++++++++++++ .../nl_to_esql_tool.ts | 7 +- 22 files changed, 452 insertions(+), 74 deletions(-) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_message/translations.ts create mode 100644 x-pack/plugins/elastic_assistant/server/routes/utils.test.ts create mode 100644 x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/common.ts create mode 100644 x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/nl_to_esql_tool.test.ts diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_message/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_message/index.tsx index 93bd03607e71f..438b2282371d9 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_message/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_message/index.tsx @@ -10,6 +10,16 @@ import { useCallback, useRef, useState } from 'react'; import { ApiConfig, Replacements } from '@kbn/elastic-assistant-common'; import { useAssistantContext } from '../../assistant_context'; import { fetchConnectorExecuteAction, FetchConnectorExecuteResponse } from '../api'; +import * as i18n from './translations'; + +/** + * TODO: This is a workaround to solve the issue with the long standing server tasks while cahtting with the assistant. + * Some models (like Llama 3.1 70B) can perform poorly and be slow which leads to a long time to handle the request. + * The `core-http-browser` has a timeout of two minutes after which it will re-try the request. In combination with the slow model it can lead to + * a situation where core http client will initiate same request again and again. + * To avoid this, we abort http request after timeout which is slightly below two minutes. + */ +const EXECUTE_ACTION_TIMEOUT = 110 * 1000; // in milliseconds interface SendMessageProps { apiConfig: ApiConfig; @@ -38,6 +48,11 @@ export const useSendMessage = (): UseSendMessage => { async ({ apiConfig, http, message, conversationId, replacements }: SendMessageProps) => { setIsLoading(true); + const timeoutId = setTimeout(() => { + abortController.current.abort(i18n.FETCH_MESSAGE_TIMEOUT_ERROR); + abortController.current = new AbortController(); + }, EXECUTE_ACTION_TIMEOUT); + try { return await fetchConnectorExecuteAction({ conversationId, @@ -52,6 +67,7 @@ export const useSendMessage = (): UseSendMessage => { traceOptions, }); } finally { + clearTimeout(timeoutId); setIsLoading(false); } }, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_message/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_message/translations.ts new file mode 100644 index 0000000000000..1185d8cfdbc65 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_message/translations.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. + */ + +import { i18n } from '@kbn/i18n'; + +export const FETCH_MESSAGE_TIMEOUT_ERROR = i18n.translate( + 'xpack.elasticAssistant.assistant.useSendMessage.fetchMessageTimeoutError', + { + defaultMessage: 'Assistant could not respond in time. Please try again later.', + } +); diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts index 2d86d05447916..5761201849c09 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts @@ -45,6 +45,7 @@ export interface AgentExecutorParams { esClient: ElasticsearchClient; langChainMessages: BaseMessage[]; llmType?: string; + isOssModel?: boolean; logger: Logger; inference: InferenceServerStart; onNewReplacements?: (newReplacements: Replacements) => void; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts index 8395076ad62ee..8f2f713c170ed 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts @@ -94,6 +94,10 @@ export const getDefaultAssistantGraph = ({ value: (x: boolean, y?: boolean) => y ?? x, default: () => false, }, + isOssModel: { + value: (x: boolean, y?: boolean) => y ?? x, + default: () => false, + }, conversation: { value: (x: ConversationResponse | undefined, y?: ConversationResponse | undefined) => y ?? x, diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts index 93890f9dfb121..840b2a9ac8ce0 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts @@ -24,6 +24,7 @@ interface StreamGraphParams { assistantGraph: DefaultAssistantGraph; inputs: GraphInputs; logger: Logger; + isOssModel?: boolean; onLlmResponse?: OnLlmResponse; request: KibanaRequest; traceOptions?: TraceOptions; @@ -36,6 +37,7 @@ interface StreamGraphParams { * @param assistantGraph * @param inputs * @param logger + * @param isOssModel * @param onLlmResponse * @param request * @param traceOptions @@ -45,6 +47,7 @@ export const streamGraph = async ({ assistantGraph, inputs, logger, + isOssModel, onLlmResponse, request, traceOptions, @@ -80,8 +83,8 @@ export const streamGraph = async ({ }; if ( - (inputs?.llmType === 'bedrock' || inputs?.llmType === 'gemini') && - inputs?.bedrockChatEnabled + inputs.isOssModel || + ((inputs?.llmType === 'bedrock' || inputs?.llmType === 'gemini') && inputs?.bedrockChatEnabled) ) { const stream = await assistantGraph.streamEvents( inputs, @@ -92,7 +95,9 @@ export const streamGraph = async ({ version: 'v2', streamMode: 'values', }, - inputs?.llmType === 'bedrock' ? { includeNames: ['Summarizer'] } : undefined + inputs.isOssModel || inputs?.llmType === 'bedrock' + ? { includeNames: ['Summarizer'] } + : undefined ); for await (const { event, data, tags } of stream) { diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts index dee23f202b3d4..daec22b436474 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts @@ -36,6 +36,7 @@ export const callAssistantGraph: AgentExecutor = async ({ inference, langChainMessages, llmType, + isOssModel, logger: parentLogger, isStream = false, onLlmResponse, @@ -48,7 +49,7 @@ export const callAssistantGraph: AgentExecutor = async ({ responseLanguage = 'English', }) => { const logger = parentLogger.get('defaultAssistantGraph'); - const isOpenAI = llmType === 'openai'; + const isOpenAI = llmType === 'openai' && !isOssModel; const llmClass = getLlmClass(llmType, bedrockChatEnabled); /** @@ -111,7 +112,7 @@ export const callAssistantGraph: AgentExecutor = async ({ }; const tools: StructuredTool[] = assistantTools.flatMap( - (tool) => tool.getTool({ ...assistantToolParams, llm: createLlmInstance() }) ?? [] + (tool) => tool.getTool({ ...assistantToolParams, llm: createLlmInstance(), isOssModel }) ?? [] ); // If KB enabled, fetch for any KB IndexEntries and generate a tool for each @@ -166,6 +167,7 @@ export const callAssistantGraph: AgentExecutor = async ({ conversationId, llmType, isStream, + isOssModel, input: latestMessage[0]?.content as string, }; @@ -175,6 +177,7 @@ export const callAssistantGraph: AgentExecutor = async ({ assistantGraph, inputs, logger, + isOssModel, onLlmResponse, request, traceOptions, diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/model_input.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/model_input.ts index f634d10f5cd4a..5f46e1ad2a741 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/model_input.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/model_input.ts @@ -22,7 +22,9 @@ interface ModelInputParams extends NodeParamsBase { export function modelInput({ logger, state }: ModelInputParams): Partial { logger.debug(() => `${NodeType.MODEL_INPUT}: Node state:\n${JSON.stringify(state, null, 2)}`); - const hasRespondStep = state.isStream && state.bedrockChatEnabled && state.llmType === 'bedrock'; + const hasRespondStep = + state.isStream && + (state.isOssModel || (state.bedrockChatEnabled && state.llmType === 'bedrock')); return { hasRespondStep, diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/translations.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/translations.ts index 9eedce48ba69d..e55e1081e6474 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/translations.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/translations.ts @@ -18,3 +18,59 @@ const KB_CATCH = export const GEMINI_SYSTEM_PROMPT = `${BASE_GEMINI_PROMPT} ${KB_CATCH}`; export const BEDROCK_SYSTEM_PROMPT = `Use tools as often as possible, as they have access to the latest data and syntax. Always return value from ESQLKnowledgeBaseTool as is. Never return tags in the response, but make sure to include tags content in the response. Do not reflect on the quality of the returned search results in your response.`; export const GEMINI_USER_PROMPT = `Now, always using the tools at your disposal, step by step, come up with a response to this request:\n\n`; + +export const STRUCTURED_SYSTEM_PROMPT = `Respond to the human as helpfully and accurately as possible. You have access to the following tools: + +{tools} + +The tool action_input should ALWAYS follow the tool JSON schema args. + +Valid "action" values: "Final Answer" or {tool_names} + +Use a json blob to specify a tool by providing an action key (tool name) and an action_input key (tool input strictly adhering to the tool JSON schema args). + +Provide only ONE action per $JSON_BLOB, as shown: + +\`\`\` + +{{ + + "action": $TOOL_NAME, + + "action_input": $TOOL_INPUT + +}} + +\`\`\` + +Follow this format: + +Question: input question to answer + +Thought: consider previous and subsequent steps + +Action: + +\`\`\` + +$JSON_BLOB + +\`\`\` + +Observation: action result + +... (repeat Thought/Action/Observation N times) + +Thought: I know what to respond + +Action: + +\`\`\` + +{{ + + "action": "Final Answer", + + "action_input": "Final response to human"}} + +Begin! Reminder to ALWAYS respond with a valid json blob of a single action with no additional output. When using tools, ALWAYS input the expected JSON schema args. Your answer will be parsed as JSON, so never use double quotes within the output and instead use backticks. Single quotes may be used, such as apostrophes. Response format is Action:\`\`\`$JSON_BLOB\`\`\`then Observation`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/prompts.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/prompts.ts index 4a7b1fd46ccb8..883047ed7b9df 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/prompts.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/prompts.ts @@ -11,6 +11,7 @@ import { DEFAULT_SYSTEM_PROMPT, GEMINI_SYSTEM_PROMPT, GEMINI_USER_PROMPT, + STRUCTURED_SYSTEM_PROMPT, } from './nodes/translations'; export const formatPrompt = (prompt: string, additionalPrompt?: string) => @@ -26,61 +27,7 @@ export const systemPrompts = { bedrock: `${DEFAULT_SYSTEM_PROMPT} ${BEDROCK_SYSTEM_PROMPT}`, // The default prompt overwhelms gemini, do not prepend gemini: GEMINI_SYSTEM_PROMPT, - structuredChat: `Respond to the human as helpfully and accurately as possible. You have access to the following tools: - -{tools} - -The tool action_input should ALWAYS follow the tool JSON schema args. - -Valid "action" values: "Final Answer" or {tool_names} - -Use a json blob to specify a tool by providing an action key (tool name) and an action_input key (tool input strictly adhering to the tool JSON schema args). - -Provide only ONE action per $JSON_BLOB, as shown: - -\`\`\` - -{{ - - "action": $TOOL_NAME, - - "action_input": $TOOL_INPUT - -}} - -\`\`\` - -Follow this format: - -Question: input question to answer - -Thought: consider previous and subsequent steps - -Action: - -\`\`\` - -$JSON_BLOB - -\`\`\` - -Observation: action result - -... (repeat Thought/Action/Observation N times) - -Thought: I know what to respond - -Action: - -\`\`\` - -{{ - - "action": "Final Answer", - - "action_input": "Final response to human"}} - -Begin! Reminder to ALWAYS respond with a valid json blob of a single action with no additional output. When using tools, ALWAYS input the expected JSON schema args. Your answer will be parsed as JSON, so never use double quotes within the output and instead use backticks. Single quotes may be used, such as apostrophes. Response format is Action:\`\`\`$JSON_BLOB\`\`\`then Observation`, + structuredChat: STRUCTURED_SYSTEM_PROMPT, }; export const openAIFunctionAgentPrompt = formatPrompt(systemPrompts.openai); diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts index 17d06b0f7042e..69632be2ffdcd 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts @@ -20,6 +20,7 @@ export interface GraphInputs { conversationId?: string; llmType?: string; isStream?: boolean; + isOssModel?: boolean; input: string; responseLanguage?: string; } @@ -31,6 +32,7 @@ export interface AgentState extends AgentStateBase { lastNode: string; hasRespondStep: boolean; isStream: boolean; + isOssModel: boolean; bedrockChatEnabled: boolean; llmType: string; responseLanguage: string; diff --git a/x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.ts b/x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.ts index dd90241809015..47f6f1a486957 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.ts @@ -30,6 +30,7 @@ import { } from '../helpers'; import { transformESSearchToAnonymizationFields } from '../../ai_assistant_data_clients/anonymization_fields/helpers'; import { EsAnonymizationFieldsSchema } from '../../ai_assistant_data_clients/anonymization_fields/types'; +import { isOpenSourceModel } from '../utils'; export const SYSTEM_PROMPT_CONTEXT_NON_I18N = (context: string) => { return `CONTEXT:\n"""\n${context}\n"""`; @@ -99,7 +100,9 @@ export const chatCompleteRoute = ( const actions = ctx.elasticAssistant.actions; const actionsClient = await actions.getActionsClientWithRequest(request); const connectors = await actionsClient.getBulk({ ids: [connectorId] }); - actionTypeId = connectors.length > 0 ? connectors[0].actionTypeId : '.gen-ai'; + const connector = connectors.length > 0 ? connectors[0] : undefined; + actionTypeId = connector?.actionTypeId ?? '.gen-ai'; + const isOssModel = isOpenSourceModel(connector); // replacements const anonymizationFieldsRes = @@ -192,6 +195,7 @@ export const chatCompleteRoute = ( actionsClient, actionTypeId, connectorId, + isOssModel, conversationId: conversationId ?? newConversation?.id, context: ctx, getElser, diff --git a/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts b/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts index c0c7bf3f6bc4e..59436070a7125 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts @@ -46,7 +46,7 @@ import { openAIFunctionAgentPrompt, structuredChatAgentPrompt, } from '../../lib/langchain/graphs/default_assistant_graph/prompts'; -import { getLlmClass, getLlmType } from '../utils'; +import { getLlmClass, getLlmType, isOpenSourceModel } from '../utils'; const DEFAULT_SIZE = 20; const ROUTE_HANDLER_TIMEOUT = 10 * 60 * 1000; // 10 * 60 seconds = 10 minutes @@ -174,10 +174,12 @@ export const postEvaluateRoute = ( name: string; graph: DefaultAssistantGraph; llmType: string | undefined; + isOssModel: boolean | undefined; }> = await Promise.all( connectors.map(async (connector) => { const llmType = getLlmType(connector.actionTypeId); - const isOpenAI = llmType === 'openai'; + const isOssModel = isOpenSourceModel(connector); + const isOpenAI = llmType === 'openai' && !isOssModel; const llmClass = getLlmClass(llmType, true); const createLlmInstance = () => new llmClass({ @@ -232,6 +234,7 @@ export const postEvaluateRoute = ( isEnabledKnowledgeBase, kbDataClient: dataClients?.kbDataClient, llm, + isOssModel, logger, modelExists: isEnabledKnowledgeBase, request: skeletonRequest, @@ -274,6 +277,7 @@ export const postEvaluateRoute = ( return { name: `${runName} - ${connector.name}`, llmType, + isOssModel, graph: getDefaultAssistantGraph({ agentRunnable, dataClients, @@ -287,7 +291,7 @@ export const postEvaluateRoute = ( ); // Run an evaluation for each graph so they show up separately (resulting in each dataset run grouped by connector) - await asyncForEach(graphs, async ({ name, graph, llmType }) => { + await asyncForEach(graphs, async ({ name, graph, llmType, isOssModel }) => { // Wrapper function for invoking the graph (to parse different input/output formats) const predict = async (input: { input: string }) => { logger.debug(`input:\n ${JSON.stringify(input, null, 2)}`); @@ -300,6 +304,7 @@ export const postEvaluateRoute = ( llmType, bedrockChatEnabled: true, isStreaming: false, + isOssModel, }, // TODO: Update to use the correct input format per dataset type { runName, @@ -310,15 +315,20 @@ export const postEvaluateRoute = ( return output; }; - const evalOutput = await evaluate(predict, { + evaluate(predict, { data: datasetName ?? '', evaluators: [], // Evals to be managed in LangSmith for now experimentPrefix: name, client: new Client({ apiKey: langSmithApiKey }), // prevent rate limiting and unexpected multiple experiment runs maxConcurrency: 5, - }); - logger.debug(`runResp:\n ${JSON.stringify(evalOutput, null, 2)}`); + }) + .then((output) => { + logger.debug(`runResp:\n ${JSON.stringify(output, null, 2)}`); + }) + .catch((err) => { + logger.error(`evaluation error:\n ${JSON.stringify(err, null, 2)}`); + }); }); return response.ok({ diff --git a/x-pack/plugins/elastic_assistant/server/routes/helpers.ts b/x-pack/plugins/elastic_assistant/server/routes/helpers.ts index 2c0c56c73a2b3..ebd9fd996dfe1 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/helpers.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/helpers.ts @@ -322,6 +322,7 @@ export interface LangChainExecuteParams { actionTypeId: string; connectorId: string; inference: InferenceServerStart; + isOssModel?: boolean; conversationId?: string; context: AwaitedProperties< Pick @@ -348,6 +349,7 @@ export const langChainExecute = async ({ telemetry, actionTypeId, connectorId, + isOssModel, context, actionsClient, inference, @@ -412,6 +414,7 @@ export const langChainExecute = async ({ inference, isStream, llmType: getLlmType(actionTypeId), + isOssModel, langChainMessages, logger, onNewReplacements, diff --git a/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts b/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts index 736d60ff666b0..4b65b5bb3f1e5 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts @@ -29,6 +29,7 @@ import { getSystemPromptFromUserConversation, langChainExecute, } from './helpers'; +import { isOpenSourceModel } from './utils'; export const postActionsConnectorExecuteRoute = ( router: IRouter, @@ -94,6 +95,9 @@ export const postActionsConnectorExecuteRoute = ( const actions = ctx.elasticAssistant.actions; const inference = ctx.elasticAssistant.inference; const actionsClient = await actions.getActionsClientWithRequest(request); + const connectors = await actionsClient.getBulk({ ids: [connectorId] }); + const connector = connectors.length > 0 ? connectors[0] : undefined; + const isOssModel = isOpenSourceModel(connector); const conversationsDataClient = await assistantContext.getAIAssistantConversationsDataClient(); @@ -129,6 +133,7 @@ export const postActionsConnectorExecuteRoute = ( actionsClient, actionTypeId, connectorId, + isOssModel, conversationId, context: ctx, getElser, diff --git a/x-pack/plugins/elastic_assistant/server/routes/utils.test.ts b/x-pack/plugins/elastic_assistant/server/routes/utils.test.ts new file mode 100644 index 0000000000000..3ca1b8edb5036 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/utils.test.ts @@ -0,0 +1,69 @@ +/* + * 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 { Connector } from '@kbn/actions-plugin/server/application/connector/types'; +import { isOpenSourceModel } from './utils'; +import { + OPENAI_CHAT_URL, + OpenAiProviderType, +} from '@kbn/stack-connectors-plugin/common/openai/constants'; + +describe('Utils', () => { + describe('isOpenSourceModel', () => { + it('should return `false` when connector is undefined', async () => { + const isOpenModel = isOpenSourceModel(); + expect(isOpenModel).toEqual(false); + }); + + it('should return `false` when connector is a Bedrock', async () => { + const connector = { actionTypeId: '.bedrock' } as Connector; + const isOpenModel = isOpenSourceModel(connector); + expect(isOpenModel).toEqual(false); + }); + + it('should return `false` when connector is a Gemini', async () => { + const connector = { actionTypeId: '.gemini' } as Connector; + const isOpenModel = isOpenSourceModel(connector); + expect(isOpenModel).toEqual(false); + }); + + it('should return `false` when connector is a OpenAI and API url is not specified', async () => { + const connector = { + actionTypeId: '.gen-ai', + } as unknown as Connector; + const isOpenModel = isOpenSourceModel(connector); + expect(isOpenModel).toEqual(false); + }); + + it('should return `false` when connector is a OpenAI and OpenAI API url is specified', async () => { + const connector = { + actionTypeId: '.gen-ai', + config: { apiUrl: OPENAI_CHAT_URL }, + } as unknown as Connector; + const isOpenModel = isOpenSourceModel(connector); + expect(isOpenModel).toEqual(false); + }); + + it('should return `false` when connector is a AzureOpenAI', async () => { + const connector = { + actionTypeId: '.gen-ai', + config: { apiProvider: OpenAiProviderType.AzureAi }, + } as unknown as Connector; + const isOpenModel = isOpenSourceModel(connector); + expect(isOpenModel).toEqual(false); + }); + + it('should return `true` when connector is a OpenAI and non-OpenAI API url is specified', async () => { + const connector = { + actionTypeId: '.gen-ai', + config: { apiUrl: 'https://elastic.llm.com/llama/chat/completions' }, + } as unknown as Connector; + const isOpenModel = isOpenSourceModel(connector); + expect(isOpenModel).toEqual(true); + }); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/routes/utils.ts b/x-pack/plugins/elastic_assistant/server/routes/utils.ts index 651a809e1a56e..5811109b94ede 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/utils.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/utils.ts @@ -19,6 +19,11 @@ import { ActionsClientSimpleChatModel, ActionsClientChatVertexAI, } from '@kbn/langchain/server'; +import { Connector } from '@kbn/actions-plugin/server/application/connector/types'; +import { + OPENAI_CHAT_URL, + OpenAiProviderType, +} from '@kbn/stack-connectors-plugin/common/openai/constants'; import { CustomHttpRequestError } from './custom_http_request_error'; export interface OutputError { @@ -189,3 +194,26 @@ export const getLlmClass = (llmType?: string, bedrockChatEnabled?: boolean) => : llmType === 'gemini' && bedrockChatEnabled ? ActionsClientChatVertexAI : ActionsClientSimpleChatModel; + +export const isOpenSourceModel = (connector?: Connector): boolean => { + if (connector == null) { + return false; + } + + const llmType = getLlmType(connector.actionTypeId); + const connectorApiUrl = connector.config?.apiUrl + ? (connector.config.apiUrl as string) + : undefined; + const connectorApiProvider = connector.config?.apiProvider + ? (connector.config?.apiProvider as OpenAiProviderType) + : undefined; + + const isOpenAiType = llmType === 'openai'; + const isOpenAI = + isOpenAiType && + (!connectorApiUrl || + connectorApiUrl === OPENAI_CHAT_URL || + connectorApiProvider === OpenAiProviderType.AzureAi); + + return isOpenAiType && !isOpenAI; +}; diff --git a/x-pack/plugins/elastic_assistant/server/types.ts b/x-pack/plugins/elastic_assistant/server/types.ts index af8d019539a66..9062bc5a434b1 100755 --- a/x-pack/plugins/elastic_assistant/server/types.ts +++ b/x-pack/plugins/elastic_assistant/server/types.ts @@ -244,6 +244,7 @@ export interface AssistantToolParams { kbDataClient?: AIAssistantKnowledgeBaseDataClient; langChainTimeout?: number; llm?: ActionsClientLlm | AssistantToolLlm; + isOssModel?: boolean; logger: Logger; modelExists: boolean; onNewReplacements?: (newReplacements: Replacements) => void; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/common.ts b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/common.ts new file mode 100644 index 0000000000000..ee2bee8fab806 --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/common.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 const getPromptSuffixForOssModel = (toolName: string) => ` + When using ${toolName} tool ALWAYS pass the user's questions directly as input into the tool. + + Always return value from ${toolName} tool as is. + + The ES|QL query should ALWAYS be wrapped in triple backticks ("\`\`\`esql"). Add a new line character right before the triple backticks. + + It is important that ES|QL query is preceeded by a new line.`; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.test.ts index 7eeb11e8df37a..589c95e8483bf 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.test.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.test.ts @@ -12,6 +12,7 @@ import type { KibanaRequest } from '@kbn/core-http-server'; import type { ExecuteConnectorRequestBody } from '@kbn/elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen'; import { loggerMock } from '@kbn/logging-mocks'; import type { AIAssistantKnowledgeBaseDataClient } from '@kbn/elastic-assistant-plugin/server/ai_assistant_data_clients/knowledge_base'; +import { getPromptSuffixForOssModel } from './common'; describe('EsqlLanguageKnowledgeBaseTool', () => { const kbDataClient = jest.fn() as unknown as AIAssistantKnowledgeBaseDataClient; @@ -108,5 +109,27 @@ describe('EsqlLanguageKnowledgeBaseTool', () => { expect(tool.tags).toEqual(['esql', 'query-generation', 'knowledge-base']); }); + + it('should return tool with the expected description for OSS model', () => { + const tool = ESQL_KNOWLEDGE_BASE_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: true, + isOssModel: true, + ...rest, + }) as DynamicTool; + + expect(tool.description).toContain(getPromptSuffixForOssModel('ESQLKnowledgeBaseTool')); + }); + + it('should return tool with the expected description for non-OSS model', () => { + const tool = ESQL_KNOWLEDGE_BASE_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: true, + isOssModel: false, + ...rest, + }) as DynamicTool; + + expect(tool.description).not.toContain(getPromptSuffixForOssModel('ESQLKnowledgeBaseTool')); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.ts index 6bf116c28719a..37e037898cd20 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.ts @@ -14,12 +14,15 @@ import { z } from '@kbn/zod'; import type { AssistantTool, AssistantToolParams } from '@kbn/elastic-assistant-plugin/server'; import { ESQL_RESOURCE } from '@kbn/elastic-assistant-plugin/server/routes/knowledge_base/constants'; import { APP_UI_ID } from '../../../../common'; +import { getPromptSuffixForOssModel } from './common'; + +const TOOL_NAME = 'ESQLKnowledgeBaseTool'; const toolDetails = { + id: 'esql-knowledge-base-tool', + name: TOOL_NAME, description: 'Call this for knowledge on how to build an ESQL query, or answer questions about the ES|QL query language. Input must always be the user query on a single line, with no other text. Your answer will be parsed as JSON, so never use quotes within the output and instead use backticks. Do not add any additional text to describe your output.', - id: 'esql-knowledge-base-tool', - name: 'ESQLKnowledgeBaseTool', }; export const ESQL_KNOWLEDGE_BASE_TOOL: AssistantTool = { ...toolDetails, @@ -31,12 +34,13 @@ export const ESQL_KNOWLEDGE_BASE_TOOL: AssistantTool = { getTool(params: AssistantToolParams) { if (!this.isSupported(params)) return null; - const { kbDataClient } = params as AssistantToolParams; + const { kbDataClient, isOssModel } = params as AssistantToolParams; if (kbDataClient == null) return null; return new DynamicStructuredTool({ name: toolDetails.name, - description: toolDetails.description, + description: + toolDetails.description + (isOssModel ? getPromptSuffixForOssModel(TOOL_NAME) : ''), schema: z.object({ question: z.string().describe(`The user's exact question about ESQL`), }), diff --git a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/nl_to_esql_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/nl_to_esql_tool.test.ts new file mode 100644 index 0000000000000..f078bccb24a36 --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/nl_to_esql_tool.test.ts @@ -0,0 +1,162 @@ +/* + * 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 { RetrievalQAChain } from 'langchain/chains'; +import type { DynamicTool } from '@langchain/core/tools'; +import { NL_TO_ESQL_TOOL } from './nl_to_esql_tool'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { ExecuteConnectorRequestBody } from '@kbn/elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen'; +import { loggerMock } from '@kbn/logging-mocks'; +import { getPromptSuffixForOssModel } from './common'; +import type { InferenceServerStart } from '@kbn/inference-plugin/server'; + +describe('NaturalLanguageESQLTool', () => { + const chain = {} as RetrievalQAChain; + const esClient = { + search: jest.fn().mockResolvedValue({}), + } as unknown as ElasticsearchClient; + const request = { + body: { + isEnabledKnowledgeBase: false, + alertsIndexPattern: '.alerts-security.alerts-default', + allow: ['@timestamp', 'cloud.availability_zone', 'user.name'], + allowReplacement: ['user.name'], + replacements: { key: 'value' }, + size: 20, + }, + } as unknown as KibanaRequest; + const logger = loggerMock.create(); + const inference = {} as InferenceServerStart; + const connectorId = 'fake-connector'; + const rest = { + chain, + esClient, + logger, + request, + inference, + connectorId, + }; + + describe('isSupported', () => { + it('returns false if isEnabledKnowledgeBase is false', () => { + const params = { + isEnabledKnowledgeBase: false, + modelExists: true, + ...rest, + }; + + expect(NL_TO_ESQL_TOOL.isSupported(params)).toBe(false); + }); + + it('returns false if modelExists is false (the ELSER model is not installed)', () => { + const params = { + isEnabledKnowledgeBase: true, + modelExists: false, // <-- ELSER model is not installed + ...rest, + }; + + expect(NL_TO_ESQL_TOOL.isSupported(params)).toBe(false); + }); + + it('returns true if isEnabledKnowledgeBase and modelExists are true', () => { + const params = { + isEnabledKnowledgeBase: true, + modelExists: true, + ...rest, + }; + + expect(NL_TO_ESQL_TOOL.isSupported(params)).toBe(true); + }); + }); + + describe('getTool', () => { + it('returns null if isEnabledKnowledgeBase is false', () => { + const tool = NL_TO_ESQL_TOOL.getTool({ + isEnabledKnowledgeBase: false, + modelExists: true, + ...rest, + }); + + expect(tool).toBeNull(); + }); + + it('returns null if modelExists is false (the ELSER model is not installed)', () => { + const tool = NL_TO_ESQL_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: false, // <-- ELSER model is not installed + ...rest, + }); + + expect(tool).toBeNull(); + }); + + it('returns null if inference plugin is not provided', () => { + const tool = NL_TO_ESQL_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: true, + ...rest, + inference: undefined, + }); + + expect(tool).toBeNull(); + }); + + it('returns null if connectorId is not provided', () => { + const tool = NL_TO_ESQL_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: true, + ...rest, + connectorId: undefined, + }); + + expect(tool).toBeNull(); + }); + + it('should return a Tool instance if isEnabledKnowledgeBase and modelExists are true', () => { + const tool = NL_TO_ESQL_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: true, + ...rest, + }); + + expect(tool?.name).toEqual('NaturalLanguageESQLTool'); + }); + + it('should return a tool with the expected tags', () => { + const tool = NL_TO_ESQL_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: true, + ...rest, + }) as DynamicTool; + + expect(tool.tags).toEqual(['esql', 'query-generation', 'knowledge-base']); + }); + + it('should return tool with the expected description for OSS model', () => { + const tool = NL_TO_ESQL_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: true, + isOssModel: true, + ...rest, + }) as DynamicTool; + + expect(tool.description).toContain(getPromptSuffixForOssModel('NaturalLanguageESQLTool')); + }); + + it('should return tool with the expected description for non-OSS model', () => { + const tool = NL_TO_ESQL_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: true, + isOssModel: false, + ...rest, + }) as DynamicTool; + + expect(tool.description).not.toContain(getPromptSuffixForOssModel('NaturalLanguageESQLTool')); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/nl_to_esql_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/nl_to_esql_tool.ts index a26d16607ac46..96b865efeaed4 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/nl_to_esql_tool.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/nl_to_esql_tool.ts @@ -11,6 +11,7 @@ import type { AssistantTool, AssistantToolParams } from '@kbn/elastic-assistant- import { lastValueFrom } from 'rxjs'; import { naturalLanguageToEsql } from '@kbn/inference-plugin/server'; import { APP_UI_ID } from '../../../../common'; +import { getPromptSuffixForOssModel } from './common'; export type ESQLToolParams = AssistantToolParams; @@ -37,7 +38,7 @@ export const NL_TO_ESQL_TOOL: AssistantTool = { getTool(params: ESQLToolParams) { if (!this.isSupported(params)) return null; - const { connectorId, inference, logger, request } = params as ESQLToolParams; + const { connectorId, inference, logger, request, isOssModel } = params as ESQLToolParams; if (inference == null || connectorId == null) return null; const callNaturalLanguageToEsql = async (question: string) => { @@ -46,6 +47,7 @@ export const NL_TO_ESQL_TOOL: AssistantTool = { client: inference.getClient({ request }), connectorId, input: question, + ...(isOssModel ? { functionCalling: 'simulated' } : {}), logger: { debug: (source) => { logger.debug(typeof source === 'function' ? source() : source); @@ -57,7 +59,8 @@ export const NL_TO_ESQL_TOOL: AssistantTool = { return new DynamicStructuredTool({ name: toolDetails.name, - description: toolDetails.description, + description: + toolDetails.description + (isOssModel ? getPromptSuffixForOssModel(TOOL_NAME) : ''), schema: z.object({ question: z.string().describe(`The user's exact question about ESQL`), }), From 55c2fd7fc1ef292961d5d69d20d1711b1fbbd468 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Mon, 7 Oct 2024 17:20:27 -0500 Subject: [PATCH 07/45] [Vega] Fix element sizing issues in fullscreen mode (#194330) - Fixes #194011 where a Vega visualization does not respect the enclosing element dimensions. - Fixes #194861 where resizing Vega visualization panel fails to update to the latest panel dimensions causing scroll bars. --- ....snap => vega_visualization.test.tsx.snap} | 4 +- .../public/components/vega_vis_component.tsx | 30 +++-------- .../vega/public/vega_view/vega_base_view.js | 12 ++--- ...on.test.js => vega_visualization.test.tsx} | 53 ++++++++++--------- .../vega/public/vega_visualization.ts | 5 +- 5 files changed, 44 insertions(+), 60 deletions(-) rename src/plugins/vis_types/vega/public/__snapshots__/{vega_visualization.test.js.snap => vega_visualization.test.tsx.snap} (77%) rename src/plugins/vis_types/vega/public/{vega_visualization.test.js => vega_visualization.test.tsx} (73%) diff --git a/src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.js.snap b/src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.tsx.snap similarity index 77% rename from src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.js.snap rename to src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.tsx.snap index a81418c79bb0b..dad12b304efdf 100644 --- a/src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.js.snap +++ b/src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.tsx.snap @@ -2,6 +2,6 @@ exports[`VegaVisualizations VegaVisualization - basics should show vega graph (may fail in dev env) 1`] = `"
"`; -exports[`VegaVisualizations VegaVisualization - basics should show vegalite graph and update on resize (may fail in dev env) 1`] = `"
  • \\"width\\" and \\"height\\" params are ignored because \\"autosize\\" is enabled. Set \\"autosize\\": \\"none\\" to disable
"`; +exports[`VegaVisualizations VegaVisualization - basics should show vegalite graph and update on resize (may fail in dev env) 1`] = `"
  • \\"width\\" and \\"height\\" params are ignored because \\"autosize\\" is enabled. Set \\"autosize\\": \\"none\\" to disable
"`; -exports[`VegaVisualizations VegaVisualization - basics should show vegalite graph and update on resize (may fail in dev env) 2`] = `"
  • \\"width\\" and \\"height\\" params are ignored because \\"autosize\\" is enabled. Set \\"autosize\\": \\"none\\" to disable
"`; +exports[`VegaVisualizations VegaVisualization - basics should show vegalite graph and update on resize (may fail in dev env) 2`] = `"
  • \\"width\\" and \\"height\\" params are ignored because \\"autosize\\" is enabled. Set \\"autosize\\": \\"none\\" to disable
"`; diff --git a/src/plugins/vis_types/vega/public/components/vega_vis_component.tsx b/src/plugins/vis_types/vega/public/components/vega_vis_component.tsx index b7a68593ff5dc..7b92bf085179c 100644 --- a/src/plugins/vis_types/vega/public/components/vega_vis_component.tsx +++ b/src/plugins/vis_types/vega/public/components/vega_vis_component.tsx @@ -7,9 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { useEffect, useRef, useMemo, useCallback } from 'react'; +import React, { useEffect, useRef, useCallback } from 'react'; import { EuiResizeObserver, EuiResizeObserverProps, useEuiTheme } from '@elastic/eui'; -import { throttle } from 'lodash'; import type { IInterpreterRenderHandlers, RenderMode } from '@kbn/expressions-plugin/common'; import { createVegaVisualization } from '../vega_visualization'; @@ -28,8 +27,6 @@ interface VegaVisComponentProps { type VegaVisController = InstanceType>; -const THROTTLE_INTERVAL = 300; - export const VegaVisComponent = ({ visData, fireEvent, @@ -64,26 +61,11 @@ export const VegaVisComponent = ({ } }, [renderComplete, visData]); - const resizeChart = useMemo( - () => - throttle( - (dimensions) => { - visController.current?.resize(dimensions); - }, - THROTTLE_INTERVAL, - { leading: false, trailing: true } - ), - [] - ); - - const onContainerResize: EuiResizeObserverProps['onResize'] = useCallback( - (dimensions) => { - if (renderCompleted.current) { - resizeChart(dimensions); - } - }, - [resizeChart] - ); + const onContainerResize: EuiResizeObserverProps['onResize'] = useCallback((dimensions) => { + if (renderCompleted.current) { + visController.current?.resize(dimensions); + } + }, []); const euiTheme = useEuiTheme(); diff --git a/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js b/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js index c2efcced1bb78..d517e593e2227 100644 --- a/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js +++ b/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js @@ -282,9 +282,9 @@ export class VegaBaseView { } } - async resize() { + async resize(dimensions) { if (this._parser.useResize && this._view) { - this.updateVegaSize(this._view); + this.updateVegaSize(this._view, dimensions); await this._view.runAsync(); // The derived class should create this method @@ -293,12 +293,8 @@ export class VegaBaseView { } updateVegaSize(view, dimensions) { - const width = Math.floor( - Math.max(0, dimensions?.width ?? this._container.getBoundingClientRect().width) - ); - const height = Math.floor( - Math.max(0, dimensions?.height ?? this._container.getBoundingClientRect().height) - ); + const width = Math.floor(Math.max(0, dimensions?.width ?? this._container.clientWidth - 1)); + const height = Math.floor(Math.max(0, dimensions?.height ?? this._container.clientHeight - 1)); if (view.width() !== width || view.height() !== height) { view.width(width).height(height); diff --git a/src/plugins/vis_types/vega/public/vega_visualization.test.js b/src/plugins/vis_types/vega/public/vega_visualization.test.tsx similarity index 73% rename from src/plugins/vis_types/vega/public/vega_visualization.test.js rename to src/plugins/vis_types/vega/public/vega_visualization.test.tsx index 3d86b3ccdca07..ab51db562f4f2 100644 --- a/src/plugins/vis_types/vega/public/vega_visualization.test.js +++ b/src/plugins/vis_types/vega/public/vega_visualization.test.tsx @@ -8,8 +8,9 @@ */ import 'jest-canvas-mock'; +import { render, screen } from '@testing-library/react'; -import { createVegaVisualization } from './vega_visualization'; +import { VegaVisType, createVegaVisualization } from './vega_visualization'; import vegaliteGraph from './test_utils/vegalite_graph.json'; import vegaGraph from './test_utils/vega_graph.json'; @@ -21,37 +22,41 @@ import { setInjectedVars, setData, setNotifications } from './services'; import { coreMock } from '@kbn/core/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import { VegaVisualizationDependencies } from './plugin'; +import React from 'react'; +import { TimeCache } from './data_model/time_cache'; jest.mock('./default_spec', () => ({ getDefaultSpec: () => jest.requireActual('./test_utils/default.spec.json'), })); describe('VegaVisualizations', () => { - let domNode; - let VegaVisualization; - let vegaVisualizationDependencies; - - let mockGetBoundingClientRect; - let mockedWidthValue; - let mockedHeightValue; + let domNode: HTMLDivElement; + let VegaVisualization: VegaVisType; + let vegaVisualizationDependencies: VegaVisualizationDependencies; + let mockedHeightValue: number; + let mockedWidthValue: number; const coreStart = coreMock.createStart(); const dataPluginStart = dataPluginMock.createStartContract(); const dataViewsPluginStart = dataViewPluginMocks.createStartContract(); const setupDOM = (width = 512, height = 512) => { + render(
); + domNode = screen.getByTestId('vega-vis-text'); + domNode.style.height = `${height}px`; + domNode.style.width = `${width}px`; mockedWidthValue = width; mockedHeightValue = height; - domNode = document.createElement('div'); - mockGetBoundingClientRect = jest - .spyOn(Element.prototype, 'getBoundingClientRect') - .mockImplementation(() => ({ width: mockedWidthValue, height: mockedHeightValue })); + // rtl does not update client dimensions on element, see https://github.com/testing-library/react-testing-library/issues/353 + jest + .spyOn(Element.prototype, 'clientHeight', 'get') + .mockImplementation(() => mockedHeightValue); + jest.spyOn(Element.prototype, 'clientWidth', 'get').mockImplementation(() => mockedWidthValue); }; - const mockGetServiceSettings = () => { - return {}; - }; + const mockGetServiceSettings = jest.fn() as any; beforeEach(() => { setInjectedVars({ @@ -68,7 +73,7 @@ describe('VegaVisualizations', () => { getServiceSettings: mockGetServiceSettings, }; - VegaVisualization = createVegaVisualization(vegaVisualizationDependencies); + VegaVisualization = createVegaVisualization(vegaVisualizationDependencies, 'view'); }); describe('VegaVisualization - basics', () => { @@ -76,15 +81,11 @@ describe('VegaVisualizations', () => { setupDOM(); }); - afterEach(() => { - mockGetBoundingClientRect.mockRestore(); - }); - test('should show vegalite graph and update on resize (may fail in dev env)', async () => { const mockedConsoleLog = jest.spyOn(console, 'log'); // mocked console.log to avoid messages in the console when running tests mockedConsoleLog.mockImplementation(() => {}); // comment this line when console logging for debugging comment this line - let vegaVis; + let vegaVis: InstanceType; try { vegaVis = new VegaVisualization(domNode, jest.fn()); @@ -95,8 +96,8 @@ describe('VegaVisualizations', () => { indexPatterns: dataViewsPluginStart, uiSettings: coreStart.uiSettings, }), - 0, - 0, + new TimeCache(dataPluginStart.query.timefilter.timefilter, 0), + {}, mockGetServiceSettings ); await vegaParser.parseAsync(); @@ -106,12 +107,14 @@ describe('VegaVisualizations', () => { mockedWidthValue = 256; mockedHeightValue = 250; + // @ts-expect-error - accessing private member await vegaVis.vegaView.resize(); expect(domNode.innerHTML).toMatchSnapshot(); } finally { vegaVis.destroy(); } + // eslint-disable-next-line no-console expect(console.log).toBeCalledTimes(2); mockedConsoleLog.mockRestore(); }); @@ -127,8 +130,8 @@ describe('VegaVisualizations', () => { indexPatterns: dataViewsPluginStart, uiSettings: coreStart.uiSettings, }), - 0, - 0, + new TimeCache(dataPluginStart.query.timefilter.timefilter, 0), + {}, mockGetServiceSettings ); await vegaParser.parseAsync(); diff --git a/src/plugins/vis_types/vega/public/vega_visualization.ts b/src/plugins/vis_types/vega/public/vega_visualization.ts index 2a1eb11f5e9c0..0060a1c0b7061 100644 --- a/src/plugins/vis_types/vega/public/vega_visualization.ts +++ b/src/plugins/vis_types/vega/public/vega_visualization.ts @@ -15,7 +15,10 @@ import { getNotifications, getData } from './services'; import type { VegaView } from './vega_view/vega_view'; import { createVegaStateRestorer } from './lib/vega_state_restorer'; -type VegaVisType = new (el: HTMLDivElement, fireEvent: IInterpreterRenderHandlers['event']) => { +export type VegaVisType = new ( + el: HTMLDivElement, + fireEvent: IInterpreterRenderHandlers['event'] +) => { render(visData: VegaParser): Promise; resize(dimensions?: { height: number; width: number }): Promise; destroy(): void; From 84d9187ab527400420118f9029e72f8d8009d4c5 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 00:23:33 +0200 Subject: [PATCH 08/45] Update dependency @redocly/cli to ^1.25.4 (main) (#195239) --- package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fb4b6ebcc44c9..a35db36de9b61 100644 --- a/package.json +++ b/package.json @@ -1491,7 +1491,7 @@ "@octokit/rest": "^17.11.2", "@parcel/watcher": "^2.1.0", "@playwright/test": "=1.46.0", - "@redocly/cli": "^1.25.3", + "@redocly/cli": "^1.25.4", "@statoscope/webpack-plugin": "^5.28.2", "@storybook/addon-a11y": "^6.5.16", "@storybook/addon-actions": "^6.5.16", diff --git a/yarn.lock b/yarn.lock index a0aa74c80e252..d244902ca5622 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8451,7 +8451,7 @@ require-from-string "^2.0.2" uri-js-replace "^1.0.1" -"@redocly/cli@^1.25.3": +"@redocly/cli@^1.25.4": version "1.25.5" resolved "https://registry.yarnpkg.com/@redocly/cli/-/cli-1.25.5.tgz#258f6d23e8298814518ec4d24d023c1e21e3b081" integrity sha512-sFh4A8wqwuig7mF/nYNVIyxSfKKZikWC+uVH6OB1IepYQXNsHFaLAU1VaNI9gS5mMvWmYx5SEuSCVB9LaNFBhw== From 28d6a22263ea1f60d9b17aacde7fa3517efd244e Mon Sep 17 00:00:00 2001 From: Thom Heymann <190132+thomheymann@users.noreply.github.com> Date: Mon, 7 Oct 2024 23:31:52 +0100 Subject: [PATCH 09/45] [Observability Onboarding] Update onboarding landing page (#194565) Resolves https://github.com/elastic/observability-dev/issues/3775 Resolves https://github.com/elastic/kibana/issues/192949 ## Summary Updates the "add data" page according to new design Screenshot 2024-10-01 at 12 36 15 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../onboarding_flow_form.tsx | 255 ++++++++++-------- .../application/onboarding_flow_form/types.ts | 2 +- ...y.ts => use_custom_cards_for_category.tsx} | 210 +++++++-------- .../use_virtual_search_results.ts | 22 +- .../application/packages_list/index.tsx | 35 +-- .../use_integration_card_list.ts | 8 +- .../public/application/packages_list/utils.ts | 21 -- .../public/application/shared/logo_icon.tsx | 6 +- .../public/assets/apple.svg | 9 + .../public/assets/linux.svg | 9 + .../translations/translations/fr-FR.json | 7 - .../translations/translations/ja-JP.json | 7 - .../translations/translations/zh-CN.json | 7 - 13 files changed, 308 insertions(+), 290 deletions(-) rename x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/{use_custom_cards_for_category.ts => use_custom_cards_for_category.tsx} (66%) delete mode 100644 x-pack/plugins/observability_solution/observability_onboarding/public/application/packages_list/utils.ts create mode 100644 x-pack/plugins/observability_solution/observability_onboarding/public/assets/apple.svg create mode 100644 x-pack/plugins/observability_solution/observability_onboarding/public/assets/linux.svg diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/onboarding_flow_form.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/onboarding_flow_form.tsx index 42f3dbd6f09f7..01a1e066c4ddb 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/onboarding_flow_form.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/onboarding_flow_form.tsx @@ -10,7 +10,6 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import type { FunctionComponent } from 'react'; import { - EuiAvatar, EuiCheckableCard, EuiFlexGroup, EuiFlexItem, @@ -21,12 +20,13 @@ import { useGeneratedHtmlId, useEuiTheme, EuiBadge, + EuiFlexGrid, } from '@elastic/eui'; +import { css } from '@emotion/react'; import { useSearchParams } from 'react-router-dom-v5-compat'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { OnboardingFlowPackageList } from '../packages_list'; -import { useCustomMargin } from '../shared/use_custom_margin'; import { Category } from './types'; import { useCustomCardsForCategory } from './use_custom_cards_for_category'; import { useVirtualSearchResults } from './use_virtual_search_results'; @@ -44,51 +44,63 @@ interface UseCaseOption { export const OnboardingFlowForm: FunctionComponent = () => { const options: UseCaseOption[] = [ { - id: 'logs', + id: 'host', label: i18n.translate( - 'xpack.observability_onboarding.experimentalOnboardingFlow.euiCheckableCard.collectAndAnalyzeMyLabel', - { defaultMessage: 'Collect and analyze logs' } + 'xpack.observability_onboarding.experimentalOnboardingFlow.euiCheckableCard.hostLabel', + { defaultMessage: 'Host' } ), description: i18n.translate( - 'xpack.observability_onboarding.onboardingFlowForm.detectPatternsAndOutliersLabel', + 'xpack.observability_onboarding.onboardingFlowForm.hostDescription', { defaultMessage: - 'Detect patterns, gain insights from logs, get alerted when surpassing error thresholds', + 'Monitor your host and the services running on it, set-up SLO, get alerted, remediate performance issues', } ), - logos: ['azure', 'aws', 'nginx', 'gcp'], - showIntegrationsBadge: true, + logos: ['kubernetes', 'opentelemetry', 'apache', 'mysql'], }, { - id: 'apm', + id: 'kubernetes', label: i18n.translate( - 'xpack.observability_onboarding.experimentalOnboardingFlow.euiCheckableCard.monitorMyApplicationPerformanceLabel', - { defaultMessage: 'Monitor my application performance' } + 'xpack.observability_onboarding.experimentalOnboardingFlow.euiCheckableCard.kubernetesLabel', + { defaultMessage: 'Kubernetes' } ), description: i18n.translate( - 'xpack.observability_onboarding.onboardingFlowForm.captureAndAnalyzeDistributedLabel', + 'xpack.observability_onboarding.onboardingFlowForm.kubernetesDescription', { defaultMessage: - 'Catch application problems, get alerted on performance issues or SLO breaches, expedite root cause analysis and remediation', + 'Observe your Kubernetes cluster, and your container workloads using logs, metrics, traces and profiling data', } ), - logos: ['opentelemetry', 'java', 'javascript', 'dotnet'], + logos: ['kubernetes', 'opentelemetry'], }, { - id: 'infra', + id: 'application', label: i18n.translate( - 'xpack.observability_onboarding.experimentalOnboardingFlow.euiCheckableCard.monitorMyInfrastructureLabel', - { defaultMessage: 'Monitor infrastructure' } + 'xpack.observability_onboarding.experimentalOnboardingFlow.euiCheckableCard.applicationLabel', + { defaultMessage: 'Application' } ), description: i18n.translate( - 'xpack.observability_onboarding.onboardingFlowForm.builtOnPowerfulElasticsearchLabel', + 'xpack.observability_onboarding.onboardingFlowForm.applicationDescription', { defaultMessage: - 'Check my system’s health, get alerted on performance issues or SLO breaches, expedite root cause analysis and remediation', + 'Monitor the frontend and backend application that you have developed, set-up synthetic monitors', } ), - logos: ['kubernetes', 'prometheus', 'docker', 'opentelemetry'], - showIntegrationsBadge: true, + logos: ['opentelemetry', 'java', 'javascript', 'dotnet'], + }, + { + id: 'cloud', + label: i18n.translate( + 'xpack.observability_onboarding.experimentalOnboardingFlow.euiCheckableCard.cloudLabel', + { defaultMessage: 'Cloud' } + ), + description: i18n.translate( + 'xpack.observability_onboarding.onboardingFlowForm.cloudDescription', + { + defaultMessage: 'Ingest telemetry data from the Cloud for your applications and services', + } + ), + logos: ['azure', 'aws', 'gcp'], }, ]; @@ -97,7 +109,6 @@ export const OnboardingFlowForm: FunctionComponent = () => { context: { isCloud }, }, } = useKibana(); - const customMargin = useCustomMargin(); const radioGroupId = useGeneratedHtmlId({ prefix: 'onboardingCategory' }); const categorySelectorTitleId = useGeneratedHtmlId(); const packageListTitleId = useGeneratedHtmlId(); @@ -152,33 +163,72 @@ export const OnboardingFlowForm: FunctionComponent = () => { return ( - - - + + + {i18n.translate( + 'xpack.observability_onboarding.experimentalOnboardingFlow.strong.startCollectingYourDataLabel', + { + defaultMessage: 'What do you want to monitor?', + } + )} + + + + {options.map((option) => ( {option.label}} + label={ + <> + + {option.label} + + {/* The description and logo icons are passed into `label` prop instead of `children` to ensure they are clickable */} + + + {option.description} + + {(option.logos || option.showIntegrationsBadge) && ( + <> + + + {option.logos?.map((logo) => ( + + + + ))} + {option.showIntegrationsBadge && ( + + + + )} + + + )} + + } checked={option.id === searchParams.get('category')} /** * onKeyDown and onKeyUp handlers disable @@ -204,54 +254,62 @@ export const OnboardingFlowForm: FunctionComponent = () => { ); } }} - > - - {option.description} - - {(option.logos || option.showIntegrationsBadge) && ( - <> - - - {option.logos?.map((logo) => ( - - - - ))} - {option.showIntegrationsBadge && ( - - - - )} - - - )} - + css={css` + flex-grow: 1; + + & > .euiPanel { + display: flex; + + & > .euiCheckableCard__label { + display: flex; + flex-direction: column; + } + } + `} + /> ))} - + {/* Hiding element instead of not rending these elements in order to preload available packages on page load */}