Skip to content

Commit

Permalink
Merge branch 'main' into vikramtha/appIDgetContext
Browse files Browse the repository at this point in the history
  • Loading branch information
TrevorJoelHarris committed Aug 13, 2024
2 parents 17bcf30 + b9cf2ea commit a4b6d3c
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 0 deletions.
31 changes: 31 additions & 0 deletions apps/teams-test-app/src/components/privateApis/CopilotAPIs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { copilot } from '@microsoft/teams-js';
import React, { ReactElement } from 'react';

import { ApiWithoutInput } from '../utils';
import { ModuleWrapper } from '../utils/ModuleWrapper';

const CopilotAPIs = (): ReactElement => {
const CheckCopilotEligibilityCapability = (): ReactElement =>
ApiWithoutInput({
name: 'checkCopilotEligibilityCapability',
title: 'Check if Copilot.Eligibility is supported',
onClick: async () =>
`Copilot.Eligibility module ${copilot.eligibility.isSupported() ? 'is' : 'is not'} supported`,
});

const GetEligibilityInfo = (): ReactElement =>
ApiWithoutInput({
name: 'getEligibilityInfo',
title: 'Get the app Eligibility Information',
onClick: async () => `${JSON.stringify(copilot.eligibility.getEligibilityInfo())}`,
});

return (
<ModuleWrapper title="Copilot.Eligibility">
<CheckCopilotEligibilityCapability />
<GetEligibilityInfo />
</ModuleWrapper>
);
};

export default CopilotAPIs;
2 changes: 2 additions & 0 deletions apps/teams-test-app/src/pages/TestApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import PagesCurrentAppAPIs from '../components/PagesCurrentAppAPIs';
import PagesTabsAPIs from '../components/PagesTabsAPIs';
import PeopleAPIs from '../components/PeopleAPIs';
import ChatAPIs from '../components/privateApis/ChatAPIs';
import CopilotAPIs from '../components/privateApis/CopilotAPIs';
import ExternalAppAuthenticationAPIs from '../components/privateApis/ExternalAppAuthenticationAPIs';
import ExternalAppCardActionsAPIs from '../components/privateApis/ExternalAppCardActionsAPIs';
import ExternalAppCommandsAPIs from '../components/privateApis/ExternalAppCommandsAPIs';
Expand Down Expand Up @@ -81,6 +82,7 @@ export const TestApp: React.FC = () => {
<ChatAPIs />
<ClipboardAPIs />
<CookieAccessComponent />
<CopilotAPIs />
<CustomAPIs />
<DialogAPIs />
<DialogCardAPIs />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Added `copilot` and `copilot.eligibility` capability that will get the eligibility information of the user using M365ChatApp. The capability is still awaiting support in one or most host applications. To track availability of this capability across different hosts see https://aka.ms/capmatrix",
"packageName": "@microsoft/teams-js",
"email": "[email protected]",
"dependentChangeType": "patch"
}
52 changes: 52 additions & 0 deletions packages/teams-js/src/private/copilot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { ensureInitialized } from '../internal/internalAPIs';
import { errorNotSupportedOnPlatform } from '../public/constants';
import { AppEligibilityInformation } from '../public/interfaces';
import { runtime } from '../public/runtime';

