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

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 (103) hide show
  1. package/dist/cjs/Chatbot/Chatbot.d.ts +2 -1
  2. package/dist/cjs/Chatbot/Chatbot.js +1 -0
  3. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +1 -1
  4. package/dist/cjs/ChatbotHeader/ChatbotHeaderTitle.d.ts +3 -1
  5. package/dist/cjs/ChatbotHeader/ChatbotHeaderTitle.js +4 -2
  6. package/dist/cjs/ChatbotHeader/ChatbotHeaderTitle.test.js +15 -7
  7. package/dist/cjs/Message/ErrorMessage/ErrorMessage.d.ts +4 -0
  8. package/dist/cjs/Message/ErrorMessage/ErrorMessage.js +26 -0
  9. package/dist/cjs/Message/Message.d.ts +3 -1
  10. package/dist/cjs/Message/Message.js +4 -3
  11. package/dist/cjs/Message/Message.test.js +25 -0
  12. package/dist/cjs/index.d.ts +2 -0
  13. package/dist/cjs/index.js +4 -1
  14. package/dist/cjs/tracking/console_tracking_provider.d.ts +10 -0
  15. package/dist/cjs/tracking/console_tracking_provider.js +27 -0
  16. package/dist/cjs/tracking/index.d.ts +2 -0
  17. package/dist/cjs/tracking/index.js +23 -0
  18. package/dist/cjs/tracking/posthog_tracking_provider.d.ts +9 -0
  19. package/dist/cjs/tracking/posthog_tracking_provider.js +37 -0
  20. package/dist/cjs/tracking/segment_tracking_provider.d.ts +10 -0
  21. package/dist/cjs/tracking/segment_tracking_provider.js +50 -0
  22. package/dist/cjs/tracking/trackingProviderProxy.d.ts +9 -0
  23. package/dist/cjs/tracking/trackingProviderProxy.js +24 -0
  24. package/dist/cjs/tracking/tracking_api.d.ts +8 -0
  25. package/dist/cjs/tracking/tracking_api.js +2 -0
  26. package/dist/cjs/tracking/tracking_registry.d.ts +4 -0
  27. package/dist/cjs/tracking/tracking_registry.js +33 -0
  28. package/dist/cjs/tracking/tracking_spi.d.ts +9 -0
  29. package/dist/cjs/tracking/tracking_spi.js +2 -0
  30. package/dist/cjs/tracking/umami_tracking_provider.d.ts +14 -0
  31. package/dist/cjs/tracking/umami_tracking_provider.js +44 -0
  32. package/dist/css/main.css +28 -3
  33. package/dist/css/main.css.map +1 -1
  34. package/dist/dynamic/tracking/package.json +1 -0
  35. package/dist/esm/Chatbot/Chatbot.d.ts +2 -1
  36. package/dist/esm/Chatbot/Chatbot.js +1 -0
  37. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +1 -1
  38. package/dist/esm/ChatbotHeader/ChatbotHeaderTitle.d.ts +3 -1
  39. package/dist/esm/ChatbotHeader/ChatbotHeaderTitle.js +4 -2
  40. package/dist/esm/ChatbotHeader/ChatbotHeaderTitle.test.js +15 -7
  41. package/dist/esm/Message/ErrorMessage/ErrorMessage.d.ts +4 -0
  42. package/dist/esm/Message/ErrorMessage/ErrorMessage.js +21 -0
  43. package/dist/esm/Message/Message.d.ts +3 -1
  44. package/dist/esm/Message/Message.js +4 -3
  45. package/dist/esm/Message/Message.test.js +25 -0
  46. package/dist/esm/index.d.ts +2 -0
  47. package/dist/esm/index.js +2 -0
  48. package/dist/esm/tracking/console_tracking_provider.d.ts +10 -0
  49. package/dist/esm/tracking/console_tracking_provider.js +23 -0
  50. package/dist/esm/tracking/index.d.ts +2 -0
  51. package/dist/esm/tracking/index.js +2 -0
  52. package/dist/esm/tracking/posthog_tracking_provider.d.ts +9 -0
  53. package/dist/esm/tracking/posthog_tracking_provider.js +33 -0
  54. package/dist/esm/tracking/segment_tracking_provider.d.ts +10 -0
  55. package/dist/esm/tracking/segment_tracking_provider.js +46 -0
  56. package/dist/esm/tracking/trackingProviderProxy.d.ts +9 -0
  57. package/dist/esm/tracking/trackingProviderProxy.js +22 -0
  58. package/dist/esm/tracking/tracking_api.d.ts +8 -0
  59. package/dist/esm/tracking/tracking_api.js +1 -0
  60. package/dist/esm/tracking/tracking_registry.d.ts +4 -0
  61. package/dist/esm/tracking/tracking_registry.js +26 -0
  62. package/dist/esm/tracking/tracking_spi.d.ts +9 -0
  63. package/dist/esm/tracking/tracking_spi.js +1 -0
  64. package/dist/esm/tracking/umami_tracking_provider.d.ts +14 -0
  65. package/dist/esm/tracking/umami_tracking_provider.js +40 -0
  66. package/dist/tsconfig.tsbuildinfo +1 -1
  67. package/package.json +5 -3
  68. package/patternfly-docs/content/extensions/chatbot/about-chatbot.md +3 -0
  69. package/patternfly-docs/content/extensions/chatbot/examples/Analytics/Analytics.md +219 -0
  70. package/patternfly-docs/content/extensions/chatbot/examples/Messages/BotMessage.tsx +24 -1
  71. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +24 -1
  72. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +13 -0
  73. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.tsx +37 -24
  74. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotInDrawer.tsx +453 -0
  75. package/patternfly-docs/content/extensions/chatbot/img/analytics-example.svg +118 -0
  76. package/patternfly-docs/content/extensions/chatbot/img/chatbot-analytics.svg +51 -0
  77. package/patternfly-docs/content/extensions/chatbot/img/posthog.svg +30 -0
  78. package/patternfly-docs/content/extensions/chatbot/img/segment.svg +36 -0
  79. package/patternfly-docs/content/extensions/chatbot/img/umami.svg +30 -0
  80. package/src/Chatbot/Chatbot.scss +19 -0
  81. package/src/Chatbot/Chatbot.tsx +2 -1
  82. package/src/ChatbotContent/ChatbotContent.scss +1 -0
  83. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +2 -0
  84. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +1 -1
  85. package/src/ChatbotFooter/ChatbotFooter.scss +9 -0
  86. package/src/ChatbotHeader/ChatbotHeader.scss +2 -1
  87. package/src/ChatbotHeader/ChatbotHeaderTitle.test.tsx +23 -7
  88. package/src/ChatbotHeader/ChatbotHeaderTitle.tsx +7 -2
  89. package/src/Message/ErrorMessage/ErrorMessage.tsx +14 -0
  90. package/src/Message/Message.test.tsx +32 -0
  91. package/src/Message/Message.tsx +50 -41
  92. package/src/MessageBar/MessageBar.scss +3 -3
  93. package/src/MessageBox/MessageBox.scss +1 -0
  94. package/src/index.ts +3 -0
  95. package/src/tracking/console_tracking_provider.ts +30 -0
  96. package/src/tracking/index.ts +3 -0
  97. package/src/tracking/posthog_tracking_provider.ts +42 -0
  98. package/src/tracking/segment_tracking_provider.ts +62 -0
  99. package/src/tracking/trackingProviderProxy.ts +28 -0
  100. package/src/tracking/tracking_api.ts +11 -0
  101. package/src/tracking/tracking_registry.ts +33 -0
  102. package/src/tracking/tracking_spi.ts +14 -0
  103. package/src/tracking/umami_tracking_provider.ts +54 -0
