Skip to content

Commit f8bf2cd

Browse files
committed
add feedback loop property to messages
1 parent 230358e commit f8bf2cd

7 files changed

Lines changed: 152 additions & 11 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<script crossorigin="anonymous" src="https://unpkg.com/@babel/standalone@7.8.7/babel.min.js"></script>
6+
<script crossorigin="anonymous" src="https://unpkg.com/react@16.8.6/umd/react.production.min.js"></script>
7+
<script crossorigin="anonymous" src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.production.min.js"></script>
8+
<script crossorigin="anonymous" src="/test-harness.js"></script>
9+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
10+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
11+
<script crossorigin="anonymous" src="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script>
12+
</head>
13+
14+
<body>
15+
<main id="webchat"></main>
16+
<script type="text/babel">
17+
run(async function () {
18+
const {
19+
React,
20+
ReactDOM: { render },
21+
WebChat: { FluentThemeProvider, ReactWebChat }
22+
} = window; // Imports in UMD fashion.
23+
24+
const { directLine, store } = testHelpers.createDirectLineEmulator();
25+
26+
const App = () => <ReactWebChat directLine={directLine} store={store} />;
27+
28+
render(
29+
<FluentThemeProvider>
30+
<App />
31+
</FluentThemeProvider>,
32+
document.getElementById('webchat')
33+
);
34+
35+
await pageConditions.uiConnected();
36+
37+
await directLine.emulateIncomingActivity({
38+
type: 'message',
39+
id: 'a-00000',
40+
timestamp: 0,
41+
text: 'This is a test message to show feedback buttons',
42+
from: {
43+
role: 'bot'
44+
},
45+
locale: 'en-US',
46+
entities: [],
47+
channelData: {
48+
feedbackLoop: {
49+
type: 'default'
50+
}
51+
}
52+
});
53+
54+
const { activity } = await directLine.actPostActivity(() => Promise.resolve({}));
55+
56+
expect(activity).toEqual(
57+
expect.objectContaining({
58+
name: 'webchat:activity-status/feedback',
59+
type: 'event',
60+
channelData: expect.objectContaining({
61+
feedbackLoop: { type: 'default' }
62+
})
63+
})
64+
);
65+
});
66+
</script>
67+
</body>
68+
</html>

packages/component/src/Activity/ActivityFeedback.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { hooks } from 'botframework-webchat-api';
22
import { getOrgSchemaMessage, OrgSchemaAction, parseAction, WebChatActivity } from 'botframework-webchat-core';
33
import cx from 'classnames';
44
import React, { memo, useMemo } from 'react';
5+
import { defaultFeedbackEntities } from './private/DefaultFeedbackEntities';
6+
import { isDefaultFeedbackActivity } from './private/isDefaultFeedbackActivity';
57

68
import Feedback from './private/Feedback';
79
import dereferenceBlankNodes from '../Utils/JSONLinkedData/dereferenceBlankNodes';
@@ -12,12 +14,27 @@ type ActivityFeedbackProps = Readonly<{
1214
activity: WebChatActivity;
1315
}>;
1416

