Skip to content

Commit

Permalink
[Stack Connectors] Add organizationId and projectId OpenAI header…
Browse files Browse the repository at this point in the history
…s, along with arbitrary headers (#213117)
  • Loading branch information
stephmilovic authored Mar 6, 2025
1 parent 5b8fd8f commit 1531849
Show file tree
Hide file tree
Showing 12 changed files with 423 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42073,7 +42073,6 @@
"xpack.stackConnectors.components.genAi.connectorTypeTitle": "OpenAI",
"xpack.stackConnectors.components.genAi.dashboardLink": "Affichez le tableau de bord d'utilisation de {apiProvider} pour le connecteur \"{ connectorName }\"",
"xpack.stackConnectors.components.genAi.defaultModelTextFieldLabel": "Modèle par défaut",
"xpack.stackConnectors.components.genAi.defaultModelTooltipContent": "Si une requête ne comprend pas de modèle, le modèle par défaut est utilisé.",
"xpack.stackConnectors.components.genAi.documentation": "documentation",
"xpack.stackConnectors.components.genAi.error.dashboardApiError": "Erreur lors de la recherche du tableau de bord d'utilisation des tokens {apiProvider}.",
"xpack.stackConnectors.components.genAi.error.requiredApiProviderText": "Un fournisseur d’API est nécessaire.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41926,7 +41926,6 @@
"xpack.stackConnectors.components.genAi.connectorTypeTitle": "OpenAI",
"xpack.stackConnectors.components.genAi.dashboardLink": "\"{ connectorName }\"コネクターの{apiProvider}使用状況ダッシュボードを表示",
"xpack.stackConnectors.components.genAi.defaultModelTextFieldLabel": "デフォルトモデル",
"xpack.stackConnectors.components.genAi.defaultModelTooltipContent": "リクエストにモデルが含まれていない場合、デフォルトが使われます。",
"xpack.stackConnectors.components.genAi.documentation": "ドキュメンテーション",
"xpack.stackConnectors.components.genAi.error.dashboardApiError": "{apiProvider}トークン使用状況ダッシュボードの検索エラー。",
"xpack.stackConnectors.components.genAi.error.requiredApiProviderText": "APIプロバイダーは必須です。",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41303,7 +41303,6 @@
"xpack.stackConnectors.components.genAi.bodyFieldLabel": "正文",
"xpack.stackConnectors.components.genAi.connectorTypeTitle": "OpenAI",
"xpack.stackConnectors.components.genAi.defaultModelTextFieldLabel": "默认模型",
"xpack.stackConnectors.components.genAi.defaultModelTooltipContent": "如果请求不包含模型,它将使用默认值。",
"xpack.stackConnectors.components.genAi.documentation": "文档",
"xpack.stackConnectors.components.genAi.error.dashboardApiError": "查找 {apiProvider} 令牌使用情况仪表板时出错。",
"xpack.stackConnectors.components.genAi.error.requiredApiProviderText": "'API 提供商'必填。",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const allowedExperimentalValues = Object.freeze({
inferenceConnectorOff: false,
crowdstrikeConnectorRTROn: true,
microsoftDefenderEndpointOn: true,
openAIAdditionalHeadersOn: false,
});

export type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export const ConfigSchema = schema.oneOf([
schema.object({
apiProvider: schema.oneOf([schema.literal(OpenAiProviderType.OpenAi)]),
apiUrl: schema.string(),
organizationId: schema.maybe(schema.string()),
projectId: schema.maybe(schema.string()),
defaultModel: schema.string({ defaultValue: DEFAULT_OPENAI_MODEL }),
headers: schema.maybe(schema.recordOf(schema.string(), schema.string())),
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
import React from 'react';
import ConnectorFields from './connector';
import { ConnectorFormTestProvider } from '../lib/test_utils';
import { act, fireEvent, render, waitFor } from '@testing-library/react';
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { DEFAULT_OPENAI_MODEL, OpenAiProviderType } from '../../../common/openai/constants';
import { useKibana } from '@kbn/triggers-actions-ui-plugin/public';
import { useGetDashboard } from '../lib/gen_ai/use_get_dashboard';
import { createStartServicesMock } from '@kbn/triggers-actions-ui-plugin/public/common/lib/kibana/kibana_react.mock';
import { ExperimentalFeaturesService } from '../../common/experimental_features_service';
import { experimentalFeaturesMock } from '../../mocks';

const mockUseKibanaReturnValue = createStartServicesMock();
jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana', () => ({
Expand All @@ -38,6 +40,9 @@ const openAiConnector = {
secrets: {
apiKey: 'thats-a-nice-looking-key',
},
__internal__: {
hasHeaders: false,
},
isDeprecated: false,
};
const azureConnector = {
Expand Down Expand Up @@ -65,6 +70,12 @@ const otherOpenAiConnector = {
const navigateToUrl = jest.fn();

describe('ConnectorFields renders', () => {
beforeAll(() => {
ExperimentalFeaturesService.init({
// @ts-ignore force enable for testing
experimentalFeatures: { ...experimentalFeaturesMock, openAIAdditionalHeadersOn: true },
});
});
beforeEach(() => {
jest.clearAllMocks();
useKibanaMock().services.application.navigateToUrl = navigateToUrl;
Expand All @@ -84,12 +95,14 @@ describe('ConnectorFields renders', () => {
expect(getAllByTestId('config.apiProvider-select')[0]).toHaveValue(
openAiConnector.config.apiProvider
);
expect(getAllByTestId('config.organizationId-input')[0]).toBeInTheDocument();
expect(getAllByTestId('config.projectId-input')[0]).toBeInTheDocument();
expect(getAllByTestId('open-ai-api-doc')[0]).toBeInTheDocument();
expect(getAllByTestId('open-ai-api-keys-doc')[0]).toBeInTheDocument();
});

test('azure ai connector fields are rendered', async () => {
const { getAllByTestId } = render(
const { getAllByTestId, queryByTestId } = render(
<ConnectorFormTestProvider connector={azureConnector}>
<ConnectorFields readOnly={false} isEdit={false} registerPreSubmitValidator={() => {}} />
</ConnectorFormTestProvider>
Expand All @@ -102,10 +115,12 @@ describe('ConnectorFields renders', () => {
);
expect(getAllByTestId('azure-ai-api-doc')[0]).toBeInTheDocument();
expect(getAllByTestId('azure-ai-api-keys-doc')[0]).toBeInTheDocument();
expect(queryByTestId('config.organizationId-input')).not.toBeInTheDocument();
expect(queryByTestId('config.projectId-input')).not.toBeInTheDocument();
});

test('other open ai connector fields are rendered', async () => {
const { getAllByTestId } = render(
const { getAllByTestId, queryByTestId } = render(
<ConnectorFormTestProvider connector={otherOpenAiConnector}>
<ConnectorFields readOnly={false} isEdit={false} registerPreSubmitValidator={() => {}} />
</ConnectorFormTestProvider>
Expand All @@ -120,6 +135,149 @@ describe('ConnectorFields renders', () => {
);
expect(getAllByTestId('other-ai-api-doc')[0]).toBeInTheDocument();
expect(getAllByTestId('other-ai-api-keys-doc')[0]).toBeInTheDocument();
expect(queryByTestId('config.organizationId-input')).not.toBeInTheDocument();
expect(queryByTestId('config.projectId-input')).not.toBeInTheDocument();
});
describe('Headers', () => {
it('toggles headers as expected', async () => {
const testFormData = {
actionTypeId: '.gen-ai',
name: 'OpenAI',
id: '123',
config: {
apiUrl: 'https://openaiurl.com',
apiProvider: OpenAiProviderType.OpenAi,
defaultModel: DEFAULT_OPENAI_MODEL,
},
secrets: {
apiKey: 'thats-a-nice-looking-key',
},
isDeprecated: false,
__internal__: {
hasHeaders: false,
},
};
render(
<ConnectorFormTestProvider connector={testFormData}>
<ConnectorFields readOnly={false} isEdit={false} registerPreSubmitValidator={() => {}} />
</ConnectorFormTestProvider>
);

const headersToggle = await screen.findByTestId('openAIViewHeadersSwitch');

expect(headersToggle).toBeInTheDocument();

await userEvent.click(headersToggle);

expect(await screen.findByTestId('openAIHeaderText')).toBeInTheDocument();
expect(await screen.findByTestId('openAIHeadersKeyInput')).toBeInTheDocument();
expect(await screen.findByTestId('openAIHeadersValueInput')).toBeInTheDocument();
expect(await screen.findByTestId('openAIAddHeaderButton')).toBeInTheDocument();
});
it('succeeds without headers', async () => {
const testFormData = {
actionTypeId: '.gen-ai',
name: 'OpenAI',
id: '123',
config: {
apiUrl: 'https://openaiurl.com',
apiProvider: OpenAiProviderType.OpenAi,
defaultModel: DEFAULT_OPENAI_MODEL,
},
secrets: {
apiKey: 'thats-a-nice-looking-key',
},
isDeprecated: false,
__internal__: {
hasHeaders: false,
},
};
const onSubmit = jest.fn();
render(
<ConnectorFormTestProvider connector={testFormData} onSubmit={onSubmit}>
<ConnectorFields readOnly={false} isEdit={false} registerPreSubmitValidator={() => {}} />
</ConnectorFormTestProvider>
);

await userEvent.click(await screen.findByTestId('form-test-provide-submit'));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
data: {
actionTypeId: '.gen-ai',
name: 'OpenAI',
id: '123',
isDeprecated: false,
config: {
apiUrl: 'https://openaiurl.com',
apiProvider: OpenAiProviderType.OpenAi,
defaultModel: DEFAULT_OPENAI_MODEL,
},
secrets: {
apiKey: 'thats-a-nice-looking-key',
},
__internal__: {
hasHeaders: false,
},
},
isValid: true,
});
});
});
it('succeeds with headers', async () => {
const testFormData = {
actionTypeId: '.gen-ai',
name: 'OpenAI',
id: '123',
config: {
apiUrl: 'https://openaiurl.com',
apiProvider: OpenAiProviderType.OpenAi,
defaultModel: DEFAULT_OPENAI_MODEL,
},
secrets: {
apiKey: 'thats-a-nice-looking-key',
},
isDeprecated: false,
__internal__: {
hasHeaders: false,
},
};
const onSubmit = jest.fn();
render(
<ConnectorFormTestProvider connector={testFormData} onSubmit={onSubmit}>
<ConnectorFields readOnly={false} isEdit={false} registerPreSubmitValidator={() => {}} />
</ConnectorFormTestProvider>
);
const headersToggle = await screen.findByTestId('openAIViewHeadersSwitch');
expect(headersToggle).toBeInTheDocument();
await userEvent.click(headersToggle);

await userEvent.type(screen.getByTestId('openAIHeadersKeyInput'), 'hello');
await userEvent.type(screen.getByTestId('openAIHeadersValueInput'), 'world');
await userEvent.click(await screen.findByTestId('form-test-provide-submit'));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
data: {
actionTypeId: '.gen-ai',
name: 'OpenAI',
id: '123',
isDeprecated: false,
config: {
apiUrl: 'https://openaiurl.com',
apiProvider: OpenAiProviderType.OpenAi,
defaultModel: DEFAULT_OPENAI_MODEL,
headers: [{ key: 'hello', value: 'world' }],
},
secrets: {
apiKey: 'thats-a-nice-looking-key',
},
__internal__: {
hasHeaders: true,
},
},
isValid: true,
});
});
});
});

describe('Dashboard link', () => {
Expand Down
Loading

0 comments on commit 1531849

Please sign in to comment.