Skip to content

Commit

Permalink
[AI Connector] Migrates AI inference Connector to use a shared compon…
Browse files Browse the repository at this point in the history
…ents from '@kbn/inference-endpoint-ui-common' (elastic#204885)

1. Migrated stack-connector `.inference` to use share UI components
elastic#203204
2. Extended package `@kbn/inference-endpoint-ui-common` to support edit
mode for the form of the connector, by adding the optional property
`isEdit` to `InferenceServiceFormFields` component
3. Resolves flaky timing out test
elastic#205129

---------

Co-authored-by: Samiul Monir <[email protected]>
Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
3 people authored and kowalczyk-krzysztof committed Jan 7, 2025
1 parent dd04900 commit 7e22b55
Show file tree
Hide file tree
Showing 43 changed files with 222 additions and 3,275 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
*/

export { InferenceServiceFormFields } from './src/components/inference_service_form_fields';
export { useProviders } from './src/hooks/use_providers';

export * from './src/types/types';
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ interface AdditionalOptionsFieldsProps {
onTaskTypeOptionsSelect: (taskType: string, provider?: string) => void;
selectedTaskType?: string;
taskTypeOptions: TaskTypeOption[];
isEdit?: boolean;
}

export const AdditionalOptionsFields: React.FC<AdditionalOptionsFieldsProps> = ({
Expand All @@ -61,6 +62,7 @@ export const AdditionalOptionsFields: React.FC<AdditionalOptionsFieldsProps> = (
selectedTaskType,
onSetProviderConfigEntry,
onTaskTypeOptionsSelect,
isEdit,
}) => {
const xsFontSize = useEuiFontSize('xs').fontSize;
const { euiTheme } = useEuiTheme();
Expand Down Expand Up @@ -106,7 +108,18 @@ export const AdditionalOptionsFields: React.FC<AdditionalOptionsFieldsProps> = (

return (
<EuiFormRow id="taskType" fullWidth isInvalid={isInvalid} error={errorMessage}>
{taskTypeOptions.length === 1 ? (
{isEdit ? (
<EuiButton
css={{
background: euiTheme.colors.disabled,
color: euiTheme.colors.lightestShade,
}}
data-test-subj="taskTypeSelectDisabled"
isDisabled
>
{config.taskType}
</EuiButton>
) : taskTypeOptions.length === 1 ? (
<EuiButton
css={{
background: euiTheme.colors.darkShade,
Expand Down Expand Up @@ -140,7 +153,11 @@ export const AdditionalOptionsFields: React.FC<AdditionalOptionsFieldsProps> = (
selectedTaskType,
config.taskType,
xsFontSize,
euiTheme.colors,
euiTheme.colors.textSubdued,
euiTheme.colors.disabled,
euiTheme.colors.lightestShade,
euiTheme.colors.darkShade,
isEdit,
taskTypeOptions,
onTaskTypeOptionsSelect,
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Form, useForm } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
import { I18nProvider } from '@kbn/i18n-react';
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';

const providers = [
const mockProviders = [
{
service: 'hugging_face',
name: 'Hugging Face',
Expand Down Expand Up @@ -110,6 +112,15 @@ const providers = [
},
] as InferenceProvider[];

jest.mock('../hooks/use_providers', () => ({
useProviders: jest.fn(() => ({
data: mockProviders,
})),
}));

const httpMock = httpServiceMock.createStartContract();
const notificationsMock = notificationServiceMock.createStartContract();

const MockFormProvider = ({ children }: { children: React.ReactElement }) => {
const { form } = useForm();

Expand All @@ -124,7 +135,7 @@ describe('Inference Services', () => {
it('renders', () => {
render(
<MockFormProvider>
<InferenceServiceFormFields providers={providers} />
<InferenceServiceFormFields http={httpMock} toasts={notificationsMock.toasts} />
</MockFormProvider>
);

Expand All @@ -134,7 +145,7 @@ describe('Inference Services', () => {
it('renders Selectable', async () => {
render(
<MockFormProvider>
<InferenceServiceFormFields providers={providers} />
<InferenceServiceFormFields http={httpMock} toasts={notificationsMock.toasts} />
</MockFormProvider>
);

Expand All @@ -145,7 +156,7 @@ describe('Inference Services', () => {
it('renders selected provider fields - hugging_face', async () => {
render(
<MockFormProvider>
<InferenceServiceFormFields providers={providers} />
<InferenceServiceFormFields http={httpMock} toasts={notificationsMock.toasts} />
</MockFormProvider>
);

Expand All @@ -165,7 +176,7 @@ describe('Inference Services', () => {
it('re-renders fields when selected to anthropic from hugging_face', async () => {
render(
<MockFormProvider>
<InferenceServiceFormFields providers={providers} />
<InferenceServiceFormFields http={httpMock} toasts={notificationsMock.toasts} />
</MockFormProvider>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ import {
import { FormattedMessage } from '@kbn/i18n-react';
import { ConnectorFormSchema } from '@kbn/triggers-actions-ui-plugin/public';

import { HttpSetup, IToasts } from '@kbn/core/public';
import * as LABELS from '../translations';
import { Config, ConfigEntryView, FieldType, InferenceProvider, Secrets } from '../types/types';
import { Config, ConfigEntryView, FieldType, Secrets } from '../types/types';
import { SERVICE_PROVIDERS } from './providers/render_service_provider/service_provider';
import { DEFAULT_TASK_TYPE, ServiceProviderKeys } from '../constants';
import { SelectableProvider } from './providers/selectable';
Expand All @@ -36,12 +37,20 @@ import { ConfigurationFormItems } from './configuration/configuration_form_items
import { AdditionalOptionsFields } from './additional_options_fields';
import { ProviderSecretHiddenField } from './hidden_fields/provider_secret_hidden_field';
import { ProviderConfigHiddenField } from './hidden_fields/provider_config_hidden_field';
import { useProviders } from '../hooks/use_providers';

interface InferenceServicesProps {
providers: InferenceProvider[];
http: HttpSetup;
toasts: IToasts;
isEdit?: boolean;
}

export const InferenceServiceFormFields: React.FC<InferenceServicesProps> = ({ providers }) => {
export const InferenceServiceFormFields: React.FC<InferenceServicesProps> = ({
http,
toasts,
isEdit,
}) => {
const { data: providers, isLoading } = useProviders(http, toasts);
const [isProviderPopoverOpen, setProviderPopoverOpen] = useState(false);
const [providerSchema, setProviderSchema] = useState<ConfigEntryView[]>([]);
const [taskTypeOptions, setTaskTypeOptions] = useState<TaskTypeOption[]>([]);
Expand Down Expand Up @@ -213,8 +222,9 @@ export const InferenceServiceFormFields: React.FC<InferenceServicesProps> = ({ p
const providerSuperSelect = useCallback(
(isInvalid: boolean) => (
<EuiFormControlLayout
clear={{ onClick: onClearProvider }}
clear={isEdit ? undefined : { onClick: onClearProvider }}
isDropdown
isDisabled={isEdit}
isInvalid={isInvalid}
fullWidth
icon={!config?.provider ? { type: 'sparkles', side: 'left' } : providerIcon}
Expand All @@ -223,6 +233,7 @@ export const InferenceServiceFormFields: React.FC<InferenceServicesProps> = ({ p
onClick={toggleProviderPopover}
data-test-subj="provider-select"
isInvalid={isInvalid}
disabled={isEdit}
onKeyDown={handleProviderKeyboardOpen}
value={config?.provider ? providerName : ''}
fullWidth
Expand All @@ -239,16 +250,31 @@ export const InferenceServiceFormFields: React.FC<InferenceServicesProps> = ({ p
</EuiFormControlLayout>
),
[
config?.provider,
handleProviderKeyboardOpen,
toggleProviderPopover,
isProviderPopoverOpen,
isEdit,
onClearProvider,
config?.provider,
providerIcon,
toggleProviderPopover,
handleProviderKeyboardOpen,
providerName,
isProviderPopoverOpen,
]
);

useEffect(() => {
if (config?.provider && isEdit) {
const newProvider = providers?.find((p) => p.service === config.provider);
// Update connector providerSchema
const newProviderSchema = Object.keys(newProvider?.configurations ?? {}).map((k) => ({
key: k,
isValid: true,
...newProvider?.configurations[k],
})) as ConfigEntryView[];

setProviderSchema(newProviderSchema);
}
}, [config?.provider, config?.taskType, isEdit, providers]);

useEffect(() => {
if (isSubmitting) {
validateFields(['config.providerConfig']);
Expand Down Expand Up @@ -291,7 +317,7 @@ export const InferenceServiceFormFields: React.FC<InferenceServicesProps> = ({ p
setRequiredProviderFormFields(existingConfiguration.filter((p) => p.required || p.sensitive));
}, [config?.providerConfig, providerSchema, secrets]);

return (
return !isLoading ? (
<>
<UseField
path="config.provider"
Expand Down Expand Up @@ -329,7 +355,7 @@ export const InferenceServiceFormFields: React.FC<InferenceServicesProps> = ({ p
className="rightArrowIcon"
>
<SelectableProvider
providers={providers}
providers={providers ?? []}
onClosePopover={closeProviderPopover}
onProviderChange={onProviderChange}
/>
Expand All @@ -355,6 +381,7 @@ export const InferenceServiceFormFields: React.FC<InferenceServicesProps> = ({ p
onTaskTypeOptionsSelect={onTaskTypeOptionsSelect}
taskTypeOptions={taskTypeOptions}
selectedTaskType={selectedTaskType}
isEdit={isEdit}
/>
<EuiSpacer size="m" />
<EuiHorizontalRule margin="xs" />
Expand All @@ -371,5 +398,5 @@ export const InferenceServiceFormFields: React.FC<InferenceServicesProps> = ({ p
</>
) : null}
</>
);
) : null;
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

import type { HttpSetup } from '@kbn/core-http-browser';
import { useQuery } from '@tanstack/react-query';
import { FieldType, InferenceProvider } from '@kbn/inference-endpoint-ui-common';
import { KibanaServerError } from '@kbn/kibana-utils-plugin/common';
import { useKibana } from './use_kibana';
import * as i18n from './translations';
import { IToasts } from '@kbn/core/public';
import { FieldType, InferenceProvider } from '../..';
import * as i18n from '../translations';

const getProviders = (http: HttpSetup): InferenceProvider[] => {
return [
Expand Down Expand Up @@ -624,9 +624,7 @@ const getProviders = (http: HttpSetup): InferenceProvider[] => {
];
};

export const useProviders = () => {
const { services } = useKibana();
const toasts = services.notifications?.toasts;
export const useProviders = (http: HttpSetup, toasts: IToasts) => {
const onErrorFn = (error: { body: KibanaServerError }) => {
toasts?.addError(new Error(error.body.message), {
title: i18n.GET_PROVIDERS_FAILED,
Expand All @@ -635,7 +633,7 @@ export const useProviders = () => {
};

const query = useQuery(['user-profile'], {
queryFn: () => getProviders(services.http),
queryFn: () => getProviders(http),
staleTime: Infinity,
refetchOnWindowFocus: false,
onError: onErrorFn,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,10 @@ export const RE_ENTER_SECRETS = (label: string) => {
values: { label },
});
};

export const GET_PROVIDERS_FAILED = i18n.translate(
'xpack.inferenceEndpointUICommon.hooks.unableToFindProvidersQueryMessage',
{
defaultMessage: 'Unable to find providers',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
"@kbn/i18n-react",
"@kbn/search-connectors",
"@kbn/es-ui-shared-plugin",
"@kbn/triggers-actions-ui-plugin"
"@kbn/triggers-actions-ui-plugin",
"@kbn/core-http-browser",
"@kbn/kibana-utils-plugin",
"@kbn/core",
"@kbn/core-http-browser-mocks",
"@kbn/core-notifications-browser-mocks"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -44983,35 +44983,22 @@
"xpack.stackConnectors.components.index.preconfiguredIndexHelpText": "Les documents sont indexés dans l'index {alertHistoryIndex}.",
"xpack.stackConnectors.components.index.resetDefaultIndexLabel": "Réinitialiser l'index par défaut",
"xpack.stackConnectors.components.index.selectMessageText": "Indexez les données dans Elasticsearch.",
"xpack.stackConnectors.components.inference.additionalOptionsLabel": "Options supplémentaires",
"xpack.stackConnectors.components.inference.bodyCodeEditorAriaLabel": "Éditeur de code",
"xpack.stackConnectors.components.inference.bodyFieldLabel": "Corps",
"xpack.stackConnectors.components.inference.completionInputLabel": "Entrée",
"xpack.stackConnectors.components.inference.completionInputTypeLabel": "Type d'entrée",
"xpack.stackConnectors.components.inference.config.optionalValue": "Facultatif",
"xpack.stackConnectors.components.inference.connectorTypeTitle": "Connecteur IA",
"xpack.stackConnectors.components.inference.copied.tooltip": "Copié !",
"xpack.stackConnectors.components.inference.copy.tooltip": "Copier dans le presse-papiers",
"xpack.stackConnectors.components.inference.copyLabel": "Copier",
"xpack.stackConnectors.components.inference.documentation": "Documentation de l'API d'inférence",
"xpack.stackConnectors.components.inference.error.requiredProviderText": "Le fournisseur est requis.",
"xpack.stackConnectors.components.inference.inferenceEndpointHelpLabel": "Les points de terminaison d'inférence fournissent une méthode simplifiée pour utiliser cette configuration, en particulier à partir de l'API",
"xpack.stackConnectors.components.inference.inferenceEndpointLabel": "Point de terminaison d'inférence",
"xpack.stackConnectors.components.inference.inferenceIdHelpLabel": "Cet identifiant ne peut pas être modifié une fois créé.",
"xpack.stackConnectors.components.inference.invalidActionText": "Nom d'action non valide.",
"xpack.stackConnectors.components.inference.providerFieldLabel": "Fournisseur",
"xpack.stackConnectors.components.inference.providerLabel": "Service",
"xpack.stackConnectors.components.inference.providerOptionalSettingsHelpLabel": "Configurer le fournisseur d'inférence. Ces paramètres sont des paramètres de fournisseur facultatifs.",
"xpack.stackConnectors.components.inference.providerOptionalSettingsLabel": "Paramètres de service",
"xpack.stackConnectors.components.inference.requiredGenericTextField": "{field} est obligatoire.",
"xpack.stackConnectors.components.inference.rerankQueryLabel": "Recherche",
"xpack.stackConnectors.components.inference.selectable.providerSearch": "Recherche",
"xpack.stackConnectors.components.inference.selectMessageText": "Envoyez des demandes aux fournisseurs d'IA tels qu'Amazon Bedrock, OpenAI et bien d'autres.",
"xpack.stackConnectors.components.inference.selectProvider": "Sélectionner un service",
"xpack.stackConnectors.components.inference.taskTypeDetailsLabel": "Paramètres des tâches",
"xpack.stackConnectors.components.inference.taskTypeFieldLabel": "Type de tâche",
"xpack.stackConnectors.components.inference.taskTypeHelpLabel": "Configurer la tâche d'inférence. Ces paramètres sont spécifiques au service et au modèle sélectionnés.",
"xpack.stackConnectors.components.inference.unableToFindProvidersQueryMessage": "Impossible de trouver des fournisseurs",
"xpack.stackConnectors.components.jira.apiTokenTextFieldLabel": "Token d'API",
"xpack.stackConnectors.components.jira.apiUrlTextFieldLabel": "URL",
"xpack.stackConnectors.components.jira.commentsTextAreaFieldLabel": "Commentaires supplémentaires",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44833,35 +44833,22 @@
"xpack.stackConnectors.components.index.preconfiguredIndexHelpText": "ドキュメントは{alertHistoryIndex}インデックスにインデックスされます。",
"xpack.stackConnectors.components.index.resetDefaultIndexLabel": "デフォルトのインデックスをリセット",
"xpack.stackConnectors.components.index.selectMessageText": "データを Elasticsearch にインデックスしてください。",
"xpack.stackConnectors.components.inference.additionalOptionsLabel": "その他のオプション",
"xpack.stackConnectors.components.inference.bodyCodeEditorAriaLabel": "コードエディター",
"xpack.stackConnectors.components.inference.bodyFieldLabel": "本文",
"xpack.stackConnectors.components.inference.completionInputLabel": "インプット",
"xpack.stackConnectors.components.inference.completionInputTypeLabel": "入力タイプ",
"xpack.stackConnectors.components.inference.config.optionalValue": "オプション",
"xpack.stackConnectors.components.inference.connectorTypeTitle": "AIコネクター",
"xpack.stackConnectors.components.inference.copied.tooltip": "コピー完了",
"xpack.stackConnectors.components.inference.copy.tooltip": "クリップボードにコピー",
"xpack.stackConnectors.components.inference.copyLabel": "コピー",
"xpack.stackConnectors.components.inference.documentation": "推論APIドキュメント",
"xpack.stackConnectors.components.inference.error.requiredProviderText": "プロバイダーは必須です。",
"xpack.stackConnectors.components.inference.inferenceEndpointHelpLabel": "推論エンドポイントは、特にAPIから、この構成を簡単に利用できる方法を提供します。",
"xpack.stackConnectors.components.inference.inferenceEndpointLabel": "推論エンドポイント",
"xpack.stackConnectors.components.inference.inferenceIdHelpLabel": "このIDは、作成すると、変更できません。",
"xpack.stackConnectors.components.inference.invalidActionText": "無効なアクション名です。",
"xpack.stackConnectors.components.inference.providerFieldLabel": "プロバイダー",
"xpack.stackConnectors.components.inference.providerLabel": "サービス",
"xpack.stackConnectors.components.inference.providerOptionalSettingsHelpLabel": "推論プロバイダーを構成します。これらの設定はオプションのプロバイダー設定です。",
"xpack.stackConnectors.components.inference.providerOptionalSettingsLabel": "サービス設定",
"xpack.stackConnectors.components.inference.requiredGenericTextField": "{field}は必須です。",
"xpack.stackConnectors.components.inference.rerankQueryLabel": "クエリー",
"xpack.stackConnectors.components.inference.selectable.providerSearch": "検索",
"xpack.stackConnectors.components.inference.selectMessageText": "Amazon Bedrock、OpenAIなどのAIプロバイダーに要求を送信します。",
"xpack.stackConnectors.components.inference.selectProvider": "サービスを選択",
"xpack.stackConnectors.components.inference.taskTypeDetailsLabel": "タスク設定",
"xpack.stackConnectors.components.inference.taskTypeFieldLabel": "タスクタイプ",
"xpack.stackConnectors.components.inference.taskTypeHelpLabel": "推論タスクを構成します。これらの設定は、選択したサービスおよびモデルに固有です。",
"xpack.stackConnectors.components.inference.unableToFindProvidersQueryMessage": "プロバイダーが見つかりません",
"xpack.stackConnectors.components.jira.apiTokenTextFieldLabel": "APIトークン",
"xpack.stackConnectors.components.jira.apiUrlTextFieldLabel": "URL",
"xpack.stackConnectors.components.jira.commentsTextAreaFieldLabel": "追加のコメント",
Expand Down
Loading

0 comments on commit 7e22b55

Please sign in to comment.