Skip to content

Commit 4939fb9

Browse files
authored
Merge pull request #465 from rebeccaalpert/inline-error
feat(Message): Add inline error message
2 parents c3178b1 + 4cf05a9 commit 4939fb9

File tree

5 files changed

+144
-43
lines changed

5 files changed

+144
-43
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="user-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
@@ -6,6 +6,7 @@ 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';
88
import rehypeExternalLinks from '../__mocks__/rehype-external-links';
9+
import { AlertActionLink } from '@patternfly/react-core';
910

1011
const ALL_ACTIONS = [
1112
{ label: /Good response/i },
@@ -141,6 +142,20 @@ const EMPTY_TABLE = `
141142

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

145+
const ERROR = {
146+
title: 'Could not load chat',
147+
children: 'Wait a few minutes and check your network settings. If the issue persists: ',
148+
actionLinks: (
149+
<React.Fragment>
150+
<AlertActionLink component="a" href="#">
151+
Start a new chat
152+
</AlertActionLink>
153+
<AlertActionLink component="a" href="#">
154+
Contact support
155+
</AlertActionLink>
156+
</React.Fragment>
157+
)
158+
};
144159
const checkListItemsRendered = () => {
145160
const items = ['Item 1', 'Item 2', 'Item 3'];
146161
expect(screen.getAllByRole('listitem')).toHaveLength(3);
@@ -769,4 +784,21 @@ describe('Message', () => {
769784
// we are mocking rehype libraries, so we can't test target _blank addition on links directly with RTL
770785
expect(rehypeExternalLinks).not.toHaveBeenCalled();
771786
});
787+
it('should handle error correctly', () => {
788+
render(<Message avatar="./img" role="user" name="User" error={ERROR} />);
789+
expect(screen.getByRole('heading', { name: /Could not load chat/i })).toBeTruthy();
790+
expect(screen.getByRole('link', { name: /Start a new chat/i })).toBeTruthy();
791+
expect(screen.getByRole('link', { name: /Contact support/i })).toBeTruthy();
792+
expect(screen.getByText('Wait a few minutes and check your network settings. If the issue persists:')).toBeTruthy();
793+
});
794+
it('should handle error correctly when loading', () => {
795+
render(<Message avatar="./img" role="user" name="User" error={ERROR} isLoading />);
796+
expect(screen.queryByRole('heading', { name: /Could not load chat/i })).toBeFalsy();
797+
expect(screen.getByText('Loading message')).toBeTruthy();
798+
});
799+
it('should handle error correctly when these is content', () => {
800+
render(<Message avatar="./img" role="user" name="User" error={ERROR} content="Test" />);
801+
expect(screen.getByRole('heading', { name: /Could not load chat/i })).toBeTruthy();
802+
expect(screen.queryByText('Test')).toBeFalsy();
803+
});
772804
});

packages/module/src/Message/Message.tsx

Lines changed: 50 additions & 41 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,
@@ -42,6 +43,7 @@ import rehypeExternalLinks from 'rehype-external-links';
4243
import rehypeSanitize from 'rehype-sanitize';
4344
import { PluggableList } from 'react-markdown/lib';
4445
import LinkMessage from './LinkMessage/LinkMessage';
46+
import ErrorMessage from './ErrorMessage/ErrorMessage';
4547

4648
export interface MessageAttachment {
4749
/** Name of file attached to the message */
@@ -141,6 +143,8 @@ export interface MessageProps extends Omit<React.HTMLProps<HTMLDivElement>, 'rol
141143
additionalRehypePlugins?: PluggableList;
142144
/** Whether to open links in message in new tab. */
143145
openLinkInNewTab?: boolean;
146+
/** Optional inline error message that can be displayed in the message */
147+
error?: AlertProps;
144148
}
145149

146150
export const MessageBase: React.FunctionComponent<MessageProps> = ({
@@ -169,6 +173,7 @@ export const MessageBase: React.FunctionComponent<MessageProps> = ({
169173
tableProps,
170174
openLinkInNewTab = true,
171175
additionalRehypePlugins = [],
176+
error,
172177
...props
173178
}: MessageProps) => {
174179
const { beforeMainContent, afterMainContent, endContent } = extraContent || {};
@@ -225,47 +230,51 @@ export const MessageBase: React.FunctionComponent<MessageProps> = ({
225230
) : (
226231
<>
227232
{beforeMainContent && <>{beforeMainContent}</>}
228-
<Markdown
229-
components={{
230-
p: (props) => <TextMessage component={ContentVariants.p} {...props} />,
231-
code: ({ children, ...props }) => (
232-
<CodeBlockMessage {...props} {...codeBlockProps}>
233-
{children}
234-
</CodeBlockMessage>
235-
),
236-
h1: (props) => <TextMessage component={ContentVariants.h1} {...props} />,
237-
h2: (props) => <TextMessage component={ContentVariants.h2} {...props} />,
238-
h3: (props) => <TextMessage component={ContentVariants.h3} {...props} />,
239-
h4: (props) => <TextMessage component={ContentVariants.h4} {...props} />,
240-
h5: (props) => <TextMessage component={ContentVariants.h5} {...props} />,
241-
h6: (props) => <TextMessage component={ContentVariants.h6} {...props} />,
242-
blockquote: (props) => <TextMessage component={ContentVariants.blockquote} {...props} />,
243-
ul: (props) => <UnorderedListMessage {...props} />,
244-
ol: (props) => <OrderedListMessage {...props} />,
245-
li: (props) => <ListItemMessage {...props} />,
246-
table: (props) => <TableMessage {...props} {...tableProps} />,
247-
tbody: (props) => <TbodyMessage {...props} />,
248-
thead: (props) => <TheadMessage {...props} />,
249-
tr: (props) => <TrMessage {...props} />,
250-
td: (props) => {
251-
// Conflicts with Td type
252-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
253-
const { width, ...rest } = props;
254-
return <TdMessage {...rest} />;
255-
},
256-
th: (props) => <ThMessage {...props} />,
257-
img: (props) => <ImageMessage {...props} />,
258-
a: (props) => (
259-
<LinkMessage href={props.href} rel={props.rel} target={props.target}>
260-
{props.children}
261-
</LinkMessage>
262-
)
263-
}}
264-
remarkPlugins={[remarkGfm]}
265-
rehypePlugins={rehypePlugins}
266-
>
267-
{content}
268-
</Markdown>
233+
{error ? (
234+
<ErrorMessage {...error} />
235+
) : (
236+
<Markdown
237+
components={{
238+
p: (props) => <TextMessage component={ContentVariants.p} {...props} />,
239+
code: ({ children, ...props }) => (
240+
<CodeBlockMessage {...props} {...codeBlockProps}>
241+
{children}
242+
</CodeBlockMessage>
243+
),
244+
h1: (props) => <TextMessage component={ContentVariants.h1} {...props} />,
245+
h2: (props) => <TextMessage component={ContentVariants.h2} {...props} />,
246+
h3: (props) => <TextMessage component={ContentVariants.h3} {...props} />,
247+
h4: (props) => <TextMessage component={ContentVariants.h4} {...props} />,
248+
h5: (props) => <TextMessage component={ContentVariants.h5} {...props} />,
249+
h6: (props) => <TextMessage component={ContentVariants.h6} {...props} />,
250+
blockquote: (props) => <TextMessage component={ContentVariants.blockquote} {...props} />,
251+
ul: (props) => <UnorderedListMessage {...props} />,
252+
ol: (props) => <OrderedListMessage {...props} />,
253+
li: (props) => <ListItemMessage {...props} />,
254+
table: (props) => <TableMessage {...props} {...tableProps} />,
255+
tbody: (props) => <TbodyMessage {...props} />,
256+
thead: (props) => <TheadMessage {...props} />,
257+
tr: (props) => <TrMessage {...props} />,
258+
td: (props) => {
259+
// Conflicts with Td type
260+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
261+
const { width, ...rest } = props;
262+
return <TdMessage {...rest} />;
263+
},
264+
th: (props) => <ThMessage {...props} />,
265+
img: (props) => <ImageMessage {...props} />,
266+
a: (props) => (
267+
<LinkMessage href={props.href} rel={props.rel} target={props.target}>
268+
{props.children}
269+
</LinkMessage>
270+
)
271+
}}
272+
remarkPlugins={[remarkGfm]}
273+
rehypePlugins={rehypePlugins}
274+
>
275+
{content}
276+
</Markdown>
277+
)}
269278
{afterMainContent && <>{afterMainContent}</>}
270279
</>
271280
)}

0 commit comments

Comments
 (0)