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 all 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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added .yarn/install-state.gz
Binary file not shown.
2 changes: 1 addition & 1 deletion packages/mgt-chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"@azure/msal-browser": "2.33.0",
"@fluentui/react": "~8.106.1",
"@fluentui/react-components": "^9.19.1",
"@fluentui/react-icons": "^2.0.200",
"@fluentui/react-icons": "^2.0.210",
"@fluentui/react-northstar": "^0.66.4",
"@microsoft/mgt-components": "*",
"@microsoft/mgt-element": "*",
Expand Down
13 changes: 9 additions & 4 deletions packages/mgt-chat/src/components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React, { useEffect, useState } from 'react';
import { ErrorBar, FluentThemeProvider, MessageThread, SendBox } from '@azure/communication-react';
import { Person, PersonCardInteraction, Spinner } from '@microsoft/mgt-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 { StatefulGraphChatClient } from 'src/statefulClient/StatefulGraphChatClient';
import { useGraphChatClient } from '../../statefulClient/useGraphChatClient';
import { onRenderMessage } from '../../utils/chat';
import ChatHeader from '../ChatHeader/ChatHeader';
import ChatMessageBar from '../ChatMessageBar/ChatMessageBar';
import { registerAppIcons } from '../styles/registerIcons';
import { ManageChatMembers } from '../ManageChatMembers/ManageChatMembers';
import { StatefulGraphChatClient } from 'src/statefulClient/StatefulGraphChatClient';
import { registerAppIcons } from '../styles/registerIcons';

registerAppIcons();

Expand Down Expand Up @@ -42,6 +43,9 @@ const useStyles = makeStyles({
display: 'flex',
alignItems: 'center',
height: '100%'
},
unsupportedContent: {
color: 'red'
}
});

Expand Down Expand Up @@ -100,6 +104,7 @@ export const Chat = ({ chatId }: IMgtChatProps) => {
<Person userId={userId} avatarSize="small" personCardInteraction={PersonCardInteraction.hover} />
);
}}
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 Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* -------------------------------------------------------------------------------------------
* 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('18px', '0px', '8px', '0px'),
...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 (
<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;
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { CacheItem, CacheService } from '@microsoft/mgt-react';
* Defines the expiration time
*/
const getConversationCacheInvalidationTime = (): number => {
return CacheService.config.conversation.invalidationPeriod || CacheService.config.defaultInvalidationPeriod;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const conversation = CacheService.config.conversation;
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
return conversation.invalidationPeriod || CacheService.config.defaultInvalidationPeriod;
};

export const cacheEntryIsValid = (cacheEntry: CacheItem | null) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { CacheService } from '@microsoft/mgt-react';

export const isConversationCacheEnabled = (): boolean =>
CacheService.config.conversation.isEnabled && CacheService.config.isEnabled;
export const isConversationCacheEnabled = (): boolean => {
const conversation = CacheService.config.conversation;
return conversation.isEnabled && CacheService.config.isEnabled;
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* -------------------------------------------------------------------------------------------
*/

/* eslint-disable no-console */
import { BetaGraph, IGraph, Providers, error, log } from '@microsoft/mgt-element';
import { HubConnection, HubConnectionBuilder, IHttpConnectionOptions, LogLevel } from '@microsoft/signalr';
import { ThreadEventEmitter } from './ThreadEventEmitter';
Expand Down
129 changes: 83 additions & 46 deletions packages/mgt-chat/src/statefulClient/StatefulGraphChatClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,58 @@
*/

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,
log,
warn
} from '@microsoft/mgt-element';
import { GraphError } from '@microsoft/microsoft-graph-client';
import {
AadUserConversationMember,
Chat,
ChatMessage,
ChatMessageAttachment,
ChatRenamedEventMessageDetail,
MembersAddedEventMessageDetail,
MembersDeletedEventMessageDetail
} from '@microsoft/microsoft-graph-types';
import {
ActiveAccountChanged,
IGraph,
log,
LoginChangedEvent,
Providers,
ProviderState,
warn
} from '@microsoft/mgt-element';
import { produce } from 'immer';
import { v4 as uuid } from 'uuid';
import { currentUserId, currentUserName } from '../utils/currentUser';
import { graph } from '../utils/graph';
import { MessageCache } from './Caching/MessageCache';
import { GraphConfig } from './GraphConfig';
import { GraphNotificationClient } from './GraphNotificationClient';
import { ThreadEventEmitter } from './ThreadEventEmitter';
import {
MessageCollection,
addChatMembers,
deleteChatMessage,
loadChat,
loadChatImage,
loadChatThread,
loadChatThreadDelta,
loadMoreChatMessages,
MessageCollection,
removeChatMember,
sendChatMessage,
updateChatMessage,
removeChatMember,
addChatMembers,
loadChatImage,
updateChatTopic,
loadChatThreadDelta
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, currentUserName } from '../utils/currentUser';
import { MessageCache } from './Caching/MessageCache';
import { GraphError } from '@microsoft/microsoft-graph-client';
import { GraphConfig } from './GraphConfig';
import { isChatMessage } from '../utils/types';

// 1x1 grey pixel
const placeholderImageContent =
Expand Down Expand Up @@ -166,15 +167,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 receive 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.
*/
interface MessageConversion {
currentValue?: Message;
futureValue?: Promise<Message>;
currentValue?: GraphChatMessage;
futureValue?: Promise<GraphChatMessage>;
}

/**
Expand Down Expand Up @@ -543,7 +551,7 @@ class StatefulGraphChatClient implements StatefulClient<GraphChatClient> {
return this.graphChatMessageToAcsChatMessage(message, this.userId);
case 'systemEventMessage':
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 @@ -820,7 +828,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 @@ -877,11 +885,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 @@ -964,23 +972,18 @@ 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 ?? '', imageIndex, messageId, image)
}
};
}
Expand Down Expand Up @@ -1012,9 +1015,41 @@ 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 {
// checking content with <attachment> tags
const unsupportedContentRegex = /<\/?attachment>/gim;
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 id = graphMessage?.id ?? '';
const chatUrl = `https://teams.microsoft.com/l/message/${chatId}/${id}?context={"contextType":"chat"}`;
const attachments = graphMessage?.attachments ?? [];

let messageData: GraphChatMessage = {
messageId,
contentType: graphMessage.body?.contentType ?? 'text',
messageType: 'chat',
Expand All @@ -1025,7 +1060,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
28 changes: 28 additions & 0 deletions packages/mgt-chat/src/utils/chat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { MessageProps, MessageRenderer } from '@azure/communication-react';
import produce from 'immer';
import React from 'react';
import { renderToString } from 'react-dom/server';
import UnsupportedContent from '../components/UnsupportedContent/UnsupportedContent';
import { isChatMessage, isGraphChatMessage } from '../utils/types';

/**
* Renders the preferred content depending on whether it is supported.
*
* @param messageProps final message values from the state.
* @param defaultOnRender default component to render content.
* @returns
*/
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) : <></>;
};
export { onRenderMessage };
Loading
Loading