@@ -17,27 +17,27 @@ describe('ChatbotHeaderTitle', () => {
17
17
  });
18
18
 
19
19
  it('should render title for default display mode', () => {
20
- render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.default} showOnDefault={'Default header title'} />);
20
+ render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.default} showOnDefault="Default header title" />);
21
21
  expect(screen.getByText('Default header title')).toBeTruthy();
22
22
  });
23
23
 
24
24
  it('should render title for docked display mode', () => {
25
- render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.docked} showOnDocked={'Docked header title'} />);
25
+ render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.docked} showOnDocked="Docked header title" />);
26
26
  expect(screen.getByText('Docked header title')).toBeTruthy();
27
27
  });
28
28
 
29
29
  it('should fallback to default title when docked display mode title is not configured', () => {
30
- render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.docked} showOnDefault={'Default header title'} />);
30
+ render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.docked} showOnDefault="Default header title" />);
31
31
  expect(screen.getByText('Default header title')).toBeTruthy();
32
32
  });
33
33
 
34
34
  it('should render title for embedded display mode', () => {
35
- render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.embedded} showOnEmbedded={'Embedded header title'} />);
35
+ render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.embedded} showOnEmbedded="Embedded header title" />);
36
36
  expect(screen.getByText('Embedded header title')).toBeTruthy();
