Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: render cta for unsupported content #2663

Merged
merged 48 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
726716e
Initial work to detect and display unsupported content
musale Aug 15, 2023
d763cbb
Set the unsupported content component in Chat
musale Aug 15, 2023
ff7ec7c
Add styling for the unsupported content component
musale Aug 15, 2023
3339f65
Fix makeStyles keys
musale Aug 15, 2023
7b0af84
Remove unused css class
musale Aug 15, 2023
30a574a
Fix build issues
musale Aug 15, 2023
b56d9db
Move the typeguard arg to a const value
musale Aug 22, 2023
34e39fe
Change the CTA text and remove unnecessary imports
musale Aug 22, 2023
7181199
Add a targetUrl for the unsupported content
musale Aug 22, 2023
0397e88
Update the Chat to use variables from the state object
musale Aug 23, 2023
4813c19
Add a types utility
musale Aug 23, 2023
42d48ab
Set the message signals for unsupported content
musale Aug 23, 2023
9c85ed2
Merge branch 'next/mgt-chat' of github.com:microsoftgraph/microsoft-g…
musale Aug 23, 2023
2c93335
Fix import errors and file license strings
musale Aug 23, 2023
441a249
Re-introduce immer's produce
musale Aug 23, 2023
0ca7277
Merge branch 'next/mgt-chat' of github.com:microsoftgraph/microsoft-g…
musale Sep 5, 2023
7349512
Refactor functions for chat into new file
musale Sep 7, 2023
eb8a33f
Initial work to detect and display unsupported content
musale Aug 15, 2023
2ea7fa1
Set the unsupported content component in Chat
musale Aug 15, 2023
9bb8522
Add styling for the unsupported content component
musale Aug 15, 2023
5f6e988
Fix makeStyles keys
musale Aug 15, 2023
38ba409
Fix build issues
musale Aug 15, 2023
f1dcd90
Move the typeguard arg to a const value
musale Aug 22, 2023
0d1799d
Change the CTA text and remove unnecessary imports
musale Aug 22, 2023
761a42d
Add a targetUrl for the unsupported content
musale Aug 22, 2023
100dc57
Update the Chat to use variables from the state object
musale Aug 23, 2023
ba18bcc
Add a types utility
musale Aug 23, 2023
fbe8368
Set the message signals for unsupported content
musale Aug 23, 2023
e553662
Fix import errors and file license strings
musale Aug 23, 2023
6a1a060
Re-introduce immer's produce
musale Aug 23, 2023
e71c6db
Refactor functions for chat into new file
musale Sep 7, 2023
1742c23
Update yarn build artifacts
musale Sep 26, 2023
9395b28
Use a deeplink to the chat for the chat url
musale Sep 26, 2023
fbc47d7
Add a margin on top and bottom of the Unsupportedcontent
musale Sep 26, 2023
dae21fc
Merge next/mgt-chat into unsupported/content
musale Oct 2, 2023
f3ff278
Update the install-state.gz
musale Oct 2, 2023
cc2953f
Update the install-state.gz
musale Oct 2, 2023
81a4e1c
Merge branch 'unsupported-content' of github.com:microsoftgraph/micro…
musale Oct 5, 2023
91e9bb9
Merge branch 'next/mgt-chat' into unsupported-content
musale Oct 5, 2023
b6f5bf4
Fix import paths and add new install artifacts
musale Oct 5, 2023
d92a1a0
Merge branch 'next/mgt-chat' into unsupported-content
musale Oct 6, 2023
e200231
New install artifacts
musale Oct 11, 2023
c80386e
Fix regex from flagging content with anchor tag
musale Oct 11, 2023
7da8a5f
Revert unnecessary eslint guards bypassed by line
musale Oct 11, 2023
6271829
Merge branch 'next/mgt-chat' into unsupported-content
gavinbarron Oct 11, 2023
ef01172
Merge branch 'unsupported-content' of github.com:microsoftgraph/micro…
musale Oct 12, 2023
38ac132
Merge branch 'next/mgt-chat' into unsupported-content
musale Oct 12, 2023
7b7ed18
removing more unnecessary eslint suppressions
gavinbarron Oct 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/mgt-chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"@fluentui/react": "~8.106.1",
"@fluentui/react-northstar": "^0.66.4",
"@fluentui/react-components": "^9.19.1",
"@fluentui/react-icons": "^2.0.200",
"@fluentui/react-icons": "^2.0.210",
"@microsoft/mgt-element": "*",
"@microsoft/mgt-components": "*",
"@microsoft/mgt-msal2-provider": "*",
Expand Down
35 changes: 30 additions & 5 deletions packages/mgt-chat/src/components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import React, { useEffect, useState } from 'react';
import { ErrorBar, FluentThemeProvider, MessageThread, SendBox } from '@azure/communication-react';
import { Person, PersonCardInteraction, Spinner } from '@microsoft/mgt-react';
import {
ErrorBar,
FluentThemeProvider,
MessageProps,
MessageRenderer,
MessageThread,
SendBox
} from '@azure/communication-react';
import { FluentTheme, MessageBarType } from '@fluentui/react';
import { FluentProvider, makeStyles, shorthands, teamsLightTheme } from '@fluentui/react-components';
import { Person, PersonCardInteraction, Spinner } from '@microsoft/mgt-react';
import React, { useEffect, useState } from 'react';
import { renderToString } from 'react-dom/server';
import { useGraphChatClient } from '../../statefulClient/useGraphChatClient';
import { isChatMessage, isGraphChatMessage } from '../../utils/types';
import ChatHeader from '../ChatHeader/ChatHeader';
import ChatMessageBar from '../ChatMessageBar/ChatMessageBar';
import { registerAppIcons } from '../styles/registerIcons';
import { ManageChatMembers } from '../ManageChatMembers/ManageChatMembers';
import UnsupportedContent from '../UnsupportedContent/UnsupportedContent';
import { registerAppIcons } from '../styles/registerIcons';
import { StatefulGraphChatClient } from 'src/statefulClient/StatefulGraphChatClient';
import ChatMessageBar from '../ChatMessageBar/ChatMessageBar';
import produce from 'immer';

