Skip to content

Commit 9d97f75

Browse files
ndangudubiyyamTrevorJoelHarriserinha
authored
Added customTelemetry subcapability under copilot (#2654)
* Stage E telemetry for bizChat * Adding changefile * Adding test * adding comments * Update change/@microsoft-teams-js-05b6fda8-1215-45ac-b8c5-338fe7d7f77e.json Co-authored-by: Erin <erinha@microsoft.com> * Change from stage to UUID * Adding UUID to the index * resolving comments * resolving comments * Apply suggestions from code review * PR feedback * More PR feedback --------- Co-authored-by: Trevor Harris <trharris@microsoft.com> Co-authored-by: Erin <erinha@microsoft.com>
1 parent af5af5a commit 9d97f75

File tree

9 files changed

+132
-8
lines changed

9 files changed

+132
-8
lines changed

apps/teams-test-app/src/components/privateApis/CopilotAPIs.tsx

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { copilot } from '@microsoft/teams-js';
1+
import { copilot, UUID } from '@microsoft/teams-js';
22
import React, { ReactElement } from 'react';
33

4-
import { ApiWithoutInput } from '../utils';
4+
import { ApiWithoutInput, ApiWithTextInput } from '../utils';
55
import { ModuleWrapper } from '../utils/ModuleWrapper';
66

77
const CopilotAPIs = (): ReactElement => {
@@ -23,11 +23,40 @@ const CopilotAPIs = (): ReactElement => {
2323
},
2424
});
2525

26+
const SendCustomTelemetryData = (): ReactElement =>
27+
ApiWithTextInput<{
28+
stageNameIdentifier: UUID;
29+
timestamp: number;
30+
}>({
31+
name: 'sendCustomTelemetryData',
32+
title: 'sendCustomTelemetryData',
33+
onClick: {
34+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
35+
validateInput: (_input) => {},
36+
submit: async (input) => {
37+
const result = await copilot.customTelemetry.sendCustomTelemetryData(
38+
input.stageNameIdentifier,
39+
input.timestamp,
40+
);
41+
return JSON.stringify(result);
42+
},
43+
},
44+
defaultInput: JSON.stringify({
45+
stageNameIdentifier: new UUID('805a4340-d5e0-4587-8f04-0ae88219699f'),
46+
timestamp: Date.now(),
47+
}),
48+
});
49+
2650
return (
27-
<ModuleWrapper title="Copilot.Eligibility">
28-
<CheckCopilotEligibilityCapability />
29-
<GetEligibilityInfo />
30-
</ModuleWrapper>
51+
<>
52+
<ModuleWrapper title="Copilot.Eligibility">
53+
<CheckCopilotEligibilityCapability />
54+
<GetEligibilityInfo />
55+
</ModuleWrapper>
56+
<ModuleWrapper title="Copilot.CustomTelemetry">
57+
<SendCustomTelemetryData />
58+
</ModuleWrapper>
59+
</>
3160
);
3261
};
3362

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "minor",
3+
"comment": "Added `customTelemetry` capability under `copilot` to send app loading data to the host.",
4+
"packageName": "@microsoft/teams-js",
5+
"email": "niharikad@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

