@patternfly/chatbot 2.2.0-prerelease.43 → 2.2.0-prerelease.45

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 (52) hide show
  1. package/dist/cjs/SourcesCard/SourcesCard.d.ts +6 -1
  2. package/dist/cjs/SourcesCard/SourcesCard.js +14 -9
  3. package/dist/cjs/SourcesCard/SourcesCard.test.js +25 -11
  4. package/dist/cjs/tracking/console_tracking_provider.d.ts +4 -5
  5. package/dist/cjs/tracking/console_tracking_provider.js +22 -15
  6. package/dist/cjs/tracking/posthog_tracking_provider.d.ts +2 -2
  7. package/dist/cjs/tracking/posthog_tracking_provider.js +21 -12
  8. package/dist/cjs/tracking/segment_tracking_provider.d.ts +2 -2
  9. package/dist/cjs/tracking/segment_tracking_provider.js +21 -12
  10. package/dist/cjs/tracking/trackingProviderProxy.d.ts +1 -1
  11. package/dist/cjs/tracking/trackingProviderProxy.js +2 -2
  12. package/dist/cjs/tracking/tracking_api.d.ts +1 -1
  13. package/dist/cjs/tracking/tracking_registry.js +46 -12
  14. package/dist/cjs/tracking/tracking_spi.d.ts +15 -5
  15. package/dist/cjs/tracking/tracking_spi.js +9 -0
  16. package/dist/cjs/tracking/umami_tracking_provider.d.ts +6 -2
  17. package/dist/cjs/tracking/umami_tracking_provider.js +66 -22
  18. package/dist/css/main.css +3 -7
  19. package/dist/css/main.css.map +1 -1
  20. package/dist/esm/SourcesCard/SourcesCard.d.ts +6 -1
  21. package/dist/esm/SourcesCard/SourcesCard.js +15 -10
  22. package/dist/esm/SourcesCard/SourcesCard.test.js +25 -11
  23. package/dist/esm/tracking/console_tracking_provider.d.ts +4 -5
  24. package/dist/esm/tracking/console_tracking_provider.js +22 -15
  25. package/dist/esm/tracking/posthog_tracking_provider.d.ts +2 -2
  26. package/dist/esm/tracking/posthog_tracking_provider.js +21 -12
  27. package/dist/esm/tracking/segment_tracking_provider.d.ts +2 -2
  28. package/dist/esm/tracking/segment_tracking_provider.js +21 -12
  29. package/dist/esm/tracking/trackingProviderProxy.d.ts +1 -1
  30. package/dist/esm/tracking/trackingProviderProxy.js +2 -2
  31. package/dist/esm/tracking/tracking_api.d.ts +1 -1
  32. package/dist/esm/tracking/tracking_registry.js +46 -12
  33. package/dist/esm/tracking/tracking_spi.d.ts +15 -5
  34. package/dist/esm/tracking/tracking_spi.js +8 -1
  35. package/dist/esm/tracking/umami_tracking_provider.d.ts +6 -2
  36. package/dist/esm/tracking/umami_tracking_provider.js +66 -22
  37. package/package.json +1 -1
  38. package/patternfly-docs/content/extensions/chatbot/examples/Analytics/Analytics.md +18 -14
  39. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithSources.tsx +34 -5
  40. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +1 -1
  41. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.tsx +4 -3
  42. package/src/SourcesCard/SourcesCard.scss +3 -7
  43. package/src/SourcesCard/SourcesCard.test.tsx +30 -17
  44. package/src/SourcesCard/SourcesCard.tsx +41 -11
  45. package/src/tracking/console_tracking_provider.ts +21 -17
  46. package/src/tracking/posthog_tracking_provider.ts +20 -13
  47. package/src/tracking/segment_tracking_provider.ts +20 -13
  48. package/src/tracking/trackingProviderProxy.ts +2 -2
  49. package/src/tracking/tracking_api.ts +1 -1
  50. package/src/tracking/tracking_registry.ts +46 -13
  51. package/src/tracking/tracking_spi.ts +18 -7
  52. package/src/tracking/umami_tracking_provider.ts +76 -20