registerAppIcons();

Expand Down Expand Up @@ -56,6 +67,19 @@ export const Chat = ({ chatId }: IMgtChatProps) => {
};
}, [chatClient]);

const onRenderMessage = (messageProps: MessageProps, defaultOnRender?: MessageRenderer) => {
const message = messageProps?.message;
if (isGraphChatMessage(message) && message?.hasUnsupportedContent) {
const unsupportedContentComponent = <UnsupportedContent targetUrl={message.rawChatUrl} />;
messageProps = produce(messageProps, (draft: MessageProps) => {
if (isChatMessage(draft.message)) {
draft.message.content = renderToString(unsupportedContentComponent);
}
});
}

return defaultOnRender ? defaultOnRender(messageProps) : <></>;
};
const isLoading = ['creating server connections', 'subscribing to notifications', 'loading messages'].includes(
musale marked this conversation as resolved.
Show resolved Hide resolved
chatState.status
);
Expand Down Expand Up @@ -100,6 +124,7 @@ export const Chat = ({ chatId }: IMgtChatProps) => {
<Person userId={userId} avatarSize="small" personCardInteraction={PersonCardInteraction.click} />
);
}}
onRenderMessage={onRenderMessage}
/>
</div>
<div className={styles.chatInput}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { List, ListItem } from '@fluentui/react-northstar';
import { Person, PersonViewType } from '@microsoft/mgt-react';
import { AadUserConversationMember } from '@microsoft/microsoft-graph-types';
import { styles } from './manage-chat-members.styles';
import { Dismiss24Regular, bundleIcon } from '@fluentui/react-icons';
import { Dismiss24Regular, Dismiss24Filled, bundleIcon } from '@fluentui/react-icons';

