Skip to content

Commit 0220c91

Browse files
committed
Run groupActivities recursively
1 parent b5bb84c commit 0220c91

4 files changed

Lines changed: 138 additions & 15 deletions

File tree

__tests__/html2/grouping/customGrouping.html

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,24 @@
3939

4040
const { directLine, store } = testHelpers.createDirectLineEmulator({ ponyfill: clock });
4141

42-
const groupActivitiesMiddleware = () => next => request => ({
43-
...next(request),
44-
duo: request.activities.reduce((result, activity) => {
45-
if (result.at(-1)?.length === 1) {
46-
result.at(-1).push(activity);
47-
} else {
48-
result.push([activity]);
49-
}
42+
const requests = [];
43+
44+
const groupActivitiesMiddleware = () => next => request => {
45+
requests.push(request);
46+
47+
return {
48+
...next(request),
49+
duo: request.activities.reduce((result, activity) => {
50+
if (result.at(-1)?.length === 1) {
51+
result.at(-1).push(activity);
52+
} else {
53+
result.push([activity]);
54+
}
5055

51-
return result;
52-
}, [])
53-
});
56+
return result;
57+
}, [])
58+
};
59+
};
5460

5561
const decoratorMiddleware = [
5662
init =>
@@ -107,6 +113,17 @@
107113
}
108114

109115
await host.snapshot('local');
116+
117+
expect(requests.at(-3).activities).toHaveLength(3);
118+
expect(requests.at(-3).activities[0]).toEqual(expect.objectContaining({ text: 'Bot 1: t=0s' }));
119+
expect(requests.at(-3).activities[1]).toEqual(expect.objectContaining({ text: 'Bot 2: t=10s' }));
120+
expect(requests.at(-3).activities[2]).toEqual(expect.objectContaining({ text: 'Bot 3: t=20s' }));
121+
expect(requests.at(-2).activities[0]).toEqual(expect.objectContaining({ text: 'Bot 4: t=80s' }));
122+
expect(requests.at(-2).activities[1]).toEqual(expect.objectContaining({ text: 'Bot 5: t=90s' }));
123+
expect(requests.at(-2).activities[2]).toEqual(expect.objectContaining({ text: 'Bot 6: t=100s' }));
124+
expect(requests.at(-1).activities[0]).toEqual(expect.objectContaining({ text: 'Bot 7: t=160s' }));
125+
expect(requests.at(-1).activities[1]).toEqual(expect.objectContaining({ text: 'Bot 8: t=170s' }));
126+
expect(requests.at(-1).activities[2]).toEqual(expect.objectContaining({ text: 'Bot 9: t=180s' }));
110127
});
111128
</script>
112129
</body>

packages/api/src/providers/GroupActivities/useGroupActivities.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ import useGroupActivitiesContext from './private/useGroupActivitiesContext';
33

44
type GroupedActivities = readonly (readonly WebChatActivity[])[];
55

