MM-13413 Make user names in system messages tappable (#3376)

Opens profile pop-over for:
* User changed channel purpose
* User changed channel name
* User changed channel header
* User archived channel
This commit is contained in:
Amit Uttam 2019-10-26 04:25:53 -03:00 committed by Elias Nahum
parent 1557b08d59
commit 0f3728a185
6 changed files with 311 additions and 0 deletions

View file

@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renderSystemMessage uses renderer for Channel Display Name update 1`] = `
<Connect(Markdown)
baseTextStyle={Object {}}
onPostPress={[MockFunction]}
textStyles={Object {}}
value="{username} updated the channel display name from: {oldDisplayName} to: {newDisplayName}"
/>
`;
exports[`renderSystemMessage uses renderer for Channel Header update 1`] = `
<Connect(Markdown)
baseTextStyle={Object {}}
onPostPress={[MockFunction]}
textStyles={Object {}}
value="{username} updated the channel header from: {oldHeader} to: {newHeader}"
/>
`;
exports[`renderSystemMessage uses renderer for Channel Purpose update 1`] = `
<Connect(Markdown)
baseTextStyle={Object {}}
onPostPress={[MockFunction]}
textStyles={Object {}}
value="{username} updated the channel purpose from: {oldPurpose} to: {newPurpose}"
/>
`;
exports[`renderSystemMessage uses renderer for archived channel 1`] = `
<Connect(Markdown)
baseTextStyle={Object {}}
onPostPress={[MockFunction]}
textStyles={Object {}}
value="{username} archived the channel"
/>
`;

View file

@ -13,6 +13,7 @@ import Icon from 'react-native-vector-icons/Ionicons';
import {Posts} from 'mattermost-redux/constants';
import CombinedSystemMessage from 'app/components/combined_system_message';
import {renderSystemMessage} from './system_message_helpers';
import FormattedText from 'app/components/formatted_text';
import Markdown from 'app/components/markdown';
import MarkdownEmoji from 'app/components/markdown/markdown_emoji';
@ -348,6 +349,13 @@ export default class PostBody extends PureComponent {
const messageStyle = isSystemMessage ? [style.message, style.systemMessage] : style.message;
const isPendingOrFailedPost = isPending || isFailed;
const messageStyles = {messageStyle, textStyles};
const intl = this.context.intl;
const systemMessage = renderSystemMessage(this.props, messageStyles, intl);
if (systemMessage) {
return systemMessage;
}
let body;
let messageComponent;
if (hasBeenDeleted) {

View file

@ -9,6 +9,9 @@ import PostBodyAdditionalContent from 'app/components/post_body_additional_conte
import {shallowWithIntl} from 'test/intl-test-helper';
import PostBody from './post_body.js';
import * as SystemMessageHelpers from './system_message_helpers';
jest.mock('./system_message_helpers');
describe('PostBody', () => {
const baseProps = {
@ -122,4 +125,9 @@ describe('PostBody', () => {
instance.measurePost(event);
expect(wrapper.state('isLongPost')).toEqual(false);
});
test('should return system message as post body', () => {
shallowWithIntl(<PostBody {...baseProps}/>);
expect(SystemMessageHelpers.renderSystemMessage.mock.calls.length).toBe(1);
});
});

View file

@ -0,0 +1,160 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {Posts} from 'mattermost-redux/constants';
import Markdown from 'app/components/markdown';
import {t} from 'app/utils/i18n';
const renderUsername = (value) => {
return (value[0] === '@') ? value : `@${value}`;
};
const renderMessage = (postBodyProps, styles, intl, localeHolder, values) => {
const {onPress} = postBodyProps;
const {messageStyle, textStyles} = styles;
return (
<Markdown
baseTextStyle={messageStyle}
onPostPress={onPress}
textStyles={textStyles}
value={intl.formatMessage(localeHolder, values)}
/>
);
};
const renderHeaderChangeMessage = (postBodyProps, styles, intl) => {
const {postProps} = postBodyProps;
let values;
if (!postProps.username) {
return null;
}
const username = renderUsername(postProps.username);
const oldHeader = postProps.old_header;
const newHeader = postProps.new_header;
let localeHolder;
if (postProps.new_header) {
if (postProps.old_header) {
localeHolder = {
id: t('mobile.system_message.update_channel_header_message_and_forget.updated_from'),
defaultMessage: '{username} updated the channel header from: {oldHeader} to: {newHeader}',
};
values = {username, oldHeader, newHeader};
return renderMessage(postBodyProps, styles, intl, localeHolder, values);
}
localeHolder = {
id: t('mobile.system_message.update_channel_header_message_and_forget.updated_to'),
defaultMessage: '{username} updated the channel header to: {newHeader}',
};
values = {username, oldHeader, newHeader};
return renderMessage(postBodyProps, styles, intl, localeHolder, values);
} else if (postProps.old_header) {
localeHolder = {
id: t('mobile.system_message.update_channel_header_message_and_forget.removed'),
defaultMessage: '{username} removed the channel header (was: {oldHeader})',
};
values = {username, oldHeader, newHeader};
return renderMessage(postBodyProps, styles, intl, localeHolder, values);
}
return null;
};
const renderPurposeChangeMessage = (postBodyProps, styles, intl) => {
const {postProps} = postBodyProps;
let values;
if (!postProps.username) {
return null;
}
const username = renderUsername(postProps.username);
const oldPurpose = postProps.old_purpose;
const newPurpose = postProps.new_purpose;
let localeHolder;
if (postProps.new_purpose) {
if (postProps.old_purpose) {
localeHolder = {
id: t('mobile.system_message.update_channel_purpose_message.updated_from'),
defaultMessage: '{username} updated the channel purpose from: {oldPurpose} to: {newPurpose}',
};
values = {username, oldPurpose, newPurpose};
return renderMessage(postBodyProps, styles, intl, localeHolder, values);
}
localeHolder = {
id: t('mobile.system_message.update_channel_purpose_message.updated_to'),
defaultMessage: '{username} updated the channel purpose to: {newPurpose}',
};
values = {username, oldPurpose, newPurpose};
return renderMessage(postBodyProps, styles, intl, localeHolder, values);
} else if (postProps.old_purpose) {
localeHolder = {
id: t('mobile.system_message.update_channel_purpose_message.removed'),
defaultMessage: '{username} removed the channel purpose (was: {oldPurpose})',
};
values = {username, oldPurpose, newPurpose};
return renderMessage(postBodyProps, styles, intl, localeHolder, values);
}
return null;
};
const renderDisplayNameChangeMessage = (postBodyProps, styles, intl) => {
const {postProps} = postBodyProps;
const oldDisplayName = postProps.old_displayname;
const newDisplayName = postProps.new_displayname;
if (!(postProps.username && postProps.old_displayname && postProps.new_displayname)) {
return null;
}
const username = renderUsername(postProps.username);
const localeHolder = {
id: t('mobile.system_message.update_channel_displayname_message_and_forget.updated_from'),
defaultMessage: '{username} updated the channel display name from: {oldDisplayName} to: {newDisplayName}',
};
const values = {username, oldDisplayName, newDisplayName};
return renderMessage(postBodyProps, styles, intl, localeHolder, values);
};
const renderArchivedMessage = (postBodyProps, styles, intl) => {
const {postProps} = postBodyProps;
const username = renderUsername(postProps.username);
const localeHolder = {
id: t('mobile.system_message.channel_archived_message'),
defaultMessage: '{username} archived the channel',
};
const values = {username};
return renderMessage(postBodyProps, styles, intl, localeHolder, values);
};
const systemMessageRenderers = {
[Posts.POST_TYPES.HEADER_CHANGE]: renderHeaderChangeMessage,
[Posts.POST_TYPES.DISPLAYNAME_CHANGE]: renderDisplayNameChangeMessage,
[Posts.POST_TYPES.PURPOSE_CHANGE]: renderPurposeChangeMessage,
[Posts.POST_TYPES.CHANNEL_DELETED]: renderArchivedMessage,
};
export const renderSystemMessage = (postBodyProps, styles, intl) => {
const renderer = systemMessageRenderers[postBodyProps.postType];
if (!renderer) {
return null;
}
return renderer(postBodyProps, styles, intl);
};

View file

@ -0,0 +1,90 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import * as SystemMessageHelpers from './system_message_helpers';
import {Posts} from 'mattermost-redux/constants';
const basePostBodyProps = {
postProps: {
username: 'username',
},
onPress: jest.fn(),
};
const mockStyles = {
messageStyle: {},
textStyles: {},
};
const mockIntl = {
formatMessage: ({defaultMessage}) => defaultMessage,
};
describe('renderSystemMessage', () => {
test('uses renderer for Channel Header update', () => {
const postBodyProps = {
...basePostBodyProps,
postProps: {
...basePostBodyProps.postProps,
old_header: 'old header',
new_header: 'new header',
},
postType: Posts.POST_TYPES.HEADER_CHANGE,
};
const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl);
expect(renderedMessage).toMatchSnapshot();
});
test('uses renderer for Channel Display Name update', () => {
const postBodyProps = {
...basePostBodyProps,
postProps: {
...basePostBodyProps.postProps,
old_displayname: 'old displayname',
new_displayname: 'new displayname',
},
postType: Posts.POST_TYPES.DISPLAYNAME_CHANGE,
};
const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl);
expect(renderedMessage).toMatchSnapshot();
});
test('uses renderer for Channel Purpose update', () => {
const postBodyProps = {
...basePostBodyProps,
postProps: {
...basePostBodyProps.postProps,
old_purpose: 'old purpose',
new_purpose: 'new purpose',
},
postType: Posts.POST_TYPES.PURPOSE_CHANGE,
};
const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl);
expect(renderedMessage).toMatchSnapshot();
});
test('uses renderer for archived channel', () => {
const postBodyProps = {
...basePostBodyProps,
postProps: {
...basePostBodyProps.postProps,
},
postType: Posts.POST_TYPES.CHANNEL_DELETED,
};
const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl);
expect(renderedMessage).toMatchSnapshot();
});
test('is null for non-qualifying system messages', () => {
const postBodyProps = {
...basePostBodyProps,
postType: 'not_relevant',
};
const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl);
expect(renderedMessage).toBeNull();
});
});

View file

@ -463,6 +463,14 @@
"mobile.storage_permission_denied_description": "Upload files to your Mattermost instance. Open Settings to grant Mattermost Read and Write access to files on this device.",
"mobile.storage_permission_denied_title": "{applicationName} would like to access your files",
"mobile.suggestion.members": "Members",
"mobile.system_message.channel_archived_message": "{username} archived the channel",
"mobile.system_message.update_channel_displayname_message_and_forget.updated_from": "{username} updated the channel display name from: {oldDisplayName} to: {newDisplayName}",
"mobile.system_message.update_channel_header_message_and_forget.removed": "{username} removed the channel header (was: {oldHeader})",
"mobile.system_message.update_channel_header_message_and_forget.updated_from": "{username} updated the channel header from: {oldHeader} to: {newHeader}",
"mobile.system_message.update_channel_header_message_and_forget.updated_to": "{username} updated the channel header to: {newHeader}",
"mobile.system_message.update_channel_purpose_message.removed": "{username} removed the channel purpose (was: {oldPurpose})",
"mobile.system_message.update_channel_purpose_message.updated_from": "{username} updated the channel purpose from: {oldPurpose} to: {newPurpose}",
"mobile.system_message.update_channel_purpose_message.updated_to": "{username} updated the channel purpose to: {newPurpose}",
"mobile.terms_of_service.alert_cancel": "Cancel",
"mobile.terms_of_service.alert_ok": "OK",
"mobile.terms_of_service.alert_retry": "Try Again",