37
37
  });
38
38
 
39
39
  it('should fallback to default title when embedded display mode title is not configured', () => {
40
- render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.embedded} showOnDefault={'Default header title'} />);
40
+ render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.embedded} showOnDefault="Default header title" />);
41
41
  expect(screen.getByText('Default header title')).toBeTruthy();
42
42
  });
43
43
 
@@ -45,7 +45,7 @@ describe('ChatbotHeaderTitle', () => {
45
45
  render(
46
46
  <ChatbotHeaderTitle
47
47
  displayMode={ChatbotDisplayMode.fullscreen}
48
- showOnFullScreen={'Fullscreen header title'}
48
+ showOnFullScreen="Fullscreen header title"
49
49
  className="custom-header-class"
50
50
  />
51
51
  );
@@ -53,7 +53,23 @@ describe('ChatbotHeaderTitle', () => {
53
53
  });
54
54
 
55
55
  it('should fallback to default title when fullscreen display mode title is not configured', () => {
56
- render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.fullscreen} showOnDefault={'Default header title'} />);
56
+ render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.fullscreen} showOnDefault="Default header title" />);
57
+ expect(screen.getByText('Default header title')).toBeTruthy();
58
+ });
59
+
60
+ it('should render title for drawer display mode', () => {
61
+ render(
62
+ <ChatbotHeaderTitle
63
+ displayMode={ChatbotDisplayMode.drawer}
64
+ showOnDrawer="Drawer header title"
65
+ className="custom-header-class"
66
+ />
67
+ );
68
+ expect(screen.getByText('Drawer header title')).toBeTruthy();
69
+ });
70
+
71
+ it('should fallback to default title when drawer display mode title is not configured', () => {
72
+ render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.drawer} showOnDefault="Default header title" />);
57
73
  expect(screen.getByText('Default header title')).toBeTruthy();
58
74
  });
59
75
  });
@@ -14,8 +14,10 @@ export interface ChatbotHeaderTitleProps {
14
14
  showOnFullScreen?: React.ReactNode | string;
15
15
  /** Content to display on docked screen */
16
16
  showOnDocked?: React.ReactNode | string;
17
- /** Content to display on overlay screen */
17
+ /** Content to display on embedded screen */
18
18
  showOnEmbedded?: React.ReactNode | string;
19
+ /** Content to display in drawer mode */
20
+ showOnDrawer?: React.ReactNode | string;
19
21
  /** Content to display by default; this will be shown if a case is not explicitly set */
20
22
  showOnDefault?: React.ReactNode | string;
21
23
  }
@@ -27,10 +29,11 @@ export const ChatbotHeaderTitle: React.FunctionComponent<ChatbotHeaderTitleProps
27
29
  showOnFullScreen,
28
30
  showOnDocked,
29
31
  showOnEmbedded,
32
+ showOnDrawer,
30
33
  showOnDefault