/**
* @beta
* @hidden
* Namespace to delegate copilot app specific APIs
* @internal
* Limited to Microsoft-internal use
*/
export namespace copilot {
/**
* @beta
* @hidden
* User information required by specific apps
* @internal
* Limited to Microsoft-internal use
*/
export namespace eligibility {
/**
* @hidden
* @internal
* Limited to Microsoft-internal use
* @beta
* @returns boolean to represent whether copilot.eligibility capability is supported
*
* @throws Error if {@linkcode app.initialize} has not successfully completed
*/
export function isSupported(): boolean {
return ensureInitialized(runtime) && !!runtime.hostVersionsInfo?.appEligibilityInformation;
}

/**
* @hidden
* @internal
* Limited to Microsoft-internal use
* @beta
* @returns the copilot eligibility information about the user
*
* @throws Error if {@linkcode app.initialize} has not successfully completed
*/
export function getEligibilityInfo(): AppEligibilityInformation {
ensureInitialized(runtime);
if (!isSupported()) {
throw errorNotSupportedOnPlatform;
}
return runtime.hostVersionsInfo!.appEligibilityInformation!;
}
}
}
1 change: 1 addition & 0 deletions packages/teams-js/src/private/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export {
openFilePreview,
} from './privateAPIs';
export { conversations } from './conversations';
export { copilot } from './copilot';
export { externalAppAuthentication } from './externalAppAuthentication';
export { externalAppCardActions } from './externalAppCardActions';
export { externalAppCommands } from './externalAppCommands';
Expand Down
150 changes: 150 additions & 0 deletions packages/teams-js/src/public/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,7 @@ export enum DevicePermission {
/** @hidden */
export interface HostVersionsInfo {
adaptiveCardSchemaVersion?: AdaptiveCardVersion;
appEligibilityInformation?: AppEligibilityInformation;
}

/**
Expand All @@ -1101,6 +1102,155 @@ export interface AdaptiveCardVersion {
minorVersion: number;
}

/**
* @hidden
* Eligibility Information for the app user.
*
* @beta
*/
export interface AppEligibilityInformation {
/**
* Describes the user’s age group, which can have implications on which product they are able to use.
*/
ageGroup: LegalAgeGroupClassification | null;
/**
* Describes the user’s chat experience based on their eligible licenses & their tenant’s eligible licenses.
* A user will be in at most one cohort.
*/
cohort: Cohort | null;
/**
* Indicates that the user is eligible for Microsoft Entra ID Authenticated Copilot experience.
*/
isCopilotEligible: boolean;
/**
* Implementation may change to be based on tenant-home region rather than IP.
*/
isCopilotEnabledRegion: boolean;
/**
* Indicates if the tenant admin has opted the user out of Copilot.
*/
isOptedOutByAdmin: boolean;
/**
* Education Eligibility Information for the app user
*/
userClassification: UserClassification;
}

/**
* @hidden
*
* @beta
*/
export interface UserClassificationWithEduType {
/**
* For EDU tenants only. Indicates if the tenant is higher ed or K12.
*/
eduType: EduType;
/**
* Describes additional traits of the user that contribute to FRE experience, etc.
*/
persona: Persona.Faculty | Persona.Student;
}

/**
* @hidden
*
* @beta
*/
export interface UserClassificationWithOtherType {
persona: Persona.Other;
}

/**
* @hidden
*
* @beta
*/
export type UserClassification = UserClassificationWithEduType | UserClassificationWithOtherType;

/**
* @hidden
*
* @beta
*/
export enum Cohort {
BCAIS = 'bcais',
BCWAF = 'bcwaf',
BCWBF = 'bcwbf',
}

/**
* @hidden
*
* @beta
*/
export enum Persona {
/**
* User has a faculty license
*/
Faculty = 'faculty',
/**
* User has a student license
*/
Student = 'student',
/**
* When user is not a faculty or student
*/
Other = 'other',
}

/**
* @hidden
*
* @beta
*/
// https://learn.microsoft.com/en-us/graph/api/resources/user?view=graph-rest-1.0#legalagegroupclassification-values
export enum LegalAgeGroupClassification {
/**
* The user is considered an adult based on the age-related regulations of their country or region.
*/
Adult = 'adult',
/**
* The user is a minor but is from a country or region that has no age-related regulations.
*/
MinorNoParentalConsentRequired = 'minorNoParentalConsentRequired',
/**
* Reserved for future use
*/
MinorWithoutParentalConsent = 'minorWithoutParentalConsent',
/**
* The user is considered a minor based on the age-related regulations of their country or region, and the administrator
* of the account obtained appropriate consent from a parent or guardian.
*/
MinorWithParentalConsent = 'minorWithParentalConsent',
/**
* The user is from a country or region that has additional age-related regulations, such as the United States,
* United Kingdom, European Union, or South Korea, and the user's age is between a minor and an adult age
* (as stipulated based on country or region). Generally, this means that teenagers are considered as notAdult in regulated countries.
*/
NonAdult = 'nonAdult',
}

/**
* @hidden
*
* @beta
*/
export enum EduType {
/**
* User is from a tenant labeled as “HigherEd”
*/
HigherEducation = 'higherEducation',
/**
* User is from a tenant labeled as “K12”
*/
K12 = 'k12',
/**
* User is from a tenant labeled as “Others” (e.g. research institutions)
*/
Other = 'other',
}

/**
* Currently supported Mime type
*/
Expand Down
77 changes: 77 additions & 0 deletions packages/teams-js/test/private/copilot.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { errorLibraryNotInitialized } from '../../src/internal/constants';
import { copilot } from '../../src/private/copilot';
import { app } from '../../src/public/app';
import { errorNotSupportedOnPlatform, FrameContexts } from '../../src/public/constants';
import { Cohort, EduType, LegalAgeGroupClassification, Persona } from '../../src/public/interfaces';
import { _minRuntimeConfigToUninitialize, Runtime } from '../../src/public/runtime';
import { Utils } from '../utils';

const mockedAppEligibilityInformation = {
cohort: Cohort.BCAIS,
ageGroup: LegalAgeGroupClassification.Adult,
isCopilotEnabledRegion: true,
isCopilotEligible: true,
isOptedOutByAdmin: false,
userClassification: {
persona: Persona.Student,
eduType: EduType.HigherEducation,
},
};

const copilotRuntimeConfig: Runtime = {
apiVersion: 4,
hostVersionsInfo: {
appEligibilityInformation: mockedAppEligibilityInformation,
},
supports: {
pages: {
appButton: {},
tabs: {},
config: {},
backStack: {},
fullTrust: {},
},
teamsCore: {},
logs: {},
},
};
describe('copilot', () => {
let utils: Utils;
beforeEach(() => {
utils = new Utils();
});

afterEach(() => {
// Reset the object since it's a singleton
if (app._uninitialize) {
utils.setRuntimeConfig(copilotRuntimeConfig);
app._uninitialize();
}
});

describe('eligibility', () => {
it('should throw if called before initialization', () => {
utils.uninitializeRuntimeConfig();
expect(() => copilot.eligibility.isSupported()).toThrowError(new Error(errorLibraryNotInitialized));
expect(() => copilot.eligibility.getEligibilityInfo()).toThrowError(new Error(errorLibraryNotInitialized));
});
it('should return EligibilityInfo if the host provided eligibility information', async () => {
await utils.initializeWithContext(FrameContexts.content);
utils.setRuntimeConfig(copilotRuntimeConfig);
expect(copilot.eligibility.isSupported()).toBeTruthy();
expect(copilot.eligibility.getEligibilityInfo()).toBe(mockedAppEligibilityInformation);
});
it('should throw if the value is not set by the host or missing ', async () => {
await utils.initializeWithContext(FrameContexts.content);
const copilotRuntimeConfigWithoutEligibilityInformation = {
...copilotRuntimeConfig,
hostVersionsInfo: undefined,
};
utils.setRuntimeConfig(copilotRuntimeConfigWithoutEligibilityInformation);
expect(copilot.eligibility.isSupported()).toBeFalsy();
expect(() => copilot.eligibility.getEligibilityInfo()).toThrowError(
expect.objectContaining(errorNotSupportedOnPlatform),
);
});
});
});

0 comments on commit a4b6d3c

Please sign in to comment.