@@ -1,40 +1,84 @@
1
1
  export class UmamiTrackingProvider {
2
- getKey() {
3
- return 'umamiKey';
2
+ constructor() {
3
+ this.verbose = false;
4
+ this.queue = [];
4
5
  }
5
6
  initialize(props) {
6
- // eslint-disable-next-line no-console
7
- console.log('UmamiProvider initialize');
8
- const umamiKey = props.umamiKey;
7
+ this.verbose = props.verbose;
8
+ this.log('UmamiProvider initialize');
9
+ this.websiteId = props.umamiKey;
9
10
  const hostUrl = props.umamiHostUrl;
10
11
  const script = document.createElement('script');
11
12
  script.src = hostUrl + '/script.js';
12
13
  script.async = true;
13
14
  script.defer = true;
14
15
  // Configure Umami properties
15
- script.setAttribute('data-website-id', umamiKey);
16
- script.setAttribute('data-domains', 'localhost'); // TODO ?
16
+ script.setAttribute('data-website-id', this.websiteId);
17
+ script.setAttribute('data-host-url', hostUrl);
17
18
  script.setAttribute('data-auto-track', 'false');
18
- script.setAttribute('data-host-url', hostUrl); // TODO ?
19
- script.setAttribute('data-exclude-search', 'false'); // TODO ?
19
+ script.setAttribute('data-exclude-search', 'false');
20
+ // Now get from config, which may override some of the above.
21
+ const UMAMI_PREFIX = 'umami-';
22
+ for (const prop in props) {
23
+ if (prop.startsWith(UMAMI_PREFIX)) {
24
+ const att = 'data-' + prop.substring(UMAMI_PREFIX.length);
25
+ const val = props[prop];
26
+ script.setAttribute(att, String(val));
27
+ }
28
+ }
29
+ script.onload = () => {
30
+ this.log('UmamiProvider script loaded');
31
+ this.flushQueue();
32
+ };
20
33
  document.body.appendChild(script);
21
34
  }
22
- identify(userID) {
23
- var _a;
24
- // eslint-disable-next-line no-console
25
- console.log('UmamiProvider userID: ' + userID);
26
- (_a = window.umami) === null || _a === void 0 ? void 0 : _a.identify({ userID });
35
+ identify(userID, userProperties = {}) {
36
+ this.log('UmamiProvider userID: ' + userID + ' => ' + JSON.stringify(userProperties));
37
+ if (window.umami) {
38
+ window.umami.identify({ userID, userProperties });
39
+ }
40
+ else {
41
+ this.queue.push({ what: 'i', name: userID, payload: userProperties });
42
+ }
27
43
  }
28
44
  trackPageView(url) {
29
- var _a;
30
- // eslint-disable-next-line no-console
31
- console.log('UmamiProvider url', url);
32
- (_a = window.umami) === null || _a === void 0 ? void 0 : _a.track({ url });
45
+ this.log('UmamiProvider url ' + url);
46
+ if (window.umami) {
47
+ window.umami.track({ url, website: this.websiteId });
48
+ }
49
+ else {
50
+ this.queue.push({ what: 'p', name: String(url) });
51
+ }
33
52
  }
34
53
  trackSingleItem(item, properties) {
35
- var _a;
36
- // eslint-disable-next-line no-console
37
- console.log('UmamiProvider: trackSingleItem' + item, properties);
38
- (_a = window.umami) === null || _a === void 0 ? void 0 : _a.track(item, properties);
54
+ this.log('UmamiProvider: trackSingleItem ' + item + JSON.stringify(properties));
55
+ if (window.umami) {
56
+ window.umami.track(item, properties);
57
+ }
58
+ else {
59
+ this.queue.push({ what: 't', name: item, payload: properties });
60
+ }
61
+ }
62
+ flushQueue() {
63
+ for (const item of this.queue) {
64
+ this.log('Queue flush ' + JSON.stringify(item));
65
+ switch (item.what) {
66
+ case 'i':
67
+ this.identify(item.name, item.payload);
68
+ break;
69
+ case 't':
70
+ this.trackSingleItem(item.name, item.payload);
71
+ break;
72
+ case 'p':
73
+ this.trackPageView(item.name);
74
+ break;
75
+ }
76
+ }
77
+ }
78
+ log(msg) {
79
+ if (this.verbose) {
80
+ // eslint-disable-next-line no-console
81
+ console.debug('UmamiProvider: ', msg);
82
+ }
39
83
  }
40
84
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/chatbot",
3
- "version": "2.2.0-prerelease.43",
3
+ "version": "2.2.0-prerelease.45",
4
4
  "description": "This library provides React components based on PatternFly 6 that can be used to build chatbots.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -36,10 +36,12 @@ Note that user code only interacts with:
36
36
 
37
37
  ### Setup
38
38
 
39
- 1. Before you can use the `trackingAPI`, you must first supply the API keys of the respective providers.
39
+ 1. Before you can use the `trackingAPI`, you must first supply the API keys of the respective providers. To enable a provider, it must be added to the `activeProviders` property:
40
40
 
41
41
  ```nolive
42
42
  const initProps: InitProps = {
43
+ verbose: false,
44
+ activeProviders: ['Segment', 'Umami', 'Posthog', 'Console' ],
43
45
  segmentKey: 'TODO-key', // TODO add your key here
44
46
  // segmentCdn: 'https://my.org/cdn', // Set up segment cdn (optional)
45
47
  // segmentIntegrations: { // Provide Segment integrations (optional)
@@ -50,13 +52,15 @@ const initProps: InitProps = {
50
52
  },
51
53
 
52
54
  posthogKey: 'TODO-key',
53
- umamiKey: 'TODO-key',
55
+ umamiKey: 'TODO-umami-key',
54
56
  umamiHostUrl: 'http://localhost:3000', // TODO where is your JS provider?
57
+ 'umami-data-domains': 'TODO umami data domain',
55
58
  something: 'test',
56
- console: 'true' // Console provider
57
59
  };
58
60
  ```
59
61
 
62
+ - **Note:** To enable output debugging via the web-browser console, set the `verbose` key to `true`. By default, this is set to `false`.
63
+
60
64
  1. Once this is done, you can create an instance of the `trackingAPI` and start sending events.
61
65
 
62
66
  ```nolive
@@ -76,22 +80,20 @@ trackingAPI.trackSingleItem("MyEvent", { response: 'Good response' })
76
80
 
77
81
  #### Tracking providers
78
82
 
79
- Only providers with a matching key in the `InitProps` will be started and used.
83
+ Only providers with a matching entry in the `InitProps.activeProviders` array will be started and used.
84
+
85
+ Possible values are:
86
+ * Umami
87
+ * Posthog
88
+ * Segment
89
+ * Console
80
90
 
81
- ```nolive
82
- const initProps: InitProps = {
83
- segmentKey: 'TODO-key', // TODO add your key here
84
- posthogKey: 'TODO-key',
85
- umamiKey: 'TODO-key',
86
- umamiHostUrl: 'http://localhost:3000', // TODO where is your JS provider?
87
- console: true
88
- ```
89
91
 
90
92
  ##### Modifying providers
91
93
 
92
94
  If you know upfront that you only want to use 1 of the supported providers, you can modify `getTrackingProviders()` and remove all other providers in the providers array.
93
95
 
94
- When using the providers you need to add additional dependencies to your package.json file:
96
+ When using the providers, you might need to add additional dependencies to your package.json file:
95
97
 
96
98
  ```nolive
97
99
  "dependencies": {
@@ -99,12 +101,14 @@ When using the providers you need to add additional dependencies to your package
99
101
  "posthog-js": "^1.194.4"
100
102
  ```
101
103
 
104
+ Depending on your local setup, this might not be necessary. For example, if you pull the ChatBot codebase as a dependency into your project, you don't need to add it as an additional dependency in your package.json.
105
+
102
106
  ##### Adding providers
103
107
 
104
108
  To add another analytics provider, you need to implement 2 interfaces, `TrackingSpi` and `trackingApi`.
105
109
 
106
110
  1. It is easiest to start by copying the `ConsoleTrackingProvider`
107
- 1. The first thing you should do is to provide a correct value in `getKey()`
111
+ 1. Add an entry for your new provider to the `Providers` enum in `tracking_spi.ts`
108
112
  1. Once you are happy enough with the implementation, add it to the array of providers in `getTrackingProviders()`
109
113
 
110
114
  ### Page flow tracking
@@ -14,7 +14,7 @@ export const MessageWithSourcesExample: React.FunctionComponent = () => {
14
14
  name="Bot"
15
15
  role="bot"
16
16
  avatar={patternflyAvatar}
17
- content="Example with sources"
17
+ content="This example has a body description that's within the recommended limit of 2 lines:"
18
18
  sources={{
19
19
  sources: [
20
20
  {
@@ -43,7 +43,36 @@ export const MessageWithSourcesExample: React.FunctionComponent = () => {
43
43
  name="Bot"
44
44
  role="bot"
45
45
  avatar={patternflyAvatar}
46
- content="Example with very long sources"
46
+ content="This example has a body description that's longer than the recommended limit of 2 lines, with a link to load the rest of the description:"
47
+ sources={{
48
+ sources: [
49
+ {
50
+ title: 'Getting started with Red Hat OpenShift',
51
+ link: '#',
52
+ body: 'Red Hat OpenShift on IBM Cloud is a managed offering to create your own cluster of compute hosts where you can deploy and manage containerized apps on IBM Cloud.',
53
+ hasShowMore: true
54
+ },
55
+ {
56
+ title: 'Azure Red Hat OpenShift documentation',
57
+ link: '#',
58
+ body: 'Microsoft Azure Red Hat OpenShift allows you to deploy a production ready Red Hat OpenShift cluster in Azure.',
59
+ hasShowMore: true
60
+ },
61
+ {
62
+ title: 'OKD Documentation: Home',
63
+ link: '#',
64
+ body: 'OKD is a distribution of Kubernetes optimized for continuous application development and multi-tenant deployment. OKD also serves as the upstream code base upon.',
65
+ hasShowMore: true
66
+ }
67
+ ],
68
+ onSetPage
69
+ }}
70
+ />
71
+ <Message
72
+ name="Bot"
73
+ role="bot"
74
+ avatar={patternflyAvatar}
75
+ content="This example has a truncated title:"
47
76
  sources={{
48
77
  sources: [
49
78
  {
@@ -66,7 +95,7 @@ export const MessageWithSourcesExample: React.FunctionComponent = () => {
66
95
  name="Bot"
67
96
  role="bot"
68
97
  avatar={patternflyAvatar}
69
- content="Example with only 1 source"
98
+ content="This example only includes 1 source:"
70
99
  sources={{
71
100
  sources: [
72
101
  {
@@ -83,7 +112,7 @@ export const MessageWithSourcesExample: React.FunctionComponent = () => {
83
112
  name="Bot"
84
113
  role="bot"
85
114
  avatar={patternflyAvatar}
86
- content="Example with sources that include a title and link"
115
+ content="This example has a title and no body description:"
87
116
  sources={{
88
117
  sources: [
89
118
  { title: 'Getting started with Red Hat OpenShift', link: '#', isExternal: true },
@@ -105,7 +134,7 @@ export const MessageWithSourcesExample: React.FunctionComponent = () => {
105
134
  name="Bot"
106
135
  role="bot"
107
136
  avatar={patternflyAvatar}
108
- content="Example with link-only sources (not recommended)"
137
+ content="This example displays the source as a link, without a title (not recommended)"
109
138
  sources={{
110
139
  sources: [
111
140
  {
@@ -144,7 +144,7 @@ If you are using Retrieval-Augmented Generation, you may want to display sources
144
144
 
145
145
  If a source will open outside of the ChatBot window, add an external link icon via `isExternal`.
146
146
 
147
- The API for a source requires a link at minimum, but we strongly recommend providing a more descriptive title and body description so users have enough context. The title is limited to 1 line and the body is limited to 2 lines.
147
+ The API for a source requires a link at minimum, but we strongly recommend providing a more descriptive title and body description so users have enough context. For the best clarity and readability, we strongly recommend limiting the title to 1 line and the body to 2 lines. If the body description is more than 2 lines, use the "long sources" or "very long sources" variant.
148
148
 
149
149
  ```js file="./MessageWithSources.tsx"
150
150
 
@@ -98,17 +98,18 @@ export default MessageLoading;
98
98
  const date = new Date();
99
99
 
100
100
  const initProps: InitProps = {
101
+ verbose: false,
101
102
  segmentKey: 'TODO-key', // TODO add your key here
102
103
  posthogKey: 'TODO-key',
103
104
  umamiKey: 'TODO-key',
104
- umamiHostUrl: 'http://localhost:3000', // TODO where is your JS provider?
105
+ umamiHostUrl: 'http://localhost:3000', // TODO where is your Umami installation?
105
106
  console: true,
106
107
  something: 'test'
107
108
  };
108
109
 
109
110
  const tracking = getTrackingProviders(initProps);
110
- tracking.identify('user-123'); // TODO get real user id
111
- tracking.trackPageView(window.document.documentURI);
111
+ tracking.identify('user-123', { superUser: true }); // TODO get real user id + properties
112
+ tracking.trackPageView(window.location.href);
112
113
 
113
114
  const actionEventName = 'MessageAction';
114
115
  const initialMessages: MessageProps[] = [
@@ -16,7 +16,7 @@
16
16
  box-shadow: var(--pf-t--global--box-shadow--sm);
17
17
  }
18
18
 
19
- .pf-chatbot__sources-card-body {
19
+ .pf-chatbot__sources-card-body-text {
20
20
  display: block;
21
21
  display: -webkit-box;
22
22
  height: 2.8125rem;
@@ -25,11 +25,6 @@
25
25
  -webkit-box-orient: vertical;
26
26
  overflow: hidden;
27
27
  text-overflow: ellipsis;
28
- margin-bottom: var(--pf-t--global--spacer--md);
29
- }
30
-
31
- .pf-chatbot__sources-card-no-footer {
32
- margin-bottom: var(--pf-t--global--spacer--lg);
33
28
  }
34
29
 
35
30
  .pf-chatbot__sources-card-footer-container {
@@ -38,13 +33,14 @@
38
33
  var(--pf-t--global--spacer--sm) !important;
39
34
  .pf-chatbot__sources-card-footer {
40
35
  display: flex;
41
- justify-content: space-between;
42
36
  align-items: center;
43
37
 
44
38
  &-buttons {
45
39
  display: flex;
46
40
  gap: var(--pf-t--global--spacer--xs);
47
41
  align-items: center;
42
+ justify-content: space-between;
43
+ flex: 1;
48
44
 
49
45
  .pf-v6-c-button {
50
46
  border-radius: var(--pf-t--global--border--radius--pill);
@@ -11,7 +11,7 @@ describe('SourcesCard', () => {
11
11
  expect(screen.getByText('Source 1')).toBeTruthy();
12
12
  // no buttons or navigation when there is only 1 source
13
13
  expect(screen.queryByRole('button')).toBeFalsy();
14
- expect(screen.queryByText('1 of 1')).toBeFalsy();
14
+ expect(screen.queryByText('1/1')).toBeFalsy();
15
15
  });
16
16
 
17
17
  it('should render card correctly if one source with a title is passed in', () => {
@@ -48,7 +48,7 @@ describe('SourcesCard', () => {
48
48
  );
49
49
  expect(screen.getByText('2 sources')).toBeTruthy();
50
50
  expect(screen.getByText('How to make an apple pie')).toBeTruthy();
51
- expect(screen.getByText('1 of 2')).toBeTruthy();
51
+ expect(screen.getByText('1/2')).toBeTruthy();
52
52
  screen.getByRole('button', { name: /Go to previous page/i });
53
53
  screen.getByRole('button', { name: /Go to next page/i });
54
54
  });
@@ -63,12 +63,12 @@ describe('SourcesCard', () => {
63
63
  />
64
64
  );
65
65
  expect(screen.getByText('How to make an apple pie')).toBeTruthy();
66
- expect(screen.getByText('1 of 2')).toBeTruthy();
66
+ expect(screen.getByText('1/2')).toBeTruthy();
67
67
  expect(screen.getByRole('button', { name: /Go to previous page/i })).toBeDisabled();
68
68
  await userEvent.click(screen.getByRole('button', { name: /Go to next page/i }));
69
69
  expect(screen.queryByText('How to make an apple pie')).toBeFalsy();
70
70
  expect(screen.getByText('How to make cookies')).toBeTruthy();
71
- expect(screen.getByText('2 of 2')).toBeTruthy();
71
+ expect(screen.getByText('2/2')).toBeTruthy();
72
72
  expect(screen.getByRole('button', { name: /Go to previous page/i })).toBeEnabled();
73
73
  expect(screen.getByRole('button', { name: /Go to next page/i })).toBeDisabled();
74
74
  });
@@ -101,19 +101,6 @@ describe('SourcesCard', () => {
101
101
  expect(screen.getByRole('button', { name: /Go to next page/i })).toBeDisabled();
102
102
  });
103
103
 
104
- it('should change ofWord appropriately', () => {
105
- render(
106
- <SourcesCard
107
- sources={[
108
- { title: 'How to make an apple pie', link: '' },
109
- { title: 'How to make cookies', link: '' }
110
- ]}
111
- ofWord={'de'}
112
- />
113
- );
114
- expect(screen.getByText('1 de 2')).toBeTruthy();
115
- });
116
-
117
104
  it('should render navigation aria label appropriately', () => {
118
105
  render(
119
106
  <SourcesCard
@@ -230,4 +217,30 @@ describe('SourcesCard', () => {
230
217
  await userEvent.click(screen.getByRole('button', { name: /Go to previous page/i }));
231
218
  expect(spy).toHaveBeenCalledTimes(2);
232
219
  });
220
+
221
+ it('should handle showMore appropriately', async () => {
222
+ render(
223
+ <SourcesCard
224
+ sources={[
225
+ {
226
+ title: 'Getting started with Red Hat OpenShift',
227
+ link: '#',
228
+ body: 'Red Hat OpenShift on IBM Cloud is a managed offering to create your own cluster of compute hosts where you can deploy and manage containerized apps on IBM Cloud ...',
229
+ hasShowMore: true
230
+ },
231
+ {
232
+ title: 'Azure Red Hat OpenShift documentation',
233
+ link: '#',
234
+ body: 'Microsoft Azure Red Hat OpenShift allows you to deploy a production ready Red Hat OpenShift cluster in Azure ...'
235
+ },
236
+ {
237
+ title: 'OKD Documentation: Home',
238
+ link: '#',
239
+ body: 'OKD is a distribution of Kubernetes optimized for continuous application development and multi-tenant deployment. OKD also serves as the upstream code base upon ...'
240
+ }
241
+ ]}
242
+ />
243
+ );
244
+ expect(screen.getByRole('region')).toHaveAttribute('class', 'pf-v6-c-expandable-section__content');
245
+ });
233
246
  });
@@ -12,6 +12,8 @@ import {
12
12
  CardFooter,
13
13
  CardProps,
14
14
  CardTitle,
15
+ ExpandableSection,
16
+ ExpandableSectionVariant,
15
17
  Icon,
16
18
  pluralize,
17
19
  Truncate
@@ -23,12 +25,18 @@ export interface SourcesCardProps extends CardProps {
23
25
  className?: string;
24
26
  /** Flag indicating if the pagination is disabled. */
25
27
  isDisabled?: boolean;
26
- /** Label for the English word "of". */
28
+ /** @deprecated ofWord has been deprecated. Label for the English word "of." */
27
29
  ofWord?: string;
28
30
  /** Accessible label for the pagination component. */
29
31
  paginationAriaLabel?: string;
30
32
  /** Content rendered inside the paginated card */
31
- sources: { title?: string; link: string; body?: React.ReactNode | string; isExternal?: boolean }[];
33
+ sources: {
34
+ title?: string;
35
+ link: string;
36
+ body?: React.ReactNode | string;
37
+ isExternal?: boolean;
38
+ hasShowMore?: boolean;
39
+ }[];
32
40
  /** Label for the English word "source" */
33
41
  sourceWord?: string;
34
42
  /** Plural for sourceWord */
@@ -43,12 +51,15 @@ export interface SourcesCardProps extends CardProps {
43
51
  onPreviousClick?: (event: React.SyntheticEvent<HTMLButtonElement>, page: number) => void;
44
52
  /** Function called when page is changed. */
45
53
  onSetPage?: (event: React.MouseEvent | React.KeyboardEvent | MouseEvent, newPage: number) => void;
54
+ /** Label for English words "show more" */
55
+ showMoreWords?: string;
56
+ /** Label for English words "show less" */
57
+ showLessWords?: string;
46
58
  }
47
59
 
48
60
  const SourcesCard: React.FunctionComponent<SourcesCardProps> = ({
49
61
  className,
50
62
  isDisabled,
51
- ofWord = 'of',
52
63
  paginationAriaLabel = 'Pagination',
53
64
  sources,
54
65
  sourceWord = 'source',
@@ -58,9 +69,16 @@ const SourcesCard: React.FunctionComponent<SourcesCardProps> = ({
58
69
  onNextClick,
59
70
  onPreviousClick,
60
71
  onSetPage,
72
+ showMoreWords = 'show more',
73
+ showLessWords = 'show less',
61
74
  ...props
62
75
  }: SourcesCardProps) => {
63
76
  const [page, setPage] = React.useState(1);
77
+ const [isExpanded, setIsExpanded] = React.useState(false);
78
+
79
+ const onToggle = (_event: React.MouseEvent, isExpanded: boolean) => {
80
+ setIsExpanded(isExpanded);
81
+ };
64
82
 
65
83
  const handleNewPage = (_evt: React.MouseEvent | React.KeyboardEvent | MouseEvent, newPage: number) => {
66
84
  setPage(newPage);
@@ -93,10 +111,23 @@ const SourcesCard: React.FunctionComponent<SourcesCardProps> = ({
93
111
  </Button>
94
112
  </CardTitle>
95
113
  {sources[page - 1].body && (
96
- <CardBody
97
- className={`pf-chatbot__sources-card-body ${sources.length === 1 && 'pf-chatbot__sources-card-no-footer'}`}
98
- >
99
- {sources[page - 1].body}
114
+ <CardBody className={`pf-chatbot__sources-card-body`}>
115
+ {sources[page - 1].hasShowMore ? (
116
+ // prevents extra VO announcements of button text - parent Message has aria-live
117
+ <div aria-live="off">
118
+ <ExpandableSection
119
+ variant={ExpandableSectionVariant.truncate}
120
+ toggleText={isExpanded ? showLessWords : showMoreWords}
121
+ onToggle={onToggle}
122
+ isExpanded={isExpanded}
123
+ truncateMaxLines={2}
124
+ >
125
+ {sources[page - 1].body}
126
+ </ExpandableSection>
127
+ </div>
128
+ ) : (
129
+ <div className="pf-chatbot__sources-card-body-text">{sources[page - 1].body}</div>
130
+ )}
100
131
  </CardBody>
101
132
  )}
102
133
  {sources.length > 1 && (
@@ -129,6 +160,9 @@ const SourcesCard: React.FunctionComponent<SourcesCardProps> = ({
129
160
  </svg>
130
161
  </Icon>
131
162
  </Button>
163
+ <span aria-hidden="true">
164
+ {page}/{sources.length}
165
+ </span>
132
166
  <Button
133
167
  variant={ButtonVariant.plain}
134
168
  isDisabled={isDisabled || page === sources.length}
@@ -156,10 +190,6 @@ const SourcesCard: React.FunctionComponent<SourcesCardProps> = ({
156
190
  </Icon>
157
191
  </Button>
158
192
  </nav>
159
-
160
- <span aria-hidden="true">
161
- {page} {ofWord} {sources.length}
162
- </span>
163
193
  </div>
164
194
  </CardFooter>
165
195
  )}
@@ -1,30 +1,34 @@
1
- import { TrackingSpi } from './tracking_spi';
1
+ import { InitProps, TrackingSpi } from './tracking_spi';
2
2
  import { TrackingApi, TrackingEventProperties } from './tracking_api';
3
3
 
4
4
  export class ConsoleTrackingProvider implements TrackingSpi, TrackingApi {
5
+ private verbose = false;
5
6
  trackPageView(url: string | undefined) {
6
- // eslint-disable-next-line no-console
7
- console.log('ConsoleProvider pageView', url);
7
+ if (this.verbose) {
8
+ // eslint-disable-next-line no-console
9
+ console.log('ConsoleProvider pageView ', url);
10
+ }
8
11
  }
9
- // eslint-disable-next-line @typescript-eslint/no-empty-function
10
- registerProvider(): void {}
11
12
 
12
- initialize(): void {
13
- // eslint-disable-next-line no-console
14
- console.log('ConsoleProvider initialize');
13
+ initialize(props: InitProps): void {
14
+ this.verbose = props.verbose;
15
+ if (this.verbose) {
16
+ // eslint-disable-next-line no-console
17
+ console.log('ConsoleProvider initialize');
18
+ }
15
19
  }
16
20
 
17
- identify(userID: string): void {
18
- // eslint-disable-next-line no-console
19
- console.log('ConsoleProvider identify', userID);
21
+ identify(userID: string, userProperties: TrackingEventProperties = {}): void {
22
+ if (this.verbose) {
23
+ // eslint-disable-next-line no-console
24
+ console.log('ConsoleProvider identify ', userID, userProperties);
25
+ }
20
26
  }
21
27
 
22
28
  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
+ if (this.verbose) {
30
+ // eslint-disable-next-line no-console
31
+ console.log('ConsoleProvider: ' + item, properties);
32
+ }
29
33
  }
30
34
  }
@@ -4,13 +4,14 @@ import { TrackingApi, TrackingEventProperties } from './tracking_api';
4
4
  import { InitProps, TrackingSpi } from './tracking_spi';
5
5
 
6
6
  export class PosthogTrackingProvider implements TrackingSpi, TrackingApi {
7
- getKey(): string {
8
- return 'posthogKey';
9
- }
7
+ private verbose = false;
10
8
 
11
9
  initialize(props: InitProps): void {
12
- // eslint-disable-next-line no-console
13
- console.log('PosthogProvider initialize');
10
+ this.verbose = props.verbose;
11
+ if (this.verbose) {
12
+ // eslint-disable-next-line no-console
13
+ console.log('PosthogProvider initialize');
14
+ }
14
15
  const posthogKey = props.posthogKey as string;
15
16
 
16
17
  posthog.init(posthogKey, {
@@ -21,22 +22,28 @@ export class PosthogTrackingProvider implements TrackingSpi, TrackingApi {
21
22
  });
22
23
  }
23
24
 
24
- identify(userID: string): void {
25
- // eslint-disable-next-line no-console
26
- console.log('PosthogProvider userID: ' + userID);
27
- posthog.identify(userID);
25
+ identify(userID: string, userProperties: TrackingEventProperties = {}): void {
26
+ if (this.verbose) {
27
+ // eslint-disable-next-line no-console
28
+ console.log('PosthogProvider userID: ' + userID);
29
+ }
30
+ posthog.identify(userID, userProperties);
28
31
  }
29
32
 
30
33
  trackPageView(url: string | undefined): void {
31
- // eslint-disable-next-line no-console
32
- console.log('PostHogProvider url', url);
34
+ if (this.verbose) {
35
+ // eslint-disable-next-line no-console
36
+ console.log('PostHogProvider url ', url);
37
+ }
33
38
  // TODO posthog seems to record that automatically.
34
39
  // How to not clash with this here? Just leave as no-op?
35
40
  }
36
41
 
37
42
  trackSingleItem(item: string, properties?: TrackingEventProperties): void {
38
- // eslint-disable-next-line no-console
39
- console.log('PosthogProvider: trackSingleItem' + item, properties);
43
+ if (this.verbose) {
44
+ // eslint-disable-next-line no-console
45
+ console.log('PosthogProvider: trackSingleItem ' + item, properties);
46
+ }
40
47
  posthog.capture(item, { properties });
41
48
  }
42
49
  }