mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Search][Playground] Add connectors to sum models (#180428)
- Add state for connector page to have one source of truth and have ability to control edit flyout by URL - Add connectorId route to translate it to state and able easily control edit flyout by URL - Use new link in playground Having a link like `insightsAndAlerting/triggersActionsConnectors/connectors/:connectorId` will transformed to `insightsAndAlerting/triggersActionsConnectors/connectors#?_a=(initialConnector:(actionType:OpenAI,actionTypeId:.gen-ai,compatibility:!.....)`. @elastic/response-ops could you check is it appropriate changes? If yes I will proceed with adding tests for it
This commit is contained in:
parent
518b751beb
commit
0893fe72e4
15 changed files with 239 additions and 152 deletions
|
@ -106,6 +106,9 @@ export const Chat = () => {
|
|||
borderRight: euiTheme.border.thin,
|
||||
paddingTop: euiTheme.size.l,
|
||||
paddingBottom: euiTheme.size.l,
|
||||
// don't allow the chat to shrink below 66.6% of the screen
|
||||
flexBasis: 0,
|
||||
minWidth: '66.6%',
|
||||
}}
|
||||
>
|
||||
<EuiFlexGroup direction="column" className="eui-fullHeight">
|
||||
|
@ -210,7 +213,7 @@ export const Chat = () => {
|
|||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiFlexItem grow={1} css={{ flexBasis: 0, minWidth: '33.3%' }}>
|
||||
<ChatSidebar selectedIndicesCount={selectedIndicesCount} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -35,16 +35,12 @@ export const SetUpConnectorPanelForStartChat: React.FC = () => {
|
|||
|
||||
return connectors && !isConnectorListLoading ? (
|
||||
<>
|
||||
{!!Object.keys(connectors).length && showCallout && (
|
||||
{!!connectors.length && showCallout && (
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.searchPlayground.emptyPrompts.setUpConnector.settled', {
|
||||
defaultMessage:
|
||||
'{connectorsNames} {count, plural, one {connector} other {connectors}} added',
|
||||
defaultMessage: '{connectorName} connector added',
|
||||
values: {
|
||||
connectorsNames: Object.values(connectors)
|
||||
.map((connector) => connector.title)
|
||||
.join(', '),
|
||||
count: Object.values(connectors).length,
|
||||
connectorName: connectors[0].title,
|
||||
},
|
||||
})}
|
||||
iconType="check"
|
||||
|
|
|
@ -30,8 +30,20 @@ describe('SummarizationModel', () => {
|
|||
|
||||
it('renders correctly with models', () => {
|
||||
const models = [
|
||||
{ name: 'Model1', disabled: false, icon: MockIcon, connectorId: 'connector1' },
|
||||
{ name: 'Model2', disabled: true, icon: MockIcon, connectorId: 'connector2' },
|
||||
{
|
||||
name: 'Model1',
|
||||
disabled: false,
|
||||
icon: MockIcon,
|
||||
connectorId: 'connector1',
|
||||
connectorName: 'nameconnector1',
|
||||
},
|
||||
{
|
||||
name: 'Model2',
|
||||
disabled: true,
|
||||
icon: MockIcon,
|
||||
connectorId: 'connector2',
|
||||
connectorName: 'nameconnector2',
|
||||
},
|
||||
];
|
||||
const { getByTestId } = render(
|
||||
<SummarizationModel selectedModel={models[1]} models={models} onSelect={jest.fn()} />
|
||||
|
|
|
@ -8,14 +8,15 @@
|
|||
import React, { useMemo } from 'react';
|
||||
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiIcon,
|
||||
EuiIconTip,
|
||||
EuiLink,
|
||||
EuiSuperSelect,
|
||||
EuiText,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -30,31 +31,64 @@ interface SummarizationModelProps {
|
|||
models: LLMModel[];
|
||||
}
|
||||
|
||||
const getOptionValue = (model: LLMModel): string => model.connectorId + model.name;
|
||||
|
||||
export const SummarizationModel: React.FC<SummarizationModelProps> = ({
|
||||
selectedModel,
|
||||
models,
|
||||
onSelect,
|
||||
}) => {
|
||||
const managementLink = useManagementLink();
|
||||
const onChange = (modelName: string) => {
|
||||
const model = models.find(({ name }) => name === modelName);
|
||||
const managementLink = useManagementLink(selectedModel.connectorId);
|
||||
const onChange = (modelValue: string) => {
|
||||
const newSelectedModel = models.find((model) => getOptionValue(model) === modelValue);
|
||||
|
||||
if (model) {
|
||||
onSelect(model);
|
||||
if (newSelectedModel) {
|
||||
onSelect(newSelectedModel);
|
||||
}
|
||||
};
|
||||
|
||||
const modelsOption: Array<EuiSuperSelectOption<string>> = useMemo(
|
||||
() =>
|
||||
models.map(({ name, disabled, icon, connectorId }) => ({
|
||||
value: name,
|
||||
disabled,
|
||||
models.map((model) => ({
|
||||
value: getOptionValue(model),
|
||||
disabled: model.disabled,
|
||||
inputDisplay: (
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type={icon} />
|
||||
<EuiIcon type={model.icon} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>{name}</EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
alignItems="center"
|
||||
gutterSize="s"
|
||||
css={{ overflow: 'hidden' }}
|
||||
>
|
||||
<EuiText size="s">{model.name}</EuiText>
|
||||
{model.showConnectorName && model.connectorName && (
|
||||
<EuiText
|
||||
size="xs"
|
||||
color="subdued"
|
||||
css={{ overflow: 'hidden', textOverflow: 'ellipsis', textWrap: 'nowrap' }}
|
||||
>
|
||||
<span title={model.connectorName}>{model.connectorName}</span>
|
||||
</EuiText>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
dropdownDisplay: (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type={model.icon} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="xs" direction="column">
|
||||
<EuiText size="s">{model.name}</EuiText>
|
||||
{model.showConnectorName && model.connectorName && (
|
||||
<EuiText size="xs" color="subdued">
|
||||
{model.connectorName}
|
||||
</EuiText>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
})),
|
||||
|
@ -63,6 +97,7 @@ export const SummarizationModel: React.FC<SummarizationModelProps> = ({
|
|||
|
||||
return (
|
||||
<EuiFormRow
|
||||
css={{ '.euiFormLabel': { display: 'flex', alignItems: 'center' } }}
|
||||
label={
|
||||
<>
|
||||
<FormattedMessage
|
||||
|
@ -77,20 +112,34 @@ export const SummarizationModel: React.FC<SummarizationModelProps> = ({
|
|||
</>
|
||||
}
|
||||
labelAppend={
|
||||
<EuiText size="xs">
|
||||
<EuiLink target="_blank" href={managementLink} data-test-subj="manageConnectorsLink">
|
||||
<FormattedMessage
|
||||
id="xpack.searchPlayground.sidebar.summarizationModel.manageConnectors"
|
||||
defaultMessage="Manage GenAI connectors"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
<EuiToolTip
|
||||
delay="long"
|
||||
content={i18n.translate(
|
||||
'xpack.searchPlayground.sidebar.summarizationModel.manageConnectorTooltip',
|
||||
{
|
||||
defaultMessage: 'Manage connector',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
target="_blank"
|
||||
href={managementLink}
|
||||
data-test-subj="manageConnectorsLink"
|
||||
iconType="wrench"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.searchPlayground.sidebar.summarizationModel.manageConnectorLink',
|
||||
{
|
||||
defaultMessage: 'Manage connector',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
}
|
||||
>
|
||||
<EuiSuperSelect
|
||||
data-test-subj="summarizationModelSelect"
|
||||
options={modelsOption}
|
||||
valueOfSelected={selectedModel.name}
|
||||
valueOfSelected={getOptionValue(selectedModel)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
|
|
@ -14,9 +14,7 @@ jest.mock('./use_load_connectors', () => ({
|
|||
useLoadConnectors: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockConnectors = {
|
||||
[LLMs.openai]: { id: 'connectorId1', title: 'OpenAI Connector' },
|
||||
};
|
||||
const mockConnectors = [{ id: 'connectorId1', title: 'OpenAI Connector', type: LLMs.openai }];
|
||||
const mockUseLoadConnectors = (data: any) => {
|
||||
(useLoadConnectors as jest.Mock).mockReturnValue({ data });
|
||||
};
|
||||
|
@ -32,57 +30,32 @@ describe('useLLMsModels Hook', () => {
|
|||
const { result } = renderHook(() => useLLMsModels());
|
||||
|
||||
expect(result.current).toEqual([
|
||||
{
|
||||
connectorId: undefined,
|
||||
disabled: true,
|
||||
icon: expect.any(Function),
|
||||
name: 'Azure OpenAI',
|
||||
value: undefined,
|
||||
},
|
||||
{
|
||||
connectorId: 'connectorId1',
|
||||
disabled: false,
|
||||
icon: expect.any(Function),
|
||||
name: 'gpt-3.5-turbo',
|
||||
id: 'connectorId1gpt-3.5-turbo ',
|
||||
name: 'gpt-3.5-turbo ',
|
||||
showConnectorName: false,
|
||||
value: 'gpt-3.5-turbo',
|
||||
},
|
||||
{
|
||||
connectorId: 'connectorId1',
|
||||
disabled: false,
|
||||
icon: expect.any(Function),
|
||||
name: 'gpt-4',
|
||||
id: 'connectorId1gpt-4 ',
|
||||
name: 'gpt-4 ',
|
||||
showConnectorName: false,
|
||||
value: 'gpt-4',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns LLMModels as disabled when no connectors are available', () => {
|
||||
mockUseLoadConnectors({});
|
||||
it('returns emptyd when connectors not available', () => {
|
||||
mockUseLoadConnectors([]);
|
||||
|
||||
const { result } = renderHook(() => useLLMsModels());
|
||||
|
||||
expect(result.current).toEqual([
|
||||
{
|
||||
connectorId: undefined,
|
||||
disabled: true,
|
||||
icon: expect.any(Function),
|
||||
name: 'Azure OpenAI',
|
||||
value: undefined,
|
||||
},
|
||||
{
|
||||
connectorId: undefined,
|
||||
disabled: true,
|
||||
icon: expect.any(Function),
|
||||
name: 'gpt-3.5-turbo',
|
||||
value: 'gpt-3.5-turbo',
|
||||
},
|
||||
{
|
||||
connectorId: undefined,
|
||||
disabled: true,
|
||||
icon: expect.any(Function),
|
||||
name: 'gpt-4',
|
||||
value: 'gpt-4',
|
||||
},
|
||||
]);
|
||||
expect(result.current).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,48 +7,82 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { OpenAILogo } from '@kbn/stack-connectors-plugin/public/common';
|
||||
import { ComponentType } from 'react';
|
||||
import { ComponentType, useMemo } from 'react';
|
||||
import { LLMs } from '../../common/types';
|
||||
import { LLMModel, SummarizationModelName } from '../types';
|
||||
import { LLMModel } from '../types';
|
||||
import { useLoadConnectors } from './use_load_connectors';
|
||||
|
||||
const llmModels: Array<{
|
||||
llm: LLMs;
|
||||
icon: ComponentType;
|
||||
models: Array<{ label: string; value?: string }>;
|
||||
}> = [
|
||||
const mapLlmToModels: Record<
|
||||
LLMs,
|
||||
{
|
||||
llm: LLMs.openai_azure,
|
||||
icon: ComponentType;
|
||||
getModels: (
|
||||
connectorName: string,
|
||||
includeName: boolean
|
||||
) => Array<{ label: string; value?: string }>;
|
||||
}
|
||||
> = {
|
||||
[LLMs.openai_azure]: {
|
||||
icon: OpenAILogo,
|
||||
models: [
|
||||
getModels: (connectorName, includeName) => [
|
||||
{
|
||||
label: i18n.translate('xpack.searchPlayground.openAIAzureModel', {
|
||||
defaultMessage: 'Azure OpenAI',
|
||||
defaultMessage: 'Azure OpenAI {name}',
|
||||
values: { name: includeName ? `(${connectorName})` : '' },
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
llm: LLMs.openai,
|
||||
[LLMs.openai]: {
|
||||
icon: OpenAILogo,
|
||||
models: Object.values(SummarizationModelName).map((model) => ({ label: model, value: model })),
|
||||
getModels: (connectorName, includeName) =>
|
||||
['gpt-3.5-turbo', 'gpt-4'].map((model) => ({
|
||||
label: `${model} ${includeName ? `(${connectorName})` : ''}`,
|
||||
value: model,
|
||||
})),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const useLLMsModels = (): LLMModel[] => {
|
||||
const { data: connectors } = useLoadConnectors();
|
||||
|
||||
return llmModels.reduce<LLMModel[]>(
|
||||
(result, { llm, icon, models }) => [
|
||||
...result,
|
||||
...models.map(({ label, value }) => ({
|
||||
name: label,
|
||||
value,
|
||||
icon,
|
||||
disabled: !connectors?.[llm],
|
||||
connectorId: connectors?.[llm]?.id,
|
||||
})),
|
||||
],
|
||||
[]
|
||||
const mapConnectorTypeToCount = useMemo(
|
||||
() =>
|
||||
connectors?.reduce<Partial<Record<LLMs, number>>>(
|
||||
(result, connector) => ({
|
||||
...result,
|
||||
[connector.type]: (result[connector.type] || 0) + 1,
|
||||
}),
|
||||
{}
|
||||
),
|
||||
[connectors]
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
connectors?.reduce<LLMModel[]>((result, connector) => {
|
||||
const llmParams = mapLlmToModels[connector.type];
|
||||
|
||||
if (!llmParams) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const showConnectorName = Number(mapConnectorTypeToCount?.[connector.type]) > 1;
|
||||
|
||||
return [
|
||||
...result,
|
||||
...llmParams.getModels(connector.name, false).map(({ label, value }) => ({
|
||||
id: connector?.id + label,
|
||||
name: label,
|
||||
value,
|
||||
connectorName: connector.name,
|
||||
showConnectorName,
|
||||
icon: llmParams.icon,
|
||||
disabled: !connector,
|
||||
connectorId: connector.id,
|
||||
})),
|
||||
];
|
||||
}, []) || [],
|
||||
[connectors, mapConnectorTypeToCount]
|
||||
);
|
||||
};
|
||||
|
|
|
@ -73,8 +73,8 @@ describe('useLoadConnectors', () => {
|
|||
const { result, waitForNextUpdate } = renderHook(() => useLoadConnectors());
|
||||
await waitForNextUpdate();
|
||||
|
||||
await expect(result.current).resolves.toStrictEqual({
|
||||
openai: {
|
||||
await expect(result.current).resolves.toStrictEqual([
|
||||
{
|
||||
actionTypeId: '.gen-ai',
|
||||
config: {
|
||||
apiProvider: 'OpenAI',
|
||||
|
@ -82,8 +82,9 @@ describe('useLoadConnectors', () => {
|
|||
id: '1',
|
||||
isMissingSecrets: false,
|
||||
title: 'OpenAI',
|
||||
type: 'openai',
|
||||
},
|
||||
openai_azure: {
|
||||
{
|
||||
actionTypeId: '.gen-ai',
|
||||
config: {
|
||||
apiProvider: 'Azure OpenAI',
|
||||
|
@ -91,8 +92,9 @@ describe('useLoadConnectors', () => {
|
|||
id: '3',
|
||||
isMissingSecrets: false,
|
||||
title: 'OpenAI Azure',
|
||||
type: 'openai_azure',
|
||||
},
|
||||
});
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -27,46 +27,44 @@ type OpenAIConnector = UserConfiguredActionConnector<
|
|||
Record<string, unknown>
|
||||
>;
|
||||
|
||||
const mapLLMToActionParam: Record<
|
||||
LLMs,
|
||||
const connectorTypeToLLM: Array<{
|
||||
actionId: string;
|
||||
actionProvider?: string;
|
||||
match: (connector: ActionConnector) => boolean;
|
||||
transform: (connector: ActionConnector) => PlaygroundConnector;
|
||||
}> = [
|
||||
{
|
||||
actionId: string;
|
||||
actionProvider?: string;
|
||||
match: (connector: ActionConnector) => boolean;
|
||||
transform: (connector: ActionConnector) => PlaygroundConnector;
|
||||
}
|
||||
> = {
|
||||
[LLMs.openai_azure]: {
|
||||
actionId: OPENAI_CONNECTOR_ID,
|
||||
actionProvider: OpenAiProviderType.AzureAi,
|
||||
match: (connector) =>
|
||||
connector.actionTypeId === OPENAI_CONNECTOR_ID &&
|
||||
(connector as OpenAIConnector).config.apiProvider === OpenAiProviderType.AzureAi,
|
||||
transform: (connector) => ({
|
||||
...connector,
|
||||
title: i18n.translate('xpack.searchPlayground.openAIAzureConnectorTitle', {
|
||||
defaultMessage: 'OpenAI Azure',
|
||||
}),
|
||||
type: LLMs.openai_azure,
|
||||
}),
|
||||
},
|
||||
[LLMs.openai]: {
|
||||
{
|
||||
actionId: OPENAI_CONNECTOR_ID,
|
||||
match: (connector) =>
|
||||
connector.actionTypeId === OPENAI_CONNECTOR_ID &&
|
||||
(connector as OpenAIConnector).config.apiProvider === OpenAiProviderType.OpenAi,
|
||||
transform: (connector) => ({
|
||||
...connector,
|
||||
title: i18n.translate('xpack.searchPlayground.openAIConnectorTitle', {
|
||||
defaultMessage: 'OpenAI',
|
||||
}),
|
||||
type: LLMs.openai,
|
||||
}),
|
||||
},
|
||||
};
|
||||
];
|
||||
|
||||
type PlaygroundConnector = ActionConnector & { title: string };
|
||||
type PlaygroundConnector = ActionConnector & { title: string; type: LLMs };
|
||||
|
||||
export const useLoadConnectors = (): UseQueryResult<
|
||||
Record<LLMs, PlaygroundConnector>,
|
||||
IHttpFetchError
|
||||
> => {
|
||||
export const useLoadConnectors = (): UseQueryResult<PlaygroundConnector[], IHttpFetchError> => {
|
||||
const {
|
||||
services: { http, notifications },
|
||||
} = useKibana();
|
||||
|
@ -76,19 +74,15 @@ export const useLoadConnectors = (): UseQueryResult<
|
|||
async () => {
|
||||
const queryResult = await loadConnectors({ http });
|
||||
|
||||
return Object.entries(mapLLMToActionParam).reduce<Partial<Record<LLMs, PlaygroundConnector>>>(
|
||||
(result, [llm, { actionId, match, transform }]) => {
|
||||
const targetConnector = queryResult.find(
|
||||
(connector) =>
|
||||
!connector.isMissingSecrets &&
|
||||
connector.actionTypeId === actionId &&
|
||||
(match?.(connector) ?? true)
|
||||
);
|
||||
return queryResult.reduce<PlaygroundConnector[]>((result, connector) => {
|
||||
const { transform } = connectorTypeToLLM.find(({ match }) => match(connector)) || {};
|
||||
|
||||
return targetConnector ? { ...result, [llm]: transform(targetConnector) } : result;
|
||||
},
|
||||
{}
|
||||
);
|
||||
if (!connector.isMissingSecrets && !!transform) {
|
||||
return [...result, transform(connector)];
|
||||
}
|
||||
|
||||
return result;
|
||||
}, []);
|
||||
},
|
||||
{
|
||||
retry: false,
|
||||
|
|
|
@ -40,13 +40,14 @@ describe('useManagementLink Hook', () => {
|
|||
const expectedUrl =
|
||||
'http://localhost:5601/app/management/insightsAndAlerting/triggersActionsConnectors';
|
||||
mockGetUrl.mockResolvedValue(expectedUrl);
|
||||
const { result, waitForNextUpdate } = renderHook(() => useManagementLink());
|
||||
const connectorId = 'test-connector-id';
|
||||
const { result, waitForNextUpdate } = renderHook(() => useManagementLink(connectorId));
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current).toBe(expectedUrl);
|
||||
expect(mockGetUrl).toHaveBeenCalledWith({
|
||||
sectionId: 'insightsAndAlerting',
|
||||
appId: 'triggersActionsConnectors',
|
||||
appId: 'triggersActionsConnectors/connectors/test-connector-id',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -62,8 +63,8 @@ describe('useManagementLink Hook', () => {
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useManagementLink());
|
||||
const connectorId = 'test-connector-id';
|
||||
const { result } = renderHook(() => useManagementLink(connectorId));
|
||||
|
||||
expect(result.current).toBe('');
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useKibana } from './use_kibana';
|
||||
|
||||
export const useManagementLink = () => {
|
||||
export const useManagementLink = (connectorId: string) => {
|
||||
const {
|
||||
services: { share },
|
||||
} = useKibana();
|
||||
|
@ -21,12 +21,12 @@ export const useManagementLink = () => {
|
|||
const getLink = async () => {
|
||||
const link = await managementLocator?.getUrl({
|
||||
sectionId: 'insightsAndAlerting',
|
||||
appId: 'triggersActionsConnectors',
|
||||
appId: `triggersActionsConnectors/connectors/${connectorId}`,
|
||||
});
|
||||
setManagementLink(link || '');
|
||||
};
|
||||
getLink();
|
||||
}, [managementLocator]);
|
||||
}, [managementLocator, connectorId]);
|
||||
|
||||
return managementLink;
|
||||
};
|
||||
|
|
|
@ -104,11 +104,6 @@ export interface AIMessage extends Message {
|
|||
retrievalDocs: Doc[];
|
||||
}
|
||||
|
||||
export enum SummarizationModelName {
|
||||
gpt3_5_turbo = 'gpt-3.5-turbo',
|
||||
gpt_4 = 'gpt-4',
|
||||
}
|
||||
|
||||
export interface ElasticsearchIndex {
|
||||
count: number; // Elasticsearch _count
|
||||
has_in_progress_syncs?: boolean; // these default to false if not a connector or crawler
|
||||
|
@ -194,7 +189,9 @@ export interface UseChatHelpers {
|
|||
export interface LLMModel {
|
||||
name: string;
|
||||
value?: string;
|
||||
showConnectorName?: boolean;
|
||||
connectorId: string;
|
||||
connectorName: string;
|
||||
icon: ComponentType;
|
||||
disabled: boolean;
|
||||
connectorId?: string;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ export type Section = 'connectors' | 'rules' | 'alerts' | 'logs';
|
|||
|
||||
export const routeToHome = `/`;
|
||||
export const routeToConnectors = `/connectors`;
|
||||
export const routeToConnectorEdit = `/connectors/:connectorId`;
|
||||
export const routeToRules = `/rules`;
|
||||
export const routeToLogs = `/logs`;
|
||||
export const legacyRouteToAlerts = `/alerts`;
|
||||
|
|
|
@ -12,7 +12,7 @@ import { Routes, Route } from '@kbn/shared-ux-router';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSpacer, EuiButtonEmpty, EuiPageHeader, EuiPageTemplate } from '@elastic/eui';
|
||||
import { routeToConnectors, routeToLogs, Section } from '../../../constants';
|
||||
import { routeToConnectorEdit, routeToConnectors, routeToLogs, Section } from '../../../constants';
|
||||
import { getAlertingSectionBreadcrumb } from '../../../lib/breadcrumb';
|
||||
import { getCurrentDocTitle } from '../../../lib/doc_title';
|
||||
import { suspendedComponentWithProps } from '../../../lib/suspended_component_with_props';
|
||||
|
@ -126,7 +126,7 @@ export const ActionsConnectorsHome: React.FunctionComponent<RouteComponentProps<
|
|||
<Route exact path={routeToLogs} component={renderLogsList} />
|
||||
<Route
|
||||
exact
|
||||
path={routeToConnectors}
|
||||
path={[routeToConnectors, routeToConnectorEdit]}
|
||||
component={suspendedComponentWithProps(ConnectorsList, 'xl')}
|
||||
/>
|
||||
</Routes>
|
||||
|
|
|
@ -29,6 +29,15 @@ const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
|
|||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||
const mocks = coreMock.createSetup();
|
||||
const { loadAllActions, loadActionTypes } = jest.requireMock('../../../lib/action_connector_api');
|
||||
const mockGetParams = jest.fn().mockReturnValue({});
|
||||
const mockGetLocation = jest.fn().mockReturnValue({ search: '' });
|
||||
const mockGetHistory = jest.fn().mockReturnValue({ push: jest.fn(), createHref: jest.fn() });
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useParams: () => mockGetParams(),
|
||||
useLocation: () => mockGetLocation(),
|
||||
useHistory: () => mockGetHistory(),
|
||||
}));
|
||||
|
||||
describe('actions_connectors_list', () => {
|
||||
describe('component empty', () => {
|
||||
|
|
|
@ -28,6 +28,7 @@ import { omit } from 'lodash';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { withTheme, EuiTheme } from '@kbn/kibana-react-plugin/common';
|
||||
import { getConnectorCompatibility } from '@kbn/actions-plugin/common';
|
||||
import { useHistory, useLocation, useParams } from 'react-router-dom';
|
||||
import { loadAllActions, loadActionTypes, deleteActions } from '../../../lib/action_connector_api';
|
||||
import {
|
||||
hasDeleteActionsCapability,
|
||||
|
@ -54,6 +55,13 @@ import { CreateConnectorFlyout } from '../../action_connector_form/create_connec
|
|||
import { EditConnectorFlyout } from '../../action_connector_form/edit_connector_flyout';
|
||||
import { getAlertingSectionBreadcrumb } from '../../../lib/breadcrumb';
|
||||
import { getCurrentDocTitle } from '../../../lib/doc_title';
|
||||
import { routeToConnectors } from '../../../constants';
|
||||
|
||||
interface EditConnectorProps {
|
||||
initialConnector?: ActionConnector;
|
||||
tab?: EditConnectorTabs;
|
||||
isFix?: boolean;
|
||||
}
|
||||
|
||||
const ConnectorIconTipWithSpacing = withTheme(({ theme }: { theme: EuiTheme }) => {
|
||||
return (
|
||||
|
@ -89,6 +97,9 @@ const ActionsConnectorsList: React.FunctionComponent = () => {
|
|||
chrome,
|
||||
docLinks,
|
||||
} = useKibana().services;
|
||||
const { connectorId } = useParams<{ connectorId?: string }>();
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const canDelete = hasDeleteActionsCapability(capabilities);
|
||||
const canSave = hasSaveActionsCapability(capabilities);
|
||||
|
||||
|
@ -97,13 +108,9 @@ const ActionsConnectorsList: React.FunctionComponent = () => {
|
|||
const [pageIndex, setPageIndex] = useState<number>(0);
|
||||
const [selectedItems, setSelectedItems] = useState<ActionConnectorTableItem[]>([]);
|
||||
const [isLoadingActionTypes, setIsLoadingActionTypes] = useState<boolean>(false);
|
||||
const [isLoadingActions, setIsLoadingActions] = useState<boolean>(false);
|
||||
const [isLoadingActions, setIsLoadingActions] = useState<boolean>(true);
|
||||
const [addFlyoutVisible, setAddFlyoutVisibility] = useState<boolean>(false);
|
||||
const [editConnectorProps, setEditConnectorProps] = useState<{
|
||||
initialConnector?: ActionConnector;
|
||||
tab?: EditConnectorTabs;
|
||||
isFix?: boolean;
|
||||
}>({});
|
||||
const [editConnectorProps, setEditConnectorProps] = useState<EditConnectorProps>({});
|
||||
const [connectorsToDelete, setConnectorsToDelete] = useState<string[]>([]);
|
||||
useEffect(() => {
|
||||
loadActions();
|
||||
|
@ -164,6 +171,19 @@ const ActionsConnectorsList: React.FunctionComponent = () => {
|
|||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
: [];
|
||||
|
||||
useEffect(() => {
|
||||
if (connectorId && !isLoadingActions) {
|
||||
const connector = actions.find((action) => action.id === connectorId);
|
||||
if (connector) {
|
||||
editItem(connector, EditConnectorTabs.Configuration);
|
||||
}
|
||||
|
||||
const linkToConnectors = history.createHref({ pathname: routeToConnectors });
|
||||
|
||||
window.history.replaceState(null, '', linkToConnectors);
|
||||
}
|
||||
}, [actions, connectorId, history, isLoadingActions, location]);
|
||||
|
||||
function setDeleteConnectorWarning(connectors: string[]) {
|
||||
const show = connectors.some((c) => {
|
||||
const action = actions.find((a) => a.id === c);
|
||||
|
@ -197,11 +217,7 @@ const ActionsConnectorsList: React.FunctionComponent = () => {
|
|||
}
|
||||
}
|
||||
|
||||
async function editItem(
|
||||
actionConnector: ActionConnector,
|
||||
tab: EditConnectorTabs,
|
||||
isFix?: boolean
|
||||
) {
|
||||
function editItem(actionConnector: ActionConnector, tab: EditConnectorTabs, isFix?: boolean) {
|
||||
setEditConnectorProps({ initialConnector: actionConnector, tab, isFix: isFix ?? false });
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue