@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.
- package/dist/cjs/SourcesCard/SourcesCard.d.ts +6 -1
- package/dist/cjs/SourcesCard/SourcesCard.js +14 -9
- package/dist/cjs/SourcesCard/SourcesCard.test.js +25 -11
- package/dist/cjs/tracking/console_tracking_provider.d.ts +4 -5
- package/dist/cjs/tracking/console_tracking_provider.js +22 -15
- package/dist/cjs/tracking/posthog_tracking_provider.d.ts +2 -2
- package/dist/cjs/tracking/posthog_tracking_provider.js +21 -12
- package/dist/cjs/tracking/segment_tracking_provider.d.ts +2 -2
- package/dist/cjs/tracking/segment_tracking_provider.js +21 -12
- package/dist/cjs/tracking/trackingProviderProxy.d.ts +1 -1
- package/dist/cjs/tracking/trackingProviderProxy.js +2 -2
- package/dist/cjs/tracking/tracking_api.d.ts +1 -1
- package/dist/cjs/tracking/tracking_registry.js +46 -12
- package/dist/cjs/tracking/tracking_spi.d.ts +15 -5
- package/dist/cjs/tracking/tracking_spi.js +9 -0
- package/dist/cjs/tracking/umami_tracking_provider.d.ts +6 -2
- package/dist/cjs/tracking/umami_tracking_provider.js +66 -22
- package/dist/css/main.css +3 -7
- package/dist/css/main.css.map +1 -1
- package/dist/esm/SourcesCard/SourcesCard.d.ts +6 -1
- package/dist/esm/SourcesCard/SourcesCard.js +15 -10
- package/dist/esm/SourcesCard/SourcesCard.test.js +25 -11
- package/dist/esm/tracking/console_tracking_provider.d.ts +4 -5
- package/dist/esm/tracking/console_tracking_provider.js +22 -15
- package/dist/esm/tracking/posthog_tracking_provider.d.ts +2 -2
- package/dist/esm/tracking/posthog_tracking_provider.js +21 -12
- package/dist/esm/tracking/segment_tracking_provider.d.ts +2 -2
- package/dist/esm/tracking/segment_tracking_provider.js +21 -12
- package/dist/esm/tracking/trackingProviderProxy.d.ts +1 -1
- package/dist/esm/tracking/trackingProviderProxy.js +2 -2
- package/dist/esm/tracking/tracking_api.d.ts +1 -1
- package/dist/esm/tracking/tracking_registry.js +46 -12
- package/dist/esm/tracking/tracking_spi.d.ts +15 -5
- package/dist/esm/tracking/tracking_spi.js +8 -1
- package/dist/esm/tracking/umami_tracking_provider.d.ts +6 -2
- package/dist/esm/tracking/umami_tracking_provider.js +66 -22
- package/package.json +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Analytics/Analytics.md +18 -14
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithSources.tsx +34 -5
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.tsx +4 -3
- package/src/SourcesCard/SourcesCard.scss +3 -7
- package/src/SourcesCard/SourcesCard.test.tsx +30 -17
- package/src/SourcesCard/SourcesCard.tsx +41 -11
- package/src/tracking/console_tracking_provider.ts +21 -17
- package/src/tracking/posthog_tracking_provider.ts +20 -13
- package/src/tracking/segment_tracking_provider.ts +20 -13
- package/src/tracking/trackingProviderProxy.ts +2 -2
- package/src/tracking/tracking_api.ts +1 -1
- package/src/tracking/tracking_registry.ts +46 -13
- package/src/tracking/tracking_spi.ts +18 -7
- package/src/tracking/umami_tracking_provider.ts +76 -20
@@ -1,40 +1,84 @@
|
|
1
1
|
export class UmamiTrackingProvider {
|
2
|
-
|
3
|
-
|
2
|
+
constructor() {
|
3
|
+
this.verbose = false;
|
4
|
+
this.queue = [];
|
4
5
|
}
|
5
6
|
initialize(props) {
|
6
|
-
|
7
|
-
|
8
|
-
|
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',
|
16
|
-
script.setAttribute('data-
|
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-
|
19
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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.
|
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
|
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.
|
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="
|
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="
|
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="
|
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="
|
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="
|
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.
|
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
|
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.
|
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
|
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
|
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
|
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
|
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: {
|
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
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
7
|
-
|
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
|
-
|
14
|
-
|
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
|
-
|
19
|
-
|
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
|
-
|
24
|
-
|
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
|
-
|
8
|
-
return 'posthogKey';
|
9
|
-
}
|
7
|
+
private verbose = false;
|
10
8
|
|
11
9
|
initialize(props: InitProps): void {
|
12
|
-
|
13
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
32
|
-
|
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
|
-
|
39
|
-
|
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
|
}
|