@patternfly/chatbot 2.2.0-prerelease.30 → 2.2.0-prerelease.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/cjs/Message/ErrorMessage/ErrorMessage.d.ts +4 -0
  2. package/dist/cjs/Message/ErrorMessage/ErrorMessage.js +26 -0
  3. package/dist/cjs/Message/Message.d.ts +3 -1
  4. package/dist/cjs/Message/Message.js +4 -3
  5. package/dist/cjs/Message/Message.test.js +25 -0
  6. package/dist/cjs/index.d.ts +2 -0
  7. package/dist/cjs/index.js +4 -1
  8. package/dist/cjs/tracking/console_tracking_provider.d.ts +10 -0
  9. package/dist/cjs/tracking/console_tracking_provider.js +27 -0
  10. package/dist/cjs/tracking/index.d.ts +2 -0
  11. package/dist/cjs/tracking/index.js +23 -0
  12. package/dist/cjs/tracking/posthog_tracking_provider.d.ts +9 -0
  13. package/dist/cjs/tracking/posthog_tracking_provider.js +37 -0
  14. package/dist/cjs/tracking/segment_tracking_provider.d.ts +10 -0
  15. package/dist/cjs/tracking/segment_tracking_provider.js +50 -0
  16. package/dist/cjs/tracking/trackingProviderProxy.d.ts +9 -0
  17. package/dist/cjs/tracking/trackingProviderProxy.js +24 -0
  18. package/dist/cjs/tracking/tracking_api.d.ts +8 -0
  19. package/dist/cjs/tracking/tracking_api.js +2 -0
  20. package/dist/cjs/tracking/tracking_registry.d.ts +4 -0
  21. package/dist/cjs/tracking/tracking_registry.js +33 -0
  22. package/dist/cjs/tracking/tracking_spi.d.ts +9 -0
  23. package/dist/cjs/tracking/tracking_spi.js +2 -0
  24. package/dist/cjs/tracking/umami_tracking_provider.d.ts +14 -0
  25. package/dist/cjs/tracking/umami_tracking_provider.js +44 -0
  26. package/dist/dynamic/tracking/package.json +1 -0
  27. package/dist/esm/Message/ErrorMessage/ErrorMessage.d.ts +4 -0
  28. package/dist/esm/Message/ErrorMessage/ErrorMessage.js +21 -0
  29. package/dist/esm/Message/Message.d.ts +3 -1
  30. package/dist/esm/Message/Message.js +4 -3
  31. package/dist/esm/Message/Message.test.js +25 -0
  32. package/dist/esm/index.d.ts +2 -0
  33. package/dist/esm/index.js +2 -0
  34. package/dist/esm/tracking/console_tracking_provider.d.ts +10 -0
  35. package/dist/esm/tracking/console_tracking_provider.js +23 -0
  36. package/dist/esm/tracking/index.d.ts +2 -0
  37. package/dist/esm/tracking/index.js +2 -0
  38. package/dist/esm/tracking/posthog_tracking_provider.d.ts +9 -0
  39. package/dist/esm/tracking/posthog_tracking_provider.js +33 -0
  40. package/dist/esm/tracking/segment_tracking_provider.d.ts +10 -0
  41. package/dist/esm/tracking/segment_tracking_provider.js +46 -0
  42. package/dist/esm/tracking/trackingProviderProxy.d.ts +9 -0
  43. package/dist/esm/tracking/trackingProviderProxy.js +22 -0
  44. package/dist/esm/tracking/tracking_api.d.ts +8 -0
  45. package/dist/esm/tracking/tracking_api.js +1 -0
  46. package/dist/esm/tracking/tracking_registry.d.ts +4 -0
  47. package/dist/esm/tracking/tracking_registry.js +26 -0
  48. package/dist/esm/tracking/tracking_spi.d.ts +9 -0
  49. package/dist/esm/tracking/tracking_spi.js +1 -0
  50. package/dist/esm/tracking/umami_tracking_provider.d.ts +14 -0
  51. package/dist/esm/tracking/umami_tracking_provider.js +40 -0
  52. package/dist/tsconfig.tsbuildinfo +1 -1
  53. package/package.json +2 -2
  54. package/patternfly-docs/content/extensions/chatbot/about-chatbot.md +3 -0
  55. package/patternfly-docs/content/extensions/chatbot/examples/Analytics/Analytics.md +214 -0
  56. package/patternfly-docs/content/extensions/chatbot/examples/Messages/BotMessage.tsx +24 -1
  57. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +24 -1
  58. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +2 -0
  59. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.tsx +37 -24
  60. package/patternfly-docs/content/extensions/chatbot/img/analytics-example.svg +118 -0
  61. package/patternfly-docs/content/extensions/chatbot/img/chatbot-analytics.svg +51 -0
  62. package/patternfly-docs/content/extensions/chatbot/img/posthog.svg +30 -0
  63. package/patternfly-docs/content/extensions/chatbot/img/segment.svg +36 -0
  64. package/patternfly-docs/content/extensions/chatbot/img/umami.svg +30 -0
  65. package/src/Message/ErrorMessage/ErrorMessage.tsx +14 -0
  66. package/src/Message/Message.test.tsx +32 -0
  67. package/src/Message/Message.tsx +50 -41
  68. package/src/index.ts +3 -0
  69. package/src/tracking/console_tracking_provider.ts +30 -0
  70. package/src/tracking/index.ts +3 -0
  71. package/src/tracking/posthog_tracking_provider.ts +42 -0
  72. package/src/tracking/segment_tracking_provider.ts +62 -0
  73. package/src/tracking/trackingProviderProxy.ts +28 -0
  74. package/src/tracking/tracking_api.ts +11 -0
  75. package/src/tracking/tracking_registry.ts +33 -0
  76. package/src/tracking/tracking_spi.ts +14 -0
  77. package/src/tracking/umami_tracking_provider.ts +54 -0
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import { AlertProps } from '@patternfly/react-core';
3
+ declare const ErrorMessage: ({ title, actionLinks, children, ...props }: AlertProps) => React.JSX.Element;
4
+ export default ErrorMessage;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ // ============================================================================
3
+ // Chatbot Main - Message - Content - Error
4
+ // ============================================================================
5
+ var __rest = (this && this.__rest) || function (s, e) {
6
+ var t = {};
7
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
8
+ t[p] = s[p];
9
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
10
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
11
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
12
+ t[p[i]] = s[p[i]];
13
+ }
14
+ return t;
15
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ const react_1 = __importDefault(require("react"));
21
+ const react_core_1 = require("@patternfly/react-core");
22
+ const ErrorMessage = (_a) => {
23
+ var { title, actionLinks, children } = _a, props = __rest(_a, ["title", "actionLinks", "children"]);
24
+ return (react_1.default.createElement(react_core_1.Alert, Object.assign({ isInline: true, variant: "danger", title: title, actionLinks: actionLinks }, props), children));
25
+ };
26
+ exports.default = ErrorMessage;
@@ -1,5 +1,5 @@
1
1
  import React, { ReactNode } from 'react';