6-
/**
7-
* @deprecated
8-
*/
96
export default function useGroupActivities(): ({
107
activities
118
}: Readonly<{ activities: readonly WebChatActivity[] }>) => Readonly<{

packages/component/src/BasicTranscript.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import useFocus from './hooks/useFocus';
5353
import useStyleSet from './hooks/useStyleSet';
5454
import ChatHistoryDOMComposer from './providers/ChatHistoryDOM/ChatHistoryDOMComposer';
5555
import useActivityElementMapRef from './providers/ChatHistoryDOM/useActivityElementRef';
56-
import GroupedRenderingActivitiesComposer from './providers/GroupedRenderingActivities/GroupedRenderingActivitiesComposer';
56+
import GroupedRenderingActivitiesComposer from './providers/GroupedRenderingActivities/GroupedRenderingActivitiesComposer2';
5757
import useNumRenderingActivities from './providers/GroupedRenderingActivities/useNumRenderingActivities';
5858
import RenderingActivitiesComposer from './providers/RenderingActivities/RenderingActivitiesComposer';
5959
import TranscriptFocusComposer from './providers/TranscriptFocus/TranscriptFocusComposer';
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { hooks } from 'botframework-webchat-api';
2+
import { type WebChatActivity } from 'botframework-webchat-core';
3+
import React, { memo, useMemo } from 'react';
4+
import { object, optional, parse, pipe, readonly, type InferOutput } from 'valibot';
5+
6+
import reactNode from '../../types/internal/reactNode';
7+
import useRenderingActivities from '../RenderingActivities/useRenderingActivities';
8+
import { type GroupedRenderingActivities } from './GroupedRenderingActivities';
9+
import GroupedRenderingActivitiesContext, {
10+
type GroupedRenderingActivitiesContextType
11+
} from './private/GroupedRenderingActivitiesContext';
12+
13+
const { useGetKeyByActivity, useGroupActivities, useStyleOptions } = hooks;
14+
15+
const groupedRenderingActivitiesComposerPropsSchema = pipe(
16+
object({
17+
children: optional(reactNode())
18+
}),
19+
readonly()
20+
);
21+
22+
type GroupedRenderingActivitiesComposerProps = InferOutput<typeof groupedRenderingActivitiesComposerPropsSchema>;
23+
24+
function validateAllEntriesTagged<T>(entries: readonly T[], bins: readonly (readonly T[])[]): boolean {
25+
return entries.every(entry => bins.some(bin => bin.includes(entry)));
26+
}
27+
28+
const GroupedRenderingActivitiesComposer = (props: GroupedRenderingActivitiesComposerProps) => {
29+
const { children } = parse(groupedRenderingActivitiesComposerPropsSchema, props);
30+
31+
const [{ groupActivitiesBy }] = useStyleOptions();
32+
const [activities] = useRenderingActivities();
33+
const getKeyByActivity = useGetKeyByActivity();
34+
const groupActivities = useGroupActivities();
35+
36+
const numRenderingActivitiesState = useMemo<readonly [number]>(
37+
() => Object.freeze([activities.length] as const),
38+
[activities]
39+
);
40+
41+
const groupedRenderingActivitiesState = useMemo<readonly [readonly GroupedRenderingActivities[]]>(() => {
42+
const run = (
43+
activities: readonly WebChatActivity[],
44+
groups: readonly string[]
45+
): readonly GroupedRenderingActivities[] => {
46+
const [name, ...nextNames] = groups;
47+
48+
if (typeof name === 'undefined') {
49+
return Object.freeze([
50+
Object.freeze({
51+
activities,
52+
children: Object.freeze([]),
53+
key: getKeyByActivity(activities[0]),
54+
groupingName: undefined
55+
})
56+
]);
57+
}
58+
59+
const result = new Map(Object.entries(groupActivities({ activities })));
60+
61+
let groupings: readonly (readonly WebChatActivity[])[];
62+
63+
if (result.has(name)) {
64+
groupings = result.get(name);
65+
} else {
66+
console.warn(
67+
`botframework-webchat: styleOptions.groupActivitiesBy has "${name}" but groupActivitiesMiddleware does not return such result, assuming all activities are grouped by themselves`
68+
);
69+
70+
groupings = Object.freeze(activities.map(activity => Object.freeze([activity])));
71+
}
72+
73+
if (!validateAllEntriesTagged<WebChatActivity>(activities, groupings)) {
74+
`botframework-webchat: Not every activities are grouped in the "${name}" property. Please fix "groupActivitiesMiddleware" and group every activities`;
75+
}
76+
77+
return Object.freeze(
78+
groupings.map(grouping =>
79+
Object.freeze({
80+
activities: grouping,
81+
children: run(grouping, nextNames),
82+
groupingName: name,
83+
key: getKeyByActivity(grouping[0])
84+
} satisfies GroupedRenderingActivities)
85+
)
86+
);
87+
};
88+
89+
return Object.freeze([run(activities, groupActivitiesBy)]);
90+
}, [activities, getKeyByActivity, groupActivities, groupActivitiesBy]);
91+
92+
const context = useMemo<GroupedRenderingActivitiesContextType>(
93+
() =>
94+
Object.freeze({
95+
groupedRenderingActivitiesState,
96+
numRenderingActivitiesState
97+
}),
98+
[groupedRenderingActivitiesState, numRenderingActivitiesState]
99+
);
100+
101+
return (
102+
<GroupedRenderingActivitiesContext.Provider value={context}>{children}</GroupedRenderingActivitiesContext.Provider>
103+
);
104+
};
105+
106+
GroupedRenderingActivitiesComposer.displayName = 'GroupedRenderingActivitiesComposer';
107+
108+
export default memo(GroupedRenderingActivitiesComposer);
109+
export { type GroupedRenderingActivitiesComposerProps };

0 commit comments

Comments
 (0)