17+
const parseActivity = (entities?: WebChatActivity['entities']) => {
18+
const graph = dereferenceBlankNodes(entities || []);
19+
const messageThing = getOrgSchemaMessage(graph);
20+
return { graph, messageThing };
21+
};
22+
23+
const useGetMessageThing = (activity: WebChatActivity) =>
24+
useMemo(() => {
25+
const { messageThing, graph } = parseActivity(activity.entities);
26+
if (messageThing?.potentialAction) {
27+
return { includeDefaultFeedback: false, messageThing, graph };
28+
} else if (isDefaultFeedbackActivity(activity)) {
29+
return { includeDefaultFeedback: true, ...parseActivity([defaultFeedbackEntities]) };
30+
}
31+
return { includeDefaultFeedback: false, ...parseActivity() };
32+
}, [activity]);
33+
1534
function ActivityFeedback({ activity }: ActivityFeedbackProps) {
1635
const [{ feedbackActionsPlacement }] = useStyleOptions();
1736

18-
const graph = useMemo(() => dereferenceBlankNodes(activity.entities || []), [activity.entities]);
19-
20-
const messageThing = useMemo(() => getOrgSchemaMessage(graph), [graph]);
37+
const { messageThing, graph, includeDefaultFeedback } = useGetMessageThing(activity);
2138

2239
const feedbackActions = useMemo<ReadonlySet<OrgSchemaAction>>(() => {
2340
try {
@@ -46,6 +63,7 @@ function ActivityFeedback({ activity }: ActivityFeedbackProps) {
4663
className={cx({
4764
'webchat__thumb-button--large': feedbackActionsPlacement === 'activity-actions'
4865
})}
66+
includeDefaultFeedback={includeDefaultFeedback}
4967
/>
5068
);
5169
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export const defaultFeedbackEntities = {
2+
'@context': 'https://schema.org',
3+
'@id': '',
4+
'@type': 'Message',
5+
type: 'https://schema.org/Message',
6+
keywords: [],
7+
potentialAction: [
8+
{
9+
'@type': 'LikeAction',
10+
actionStatus: 'PotentialActionStatus'
11+
},
12+
{
13+
'@type': 'DislikeAction',
14+
actionStatus: 'PotentialActionStatus'
15+
}
16+
]
17+
};

packages/component/src/Activity/private/Feedback.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ type Props = Readonly<
1111
PropsWithChildren<{
1212
actions: ReadonlySet<OrgSchemaAction>;
1313
className?: string | undefined;
14+
includeDefaultFeedback?: boolean;
1415
}>
1516
>;
1617

1718
const DEBOUNCE_TIMEOUT = 500;
1819

19-
const Feedback = memo(({ actions, className }: Props) => {
20+
const Feedback = memo(({ actions, className, includeDefaultFeedback }: Props) => {
2021
const [{ clearTimeout, setTimeout }] = usePonyfill();
2122
const [selectedAction, setSelectedAction] = useState<OrgSchemaAction | undefined>();
2223
const postActivity = usePostActivity();
@@ -36,15 +37,19 @@ const Feedback = memo(({ actions, className }: Props) => {
3637
entities: [selectedAction],
3738
name: 'webchat:activity-status/feedback',
3839
type: 'event',
39-
channelData: {
40-
feedbackLoop: { type: 'default' }
41-
}
40+
...(includeDefaultFeedback
41+
? {
42+
channelData: {
43+
feedbackLoop: { type: 'default' }
44+
}
45+
}
46+
: {})
4247
} as any),
4348
DEBOUNCE_TIMEOUT
4449
);
4550

4651
return () => clearTimeout(timeout);
47-
}, [clearTimeout, postActivityRef, selectedAction, setTimeout]);
52+
}, [clearTimeout, includeDefaultFeedback, postActivityRef, selectedAction, setTimeout]);
4853

4954
const actionProps = useMemo(
5055
() =>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { type WebChatActivity } from 'botframework-webchat-core';
2+
3+
type ActivityType = 'event' | 'message';
4+
5+
type FeedbackActivity<T extends ActivityType> = WebChatActivity & {
6+
type: T;
7+
channelData: {
8+
feedbackLoop: {
9+
type: 'default';
10+
};
11+
};
12+
};
13+
14+
export const isDefaultFeedbackActivity = (activity: WebChatActivity): activity is FeedbackActivity<ActivityType> => {
15+
if (
16+
(activity.type === 'message' || activity.type === 'event') &&
17+
activity.channelData.feedbackLoop?.type === 'default'
18+
) {
19+
return true;
20+
}
21+
22+
return false;
23+
};

packages/core/src/types/WebChatActivity.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ type ChannelData<SendStatus extends SupportedSendStatus | undefined, Type extend
112112

113113
// TODO: [P2] #3953 It seems Direct Line added a new "summary" field to cater this case.
114114
'webchat:fallback-text'?: string;
115+
116+
feedbackLoop?: {
117+
type: 'default';
118+
};
115119
}
116120
: Record<any, any>)
117121
>;

packages/fluent-theme/src/components/activity/FeedbackForm.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { memo, useState, useMemo, useCallback } from 'react';
22
import { hooks } from 'botframework-webchat-component';
33
import { useStyles } from '../../styles';
44
import styles from './FeedbackForm.module.css';
5-
import { type WebChatActivity, parseThing } from 'botframework-webchat-core';
5+
import { type WebChatActivity, parseThing, markActivity } from 'botframework-webchat-core';
66
import StackedLayout from './private/StackedLayout';
77
import TextArea from '../sendBox/TextArea';
88
import cx from 'classnames';
@@ -45,15 +45,21 @@ function FeedbackForm({ activity }: Readonly<{ activity: WebChatActivity }>) {
4545
}
4646
}
4747
} as any);
48+
if (activity.id) {
49+
markActivity({ id: activity.id }, 'feedbackActionClicked', true);
50+
}
4851
setFeedback('');
4952
}
5053
},
51-
[feedback, postActivity, reactionType]
54+
[activity.id, feedback, postActivity, reactionType]
5255
);
5356

5457
const handleCancel = useCallback(() => {
5558
setFeedback('');
56-
}, []);
59+
if (activity.id) {
60+
markActivity({ id: activity.id }, 'feedbackActionClicked', true);
61+
}
62+
}, [activity.id]);
5763

5864
const handleChange = useCallback(
5965
event => {

0 commit comments

Comments
 (0)