2
- import { AvatarProps, LabelGroupProps } from '@patternfly/react-core';
2
+ import { AlertProps, AvatarProps, LabelGroupProps } from '@patternfly/react-core';
3
3
  import { ActionProps } from '../ResponseActions/ResponseActions';
4
4
  import { SourcesCardProps } from '../SourcesCard';
5
5
  import { QuickStart, QuickstartAction } from './QuickStarts/types';
@@ -102,6 +102,8 @@ export interface MessageProps extends Omit<React.HTMLProps<HTMLDivElement>, 'rol
102
102
  additionalRehypePlugins?: PluggableList;
103
103
  /** Whether to open links in message in new tab. */
104
104
  openLinkInNewTab?: boolean;
105
+ /** Optional inline error message that can be displayed in the message */
106
+ error?: AlertProps;
105
107
  }
106
108
  export declare const MessageBase: React.FunctionComponent<MessageProps>;
107
109
  declare const Message: React.ForwardRefExoticComponent<Omit<MessageProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
@@ -46,8 +46,9 @@ const rehype_unwrap_images_1 = __importDefault(require("rehype-unwrap-images"));
46
46
  const rehype_external_links_1 = __importDefault(require("rehype-external-links"));
47
47
  const rehype_sanitize_1 = __importDefault(require("rehype-sanitize"));
48
48
  const LinkMessage_1 = __importDefault(require("./LinkMessage/LinkMessage"));
