Skip to content

Commit cf605fe

Browse files
authored
Added copilot view API for closing the side panel (#2856)
* Added new copilot view apis. * Added copilot APIs in teams test app for E2E testing. * Added unit tests for close side panel. * Added new rendering surfaces values. * Increased 7 bytes the size limit for teams-js. * Fix pr comments.
1 parent e9f0924 commit cf605fe

File tree

9 files changed

+144
-3
lines changed

9 files changed

+144
-3
lines changed

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,27 @@ const CopilotAPIs = (): ReactElement => {
110110
},
111111
});
112112

113+
const CheckCopilotViewCapability = (): ReactElement =>
114+
ApiWithoutInput({
115+
name: 'checkCopilotViewCapability',
116+
title: 'Check if Copilot.View is supported',
117+
onClick: async () => `Copilot.View module ${copilot.view.isSupported() ? 'is' : 'is not'} supported`,
118+
});
119+
120+
const CloseSidePanel = (): ReactElement =>
121+
ApiWithoutInput({
122+
name: 'closeSidePanel',
123+
title: 'Close Side Panel',
124+
onClick: async () => {
125+
try {
126+
await copilot.view.closeSidePanel();
127+
return 'copilot.view.closeSidePanel() was called';
128+
} catch (error) {
129+
return `Error: ${error}`;
130+
}
131+
},
132+
});
133+
113134
return (
114135
<>
115136
<ModuleWrapper title="Copilot.Eligibility">
@@ -126,6 +147,10 @@ const CopilotAPIs = (): ReactElement => {
126147
<GetContent />
127148
<PreCheckUserConsent />
128149
</ModuleWrapper>
150+
<ModuleWrapper title="Copilot.View">
151+
<CheckCopilotViewCapability />
152+
<CloseSidePanel />
153+
</ModuleWrapper>
129154
</>
130155
);
131156
};
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 view copilot API with function to close the side panel.",
4+
"packageName": "@microsoft/teams-js",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
"brotli": false,
163163
"path": "./packages/teams-js/dist/esm/packages/teams-js/src/index.js",
164164
"import": "{ app, authentication, pages }",
165-
"limit": "57.60 KB"
165+
"limit": "57.67 KB"
166166
},
167167
{
168168
"brotli": false,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export const enum ApiName {
113113
Copilot_SidePanel_RegisterOnUserConsentChange = 'copilot.sidePanel.registerOnUserConsentChange',
114114
Copilot_SidePanel_GetContent = 'copilot.sidePanel.getContent',
115115
Copilot_SidePanel_PreCheckUserConsent = 'copilot.sidePanel.preCheckUserConsent',
116+
Copilot_View_CloseSidePanel = 'copilot.view.closeSidePanel',
116117
Dialog_AdaptiveCard_Bot_Open = 'dialog.adaptiveCard.bot.open',
117118
Dialog_AdaptiveCard_Open = 'dialog.adaptiveCard.open',
118119
Dialog_RegisterMessageForChildHandler = 'dialog.registerMessageForChildHandler',
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as customTelemetry from './customTelemetry';
22
import * as eligibility from './eligibility';
33
import * as sidePanel from './sidePanel';
4+
import * as view from './view';
45

5-
export { customTelemetry, eligibility, sidePanel };
6+
export { customTelemetry, eligibility, sidePanel, view };
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { callFunctionInHostAndHandleResponse } from '../../internal/communication';
2+
import { ensureInitialized } from '../../internal/internalAPIs';
3+
import { ResponseHandler } from '../../internal/responseHandler';
4+
import { ApiName, ApiVersionNumber, getApiVersionTag } from '../../internal/telemetry';
5+
import { runtime } from '../../public/runtime';
6+
7+
const copilotTelemetryVersionNumber: ApiVersionNumber = ApiVersionNumber.V_2;
8+
9+
/**
10+
* @hidden
11+
* @internal
12+
* Limited to Microsoft-internal use
13+
* @beta
14+
* @returns boolean to represent whether copilot.view capability is supported
15+
*
16+
* @throws Error if {@linkcode app.initialize} has not successfully completed
17+
*/
18+
export function isSupported(): boolean {
19+
return ensureInitialized(runtime) && !!runtime.supports.copilot?.view;
20+
}
21+
22+
/**
23+
* Closes the side panel that is hosting the copilot app.
24+
*
25+
* @throws { Error } - Throws a Error if host SDK returns an error as a response to this call
26+
*
27+
* @hidden
28+
* @beta
29+
* @internal
30+
* Limited to Microsoft-internal use
31+
*/
32+
export async function closeSidePanel(): Promise<void> {
33+
ensureInitialized(runtime);
34+
await callFunctionInHostAndHandleResponse(
35+
ApiName.Copilot_View_CloseSidePanel,
36+
[],
37+
new CloseSidePanelResponseHandler(),
38+
getApiVersionTag(copilotTelemetryVersionNumber, ApiName.Copilot_View_CloseSidePanel),
39+
);
40+
}
41+
42+
class CloseSidePanelResponseHandler extends ResponseHandler<Record<string, never>, Record<string, never>> {
43+
public validate(_response: Record<string, never>): boolean {
44+
return true;
45+
}
46+
47+
public deserialize(response: Record<string, never>): Record<string, never> {
48+
return response;
49+
}
50+
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,17 @@ export enum FrameContexts {
112112
*/
113113
export enum RenderingSurfaces {
114114
/**
115-
* The mode the copilot app rendered by the host.
115+
* Copilot running as a side panel in the host application.
116116
*/
117117
copilotSidePanel = 'copilotSidePanel',
118+
/**
119+
* Copilot running in the main pane of the host application.
120+
*/
121+
copilotMainPane = 'copilotMainPane',
122+
/**
123+
* Copilot running in full screen mode as an embedded app in the host application.
124+
*/
125+
copilotFullScreen = 'copilotFullScreen',
118126
}
119127

120128
/**

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ interface IRuntimeV4 extends IBaseRuntime {
240240
readonly customTelemetry?: {};
241241
readonly eligibility?: {};
242242
readonly sidePanel?: {};
243+
readonly view?: {};
243244
};
244245
readonly dialog?: {
245246
readonly card?: {

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,4 +652,52 @@ describe('copilot', () => {
652652
});
653653
});
654654
});
655+
656+
describe('copilot.view', () => {
657+
describe('isSupported', () => {
658+
it('should throw if called before initialization', () => {
659+
expect.assertions(1);
660+
utils.uninitializeRuntimeConfig();
661+
expect(() => copilot.view.isSupported()).toThrowError(new Error(errorLibraryNotInitialized));
662+
});
663+
664+
it('should return false if view is not supported in runtimeConfig', async () => {
665+
expect.assertions(1);
666+
await utils.initializeWithContext(FrameContexts.content);
667+
utils.setRuntimeConfig(_minRuntimeConfigToUninitialize);
668+
expect(copilot.view.isSupported()).toBe(false);
669+
});
670+
671+
it('should return true if view is supported in runtimeConfig', async () => {
672+
expect.assertions(1);
673+
await utils.initializeWithContext(FrameContexts.content);
674+
const runtimeWithView = {
675+
..._minRuntimeConfigToUninitialize,
676+
supports: {
677+
..._minRuntimeConfigToUninitialize.supports,
678+
copilot: { view: {} },
679+
},
680+
};
681+
utils.setRuntimeConfig(runtimeWithView);
682+
expect(copilot.view.isSupported()).toBe(true);
683+
});
684+
});
685+
686+
describe('closeSidePanel', () => {
687+
it('should throw if called before initialization', async () => {
688+
expect.assertions(1);
689+
utils.uninitializeRuntimeConfig();
690+
await expect(copilot.view.closeSidePanel()).rejects.toThrowError(new Error(errorLibraryNotInitialized));
691+
});
692+
693+
it('should call the closeSidePanel API if supported', async () => {
694+
expect.assertions(1);
695+
await utils.initializeWithContext(FrameContexts.content);
696+
utils.setRuntimeConfig(copilotRuntimeConfig);
697+
copilot.view.closeSidePanel();
698+
const message = utils.findMessageByFunc('copilot.view.closeSidePanel');
699+
expect(message).not.toBeNull();
700+
});
701+
});
702+
});
655703
});

0 commit comments

Comments
 (0)