31
34
  }: ChatbotHeaderTitleProps) => {
32
35
  const renderChildren = () => {
33
- if (displayMode && (showOnDefault || showOnFullScreen || showOnEmbedded || showOnDocked)) {
36
+ if (displayMode) {
34
37
  /* eslint-disable indent */
35
38
  switch (displayMode) {
36
39
  case ChatbotDisplayMode.fullscreen:
@@ -39,6 +42,8 @@ export const ChatbotHeaderTitle: React.FunctionComponent<ChatbotHeaderTitleProps
39
42
  return showOnDocked ?? showOnDefault;
40
43
  case ChatbotDisplayMode.embedded:
41
44
  return showOnEmbedded ?? showOnDefault;
45
+ case ChatbotDisplayMode.drawer:
46
+ return showOnDrawer ?? showOnDefault;
42
47
  default:
43
48
  return showOnDefault;
44
49
  }
@@ -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;
@@ -6,6 +6,7 @@ import userEvent from '@testing-library/user-event';
6
6
  import { monitorSampleAppQuickStart } from './QuickStarts/monitor-sampleapp-quickstart';
7
7
  import { monitorSampleAppQuickStartWithImage } from './QuickStarts/monitor-sampleapp-quickstart-with-image';
8
8
  import rehypeExternalLinks from '../__mocks__/rehype-external-links';
9
+ import { AlertActionLink } from '@patternfly/react-core';
9
10
 
10
11
  const ALL_ACTIONS = [
11
12
  { label: /Good response/i },
@@ -141,6 +142,20 @@ const EMPTY_TABLE = `
141
142
 
142
143
  const IMAGE = `![Multi-colored wavy lines on a black background](https://cdn.dribbble.com/userupload/10651749/file/original-8a07b8e39d9e8bf002358c66fce1223e.gif)`;
143
144
 
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
+ };
144
159
  const checkListItemsRendered = () => {
145
160
  const items = ['Item 1', 'Item 2', 'Item 3'];
146
161
  expect(screen.getAllByRole('listitem')).toHaveLength(3);
@@ -769,4 +784,21 @@ describe('Message', () => {
769
784
  // we are mocking rehype libraries, so we can't test target _blank addition on links directly with RTL
770
785
  expect(rehypeExternalLinks).not.toHaveBeenCalled();
771
786
  });
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
+ });
772
804
  });
@@ -7,6 +7,7 @@ import React, { ReactNode } from 'react';
7
7
  import Markdown from 'react-markdown';
8
8
  import remarkGfm from 'remark-gfm';
9
9
  import {
10
+ AlertProps,
10
11
  Avatar,
11
12
  AvatarProps,
12
13
  ContentVariants,
@@ -42,6 +43,7 @@ import rehypeExternalLinks from 'rehype-external-links';
42
43
  import rehypeSanitize from 'rehype-sanitize';
43
44
  import { PluggableList } from 'react-markdown/lib';
44
45
  import LinkMessage from './LinkMessage/LinkMessage';
46
+ import ErrorMessage from './ErrorMessage/ErrorMessage';
45
47
 
46
48
  export interface MessageAttachment {
47
49
  /** Name of file attached to the message */
@@ -141,6 +143,8 @@ export interface MessageProps extends Omit<React.HTMLProps<HTMLDivElement>, 'rol
141
143
  additionalRehypePlugins?: PluggableList;
142
144
  /** Whether to open links in message in new tab. */
143
145
  openLinkInNewTab?: boolean;
146
+ /** Optional inline error message that can be displayed in the message */
147
+ error?: AlertProps;
144
148
  }
145
149
 
146
150
  export const MessageBase: React.FunctionComponent<MessageProps> = ({
@@ -169,6 +173,7 @@ export const MessageBase: React.FunctionComponent<MessageProps> = ({
169
173
  tableProps,
170
174
  openLinkInNewTab = true,
171
175
  additionalRehypePlugins = [],
176
+ error,
172
177
  ...props
173
178
  }: MessageProps) => {
174
179
  const { beforeMainContent, afterMainContent, endContent } = extraContent || {};
@@ -225,47 +230,51 @@ export const MessageBase: React.FunctionComponent<MessageProps> = ({
225
230
  ) : (
226
231
  <>
227
232
  {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
+ )}
269
278
  {afterMainContent && <>{afterMainContent}</>}
270
279
  </>
271
280
  )}
@@ -50,9 +50,9 @@
50
50
  }
51
51
 
52
52
  .pf-chatbot__message-textarea {
53
- --pf-v6-c-form-control--before--BorderStyle: none;
54
- --pf-v6-c-form-control--after--BorderStyle: none;
55
- resize: none;
53
+ --pf-v6-c-form-control--before--BorderStyle: none !important;
54
+ --pf-v6-c-form-control--after--BorderStyle: none !important;
55
+ resize: none !important;
56
56
  background-color: transparent;
57
57
  font-size: var(--pf-t--global--font--size--md);
58
58
  line-height: 1.5rem;
@@ -37,6 +37,7 @@
37
37
 
38
38
  @media screen and (min-width: 64rem) {
39
39
  .pf-chatbot--embedded,
40
+ .pf-chatbot--drawer,
40
41
  .pf-chatbot--fullscreen {
41
42
  .pf-chatbot__messagebox {
42
43
  max-width: 60rem;
package/src/index.ts CHANGED
@@ -80,3 +80,6 @@ export * from './SourcesCard';
80
80
 
81
81
  export { default as TermsOfUse } from './TermsOfUse';
82
82
  export * from './TermsOfUse';
83
+
84
+ export { default as tracking } from './tracking';
85
+ export * from './tracking';
@@ -0,0 +1,30 @@
1
+ import { TrackingSpi } from './tracking_spi';
2
+ import { TrackingApi, TrackingEventProperties } from './tracking_api';
3
+
4
+ export class ConsoleTrackingProvider implements TrackingSpi, TrackingApi {
5
+ trackPageView(url: string | undefined) {
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(): void {}
11
+
12
+ initialize(): void {
13
+ // eslint-disable-next-line no-console
14
+ console.log('ConsoleProvider initialize');
15
+ }
16
+
17
+ identify(userID: string): void {
18
+ // eslint-disable-next-line no-console
19
+ console.log('ConsoleProvider identify', userID);
20
+ }
21
+
22
+ trackSingleItem(item: string, properties?: TrackingEventProperties): void {
23
+ // eslint-disable-next-line no-console
24
+ console.log('ConsoleProvider: ' + item, properties);
25
+ }
26
+
27
+ getKey(): string {
28
+ return 'console';
29
+ }
30
+ }
@@ -0,0 +1,3 @@
1
+ export { default } from './tracking_registry';
2
+
3
+ export * from './tracking_registry';
@@ -0,0 +1,42 @@
1
+ import { posthog } from 'posthog-js';
2
+
3
+ import { TrackingApi, TrackingEventProperties } from './tracking_api';
4
+ import { InitProps, TrackingSpi } from './tracking_spi';
5
+
6
+ export class PosthogTrackingProvider implements TrackingSpi, TrackingApi {
7
+ getKey(): string {
8
+ return 'posthogKey';
9
+ }
10
+
11
+ initialize(props: InitProps): void {
12
+ // eslint-disable-next-line no-console
13
+ console.log('PosthogProvider initialize');
14
+ const posthogKey = props.posthogKey as string;
15
+
16
+ posthog.init(posthogKey, {
17
+ // eslint-disable-next-line camelcase
18
+ api_host: 'https://us.i.posthog.com',
19
+ // eslint-disable-next-line camelcase
20
+ person_profiles: 'identified_only' // or 'always' to create profiles for anonymous users as well
21
+ });
22
+ }
23
+
24
+ identify(userID: string): void {
25
+ // eslint-disable-next-line no-console
26
+ console.log('PosthogProvider userID: ' + userID);
27
+ posthog.identify(userID);
28
+ }
29
+
30
+ trackPageView(url: string | undefined): void {
31
+ // eslint-disable-next-line no-console
32
+ console.log('PostHogProvider url', url);
33
+ // TODO posthog seems to record that automatically.
34
+ // How to not clash with this here? Just leave as no-op?
35
+ }
36
+
37
+ trackSingleItem(item: string, properties?: TrackingEventProperties): void {
38
+ // eslint-disable-next-line no-console
39
+ console.log('PosthogProvider: trackSingleItem' + item, properties);
40
+ posthog.capture(item, { properties });
41
+ }
42
+ }
@@ -0,0 +1,62 @@
1
+ import { AnalyticsBrowser } from '@segment/analytics-next';
2
+
3
+ import { TrackingApi, TrackingEventProperties } from './tracking_api';
4
+ import { InitProps, TrackingSpi } from './tracking_spi';
5
+
6
+ export class SegmentTrackingProvider implements TrackingSpi, TrackingApi {
7
+ private analytics: AnalyticsBrowser | undefined;
8
+ getKey(): string {
9
+ return 'segmentKey';
10
+ }
11
+
12
+ initialize(props: InitProps): void {
13
+ // eslint-disable-next-line no-console
14
+ console.log('SegmentProvider initialize');
15
+ const segmentKey = props.segmentKey as string;
16
+
17
+ // We need to create an object here, as ts lint is unhappy otherwise
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ const integrations = props.segmentIntegrations as any;
20
+
21
+ this.analytics = AnalyticsBrowser.load(
22
+ {
23
+ writeKey: segmentKey,
24
+ cdnURL: props.segmentCdn as string
25
+ },
26
+
27
+ {
28
+ integrations: {
29
+ ...integrations
30
+ }
31
+ }
32
+ );
33
+ }
34
+
35
+ identify(userID: string): void {
36
+ // eslint-disable-next-line no-console
37
+ console.log('SegmentProvider userID: ' + userID);
38
+ if (this.analytics) {
39
+ this.analytics.identify(userID);
40
+ }
41
+ }
42
+
43
+ trackPageView(url: string | undefined): void {
44
+ // eslint-disable-next-line no-console
45
+ console.log('SegmentProvider url', url);
46
+ if (this.analytics) {
47
+ if (url) {
48
+ this.analytics.page(url);
49
+ } else {
50
+ this.analytics.page(); // Uses window.url
51
+ }
52
+ }
53
+ }
54
+
55
+ trackSingleItem(item: string, properties?: TrackingEventProperties): void {
56
+ // eslint-disable-next-line no-console
57
+ console.log('SegmentProvider: trackSingleItem' + item, properties);
58
+ if (this.analytics) {
59
+ this.analytics.track(item, { properties });
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,28 @@
1
+ import { TrackingApi, TrackingEventProperties } from './tracking_api';
2
+ class TrackingProviderProxy implements TrackingApi {
3
+ providers: TrackingApi[] = [];
4
+
5
+ constructor(providers: TrackingApi[]) {
6
+ this.providers = providers;
7
+ }
8
+
9
+ identify(userID: string): void {
10
+ for (const provider of this.providers) {
11
+ provider.identify(userID);
12
+ }
13
+ }
14
+
15
+ trackSingleItem(eventName: string, properties?: TrackingEventProperties): void {
16
+ for (const provider of this.providers) {
17
+ provider.trackSingleItem(eventName, properties);
18
+ }
19
+ }
20
+
21
+ trackPageView(url: string | undefined) {
22
+ for (const provider of this.providers) {
23
+ provider.trackPageView(url);
24
+ }
25
+ }
26
+ }
27
+
28
+ export default TrackingProviderProxy;
@@ -0,0 +1,11 @@
1
+ export interface TrackingEventProperties {
2
+ [key: string]: string | number | boolean | undefined;
3
+ }
4
+
5
+ export interface TrackingApi {
6
+ identify: (userID: string) => void;
7
+
8
+ trackPageView: (url: string | undefined) => void;
9
+
10
+ trackSingleItem: (eventName: string, properties: TrackingEventProperties | undefined) => void;
11
+ }
@@ -0,0 +1,33 @@
1
+ import { InitProps, TrackingSpi } from './tracking_spi';
2
+ import { TrackingApi } from './tracking_api';
3
+ import TrackingProviderProxy from './trackingProviderProxy';
4
+ import { ConsoleTrackingProvider } from './console_tracking_provider';
5
+ import { SegmentTrackingProvider } from './segment_tracking_provider';
6
+ import { PosthogTrackingProvider } from './posthog_tracking_provider';
7
+ import { UmamiTrackingProvider } from './umami_tracking_provider';
8
+
9
+ export const getTrackingProviders = (initProps: InitProps): TrackingApi => {
10
+ const providers: TrackingSpi[] = [];
11
+ providers.push(new SegmentTrackingProvider());
12
+ providers.push(new PosthogTrackingProvider());
13
+ providers.push(new UmamiTrackingProvider());
14
+
15
+ // TODO dynamically find and register providers
16
+
17
+ // Initialize them
18
+ const enabledProviders: TrackingSpi[] = [];
19
+ for (const provider of providers) {
20
+ const key = provider.getKey();
21
+ if (Object.keys(initProps).indexOf(key) > -1) {
22
+ provider.initialize(initProps);
23
+ enabledProviders.push(provider);
24
+ }
25
+ }
26
+ // Add the console provider
27
+ const consoleTrackingProvider = new ConsoleTrackingProvider();
28
+ enabledProviders.push(consoleTrackingProvider); // TODO noop- provider?
29
+
30
+ return new TrackingProviderProxy(enabledProviders);
31
+ };
32
+
33
+ export default getTrackingProviders;
@@ -0,0 +1,14 @@
1
+ import { TrackingApi, TrackingEventProperties } from './tracking_api';
2
+
3
+ export interface InitProps {
4
+ [key: string]: string | number | boolean;
5
+ }
6
+
7
+ export interface TrackingSpi extends TrackingApi {
8
+ // Return a key in InitProps to check if the provided should be enabled
9
+ getKey: () => string;
10
+ // Initialize the provider
11
+ initialize: (props: InitProps) => void;
12
+ // Track a single item
13
+ trackSingleItem: (item: string, properties?: TrackingEventProperties) => void;
14
+ }
@@ -0,0 +1,54 @@
1
+ import { InitProps, TrackingSpi } from './tracking_spi';
2
+ import { TrackingApi, TrackingEventProperties } from './tracking_api';
3
+
4
+ declare global {
5
+ interface Window {
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ umami: any;
8
+ }
9
+ }
10
+
11
+ export class UmamiTrackingProvider implements TrackingSpi, TrackingApi {
12
+ getKey(): string {
13
+ return 'umamiKey';
14
+ }
15
+
16
+ initialize(props: InitProps): void {
17
+ // eslint-disable-next-line no-console
18
+ console.log('UmamiProvider initialize');
19
+ const umamiKey = props.umamiKey as string;
20
+ const hostUrl = props.umamiHostUrl as string;
21
+
22
+ const script = document.createElement('script');
23
+ script.src = hostUrl + '/script.js';
24
+ script.async = true;
25
+ script.defer = true;
26
+
27
+ // Configure Umami properties
28
+ script.setAttribute('data-website-id', umamiKey);
29
+ script.setAttribute('data-domains', 'localhost'); // TODO ?
30
+ script.setAttribute('data-auto-track', 'false');
31
+ script.setAttribute('data-host-url', hostUrl); // TODO ?
32
+ script.setAttribute('data-exclude-search', 'false'); // TODO ?
33
+
34
+ document.body.appendChild(script);
35
+ }
36
+
37
+ identify(userID: string): void {
38
+ // eslint-disable-next-line no-console
39
+ console.log('UmamiProvider userID: ' + userID);
40
+ window.umami?.identify({ userID });
41
+ }
42
+
43
+ trackPageView(url: string | undefined): void {
44
+ // eslint-disable-next-line no-console
45
+ console.log('UmamiProvider url', url);
46
+ window.umami?.track({ url });
47
+ }
48
+
49
+ trackSingleItem(item: string, properties?: TrackingEventProperties): void {
50
+ // eslint-disable-next-line no-console
51
+ console.log('UmamiProvider: trackSingleItem' + item, properties);
52
+ window.umami?.track(item, properties);
53
+ }
54
+ }