diff --git a/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/index.tsx b/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/index.tsx index 50d0ce4bdf7371e..fefcbcaf3bb547f 100644 --- a/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/index.tsx @@ -14,6 +14,7 @@ import { import { apmLabsButton } from '@kbn/observability-plugin/common'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { ObservabilityAIAssistantActionMenuItem } from '@kbn/observability-ai-assistant-plugin/public'; import { getAlertingCapabilities } from '../../../alerting/utils/get_alerting_capabilities'; import { getLegacyApmHref } from '../../../shared/links/apm/apm_link'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; @@ -96,6 +97,7 @@ export function ApmHeaderActionMenu() { })} + ); } diff --git a/x-pack/plugins/exploratory_view/kibana.jsonc b/x-pack/plugins/exploratory_view/kibana.jsonc index 8c30a5c5b01ff8c..2c5807a2bbd4136 100644 --- a/x-pack/plugins/exploratory_view/kibana.jsonc +++ b/x-pack/plugins/exploratory_view/kibana.jsonc @@ -22,7 +22,8 @@ "security", "share", "triggersActionsUi", - "unifiedSearch" + "unifiedSearch", + "observabilityAIAssistant" ], "optionalPlugins": ["discover", "embeddable", "home", "licensing", "spaces", "usageCollection"], "requiredBundles": [ diff --git a/x-pack/plugins/exploratory_view/public/application/index.tsx b/x-pack/plugins/exploratory_view/public/application/index.tsx index c5daebeac0d3964..83cf16274f9e755 100644 --- a/x-pack/plugins/exploratory_view/public/application/index.tsx +++ b/x-pack/plugins/exploratory_view/public/application/index.tsx @@ -19,6 +19,7 @@ import { } from '@kbn/kibana-react-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; +import { ObservabilityAIAssistantProvider } from '@kbn/observability-ai-assistant-plugin/public'; import { PluginContext } from '../context/plugin_context'; import { routes } from '../routes'; import { ExploratoryViewPublicPluginsStart } from '../plugin'; @@ -70,34 +71,41 @@ export const renderApp = ({ const ApplicationUsageTrackingProvider = usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment; + const aiAssistantService = plugins.observabilityAIAssistant; + ReactDOM.render( - - + - - - - - - - - - - - + + + + + + + + + + + + + , diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx index fdc97f4999fcdb8..f8e86388131aae5 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx @@ -9,6 +9,10 @@ import React, { useState } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { LensEmbeddableInput, TypedLensByValueInput } from '@kbn/lens-plugin/public'; +import { + ObservabilityAIAssistantActionMenuItem, + useObservabilityAIAssistantOptional, +} from '@kbn/observability-ai-assistant-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { EmbedAction } from '../../header/embed_action'; import { ObservabilityAppServices } from '../../../../../application/types'; @@ -29,6 +33,8 @@ export function ExpViewActionMenuContent({ const LensSaveModalComponent = lens.SaveModalComponent; + const service = useObservabilityAIAssistantOptional(); + return ( <> + {service?.isEnabled() ? ( + + + + ) : null} {isSaveOpen && lensAttributes && ( diff --git a/x-pack/plugins/exploratory_view/public/plugin.ts b/x-pack/plugins/exploratory_view/public/plugin.ts index 99b811c69d8c9b6..92f1efa9651856f 100644 --- a/x-pack/plugins/exploratory_view/public/plugin.ts +++ b/x-pack/plugins/exploratory_view/public/plugin.ts @@ -37,6 +37,7 @@ import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/publi import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-assistant-plugin/public'; import { getExploratoryViewEmbeddable } from './components/shared/exploratory_view/embeddable'; import { createExploratoryViewUrl } from './components/shared/exploratory_view/configurations/exploratory_view_url'; import getAppDataView from './utils/observability_data_views/get_app_data_view'; @@ -70,6 +71,7 @@ export interface ExploratoryViewPublicPluginsStart { usageCollection: UsageCollectionSetup; unifiedSearch: UnifiedSearchPublicPluginStart; home?: HomePublicPluginStart; + observabilityAIAssistant: ObservabilityAIAssistantPluginStart; } export type ExploratoryViewPublicSetup = ReturnType; diff --git a/x-pack/plugins/exploratory_view/tsconfig.json b/x-pack/plugins/exploratory_view/tsconfig.json index 18a9e70d007d703..6cb12bc9582def2 100644 --- a/x-pack/plugins/exploratory_view/tsconfig.json +++ b/x-pack/plugins/exploratory_view/tsconfig.json @@ -39,7 +39,8 @@ "@kbn/shared-ux-router", "@kbn/core-application-browser", "@kbn/observability-shared-plugin", - "@kbn/core-ui-settings-browser-mocks" + "@kbn/core-ui-settings-browser-mocks", + "@kbn/observability-ai-assistant-plugin" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/infra/public/pages/logs/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/page_content.tsx index 9e8fb413931a307..ca6c281a47309a9 100644 --- a/x-pack/plugins/infra/public/pages/logs/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/page_content.tsx @@ -11,6 +11,7 @@ import React, { useContext } from 'react'; import { Routes, Route } from '@kbn/shared-ux-router'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { HeaderMenuPortal, useLinkProps } from '@kbn/observability-shared-plugin/public'; +import { ObservabilityAIAssistantActionMenuItem } from '@kbn/observability-ai-assistant-plugin/public'; import { LazyAlertDropdownWrapper } from '../../alerting/log_threshold'; import { HelpCenterContent } from '../../components/help_center_content'; import { useReadOnlyBadge } from '../../hooks/use_readonly_badge'; @@ -81,6 +82,7 @@ export const LogsPageContent: React.FunctionComponent = () => { > {ADD_DATA_LABEL} + )} diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index 1ae6933b76d5546..bf854fb546ae1f4 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -14,6 +14,7 @@ import { Routes, Route } from '@kbn/shared-ux-router'; import { EuiErrorBoundary, EuiHeaderLinks, EuiHeaderLink } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { HeaderMenuPortal, useLinkProps } from '@kbn/observability-shared-plugin/public'; +import { ObservabilityAIAssistantActionMenuItem } from '@kbn/observability-ai-assistant-plugin/public'; import { MetricsSourceConfigurationProperties } from '../../../common/metrics_sources'; import { HelpCenterContent } from '../../components/help_center_content'; import { useReadOnlyBadge } from '../../hooks/use_readonly_badge'; @@ -89,6 +90,7 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { > {ADD_DATA_LABEL} + )} diff --git a/x-pack/plugins/observability/kibana.jsonc b/x-pack/plugins/observability/kibana.jsonc index a34eb3ffb0ae671..2ced468eb1f9896 100644 --- a/x-pack/plugins/observability/kibana.jsonc +++ b/x-pack/plugins/observability/kibana.jsonc @@ -22,6 +22,7 @@ "inspector", "lens", "observabilityShared", + "observabilityAIAssistant", "ruleRegistry", "triggersActionsUi", "security", diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index bf682b6bf7eff7e..f4be5a624786809 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -21,6 +21,7 @@ import { } from '@kbn/kibana-react-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; +import { ObservabilityAIAssistantProvider } from '@kbn/observability-ai-assistant-plugin/public'; import { HasDataContextProvider } from '../context/has_data_context/has_data_context'; import { PluginContext } from '../context/plugin_context/plugin_context'; import { ConfigSchema, ObservabilityPublicPluginsStart } from '../plugin'; @@ -100,32 +101,34 @@ export const renderApp = ({ kibanaVersion, }} > - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/x-pack/plugins/observability/public/pages/alert_details/alert_details.test.tsx b/x-pack/plugins/observability/public/pages/alert_details/alert_details.test.tsx index 4cc8895ae6a4c83..a5bb4cbb4aeb506 100644 --- a/x-pack/plugins/observability/public/pages/alert_details/alert_details.test.tsx +++ b/x-pack/plugins/observability/public/pages/alert_details/alert_details.test.tsx @@ -49,6 +49,7 @@ const mockKibana = () => { useKibanaMock.mockReturnValue({ services: { ...kibanaStartMock.startContract(), + theme: {}, cases: casesPluginMock.createStartContract(), http: { basePath: { diff --git a/x-pack/plugins/observability/public/pages/alert_details/alert_details.tsx b/x-pack/plugins/observability/public/pages/alert_details/alert_details.tsx index 7695a6d7d6758e7..c988d967fefa107 100644 --- a/x-pack/plugins/observability/public/pages/alert_details/alert_details.tsx +++ b/x-pack/plugins/observability/public/pages/alert_details/alert_details.tsx @@ -27,6 +27,7 @@ import { getTimeZone } from '../../utils/get_time_zone'; import { isAlertDetailsEnabledPerApp } from '../../utils/is_alert_details_enabled'; import { observabilityFeatureId } from '../../../common'; import { paths } from '../../../common/locators/paths'; +import { HeaderMenu } from '../overview/components/header_menu/header_menu'; interface AlertDetailsPathParams { alertId: string; @@ -137,6 +138,7 @@ export function AlertDetails() { }} data-test-subj="alertDetails" > + {AlertDetailsAppSection && rule && ( diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts.tsx index 363ad320ed5fbb3..cefc17fa87de6fb 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts.tsx @@ -31,6 +31,7 @@ import { calculateTimeRangeBucketSize } from '../overview/helpers/calculate_buck import { getAlertSummaryTimeRange } from '../../utils/alert_summary_widget'; import { observabilityAlertFeatureIds } from '../../../common/constants'; import { ALERTS_URL_STORAGE_KEY } from '../../../common/constants'; +import { HeaderMenu } from '../overview/components/header_menu/header_menu'; const ALERTS_SEARCH_BAR_ID = 'alerts-search-bar-o11y'; const ALERTS_PER_PAGE = 50; @@ -175,6 +176,7 @@ function InternalAlertsPage() { rightSideItems: renderRuleStats(ruleStats, manageRulesHref, ruleStatsLoading), }} > + + ) : ( diff --git a/x-pack/plugins/observability/public/pages/overview/components/header_menu/header_menu.tsx b/x-pack/plugins/observability/public/pages/overview/components/header_menu/header_menu.tsx index 4ba97e29fd831c9..111acb054e163dc 100644 --- a/x-pack/plugins/observability/public/pages/overview/components/header_menu/header_menu.tsx +++ b/x-pack/plugins/observability/public/pages/overview/components/header_menu/header_menu.tsx @@ -8,6 +8,10 @@ import { EuiHeaderLink, EuiHeaderLinks } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { + ObservabilityAIAssistantActionMenuItem, + useObservabilityAIAssistantOptional, +} from '@kbn/observability-ai-assistant-plugin/public'; import { useKibana } from '../../../../utils/kibana_react'; import { usePluginContext } from '../../../../hooks/use_plugin_context'; import HeaderMenuPortal from './header_menu_portal'; @@ -18,6 +22,8 @@ export function HeaderMenu(): React.ReactElement | null { appMountParameters: { setHeaderActionMenu }, } = usePluginContext(); + const aiAssistant = useObservabilityAIAssistantOptional(); + return ( @@ -28,6 +34,7 @@ export function HeaderMenu(): React.ReactElement | null { > {addDataLinkText} + {aiAssistant?.isEnabled() ? : null} ); diff --git a/x-pack/plugins/observability/public/pages/rule_details/rule_details.tsx b/x-pack/plugins/observability/public/pages/rule_details/rule_details.tsx index 71ffbc9ed9335fb..fcc562111dfe5f8 100644 --- a/x-pack/plugins/observability/public/pages/rule_details/rule_details.tsx +++ b/x-pack/plugins/observability/public/pages/rule_details/rule_details.tsx @@ -38,6 +38,7 @@ import { getDefaultAlertSummaryTimeRange, } from '../../utils/alert_summary_widget'; import type { AlertStatus } from '../../../common/typings'; +import { HeaderMenu } from '../overview/components/header_menu/header_menu'; export type TabId = typeof RULE_DETAILS_ALERTS_TAB | typeof RULE_DETAILS_EXECUTION_TAB; @@ -217,6 +218,7 @@ export function RuleDetailsPage() { ], }} > + ({ })); jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({ - appMountParameters: {} as AppMountParameters, + appMountParameters: { + setHeaderActionMenu: () => {}, + } as unknown as AppMountParameters, config: { unsafe: { slo: { enabled: false }, @@ -47,12 +49,6 @@ jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({ compositeSlo: { enabled: false, }, - aiAssistant: { - enabled: false, - feedback: { - enabled: false, - }, - }, }, observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), ObservabilityPageTemplate: KibanaPageTemplate, diff --git a/x-pack/plugins/observability/public/pages/rules/rules.tsx b/x-pack/plugins/observability/public/pages/rules/rules.tsx index 9b6e43501ebabec..2de5ebd13bd45fa 100644 --- a/x-pack/plugins/observability/public/pages/rules/rules.tsx +++ b/x-pack/plugins/observability/public/pages/rules/rules.tsx @@ -18,6 +18,7 @@ import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; import { useKibana } from '../../utils/kibana_react'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types'; +import { HeaderMenu } from '../overview/components/header_menu/header_menu'; export function RulesPage() { const { @@ -142,6 +143,7 @@ export function RulesPage() { }} data-test-subj="rulesPage" > + { useKibanaMock.mockReturnValue({ services: { + theme: {}, application: { navigateToUrl: mockNavigate }, charts: chartPluginMock.createStartContract(), http: { diff --git a/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx b/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx index e01abac1f869732..ac806e976e3367d 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx @@ -28,6 +28,7 @@ import type { SloDetailsPathParams } from './types'; import { AutoRefreshButton } from '../slos/components/auto_refresh_button'; import { FeedbackButton } from '../../components/slo/feedback_button/feedback_button'; import { useGetInstanceIdQueryParam } from './hooks/use_get_instance_id_query_param'; +import { HeaderMenu } from '../overview/components/header_menu/header_menu'; export function SloDetailsPage() { const { @@ -83,6 +84,7 @@ export function SloDetailsPage() { }} data-test-subj="sloDetailsPage" > + {isLoading && } {!isLoading && } diff --git a/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx b/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx index c35bcf036835778..06d3285f8945d84 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx @@ -66,6 +66,7 @@ const mockBasePathPrepend = jest.fn(); const mockKibana = () => { useKibanaMock.mockReturnValue({ services: { + theme: {}, application: { navigateToUrl: mockNavigate, }, diff --git a/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.tsx b/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.tsx index fa4dc433e3f5fea..a9da5c0c0ee9721 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.tsx @@ -19,6 +19,7 @@ import { useCapabilities } from '../../hooks/slo/use_capabilities'; import { useFetchSloGlobalDiagnosis } from '../../hooks/slo/use_fetch_global_diagnosis'; import { FeedbackButton } from '../../components/slo/feedback_button/feedback_button'; import { SloEditForm } from './components/slo_edit_form'; +import { HeaderMenu } from '../overview/components/header_menu/header_menu'; export function SloEditPage() { const { @@ -83,6 +84,7 @@ export function SloEditPage() { }} data-test-subj="slosEditPage" > + ); diff --git a/x-pack/plugins/observability/public/pages/slos/slos.test.tsx b/x-pack/plugins/observability/public/pages/slos/slos.test.tsx index 9f16cb39b9a0c86..09d5af66b55dc6e 100644 --- a/x-pack/plugins/observability/public/pages/slos/slos.test.tsx +++ b/x-pack/plugins/observability/public/pages/slos/slos.test.tsx @@ -67,6 +67,7 @@ const mockGetAddRuleFlyout = jest.fn().mockReturnValue(() =>
Add rule flyou const mockKibana = () => { useKibanaMock.mockReturnValue({ services: { + theme: {}, application: { navigateToUrl: mockNavigate }, charts: chartPluginMock.createSetupContract(), data: { diff --git a/x-pack/plugins/observability/public/pages/slos/slos.tsx b/x-pack/plugins/observability/public/pages/slos/slos.tsx index bae9f72b168a696..935aae8614a8876 100644 --- a/x-pack/plugins/observability/public/pages/slos/slos.tsx +++ b/x-pack/plugins/observability/public/pages/slos/slos.tsx @@ -20,6 +20,7 @@ import { AutoRefreshButton } from './components/auto_refresh_button'; import { HeaderTitle } from './components/header_title'; import { FeedbackButton } from '../../components/slo/feedback_button/feedback_button'; import { paths } from '../../../common/locators/paths'; +import { HeaderMenu } from '../overview/components/header_menu/header_menu'; export function SlosPage() { const { @@ -88,6 +89,7 @@ export function SlosPage() { }} data-test-subj="slosPage" > + ); diff --git a/x-pack/plugins/observability/public/pages/slos_welcome/slos_welcome.test.tsx b/x-pack/plugins/observability/public/pages/slos_welcome/slos_welcome.test.tsx index f4b7e1ec6225c67..155e49c3976fc1e 100644 --- a/x-pack/plugins/observability/public/pages/slos_welcome/slos_welcome.test.tsx +++ b/x-pack/plugins/observability/public/pages/slos_welcome/slos_welcome.test.tsx @@ -36,6 +36,7 @@ const mockNavigate = jest.fn(); const mockKibana = () => { useKibanaMock.mockReturnValue({ services: { + theme: {}, application: { navigateToUrl: mockNavigate }, http: { basePath: { diff --git a/x-pack/plugins/observability/public/pages/slos_welcome/slos_welcome.tsx b/x-pack/plugins/observability/public/pages/slos_welcome/slos_welcome.tsx index 5649a577c5e87ba..2f0ef6ad1389ee9 100644 --- a/x-pack/plugins/observability/public/pages/slos_welcome/slos_welcome.tsx +++ b/x-pack/plugins/observability/public/pages/slos_welcome/slos_welcome.tsx @@ -26,6 +26,7 @@ import { useFetchSloList } from '../../hooks/slo/use_fetch_slo_list'; import { paths } from '../../../common/locators/paths'; import illustration from './assets/illustration.svg'; import { useFetchSloGlobalDiagnosis } from '../../hooks/slo/use_fetch_global_diagnosis'; +import { HeaderMenu } from '../overview/components/header_menu/header_menu'; export function SlosWelcomePage() { const { @@ -57,6 +58,7 @@ export function SlosWelcomePage() { return hasSlosAndHasPermissions || isLoading ? null : ( + diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index f7c56d5d76e3587..0a5b238023b4ee7 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -51,6 +51,10 @@ import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { ExploratoryViewPublicStart } from '@kbn/exploratory-view-plugin/public'; +import { + ObservabilityAIAssistantPluginSetup, + ObservabilityAIAssistantPluginStart, +} from '@kbn/observability-ai-assistant-plugin/public'; import { RulesLocatorDefinition } from './locators/rules'; import { RuleDetailsLocatorDefinition } from './locators/rule_details'; import { SloDetailsLocatorDefinition } from './locators/slo_details'; @@ -100,6 +104,7 @@ export type ObservabilityPublicSetup = ReturnType; export interface ObservabilityPublicPluginsSetup { data: DataPublicPluginSetup; observabilityShared: ObservabilitySharedPluginSetup; + observabilityAIAssistant: ObservabilityAIAssistantPluginSetup; share: SharePluginSetup; triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; home?: HomePublicPluginSetup; @@ -120,6 +125,7 @@ export interface ObservabilityPublicPluginsStart { lens: LensPublicStart; licensing: LicensingPluginStart; observabilityShared: ObservabilitySharedPluginStart; + observabilityAIAssistant: ObservabilityAIAssistantPluginStart; ruleTypeRegistry: RuleTypeRegistryContract; security: SecurityPluginStart; share: SharePluginStart; diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index cdd4c30024ff47e..5004d592c588b0a 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -82,7 +82,8 @@ "@kbn/data-view-editor-plugin", "@kbn/actions-plugin", "@kbn/core-capabilities-common", - "@kbn/deeplinks-analytics" + "@kbn/deeplinks-analytics", + "@kbn/observability-ai-assistant-plugin" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability_ai_assistant/public/components/action_menu_item/action_menu_item.tsx b/x-pack/plugins/observability_ai_assistant/public/components/action_menu_item/action_menu_item.tsx new file mode 100644 index 000000000000000..69d6d37ca859a1d --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/components/action_menu_item/action_menu_item.tsx @@ -0,0 +1,70 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiHeaderLink } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useState } from 'react'; +import { useConversation } from '../../hooks/use_conversation'; +import { useObservabilityAIAssistant } from '../../hooks/use_observability_ai_assistant'; +import { EMPTY_CONVERSATION_TITLE } from '../../i18n'; +import { AssistantAvatar } from '../assistant_avatar'; +import { ChatFlyout } from '../chat/chat_flyout'; + +export function ObservabilityAIAssistantActionMenuItem() { + const service = useObservabilityAIAssistant(); + + const [conversationId, setConversationId] = useState(); + + const { conversation, displayedMessages, setDisplayedMessages, save } = + useConversation(conversationId); + + const [isOpen, setIsOpen] = useState(false); + + if (!service.isEnabled()) { + return null; + } + + return ( + <> + { + setIsOpen(() => true); + }} + > + + + + + + {i18n.translate('xpack.observabilityAiAssistant.actionMenuItemLabel', { + defaultMessage: 'AI Assistant', + })} + + + + { + setIsOpen(() => false); + }} + onChatComplete={(messages) => { + save(messages) + .then((nextConversation) => { + setConversationId(nextConversation.conversation.id); + }) + .catch(() => {}); + }} + onChatUpdate={(nextMessages) => { + setDisplayedMessages(nextMessages); + }} + /> + + ); +} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.stories.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.stories.tsx index befacf1ae391207..0dc504994100635 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.stories.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.stories.tsx @@ -8,20 +8,23 @@ import { ComponentStory } from '@storybook/react'; import React from 'react'; import { Observable } from 'rxjs'; +import { MessageRole } from '../../../common'; import { getSystemMessage } from '../../service/get_system_message'; import { ObservabilityAIAssistantService } from '../../types'; +import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator'; import { ChatBody as Component } from './chat_body'; export default { component: Component, title: 'app/Organisms/ChatBody', + decorators: [KibanaReactStorybookDecorator], }; type ChatBodyProps = React.ComponentProps; const Template: ComponentStory = (props: ChatBodyProps) => { return ( -
+
); @@ -29,7 +32,16 @@ const Template: ComponentStory = (props: ChatBodyProps) => { const defaultProps: ChatBodyProps = { title: 'My Conversation', - messages: [getSystemMessage()], + messages: [ + getSystemMessage(), + { + '@timestamp': new Date().toISOString(), + message: { + role: MessageRole.User, + content: `{"entries":[{"@timestamp":"2023-08-04T06:31:15.160Z","public":false,"confidence":"high","is_correction":false,"namespace":"default","text":"The user's name is Dario.","user":{"name":"elastic","id":"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0"},"ml":{"model_id":".elser_model_1"}},{"@timestamp":"2023-08-03T16:53:21.848Z","public":true,"confidence":"high","is_correction":false,"namespace":"default","text":"The RENAME command in ES|QL is used to rename a column. The syntax is 'RENAME = '. For example, 'FROM employees | KEEP first_name, last_name, still_hired | RENAME employed = still_hired' will rename the 'still_hired' column to 'employed'. If a column with the new name already exists, it will be replaced by the new column. Multiple columns can be renamed with a single RENAME command.","user":{"name":"elastic","id":"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0"},"ml":{"model_id":".elser_model_1"}},{"@timestamp":"2023-08-03T16:52:02.052Z","public":true,"confidence":"high","is_correction":false,"namespace":"default","text":"The KEEP command in ES|QL is used to specify what columns are returned and the order in which they are returned. To limit the columns that are returned, a comma-separated list of column names is used. The columns are then returned in the specified order. Wildcards can also be used to return all columns with a name that matches a pattern. For example, 'FROM employees | KEEP h*' will return all columns with a name that starts with an 'h'. The asterisk wildcard (*) by itself translates to all columns that do not match the other arguments.","user":{"name":"elastic","id":"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0"},"ml":{"model_id":".elser_model_1"}},{"@timestamp":"2023-08-03T16:55:18.984Z","public":true,"confidence":"high","is_correction":false,"namespace":"default","text":"The WHERE command in ES|QL is used to produce a table that contains all the rows from the input table for which the provided condition evaluates to true. For example, 'FROM employees | KEEP first_name, last_name, still_hired | WHERE still_hired == true' will return only the rows where 'still_hired' is true. WHERE supports various operators and functions for calculating values.","user":{"name":"elastic","id":"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0"},"ml":{"model_id":".elser_model_1"}},{"@timestamp":"2023-08-03T16:53:57.401Z","public":true,"confidence":"high","is_correction":false,"namespace":"default","text":"The SORT command in ES|QL is used to sort rows on one or more fields. The default sort order is ascending, but this can be explicitly set using ASC or DESC. For example, 'FROM employees | KEEP first_name, last_name, height | SORT height DESC' will sort the rows in descending order of height. Additional sort expressions can be provided to act as tie breakers. By default, null values are treated as being larger than any other value, meaning they are sorted last in an ascending order and first in a descending order. This can be changed by providing NULLS FIRST or NULLS LAST.","user":{"name":"elastic","id":"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0"},"ml":{"model_id":".elser_model_1"}},{"@timestamp":"2023-08-03T16:50:09.345Z","public":true,"confidence":"high","is_correction":false,"namespace":"default","text":"The EVAL command in ES|QL is used to append new columns to a table. For example, 'FROM employees | KEEP first_name, last_name, height | EVAL height_feet = height * 3.281, height_cm = height * 100' will append new columns 'height_feet' and 'height_cm' to the 'employees' table. If the specified column already exists, the existing column will be dropped, and the new column will be appended to the table. EVAL supports various functions for calculating values.","user":{"name":"elastic","id":"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0"},"ml":{"model_id":".elser_model_1"}},{"@timestamp":"2023-08-03T16:49:37.882Z","public":true,"confidence":"high","is_correction":false,"namespace":"default","text":"The ENRICH command in ES|QL is used to add data from existing indices to incoming records at query time. It requires an enrich policy to be executed, which defines a match field and a set of enrich fields. ENRICH looks for records in the enrich index based on the match field value. The matching key in the input dataset can be defined using 'ON '. If it’s not specified, the match will be performed on a field with the same name as the match field defined in the enrich policy. You can specify which attributes to be added to the result using 'WITH , ...' syntax. Attributes can also be renamed using 'WITH new_name='. By default, ENRICH will add all the enrich fields defined in the enrich policy to the result. In case of name collisions, the newly created fields will override the existing fields.","user":{"name":"elastic","id":"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0"},"ml":{"model_id":".elser_model_1"}},{"@timestamp":"2023-08-03T16:50:45.339Z","public":true,"confidence":"high","is_correction":false,"namespace":"default","text":"The GROK command in ES|QL enables you to extract structured data out of a string. GROK matches the string against patterns, based on regular expressions, and extracts the specified patterns as columns. For example, 'ROW a = "1953-01-23T12:15:00Z 127.0.0.1 some.email@foo.com 42" | GROK a "%{TIMESTAMP_ISO8601:date} %{IP:ip} %{EMAILADDRESS:email} %{NUMBER:num:int}" | KEEP date, ip, email, num' will extract the date, IP, email, and number from the string into separate columns. Refer to the grok processor documentation for the syntax of grok patterns.","user":{"name":"elastic","id":"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0"},"ml":{"model_id":".elser_model_1"}},{"@timestamp":"2023-08-03T16:44:22.647Z","public":true,"confidence":"high","is_correction":false,"namespace":"default","text":"The FROM source command in ES|QL returns a table with up to 10,000 documents from a data stream, index, or alias. Each row in the table represents a document, and each column corresponds to a field, which can be accessed by the name of that field. Date math can be used to refer to indices, aliases and data streams, which is useful for time series data. Comma-separated lists or wildcards can be used to query multiple data streams, indices, or aliases.","user":{"name":"elastic","id":"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0"},"ml":{"model_id":".elser_model_1"}},{"@timestamp":"2023-08-03T16:42:52.832Z","public":true,"confidence":"high","is_correction":false,"namespace":"default","text":"ES|QL, the Elasticsearch Query Language, is a query language designed for iterative data exploration. An ES|QL query consists of a series of commands, separated by pipes. Each query starts with a source command that produces a table, typically with data from Elasticsearch. This can be followed by one or more processing commands that modify the input table by adding, removing, or changing rows and columns. Processing commands can be chained together, with each command working on the output table of the previous command. The result of a query is the table produced by the final processing command. ES|QL can be used via the _esql endpoint, and results are returned as JSON by default. It can also be used in Kibana's Discover and Lens features for data exploration and visualization. Currently, ES|QL supports field types such as alias, boolean, date, ip, keyword family, double/float/half_float, long int/short/byte, and version.","user":{"name":"elastic","id":"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0"},"ml":{"model_id":".elser_model_1"}}]}`, + }, + }, + ], connectors: { connectors: [ { diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.tsx index d7deb33791cf51f..365db887e8bcbd2 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.tsx @@ -12,7 +12,6 @@ import { EuiLoadingSpinner, EuiPanel, EuiSpacer, - useEuiTheme, } from '@elastic/eui'; import { css } from '@emotion/css'; import type { AuthenticatedUser } from '@kbn/security-plugin/common'; @@ -21,7 +20,6 @@ import type { Message } from '../../../common/types'; import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors'; import { useTimeline } from '../../hooks/use_timeline'; import { ObservabilityAIAssistantService } from '../../types'; -import { HideExpandConversationListButton } from '../buttons/hide_expand_conversation_list_button'; import { MissingCredentialsCallout } from '../missing_credentials_callout'; import { ChatHeader } from './chat_header'; import { ChatPromptEditor } from './chat_prompt_editor'; @@ -29,6 +27,7 @@ import { ChatTimeline } from './chat_timeline'; const containerClassName = css` max-height: 100%; + max-width: 100%; `; const timelineClassName = css` @@ -46,8 +45,6 @@ export function ChatBody({ currentUser, service, connectorsManagementHref, - isConversationListExpanded, - onToggleExpandConversationList, onChatUpdate, onChatComplete, }: { @@ -57,13 +54,9 @@ export function ChatBody({ currentUser?: Pick; service: ObservabilityAIAssistantService; connectorsManagementHref: string; - isConversationListExpanded?: boolean; - onToggleExpandConversationList?: () => void; onChatUpdate: (messages: Message[]) => void; onChatComplete: (messages: Message[]) => void; }) { - const { euiTheme } = useEuiTheme(); - const timeline = useTimeline({ messages, connectors, @@ -120,21 +113,6 @@ export function ChatBody({ return ( - - {onToggleExpandConversationList ? ( - - - - ) : null} - diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_flyout.stories.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_flyout.stories.tsx index a3c273a5d4251e3..bcadebcf4c729c6 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_flyout.stories.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_flyout.stories.tsx @@ -21,7 +21,7 @@ type ChatFlyoutProps = React.ComponentProps; const Template: ComponentStory = (props: ChatFlyoutProps) => { return ( -
+
); diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_flyout.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_flyout.tsx index d2595354a4581b3..7afbbc8e5b89c62 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_flyout.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_flyout.tsx @@ -4,26 +4,39 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiFlyout } from '@elastic/eui'; -import React, { useState } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiFlyout, EuiLink, EuiPanel, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/css'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; import type { Message } from '../../../common/types'; import { useCurrentUser } from '../../hooks/use_current_user'; import { useGenAIConnectors } from '../../hooks/use_genai_connectors'; import { useKibana } from '../../hooks/use_kibana'; import { useObservabilityAIAssistant } from '../../hooks/use_observability_ai_assistant'; +import { useObservabilityAIAssistantRouter } from '../../hooks/use_observability_ai_assistant_router'; import { getConnectorsManagementHref } from '../../utils/get_connectors_management_href'; import { ChatBody } from './chat_body'; +const containerClassName = css` + max-height: 100%; +`; + export function ChatFlyout({ title, messages, + conversationId, isOpen, onClose, + onChatUpdate, + onChatComplete, }: { title: string; messages: Message[]; + conversationId?: string; isOpen: boolean; onClose: () => void; + onChatUpdate?: (messages: Message[]) => void; + onChatComplete?: (messages: Message[]) => void; }) { const connectors = useGenAIConnectors(); @@ -33,13 +46,46 @@ export function ChatFlyout({ services: { http }, } = useKibana(); - const [isConversationListExpanded, setIsConversationListExpanded] = useState(false); - const service = useObservabilityAIAssistant(); + const { euiTheme } = useEuiTheme(); + + const router = useObservabilityAIAssistantRouter(); + return isOpen ? ( - + + + + {conversationId ? ( + + {i18n.translate('xpack.observabilityAiAssistant.conversationDeepLinkLabel', { + defaultMessage: 'Open conversation', + })} + + ) : ( + + {i18n.translate('xpack.observabilityAiAssistant.conversationListDeepLinkLabel', { + defaultMessage: 'Go to conversations', + })} + + )} + + - setIsConversationListExpanded(!isConversationListExpanded) - } - onChatComplete={() => {}} - onChatUpdate={() => {}} + onChatUpdate={(nextMessages) => { + if (onChatUpdate) { + onChatUpdate(nextMessages); + } + }} + onChatComplete={(nextMessages) => { + if (onChatComplete) { + onChatComplete(nextMessages); + } + }} /> diff --git a/x-pack/plugins/observability_ai_assistant/public/hooks/__storybook_mocks__/use_kibana.ts b/x-pack/plugins/observability_ai_assistant/public/hooks/__storybook_mocks__/use_kibana.ts index 41239c6e4af1ac7..d9ab341dce80d8a 100644 --- a/x-pack/plugins/observability_ai_assistant/public/hooks/__storybook_mocks__/use_kibana.ts +++ b/x-pack/plugins/observability_ai_assistant/public/hooks/__storybook_mocks__/use_kibana.ts @@ -15,6 +15,11 @@ export function useKibana() { } }, }, + http: { + basePath: { + prepend: () => '', + }, + }, notifications: { toasts: { addSuccess: () => {}, diff --git a/x-pack/plugins/observability_ai_assistant/public/hooks/__storybook_mocks__/use_observability_ai_assistant.ts b/x-pack/plugins/observability_ai_assistant/public/hooks/__storybook_mocks__/use_observability_ai_assistant.ts new file mode 100644 index 000000000000000..15aa5d0428ab3da --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/hooks/__storybook_mocks__/use_observability_ai_assistant.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function useObservabilityAIAssistant() { + return {}; +} diff --git a/x-pack/plugins/observability_ai_assistant/public/hooks/use_conversation.ts b/x-pack/plugins/observability_ai_assistant/public/hooks/use_conversation.ts new file mode 100644 index 000000000000000..39e64c5c95666ba --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/hooks/use_conversation.ts @@ -0,0 +1,112 @@ +/* + * 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'; +import { merge, omit } from 'lodash'; +import { Dispatch, SetStateAction, useState } from 'react'; +import type { Conversation, Message } from '../../common'; +import type { ConversationCreateRequest } from '../../common/types'; +import { useAbortableAsync, type AbortableAsyncState } from './use_abortable_async'; +import { useKibana } from './use_kibana'; +import { useObservabilityAIAssistant } from './use_observability_ai_assistant'; +import { createNewConversation } from './use_timeline'; + +export function useConversation(conversationId?: string): { + conversation: AbortableAsyncState; + displayedMessages: Message[]; + setDisplayedMessages: Dispatch>; + save: (messages: Message[]) => Promise; +} { + const service = useObservabilityAIAssistant(); + + const { + services: { notifications }, + } = useKibana(); + + const [displayedMessages, setDisplayedMessages] = useState([]); + + const conversation: AbortableAsyncState = + useAbortableAsync( + ({ signal }) => { + if (!conversationId) { + const nextConversation = createNewConversation(); + setDisplayedMessages(nextConversation.messages); + return nextConversation; + } + + return service + .callApi('GET /internal/observability_ai_assistant/conversation/{conversationId}', { + signal, + params: { path: { conversationId } }, + }) + .then((nextConversation) => { + setDisplayedMessages(nextConversation.messages); + return nextConversation; + }) + .catch((error) => { + setDisplayedMessages([]); + throw error; + }); + }, + [conversationId] + ); + + return { + conversation, + displayedMessages, + setDisplayedMessages, + save: (messages: Message[]) => { + const conversationObject = conversation.value!; + return conversationId + ? service + .callApi(`POST /internal/observability_ai_assistant/conversation/{conversationId}`, { + signal: null, + params: { + path: { + conversationId, + }, + body: { + conversation: merge( + { + '@timestamp': conversationObject['@timestamp'], + conversation: { + id: conversationId, + }, + }, + omit(conversationObject, 'conversation.last_updated', 'namespace', 'user'), + { messages } + ), + }, + }, + }) + .catch((err) => { + notifications.toasts.addError(err, { + title: i18n.translate('xpack.observabilityAiAssistant.errorUpdatingConversation', { + defaultMessage: 'Could not update conversation', + }), + }); + throw err; + }) + : service + .callApi(`PUT /internal/observability_ai_assistant/conversation`, { + signal: null, + params: { + body: { + conversation: merge({}, conversationObject, { messages }), + }, + }, + }) + .catch((err) => { + notifications.toasts.addError(err, { + title: i18n.translate('xpack.observabilityAiAssistant.errorCreatingConversation', { + defaultMessage: 'Could not create conversation', + }), + }); + throw err; + }); + }, + }; +} diff --git a/x-pack/plugins/observability_ai_assistant/public/hooks/use_observability_ai_assistant.ts b/x-pack/plugins/observability_ai_assistant/public/hooks/use_observability_ai_assistant.ts index 8d8938fc4952133..041d2ebcba498d4 100644 --- a/x-pack/plugins/observability_ai_assistant/public/hooks/use_observability_ai_assistant.ts +++ b/x-pack/plugins/observability_ai_assistant/public/hooks/use_observability_ai_assistant.ts @@ -7,6 +7,12 @@ import { useContext } from 'react'; import { ObservabilityAIAssistantContext } from '../context/observability_ai_assistant_provider'; +export function useObservabilityAIAssistantOptional() { + const services = useContext(ObservabilityAIAssistantContext); + + return services; +} + export function useObservabilityAIAssistant() { const services = useContext(ObservabilityAIAssistantContext); diff --git a/x-pack/plugins/observability_ai_assistant/public/index.ts b/x-pack/plugins/observability_ai_assistant/public/index.ts index bdb716557db5cdd..18039652eaa66b2 100644 --- a/x-pack/plugins/observability_ai_assistant/public/index.ts +++ b/x-pack/plugins/observability_ai_assistant/public/index.ts @@ -20,11 +20,22 @@ export const ContextualInsight = withSuspense( lazy(() => import('./components/insight/insight').then((m) => ({ default: m.Insight }))) ); +export const ObservabilityAIAssistantActionMenuItem = withSuspense( + lazy(() => + import('./components/action_menu_item/action_menu_item').then((m) => ({ + default: m.ObservabilityAIAssistantActionMenuItem, + })) + ) +); + export { ObservabilityAIAssistantProvider } from './context/observability_ai_assistant_provider'; export type { ObservabilityAIAssistantPluginSetup, ObservabilityAIAssistantPluginStart }; -export { useObservabilityAIAssistant } from './hooks/use_observability_ai_assistant'; +export { + useObservabilityAIAssistant, + useObservabilityAIAssistantOptional, +} from './hooks/use_observability_ai_assistant'; export type { Conversation, Message } from '../common'; export { MessageRole } from '../common'; diff --git a/x-pack/plugins/observability_ai_assistant/public/plugin.tsx b/x-pack/plugins/observability_ai_assistant/public/plugin.tsx index 6ceff112ada4cdb..4c0b18413155746 100644 --- a/x-pack/plugins/observability_ai_assistant/public/plugin.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/plugin.tsx @@ -68,7 +68,7 @@ export class ObservabilityAIAssistantPlugin title: i18n.translate('xpack.observabilityAiAssistant.conversationsDeepLinkTitle', { defaultMessage: 'Conversations', }), - path: '/conversations', + path: '/conversations/new', }, ], diff --git a/x-pack/plugins/observability_ai_assistant/public/routes/config.tsx b/x-pack/plugins/observability_ai_assistant/public/routes/config.tsx index db3384d7005d579..f4245cb69d9e7b6 100644 --- a/x-pack/plugins/observability_ai_assistant/public/routes/config.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/routes/config.tsx @@ -38,6 +38,9 @@ const observabilityAIAssistantRoutes = { }), element: , }, + '/conversations': { + element: , + }, }, }, }; diff --git a/x-pack/plugins/observability_ai_assistant/public/routes/conversations/conversation_view.tsx b/x-pack/plugins/observability_ai_assistant/public/routes/conversations/conversation_view.tsx index 4ffaa69d2acf84e..2092390aedb9093 100644 --- a/x-pack/plugins/observability_ai_assistant/public/routes/conversations/conversation_view.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/routes/conversations/conversation_view.tsx @@ -7,20 +7,18 @@ import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; import { css } from '@emotion/css'; import { i18n } from '@kbn/i18n'; -import { merge, omit } from 'lodash'; import React, { useMemo, useState } from 'react'; -import type { ConversationCreateRequest, Message } from '../../../common/types'; import { ChatBody } from '../../components/chat/chat_body'; import { ConversationList } from '../../components/chat/conversation_list'; -import { AbortableAsyncState, useAbortableAsync } from '../../hooks/use_abortable_async'; +import { useAbortableAsync } from '../../hooks/use_abortable_async'; import { useConfirmModal } from '../../hooks/use_confirm_modal'; +import { useConversation } from '../../hooks/use_conversation'; import { useCurrentUser } from '../../hooks/use_current_user'; import { useGenAIConnectors } from '../../hooks/use_genai_connectors'; import { useKibana } from '../../hooks/use_kibana'; import { useObservabilityAIAssistant } from '../../hooks/use_observability_ai_assistant'; import { useObservabilityAIAssistantParams } from '../../hooks/use_observability_ai_assistant_params'; import { useObservabilityAIAssistantRouter } from '../../hooks/use_observability_ai_assistant_router'; -import { createNewConversation } from '../../hooks/use_timeline'; import { EMPTY_CONVERSATION_TITLE } from '../../i18n'; import { getConnectorsManagementHref } from '../../utils/get_connectors_management_href'; @@ -63,31 +61,8 @@ export function ConversationView() { const conversationId = 'conversationId' in path ? path.conversationId : undefined; - const conversation: AbortableAsyncState = - useAbortableAsync( - ({ signal }) => { - if (!conversationId) { - const nextConversation = createNewConversation(); - setDisplayedMessages(nextConversation.messages); - return nextConversation; - } - - return service - .callApi('GET /internal/observability_ai_assistant/conversation/{conversationId}', { - signal, - params: { path: { conversationId } }, - }) - .then((nextConversation) => { - setDisplayedMessages(nextConversation.messages); - return nextConversation; - }) - .catch((error) => { - setDisplayedMessages([]); - throw error; - }); - }, - [conversationId] - ); + const { conversation, displayedMessages, setDisplayedMessages, save } = + useConversation(conversationId); const conversations = useAbortableAsync( ({ signal }) => { @@ -113,8 +88,6 @@ export function ConversationView() { ]; }, [conversations.value?.conversations, conversationId, observabilityAIAssistantRouter]); - const [displayedMessages, setDisplayedMessages] = useState([]); - function navigateToConversation(nextConversationId?: string) { observabilityAIAssistantRouter.push( nextConversationId ? '/conversations/{conversationId}' : '/conversations/new', @@ -233,71 +206,14 @@ export function ConversationView() { service={service} messages={displayedMessages} onChatComplete={(messages) => { - const conversationObject = conversation.value!; - if (conversationId) { - service - .callApi( - `POST /internal/observability_ai_assistant/conversation/{conversationId}`, - { - signal: null, - params: { - path: { - conversationId, - }, - body: { - conversation: merge( - { - '@timestamp': conversationObject['@timestamp'], - conversation: { - id: conversationId, - }, - }, - omit( - conversationObject, - 'conversation.last_updated', - 'namespace', - 'user' - ), - { messages } - ), - }, - }, - } - ) - .then(() => { - conversations.refresh(); - }) - .catch((err) => { - notifications.toasts.addError(err, { - title: i18n.translate( - 'xpack.observabilityAiAssistant.errorCreatingConversation', - { defaultMessage: 'Could not create conversation' } - ), - }); - }); - } else { - service - .callApi(`PUT /internal/observability_ai_assistant/conversation`, { - signal: null, - params: { - body: { - conversation: merge({}, conversationObject, { messages }), - }, - }, - }) - .then((createdConversation) => { - navigateToConversation(createdConversation.conversation.id); - conversations.refresh(); - }) - .catch((err) => { - notifications.toasts.addError(err, { - title: i18n.translate( - 'xpack.observabilityAiAssistant.errorCreatingConversation', - { defaultMessage: 'Could not create conversation' } - ), - }); - }); - } + save(messages) + .then((nextConversation) => { + conversations.refresh(); + if (!conversationId) { + navigateToConversation(nextConversation.conversation.id); + } + }) + .catch(() => {}); }} onChatUpdate={(messages) => { setDisplayedMessages(messages); diff --git a/x-pack/plugins/profiling/public/components/profiling_header_action_menu.tsx b/x-pack/plugins/profiling/public/components/profiling_header_action_menu.tsx index b973c763142be01..fafcc1b36d9d8a9 100644 --- a/x-pack/plugins/profiling/public/components/profiling_header_action_menu.tsx +++ b/x-pack/plugins/profiling/public/components/profiling_header_action_menu.tsx @@ -7,6 +7,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiHeaderLink, EuiHeaderLinks, EuiIcon } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; +import { ObservabilityAIAssistantActionMenuItem } from '@kbn/observability-ai-assistant-plugin/public'; import { useProfilingRouter } from '../hooks/use_profiling_router'; import { NoDataTabs } from '../views/no_data_view'; @@ -31,6 +32,7 @@ export function ProfilingHeaderActionMenu() { + ); } diff --git a/x-pack/plugins/synthetics/kibana.jsonc b/x-pack/plugins/synthetics/kibana.jsonc index bfef1566d7e9c24..511666996f8295e 100644 --- a/x-pack/plugins/synthetics/kibana.jsonc +++ b/x-pack/plugins/synthetics/kibana.jsonc @@ -24,6 +24,7 @@ "licensing", "observability", "observabilityShared", + "observabilityAIAssistant", "ruleRegistry", "security", "share", diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/header/action_menu_content.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/header/action_menu_content.tsx index d74492794d08f81..a123f4be953829d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/header/action_menu_content.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/header/action_menu_content.tsx @@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { useHistory, useRouteMatch } from 'react-router-dom'; import { createExploratoryViewUrl } from '@kbn/exploratory-view-plugin/public'; +import { ObservabilityAIAssistantActionMenuItem } from '@kbn/observability-ai-assistant-plugin/public'; import { LastRefreshed } from '../components/last_refreshed'; import { AutoRefreshButton } from '../components/auto_refresh_button'; import { useSyntheticsSettingsContext } from '../../../contexts'; @@ -102,8 +103,8 @@ export function ActionMenuContent(): React.ReactElement { {ANALYZE_DATA} - + ); } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx index 1be49596f148089..f9b37b64df403b0 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx @@ -17,6 +17,7 @@ import { } from '@kbn/kibana-react-plugin/public'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { InspectorContextProvider } from '@kbn/observability-shared-plugin/public'; +import { ObservabilityAIAssistantProvider } from '@kbn/observability-ai-assistant-plugin/public'; import { SyntheticsAppProps } from './contexts'; import { @@ -98,30 +99,32 @@ const Application = (props: SyntheticsAppProps) => { fleet: startPlugins.fleet, }} > - - - - - - -
- - - - - - - -
-
-
-
-
-
-
+ + + + + + + +
+ + + + + + + +
+
+
+
+
+
+
+
diff --git a/x-pack/plugins/synthetics/public/plugin.ts b/x-pack/plugins/synthetics/public/plugin.ts index 28802415c38bf82..28ac4ba859e5f97 100644 --- a/x-pack/plugins/synthetics/public/plugin.ts +++ b/x-pack/plugins/synthetics/public/plugin.ts @@ -49,6 +49,10 @@ import type { ObservabilitySharedPluginSetup, ObservabilitySharedPluginStart, } from '@kbn/observability-shared-plugin/public'; +import { + ObservabilityAIAssistantPluginStart, + ObservabilityAIAssistantPluginSetup, +} from '@kbn/observability-ai-assistant-plugin/public'; import { PLUGIN } from '../common/constants/plugin'; import { OVERVIEW_ROUTE } from '../common/constants/ui'; import { locators } from './apps/locators'; @@ -61,6 +65,7 @@ export interface ClientPluginsSetup { exploratoryView: ExploratoryViewPublicSetup; observability: ObservabilityPublicSetup; observabilityShared: ObservabilitySharedPluginSetup; + observabilityAIAssistant: ObservabilityAIAssistantPluginSetup; share: SharePluginSetup; triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; cloud?: CloudSetup; @@ -76,6 +81,7 @@ export interface ClientPluginsStart { exploratoryView: ExploratoryViewPublicStart; observability: ObservabilityPublicStart; observabilityShared: ObservabilitySharedPluginStart; + observabilityAIAssistant: ObservabilityAIAssistantPluginStart; share: SharePluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; cases: CasesUiStart; diff --git a/x-pack/plugins/synthetics/tsconfig.json b/x-pack/plugins/synthetics/tsconfig.json index 85727394263a6f1..12bc371fef91512 100644 --- a/x-pack/plugins/synthetics/tsconfig.json +++ b/x-pack/plugins/synthetics/tsconfig.json @@ -76,6 +76,7 @@ "@kbn/std", "@kbn/core-saved-objects-server-mocks", "@kbn/shared-ux-page-kibana-template", + "@kbn/observability-ai-assistant-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/uptime/kibana.jsonc b/x-pack/plugins/uptime/kibana.jsonc index 9d082f5f1a70b75..df0b2e13839cfd4 100644 --- a/x-pack/plugins/uptime/kibana.jsonc +++ b/x-pack/plugins/uptime/kibana.jsonc @@ -24,6 +24,7 @@ "licensing", "observability", "observabilityShared", + "observabilityAIAssistant", "ruleRegistry", "security", "share", diff --git a/x-pack/plugins/uptime/public/legacy_uptime/app/uptime_app.tsx b/x-pack/plugins/uptime/public/legacy_uptime/app/uptime_app.tsx index c967c732ec8d4b3..fb45099888592c4 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/app/uptime_app.tsx +++ b/x-pack/plugins/uptime/public/legacy_uptime/app/uptime_app.tsx @@ -18,6 +18,7 @@ import { } from '@kbn/kibana-react-plugin/public'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { InspectorContextProvider } from '@kbn/observability-shared-plugin/public'; +import { ObservabilityAIAssistantProvider } from '@kbn/observability-ai-assistant-plugin/public'; import { ClientPluginsSetup, ClientPluginsStart } from '../../plugin'; import { UMUpdateBadge } from '../lib/lib'; import { @@ -129,32 +130,34 @@ const Application = (props: UptimeAppProps) => { cases: startPlugins.cases, }} > - - - - - - - -
- - - - - - - -
-
-
-
-
-
-
-
+ + + + + + + + +
+ + + + + + + +
+
+
+
+
+
+
+
+
diff --git a/x-pack/plugins/uptime/public/legacy_uptime/components/common/header/action_menu_content.tsx b/x-pack/plugins/uptime/public/legacy_uptime/components/common/header/action_menu_content.tsx index 38047aff244b41a..d6a37ea9bd4ee7d 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/components/common/header/action_menu_content.tsx +++ b/x-pack/plugins/uptime/public/legacy_uptime/components/common/header/action_menu_content.tsx @@ -13,6 +13,7 @@ import { useHistory, useRouteMatch } from 'react-router-dom'; import { useSelector } from 'react-redux'; import { createExploratoryViewUrl } from '@kbn/exploratory-view-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ObservabilityAIAssistantActionMenuItem } from '@kbn/observability-ai-assistant-plugin/public'; import { stringifyUrlParams } from '../../../lib/helper/url_params/stringify_url_params'; import { useUptimeSettingsContext } from '../../../contexts/uptime_settings_context'; import { useGetUrlParams } from '../../../hooks'; @@ -116,6 +117,7 @@ export function ActionMenuContent(): React.ReactElement { {ADD_DATA_LABEL} + ); } diff --git a/x-pack/plugins/uptime/public/plugin.ts b/x-pack/plugins/uptime/public/plugin.ts index 0b4942e4020d72c..7a13252327a9009 100644 --- a/x-pack/plugins/uptime/public/plugin.ts +++ b/x-pack/plugins/uptime/public/plugin.ts @@ -51,6 +51,10 @@ import type { ObservabilitySharedPluginStart, } from '@kbn/observability-shared-plugin/public'; import { AppStatus, AppUpdater } from '@kbn/core-application-browser'; +import { + ObservabilityAIAssistantPluginStart, + ObservabilityAIAssistantPluginSetup, +} from '@kbn/observability-ai-assistant-plugin/public'; import { PLUGIN } from '../common/constants/plugin'; import { LazySyntheticsPolicyCreateExtension, @@ -69,6 +73,7 @@ export interface ClientPluginsSetup { exploratoryView: ExploratoryViewPublicSetup; observability: ObservabilityPublicSetup; observabilityShared: ObservabilitySharedPluginSetup; + observabilityAIAssistant: ObservabilityAIAssistantPluginSetup; share: SharePluginSetup; triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; cloud?: CloudSetup; @@ -84,6 +89,7 @@ export interface ClientPluginsStart { exploratoryView: ExploratoryViewPublicStart; observability: ObservabilityPublicStart; observabilityShared: ObservabilitySharedPluginStart; + observabilityAIAssistant: ObservabilityAIAssistantPluginStart; share: SharePluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; cases: CasesUiStart; diff --git a/x-pack/plugins/uptime/tsconfig.json b/x-pack/plugins/uptime/tsconfig.json index 0bc244a45de0ee1..ddfe50db6594081 100644 --- a/x-pack/plugins/uptime/tsconfig.json +++ b/x-pack/plugins/uptime/tsconfig.json @@ -72,6 +72,7 @@ "@kbn/core-http-router-server-internal", "@kbn/actions-plugin", "@kbn/core-saved-objects-server", + "@kbn/observability-ai-assistant-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/ux/kibana.jsonc b/x-pack/plugins/ux/kibana.jsonc index 341d9d8fcc1f868..26a2ab78a926a39 100644 --- a/x-pack/plugins/ux/kibana.jsonc +++ b/x-pack/plugins/ux/kibana.jsonc @@ -15,6 +15,7 @@ "licensing", "triggersActionsUi", "observabilityShared", + "observabilityAIAssistant", "embeddable", "infra", "inspector", diff --git a/x-pack/plugins/ux/public/application/ux_app.tsx b/x-pack/plugins/ux/public/application/ux_app.tsx index 85af82597863ad0..aa1ae124d027964 100644 --- a/x-pack/plugins/ux/public/application/ux_app.tsx +++ b/x-pack/plugins/ux/public/application/ux_app.tsx @@ -32,6 +32,7 @@ import { InspectorContextProvider, useBreadcrumbs, } from '@kbn/observability-shared-plugin/public'; +import { ObservabilityAIAssistantProvider } from '@kbn/observability-ai-assistant-plugin/public'; import { CsmSharedContextProvider } from '../components/app/rum_dashboard/csm_shared_context'; import { DASHBOARD_LABEL, @@ -111,6 +112,7 @@ export function UXAppRoot({ maps, observability, observabilityShared, + observabilityAIAssistant, exploratoryView, data, dataViews, @@ -147,40 +149,42 @@ export function UXAppRoot({ lens, }} > - - + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + ); diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/action_menu/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/action_menu/index.tsx index 4a18759a0aebe37..88709a068fea8a0 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/action_menu/index.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/action_menu/index.tsx @@ -14,6 +14,7 @@ import { createExploratoryViewUrl, } from '@kbn/exploratory-view-plugin/public'; import { AppMountParameters } from '@kbn/core/public'; +import { ObservabilityAIAssistantActionMenuItem } from '@kbn/observability-ai-assistant-plugin/public'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { SERVICE_NAME } from '../../../../../common/elasticsearch_fieldnames'; import { UxInspectorHeaderLink } from './inpector_link'; @@ -85,6 +86,7 @@ export function UXActionMenu({ })} + ); diff --git a/x-pack/plugins/ux/public/plugin.ts b/x-pack/plugins/ux/public/plugin.ts index a578b6aca0b790a..9cb846238aa47b6 100644 --- a/x-pack/plugins/ux/public/plugin.ts +++ b/x-pack/plugins/ux/public/plugin.ts @@ -42,6 +42,10 @@ import { ObservabilitySharedPluginSetup, ObservabilitySharedPluginStart, } from '@kbn/observability-shared-plugin/public'; +import { + ObservabilityAIAssistantPluginStart, + ObservabilityAIAssistantPluginSetup, +} from '@kbn/observability-ai-assistant-plugin/public'; export type UxPluginSetup = void; export type UxPluginStart = void; @@ -54,6 +58,7 @@ export interface ApmPluginSetupDeps { licensing: LicensingPluginSetup; observability: ObservabilityPublicSetup; observabilityShared: ObservabilitySharedPluginSetup; + observabilityAIAssistant: ObservabilityAIAssistantPluginSetup; } export interface ApmPluginStartDeps { @@ -65,6 +70,7 @@ export interface ApmPluginStartDeps { inspector: InspectorPluginStart; observability: ObservabilityPublicStart; observabilityShared: ObservabilitySharedPluginStart; + observabilityAIAssistant: ObservabilityAIAssistantPluginStart; exploratoryView: ExploratoryViewPublicStart; dataViews: DataViewsPublicPluginStart; lens: LensPublicStart; diff --git a/x-pack/plugins/ux/tsconfig.json b/x-pack/plugins/ux/tsconfig.json index e91b3b24198f63e..0b2adfd3e66b74a 100644 --- a/x-pack/plugins/ux/tsconfig.json +++ b/x-pack/plugins/ux/tsconfig.json @@ -39,6 +39,7 @@ "@kbn/exploratory-view-plugin", "@kbn/observability-shared-plugin", "@kbn/shared-ux-router", + "@kbn/observability-ai-assistant-plugin", ], "exclude": [ "target/**/*",