Skip to content

Commit abea4be

Browse files
author
=
committed
Merge branch 'temp'
2 parents 93043a9 + 1e89aeb commit abea4be

4 files changed

Lines changed: 272 additions & 62 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/
107107
- Added `disableFileUpload` flag to completelly disable file upload feature, in PR [#5508](https://github.com/microsoft/BotFramework-WebChat/pull/5508), by [@JamesNewbyAtMicrosoft](https://github.com/JamesNewbyAtMicrosoft)
108108
- Deprecated `hideUploadButton` in favor of `disableFileUpload`.
109109
- Updated `BasicSendBoxToolbar` to rely solely on `disableFileUpload`.
110+
- Added livestreaming support for livestreaming data in `entities` in PR [#5517](https://github.com/microsoft/BotFramework-WebChat/pull/5517) by [@kylerohn](https://github.com/kylerohn)
110111

111112
### Changed
112113

docs/LIVESTREAMING.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ To simplify this documentation, we are using the term "bot" instead of "copilot"
6464

6565
Bot developers would need to implement the livestreaming as outlined in this section. The implementation below will enable livestreaming to both Azure Bot Services and Teams.
6666

67+
> [!NOTE]
68+
> In the scenarios below, the livestream metadata is inside the `channelData` field. BotFramework-WebChat checks both `channelData` and the first element of the `entities` field for livestreaming metadata. It will appear in different places depending on the platform used to communicate with BotFramework-WebChat
69+
6770
### Scenario 1: Livestream from start to end
6871

6972
> In this example, we assume the bot is livestreaming the following sentence to the user:

packages/core/src/utils/getActivityLivestreamingMetadata.spec.ts

Lines changed: 169 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { WebChatActivity } from '../types/WebChatActivity';
22
import getActivityLivestreamingMetadata from './getActivityLivestreamingMetadata';
33

44
describe.each([['with "streamId"' as const], ['without "streamId"' as const]])('activity %s', variant => {
5-
describe('activity with "streamType" of "streaming"', () => {
5+
describe('activity with "streamType" of "streaming" (channelData)', () => {
66
let activity: WebChatActivity;
77

88
beforeEach(() => {
@@ -32,7 +32,7 @@ describe.each([['with "streamId"' as const], ['without "streamId"' as const]])('
3232
}
3333
});
3434

35-
describe('activity with "streamType" of "informative message"', () => {
35+
describe('activity with "streamType" of "informative message" (channelData)', () => {
3636
let activity: WebChatActivity;
3737

3838
beforeEach(() => {
@@ -62,7 +62,7 @@ describe.each([['with "streamId"' as const], ['without "streamId"' as const]])('
6262
}
6363
});
6464

65-
describe('activity with "streamType" of "final"', () => {
65+
describe('activity with "streamType" of "final" (channelData)', () => {
6666
let activity: WebChatActivity;
6767

6868
beforeEach(() => {
@@ -91,22 +91,130 @@ describe.each([['with "streamId"' as const], ['without "streamId"' as const]])('
9191
});
9292
});
9393

94+
describe.each([['with "streamId"' as const], ['without "streamId"' as const]])('activity %s', variant => {
95+
describe('activity with "streamType" of "streaming" (entities)', () => {
96+
let activity: WebChatActivity;
97+
98+
beforeEach(() => {
99+
activity = {
100+
entities: [
101+
{
102+
...(variant === 'with "streamId"' ? { streamId: 'a-00001' } : {}),
103+
streamSequence: 1,
104+
streamType: 'streaming'
105+
}
106+
],
107+
channelData: {},
108+
id: 'a-00002',
109+
text: 'Hello, World!',
110+
type: 'typing'
111+
} as any;
112+
});
113+
114+
test('should return type of "interim activity"', () =>
115+
expect(getActivityLivestreamingMetadata(activity)).toHaveProperty('type', 'interim activity'));
116+
test('should return sequence number', () =>
117+
expect(getActivityLivestreamingMetadata(activity)).toHaveProperty('sequenceNumber', 1));
118+
119+
if (variant === 'with "streamId"') {
120+
test('should return session ID with value from "entities.streamId"', () =>
121+
expect(getActivityLivestreamingMetadata(activity)).toHaveProperty('sessionId', 'a-00001'));
122+
} else {
123+
test('should return session ID with value of "activity.id"', () =>
124+
expect(getActivityLivestreamingMetadata(activity)).toHaveProperty('sessionId', 'a-00002'));
125+
}
126+
});
127+
128+
describe('activity with "streamType" of "informative message" (entities)', () => {
129+
let activity: WebChatActivity;
130+
131+
beforeEach(() => {
132+
activity = {
133+
entities: [
134+
{
135+
...(variant === 'with "streamId"' ? { streamId: 'a-00001' } : {}),
136+
streamSequence: 1,
137+
streamType: 'informative'
138+
}
139+
],
140+
channelData: {},
141+
id: 'a-00002',
142+
text: 'Hello, World!',
143+
type: 'typing'
144+
} as any;
145+
});
146+
147+
test('should return type of "informative message"', () =>
148+
expect(getActivityLivestreamingMetadata(activity)).toHaveProperty('type', 'informative message'));
149+
test('should return sequence number', () =>
150+
expect(getActivityLivestreamingMetadata(activity)).toHaveProperty('sequenceNumber', 1));
151+
152+
if (variant === 'with "streamId"') {
153+
test('should return session ID with value from "entities.streamId"', () =>
154+
expect(getActivityLivestreamingMetadata(activity)).toHaveProperty('sessionId', 'a-00001'));
155+
} else {
156+
test('should return session ID with value of "activity.id"', () =>
157+
expect(getActivityLivestreamingMetadata(activity)).toHaveProperty('sessionId', 'a-00002'));
158+
}
159+
});
160+
161+
describe('activity with "streamType" of "final" (entities)', () => {
162+
let activity: WebChatActivity;
163+
164+
beforeEach(() => {
165+
activity = {
166+
entities: [
167+
{
168+
...(variant === 'with "streamId"' ? { streamId: 'a-00001' } : {}),
169+
streamType: 'final'
170+
}
171+
],
172+
channelData: {},
173+
id: 'a-00002',
174+
text: 'Hello, World!',
175+
type: 'message'
176+
} as any;
177+
});
178+
179+
if (variant === 'with "streamId"') {
180+
test('should return type of "final activity"', () =>
181+
expect(getActivityLivestreamingMetadata(activity)).toHaveProperty('type', 'final activity'));
182+
test('should return sequence number of Infinity', () =>
183+
expect(getActivityLivestreamingMetadata(activity)).toHaveProperty('sequenceNumber', Infinity));
184+
test('should return session ID', () =>
185+
expect(getActivityLivestreamingMetadata(activity)).toHaveProperty('sessionId', 'a-00001'));
186+
} else {
187+
// Final activity must have "streamId". Final activity without "streamId" is not a valid livestream activity.
188+
test('should return undefined', () => expect(getActivityLivestreamingMetadata(activity)).toBeUndefined());
189+
}
190+
});
191+
});
192+
94193
test('invalid activity should return undefined', () =>
95194
expect(getActivityLivestreamingMetadata('invalid' as any)).toBeUndefined());
96195

97-
test('activity with "streamType" of "streaming" without critical fields should return undefined', () =>
196+
test('activity with "streamType" of "streaming" without critical fields should return undefined (channelData)', () =>
98197
expect(
99198
getActivityLivestreamingMetadata({
100199
channelData: { streamType: 'streaming' },
101200
type: 'typing'
102201
} as any)
103202
).toBeUndefined());
104203

204+
test('activity with "streamType" of "streaming" without critical fields should return undefined (entities)', () =>
205+
expect(
206+
getActivityLivestreamingMetadata({
207+
entities: [{ streamType: 'streaming' }],
208+
channelData: {},
209+
type: 'typing'
210+
} as any)
211+
).toBeUndefined());
212+
105213
describe.each([
106214
['integer', 1, true],
107215
['zero', 0, false],
108216
['decimal', 1.234, false]
109-
])('activity with %s "streamSequence" should return undefined', (_, streamSequence, isValid) => {
217+
])('activity with %s "streamSequence" should return undefined (channelData)', (_, streamSequence, isValid) => {
110218
const activity = {
111219
channelData: { streamSequence, streamType: 'streaming' },
112220
id: 'a-00001',
@@ -121,7 +229,27 @@ describe.each([
121229
}
122230
});
123231

124-
describe('"typing" activity with "streamType" of "final"', () => {
232+
describe.each([
233+
['integer', 1, true],
234+
['zero', 0, false],
235+
['decimal', 1.234, false]
236+
])('activity with %s "streamSequence" should return undefined (entities)', (_, streamSequence, isValid) => {
237+
const activity = {
238+
entities: [{ streamSequence, streamType: 'streaming' }],
239+
channelData: {},
240+
id: 'a-00001',
241+
text: '',
242+
type: 'typing'
243+
} as any;
244+
245+
if (isValid) {
246+
expect(getActivityLivestreamingMetadata(activity)).toBeTruthy();
247+
} else {
248+
expect(getActivityLivestreamingMetadata(activity)).toBeUndefined();
249+
}
250+
});
251+
252+
describe('"typing" activity with "streamType" of "final" (channelData)', () => {
125253
test('should return undefined if "text" field is defined', () =>
126254
expect(
127255
getActivityLivestreamingMetadata({
@@ -143,11 +271,45 @@ describe('"typing" activity with "streamType" of "final"', () => {
143271
).toHaveProperty('type', 'final activity'));
144272
});
145273

146-
test('activity with "streamType" of "streaming" without "content" should return type of "contentless"', () =>
274+
describe('"typing" activity with "streamType" of "final" (entities)', () => {
275+
test('should return undefined if "text" field is defined', () =>
276+
expect(
277+
getActivityLivestreamingMetadata({
278+
entities: [{ streamId: 'a-00001', streamType: 'final' }],
279+
channelData: {},
280+
id: 'a-00002',
281+
text: 'Final "typing" activity, must not have "text".',
282+
type: 'typing'
283+
} as any)
284+
).toBeUndefined());
285+
286+
test('should return truthy if "text" field is not defined', () =>
287+
expect(
288+
getActivityLivestreamingMetadata({
289+
entities: [{ streamId: 'a-00001', streamType: 'final' }],
290+
channelData: {},
291+
id: 'a-00002',
292+
// Final activity can be "typing" if it does not have "text".
293+
type: 'typing'
294+
} as any)
295+
).toHaveProperty('type', 'final activity'));
296+
});
297+
298+
test('activity with "streamType" of "streaming" without "content" should return type of "contentless" (channelData)', () =>
147299
expect(
148300
getActivityLivestreamingMetadata({
149301
channelData: { streamSequence: 1, streamType: 'streaming' },
150302
id: 'a-00001',
151303
type: 'typing'
152304
} as any)
153305
).toHaveProperty('type', 'contentless'));
306+
307+
test('activity with "streamType" of "streaming" without "content" should return type of "contentless" (entities)', () =>
308+
expect(
309+
getActivityLivestreamingMetadata({
310+
entities: [{ streamSequence: 1, streamType: 'streaming' }],
311+
channelData: {},
312+
id: 'a-00001',
313+
type: 'typing'
314+
} as any)
315+
).toHaveProperty('type', 'contentless'));

0 commit comments

Comments
 (0)