[Observability AI Assistant] Fix Storybook (#183399)

Resolves https://github.com/elastic/kibana/issues/183400

## Summary

This fixes all broken stories in the Observability AI Assistant
storybook.


9200c47e-87bb-484d-a552-68afdf79108c

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Coen Warmer 2024-05-31 17:12:46 +02:00 committed by GitHub
parent 45a73a7f29
commit 4eb175f384
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 325 additions and 48 deletions

View file

@ -6,5 +6,8 @@
*/
import { EuiThemeProviderDecorator } from '@kbn/kibana-react-plugin/common';
import * as jest from 'jest-mock';
export const decorators = [EuiThemeProviderDecorator];
window.jest = jest;

View file

@ -8,7 +8,7 @@
import { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import React from 'react';
import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
import { ChatBody as Component } from './chat_body';
import { buildSystemMessage } from '../../utils/builders';

View file

@ -8,8 +8,8 @@
import { ComponentStory } from '@storybook/react';
import React from 'react';
import { buildSystemMessage } from '../../utils/builders';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
import { ChatFlyout as Component } from './chat_flyout';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
import { ChatFlyout as Component, FlyoutPositionMode } from './chat_flyout';
export default {
component: Component,
@ -31,6 +31,7 @@ const defaultProps: ChatFlyoutProps = {
isOpen: true,
initialTitle: 'How is this working',
initialMessages: [buildSystemMessage()],
initialFlyoutPositionMode: FlyoutPositionMode.OVERLAY,
onClose: () => {},
};

View file

@ -7,7 +7,8 @@
import { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import React from 'react';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
import { buildConversation } from '../../utils/builders';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
import { ConversationList as Component } from './conversation_list';
type ConversationListProps = React.ComponentProps<typeof Component>;
@ -28,25 +29,64 @@ const Wrapper = (props: ConversationListProps) => {
);
};
export const ChatHeaderLoading: ComponentStoryObj<typeof Component> = {
args: {},
render: Wrapper,
};
export const ChatHeaderError: ComponentStoryObj<typeof Component> = {
args: {},
render: Wrapper,
};
export const ChatHeaderLoaded: ComponentStoryObj<typeof Component> = {
export const ConversationListLoading: ComponentStoryObj<typeof Component> = {
args: {
conversations: {
loading: true,
error: undefined,
value: { conversations: [] },
refresh: () => {},
},
isLoading: true,
},
render: Wrapper,
};
export const ConversationListError: ComponentStoryObj<typeof Component> = {
args: {
conversations: {
loading: false,
error: new Error('Failed to load conversations'),
value: { conversations: [] },
refresh: () => {},
},
isLoading: false,
},
render: Wrapper,
};
export const ConversationListLoaded: ComponentStoryObj<typeof Component> = {
args: {
conversations: {
loading: false,
error: undefined,
value: {
conversations: [
buildConversation({
conversation: {
id: 'foo',
title: 'Why is database service responding with errors after I did rm -rf /postgres',
last_updated: '',
},
}),
],
},
refresh: () => {},
},
selectedConversationId: '',
},
render: Wrapper,
};
export const ChatHeaderEmpty: ComponentStoryObj<typeof Component> = {
export const ConversationListEmpty: ComponentStoryObj<typeof Component> = {
args: {
conversations: {
loading: false,
error: undefined,
value: { conversations: [] },
refresh: () => {},
},
isLoading: false,
selectedConversationId: '',
},
render: Wrapper,

View file

@ -46,17 +46,17 @@ const newChatButtonWrapperClassName = css`
`;
export function ConversationList({
conversations,
isLoading,
selectedConversationId,
onConversationSelect,
onConversationDeleteClick,
conversations,
isLoading,
}: {
conversations: UseConversationListResult['conversations'];
isLoading: boolean;
selectedConversationId?: string;
onConversationSelect?: (conversationId?: string) => void;
onConversationDeleteClick: (conversationId: string) => void;
conversations: UseConversationListResult['conversations'];
isLoading: boolean;
}) {
const router = useObservabilityAIAssistantRouter();

View file

@ -7,7 +7,7 @@
import { ComponentStory } from '@storybook/react';
import React from 'react';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
import { FunctionListPopover as Component } from './function_list_popover';
export default {
@ -25,9 +25,9 @@ const Template: ComponentStory<typeof Component> = (props: FunctionListPopover)
const defaultProps: FunctionListPopover = {
onSelectFunction: () => {},
disabled: false,
mode: 'prompt',
selectedFunctionName: 'foo',
mode: 'function',
selectedFunctionName: '',
};
export const ConversationList = Template.bind({});
ConversationList.args = defaultProps;
export const FunctionListPopover = Template.bind({});
FunctionListPopover.args = defaultProps;

View file

@ -7,7 +7,7 @@
import { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import { merge } from 'lodash';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
import { KnowledgeBaseCallout as Component } from './knowledge_base_callout';
const meta: ComponentMeta<typeof Component> = {

View file

@ -6,9 +6,10 @@
*/
import React from 'react';
import { ComponentStory } from '@storybook/react';
import { ComponentStory, ComponentStoryObj } from '@storybook/react';
import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public';
import { PromptEditor as Component, PromptEditorProps } from './prompt_editor';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
/*
JSON Schema validation in the PromptEditor compponent does not work
@ -32,7 +33,65 @@ const Template: ComponentStory<typeof Component> = (props: PromptEditorProps) =>
return <Component {...props} />;
};
const defaultProps = {};
export const PromptEditorDisabled: ComponentStoryObj<typeof Component> = {
args: {
disabled: true,
hidden: false,
loading: false,
initialRole: MessageRole.User,
initialFunctionCall: undefined,
initialContent: '',
onChangeHeight: () => {},
onSendTelemetry: () => {},
onSubmit: () => {},
},
render: Template,
};
export const PromptEditor = Template.bind({});
PromptEditor.args = defaultProps;
export const PromptEditorLoading: ComponentStoryObj<typeof Component> = {
args: {
disabled: false,
hidden: false,
loading: true,
initialRole: MessageRole.User,
initialFunctionCall: undefined,
initialContent: '',
onChangeHeight: () => {},
onSendTelemetry: () => {},
onSubmit: () => {},
},
render: Template,
};
export const PromptEditorWithInitialContent: ComponentStoryObj<typeof Component> = {
args: {
disabled: false,
hidden: false,
loading: true,
initialRole: MessageRole.User,
initialFunctionCall: undefined,
initialContent: 'Can you help me with this?',
onChangeHeight: () => {},
onSendTelemetry: () => {},
onSubmit: () => {},
},
render: Template,
};
export const PromptEditorWithInitialFunction: ComponentStoryObj<typeof Component> = {
args: {
disabled: false,
hidden: false,
loading: false,
initialRole: MessageRole.User,
initialFunctionCall: {
name: 'get stuff',
trigger: MessageRole.User,
},
initialContent: '',
onChangeHeight: () => {},
onSendTelemetry: () => {},
onSubmit: () => {},
},
render: Template,
};

View file

@ -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 useChat() {
return { next: () => {}, messages: [], setMessages: () => {}, state: undefined, stop: () => {} };
}

View file

@ -0,0 +1,19 @@
/*
* 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 { Subject } from 'rxjs';
export function useConversation() {
return {
conversation: {},
state: 'idle',
next: new Subject(),
stop: () => {},
messages: [],
saveTitle: () => {},
};
}

View file

@ -0,0 +1,31 @@
/*
* 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 { buildConversation } from '../../utils/builders';
export function useConversationList() {
return {
deleteConversation: () => {},
conversations: {
loading: false,
error: undefined,
value: {
conversations: [
buildConversation({
conversation: {
id: 'foo',
title: 'Why is database service responding with errors after I did rm -rf /postgres',
last_updated: '',
},
}),
],
},
refresh: () => {},
},
isLoading: false,
};
}

View file

@ -5,16 +5,16 @@
* 2.0.
*/
import React from 'react';
import { Subject } from 'rxjs';
import { useChat } from './use_chat';
const ObservabilityAIAssistantMultipaneFlyoutContext = React.createContext(undefined);
export function useKibana() {
return {
services: {
uiSettings: {
get: (setting: string) => {
if (setting === 'dateFormat') {
return 'MMM D, YYYY HH:mm';
}
},
},
application: { navigateToApp: () => {} },
http: {
basePath: {
prepend: () => '',
@ -26,6 +26,32 @@ export function useKibana() {
addError: () => {},
},
},
plugins: {
start: {
licensing: {
license$: new Subject(),
},
observabilityAIAssistant: {
useChat,
ObservabilityAIAssistantMultipaneFlyoutContext,
},
share: {
url: {
locators: {
get: () => {},
},
},
},
triggersActionsUi: { getAddRuleFlyout: {}, getAddConnectorFlyout: {} },
},
},
uiSettings: {
get: (setting: string) => {
if (setting === 'dateFormat') {
return 'MMM D, YYYY HH:mm';
}
},
},
},
};
}

View file

@ -0,0 +1,18 @@
/*
* 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 useObservable from 'react-use/lib/useObservable';
import { Subject } from 'rxjs';
export function useLicense() {
const license = useObservable(new Subject());
return {
getLicense: () => license ?? null,
hasAtLeast: () => true,
};
}

View file

@ -5,10 +5,18 @@
* 2.0.
*/
import { Subject } from 'rxjs';
const service = {
start: async () => {
return {
chat: () => new Subject(),
complete: () => new Subject(),
getFunctions: [],
getSystemMessage: () => {},
hasFunction: () => true,
hasRenderFunction: () => true,
sendAnalyticsEvent: () => {},
};
},
};

View file

@ -0,0 +1,17 @@
/*
* 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 useObservabilityAIAssistantAppService() {
return {
conversations: {
predefinedConversation$: {
subscribe: () => {},
unsubscribe: () => {},
},
},
};
}

View file

@ -5,10 +5,27 @@
* 2.0.
*/
import { FunctionVisibility } from '@kbn/observability-ai-assistant-plugin/public';
import { Subject } from 'rxjs';
export function useObservabilityAIAssistantChatService() {
return {
chat: () => new Subject(),
complete: () => new Subject(),
getFunctions: () => {
return [];
return [
{
id: 'foo',
name: 'foo',
description: 'use this function to foo',
descriptionForUser: 'a function that functions',
visibility: FunctionVisibility.All,
},
];
},
getSystemMessage: () => {},
hasFunction: () => true,
hasRenderFunction: () => true,
sendAnalyticsEvent: () => {},
};
}

View file

@ -0,0 +1,14 @@
/*
* 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 useObservabilityAIAssistantRouter() {
return {
push: () => {},
replace: () => {},
link: () => {},
};
}

View file

@ -11,6 +11,8 @@ import {
createStorybookService,
type ObservabilityAIAssistantChatService,
} from '@kbn/observability-ai-assistant-plugin/public';
import { Subject } from 'rxjs';
import { coreMock } from '@kbn/core/public/mocks';
import { ObservabilityAIAssistantAppService } from '../service/create_app_service';
import { ObservabilityAIAssistantAppServiceProvider } from '../context/observability_ai_assistant_app_service_provider';
@ -20,21 +22,33 @@ const mockService: ObservabilityAIAssistantAppService = {
const mockChatService: ObservabilityAIAssistantChatService = createStorybookChatService();
const coreStart = coreMock.createStart();
export function KibanaReactStorybookDecorator(Story: ComponentType) {
const ObservabilityAIAssistantChatServiceContext = React.createContext(mockChatService);
const ObservabilityAIAssistantMultipaneFlyoutContext = React.createContext({
container: <div />,
setVisibility: () => false,
});
return (
<KibanaContextProvider
services={{
triggersActionsUi: { getAddRuleFlyout: {} },
uiSettings: {
get: (setting: string) => {
if (setting === 'dateFormat') {
return 'MMM D, YYYY HH:mm';
}
},
...coreStart,
licensing: {
license$: new Subject(),
},
observabilityAIAssistant: {
ObservabilityAIAssistantChatServiceContext,
// observabilityAIAssistant: {
// ObservabilityAIAssistantChatServiceContext,
// ObservabilityAIAssistantMultipaneFlyoutContext,
// },
plugins: {
start: {
observabilityAIAssistant: {
ObservabilityAIAssistantMultipaneFlyoutContext,
},
triggersActionsUi: { getAddRuleFlyout: {}, getAddConnectorFlyout: {} },
},
},
}}
>