Skip to content
29 changes: 15 additions & 14 deletions libraries/botbuilder-core/src/transcriptLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,24 @@ export class TranscriptLoggerMiddleware implements Middleware {

activities.map((a: Partial<Activity>, index: number) => {
const clonedActivity = this.cloneActivity(a);
// If present, set the id of the cloned activity to the id received from the server.
if (index < responses.length) {
if (!clonedActivity.id) {
clonedActivity.id = responses[index].id;

// For certain channels, a ResourceResponse with an id is not always sent to the bot.
// This fix uses the timestamp on the activity to populate its id for logging the transcript.
// If there is no outgoing timestamp, the current time for the bot is used for the activity.id.
// See https://github.com/microsoft/botbuilder-js/issues/1122
if (!clonedActivity.id) {
if (clonedActivity.timestamp) {
clonedActivity.id = new Date(clonedActivity.timestamp).getTime().toString();
} else {
clonedActivity.id = Date.now().toString();
}
}
clonedActivity.id = responses[index].id;
}

// For certain channels, a ResourceResponse with an id is not always sent to the bot.
// This fix uses the timestamp on the activity to populate its id for logging the transcript.
// If there is no outgoing timestamp, the current time for the bot is used for the activity.id.
// See https://github.com/microsoft/botbuilder-js/issues/1122
if (!clonedActivity.id) {
const prefix = `g_${Math.random().toString(36).slice(2,8)}`;
if (clonedActivity.timestamp) {
clonedActivity.id = `${prefix}${new Date(clonedActivity.timestamp).getTime().toString()}`;
} else {
clonedActivity.id = `${prefix}${new Date().getTime().toString()}`;
}
}

this.logActivity(transcript, clonedActivity);
});

Expand Down
2 changes: 1 addition & 1 deletion libraries/botbuilder-core/src/turnContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ export class TurnContext {
const o: Partial<Activity> = TurnContext.applyConversationReference({...a}, ref);
if (!o.type) { o.type = ActivityTypes.Message; }
if (o.type !== ActivityTypes.Trace) { sentNonTraceActivity = true; }

if (o.id) { delete o.id; }
return o;
});

Expand Down
9 changes: 6 additions & 3 deletions libraries/botbuilder-core/tests/transcriptMiddleware.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,13 @@ describe(`TranscriptLoggerMiddleware`, function () {
// sent activities do not contain the id returned from the service, so it should be undefined here
.assertReply(activity => assert.equal(activity.id, undefined) && assert.equal(activity.text, 'echo:foo'))
.send('bar')
.assertReply(activity => assert.equal(activity.id, undefined) && assert.equal(activity.text, 'echo:bar'))
.assertReply(activity => assert.equal(activity.id, undefined) && assert.equal(activity.text, 'echo:bar'))
.then(() => {
transcriptStore.getTranscriptActivities('test', conversationId).then(pagedResult => {
assert.equal(pagedResult.items.length, 4);
assert.equal(pagedResult.items[0].text, 'foo');
// Transcript activities should have the id present on the activity when received
assert.equal(pagedResult.items[0].id, 'testFooId');

assert.equal(pagedResult.items[1].text, 'echo:foo');
// Sent Activities in the transcript store should have the Id returned from Resource Response
// (the test adapter increments a number and uses this for the id)
Expand All @@ -238,6 +237,7 @@ describe(`TranscriptLoggerMiddleware`, function () {
assert.equal(pagedResult.items[3].id, '2');
pagedResult.items.forEach(a => {
assert(a.timestamp);
assert(a.id);
})
done();
});
Expand Down Expand Up @@ -286,7 +286,10 @@ describe(`TranscriptLoggerMiddleware`, function () {
// 1. The outgoing Activity.id is falsey
// 2. The ResourceResponse.id is falsey (some channels may not emit a ResourceResponse with an id value)
// 3. The outgoing Activity.timestamp exists
assert.equal(pagedResult.items[1].id, timestamp.getTime().toString());
// Activity.Id ends with Activity.Timestamp and generated id starts with 'g_'
assert.ok(pagedResult.items[1].id.endsWith(timestamp.getTime().toString()));
assert.ok(pagedResult.items[1].id.startsWith('g_'));
//assert.equal(pagedResult.items[1].id, timestamp.getTime().toString());
pagedResult.items.forEach(a => {
assert(a.timestamp);
});
Expand Down
31 changes: 31 additions & 0 deletions libraries/botbuilder-core/tests/turnContext.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,28 @@ class SimpleAdapter extends BotAdapter {
}
}

class SendAdapter extends BotAdapter {
sendActivities(context, activities) {
assert(context, `SendAdapter.sendActivities: missing context.`);
assert(activities, `SendAdapter.sendActivities: missing activities.`);
assert(Array.isArray(activities), `SendAdapter.sendActivities: activities not array.`);
assert(activities.length > 0, `SendAdapter.sendActivities: empty activities array.`);
return Promise.resolve(activities);
}

updateActivity(context, activity) {
assert(context, `SendAdapter.updateActivity: missing context.`);
assert(activity, `SendAdapter.updateActivity: missing activity.`);
return Promise.resolve();
}

deleteActivity(context, reference) {
assert(context, `SendAdapter.deleteActivity: missing context.`);
assert(reference, `SendAdapter.deleteActivity: missing reference.`);
return Promise.resolve();
}
}

describe(`TurnContext`, function () {
this.timeout(5000);

Expand Down Expand Up @@ -410,4 +432,13 @@ describe(`TurnContext`, function () {
assert(text,' test activity');
assert(activity.text,' test activity');
});

it(`should clear existing activity.id in context.sendActivity().`, function (done) {
const context = new TurnContext(new SendAdapter(), testMessage);
context.sendActivity(testMessage).then((response) => {
assert(response, `response is missing.`);
assert(response.id === undefined, `invalid response id of "${response.id}" sent back. Should be 'undefined'`);
done();
});
});
});