From 5be0269a9a1cd0387bcf9896769fe1ec3de2f207 Mon Sep 17 00:00:00 2001 From: Gavin Barron Date: Tue, 28 Nov 2023 19:20:09 +0000 Subject: [PATCH 1/4] feat: add support for using premium apis --- .../mgt-chat/src/components/Chat/Chat.tsx | 8 ++- .../src/statefulClient/GraphConfig.ts | 1 + .../mgt-chat/src/statefulClient/graph.chat.ts | 23 +++++---- .../src/utils/addPremiumApiSegment.tests.ts | 25 +++++++++ .../src/utils/addPremiumApiSegment.ts | 10 ++++ samples/react-chat/package.json | 1 + samples/react-chat/src/App.tsx | 51 +++++++++++-------- 7 files changed, 86 insertions(+), 33 deletions(-) create mode 100644 packages/mgt-chat/src/utils/addPremiumApiSegment.tests.ts create mode 100644 packages/mgt-chat/src/utils/addPremiumApiSegment.ts diff --git a/packages/mgt-chat/src/components/Chat/Chat.tsx b/packages/mgt-chat/src/components/Chat/Chat.tsx index f6918853ab..aad85f3723 100644 --- a/packages/mgt-chat/src/components/Chat/Chat.tsx +++ b/packages/mgt-chat/src/components/Chat/Chat.tsx @@ -10,11 +10,13 @@ import ChatMessageBar from '../ChatMessageBar/ChatMessageBar'; import { renderMGTMention } from '../../utils/mentions'; import { registerAppIcons } from '../styles/registerIcons'; import { ChatHeader } from '../ChatHeader/ChatHeader'; +import { GraphConfig } from '../../statefulClient/GraphConfig'; registerAppIcons(); interface IMgtChatProps { chatId: string; + usePremiumApis?: boolean; } const useStyles = makeStyles({ @@ -91,7 +93,7 @@ const messageThreadStyles: MessageThreadStyles = { } }; -export const Chat = ({ chatId }: IMgtChatProps) => { +export const Chat = ({ chatId, usePremiumApis }: IMgtChatProps) => { const styles = useStyles(); const chatClient: StatefulGraphChatClient = useGraphChatClient(chatId); const [chatState, setChatState] = useState(chatClient.getState()); @@ -102,6 +104,10 @@ export const Chat = ({ chatId }: IMgtChatProps) => { }; }, [chatClient]); + useEffect(() => { + GraphConfig.usePremiumApis = usePremiumApis ?? false; + }, [usePremiumApis]); + const isLoading = ['creating server connections', 'subscribing to notifications', 'loading messages'].includes( chatState.status ); diff --git a/packages/mgt-chat/src/statefulClient/GraphConfig.ts b/packages/mgt-chat/src/statefulClient/GraphConfig.ts index 5c278ff238..dab9a6eae2 100644 --- a/packages/mgt-chat/src/statefulClient/GraphConfig.ts +++ b/packages/mgt-chat/src/statefulClient/GraphConfig.ts @@ -17,6 +17,7 @@ export class GraphConfig { public static canarySubscriptionVersion = 'testprodv1.0e2ewebsockets'; public static webSocketsPrefix = 'websockets:'; + static usePremiumApis = false; public static get graphEndpoint(): GraphEndpoint { return GraphConfig.useCanary ? 'https://canary.graph.microsoft.com' : 'https://graph.microsoft.com'; diff --git a/packages/mgt-chat/src/statefulClient/graph.chat.ts b/packages/mgt-chat/src/statefulClient/graph.chat.ts index cf28e96334..a91a0c1e05 100644 --- a/packages/mgt-chat/src/statefulClient/graph.chat.ts +++ b/packages/mgt-chat/src/statefulClient/graph.chat.ts @@ -16,6 +16,7 @@ import { CacheService, IGraph, prepScopes } from '@microsoft/mgt-element'; import { ResponseType } from '@microsoft/microsoft-graph-client'; import { AadUserConversationMember, Chat, ChatMessage } from '@microsoft/microsoft-graph-types'; import { chatOperationScopes } from './chatOperationScopes'; +import { addPremiumApiSegment } from '../utils/addPremiumApiSegment'; /** * Generic collection response from graph @@ -40,7 +41,7 @@ export type MessageCollection = GraphCollection; */ export const loadChat = async (graph: IGraph, chatId: string): Promise => (await graph - .api(`/chats/${chatId}?$expand=members`) + .api(addPremiumApiSegment(`/chats/${chatId}?$expand=members`)) .middlewareOptions(prepScopes(...chatOperationScopes.loadChat)) .get()) as Chat; @@ -59,7 +60,7 @@ export const loadChatThread = async ( messageCount: number ): Promise => { const response = (await graph - .api(`/chats/${chatId}/messages`) + .api(addPremiumApiSegment(`/chats/${chatId}/messages`)) .orderby('createdDateTime DESC') .top(messageCount) .middlewareOptions(prepScopes(...chatOperationScopes.loadChatMessages)) @@ -86,7 +87,7 @@ export const loadChatThreadDelta = async ( messageCount: number ): Promise => { const response = (await graph - .api(`/chats/${chatId}/messages`) + .api(addPremiumApiSegment(`/chats/${chatId}/messages`)) .filter(`lastModifiedDateTime gt ${lastModified}`) .orderby('lastModifiedDateTime DESC') .top(messageCount) @@ -128,7 +129,7 @@ export const sendChatMessage = async (graph: IGraph, chatId: string, content: st // if (fail) throw new Error('fail'); return (await graph - .api(`/chats/${chatId}/messages`) + .api(addPremiumApiSegment(`/chats/${chatId}/messages`)) .middlewareOptions(prepScopes(...chatOperationScopes.sendChatMessage)) .post({ body: { content } })) as ChatMessage; }; @@ -155,7 +156,7 @@ export const updateChatMessage = async ( // if (fail) throw new Error('fail'); await graph - .api(`/chats/${chatId}/messages/${messageId}`) + .api(addPremiumApiSegment(`/chats/${chatId}/messages/${messageId}`)) .middlewareOptions(prepScopes(...chatOperationScopes.updateChatMessage)) .patch({ body: { content } }); }; @@ -170,14 +171,14 @@ export const updateChatMessage = async ( */ export const deleteChatMessage = async (graph: IGraph, chatId: string, messageId: string): Promise => { await graph - .api(`/me/chats/${chatId}/messages/${messageId}/softDelete`) + .api(addPremiumApiSegment(`/me/chats/${chatId}/messages/${messageId}/softDelete`)) .middlewareOptions(prepScopes(...chatOperationScopes.deleteChatMessage)) .post({}); }; export const removeChatMember = async (graph: IGraph, chatId: string, membershipId: string): Promise => { await graph - .api(`/chats/${chatId}/members/${membershipId}`) + .api(addPremiumApiSegment(`/chats/${chatId}/members/${membershipId}`)) .middlewareOptions(prepScopes(...chatOperationScopes.removeChatMember)) .delete(); }; @@ -201,7 +202,7 @@ export const addChatMembers = async ( return { id: index, method: 'POST', - url: `/chats/${chatId}/members`, + url: addPremiumApiSegment(`/chats/${chatId}/members`), headers: { 'Content-Type': 'application/json' }, @@ -236,7 +237,7 @@ export const loadChatImage = async (graph: IGraph, url: string): Promise => { await graph - .api(`/chats/${chatId}`) + .api(addPremiumApiSegment(`/chats/${chatId}`)) .middlewareOptions(prepScopes(...chatOperationScopes.updateChatMessage)) .patch({ topic }); }; diff --git a/packages/mgt-chat/src/utils/addPremiumApiSegment.tests.ts b/packages/mgt-chat/src/utils/addPremiumApiSegment.tests.ts new file mode 100644 index 0000000000..f7ac92b91e --- /dev/null +++ b/packages/mgt-chat/src/utils/addPremiumApiSegment.tests.ts @@ -0,0 +1,25 @@ +import { GraphConfig } from '../statefulClient/GraphConfig'; +import { addPremiumApiSegment } from './addPremiumApiSegment'; +import { expect } from '@open-wc/testing'; + +describe('addPremiumApiSegment tests', () => { + it('should return the original url if premium apis are not enabled', async () => { + const url = 'https://graph.microsoft.com/v1.0/me'; + const result = addPremiumApiSegment(url); + await expect(result).to.eql(url); + }); + + it('should add the premium api segment to the url', async () => { + const url = 'https://graph.microsoft.com/v1.0/me'; + GraphConfig.usePremiumApis = true; + const result = addPremiumApiSegment(url); + await expect(result).to.eql(`${url}?model=B`); + }); + + it('should add the premium api segment to the url when it already has query params', async () => { + const url = 'https://graph.microsoft.com/v1.0/me?select=id'; + GraphConfig.usePremiumApis = true; + const result = addPremiumApiSegment(url); + await expect(result).to.eql(`${url}&model=B`); + }); +}); diff --git a/packages/mgt-chat/src/utils/addPremiumApiSegment.ts b/packages/mgt-chat/src/utils/addPremiumApiSegment.ts new file mode 100644 index 0000000000..1c456468c1 --- /dev/null +++ b/packages/mgt-chat/src/utils/addPremiumApiSegment.ts @@ -0,0 +1,10 @@ +import { GraphConfig } from '../statefulClient/GraphConfig'; + +export const addPremiumApiSegment = (url: string) => { + // early exit if premium apis are not enabled + if (!GraphConfig.usePremiumApis) { + return url; + } + const urlHasExistingQueryParams = url.includes('?'); + return `${url}${urlHasExistingQueryParams ? '&' : '?'}model=B`; +}; diff --git a/samples/react-chat/package.json b/samples/react-chat/package.json index 5b84554933..f5576b04f5 100644 --- a/samples/react-chat/package.json +++ b/samples/react-chat/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@fluentui/react-components": "^9.19.1", "@microsoft/mgt-chat": "*", "@microsoft/mgt-msal2-provider": "*", "@microsoft/mgt-react": "*", diff --git a/samples/react-chat/src/App.tsx b/samples/react-chat/src/App.tsx index f390153e18..e0ff9ea492 100644 --- a/samples/react-chat/src/App.tsx +++ b/samples/react-chat/src/App.tsx @@ -1,9 +1,10 @@ -import React, { memo, useCallback, useState } from 'react'; +import React, { ChangeEvent, memo, useCallback, useState } from 'react'; import './App.css'; import { Get, Login } from '@microsoft/mgt-react'; import { Chat, NewChat } from '@microsoft/mgt-chat'; import { Chat as GraphChat } from '@microsoft/microsoft-graph-types'; import ChatListTemplate from './components/ChatListTemplate/ChatListTemplate'; +import { FluentProvider, Switch, SwitchOnChangeData, teamsLightTheme } from '@fluentui/react-components'; const ChatList = memo(({ chatSelected }: { chatSelected: (e: GraphChat) => void }) => { return ( @@ -25,30 +26,38 @@ function App() { setShowNewChat(false); }, []); + const [usePremiumApis, setUsePremiumApis] = useState(false); + const onToggleChanged = useCallback((ev: ChangeEvent, data: SwitchOnChangeData) => { + setUsePremiumApis(data.checked ?? false); + }, []); + return (
-
- Mgt Chat test harness -
- -
-
-
- - Selected chat: {chatId} -
- + +
+ Mgt Chat test harness
- - {showNewChat && ( -
- setShowNewChat(false)} mode="auto" /> -
- )} -
+ + +
+
+ + + Selected chat: {chatId} +
+ +
+ + {showNewChat && ( +
+ setShowNewChat(false)} mode="auto" /> +
+ )} +
-
{chatId && }
-
+
{chatId && }
+
+
); } From bc966fc02167f0433c17e3588096183363631100 Mon Sep 17 00:00:00 2001 From: Gavin Barron Date: Wed, 29 Nov 2023 17:22:53 +0000 Subject: [PATCH 2/4] updated lock file --- yarn.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/yarn.lock b/yarn.lock index 98bd5ab0d5..705c819d24 100644 --- a/yarn.lock +++ b/yarn.lock @@ -32873,6 +32873,7 @@ __metadata: version: 0.0.0-use.local resolution: "react-chat@workspace:samples/react-chat" dependencies: + "@fluentui/react-components": ^9.19.1 "@microsoft/mgt-chat": "*" "@microsoft/mgt-msal2-provider": "*" "@microsoft/mgt-react": "*" From ff1b5864afb7046dce0c4601fd433cca45a19c07 Mon Sep 17 00:00:00 2001 From: Gavin Barron Date: Wed, 7 Feb 2024 18:14:06 +0000 Subject: [PATCH 3/4] only use premium apis when loading the /messages collection --- .../statefulClient/GraphNotificationClient.ts | 5 ++++- .../mgt-chat/src/statefulClient/graph.chat.ts | 18 +++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/mgt-chat/src/statefulClient/GraphNotificationClient.ts b/packages/mgt-chat/src/statefulClient/GraphNotificationClient.ts index e27870989c..19f394e084 100644 --- a/packages/mgt-chat/src/statefulClient/GraphNotificationClient.ts +++ b/packages/mgt-chat/src/statefulClient/GraphNotificationClient.ts @@ -18,6 +18,7 @@ import type { import { GraphConfig } from './GraphConfig'; import { SubscriptionsCache } from './Caching/SubscriptionCache'; import { Timer } from '../utils/Timer'; +import { addPremiumApiSegment } from 'src/utils/addPremiumApiSegment'; export const appSettings = { defaultSubscriptionLifetimeInMinutes: 10, @@ -394,7 +395,9 @@ export class GraphNotificationClient { await this.subscriptionCache.deleteCachedSubscriptions(chatId, sessionId); } const promises: Promise[] = []; - promises.push(this.subscribeToResource(`/chats/${chatId}/messages`, ['created', 'updated', 'deleted'])); + promises.push( + this.subscribeToResource(addPremiumApiSegment(`/chats/${chatId}/messages`), ['created', 'updated', 'deleted']) + ); promises.push(this.subscribeToResource(`/chats/${chatId}/members`, ['created', 'deleted'])); promises.push(this.subscribeToResource(`/chats/${chatId}`, ['updated', 'deleted'])); await Promise.all(promises); diff --git a/packages/mgt-chat/src/statefulClient/graph.chat.ts b/packages/mgt-chat/src/statefulClient/graph.chat.ts index a91a0c1e05..e083619b6a 100644 --- a/packages/mgt-chat/src/statefulClient/graph.chat.ts +++ b/packages/mgt-chat/src/statefulClient/graph.chat.ts @@ -41,7 +41,7 @@ export type MessageCollection = GraphCollection; */ export const loadChat = async (graph: IGraph, chatId: string): Promise => (await graph - .api(addPremiumApiSegment(`/chats/${chatId}?$expand=members`)) + .api(`/chats/${chatId}?$expand=members`) .middlewareOptions(prepScopes(...chatOperationScopes.loadChat)) .get()) as Chat; @@ -129,7 +129,7 @@ export const sendChatMessage = async (graph: IGraph, chatId: string, content: st // if (fail) throw new Error('fail'); return (await graph - .api(addPremiumApiSegment(`/chats/${chatId}/messages`)) + .api(`/chats/${chatId}/messages`) .middlewareOptions(prepScopes(...chatOperationScopes.sendChatMessage)) .post({ body: { content } })) as ChatMessage; }; @@ -156,7 +156,7 @@ export const updateChatMessage = async ( // if (fail) throw new Error('fail'); await graph - .api(addPremiumApiSegment(`/chats/${chatId}/messages/${messageId}`)) + .api(`/chats/${chatId}/messages/${messageId}`) .middlewareOptions(prepScopes(...chatOperationScopes.updateChatMessage)) .patch({ body: { content } }); }; @@ -171,14 +171,14 @@ export const updateChatMessage = async ( */ export const deleteChatMessage = async (graph: IGraph, chatId: string, messageId: string): Promise => { await graph - .api(addPremiumApiSegment(`/me/chats/${chatId}/messages/${messageId}/softDelete`)) + .api(`/me/chats/${chatId}/messages/${messageId}/softDelete`) .middlewareOptions(prepScopes(...chatOperationScopes.deleteChatMessage)) .post({}); }; export const removeChatMember = async (graph: IGraph, chatId: string, membershipId: string): Promise => { await graph - .api(addPremiumApiSegment(`/chats/${chatId}/members/${membershipId}`)) + .api(`/chats/${chatId}/members/${membershipId}`) .middlewareOptions(prepScopes(...chatOperationScopes.removeChatMember)) .delete(); }; @@ -202,7 +202,7 @@ export const addChatMembers = async ( return { id: index, method: 'POST', - url: addPremiumApiSegment(`/chats/${chatId}/members`), + url: `/chats/${chatId}/members`, headers: { 'Content-Type': 'application/json' }, @@ -237,7 +237,7 @@ export const loadChatImage = async (graph: IGraph, url: string): Promise => { await graph - .api(addPremiumApiSegment(`/chats/${chatId}`)) + .api(`/chats/${chatId}`) .middlewareOptions(prepScopes(...chatOperationScopes.updateChatMessage)) .patch({ topic }); }; From 05f67f094de864e13de37107f0d7f1d8629b95fa Mon Sep 17 00:00:00 2001 From: Gavin Barron Date: Wed, 7 Feb 2024 22:54:16 +0000 Subject: [PATCH 4/4] fix imports to use relative paths --- packages/mgt-chat/src/statefulClient/GraphNotificationClient.ts | 2 +- packages/mgt-chat/src/utils/mentions.tsx | 2 +- packages/mgt-chat/src/utils/types.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mgt-chat/src/statefulClient/GraphNotificationClient.ts b/packages/mgt-chat/src/statefulClient/GraphNotificationClient.ts index 0f157bf509..a1f431d95d 100644 --- a/packages/mgt-chat/src/statefulClient/GraphNotificationClient.ts +++ b/packages/mgt-chat/src/statefulClient/GraphNotificationClient.ts @@ -18,7 +18,7 @@ import type { import { GraphConfig } from './GraphConfig'; import { SubscriptionsCache } from './Caching/SubscriptionCache'; import { Timer } from '../utils/Timer'; -import { addPremiumApiSegment } from 'src/utils/addPremiumApiSegment'; +import { addPremiumApiSegment } from '../utils/addPremiumApiSegment'; export const appSettings = { defaultSubscriptionLifetimeInMinutes: 10, diff --git a/packages/mgt-chat/src/utils/mentions.tsx b/packages/mgt-chat/src/utils/mentions.tsx index c3448d932f..f9a741428d 100644 --- a/packages/mgt-chat/src/utils/mentions.tsx +++ b/packages/mgt-chat/src/utils/mentions.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { MgtTemplateProps, Person } from '@microsoft/mgt-react'; import { ChatMessageMention, User } from '@microsoft/microsoft-graph-types'; -import { GraphChatClient } from 'src/statefulClient/StatefulGraphChatClient'; +import { GraphChatClient } from '../statefulClient/StatefulGraphChatClient'; import { Mention } from '@azure/communication-react'; export const renderMGTMention = (chatState: GraphChatClient) => { diff --git a/packages/mgt-chat/src/utils/types.ts b/packages/mgt-chat/src/utils/types.ts index 7852917747..bc62211de3 100644 --- a/packages/mgt-chat/src/utils/types.ts +++ b/packages/mgt-chat/src/utils/types.ts @@ -6,7 +6,7 @@ */ import { ChatMessage, Message } from '@azure/communication-react'; -import { GraphChatMessage } from 'src/statefulClient/StatefulGraphChatClient'; +import type { GraphChatMessage } from '../statefulClient/StatefulGraphChatClient'; /** * A typeguard to get the ChatMessage type