interface ListChatMembersProps {
currentUserId: string;
Expand All @@ -21,7 +21,7 @@ interface ListChatMembersProps {
closeParentPopover: () => void;
}

const RemovePerson = bundleIcon(Dismiss24Regular, () => null);
musale marked this conversation as resolved.
Show resolved Hide resolved
const RemovePerson = bundleIcon(Dismiss24Filled, Dismiss24Regular);
musale marked this conversation as resolved.
Show resolved Hide resolved

const ListChatMembers = ({ members, currentUserId, removeChatMember, closeParentPopover }: ListChatMembersProps) => {
const [removeDialogOpen, setRemoveDialogOpen] = useState(false);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* -------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
* See License in the project root for license information.
* -------------------------------------------------------------------------------------------
*/
import { makeStyles, shorthands } from '@fluentui/react-components';
import React from 'react';
import { ArrowSquareUpRight24Regular } from '@fluentui/react-icons';

const useStyles = makeStyles({
container: {
backgroundColor: '#ebebeb',
display: 'flex',
boxShadow: '0px 4px 8px 0px rgba(0, 0, 0, 0.14), 0px 0px 2px 0px rgba(0, 0, 0, 0.12)',
textDecorationLine: 'none',
color: '#424242',
...shorthands.margin('4px 0 0 0'),
...shorthands.borderRadius('6px'),
...shorthands.padding('16px'),
...shorthands.gap('6px'),
':hover': {
backgroundColor: '#fafafa'
},
':visited': {
color: '#424242'
}
},
cta: {
fontFamily: 'Segoe UI',
fontSize: '12px',
fontStyle: 'normal',
fontWeight: '400',
lineHeight: '16px',
textDecorationLine: 'none'
}
});

interface UnsupportedContentProps {
targetUrl: string;
}

const UnsupportedContent = (props: UnsupportedContentProps) => {
const styles = useStyles();
return (
// TODO: update this URL to the correct value.
<a className={styles.container} target="blank" href={props.targetUrl}>
<ArrowSquareUpRight24Regular />
<p className={styles.cta}>View this message in Microsoft Teams.</p>
</a>
);
};

export default UnsupportedContent;
116 changes: 79 additions & 37 deletions packages/mgt-chat/src/statefulClient/StatefulGraphChatClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,47 @@
*/

import {
MessageThreadProps,
SendBoxProps,
ChatMessage as AcsChatMessage,
ErrorBarProps,
SystemMessage,
ContentSystemMessage,
Message
ErrorBarProps,
Message,
MessageThreadProps,
SendBoxProps,
SystemMessage
} from '@azure/communication-react';
import { IDynamicPerson, getUserWithPhoto } from '@microsoft/mgt-components';
import { ActiveAccountChanged, IGraph, LoginChangedEvent, ProviderState, Providers } from '@microsoft/mgt-element';
import {
AadUserConversationMember,
Chat,
ChatMessage,
ChatMessageAttachment,
ChatRenamedEventMessageDetail,
MembersAddedEventMessageDetail,
MembersDeletedEventMessageDetail
} from '@microsoft/microsoft-graph-types';
import { ActiveAccountChanged, IGraph, LoginChangedEvent, Providers, ProviderState } from '@microsoft/mgt-element';
import { produce } from 'immer';
import { isChatMessage } from '../utils/types';
import { v4 as uuid } from 'uuid';
import { currentUserId } from '../utils/currentUser';
import { graph } from '../utils/graph';
import { GraphNotificationClient } from './GraphNotificationClient';
import { ThreadEventEmitter } from './ThreadEventEmitter';
import {
MessageCollection,
addChatMembers,
deleteChatMessage,
loadChat,
loadChatImage,
loadChatThread,
loadMoreChatMessages,
MessageCollection,
removeChatMember,
sendChatMessage,
updateChatMessage,
removeChatMember,
addChatMembers,
loadChatImage,
updateChatTopic
} from './graph.chat';
import { getUserWithPhoto } from '@microsoft/mgt-components';
import { GraphNotificationClient } from './GraphNotificationClient';
import { ThreadEventEmitter } from './ThreadEventEmitter';
import { IDynamicPerson } from '@microsoft/mgt-react';
import { updateMessageContentWithImage } from './updateMessageContentWithImage';
import { graph } from '../utils/graph';
import { currentUserId, getCurrentUser, currentUserName } from '../utils/currentUser';
import { currentUserName } from '../utils/currentUser';
import { GraphError } from '@microsoft/microsoft-graph-client';

// 1x1 grey pixel
Expand Down Expand Up @@ -144,15 +146,22 @@ type MessageEventType =
| '#microsoft.graph.membersDeletedEventMessageDetail'
| '#microsoft.graph.chatRenamedEventMessageDetail';

/**
* Extended Message type with additional properties.
*/
export type GraphChatMessage = Message & {
hasUnsupportedContent: boolean;
rawChatUrl: string;
};
/**
* Holder type account for async conversion of messages.
* Some messages need to be written to the UI immediately and recieve an async update.
* Some messages do not have a current value and will be added after the future value is resolved.
* Some messages do not have a future value and will be added immediately.
*/
type MessageConversion = {
currentValue?: Message;
futureValue?: Promise<Message>;
currentValue?: GraphChatMessage;
futureValue?: Promise<GraphChatMessage>;
};

/**
Expand Down Expand Up @@ -449,7 +458,7 @@ class StatefulGraphChatClient implements StatefulClient<GraphChatClient> {
this.notifyStateChange((draft: GraphChatClient) => {
draft.participants = this._chat?.members || [];
draft.participantCount = draft.participants.length;
const initialMessages: Message[] = [];
const initialMessages: GraphChatMessage[] = [];
draft.messages = draft.messages.concat(
messageConversions
.map(m => m.currentValue)
Expand Down Expand Up @@ -489,7 +498,7 @@ class StatefulGraphChatClient implements StatefulClient<GraphChatClient> {
case 'message':
return this.graphChatMessageToAcsChatMessage(message, this.userId);
case 'unknownFutureValue':
return { futureValue: this.buildSystemContentMessage(message) };
return { futureValue: this.buildSystemContentMessage(message) } as MessageConversion;
default:
throw new Error(`Unknown message type ${message.messageType?.toString() || 'undefined'}`);
}
Expand Down Expand Up @@ -552,9 +561,10 @@ class StatefulGraphChatClient implements StatefulClient<GraphChatClient> {
// it's here to help us catch messages we have't handled yet
default:
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, no-console
console.warn(`Unknown system message type ${eventDetail['@odata.type']}

detail: ${JSON.stringify(eventDetail)}`);
console.warn(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`Unknown system message type ${eventDetail['@odata.type']} detail: ${JSON.stringify(eventDetail)}`
);
}
}

Expand Down Expand Up @@ -761,7 +771,7 @@ detail: ${JSON.stringify(eventDetail)}`);
.map(m => this.convertChatMessage(m));

// update the state with the current values
const currentValueMessages: Message[] = [];
const currentValueMessages: GraphChatMessage[] = [];
messageConversions
.map(m => m.currentValue)
// need to use a reduce here to filter out undefined values in a way that TypeScript understands
Expand Down Expand Up @@ -819,11 +829,11 @@ detail: ${JSON.stringify(eventDetail)}`);
* Update the state with given message either replacing an existing message matching on the id or adding to the list
*
* @private
* @param {(Message)} [message]
* @param {(GraphChatMessage)} [message]
* @return {*}
* @memberof StatefulGraphChatClient
*/
private updateMessages(message?: Message) {
private updateMessages(message?: GraphChatMessage) {
if (!message) return;
this.notifyStateChange((draft: GraphChatClient) => {
const index = draft.messages.findIndex(m => m.messageId === message.messageId);
Expand Down Expand Up @@ -906,23 +916,23 @@ detail: ${JSON.stringify(eventDetail)}`);
index++;
match = this.graphImageMatch(messageResult);
}
let placeholderMessage = this.buildAcsMessage(
graphMessage,
currentUser,
messageId,
messageResult
) as AcsChatMessage;
let placeholderMessage = this.buildAcsMessage(graphMessage, currentUser, messageId, messageResult);
conversion.currentValue = placeholderMessage;
// local function to update the message with data from each of the resolved image requests
const updateMessage = async () => {
await Promise.all(Object.values(futureImages));
for (const [imageIndex, futureImage] of Object.entries(futureImages)) {
const image = await futureImage;
if (image) {
if (image && isChatMessage(placeholderMessage)) {
placeholderMessage = {
...placeholderMessage,
...{
content: updateMessageContentWithImage(placeholderMessage.content || '', imageIndex, messageId, image)
content: updateMessageContentWithImage(
placeholderMessage?.content as string,
musale marked this conversation as resolved.
Show resolved Hide resolved
imageIndex,
messageId,
image
)
}
};
}
Expand Down Expand Up @@ -954,9 +964,39 @@ detail: ${JSON.stringify(eventDetail)}`);
return result;
}

private buildAcsMessage(graphMessage: ChatMessage, currentUser: string, messageId: string, content: string): Message {
private hasUnsupportedContent(content: string, attachments: ChatMessageAttachment[]): boolean {
const unsupportedContentTypes = [
'application/vnd.microsoft.card.codesnippet',
'application/vnd.microsoft.card.fluid',
'reference'
];
const isUnsupported: boolean[] = [];

if (attachments.length) {
for (const attachment of attachments) {
const contentType = attachment?.contentType ?? '';
isUnsupported.push(unsupportedContentTypes.includes(contentType));
}
} else {
const unsupportedContentRegex = /<\/?[atchmenbl]+>/gim;
musale marked this conversation as resolved.
Show resolved Hide resolved
const contentUnsupported = Boolean(content) && unsupportedContentRegex.test(content);
isUnsupported.push(contentUnsupported);
}
return isUnsupported.every(e => e === true);
}

private buildAcsMessage(
graphMessage: ChatMessage,
currentUser: string,
messageId: string,
content: string
): GraphChatMessage {
const senderId = graphMessage.from?.user?.id || undefined;
let messageData: Message = {
const chatId = graphMessage?.chatId ?? '';
const chatUrl = `https://teams.microsoft.com/_#/conversations/${chatId}?ctx=chat`;
// check content is supported
const attachments = graphMessage?.attachments ?? [];
let messageData: GraphChatMessage = {
messageId,
contentType: graphMessage.body?.contentType ?? 'text',
messageType: 'chat',
Expand All @@ -967,7 +1007,9 @@ detail: ${JSON.stringify(eventDetail)}`);
senderId,
mine: senderId === currentUser,
status: 'seen',
attached: 'top'
attached: 'top',
hasUnsupportedContent: this.hasUnsupportedContent(content, attachments),
rawChatUrl: chatUrl
};
if (graphMessage?.policyViolation) {
messageData = Object.assign(messageData, {
Expand Down
27 changes: 27 additions & 0 deletions packages/mgt-chat/src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* -------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
* See License in the project root for license information.
* -------------------------------------------------------------------------------------------
*/

import { ChatMessage, Message } from '@azure/communication-react';
import { GraphChatMessage } from 'src/statefulClient/StatefulGraphChatClient';

/**
* A typeguard to get the ChatMessage type
* @param msg of Message
* @returns ChatMessage
*/
export const isChatMessage = (msg: Message): msg is ChatMessage => {
return 'content' in msg;
};

/**
* A typeguard to get the GraphChatMessage type
* @param msg of Message
* @returns GraphChatMessage
*/
export const isGraphChatMessage = (msg: Message): msg is GraphChatMessage => {
return 'content' in msg && 'hasUnsupportedContent' in msg && 'rawChatUrl' in msg;
};
Loading
Loading