packages/teams-js/src/internal/telemetry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export const enum ApiName {
107107
Conversations_OpenConversation = 'conversations.openConversation',
108108
Conversations_RegisterCloseConversationHandler = 'conversations.registerCloseConversationHandler',
109109
Conversations_RegisterStartConversationHandler = 'conversations.registerStartConversationHandler',
110+
Copilot_CustomTelemetry_SendCustomTelemetryData = 'copilot.customTelemetry.sendCustomTelemetryData',
110111
Copilot_Eligibility_GetEligibilityInfo = 'copilot.eligibility.getEligibilityInfo',
111112
Dialog_AdaptiveCard_Bot_Open = 'dialog.adaptiveCard.bot.open',
112113
Dialog_AdaptiveCard_Open = 'dialog.adaptiveCard.open',
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as customTelemetry from './customTelemetry';
12
import * as eligibility from './eligibility';
23

3-
export { eligibility };
4+
export { customTelemetry, eligibility };
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* @beta
3+
* @hidden
4+
* User information required by specific apps
5+
* @internal
6+
* Limited to Microsoft-internal use
7+
* @module
8+
*/
9+
10+
import { callFunctionInHost } from '../../internal/communication';
11+
import { ensureInitialized } from '../../internal/internalAPIs';
12+
import { ApiName, ApiVersionNumber, getApiVersionTag, getLogger } from '../../internal/telemetry';
13+
import { getCurrentTimestamp } from '../../internal/utils';
14+
import { runtime } from '../../public/runtime';
15+
import { UUID } from '../../public/uuidObject';
16+
17+
const copilotTelemetryVersionNumber: ApiVersionNumber = ApiVersionNumber.V_2;
18+
const copilotLogger = getLogger('copilot');
19+
20+
/**
21+
* Sends custom telemetry data to the host.
22+
*
23+
* @param { UUID } stageNameIdentifier - The stageName UUID identifier for the telemetry data.
24+
* @param { number } [timestamp=getCurrentTimestamp() ?? Date.now()] - The timestamp of the telemetry data. Defaults to the current timestamp.
25+
* @returns { Promise<void> } - promise resolves when the host SDK acknowledges that it has received the message.
26+
* @throws { Error } - Throws an error if the app has not been successfully initialized or the host SDK returns an error as a response to this call
27+
*
28+
* @hidden
29+
* @internal
30+
* Limited to Microsoft-internal use
31+
* @beta
32+
*/
33+
export async function sendCustomTelemetryData(
34+
stageNameIdentifier: UUID,
35+
timestamp: number = getCurrentTimestamp() ?? Date.now(),
36+
): Promise<void> {
37+
ensureInitialized(runtime);
38+
copilotLogger(
39+
'Sending custom telemetry data to host for stage: %s to record timestamp: %s',
40+
stageNameIdentifier,
41+
timestamp,
42+
);
43+
return callFunctionInHost(
44+
ApiName.Copilot_CustomTelemetry_SendCustomTelemetryData,
45+
[stageNameIdentifier.toString(), timestamp],
46+
getApiVersionTag(copilotTelemetryVersionNumber, ApiName.Copilot_CustomTelemetry_SendCustomTelemetryData),
47+
);
48+
}

packages/teams-js/src/public/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export {
121121
onCompleteHandlerFunctionType,
122122
returnFocus,
123123
} from './navigation';
124+
export { UUID } from './uuidObject';
124125
export * as settings from './settings';
125126
export * as tasks from './tasks';
126127
export * as liveShare from './liveShareHost';

packages/teams-js/src/public/runtime.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ interface IRuntimeV4 extends IBaseRuntime {
235235
readonly clipboard?: {};
236236
readonly conversations?: {};
237237
readonly copilot?: {
238+
readonly customTelemetry?: {};
238239
readonly eligibility?: {};
239240
};
240241
readonly dialog?: {

packages/teams-js/src/public/uuidObject.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,26 @@ import { generateGUID, validateUuid } from '../internal/utils';
44
* @internal
55
* Limited to Microsoft-internal use
66
*
7-
* UUID object
7+
* Represents a UUID (Universally Unique Identifier) object.
8+
* This class provides a way to generate, validate, and represent UUIDs as strings.
89
*/
910
export class UUID {
11+
/**
12+
* Creates an instance of the UUID class.
13+
* If no UUID string is provided, a new UUID is generated.
14+
*
15+
* @param {string} [uuid=generateGUID()] - The UUID string. Defaults to a newly generated UUID.
16+
* @throws {Error} - Throws an error if the provided UUID is invalid.
17+
*/
1018
public constructor(private readonly uuid: string = generateGUID()) {
1119
validateUuid(uuid);
1220
}
1321

22+
/**
23+
* Returns the UUID as a string.
24+
*
25+
* @returns {string} - The UUID string.
26+
*/
1427
public toString(): string {
1528
return this.uuid;
1629
}

packages/teams-js/test/private/copilot.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { errorLibraryNotInitialized } from '../../src/internal/constants';
2+
import { ApiName } from '../../src/internal/telemetry';
23
import * as copilot from '../../src/private/copilot/copilot';
34
import * as app from '../../src/public/app/app';
45
import { errorNotSupportedOnPlatform, FrameContexts } from '../../src/public/constants';
56
import { Cohort, EduType, ErrorCode, LegalAgeGroupClassification, Persona } from '../../src/public/interfaces';
67
import { _minRuntimeConfigToUninitialize, Runtime } from '../../src/public/runtime';
8+
import { UUID } from '../../src/public/uuidObject';
79
import { Utils } from '../utils';
810

911
const mockedAppEligibilityInformation = {
@@ -51,6 +53,7 @@ const copilotRuntimeConfig: Runtime = {
5153
supports: {
5254
copilot: {
5355
eligibility: {},
56+
customTelemetry: {},
5457
},
5558
pages: {
5659
appButton: {},
@@ -384,4 +387,24 @@ describe('copilot', () => {
384387
});
385388
});
386389
});
390+
391+
describe('copilot.customTelemetry', () => {
392+
describe('sendCustomTelemetryData', () => {
393+
it('sendCustomTelemetryData should throw if called before initialization', async () => {
394+
expect.assertions(1);
395+
utils.uninitializeRuntimeConfig();
396+
await expect(copilot.customTelemetry.sendCustomTelemetryData(new UUID())).rejects.toThrowError(
397+
new Error(errorLibraryNotInitialized),
398+
);
399+
});
400+
401+
it('sendCustomTelemetryData message should not be null', async () => {
402+
expect.assertions(1);
403+
await utils.initializeWithContext(FrameContexts.content);
404+
await expect(copilot.customTelemetry.sendCustomTelemetryData(new UUID('805a4340-d5e0-4587-8f04-0ae88219699f')));
405+
const message = utils.findMessageByFunc(ApiName.Copilot_CustomTelemetry_SendCustomTelemetryData);
406+
expect(message).not.toBeNull();
407+
});
408+
});
409+
});
387410
});

0 commit comments

Comments
 (0)