Merge pull request #1786 from mattermost/release-1.9

Merge Release 1.9 into master
This commit is contained in:
Elias Nahum 2018-06-19 19:12:49 -04:00 committed by GitHub
commit 6549af68d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 195 additions and 74 deletions

View file

@ -113,7 +113,7 @@ android {
applicationId "com.mattermost.rnbeta"
minSdkVersion 21
targetSdkVersion 23
versionCode 110
versionCode 112
versionName "1.9.0"
ndk {
abiFilters "armeabi-v7a", "x86"

View file

@ -156,8 +156,8 @@ const postTypeMessage = {
export default class CombinedSystemMessage extends React.PureComponent {
static propTypes = {
actions: PropTypes.shape({
getProfilesByIds: PropTypes.func.isRequired,
getProfilesByUsernames: PropTypes.func.isRequired,
getMissingProfilesByIds: PropTypes.func.isRequired,
getMissingProfilesByUsernames: PropTypes.func.isRequired,
}).isRequired,
allUserIds: PropTypes.array.isRequired,
allUsernames: PropTypes.array.isRequired,
@ -168,6 +168,7 @@ export default class CombinedSystemMessage extends React.PureComponent {
showJoinLeave: PropTypes.bool.isRequired,
teammateNameDisplay: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
userProfiles: PropTypes.array.isRequired,
};
static defaultProps = {
@ -179,14 +180,6 @@ export default class CombinedSystemMessage extends React.PureComponent {
intl: intlShape,
};
constructor(props) {
super(props);
this.state = {
userProfiles: [],
};
}
componentDidMount() {
this.loadUserProfiles(this.props.allUserIds, this.props.allUsernames);
}
@ -197,34 +190,24 @@ export default class CombinedSystemMessage extends React.PureComponent {
}
}
loadUserProfiles = async (allUserIds, allUsernames) => {
const {actions} = this.props;
const userProfiles = [];
loadUserProfiles = (allUserIds, allUsernames) => {
if (allUserIds.length > 0) {
const {data} = await actions.getProfilesByIds(allUserIds);
if (data.length > 0) {
userProfiles.push(...data);
}
this.props.actions.getMissingProfilesByIds(allUserIds);
}
if (allUsernames.length > 0) {
const {data} = await actions.getProfilesByUsernames(allUsernames);
if (data.length > 0) {
userProfiles.push(...data);
}
this.props.actions.getMissingProfilesByUsernames(allUsernames);
}
this.setState({userProfiles});
}
getAllUsersDisplayName = () => {
const {userProfiles} = this.state;
const {
allUserIds,
allUsernames,
currentUserId,
currentUsername,
teammateNameDisplay,
userProfiles,
} = this.props;
const {formatMessage} = this.context.intl;
const usersDisplayName = userProfiles.reduce((acc, user) => {
@ -248,7 +231,11 @@ export default class CombinedSystemMessage extends React.PureComponent {
const usersDisplayName = this.getAllUsersDisplayName();
const displayNames = userIds.
filter((userId) => {
return userId !== currentUserId && userId !== currentUsername;
return (
usersDisplayName[userId] &&
userId !== currentUserId &&
userId !== currentUsername
);
}).
map((userId) => {
return usersDisplayName[userId];

View file

@ -13,13 +13,13 @@ import {Posts} from 'mattermost-redux/constants';
import CombinedSystemMessage from './combined_system_message';
/* eslint-disable max-nested-callbacks, no-console */
/* eslint-disable max-nested-callbacks */
describe('CombinedSystemMessage', () => {
const baseProps = {
actions: {
getProfilesByIds: emptyFunction,
getProfilesByUsernames: emptyFunction,
getMissingProfilesByIds: emptyFunction,
getMissingProfilesByUsernames: emptyFunction,
},
allUserIds: ['user_id_1', 'user_id_2', 'user_id_3'],
currentUserId: 'user_id_3',
@ -31,46 +31,68 @@ describe('CombinedSystemMessage', () => {
showJoinLeave: true,
teammateNameDisplay: 'username',
theme: {centerChannelColor: '#aaa'},
userProfiles: [{id: 'user_id_1', username: 'user1'}, {id: 'user_id_2', username: 'user2'}, {id: 'user_id_3', username: 'user3'}],
};
test('should match snapshot', () => {
const props = {
...baseProps,
actions: {
getProfilesByIds: jest.fn(() => Promise.resolve({data: true})),
getProfilesByUsernames: emptyFunction,
getMissingProfilesByIds: jest.fn(),
getMissingProfilesByUsernames: emptyFunction,
},
};
const wrapper = shallowWithIntl(
<CombinedSystemMessage {...props}/>
);
wrapper.setState({userProfiles: [{id: 'user_id_1', username: 'user1'}, {id: 'user_id_2', username: 'user2'}, {id: 'user_id_3', username: 'user3'}]});
const {postType, userIds, actorId} = baseProps.messageData[0];
expect(wrapper.instance().renderSystemMessage(postType, userIds, actorId, {activityType: {fontSize: 14}, text: {opacity: 0.6}}, 1)).toMatchSnapshot();
// on componentDidMount
expect(props.actions.getProfilesByIds).toHaveBeenCalledTimes(1);
expect(props.actions.getProfilesByIds).toHaveBeenCalledWith(props.allUserIds);
expect(props.actions.getMissingProfilesByIds).toHaveBeenCalledTimes(1);
expect(props.actions.getMissingProfilesByIds).toHaveBeenCalledWith(props.allUserIds);
});
test('should match snapshot', () => {
const props = {
...baseProps,
actions: {
getProfilesByIds: jest.fn(() => Promise.resolve({data: true})),
getProfilesByUsernames: emptyFunction,
},
};
const localeFormat = {
id: ['combined_system_message.first_user_and_second_user_were', 'combined_system_message.removed_from_team'],
defaultMessage: ['{firstUser} and {secondUser} were ', 'removed from the team'],
};
const wrapper = shallowWithIntl(
<CombinedSystemMessage {...baseProps}/>
);
expect(wrapper.instance().renderFormattedMessage(localeFormat, 'first_user', 'second_user', 'actor', {activityType: {fontSize: 14}, text: {opacity: 0.6}})).toMatchSnapshot();
});
test('should call getMissingProfilesByIds and/or getMissingProfilesByUsernames on loadUserProfiles', () => {
const props = {
...baseProps,
allUserIds: [],
actions: {
getMissingProfilesByIds: jest.fn(),
getMissingProfilesByUsernames: jest.fn(),
},
};
const wrapper = shallowWithIntl(
<CombinedSystemMessage {...props}/>
);
wrapper.setState({userProfiles: [{id: 'user_id_1', username: 'user1'}, {id: 'user_id_2', username: 'user2'}, {id: 'user_id_3', username: 'user3'}]});
expect(wrapper.instance().renderFormattedMessage(localeFormat, 'first_user', 'second_user', 'actor', {activityType: {fontSize: 14}, text: {opacity: 0.6}})).toMatchSnapshot();
wrapper.instance().loadUserProfiles([], []);
expect(props.actions.getMissingProfilesByIds).toHaveBeenCalledTimes(0);
expect(props.actions.getMissingProfilesByUsernames).toHaveBeenCalledTimes(0);
wrapper.instance().loadUserProfiles(['user_id_1'], []);
expect(props.actions.getMissingProfilesByIds).toHaveBeenCalledTimes(1);
expect(props.actions.getMissingProfilesByIds).toHaveBeenCalledWith(['user_id_1']);
expect(props.actions.getMissingProfilesByUsernames).toHaveBeenCalledTimes(0);
wrapper.instance().loadUserProfiles(['user_id_1', 'user_id_2'], ['user1']);
expect(props.actions.getMissingProfilesByIds).toHaveBeenCalledTimes(2);
expect(props.actions.getMissingProfilesByIds).toHaveBeenCalledWith(['user_id_1', 'user_id_2']);
expect(props.actions.getMissingProfilesByUsernames).toHaveBeenCalledTimes(1);
expect(props.actions.getMissingProfilesByUsernames).toHaveBeenCalledWith(['user1']);
});
});

View file

@ -4,30 +4,36 @@
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getProfilesByIds, getProfilesByUsernames} from 'mattermost-redux/actions/users';
import {getMissingProfilesByIds, getMissingProfilesByUsernames} from 'mattermost-redux/actions/users';
import {Preferences} from 'mattermost-redux/constants';
import {getBool, getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
import {getCurrentUser, makeGetProfilesByIdsAndUsernames} from 'mattermost-redux/selectors/entities/users';
import CombinedSystemMessage from './combined_system_message';
function mapStateToProps(state) {
const currentUser = getCurrentUser(state);
return {
currentUserId: currentUser.id,
currentUsername: currentUser.username,
showJoinLeave: getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE, true),
teammateNameDisplay: getTeammateNameDisplaySetting(state),
function makeMapStateToProps() {
const getProfilesByIdsAndUsernames = makeGetProfilesByIdsAndUsernames();
return (state, ownProps) => {
const currentUser = getCurrentUser(state);
const {allUserIds, allUsernames} = ownProps;
return {
currentUserId: currentUser.id,
currentUsername: currentUser.username,
showJoinLeave: getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE, true),
teammateNameDisplay: getTeammateNameDisplaySetting(state),
userProfiles: getProfilesByIdsAndUsernames(state, {allUserIds, allUsernames}),
};
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getProfilesByIds,
getProfilesByUsernames,
getMissingProfilesByIds,
getMissingProfilesByUsernames,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(CombinedSystemMessage);
export default connect(makeMapStateToProps, mapDispatchToProps)(CombinedSystemMessage);

96
app/fetch_preconfig.js Normal file
View file

@ -0,0 +1,96 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import RNFetchBlob from 'react-native-fetch-blob';
import {Client4} from 'mattermost-redux/client';
import {General} from 'mattermost-redux/constants';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
const HEADER_X_VERSION_ID = 'X-Version-Id';
const HEADER_X_CLUSTER_ID = 'X-Cluster-Id';
const HEADER_TOKEN = 'Token';
Client4.doFetchWithResponse = async (url, options) => {
if (!Client4.online) {
throw {
message: 'no internet connection',
url,
};
}
const response = await fetch(url, Client4.getOptions(options));
const headers = response.headers;
let data;
try {
data = await response.json();
} catch (err) {
if (response && response.resp && response.resp.data && response.resp.data.includes('SSL certificate')) {
throw {
message: 'You need to use a valid client certificate in order to connect to this Mattermost server',
status_code: 401,
url,
};
}
throw {
intl: {
id: 'mobile.request.invalid_response',
defaultMessage: 'Received invalid response from the server.',
},
};
}
// Need to only accept version in the header from requests that are not cached
// to avoid getting an old version from a cached response
if ((headers[HEADER_X_VERSION_ID] || headers[HEADER_X_VERSION_ID.toLowerCase()]) &&
(!headers['Cache-Control'] && !headers['cache-control'])) {
const serverVersion = headers[HEADER_X_VERSION_ID] || headers[HEADER_X_VERSION_ID.toLowerCase()];
if (serverVersion && this.serverVersion !== serverVersion) {
this.serverVersion = serverVersion;
EventEmitter.emit(General.SERVER_VERSION_CHANGED, serverVersion);
}
}
if (headers[HEADER_X_CLUSTER_ID] || headers[HEADER_X_CLUSTER_ID.toLowerCase()]) {
const clusterId = headers[HEADER_X_CLUSTER_ID] || headers[HEADER_X_CLUSTER_ID.toLowerCase()];
if (clusterId && this.clusterId !== clusterId) {
this.clusterId = clusterId;
}
}
if (headers[HEADER_TOKEN] || headers[HEADER_TOKEN.toLowerCase()]) {
const token = headers[HEADER_TOKEN] || headers[HEADER_TOKEN.toLowerCase()];
Client4.setToken(token);
}
if (response.ok) {
const headersMap = new Map();
Object.keys(headers).forEach((key) => {
headersMap.set(key, headers[key]);
});
return {
response,
headers: headersMap,
data,
};
}
const msg = data.message || '';
if (Client4.logToConsole) {
console.error(msg); // eslint-disable-line no-console
}
throw {
message: msg,
server_error_id: data.id,
status_code: data.status_code,
url,
};
};
window.fetch = new RNFetchBlob.polyfill.Fetch({
auto: true,
}).build();

View file

@ -48,6 +48,7 @@ import avoidNativeBridge from 'app/utils/avoid_native_bridge';
import LocalConfig from 'assets/config';
import App from './app';
import './fetch_preconfig';
const AUTHENTICATION_TIMEOUT = 5 * 60 * 1000;

View file

@ -103,21 +103,23 @@ export default class Permalink extends PureComponent {
}
componentWillMount() {
this.mounted = true;
if (this.state.loading) {
this.loadPosts(this.props);
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.channelId === '') {
if (nextProps.channelId === '' && this.mounted) {
this.handleClose();
}
if (this.props.channelName !== nextProps.channelName) {
if (this.props.channelName !== nextProps.channelName && this.mounted) {
this.setState({title: nextProps.channelName});
}
if (this.props.focusedPostId !== nextProps.focusedPostId) {
if (this.props.focusedPostId !== nextProps.focusedPostId && this.mounted) {
this.setState({loading: true});
if (nextProps.postIds && nextProps.postIds.length < 10) {
this.loadPosts(nextProps);
@ -127,6 +129,10 @@ export default class Permalink extends PureComponent {
}
}
componentWillUnmount() {
this.mounted = false;
}
goToThread = preventDoubleTap((post) => {
const {actions, navigator, theme} = this.props;
const channelId = post.channel_id;
@ -157,6 +163,7 @@ export default class Permalink extends PureComponent {
handleClose = () => {
const {actions, navigator, onClose} = this.props;
if (this.refs.view) {
this.mounted = false;
this.refs.view.zoomOut().then(() => {
actions.selectPost('');
navigator.dismissModal({animationType: 'none'});
@ -233,7 +240,7 @@ export default class Permalink extends PureComponent {
let focusChannelId = channelId;
const post = await actions.getPostThread(focusedPostId, false);
if (post.error && (!postIds || !postIds.length)) {
if (this.mounted && post.error && (!postIds || !postIds.length)) {
if (isPermalink && post.error.message.toLowerCase() !== 'network request failed') {
this.setState({
error: formatMessage({
@ -281,8 +288,10 @@ export default class Permalink extends PureComponent {
};
retry = () => {
this.setState({loading: true, error: null, retry: false});
this.loadPosts(this.props);
if (this.mounted) {
this.setState({loading: true, error: null, retry: false});
this.loadPosts(this.props);
}
};
render() {

View file

@ -166,7 +166,7 @@ platform :ios do
def replace_ios_assets()
if ENV['IOS_REPLACE_ASSETS'] == 'true'
sh 'cp -R ../dist/assets/release/icons/ios/* ../ios/Mattermost/Images.xcassets/AppIcon.appiconset/'
sh 'cp -R ../dist/assets/release/splash_screen/ios/* ../ios/SplashScreenResource/*'
sh 'cp -R ../dist/assets/release/splash_screen/ios/* ../ios/SplashScreenResource/'
end
end
@ -571,7 +571,7 @@ def configure_from_env
unless ENV['SEGMENT_API_KEY'].nil? || ENV['SEGMENT_API_KEY'].empty?
find_replace_string(
path_to_file: './dist/assets/config.json',
old_string: '"SegmentApiKey": "3MT7rAoC0OP7yy3ThzqFSAtKzmzqtUPX"',
old_string: '"SegmentApiKey": "oPPT2MA24VYZTBGDyXtUDa7u50lkPIE4"',
new_string: "\"SegmentApiKey\": \"#{ENV['SEGMENT_API_KEY']}\""
)
end

View file

@ -2516,7 +2516,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 110;
CURRENT_PROJECT_VERSION = 112;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
@ -2566,7 +2566,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 110;
CURRENT_PROJECT_VERSION = 112;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;

View file

@ -34,7 +34,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>110</string>
<string>112</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View file

@ -23,7 +23,7 @@
<key>CFBundleShortVersionString</key>
<string>1.9.0</string>
<key>CFBundleVersion</key>
<string>110</string>
<string>112</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>

View file

@ -19,6 +19,6 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>110</string>
<string>112</string>
</dict>
</plist>

8
package-lock.json generated
View file

@ -9878,8 +9878,8 @@
}
},
"mattermost-redux": {
"version": "github:mattermost/mattermost-redux#85edf0d1cae3b786d9be1946f19a23603af18417",
"from": "github:mattermost/mattermost-redux#85edf0d1cae3b786d9be1946f19a23603af18417",
"version": "github:mattermost/mattermost-redux#c9b633da7fc3fc9ba3cc40ecc9665e088defbe73",
"from": "github:mattermost/mattermost-redux#c9b633da7fc3fc9ba3cc40ecc9665e088defbe73",
"requires": {
"deep-equal": "1.0.1",
"eslint-plugin-header": "1.2.0",
@ -14484,8 +14484,8 @@
}
},
"react-native-fetch-blob": {
"version": "github:enahum/react-native-fetch-blob#2e8f9b05b012496bd3ff9790aace3f2b462e2fee",
"from": "react-native-fetch-blob@github:enahum/react-native-fetch-blob#2e8f9b05b012496bd3ff9790aace3f2b462e2fee",
"version": "github:enahum/react-native-fetch-blob#c4c798032d9c255fbae75d13a0a985af9b8b42ca",
"from": "github:enahum/react-native-fetch-blob#c4c798032d9c255fbae75d13a0a985af9b8b42ca",
"requires": {
"base-64": "0.1.0",
"glob": "7.0.6"

View file

@ -15,7 +15,7 @@
"intl": "1.2.5",
"jail-monkey": "1.0.0",
"jsc-android": "216113.0.3",
"mattermost-redux": "github:mattermost/mattermost-redux#85edf0d1cae3b786d9be1946f19a23603af18417",
"mattermost-redux": "github:mattermost/mattermost-redux#c9b633da7fc3fc9ba3cc40ecc9665e088defbe73",
"mime-db": "1.33.0",
"prop-types": "15.6.1",
"react": "16.3.2",
@ -32,7 +32,7 @@
"react-native-drawer": "2.5.0",
"react-native-exception-handler": "2.7.5",
"react-native-fast-image": "4.0.8",
"react-native-fetch-blob": "enahum/react-native-fetch-blob.git#2e8f9b05b012496bd3ff9790aace3f2b462e2fee",
"react-native-fetch-blob": "enahum/react-native-fetch-blob.git#c4c798032d9c255fbae75d13a0a985af9b8b42ca",
"react-native-image-gallery": "enahum/react-native-image-gallery#a98b1051e94f5a394541ca1ff9b15e2c9ffed84f",
"react-native-image-picker": "0.26.7",
"react-native-keyboard-aware-scroll-view": "0.5.0",