49
+ const ErrorMessage_1 = __importDefault(require("./ErrorMessage/ErrorMessage"));
49
50
  const MessageBase = (_a) => {
50
- var { role, content, extraContent, name, avatar, timestamp, isLoading, actions, sources, botWord = 'AI', loadingWord = 'Loading message', codeBlockProps, quickResponses, quickResponseContainerProps = { numLabels: 5 }, attachments, hasRoundAvatar = true, avatarProps, quickStarts, userFeedbackForm, userFeedbackComplete, isLiveRegion = true, innerRef, tableProps, openLinkInNewTab = true, additionalRehypePlugins = [] } = _a, props = __rest(_a, ["role", "content", "extraContent", "name", "avatar", "timestamp", "isLoading", "actions", "sources", "botWord", "loadingWord", "codeBlockProps", "quickResponses", "quickResponseContainerProps", "attachments", "hasRoundAvatar", "avatarProps", "quickStarts", "userFeedbackForm", "userFeedbackComplete", "isLiveRegion", "innerRef", "tableProps", "openLinkInNewTab", "additionalRehypePlugins"]);
51
+ var { role, content, extraContent, name, avatar, timestamp, isLoading, actions, sources, botWord = 'AI', loadingWord = 'Loading message', codeBlockProps, quickResponses, quickResponseContainerProps = { numLabels: 5 }, attachments, hasRoundAvatar = true, avatarProps, quickStarts, userFeedbackForm, userFeedbackComplete, isLiveRegion = true, innerRef, tableProps, openLinkInNewTab = true, additionalRehypePlugins = [], error } = _a, props = __rest(_a, ["role", "content", "extraContent", "name", "avatar", "timestamp", "isLoading", "actions", "sources", "botWord", "loadingWord", "codeBlockProps", "quickResponses", "quickResponseContainerProps", "attachments", "hasRoundAvatar", "avatarProps", "quickStarts", "userFeedbackForm", "userFeedbackComplete", "isLiveRegion", "innerRef", "tableProps", "openLinkInNewTab", "additionalRehypePlugins", "error"]);
51
52
  const { beforeMainContent, afterMainContent, endContent } = extraContent || {};
52
53
  let rehypePlugins = [rehype_unwrap_images_1.default];
53
54
  if (openLinkInNewTab) {
@@ -77,7 +78,7 @@ const MessageBase = (_a) => {
77
78
  react_1.default.createElement("div", { className: "pf-chatbot__message-and-actions" },
78
79
  isLoading ? (react_1.default.createElement(MessageLoading_1.default, { loadingWord: loadingWord })) : (react_1.default.createElement(react_1.default.Fragment, null,
79
80
  beforeMainContent && react_1.default.createElement(react_1.default.Fragment, null, beforeMainContent),
80
- react_1.default.createElement(react_markdown_1.default, { components: {
81
+ error ? (react_1.default.createElement(ErrorMessage_1.default, Object.assign({}, error))) : (react_1.default.createElement(react_markdown_1.default, { components: {
81
82
  p: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.p }, props)),
82
83
  code: (_a) => {
83
84
  var { children } = _a, props = __rest(_a, ["children"]);
@@ -106,7 +107,7 @@ const MessageBase = (_a) => {
106
107
  th: (props) => react_1.default.createElement(ThMessage_1.default, Object.assign({}, props)),
107
108
  img: (props) => react_1.default.createElement(ImageMessage_1.default, Object.assign({}, props)),
108
109
  a: (props) => (react_1.default.createElement(LinkMessage_1.default, { href: props.href, rel: props.rel, target: props.target }, props.children))
109
- }, remarkPlugins: [remark_gfm_1.default], rehypePlugins: rehypePlugins }, content),
110
+ }, remarkPlugins: [remark_gfm_1.default], rehypePlugins: rehypePlugins }, content)),
110
111
  afterMainContent && react_1.default.createElement(react_1.default.Fragment, null, afterMainContent))),
111
112
  !isLoading && sources && react_1.default.createElement(SourcesCard_1.default, Object.assign({}, sources)),
112
113
  quickStarts && quickStarts.quickStart && (react_1.default.createElement(QuickStartTile_1.default, { quickStart: quickStarts.quickStart, onSelectQuickStart: quickStarts.onSelectQuickStart, minuteWord: quickStarts.minuteWord, minuteWordPlural: quickStarts.minuteWordPlural, prerequisiteWord: quickStarts.prerequisiteWord, prerequisiteWordPlural: quickStarts.prerequisiteWordPlural, quickStartButtonAriaLabel: quickStarts.quickStartButtonAriaLabel })),
@@ -20,6 +20,7 @@ const user_event_1 = __importDefault(require("@testing-library/user-event"));
20
20
  const monitor_sampleapp_quickstart_1 = require("./QuickStarts/monitor-sampleapp-quickstart");
21
21
  const monitor_sampleapp_quickstart_with_image_1 = require("./QuickStarts/monitor-sampleapp-quickstart-with-image");
22
22
  const rehype_external_links_1 = __importDefault(require("../__mocks__/rehype-external-links"));
23
+ const react_core_1 = require("@patternfly/react-core");
23
24
  const ALL_ACTIONS = [
24
25
  { label: /Good response/i },
25
26
  { label: /Bad response/i },
@@ -139,6 +140,13 @@ const EMPTY_TABLE = `
139
140
 
140
141
  `;
141
142
  const IMAGE = `![Multi-colored wavy lines on a black background](https://cdn.dribbble.com/userupload/10651749/file/original-8a07b8e39d9e8bf002358c66fce1223e.gif)`;
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: (react_1.default.createElement(react_1.default.Fragment, null,
147
+ react_1.default.createElement(react_core_1.AlertActionLink, { component: "a", href: "#" }, "Start a new chat"),
148
+ react_1.default.createElement(react_core_1.AlertActionLink, { component: "a", href: "#" }, "Contact support")))
149
+ };
142
150
  const checkListItemsRendered = () => {
143
151
  const items = ['Item 1', 'Item 2', 'Item 3'];
144
152
  expect(react_2.screen.getAllByRole('listitem')).toHaveLength(3);
@@ -594,4 +602,21 @@ describe('Message', () => {
594
602
  // we are mocking rehype libraries, so we can't test target _blank addition on links directly with RTL
595
603
  expect(rehype_external_links_1.default).not.toHaveBeenCalled();
596
604
  });
605
+ it('should handle error correctly', () => {
606
+ (0, react_2.render)(react_1.default.createElement(Message_1.default, { avatar: "./img", role: "user", name: "User", error: ERROR }));
607
+ expect(react_2.screen.getByRole('heading', { name: /Could not load chat/i })).toBeTruthy();
608
+ expect(react_2.screen.getByRole('link', { name: /Start a new chat/i })).toBeTruthy();
609
+ expect(react_2.screen.getByRole('link', { name: /Contact support/i })).toBeTruthy();
610
+ expect(react_2.screen.getByText('Wait a few minutes and check your network settings. If the issue persists:')).toBeTruthy();
611
+ });
612
+ it('should handle error correctly when loading', () => {
613
+ (0, react_2.render)(react_1.default.createElement(Message_1.default, { avatar: "./img", role: "user", name: "User", error: ERROR, isLoading: true }));
614
+ expect(react_2.screen.queryByRole('heading', { name: /Could not load chat/i })).toBeFalsy();
615
+ expect(react_2.screen.getByText('Loading message')).toBeTruthy();
616
+ });
617
+ it('should handle error correctly when these is content', () => {
618
+ (0, react_2.render)(react_1.default.createElement(Message_1.default, { avatar: "./img", role: "user", name: "User", error: ERROR, content: "Test" }));
619
+ expect(react_2.screen.getByRole('heading', { name: /Could not load chat/i })).toBeTruthy();
620
+ expect(react_2.screen.queryByText('Test')).toBeFalsy();
621
+ });
597
622
  });
@@ -52,3 +52,5 @@ export { default as SourcesCard } from './SourcesCard';
52
52
  export * from './SourcesCard';
53
53
  export { default as TermsOfUse } from './TermsOfUse';
54
54
  export * from './TermsOfUse';
55
+ export { default as tracking } from './tracking';
56
+ export * from './tracking';
package/dist/cjs/index.js CHANGED
@@ -18,7 +18,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
18
18
  return (mod && mod.__esModule) ? mod : { "default": mod };
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.TermsOfUse = exports.SourcesCard = exports.SourceDetailsMenuItem = exports.Settings = exports.ResponseActions = exports.PreviewAttachment = exports.MessageBox = exports.MessageBar = exports.Message = exports.LoadingMessage = exports.FileDropZone = exports.FileDetailsLabel = exports.FileDetails = exports.Compare = exports.CodeModal = exports.ChatbotWelcomePrompt = exports.ChatbotToggle = exports.ChatbotPopover = exports.ChatbotModal = exports.ChatbotHeader = exports.ChatbotFooter = exports.ChatbotConversationHistoryNav = exports.ChatbotContent = exports.ChatbotAlert = exports.Chatbot = exports.AttachMenu = exports.AttachmentEdit = void 0;
21
+ exports.tracking = exports.TermsOfUse = exports.SourcesCard = exports.SourceDetailsMenuItem = exports.Settings = exports.ResponseActions = exports.PreviewAttachment = exports.MessageBox = exports.MessageBar = exports.Message = exports.LoadingMessage = exports.FileDropZone = exports.FileDetailsLabel = exports.FileDetails = exports.Compare = exports.CodeModal = exports.ChatbotWelcomePrompt = exports.ChatbotToggle = exports.ChatbotPopover = exports.ChatbotModal = exports.ChatbotHeader = exports.ChatbotFooter = exports.ChatbotConversationHistoryNav = exports.ChatbotContent = exports.ChatbotAlert = exports.Chatbot = exports.AttachMenu = exports.AttachmentEdit = void 0;
22
22
  var AttachmentEdit_1 = require("./AttachmentEdit");
23
23
  Object.defineProperty(exports, "AttachmentEdit", { enumerable: true, get: function () { return __importDefault(AttachmentEdit_1).default; } });
24
24
  __exportStar(require("./AttachmentEdit"), exports);
@@ -100,3 +100,6 @@ __exportStar(require("./SourcesCard"), exports);
100
100
  var TermsOfUse_1 = require("./TermsOfUse");
101
101
  Object.defineProperty(exports, "TermsOfUse", { enumerable: true, get: function () { return __importDefault(TermsOfUse_1).default; } });
102
102
  __exportStar(require("./TermsOfUse"), exports);
103
+ var tracking_1 = require("./tracking");
104
+ Object.defineProperty(exports, "tracking", { enumerable: true, get: function () { return __importDefault(tracking_1).default; } });
105
+ __exportStar(require("./tracking"), exports);
@@ -0,0 +1,10 @@
1
+ import { TrackingSpi } from './tracking_spi';
2
+ import { TrackingApi, TrackingEventProperties } from './tracking_api';
3
+ export declare class ConsoleTrackingProvider implements TrackingSpi, TrackingApi {
4
+ trackPageView(url: string | undefined): void;
5
+ registerProvider(): void;
6
+ initialize(): void;
7
+ identify(userID: string): void;
8
+ trackSingleItem(item: string, properties?: TrackingEventProperties): void;
9
+ getKey(): string;
10
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConsoleTrackingProvider = void 0;
4
+ class ConsoleTrackingProvider {
5
+ trackPageView(url) {
6
+ // eslint-disable-next-line no-console
7
+ console.log('ConsoleProvider pageView', url);
8
+ }
9
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
10
+ registerProvider() { }
11
+ initialize() {
12
+ // eslint-disable-next-line no-console
13
+ console.log('ConsoleProvider initialize');
14
+ }
15
+ identify(userID) {
16
+ // eslint-disable-next-line no-console
17
+ console.log('ConsoleProvider identify', userID);
18
+ }
19
+ trackSingleItem(item, properties) {
20
+ // eslint-disable-next-line no-console
21
+ console.log('ConsoleProvider: ' + item, properties);
22
+ }
23
+ getKey() {
24
+ return 'console';
25
+ }
26
+ }
27
+ exports.ConsoleTrackingProvider = ConsoleTrackingProvider;
@@ -0,0 +1,2 @@
1
+ export { default } from './tracking_registry';
2
+ export * from './tracking_registry';
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.default = void 0;
21
+ var tracking_registry_1 = require("./tracking_registry");
22
+ Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(tracking_registry_1).default; } });
23
+ __exportStar(require("./tracking_registry"), exports);
@@ -0,0 +1,9 @@
1
+ import { TrackingApi, TrackingEventProperties } from './tracking_api';
2
+ import { InitProps, TrackingSpi } from './tracking_spi';
3
+ export declare class PosthogTrackingProvider implements TrackingSpi, TrackingApi {
4
+ getKey(): string;
5
+ initialize(props: InitProps): void;
6
+ identify(userID: string): void;
7
+ trackPageView(url: string | undefined): void;
8
+ trackSingleItem(item: string, properties?: TrackingEventProperties): void;
9
+ }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PosthogTrackingProvider = void 0;
4
+ const posthog_js_1 = require("posthog-js");
5
+ class PosthogTrackingProvider {
6
+ getKey() {
7
+ return 'posthogKey';
8
+ }
9
+ initialize(props) {
10
+ // eslint-disable-next-line no-console
11
+ console.log('PosthogProvider initialize');
12
+ const posthogKey = props.posthogKey;
13
+ posthog_js_1.posthog.init(posthogKey, {
14
+ // eslint-disable-next-line camelcase
15
+ api_host: 'https://us.i.posthog.com',
16
+ // eslint-disable-next-line camelcase
17
+ person_profiles: 'identified_only' // or 'always' to create profiles for anonymous users as well
18
+ });
19
+ }
20
+ identify(userID) {
21
+ // eslint-disable-next-line no-console
22
+ console.log('PosthogProvider userID: ' + userID);
23
+ posthog_js_1.posthog.identify(userID);
24
+ }
25
+ trackPageView(url) {
26
+ // eslint-disable-next-line no-console
27
+ console.log('PostHogProvider url', url);
28
+ // TODO posthog seems to record that automatically.
29
+ // How to not clash with this here? Just leave as no-op?
30
+ }
31
+ trackSingleItem(item, properties) {
32
+ // eslint-disable-next-line no-console
33
+ console.log('PosthogProvider: trackSingleItem' + item, properties);
34
+ posthog_js_1.posthog.capture(item, { properties });
35
+ }
36
+ }
37
+ exports.PosthogTrackingProvider = PosthogTrackingProvider;
@@ -0,0 +1,10 @@
1
+ import { TrackingApi, TrackingEventProperties } from './tracking_api';
2
+ import { InitProps, TrackingSpi } from './tracking_spi';
3
+ export declare class SegmentTrackingProvider implements TrackingSpi, TrackingApi {
4
+ private analytics;
5
+ getKey(): string;
6
+ initialize(props: InitProps): void;
7
+ identify(userID: string): void;
8
+ trackPageView(url: string | undefined): void;
9
+ trackSingleItem(item: string, properties?: TrackingEventProperties): void;
10
+ }
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SegmentTrackingProvider = void 0;
4
+ const analytics_next_1 = require("@segment/analytics-next");
5
+ class SegmentTrackingProvider {
6
+ getKey() {
7
+ return 'segmentKey';
8
+ }
9
+ initialize(props) {
10
+ // eslint-disable-next-line no-console
11
+ console.log('SegmentProvider initialize');
12
+ const segmentKey = props.segmentKey;
13
+ // We need to create an object here, as ts lint is unhappy otherwise
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ const integrations = props.segmentIntegrations;
16
+ this.analytics = analytics_next_1.AnalyticsBrowser.load({
17
+ writeKey: segmentKey,
18
+ cdnURL: props.segmentCdn
19
+ }, {
20
+ integrations: Object.assign({}, integrations)
21
+ });
22
+ }
23
+ identify(userID) {
24
+ // eslint-disable-next-line no-console
25
+ console.log('SegmentProvider userID: ' + userID);
26
+ if (this.analytics) {
27
+ this.analytics.identify(userID);
28
+ }
29
+ }
30
+ trackPageView(url) {
31
+ // eslint-disable-next-line no-console
32
+ console.log('SegmentProvider url', url);
33
+ if (this.analytics) {
34
+ if (url) {
35
+ this.analytics.page(url);
36
+ }
37
+ else {
38
+ this.analytics.page(); // Uses window.url
39
+ }
40
+ }
41
+ }
42
+ trackSingleItem(item, properties) {
43
+ // eslint-disable-next-line no-console
44
+ console.log('SegmentProvider: trackSingleItem' + item, properties);
45
+ if (this.analytics) {
46
+ this.analytics.track(item, { properties });
47
+ }
48
+ }
49
+ }
50
+ exports.SegmentTrackingProvider = SegmentTrackingProvider;
@@ -0,0 +1,9 @@
1
+ import { TrackingApi, TrackingEventProperties } from './tracking_api';
2
+ declare class TrackingProviderProxy implements TrackingApi {
3
+ providers: TrackingApi[];
4
+ constructor(providers: TrackingApi[]);
5
+ identify(userID: string): void;
6
+ trackSingleItem(eventName: string, properties?: TrackingEventProperties): void;
7
+ trackPageView(url: string | undefined): void;
8
+ }
9
+ export default TrackingProviderProxy;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class TrackingProviderProxy {
4
+ constructor(providers) {
5
+ this.providers = [];
6
+ this.providers = providers;
7
+ }
8
+ identify(userID) {
9
+ for (const provider of this.providers) {
10
+ provider.identify(userID);
11
+ }
12
+ }
13
+ trackSingleItem(eventName, properties) {
14
+ for (const provider of this.providers) {
15
+ provider.trackSingleItem(eventName, properties);
16
+ }
17
+ }
18
+ trackPageView(url) {
19
+ for (const provider of this.providers) {
20
+ provider.trackPageView(url);
21
+ }
22
+ }
23
+ }
24
+ exports.default = TrackingProviderProxy;
@@ -0,0 +1,8 @@
1
+ export interface TrackingEventProperties {
2
+ [key: string]: string | number | boolean | undefined;
3
+ }
4
+ export interface TrackingApi {
5
+ identify: (userID: string) => void;
6
+ trackPageView: (url: string | undefined) => void;
7
+ trackSingleItem: (eventName: string, properties: TrackingEventProperties | undefined) => void;
8
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ import { InitProps } from './tracking_spi';
2
+ import { TrackingApi } from './tracking_api';
3
+ export declare const getTrackingProviders: (initProps: InitProps) => TrackingApi;
4
+ export default getTrackingProviders;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getTrackingProviders = void 0;
7
+ const trackingProviderProxy_1 = __importDefault(require("./trackingProviderProxy"));
8
+ const console_tracking_provider_1 = require("./console_tracking_provider");
9
+ const segment_tracking_provider_1 = require("./segment_tracking_provider");
10
+ const posthog_tracking_provider_1 = require("./posthog_tracking_provider");
11
+ const umami_tracking_provider_1 = require("./umami_tracking_provider");
12
+ const getTrackingProviders = (initProps) => {
13
+ const providers = [];
14
+ providers.push(new segment_tracking_provider_1.SegmentTrackingProvider());
15
+ providers.push(new posthog_tracking_provider_1.PosthogTrackingProvider());
16
+ providers.push(new umami_tracking_provider_1.UmamiTrackingProvider());
17
+ // TODO dynamically find and register providers
18
+ // Initialize them
19
+ const enabledProviders = [];
20
+ for (const provider of providers) {
21
+ const key = provider.getKey();
22
+ if (Object.keys(initProps).indexOf(key) > -1) {
23
+ provider.initialize(initProps);
24
+ enabledProviders.push(provider);
25
+ }
26
+ }
27
+ // Add the console provider
28
+ const consoleTrackingProvider = new console_tracking_provider_1.ConsoleTrackingProvider();
29
+ enabledProviders.push(consoleTrackingProvider); // TODO noop- provider?
30
+ return new trackingProviderProxy_1.default(enabledProviders);
31
+ };
32
+ exports.getTrackingProviders = getTrackingProviders;
33
+ exports.default = exports.getTrackingProviders;
@@ -0,0 +1,9 @@
1
+ import { TrackingApi, TrackingEventProperties } from './tracking_api';
2
+ export interface InitProps {
3
+ [key: string]: string | number | boolean;
4
+ }
5
+ export interface TrackingSpi extends TrackingApi {
6
+ getKey: () => string;
7
+ initialize: (props: InitProps) => void;
8
+ trackSingleItem: (item: string, properties?: TrackingEventProperties) => void;
9
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,14 @@
1
+ import { InitProps, TrackingSpi } from './tracking_spi';
2
+ import { TrackingApi, TrackingEventProperties } from './tracking_api';
3
+ declare global {
4
+ interface Window {
5
+ umami: any;
6
+ }
7
+ }
8
+ export declare class UmamiTrackingProvider implements TrackingSpi, TrackingApi {
9
+ getKey(): string;
10
+ initialize(props: InitProps): void;
11
+ identify(userID: string): void;
12
+ trackPageView(url: string | undefined): void;
13
+ trackSingleItem(item: string, properties?: TrackingEventProperties): void;
14
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UmamiTrackingProvider = void 0;
4
+ class UmamiTrackingProvider {
5
+ getKey() {
6
+ return 'umamiKey';
7
+ }
8
+ initialize(props) {
9
+ // eslint-disable-next-line no-console
10
+ console.log('UmamiProvider initialize');
11
+ const umamiKey = props.umamiKey;
12
+ const hostUrl = props.umamiHostUrl;
13
+ const script = document.createElement('script');
14
+ script.src = hostUrl + '/script.js';
15
+ script.async = true;
16
+ script.defer = true;
17
+ // Configure Umami properties
18
+ script.setAttribute('data-website-id', umamiKey);
19
+ script.setAttribute('data-domains', 'localhost'); // TODO ?
20
+ script.setAttribute('data-auto-track', 'false');
21
+ script.setAttribute('data-host-url', hostUrl); // TODO ?
22
+ script.setAttribute('data-exclude-search', 'false'); // TODO ?
23
+ document.body.appendChild(script);
24
+ }
25
+ identify(userID) {
26
+ var _a;
27
+ // eslint-disable-next-line no-console
28
+ console.log('UmamiProvider userID: ' + userID);
29
+ (_a = window.umami) === null || _a === void 0 ? void 0 : _a.identify({ userID });
30
+ }
31
+ trackPageView(url) {
32
+ var _a;
33
+ // eslint-disable-next-line no-console
34
+ console.log('UmamiProvider url', url);
35
+ (_a = window.umami) === null || _a === void 0 ? void 0 : _a.track({ url });
36
+ }
37
+ trackSingleItem(item, properties) {
38
+ var _a;
39
+ // eslint-disable-next-line no-console
40
+ console.log('UmamiProvider: trackSingleItem' + item, properties);
41
+ (_a = window.umami) === null || _a === void 0 ? void 0 : _a.track(item, properties);
42
+ }
43
+ }
44
+ exports.UmamiTrackingProvider = UmamiTrackingProvider;
@@ -0,0 +1 @@
1
+ {"main":"../../cjs/tracking/index.js","module":"../../esm/tracking/index.js","typings":"../../esm/tracking/index.d.ts"}
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import { AlertProps } from '@patternfly/react-core';
3
+ declare const ErrorMessage: ({ title, actionLinks, children, ...props }: AlertProps) => React.JSX.Element;
4
+ export default ErrorMessage;
@@ -0,0 +1,21 @@
1
+ // ============================================================================
2
+ // Chatbot Main - Message - Content - Error
3
+ // ============================================================================
4
+ var __rest = (this && this.__rest) || function (s, e) {
5
+ var t = {};
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
7
+ t[p] = s[p];
8
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
9
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
10
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
11
+ t[p[i]] = s[p[i]];
12
+ }
13
+ return t;
14
+ };
15
+ import React from 'react';
16
+ import { Alert } from '@patternfly/react-core';
17
+ const ErrorMessage = (_a) => {
18
+ var { title, actionLinks, children } = _a, props = __rest(_a, ["title", "actionLinks", "children"]);
19
+ return (React.createElement(Alert, Object.assign({ isInline: true, variant: "danger", title: title, actionLinks: actionLinks }, props), children));
20
+ };
21
+ export default ErrorMessage;
@@ -1,5 +1,5 @@
1
1
  import React, { ReactNode } from 'react';
2
- import { AvatarProps, LabelGroupProps } from '@patternfly/react-core';
2
+ import { AlertProps, AvatarProps, LabelGroupProps } from '@patternfly/react-core';
3
3
  import { ActionProps } from '../ResponseActions/ResponseActions';
4
4
  import { SourcesCardProps } from '../SourcesCard';
5
5
  import { QuickStart, QuickstartAction } from './QuickStarts/types';
@@ -102,6 +102,8 @@ export interface MessageProps extends Omit<React.HTMLProps<HTMLDivElement>, 'rol
102
102
  additionalRehypePlugins?: PluggableList;
103
103
  /** Whether to open links in message in new tab. */
104
104
  openLinkInNewTab?: boolean;
105
+ /** Optional inline error message that can be displayed in the message */
106
+ error?: AlertProps;
105
107
  }
106
108
  export declare const MessageBase: React.FunctionComponent<MessageProps>;
107
109
  declare const Message: React.ForwardRefExoticComponent<Omit<MessageProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
@@ -40,8 +40,9 @@ import rehypeUnwrapImages from 'rehype-unwrap-images';
40
40
  import rehypeExternalLinks from 'rehype-external-links';
41
41
  import rehypeSanitize from 'rehype-sanitize';
42
42
  import LinkMessage from './LinkMessage/LinkMessage';
43
+ import ErrorMessage from './ErrorMessage/ErrorMessage';
43
44
  export const MessageBase = (_a) => {
44
- var { role, content, extraContent, name, avatar, timestamp, isLoading, actions, sources, botWord = 'AI', loadingWord = 'Loading message', codeBlockProps, quickResponses, quickResponseContainerProps = { numLabels: 5 }, attachments, hasRoundAvatar = true, avatarProps, quickStarts, userFeedbackForm, userFeedbackComplete, isLiveRegion = true, innerRef, tableProps, openLinkInNewTab = true, additionalRehypePlugins = [] } = _a, props = __rest(_a, ["role", "content", "extraContent", "name", "avatar", "timestamp", "isLoading", "actions", "sources", "botWord", "loadingWord", "codeBlockProps", "quickResponses", "quickResponseContainerProps", "attachments", "hasRoundAvatar", "avatarProps", "quickStarts", "userFeedbackForm", "userFeedbackComplete", "isLiveRegion", "innerRef", "tableProps", "openLinkInNewTab", "additionalRehypePlugins"]);
45
+ var { role, content, extraContent, name, avatar, timestamp, isLoading, actions, sources, botWord = 'AI', loadingWord = 'Loading message', codeBlockProps, quickResponses, quickResponseContainerProps = { numLabels: 5 }, attachments, hasRoundAvatar = true, avatarProps, quickStarts, userFeedbackForm, userFeedbackComplete, isLiveRegion = true, innerRef, tableProps, openLinkInNewTab = true, additionalRehypePlugins = [], error } = _a, props = __rest(_a, ["role", "content", "extraContent", "name", "avatar", "timestamp", "isLoading", "actions", "sources", "botWord", "loadingWord", "codeBlockProps", "quickResponses", "quickResponseContainerProps", "attachments", "hasRoundAvatar", "avatarProps", "quickStarts", "userFeedbackForm", "userFeedbackComplete", "isLiveRegion", "innerRef", "tableProps", "openLinkInNewTab", "additionalRehypePlugins", "error"]);
45
46
  const { beforeMainContent, afterMainContent, endContent } = extraContent || {};
46
47
  let rehypePlugins = [rehypeUnwrapImages];
47
48
  if (openLinkInNewTab) {
@@ -71,7 +72,7 @@ export const MessageBase = (_a) => {
71
72
  React.createElement("div", { className: "pf-chatbot__message-and-actions" },
72
73
  isLoading ? (React.createElement(MessageLoading, { loadingWord: loadingWord })) : (React.createElement(React.Fragment, null,
73
74
  beforeMainContent && React.createElement(React.Fragment, null, beforeMainContent),
74
- React.createElement(Markdown, { components: {
75
+ error ? (React.createElement(ErrorMessage, Object.assign({}, error))) : (React.createElement(Markdown, { components: {
75
76
  p: (props) => React.createElement(TextMessage, Object.assign({ component: ContentVariants.p }, props)),
76
77
  code: (_a) => {
77
78
  var { children } = _a, props = __rest(_a, ["children"]);
@@ -100,7 +101,7 @@ export const MessageBase = (_a) => {
100
101
  th: (props) => React.createElement(ThMessage, Object.assign({}, props)),
101
102
  img: (props) => React.createElement(ImageMessage, Object.assign({}, props)),
102
103
  a: (props) => (React.createElement(LinkMessage, { href: props.href, rel: props.rel, target: props.target }, props.children))
103
- }, remarkPlugins: [remarkGfm], rehypePlugins: rehypePlugins }, content),
104
+ }, remarkPlugins: [remarkGfm], rehypePlugins: rehypePlugins }, content)),
104
105
  afterMainContent && React.createElement(React.Fragment, null, afterMainContent))),
105
106
  !isLoading && sources && React.createElement(SourcesCard, Object.assign({}, sources)),
106
107
  quickStarts && quickStarts.quickStart && (React.createElement(QuickStartTile, { quickStart: quickStarts.quickStart, onSelectQuickStart: quickStarts.onSelectQuickStart, minuteWord: quickStarts.minuteWord, minuteWordPlural: quickStarts.minuteWordPlural, prerequisiteWord: quickStarts.prerequisiteWord, prerequisiteWordPlural: quickStarts.prerequisiteWordPlural, quickStartButtonAriaLabel: quickStarts.quickStartButtonAriaLabel })),
@@ -15,6 +15,7 @@ import userEvent from '@testing-library/user-event';
15
15
  import { monitorSampleAppQuickStart } from './QuickStarts/monitor-sampleapp-quickstart';
16
16
  import { monitorSampleAppQuickStartWithImage } from './QuickStarts/monitor-sampleapp-quickstart-with-image';
17
17
  import rehypeExternalLinks from '../__mocks__/rehype-external-links';
18
+ import { AlertActionLink } from '@patternfly/react-core';
18
19
  const ALL_ACTIONS = [
19
20
  { label: /Good response/i },
20
21
  { label: /Bad response/i },
@@ -134,6 +135,13 @@ const EMPTY_TABLE = `
134
135
 
135
136
  `;
136
137
  const IMAGE = `![Multi-colored wavy lines on a black background](https://cdn.dribbble.com/userupload/10651749/file/original-8a07b8e39d9e8bf002358c66fce1223e.gif)`;
138
+ const ERROR = {
139
+ title: 'Could not load chat',
140
+ children: 'Wait a few minutes and check your network settings. If the issue persists: ',
141
+ actionLinks: (React.createElement(React.Fragment, null,
142
+ React.createElement(AlertActionLink, { component: "a", href: "#" }, "Start a new chat"),
143
+ React.createElement(AlertActionLink, { component: "a", href: "#" }, "Contact support")))
144
+ };
137
145
  const checkListItemsRendered = () => {
138
146
  const items = ['Item 1', 'Item 2', 'Item 3'];
139
147
  expect(screen.getAllByRole('listitem')).toHaveLength(3);
@@ -589,4 +597,21 @@ describe('Message', () => {
589
597
  // we are mocking rehype libraries, so we can't test target _blank addition on links directly with RTL
590
598
  expect(rehypeExternalLinks).not.toHaveBeenCalled();
591
599
  });
600
+ it('should handle error correctly', () => {
601
+ render(React.createElement(Message, { avatar: "./img", role: "user", name: "User", error: ERROR }));
602
+ expect(screen.getByRole('heading', { name: /Could not load chat/i })).toBeTruthy();
603
+ expect(screen.getByRole('link', { name: /Start a new chat/i })).toBeTruthy();
604
+ expect(screen.getByRole('link', { name: /Contact support/i })).toBeTruthy();
605
+ expect(screen.getByText('Wait a few minutes and check your network settings. If the issue persists:')).toBeTruthy();
606
+ });
607
+ it('should handle error correctly when loading', () => {
608
+ render(React.createElement(Message, { avatar: "./img", role: "user", name: "User", error: ERROR, isLoading: true }));
609
+ expect(screen.queryByRole('heading', { name: /Could not load chat/i })).toBeFalsy();
610
+ expect(screen.getByText('Loading message')).toBeTruthy();
611
+ });
612
+ it('should handle error correctly when these is content', () => {
613
+ render(React.createElement(Message, { avatar: "./img", role: "user", name: "User", error: ERROR, content: "Test" }));
614
+ expect(screen.getByRole('heading', { name: /Could not load chat/i })).toBeTruthy();
615
+ expect(screen.queryByText('Test')).toBeFalsy();
616
+ });
592
617
  });