Skip to content

Commit

Permalink
[Obs AI Assistant] Add Search Connector tab to settings (elastic#176655)
Browse files Browse the repository at this point in the history
This PR simply adds a new tab that:
- describes what search connectors are and how they can improve
relevancy of assistant responses (copy writer welcome!)
 - links to enterprise search

The tab will be disabled if enterprise search plugin is not enabled

**Motivation for PR**
Making customers aware of search connectors and their benefits in
relation to the AI Assistant. We can perhaps simplify the process of
adding Search Connectors down the line but for now I suggest keeping it
simple.


![image](https://github.com/elastic/kibana/assets/209966/9653bc28-cc69-4215-ae9d-b13c293bdd64)
  • Loading branch information
sorenlouv authored Feb 13, 2024
1 parent ece790c commit 63bd950
Show file tree
Hide file tree
Showing 14 changed files with 113 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
"server": false,
"browser": true,
"requiredPlugins": ["management"],
"optionalPlugins": ["actions", "home", "observabilityAIAssistant", "serverless"],
"optionalPlugins": [
"actions",
"home",
"observabilityAIAssistant",
"serverless",
"enterpriseSearch"
],
"requiredBundles": ["kibanaReact"]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,11 @@ export const mountManagementSection = async ({ core, mountParams }: MountParams)
<I18nProvider>
<AppContextProvider
value={{
...startDeps,
application: coreStart.application,
http: coreStart.http,
notifications: coreStart.notifications,
observabilityAIAssistant: startDeps.observabilityAIAssistant,
uiSettings: coreStart.uiSettings,
serverless: startDeps.serverless,
setBreadcrumbs,
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@
import React, { createContext } from 'react';
import type { ChromeBreadcrumb } from '@kbn/core-chrome-browser';
import type { CoreStart, HttpSetup } from '@kbn/core/public';
import type { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-assistant-plugin/public';
import type { StartDependencies } from '../plugin';

export interface ContextValue extends StartDependencies {
application: CoreStart['application'];
http: HttpSetup;
notifications: CoreStart['notifications'];
observabilityAIAssistant: ObservabilityAIAssistantPluginStart;
setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void;
uiSettings: CoreStart['uiSettings'];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function useCreateKnowledgeBaseEntry() {
observabilityAIAssistant,
} = useAppContext();
const queryClient = useQueryClient();
const observabilityAIAssistantApi = observabilityAIAssistant.service.callApi;
const observabilityAIAssistantApi = observabilityAIAssistant?.service.callApi;

return useMutation<
void,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function useDeleteKnowledgeBaseEntry() {
notifications: { toasts },
} = useAppContext();
const queryClient = useQueryClient();
const observabilityAIAssistantApi = observabilityAIAssistant.service.callApi;
const observabilityAIAssistantApi = observabilityAIAssistant?.service.callApi;

return useMutation<unknown, ServerError, { id: string }>(
[REACT_QUERY_KEYS.CREATE_KB_ENTRIES],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function useGetKnowledgeBaseEntries({
}) {
const { observabilityAIAssistant } = useAppContext();

const observabilityAIAssistantApi = observabilityAIAssistant.service.callApi;
const observabilityAIAssistantApi = observabilityAIAssistant?.service.callApi;

const { isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery({
queryKey: [REACT_QUERY_KEYS.GET_KB_ENTRIES, query, sortBy, sortDirection],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function useImportKnowledgeBaseEntries() {
notifications: { toasts },
} = useAppContext();
const queryClient = useQueryClient();
const observabilityAIAssistantApi = observabilityAIAssistant.service.callApi;
const observabilityAIAssistantApi = observabilityAIAssistant?.service.callApi;

return useMutation<
void,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { CoreSetup, Plugin } from '@kbn/core/public';
import { ManagementSetup } from '@kbn/management-plugin/public';
import { HomePublicPluginSetup } from '@kbn/home-plugin/public';
import { ServerlessPluginStart } from '@kbn/serverless/public';
import { EnterpriseSearchPublicStart } from '@kbn/enterprise-search-plugin/public';

import type {
ObservabilityAIAssistantPluginSetup,
ObservabilityAIAssistantPluginStart,
Expand All @@ -31,6 +33,7 @@ export interface SetupDependencies {
export interface StartDependencies {
observabilityAIAssistant?: ObservabilityAIAssistantPluginStart;
serverless?: ServerlessPluginStart;
enterpriseSearch?: EnterpriseSearchPublicStart;
}

export class AiAssistantManagementObservabilityPlugin
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiLink, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useAppContext } from '../../hooks/use_app_context';

export const SELECTED_CONNECTOR_LOCAL_STORAGE_KEY =
'xpack.observabilityAiAssistant.lastUsedConnector';

export function SearchConnectorTab() {
const { application } = useAppContext();
const url = application.getUrlForApp('enterprise_search', { path: '/content/connectors' });

return (
<>
<EuiText>
{i18n.translate(
'aiAssistantManagementObservability.searchConnectorTab.searchConnectorsEnablesYouTextLabel',
{
defaultMessage:
'Connectors enable you to index content from external sources thereby making it available for the AI Assistant. This can greatly improve the relevance of the AI Assistant’s responses.',
}
)}
</EuiText>

<EuiText>
<FormattedMessage
id="aiAssistantManagementObservability.searchConnectorTab.searchConnectorsManagementLink"
defaultMessage="You can manage connectors under {searchConnectorLink}."
values={{
searchConnectorLink: (
<EuiLink
data-test-subj="pluginsSearchConnectorTabSearchConnectorsManagementPageLink"
href={url}
>
{i18n.translate(
'aiAssistantManagementObservability.searchConnectorTab.searchConnectorsManagementPageLinkLabel',
{ defaultMessage: 'Connectors' }
)}
</EuiLink>
),
}}
/>
</EuiText>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import React, { useEffect, useState } from 'react';
import React, { useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiSpacer, EuiTab, EuiTabs, EuiTitle } from '@elastic/eui';
import { useAppContext } from '../../hooks/use_app_context';
Expand All @@ -15,10 +15,12 @@ import { KnowledgeBaseTab } from './knowledge_base_tab';
import { useObservabilityAIAssistantManagementRouterParams } from '../../hooks/use_observability_management_params';
import { useObservabilityAIAssistantManagementRouter } from '../../hooks/use_observability_management_router';
import type { TabsRt } from '../config';
import { SearchConnectorTab } from './search_connector_tab';
export function SettingsPage() {
const {
application: { navigateToApp },
serverless,
enterpriseSearch,
setBreadcrumbs,
} = useAppContext();

Expand Down Expand Up @@ -60,7 +62,7 @@ export function SettingsPage() {
}
}, [navigateToApp, serverless, setBreadcrumbs]);

const tabs: Array<{ id: TabsRt; name: string; content: JSX.Element }> = [
const tabs: Array<{ id: TabsRt; name: string; content: JSX.Element; disabled?: boolean }> = [
{
id: 'settings',
name: i18n.translate('aiAssistantManagementObservability.settingsPage.settingsLabel', {
Expand All @@ -75,16 +77,20 @@ export function SettingsPage() {
}),
content: <KnowledgeBaseTab />,
},
{
id: 'search_connector',
name: i18n.translate('aiAssistantManagementObservability.settingsPage.searchConnector', {
defaultMessage: 'Search Connectors',
}),
content: <SearchConnectorTab />,
disabled: enterpriseSearch == null,
},
];

const [selectedTabId, setSelectedTabId] = useState<TabsRt>(
tab ? tabs.find((t) => t.id === tab)?.id : tabs[0].id
);

const selectedTabId = tabs.some((t) => t.id === tab) ? tab : tabs[0].id;
const selectedTabContent = tabs.find((obj) => obj.id === selectedTabId)?.content;

const onSelectedTabChanged = (id: TabsRt) => {
setSelectedTabId(id);
router.push('/', { path: '/', query: { tab: id } });
};

Expand All @@ -101,16 +107,18 @@ export function SettingsPage() {
<EuiSpacer size="m" />

<EuiTabs data-test-subj="settingsPageTabs">
{tabs.map((t, index) => (
<EuiTab
key={index}
data-test-subj={`settingsPageTab-${t.id}`}
onClick={() => onSelectedTabChanged(t.id)}
isSelected={t.id === selectedTabId}
>
{t.name}
</EuiTab>
))}
{tabs
.filter((t) => !t.disabled)
.map((t, index) => (
<EuiTab
key={index}
data-test-subj={`settingsPageTab-${t.id}`}
onClick={() => onSelectedTabChanged(t.id)}
isSelected={t.id === selectedTabId}
>
{t.name}
</EuiTab>
))}
</EuiTabs>

<EuiSpacer size="l" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ export function SettingsTab() {
observabilityAIAssistant,
} = useAppContext();

// If the AI Assistant is not available, don't render the settings tab
if (!observabilityAIAssistant) {
return null;
}

const {
connectors = [],
selectedConnector,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import * as t from 'io-ts';
import { createRouter } from '@kbn/typed-react-router-config';
import { SettingsPage } from './components/settings_page';

const Tabs = t.union([t.literal('settings'), t.literal('knowledge_base'), t.undefined]);
const Tabs = t.union([
t.literal('settings'),
t.literal('knowledge_base'),
t.literal('search_connector'),
t.undefined,
]);
export type TabsRt = t.TypeOf<typeof Tabs>;

const aIAssistantManagementObservabilityRoutes = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"@kbn/core-chrome-browser",
"@kbn/observability-ai-assistant-plugin",
"@kbn/serverless",
"@kbn/translations-plugin"
"@kbn/translations-plugin",
"@kbn/enterprise-search-plugin"
],
"exclude": ["target/**/*"]
}
6 changes: 5 additions & 1 deletion x-pack/plugins/enterprise_search/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,13 +437,17 @@ export class EnterpriseSearchPlugin implements Plugin {
}
}

public async start(core: CoreStart) {
public start(core: CoreStart) {
if (!this.config.ui?.enabled) {
return;
}
// This must be called here in start() and not in `applications/index.tsx` to prevent loading
// race conditions with our apps' `routes.ts` being initialized before `renderApp()`
docLinks.setDocLinks(core.docLinks);

// Return empty start contract rather than void in order for plugins
// that depend on the enterprise search plugin to determine whether it is enabled or not
return {};
}

public stop() {}
Expand Down

0 comments on commit 63bd950

Please sign in to comment.