Skip to content

Commit d657c50

Browse files
committed
feat(Message): Add inline error message
1 parent 51b8b73 commit d657c50

File tree

5 files changed

+139
-38
lines changed

5 files changed

+139
-38
lines changed

packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/BotMessage.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import Message from '@patternfly/chatbot/dist/dynamic/Message';
33
import patternflyAvatar from './patternfly_avatar.jpg';
44
import squareImg from './PF-social-color-square.svg';
5-
import { Form, FormGroup, Radio } from '@patternfly/react-core';
5+
import { AlertActionLink, Form, FormGroup, Radio } from '@patternfly/react-core';
66

77
export const BotMessageExample: React.FunctionComponent = () => {
88
const [variant, setVariant] = React.useState('code');
@@ -140,6 +140,21 @@ _Italic text, formatted with single underscores_
140140

141141
const image = `![Multi-colored wavy lines on a black background](https://cdn.dribbble.com/userupload/10651749/file/original-8a07b8e39d9e8bf002358c66fce1223e.gif)`;
142142

143+
const error = {
144+
title: 'Could not load chat',
145+
children: 'Wait a few minutes and check your network settings. If the issue persists: ',
146+
actionLinks: (
147+
<React.Fragment>
148+
<AlertActionLink component="a" href="#">
149+
Start a new chat
150+
</AlertActionLink>
151+
<AlertActionLink component="a" href="#">
152+
Contact support
153+
</AlertActionLink>
154+
</React.Fragment>
155+
)
156+
};
157+
143158
return (
144159
<>
145160
<Message
@@ -258,6 +273,13 @@ _Italic text, formatted with single underscores_
258273
label="Image"
259274
id="image"
260275
/>
276+
<Radio
277+
isChecked={variant === 'error'}
278+
onChange={() => setVariant('error')}
279+
name="bot-message-error"
280+
label="Error"
281+
id="error"
282+
/>
261283
</FormGroup>
262284
</Form>
263285
<Message
@@ -268,6 +290,7 @@ _Italic text, formatted with single underscores_
268290
tableProps={
269291
variant === 'table' ? { 'aria-label': 'App information and user roles for bot messages' } : undefined
270292
}
293+
error={variant === 'error' ? error : undefined}
271294
/>
272295
</>
273296
);

packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22

33
import Message from '@patternfly/chatbot/dist/dynamic/Message';
44
import userAvatar from './user_avatar.svg';
5-
import { Form, FormGroup, Radio } from '@patternfly/react-core';
5+
import { AlertActionLink, Form, FormGroup, Radio } from '@patternfly/react-core';
66

77
export const UserMessageExample: React.FunctionComponent = () => {
88
const [variant, setVariant] = React.useState('code');
@@ -140,6 +140,21 @@ _Italic text, formatted with single underscores_
140140

141141
const image = `![Multi-colored wavy lines on a black background](https://cdn.dribbble.com/userupload/10651749/file/original-8a07b8e39d9e8bf002358c66fce1223e.gif)`;
142142

143+
const error = {
144+
title: 'Could not load chat',
145+
children: 'Wait a few minutes and check your network settings. If the issue persists: ',
146+
actionLinks: (
147+
<React.Fragment>
148+
<AlertActionLink component="a" href="#">
149+
Start a new chat
150+
</AlertActionLink>
151+
<AlertActionLink component="a" href="#">
152+
Contact support
153+
</AlertActionLink>
154+
</React.Fragment>
155+
)
156+
};
157+
143158
return (
144159
<>
145160
<Message
@@ -235,6 +250,13 @@ _Italic text, formatted with single underscores_
235250
label="Image"
236251
id="user-image"
237252
/>
253+
<Radio
254+
isChecked={variant === 'error'}
255+
onChange={() => setVariant('error')}
256+
name="user-message-error"
257+
label="Error"
258+
id="error"
259+
/>
238260
</FormGroup>
239261
</Form>
240262
<Message
@@ -245,6 +267,7 @@ _Italic text, formatted with single underscores_
245267
tableProps={
246268
variant === 'table' ? { 'aria-label': 'App information and user roles for user messages' } : undefined
247269
}
270+
error={variant === 'error' ? error : undefined}
248271
/>
249272
</>
250273
);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// ============================================================================
2+
// Chatbot Main - Message - Content - Error
3+
// ============================================================================
4+
5+
import React from 'react';
6+
import { Alert, AlertProps } from '@patternfly/react-core';
7+
8+
const ErrorMessage = ({ title, actionLinks, children, ...props }: AlertProps) => (
9+
<Alert isInline variant="danger" title={title} actionLinks={actionLinks} {...props}>
10+
{children}
11+
</Alert>
12+
);
13+
14+
export default ErrorMessage;

packages/module/src/Message/Message.test.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Message from './Message';
55
import userEvent from '@testing-library/user-event';
66
import { monitorSampleAppQuickStart } from './QuickStarts/monitor-sampleapp-quickstart';
77
import { monitorSampleAppQuickStartWithImage } from './QuickStarts/monitor-sampleapp-quickstart-with-image';
8+
import { AlertActionLink } from '@patternfly/react-core';
89

910
const ALL_ACTIONS = [
1011
{ label: /Good response/i },
@@ -140,6 +141,20 @@ const EMPTY_TABLE = `
140141

141142
const IMAGE = `![Multi-colored wavy lines on a black background](https://cdn.dribbble.com/userupload/10651749/file/original-8a07b8e39d9e8bf002358c66fce1223e.gif)`;
142143

144+
const ERROR = {
145+
title: 'Could not load chat',
146+
children: 'Wait a few minutes and check your network settings. If the issue persists: ',
147+
actionLinks: (
148+
<React.Fragment>
149+
<AlertActionLink component="a" href="#">
150+
Start a new chat
151+
</AlertActionLink>
152+
<AlertActionLink component="a" href="#">
153+
Contact support
154+
</AlertActionLink>
155+
</React.Fragment>
156+
)
157+
};
143158
const checkListItemsRendered = () => {
144159
const items = ['Item 1', 'Item 2', 'Item 3'];
145160
expect(screen.getAllByRole('listitem')).toHaveLength(3);
@@ -747,4 +762,21 @@ describe('Message', () => {
747762
render(<Message avatar="./img" role="user" name="User" content={IMAGE} />);
748763
expect(screen.getByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeTruthy();
749764
});
765+
it('should handle error correctly', () => {
766+
render(<Message avatar="./img" role="user" name="User" error={ERROR} />);
767+
expect(screen.getByRole('heading', { name: /Could not load chat/i })).toBeTruthy();
768+
expect(screen.getByRole('link', { name: /Start a new chat/i })).toBeTruthy();
769+
expect(screen.getByRole('link', { name: /Contact support/i })).toBeTruthy();
770+
expect(screen.getByText('Wait a few minutes and check your network settings. If the issue persists:')).toBeTruthy();
771+
});
772+
it('should handle error correctly when loading', () => {
773+
render(<Message avatar="./img" role="user" name="User" error={ERROR} isLoading />);
774+
expect(screen.queryByRole('heading', { name: /Could not load chat/i })).toBeFalsy();
775+
expect(screen.getByText('Loading message')).toBeTruthy();
776+
});
777+
it('should handle error correctly when these is content', () => {
778+
render(<Message avatar="./img" role="user" name="User" error={ERROR} content="Test" />);
779+
expect(screen.getByRole('heading', { name: /Could not load chat/i })).toBeTruthy();
780+
expect(screen.queryByText('Test')).toBeFalsy();
781+
});
750782
});

packages/module/src/Message/Message.tsx

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import React, { ReactNode } from 'react';
77
import Markdown from 'react-markdown';
88
import remarkGfm from 'remark-gfm';
99
import {
10+
AlertProps,
1011
Avatar,
1112
AvatarProps,
1213
ContentVariants,
@@ -38,6 +39,7 @@ import ThMessage from './TableMessage/ThMessage';
3839
import { TableProps } from '@patternfly/react-table';
3940
import ImageMessage from './ImageMessage/ImageMessage';
4041
import rehypeUnwrapImages from 'rehype-unwrap-images';
42+
import ErrorMessage from './ErrorMessage/ErrorMessage';
4143

4244
export interface MessageAttachment {
4345
/** Name of file attached to the message */
@@ -133,6 +135,8 @@ export interface MessageProps extends Omit<React.HTMLProps<HTMLDivElement>, 'rol
133135
innerRef?: React.Ref<HTMLDivElement>;
134136
/** Props for table message. It is important to include a detailed aria-label that describes the purpose of the table. */
135137
tableProps?: Required<Pick<TableProps, 'aria-label'>> & TableProps;
138+
/** Optional inline error message that can be displayed in the message */
139+
error?: AlertProps;
136140
}
137141

138142
export const MessageBase: React.FunctionComponent<MessageProps> = ({
@@ -159,6 +163,7 @@ export const MessageBase: React.FunctionComponent<MessageProps> = ({
159163
isLiveRegion = true,
160164
innerRef,
161165
tableProps,
166+
error,
162167
...props
163168
}: MessageProps) => {
164169
const { beforeMainContent, afterMainContent, endContent } = extraContent || {};
@@ -208,42 +213,46 @@ export const MessageBase: React.FunctionComponent<MessageProps> = ({
208213
) : (
209214
<>
210215
{beforeMainContent && <>{beforeMainContent}</>}
211-
<Markdown
212-
components={{
213-
p: (props) => <TextMessage component={ContentVariants.p} {...props} />,
214-
code: ({ children, ...props }) => (
215-
<CodeBlockMessage {...props} {...codeBlockProps}>
216-
{children}
217-
</CodeBlockMessage>
218-
),
219-
h1: (props) => <TextMessage component={ContentVariants.h1} {...props} />,
220-
h2: (props) => <TextMessage component={ContentVariants.h2} {...props} />,
221-
h3: (props) => <TextMessage component={ContentVariants.h3} {...props} />,
222-
h4: (props) => <TextMessage component={ContentVariants.h4} {...props} />,
223-
h5: (props) => <TextMessage component={ContentVariants.h5} {...props} />,
224-
h6: (props) => <TextMessage component={ContentVariants.h6} {...props} />,
225-
blockquote: (props) => <TextMessage component={ContentVariants.blockquote} {...props} />,
226-
ul: (props) => <UnorderedListMessage {...props} />,
227-
ol: (props) => <OrderedListMessage {...props} />,
228-
li: (props) => <ListItemMessage {...props} />,
229-
table: (props) => <TableMessage {...props} {...tableProps} />,
230-
tbody: (props) => <TbodyMessage {...props} />,
231-
thead: (props) => <TheadMessage {...props} />,
232-
tr: (props) => <TrMessage {...props} />,
233-
td: (props) => {
234-
// Conflicts with Td type
235-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
236-
const { width, ...rest } = props;
237-
return <TdMessage {...rest} />;
238-
},
239-
th: (props) => <ThMessage {...props} />,
240-
img: (props) => <ImageMessage {...props} />
241-
}}
242-
remarkPlugins={[remarkGfm]}
243-
rehypePlugins={[rehypeUnwrapImages]}
244-
>
245-
{content}
246-
</Markdown>
216+
{error ? (
217+
<ErrorMessage {...error} />
218+
) : (
219+
<Markdown
220+
components={{
221+
p: (props) => <TextMessage component={ContentVariants.p} {...props} />,
222+
code: ({ children, ...props }) => (
223+
<CodeBlockMessage {...props} {...codeBlockProps}>
224+
{children}
225+
</CodeBlockMessage>
226+
),
227+
h1: (props) => <TextMessage component={ContentVariants.h1} {...props} />,
228+
h2: (props) => <TextMessage component={ContentVariants.h2} {...props} />,
229+
h3: (props) => <TextMessage component={ContentVariants.h3} {...props} />,
230+
h4: (props) => <TextMessage component={ContentVariants.h4} {...props} />,
231+
h5: (props) => <TextMessage component={ContentVariants.h5} {...props} />,
232+
h6: (props) => <TextMessage component={ContentVariants.h6} {...props} />,
233+
blockquote: (props) => <TextMessage component={ContentVariants.blockquote} {...props} />,
234+
ul: (props) => <UnorderedListMessage {...props} />,
235+
ol: (props) => <OrderedListMessage {...props} />,
236+
li: (props) => <ListItemMessage {...props} />,
237+
table: (props) => <TableMessage {...props} {...tableProps} />,
238+
tbody: (props) => <TbodyMessage {...props} />,
239+
thead: (props) => <TheadMessage {...props} />,
240+
tr: (props) => <TrMessage {...props} />,
241+
td: (props) => {
242+
// Conflicts with Td type
243+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
244+
const { width, ...rest } = props;
245+
return <TdMessage {...rest} />;
246+
},
247+
th: (props) => <ThMessage {...props} />,
248+
img: (props) => <ImageMessage {...props} />
249+
}}
250+
remarkPlugins={[remarkGfm]}
251+
rehypePlugins={[rehypeUnwrapImages]}
252+
>
253+
{content}
254+
</Markdown>
255+
)}
247256
{afterMainContent && <>{afterMainContent}</>}
248257
</>
249258
)}

0 commit comments

Comments
 (0)