Skip to content

Commit 90b5129

Browse files
committed
change source of truth to feedbackactions
1 parent e02202f commit 90b5129

3 files changed

Lines changed: 80 additions & 72 deletions

File tree

packages/component/src/Activity/ActivityFeedback.tsx

Lines changed: 70 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import { hooks } from 'botframework-webchat-api';
22
import { getOrgSchemaMessage, OrgSchemaAction, parseAction, WebChatActivity } from 'botframework-webchat-core';
33
import classNames from 'classnames';
4-
import React, { memo, useCallback, useMemo, useState } from 'react';
4+
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
55

66
import useStyleSet from '../hooks/useStyleSet';
77
import dereferenceBlankNodes from '../Utils/JSONLinkedData/dereferenceBlankNodes';
88
import Feedback from './private/Feedback';
99
import FeedbackForm from './private/FeedbackForm';
1010
import getDisclaimer from './private/getDisclaimer';
1111
import hasFeedbackLoop from './private/hasFeedbackLoop';
12-
import { useRefFrom } from 'use-ref-from';
1312

1413
const { useStyleOptions } = hooks;
1514

@@ -43,18 +42,7 @@ const defaultFeedbackEntities = {
4342
]
4443
};
4544

46-
function ActivityFeedback({ activity }: ActivityFeedbackProps) {
47-
const [{ feedbackActionsPlacement }] = useStyleOptions();
48-
const [{ feedbackForm }] = useStyleSet();
49-
50-
const [selectedAction, setSelectedAction] = useState<OrgSchemaAction | undefined>();
51-
const selectedActionRef = useRefFrom(selectedAction);
52-
const [feedbackSubmitted, setFeedbackSubmitted] = useState<boolean>(false);
53-
const feedbackSubmittedRef = useRefFrom(feedbackSubmitted);
54-
const submittedAction = feedbackSubmitted ? selectedAction : undefined;
55-
56-
const isFeedbackLoopSupported = hasFeedbackLoop(activity);
57-
45+
const useFeedbackActions = (activity: WebChatActivity, isFeedbackLoopSupported: boolean) => {
5846
const { graph, messageThing } = useMemo(() => {
5947
if (isFeedbackLoopSupported) {
6048
return parseActivity([defaultFeedbackEntities]);
@@ -63,87 +51,113 @@ function ActivityFeedback({ activity }: ActivityFeedbackProps) {
6351
return parseActivity(activity.entities);
6452
}, [activity.entities, isFeedbackLoopSupported]);
6553

66-
const feedbackActions = useMemo<ReadonlySet<OrgSchemaAction>>(() => {
54+
const feedbackActions = useMemo<OrgSchemaAction[]>(() => {
6755
try {
68-
const reactActions = (messageThing?.potentialAction || [])
69-
.filter(({ '@type': type }) => type === 'LikeAction' || type === 'DislikeAction')
70-
.map(action => (submittedAction && action['@type'] === submittedAction['@type'] ? submittedAction : action));
56+
const reactActions = (messageThing?.potentialAction || []).filter(
57+
({ '@type': type }) => type === 'LikeAction' || type === 'DislikeAction'
58+
);
7159

7260
if (reactActions.length) {
73-
return Object.freeze(new Set(reactActions));
61+
return reactActions;
7462
}
7563

7664
const voteActions = graph.filter(({ type }) => type === 'https://schema.org/VoteAction').map(parseAction);
7765

7866
if (voteActions.length) {
79-
return Object.freeze(new Set(voteActions));
67+
return voteActions;
8068
}
8169
} catch {
8270
// Intentionally left blank.
8371
}
84-
return Object.freeze(new Set([] as OrgSchemaAction[]));
85-
}, [graph, messageThing, submittedAction]);
72+
return [] as OrgSchemaAction[];
73+
}, [graph, messageThing]);
8674

87-
const handleFeedbackActionClick = useCallback(
88-
(action: OrgSchemaAction) => setSelectedAction(action === selectedAction ? undefined : action),
89-
[selectedAction, setSelectedAction]
90-
);
75+
const [currentFeedbackActions, setCurrentFeedbackActions] = useState(feedbackActions);
9176

92-
const handleFeedbackFormReset = useCallback(() => {
93-
if (feedbackSubmittedRef.current) {
94-
return;
95-
}
77+
// Handle feedback actions update via incoming activity
78+
useEffect(() => {
79+
setCurrentFeedbackActions(feedbackActions);
80+
}, [feedbackActions]);
9681

97-
setSelectedAction(undefined);
98-
setFeedbackSubmitted(false);
99-
}, [feedbackSubmittedRef]);
82+
return { currentFeedbackActions, setCurrentFeedbackActions };
83+
};
10084

101-
const handleFeedbackFormSubmit = useCallback(() => {
102-
if (feedbackSubmittedRef.current || !selectedActionRef.current) {
103-
return;
104-
}
85+
function ActivityFeedback({ activity }: ActivityFeedbackProps) {
86+
const [{ feedbackActionsPlacement }] = useStyleOptions();
87+
const [{ feedbackForm }] = useStyleSet();
88+
89+
const isFeedbackLoopSupported = hasFeedbackLoop(activity);
90+
91+
const { currentFeedbackActions: feedbackActions, setCurrentFeedbackActions } = useFeedbackActions(
92+
activity,
93+
isFeedbackLoopSupported
94+
);
95+
96+
const selectedAction = feedbackActions.find(
97+
action => action.actionStatus === 'CompletedActionStatus' || action.actionStatus === 'ActiveActionStatus'
98+
);
10599

106-
setSelectedAction({
107-
...selectedActionRef.current,
108-
actionStatus: 'CompletedActionStatus' as const
109-
});
110-
setFeedbackSubmitted(true);
111-
}, [feedbackSubmittedRef, selectedActionRef]);
100+
const handleFeedbackActionClick = useCallback(
101+
(action: OrgSchemaAction) => {
102+
const newActions: OrgSchemaAction[] = feedbackActions.map(feedbackAction => {
103+
// reset all actions to potential action status (unclicked the active action)
104+
if (action.actionStatus === 'ActiveActionStatus') {
105+
return {
106+
...feedbackAction,
107+
actionStatus: 'PotentialActionStatus'
108+
};
109+
}
110+
111+
return {
112+
...feedbackAction,
113+
actionStatus: feedbackAction === action ? 'ActiveActionStatus' : 'PotentialActionStatus'
114+
};
115+
});
116+
setCurrentFeedbackActions(newActions);
117+
},
118+
[feedbackActions, setCurrentFeedbackActions]
119+
);
120+
121+
const handleFeedbackFormClick = useCallback(
122+
(isSubmitted = false) => {
123+
const newActions: OrgSchemaAction[] = feedbackActions.map(action => ({
124+
...action,
125+
actionStatus:
126+
action.actionStatus === 'ActiveActionStatus' && isSubmitted
127+
? 'CompletedActionStatus'
128+
: 'PotentialActionStatus'
129+
}));
130+
setCurrentFeedbackActions(newActions);
131+
},
132+
[feedbackActions, setCurrentFeedbackActions]
133+
);
112134

113135
const FeedbackComponent = useMemo(
114136
() => (
115137
<Feedback
116138
actions={feedbackActions}
117139
className={classNames({
118140
'webchat__thumb-button--large': feedbackActionsPlacement === 'activity-actions',
119-
'webchat__thumb-button--submitted': feedbackSubmitted
141+
'webchat__thumb-button--submitted': selectedAction?.actionStatus === 'CompletedActionStatus'
120142
})}
121143
isFeedbackFormSupported={isFeedbackLoopSupported}
122144
onActionClick={handleFeedbackActionClick}
123145
selectedAction={selectedAction}
124146
/>
125147
),
126-
[
127-
feedbackActions,
128-
feedbackActionsPlacement,
129-
feedbackSubmitted,
130-
handleFeedbackActionClick,
131-
isFeedbackLoopSupported,
132-
selectedAction
133-
]
148+
[feedbackActions, feedbackActionsPlacement, handleFeedbackActionClick, isFeedbackLoopSupported, selectedAction]
134149
);
135150

136151
const FeedbackFormComponent = useMemo(
137152
() => (
138153
<FeedbackForm
139154
disclaimer={getDisclaimer(activity)}
140155
feedbackType={selectedAction?.['@type']}
141-
onReset={handleFeedbackFormReset}
142-
onSubmit={handleFeedbackFormSubmit}
156+
onFeedbackFormButtonClick={handleFeedbackFormClick}
143157
replyToId={activity.id}
144158
/>
145159
),
146-
[activity, handleFeedbackFormReset, handleFeedbackFormSubmit, selectedAction]
160+
[activity, handleFeedbackFormClick, selectedAction]
147161
);
148162

149163
if (feedbackActionsPlacement === 'activity-actions' && isFeedbackLoopSupported) {
@@ -153,7 +167,7 @@ function ActivityFeedback({ activity }: ActivityFeedbackProps) {
153167
{FeedbackComponent}
154168
</div>
155169
{/* Hide feedback form if feedback has already been submitted */}
156-
{!feedbackSubmitted && selectedAction && selectedAction['@type'] && FeedbackFormComponent}
170+
{selectedAction?.['@type'] && selectedAction?.actionStatus === 'ActiveActionStatus' && FeedbackFormComponent}
157171
</div>
158172
);
159173
}

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const { usePonyfill, usePostActivity, useLocalizer } = hooks;
99

1010
type Props = Readonly<
1111
PropsWithChildren<{
12-
actions: ReadonlySet<OrgSchemaAction>;
12+
actions: OrgSchemaAction[];
1313
className?: string | undefined;
1414
isFeedbackFormSupported?: boolean;
1515
onActionClick?: (action: OrgSchemaAction) => void;
@@ -58,17 +58,13 @@ const Feedback = memo(({ actions, className, isFeedbackFormSupported, onActionCl
5858

5959
return (
6060
<Fragment>
61-
{[...actions].map((action, index) => (
61+
{actions.map((action, index) => (
6262
<FeedbackVoteButton
6363
action={action}
6464
className={className}
6565
key={action['@id'] || index}
6666
onClick={onActionClick}
67-
pressed={
68-
selectedAction === action ||
69-
action.actionStatus === 'CompletedActionStatus' ||
70-
action.actionStatus === 'ActiveActionStatus'
71-
}
67+
pressed={action.actionStatus === 'CompletedActionStatus' || action.actionStatus === 'ActiveActionStatus'}
7268
{...actionProps}
7369
/>
7470
))}

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

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,22 @@ const { useLocalizer, usePostActivity } = hooks;
1313
type FeedbackFormProps = Readonly<{
1414
disclaimer?: string;
1515
feedbackType: string;
16-
onReset: () => void;
17-
onSubmit: () => void;
16+
onFeedbackFormButtonClick: (isSubmitted?: boolean) => void;
1817
replyToId?: string;
1918
}>;
2019

21-
function FeedbackForm({ feedbackType, disclaimer, onReset, onSubmit, replyToId }: FeedbackFormProps) {
20+
function FeedbackForm({ feedbackType, disclaimer, onFeedbackFormButtonClick, replyToId }: FeedbackFormProps) {
2221
const [{ feedbackForm }] = useStyleSet();
2322
const [hasFocus, setHasFocus] = useState(false);
2423
const [userFeedback, setUserFeedback] = useState('');
2524
const feedbackTextAreaRef = useRef<HTMLTextAreaElement>(null);
2625
const localize = useLocalizer();
27-
const onResetRef = useRefFrom(onReset);
28-
const onSubmitRef = useRefFrom(onSubmit);
26+
const onFeedbackFormButtonClickRef = useRefFrom(onFeedbackFormButtonClick);
2927
const postActivity = usePostActivity();
3028

3129
const handleCancel = useCallback(() => {
32-
onResetRef.current();
33-
}, [onResetRef]);
30+
onFeedbackFormButtonClickRef.current();
31+
}, [onFeedbackFormButtonClickRef]);
3432

3533
const handleSubmit = useCallback(
3634
event => {
@@ -49,9 +47,9 @@ function FeedbackForm({ feedbackType, disclaimer, onReset, onSubmit, replyToId }
4947
}
5048
} as any);
5149

52-
onSubmitRef.current();
50+
onFeedbackFormButtonClickRef.current(true);
5351
},
54-
[feedbackType, onSubmitRef, postActivity, replyToId, userFeedback]
52+
[feedbackType, onFeedbackFormButtonClickRef, postActivity, replyToId, userFeedback]
5553
);
5654

5755
const handleChange: FormEventHandler<HTMLTextAreaElement> = useCallback(

0 commit comments

Comments
 (0)