Initial Redux Store (#84)

* Moved SelectServer view state into store

* Moved view state for Login to stores

* Renamed RequestStatus values

* Merged duplicated view state for login

* initial redux store

* Fix eslint

* user store and force logout when necessary

* channel store

* address feedback

* Revert naming the property of data
This commit is contained in:
enahum 2016-12-06 17:11:40 -03:00 committed by Harrison Healey
parent 74c02789ee
commit 9125d0cac2
89 changed files with 3681 additions and 1653 deletions

View file

@ -65,7 +65,6 @@
"linebreak-style": 2,
"lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true, "allowBlockEnd": true }],
"max-lines": [1, {"max": 450, "skipBlankLines": true, "skipComments": false}],
"max-nested-callbacks": [1, {"max":1}],
"max-nested-callbacks": [2, {"max":2}],
"max-statements-per-line": [2, {"max": 1}],
"multiline-ternary": [1, "never"],
@ -148,7 +147,7 @@
"no-template-curly-in-string": 2,
"no-ternary": 0,
"no-this-before-super": 2,
"no-throw-literal": 2,
"no-throw-literal": 0,
"no-trailing-spaces": [2, { "skipBlankLines": false }],
"no-undef-init": 2,
"no-undefined": 2,

View file

@ -1,24 +1,7 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {AppRegistry} from 'react-native';
import configureStore from 'store/configureStore.js';
import {Provider} from 'react-redux';
import RootContainer from 'containers/root_container.js';
const store = configureStore();
class Mattermost extends React.Component {
render() {
return (
<Provider store={store}>
<RootContainer/>
</Provider>
);
}
}
import Mattermost from 'mattermost';
AppRegistry.registerComponent('Mattermost', () => Mattermost);

View file

@ -1,24 +1,7 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {AppRegistry} from 'react-native';
import configureStore from 'store/configureStore.js';
import {Provider} from 'react-redux';
import RootContainer from 'containers/root_container.js';
const store = configureStore();
class Mattermost extends React.Component {
render() {
return (
<Provider store={store}>
<RootContainer/>
</Provider>
);
}
}
import Mattermost from 'mattermost';
AppRegistry.registerComponent('Mattermost', () => Mattermost);

View file

@ -6,6 +6,7 @@
"babel-preset-es2015": "6.18.0",
"intl": "1.2.5",
"isomorphic-fetch": "2.2.1",
"keymirror": "0.1.1",
"lodash": "4.16.4",
"react": "15.3.2",
"react-addons-pure-render-mixin": "15.3.2",

View file

@ -1,30 +1,479 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {ChannelTypes} from 'constants';
import {Constants, ChannelTypes, UsersTypes} from 'constants';
import {forceLogoutIfNecessary} from './helpers';
import {batchActions} from 'redux-batched-actions';
import Client from 'client/client_instance.js';
import Client from 'client';
export function createChannel(channel, userId) {
return async (dispatch, getState) => {
try {
dispatch(batchActions([
{
type: ChannelTypes.CREATE_CHANNEL_REQUEST
},
{
type: ChannelTypes.CHANNEL_MEMBERS_REQUEST
}
]), getState);
const created = await Client.createChannel(channel);
const member = {
channel_id: created.id,
user_id: userId,
roles: `${Constants.CHANNEL_USER_ROLE} ${Constants.CHANNEL_ADMIN_ROLE}`,
last_viewed_at: 0,
msg_count: 0,
mention_count: 0,
notify_props: {desktop: 'default', mark_unread: 'all'},
last_update_at: created.create_at
};
dispatch(batchActions([
{
type: ChannelTypes.RECEIVED_CHANNEL,
data: created
},
{
type: ChannelTypes.CREATE_CHANNEL_SUCCESS
},
{
type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,
data: member
},
{
type: ChannelTypes.CHANNEL_MEMBERS_SUCCESS
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch(batchActions([
{
type: ChannelTypes.CREATE_CHANNEL_FAILURE,
error
},
{
type: ChannelTypes.CHANNEL_MEMBERS_FAILURE,
error
}
]), getState);
}
};
}
export function createDirectChannel(userId, otherUserId) {
return async (dispatch, getState) => {
try {
dispatch(batchActions([
{
type: ChannelTypes.CREATE_CHANNEL_REQUEST
},
{
type: ChannelTypes.CHANNEL_MEMBERS_REQUEST
},
{
type: UsersTypes.PROFILES_REQUEST
}
]), getState);
const created = await Client.createDirectChannel(otherUserId);
const profile = await Client.getUser(otherUserId);
const member = {
channel_id: created.id,
user_id: userId,
roles: `${Constants.CHANNEL_USER_ROLE} ${Constants.CHANNEL_ADMIN_ROLE}`,
last_viewed_at: 0,
msg_count: 0,
mention_count: 0,
notify_props: {desktop: 'default', mark_unread: 'all'},
last_update_at: created.create_at
};
dispatch(batchActions([
{
type: ChannelTypes.RECEIVED_CHANNEL,
data: created
},
{
type: ChannelTypes.CREATE_CHANNEL_SUCCESS
},
{
type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,
data: member
},
{
type: ChannelTypes.CHANNEL_MEMBERS_SUCCESS
},
{
type: UsersTypes.RECEIVED_PROFILES,
data: {[profile.id]: profile}
},
{
type: UsersTypes.PROFILES_SUCCESS
},
{
type: UsersTypes.RECEIVED_PREFERENCE,
data: {category: Constants.CATEGORY_DIRECT_CHANNEL_SHOW, name: otherUserId, value: 'true'}
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch(batchActions([
{
type: ChannelTypes.CREATE_CHANNEL_FAILURE,
error
},
{
type: ChannelTypes.CHANNEL_MEMBERS_FAILURE,
error
},
{
type: UsersTypes.PROFILES_FAILURE
}
]), getState);
}
};
}
export function updateChannel(channel) {
return async (dispatch, getState) => {
try {
dispatch({type: ChannelTypes.UPDATE_CHANNEL_REQUEST}, getState);
const updated = await Client.updateChannel(channel);
dispatch(batchActions([
{
type: ChannelTypes.RECEIVED_CHANNEL,
data: updated
},
{
type: ChannelTypes.UPDATE_CHANNEL_SUCCESS
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch({type: ChannelTypes.UPDATE_CHANNEL_FAILURE, error}, getState);
}
};
}
export function updateChannelNotifyProps(userId, teamId, channelId, props) {
return async (dispatch, getState) => {
try {
dispatch({type: ChannelTypes.NOTIFY_PROPS_REQUEST}, getState);
const data = {
user_id: userId,
channel_id: channelId,
...props
};
const notifyProps = await Client.updateChannelNotifyProps(teamId, data);
dispatch(batchActions([
{
type: ChannelTypes.RECEIVED_CHANNEL_PROPS,
data: notifyProps,
channel_id: channelId
},
{
type: ChannelTypes.NOTIFY_PROPS_SUCCESS
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch({type: ChannelTypes.NOTIFY_PROPS_FAILURE, error}, getState);
}
};
}
export function getChannel(teamId, channelId) {
return async (dispatch, getState) => {
try {
dispatch({type: ChannelTypes.CHANNEL_REQUEST}, getState);
const data = await Client.getChannel(teamId, channelId);
dispatch(batchActions([
{
type: ChannelTypes.RECEIVED_CHANNEL,
data: data.channel
},
{
type: ChannelTypes.CHANNEL_SUCCESS
},
{
type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,
data: data.member
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch({type: ChannelTypes.CHANNELS_FAILURE, error}, getState);
}
};
}
export function fetchMyChannelsAndMembers(teamId) {
return async (dispatch, getState) => {
try {
dispatch(batchActions([
{
type: ChannelTypes.CHANNELS_REQUEST
},
{
type: ChannelTypes.CHANNEL_MEMBERS_REQUEST
}
]), getState);
const channels = Client.getChannels(teamId);
const channelMembers = Client.getMyChannelMembers(teamId);
dispatch(batchActions([
{
type: ChannelTypes.CHANNELS_RECEIVED,
teamId,
channels: await channels
type: ChannelTypes.RECEIVED_CHANNELS,
data: await channels
},
{
type: ChannelTypes.CHANNEL_MEMBERS_RECEIVED,
teamId,
channelMembers: await channelMembers
type: ChannelTypes.CHANNELS_SUCCESS
},
{
type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBERS,
data: await channelMembers
},
{
type: ChannelTypes.CHANNEL_MEMBERS_SUCCESS
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch(batchActions([
{
type: ChannelTypes.CHANNELS_FAILURE,
error
},
{
type: ChannelTypes.CHANNEL_MEMBERS_FAILURE,
error
}
]), getState);
} catch (err) {
console.error(err); // eslint-disable-line no-console
}
};
}
export function leaveChannel(teamId, channelId) {
return async (dispatch, getState) => {
try {
dispatch({type: ChannelTypes.LEAVE_CHANNEL_REQUEST}, getState);
await Client.leaveChannel(teamId, channelId);
dispatch(batchActions([
{
type: ChannelTypes.LEAVE_CHANNEL,
channel_id: channelId
},
{
type: ChannelTypes.LEAVE_CHANNEL_SUCCESS
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch({type: ChannelTypes.LEAVE_CHANNEL_FAILURE, error}, getState);
}
};
}
export function joinChannel(userId, teamId, channelId, channelName) {
return async (dispatch, getState) => {
try {
dispatch({type: ChannelTypes.JOIN_CHANNEL_REQUEST}, getState);
let channel;
if (channelId) {
channel = await Client.joinChannel(teamId, channelId);
} else if (channelName) {
channel = await Client.joinChannelByName(teamId, channelName);
}
const channelMember = {
channel_id: channel.id,
user_id: userId,
roles: `${Constants.CHANNEL_USER_ROLE}`,
last_viewed_at: 0,
msg_count: 0,
mention_count: 0,
notify_props: {desktop: 'default', mark_unread: 'all'},
last_update_at: new Date().getTime()
};
dispatch(batchActions([
{
type: ChannelTypes.RECEIVED_CHANNEL,
data: channel
},
{
type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,
data: channelMember
},
{
type: ChannelTypes.JOIN_CHANNEL_SUCCESS
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch({type: ChannelTypes.JOIN_CHANNEL_FAILURE, error}, getState);
}
};
}
export function deleteChannel(teamId, channelId) {
return async (dispatch, getState) => {
try {
dispatch({type: ChannelTypes.DELETE_CHANNEL_REQUEST}, getState);
await Client.deleteChannel(teamId, channelId);
dispatch(batchActions([
{
type: ChannelTypes.RECEIVED_CHANNEL_DELETED,
channel_id: channelId
},
{
type: ChannelTypes.DELETE_CHANNEL_SUCCESS
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch({type: ChannelTypes.DELETE_CHANNEL_FAILURE, error}, getState);
}
};
}
export function updateLastViewedAt(teamId, channelId, active) {
return async (dispatch, getState) => {
try {
dispatch({type: ChannelTypes.UPDATE_LAST_VIEWED_REQUEST}, getState);
// this API should return the timestamp that was set
await Client.updateLastViewedAt(teamId, channelId, active);
dispatch(batchActions([
{
type: ChannelTypes.RECEIVED_LAST_VIEWED,
channel_id: channelId,
last_viewed_at: new Date().getTime()
},
{
type: ChannelTypes.UPDATE_LAST_VIEWED_SUCCESS
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch({type: ChannelTypes.UPDATE_LAST_VIEWED_FAILURE, error}, getState);
}
};
}
export function getMoreChannels(teamId, offset, limit = Constants.CHANNELS_CHUNK_SIZE) {
return async (dispatch, getState) => {
try {
dispatch({type: ChannelTypes.MORE_CHANNELS_REQUEST}, getState);
const channels = Client.getMoreChannels(teamId, offset, limit);
dispatch(batchActions([
{
type: ChannelTypes.RECEIVED_MORE_CHANNELS,
data: await channels
},
{
type: ChannelTypes.MORE_CHANNELS_SUCCESS
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch({type: ChannelTypes.MORE_CHANNELS_FAILURE, error}, getState);
}
};
}
export function getChannelStats(teamId, channelId) {
return async (dispatch, getState) => {
try {
dispatch({type: ChannelTypes.CHANNEL_STATS_REQUEST}, getState);
const stat = await Client.getChannelStats(teamId, channelId);
dispatch(batchActions([
{
type: ChannelTypes.RECEIVED_CHANNEL_STATS,
data: stat
},
{
type: ChannelTypes.CHANNEL_STATS_SUCCESS
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch({type: ChannelTypes.CHANNEL_STATS_FAILURE, error}, getState);
}
};
}
export function addChannelMember(teamId, channelId, userId) {
return async (dispatch, getState) => {
try {
dispatch({type: ChannelTypes.ADD_CHANNEL_MEMBER_REQUEST}, getState);
await Client.addChannelMember(teamId, channelId, userId);
dispatch(batchActions([
{
type: UsersTypes.RECEIVED_PROFILE_IN_CHANNEL,
user_id: userId
},
{
type: ChannelTypes.ADD_CHANNEL_MEMBER_SUCCESS
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch({type: ChannelTypes.ADD_CHANNEL_MEMBER_FAILURE, error}, getState);
}
};
}
export function removeChannelMember(teamId, channelId, userId) {
return async (dispatch, getState) => {
try {
dispatch({type: ChannelTypes.REMOVE_CHANNEL_MEMBER_REQUEST}, getState);
await Client.addChannelMember(teamId, channelId, userId);
dispatch(batchActions([
{
type: UsersTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL,
user_id: userId
},
{
type: ChannelTypes.REMOVE_CHANNEL_MEMBER_SUCCESS
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch({type: ChannelTypes.REMOVE_CHANNEL_MEMBER_FAILURE, error}, getState);
}
};
}
export default {
createChannel,
createDirectChannel,
updateChannel,
updateChannelNotifyProps,
getChannel,
fetchMyChannelsAndMembers,
leaveChannel,
joinChannel,
deleteChannel,
updateLastViewedAt,
getMoreChannels,
getChannelStats,
addChannelMember,
removeChannelMember
};

View file

@ -1,7 +1,7 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import Client from 'client/client_instance.js';
import Client from 'client';
import {bindClientFunc} from './helpers.js';
import {GeneralTypes} from 'constants';
@ -18,11 +18,20 @@ export function getClientConfig() {
return bindClientFunc(
Client.getClientConfig,
GeneralTypes.CLIENT_CONFIG_REQUEST,
GeneralTypes.CLIENT_CONFIG_SUCCESS,
[GeneralTypes.CLIENT_CONFIG_RECEIVED, GeneralTypes.CLIENT_CONFIG_SUCCESS],
GeneralTypes.CLIENT_CONFIG_FAILURE
);
}
export function getLicenseConfig() {
return bindClientFunc(
Client.getLicenseConfig,
GeneralTypes.CLIENT_LICENSE_REQUEST,
[GeneralTypes.CLIENT_LICENSE_RECEIVED, GeneralTypes.CLIENT_LICENSE_SUCCESS],
GeneralTypes.CLIENT_LICENSE_FAILURE
);
}
export function logClientError(message, level = 'ERROR') {
return bindClientFunc(
Client.logClientError,

View file

@ -1,6 +1,25 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import Client from 'client';
import {UsersTypes} from 'constants';
const HTTP_UNAUTHORIZED = 401;
export async function forceLogoutIfNecessary(err, dispatch) {
if (err.status_code === HTTP_UNAUTHORIZED && err.url.indexOf('/login') === -1) {
await Client.logout();
dispatch({type: UsersTypes.LOGOUT_SUCCESS});
}
}
function dispatcher(type, data, dispatch, getState) {
if (type.indexOf('SUCCESS') === -1) { // we don't want to pass the data for the request types
dispatch(requestSuccess(type, data), getState);
} else {
dispatch(requestData(type), getState);
}
}
export function requestData(type) {
return {
type
@ -21,24 +40,22 @@ export function requestFailure(type, error) {
};
}
export function emptyError() {
return {
id: '',
message: '',
detailed_error: '',
status_code: ''
};
}
export function bindClientFunc(clientFunc, request, success, failure, ...args) {
return async (dispatch, getState) => {
dispatch(requestData(request), getState);
try {
const data = await clientFunc(...args);
dispatch(requestSuccess(success, data), getState);
if (Array.isArray(success)) {
success.forEach((s) => {
dispatcher(s, data, dispatch, getState);
});
} else {
dispatcher(success, data, dispatch, getState);
}
} catch (err) {
dispatch(requestFailure(failure, err));
forceLogoutIfNecessary(err, dispatch);
dispatch(requestFailure(failure, err), getState);
}
};
}

View file

@ -1,18 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {LoginTypes} from 'constants';
import Client from 'client/client_instance';
import {bindClientFunc} from 'actions/helpers.js';
export function login(loginId, password, mfaToken = '') {
return bindClientFunc(
Client.login,
LoginTypes.LOGIN_REQUEST,
LoginTypes.LOGIN_SUCCESS,
LoginTypes.LOGIN_FAILURE,
loginId,
password,
mfaToken
);
}

View file

@ -1,15 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {LogoutTypes} from 'constants';
import Client from 'client/client_instance';
import {bindClientFunc} from 'actions/helpers.js';
export function logout() {
return bindClientFunc(
Client.logout,
LogoutTypes.LOGOUT_REQUEST,
LogoutTypes.LOGOUT_SUCCESS,
LogoutTypes.LOGOUT_FAILURE
);
}

View file

@ -2,15 +2,15 @@
// See License.txt for license information.
import {bindClientFunc} from './helpers.js';
import Client from 'client/client_instance';
import {PostsTypes as types} from 'constants';
import Client from 'client';
import {PostsTypes} from 'constants';
export function fetchPosts(teamId, channelId) {
return bindClientFunc(
Client.fetchPosts,
types.FETCH_POSTS_REQUEST,
types.FETCH_POSTS_SUCCESS,
types.FETCH_POSTS_FAILURE,
PostsTypes.FETCH_POSTS_REQUEST,
PostsTypes.FETCH_POSTS_SUCCESS,
PostsTypes.FETCH_POSTS_FAILURE,
teamId,
channelId
);

View file

@ -2,14 +2,15 @@
// See License.txt for license information.
import {bindClientFunc} from './helpers.js';
import Client from 'client/client_instance';
import Client from 'client';
import {TeamsTypes} from 'constants';
export function selectTeam(team) {
Client.setTeamId(team.id);
return {
type: TeamsTypes.SELECT_TEAM,
teamId: team.id
return async (dispatch, getState) => {
dispatch({
type: TeamsTypes.SELECT_TEAM,
teamId: team.id
}, getState);
};
}
@ -17,7 +18,7 @@ export function fetchTeams() {
return bindClientFunc(
Client.getAllTeams,
TeamsTypes.FETCH_TEAMS_REQUEST,
TeamsTypes.FETCH_TEAMS_SUCCESS,
[TeamsTypes.RECEIVED_ALL_TEAMS, TeamsTypes.FETCH_TEAMS_SUCCESS],
TeamsTypes.FETCH_TEAMS_FAILURE
);
}

215
src/actions/users.js Normal file
View file

@ -0,0 +1,215 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {batchActions} from 'redux-batched-actions';
import Client from 'client';
// TODO: uncomment when PLT-4167 is merged
// import {Constants, UsersTypes, TeamsTypes} from 'constants';
import {Constants, UsersTypes} from 'constants';
import {bindClientFunc} from 'actions/helpers';
import {forceLogoutIfNecessary} from './helpers';
export function login(loginId, password, mfaToken = '') {
return async (dispatch, getState) => {
dispatch({type: UsersTypes.LOGIN_REQUEST}, getState);
Client.login(loginId, password, mfaToken).
then(async (data) => {
try {
const preferences = Client.getMyPreferences();
// TODO: uncomment when PLT-4167 is merged
// const teamMembers = Client.getMyTeamMembers();
dispatch(batchActions([
{
type: UsersTypes.RECEIVED_ME,
data
},
{
type: UsersTypes.RECEIVED_PREFERENCES,
data: await preferences
},
// TODO: uncomment when PLT-4167 is merged
// {
// type: TeamsTypes.RECEIVED_MY_TEAM_MEMBERS,
// data: await teamMembers
// },
{
type: UsersTypes.LOGIN_SUCCESS
}
]), getState);
} catch (err) {
dispatch({type: UsersTypes.LOGIN_FAILURE, error: err}, getState);
}
}).
catch((err) => {
dispatch({type: UsersTypes.LOGIN_FAILURE, error: err}, getState);
});
};
}
export function logout() {
return bindClientFunc(
Client.logout,
UsersTypes.LOGOUT_REQUEST,
UsersTypes.LOGOUT_SUCCESS,
UsersTypes.LOGOUT_FAILURE,
);
}
export function getProfiles(offset, limit = Constants.PROFILE_CHUNK_SIZE) {
return bindClientFunc(
Client.getProfiles,
UsersTypes.PROFILES_REQUEST,
[UsersTypes.RECEIVED_PROFILES, UsersTypes.PROFILES_SUCCESS],
UsersTypes.PROFILES_FAILURE,
offset,
limit
);
}
export function getProfilesByIds(userIds) {
return bindClientFunc(
Client.getProfilesByIds,
UsersTypes.PROFILES_REQUEST,
[UsersTypes.RECEIVED_PROFILES, UsersTypes.PROFILES_SUCCESS],
UsersTypes.PROFILES_FAILURE,
userIds
);
}
export function getProfilesInTeam(teamId, offset, limit = Constants.PROFILE_CHUNK_SIZE) {
return async (dispatch, getState) => {
try {
dispatch({type: UsersTypes.PROFILES_IN_TEAM_REQUEST}, getState);
const profiles = await Client.getProfilesInTeam(teamId, offset, limit);
dispatch(batchActions([
{
type: UsersTypes.RECEIVED_PROFILES_IN_TEAM,
data: profiles,
offset,
count: Object.keys(profiles).length
},
{
type: UsersTypes.RECEIVED_PROFILES,
data: profiles
},
{
type: UsersTypes.PROFILES_IN_TEAM_SUCCESS
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch({type: UsersTypes.PROFILES_IN_TEAM_FAILURE, error}, getState);
}
};
}
export function getProfilesInChannel(teamId, channelId, offset, limit = Constants.PROFILE_CHUNK_SIZE) {
return async (dispatch, getState) => {
try {
dispatch({type: UsersTypes.PROFILES_IN_CHANNEL_REQUEST}, getState);
const profiles = await Client.getProfilesInChannel(teamId, channelId, offset, limit);
dispatch(batchActions([
{
type: UsersTypes.RECEIVED_PROFILES_IN_CHANNEL,
data: profiles,
offset,
count: Object.keys(profiles).length
},
{
type: UsersTypes.RECEIVED_PROFILES,
data: profiles
},
{
type: UsersTypes.PROFILES_IN_CHANNEL_SUCCESS
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch({type: UsersTypes.PROFILES_IN_CHANNEL_FAILURE, error}, getState);
}
};
}
export function getProfilesNotInChannel(teamId, channelId, offset, limit = Constants.PROFILE_CHUNK_SIZE) {
return async (dispatch, getState) => {
try {
dispatch({type: UsersTypes.PROFILES_NOT_IN_CHANNEL_REQUEST}, getState);
const profiles = await Client.getProfilesInChannel(teamId, channelId, offset, limit);
dispatch(batchActions([
{
type: UsersTypes.RECEIVED_PROFILES_NOT_IN_CHANNEL,
data: profiles,
offset,
count: Object.keys(profiles).length
},
{
type: UsersTypes.RECEIVED_PROFILES,
data: profiles
},
{
type: UsersTypes.PROFILES_NOT_IN_CHANNEL_SUCCESS
}
]), getState);
} catch (error) {
forceLogoutIfNecessary(error, dispatch);
dispatch({type: UsersTypes.PROFILES_NOT_IN_CHANNEL_FAILURE, error}, getState);
}
};
}
export function getStatusesByIds(userIds) {
return bindClientFunc(
Client.getStatusesByIds,
UsersTypes.PROFILES_STATUSES_REQUEST,
[UsersTypes.RECEIVED_STATUSES, UsersTypes.PROFILES_STATUSES_SUCCESS],
UsersTypes.PROFILES_STATUSES_FAILURE,
userIds
);
}
export function getSessions(userId) {
return bindClientFunc(
Client.getSessions,
UsersTypes.SESSIONS_REQUEST,
[UsersTypes.RECEIVED_SESSIONS, UsersTypes.SESSIONS_SUCCESS],
UsersTypes.SESSIONS_FAILURE,
userId
);
}
export function revokeSession(id) {
return bindClientFunc(
Client.revokeSession,
UsersTypes.REVOKE_SESSION_REQUEST,
[UsersTypes.RECEIVED_REVOKED_SESSION, UsersTypes.REVOKE_SESSION_SUCCESS],
UsersTypes.REVOKE_SESSION_FAILURE,
id
);
}
export function getAudits(userId) {
return bindClientFunc(
Client.getAudits,
UsersTypes.AUDITS_REQUEST,
[UsersTypes.RECEIVED_AUDITS, UsersTypes.AUDITS_SUCCESS],
UsersTypes.AUDITS_FAILURE,
userId
);
}
export default {
login,
logout,
getProfiles,
getProfilesByIds,
getProfilesInTeam,
getProfilesInChannel,
getProfilesNotInChannel,
getStatusesByIds,
getSessions,
revokeSession,
getAudits
};

View file

@ -0,0 +1,27 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {UsersTypes} from 'constants';
export function handleLoginIdChanged(loginId) {
return async (dispatch, getState) => {
dispatch({
type: UsersTypes.LOGIN_ID_CHANGED,
loginId
}, getState);
};
}
export function handlePasswordChanged(password) {
return async (dispatch, getState) => {
dispatch({
type: UsersTypes.PASSWORD_CHANGED,
password
}, getState);
};
}
export default {
handleLoginIdChanged,
handlePasswordChanged
};

View file

@ -0,0 +1,17 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {GeneralTypes} from 'constants';
export function handleServerUrlChanged(serverUrl) {
return async (dispatch, getState) => {
dispatch({
type: GeneralTypes.SERVER_URL_CHANGED,
serverUrl
}, getState);
};
}
export default {
handleServerUrlChanged
};

View file

@ -46,6 +46,10 @@ export default class Client {
return `${this.url}${this.urlVersion}/teams`;
}
getPreferencesRoute() {
return `${this.url}${this.urlVersion}/preferences`;
}
getTeamNeededRoute(teamId) {
return `${this.url}${this.urlVersion}/teams/${teamId}`;
}
@ -120,14 +124,21 @@ export default class Client {
`${this.getGeneralRoute()}/client_props`,
{method: 'get'}
);
}
};
getLicenseConfig = async () => {
return this.doFetch(
`${this.getLicenseRoute()}/client_config`,
{method: 'get'}
);
};
getPing = async () => {
return this.doFetch(
`${this.getGeneralRoute()}/ping`,
{method: 'get'}
);
}
};
logClientError = async (message, level = 'ERROR') => {
const body = {
@ -139,16 +150,33 @@ export default class Client {
`${this.getGeneralRoute()}/log_client`,
{method: 'post', body}
);
}
};
// User routes
createUser = async (user) => {
return this.createUserWithInvite(user);
};
// TODO: add deep linking to emails so we can create accounts from within
// the mobile app
createUserWithInvite = async(user, data, emailHash, inviteId) => {
let url = `${this.getUsersRoute()}/create`;
url += '?d=' + encodeURIComponent(data);
if (emailHash) {
url += '&h=' + encodeURIComponent(emailHash);
}
if (inviteId) {
url += '&iid=' + encodeURIComponent(inviteId);
}
return this.doFetch(
`${this.getUsersRoute()}/create`,
url,
{method: 'post', body: JSON.stringify(user)}
);
}
};
login = async (loginId, password, token = '') => {
const body = {
@ -167,7 +195,7 @@ export default class Client {
}
return data;
}
};
logout = async () => {
const {response} = await this.doFetchWithResponse(
@ -178,14 +206,125 @@ export default class Client {
this.token = '';
}
return response;
}
};
updateUser = async (user) => {
return this.doFetch(
`${this.getUsersRoute()}/update`,
{method: 'post', body: JSON.stringify(user)}
);
};
updatePassword = async (userId, currentPassword, newPassword) => {
const data = {
user_id: userId,
current_password: currentPassword,
new_password: newPassword
};
return this.doFetch(
`${this.getUsersRoute()}/newpassword`,
{method: 'post', body: JSON.stringify(data)}
);
};
updateUserNotifyProps = async (notifyProps) => {
return this.doFetch(
`${this.getUsersRoute()}/update_notify`,
{method: 'post', body: JSON.stringify(notifyProps)}
);
};
updateUserRoles = async (userId, newRoles) => {
return this.doFetch(
`${this.getUserNeededRoute(userId)}/update_roles`,
{method: 'post', body: JSON.stringify({new_roles: newRoles})}
);
};
getMyPreferences = async () => {
return this.doFetch(
`${this.getPreferencesRoute()}/`,
{method: 'get'}
);
};
getProfiles = async (offset, limit) => {
return this.doFetch(
`${this.getUsersRoute()}/${offset}/${limit}`,
{method: 'get'}
);
};
getProfilesByIds = async (userIds) => {
return this.doFetch(
`${this.getUsersRoute()}/ids`,
{method: 'post', body: JSON.stringify(userIds)}
);
};
getProfilesInTeam = async (teamId, offset, limit) => {
return this.doFetch(
`${this.getTeamNeededRoute(teamId)}/users/${offset}/${limit}`,
{method: 'get'}
);
};
getProfilesInChannel = async (teamId, channelId, offset, limit) => {
return this.doFetch(
`${this.getChannelNeededRoute(teamId, channelId)}/users/${offset}/${limit}`,
{method: 'get'}
);
};
getProfilesNotInChannel = async (teamId, channelId, offset, limit) => {
return this.doFetch(
`${this.getChannelNeededRoute(teamId, channelId)}/users/not_in_channel/${offset}/${limit}`,
{method: 'get'}
);
};
getUser = async (userId) => {
return this.doFetch(
`${this.getUserNeededRoute(userId)}/get`,
{method: 'get'}
);
};
getStatusesByIds = async (userIds) => {
return this.doFetch(
`${this.getUsersRoute()}/status/ids`,
{method: 'post', body: JSON.stringify(userIds)}
);
};
getSessions = async (userId) => {
return this.doFetch(
`${this.getUserNeededRoute(userId)}/sessions`,
{method: 'get'}
);
};
revokeSession = async (id) => {
return this.doFetch(
`${this.getUsersRoute()}/revoke_session`,
{method: 'post', body: JSON.stringify({id})}
);
};
getAudits = async (userId) => {
return this.doFetch(
`${this.getUserNeededRoute(userId)}/audits`,
{method: 'get'}
);
};
getInitialLoad = async () => {
return this.doFetch(
`${this.getUsersRoute()}/initial_load`,
{method: 'get'}
);
}
};
// Team routes
@ -194,21 +333,49 @@ export default class Client {
`${this.getTeamsRoute()}/create`,
{method: 'post', body: JSON.stringify(team)}
);
}
};
getAllTeams = async() => {
return this.doFetch(
`${this.getTeamsRoute()}/all`,
{method: 'get'}
);
}
};
getMyTeamMembers = async() => {
return this.doFetch(
`${this.getTeamsRoute()}/members`,
{method: 'get'}
);
};
getAllTeamListings = async () => {
return this.doFetch(
`${this.getTeamsRoute()}/all_team_listings`,
{method: 'get'}
);
}
};
addUserToTeamFromInvite = async (inviteId, data, hash) => {
return this.doFetch(
`${this.getTeamsRoute()}/add_user_to_team_from_invite`,
{method: 'post', body: JSON.stringify({hash, data, invite_id: inviteId})}
);
};
addChannelMember = async (teamId, channelId, userId) => {
return this.doFetch(
`${this.getChannelNeededRoute(teamId, channelId)}/add`,
{method: 'post', body: JSON.stringify({user_id: userId})}
);
};
removeChannelMember = async (teamId, channelId, userId) => {
return this.doFetch(
`${this.getChannelNeededRoute(teamId, channelId)}/remove`,
{method: 'post', body: JSON.stringify({user_id: userId})}
);
};
// Channel routes
@ -217,21 +384,98 @@ export default class Client {
`${this.getChannelsRoute(channel.team_id)}/create`,
{method: 'post', body: JSON.stringify(channel)}
);
}
};
createDirectChannel = async (userId) => {
return this.doFetch(
`${this.getChannelsRoute('fake')}/create_direct`,
{method: 'post', body: JSON.stringify({user_id: userId})}
);
};
getChannel = async(teamId, channelId) => {
return this.doFetch(
`${this.getChannelNeededRoute(teamId, channelId)}/`,
{method: 'get'}
);
};
getChannels = async (teamId) => {
return this.doFetch(
`${this.getChannelsRoute(teamId)}/`,
{method: 'get'}
);
}
};
getMyChannelMembers = async (teamId) => {
return this.doFetch(
`${this.getChannelsRoute(teamId)}/members`,
{method: 'get'}
);
}
};
updateChannel = async (channel) => {
return this.doFetch(
`${this.getChannelsRoute(channel.team_id)}/update`,
{method: 'post', body: JSON.stringify(channel)}
);
};
updateChannelNotifyProps = async (teamId, data) => {
return this.doFetch(
`${this.getChannelsRoute(teamId)}/update_notify_props`,
{method: 'post', body: JSON.stringify(data)}
);
};
leaveChannel = async(teamId, channelId) => {
return this.doFetch(
`${this.getChannelNeededRoute(teamId, channelId)}/leave`,
{method: 'post'}
);
};
joinChannel = async(teamId, channelId) => {
return this.doFetch(
`${this.getChannelNeededRoute(teamId, channelId)}/join`,
{method: 'post'}
);
};
joinChannelByName = async(teamId, channelName) => {
return this.doFetch(
`${this.getChannelNameRoute(teamId, channelName)}/join`,
{method: 'post'}
);
};
deleteChannel = async(teamId, channelId) => {
return this.doFetch(
`${this.getChannelNeededRoute(teamId, channelId)}/delete`,
{method: 'post'}
);
};
updateLastViewedAt = async(teamId, channelId, active) => {
return this.doFetch(
`${this.getChannelNeededRoute(teamId, channelId)}/update_last_viewed_at`,
{method: 'post', body: JSON.stringify({active})}
);
};
getMoreChannels = async(teamId, offset, limit) => {
return this.doFetch(
`${this.getChannelsRoute(teamId)}/more/${offset}/${limit}`,
{method: 'get'}
);
};
getChannelStats = async(teamId, channelId) => {
return this.doFetch(
`${this.getChannelNeededRoute(teamId, channelId)}/stats`,
{method: 'get'}
);
};
// Post routes
fetchPosts = (teamId, channelId, onRequest, onSuccess, onFailure) => {
@ -242,20 +486,20 @@ export default class Client {
onSuccess,
onFailure
);
}
};
createPost = async (teamId, post) => {
return this.doFetch(
`${this.getPostsRoute(teamId, post.channel_id)}/create`,
{method: 'post', body: JSON.stringify(post)}
);
}
};
doFetch = async (url, options) => {
const {data} = await this.doFetchWithResponse(url, options);
return data;
}
};
doFetchWithResponse = async (url, options) => {
const response = await fetch(url, this.getOptions(options));
@ -288,6 +532,6 @@ export default class Client {
console.error(msg); // eslint-disable-line no-console
}
throw new Error(msg);
}
throw {message: msg, status_code: data.status_code, url};
};
}

View file

@ -3,7 +3,8 @@
import React, {Component, PropTypes} from 'react';
import {TouchableHighlight, Text} from 'react-native';
import * as logoutActions from 'actions/logout';
import {logout} from 'actions/users';
import {RequestStatus} from 'constants';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {Actions as Routes} from 'react-native-router-flux';
@ -17,8 +18,8 @@ class Logout extends Component {
static propTypes = propTypes;
componentWillReceiveProps(nextProps) {
if (this.props.logout.status === 'fetching' &&
nextProps.logout.status === 'fetched') {
if (this.props.logout.status === RequestStatus.STARTED &&
nextProps.logout.status === RequestStatus.SUCCESS) {
Routes.popTo('goToSelectServer');
}
}
@ -36,13 +37,13 @@ class Logout extends Component {
function mapStateToProps(state) {
return {
logout: state.views.logout
logout: state.requests.users.logout
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(logoutActions, dispatch)
actions: bindActionCreators({logout}, dispatch)
};
}

View file

@ -1,8 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
export const CHANNEL_RECEIVED = 'mattermost/channels/channel_received';
export const CHANNELS_RECEIVED = 'mattermost/channels/channels_received';
export const CHANNEL_MEMBER_RECEIVED = 'mattermost/channels/channel_member_received';
export const CHANNEL_MEMBERS_RECEVED = 'mattermost/channels/channel_members_received';

76
src/constants/channels.js Normal file
View file

@ -0,0 +1,76 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import keymirror from 'keymirror';
const ChannelTypes = keymirror({
CHANNEL_REQUEST: null,
CHANNEL_SUCCESS: null,
CHANNEL_FAILURE: null,
CHANNELS_REQUEST: null,
CHANNELS_SUCCESS: null,
CHANNELS_FAILURE: null,
CHANNEL_MEMBERS_REQUEST: null,
CHANNEL_MEMBERS_SUCCESS: null,
CHANNEL_MEMBERS_FAILURE: null,
CREATE_CHANNEL_REQUEST: null,
CREATE_CHANNEL_SUCCESS: null,
CREATE_CHANNEL_FAILURE: null,
UPDATE_CHANNEL_REQUEST: null,
UPDATE_CHANNEL_SUCCESS: null,
UPDATE_CHANNEL_FAILURE: null,
NOTIFY_PROPS_REQUEST: null,
NOTIFY_PROPS_SUCCESS: null,
NOTIFY_PROPS_FAILURE: null,
LEAVE_CHANNEL_REQUEST: null,
LEAVE_CHANNEL_SUCCESS: null,
LEAVE_CHANNEL_FAILURE: null,
JOIN_CHANNEL_REQUEST: null,
JOIN_CHANNEL_SUCCESS: null,
JOIN_CHANNEL_FAILURE: null,
DELETE_CHANNEL_REQUEST: null,
DELETE_CHANNEL_SUCCESS: null,
DELETE_CHANNEL_FAILURE: null,
UPDATE_LAST_VIEWED_REQUEST: null,
UPDATE_LAST_VIEWED_SUCCESS: null,
UPDATE_LAST_VIEWED_FAILURE: null,
MORE_CHANNELS_REQUEST: null,
MORE_CHANNELS_SUCCESS: null,
MORE_CHANNELS_FAILURE: null,
CHANNEL_STATS_REQUEST: null,
CHANNEL_STATS_SUCCESS: null,
CHANNEL_STATS_FAILURE: null,
ADD_CHANNEL_MEMBER_REQUEST: null,
ADD_CHANNEL_MEMBER_SUCCESS: null,
ADD_CHANNEL_MEMBER_FAILURE: null,
REMOVE_CHANNEL_MEMBER_REQUEST: null,
REMOVE_CHANNEL_MEMBER_SUCCESS: null,
REMOVE_CHANNEL_MEMBER_FAILURE: null,
SELECTED_CHANNEL: null,
LEAVE_CHANNEL: null,
RECEIVED_CHANNEL: null,
RECEIVED_CHANNELS: null,
RECEIVED_MY_CHANNEL_MEMBERS: null,
RECEIVED_MY_CHANNEL_MEMBER: null,
RECEIVED_MORE_CHANNELS: null,
RECEIVED_CHANNEL_STATS: null,
RECEIVED_CHANNEL_PROPS: null,
RECEIVED_CHANNEL_DELETED: null,
RECEIVED_LAST_VIEWED: null
});
export default ChannelTypes;

View file

@ -0,0 +1,15 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
const Constants = {
POST_CHUNK_SIZE: 60,
PROFILE_CHUNK_SIZE: 100,
CHANNELS_CHUNK_SIZE: 50,
CHANNEL_USER_ROLE: 'channel_user',
CHANNEL_ADMIN_ROLE: 'channel_admin',
CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show'
};
export default Constants;

View file

@ -1,6 +1,12 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
export const DEVICE_REQUEST = 'DEVICE_REQUEST';
export const DEVICE_SUCCESS = 'DEVICE_SUCCESS';
export const DEVICE_FAILURE = 'DEVICE_FAILURE';
import keymirror from 'keymirror';
const DeviceTypes = keymirror({
DEVICE_REQUEST: null,
DEVICE_SUCCESS: null,
DEVICE_FAILURE: null
});
export default DeviceTypes;

View file

@ -1,14 +1,28 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
export const PING_REQUEST = 'PING_REQUEST';
export const PING_SUCCESS = 'PING_SUCCESS';
export const PING_FAILURE = 'PING_FAILURE';
import keymirror from 'keymirror';
export const CLIENT_CONFIG_REQUEST = 'CLIENT_CONFIG_REQUEST';
export const CLIENT_CONFIG_SUCCESS = 'CLIENT_CONFIG_SUCCESS';
export const CLIENT_CONFIG_FAILURE = 'CLIENT_CONFIG_FAILURE';
const GeneralTypes = keymirror({
PING_REQUEST: null,
PING_SUCCESS: null,
PING_FAILURE: null,
export const LOG_CLIENT_ERROR_REQUEST = 'LOG_CLIENT_ERROR_REQUEST';
export const LOG_CLIENT_ERROR_SUCCESS = 'LOG_CLIENT_ERROR_SUCCESS';
export const LOG_CLIENT_ERROR_FAILURE = 'LOG_CLIENT_ERROR_FAILURE';
CLIENT_CONFIG_REQUEST: null,
CLIENT_CONFIG_SUCCESS: null,
CLIENT_CONFIG_FAILURE: null,
CLIENT_CONFIG_RECEIVED: null,
CLIENT_LICENSE_REQUEST: null,
CLIENT_LICENSE_SUCCESS: null,
CLIENT_LICENSE_FAILURE: null,
CLIENT_LICENSE_RECEIVED: null,
LOG_CLIENT_ERROR_REQUEST: null,
LOG_CLIENT_ERROR_SUCCESS: null,
LOG_CLIENT_ERROR_FAILURE: null,
SERVER_URL_CHANGED: null
});
export default GeneralTypes;

View file

@ -1,20 +1,22 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import * as ChannelTypes from './channel';
import * as DeviceTypes from './device';
import * as GeneralTypes from './general';
import * as LoginTypes from './login';
import * as LogoutTypes from './logout';
import * as TeamsTypes from './teams';
import * as PostsTypes from './posts';
import Constants from './constants';
import ChannelTypes from './channels';
import DeviceTypes from './device';
import GeneralTypes from './general';
import UsersTypes from './users';
import TeamsTypes from './teams';
import PostsTypes from './posts';
import RequestStatus from './request_status';
export {
Constants,
DeviceTypes,
GeneralTypes,
LoginTypes,
LogoutTypes,
UsersTypes,
TeamsTypes,
ChannelTypes,
PostsTypes
PostsTypes,
RequestStatus
};

View file

@ -1,6 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
export const LOGIN_REQUEST = 'LOGIN_REQUEST';
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_FAILURE = 'LOGIN_FAILURE';

View file

@ -1,6 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
export const LOGOUT_REQUEST = 'LOGOUT_REQUEST';
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
export const LOGOUT_FAILURE = 'LOGOUT_FAILURE';

View file

@ -1,6 +1,22 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
export const FETCH_POSTS_REQUEST = 'FETCH_POSTS_REQUEST';
export const FETCH_POSTS_SUCCESS = 'FETCH_POSTS_SUCCESS';
export const FETCH_POSTS_FAILURE = 'FETCH_POSTS_FAILURE';
import keymirror from 'keymirror';
const PostsTypes = keymirror({
FETCH_POSTS_REQUEST: null,
FETCH_POSTS_SUCCESS: null,
FETCH_POSTS_FAILURE: null,
RECEIVED_POST: null,
RECEIVED_POSTS: null,
RECEIVED_FOCUSED_POST: null,
RECEIVED_POST_SELECTED: null,
RECEIVED_EDIT_POST: null,
CREATE_POST: null,
CREATE_COMMENT: null,
POST_DELETED: null,
REMOVE_POST: null
});
export default PostsTypes;

View file

@ -0,0 +1,9 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
export default {
NOT_STARTED: 'not_started',
STARTED: 'started',
SUCCESS: 'success',
FAILURE: 'failure'
};

View file

@ -1,8 +1,22 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
export const SELECT_TEAM = 'SELECT_TEAM';
import keymirror from 'keymirror';
export const FETCH_TEAMS_REQUEST = 'FETCH_TEAMS_REQUEST';
export const FETCH_TEAMS_SUCCESS = 'FETCH_TEAMS_SUCCESS';
export const FETCH_TEAMS_FAILURE = 'FETCH_TEAMS_FAILURE';
const TeamTypes = keymirror({
FETCH_TEAMS_REQUEST: null,
FETCH_TEAMS_SUCCESS: null,
FETCH_TEAMS_FAILURE: null,
CREATED_TEAM: null,
SELECT_TEAM: null,
UPDATED_TEAM: null,
RECEIVED_ALL_TEAMS: null,
RECEIVED_MY_TEAM_MEMBERS: null,
RECEIVED_TEAM_LISTINGS: null,
RECEIVED_MEMBERS_IN_TEAM: null,
RECEIVED_TEAM_STATS: null,
LEAVE_TEAM: null
});
export default TeamTypes;

66
src/constants/users.js Normal file
View file

@ -0,0 +1,66 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import keymirror from 'keymirror';
const UserTypes = keymirror({
LOGIN_ID_CHANGED: null,
PASSWORD_CHANGED: null,
LOGIN_REQUEST: null,
LOGIN_SUCCESS: null,
LOGIN_FAILURE: null,
LOGOUT_REQUEST: null,
LOGOUT_SUCCESS: null,
LOGOUT_FAILURE: null,
PROFILES_REQUEST: null,
PROFILES_SUCCESS: null,
PROFILES_FAILURE: null,
PROFILES_IN_TEAM_REQUEST: null,
PROFILES_IN_TEAM_SUCCESS: null,
PROFILES_IN_TEAM_FAILURE: null,
PROFILES_IN_CHANNEL_REQUEST: null,
PROFILES_IN_CHANNEL_SUCCESS: null,
PROFILES_IN_CHANNEL_FAILURE: null,
PROFILES_NOT_IN_CHANNEL_REQUEST: null,
PROFILES_NOT_IN_CHANNEL_SUCCESS: null,
PROFILES_NOT_IN_CHANNEL_FAILURE: null,
PROFILES_STATUSES_REQUEST: null,
PROFILES_STATUSES_SUCCESS: null,
PROFILES_STATUSES_FAILURE: null,
SESSIONS_REQUEST: null,
SESSIONS_SUCCESS: null,
SESSIONS_FAILURE: null,
REVOKE_SESSION_REQUEST: null,
REVOKE_SESSION_SUCCESS: null,
REVOKE_SESSION_FAILURE: null,
AUDITS_REQUEST: null,
AUDITS_SUCCESS: null,
AUDITS_FAILURE: null,
RECEIVED_ME: null,
RECEIVED_PROFILES: null,
RECEIVED_PROFILES_IN_TEAM: null,
RECEIVED_PROFILES_IN_CHANNEL: null,
RECEIVED_PROFILE_IN_CHANNEL: null,
RECEIVED_PROFILES_NOT_IN_CHANNEL: null,
RECEIVED_PROFILE_NOT_IN_CHANNEL: null,
RECEIVED_SESSIONS: null,
RECEIVED_REVOKED_SESSION: null,
RECEIVED_AUDITS: null,
RECEIVED_STATUSES: null,
RECEIVED_PREFERENCE: null,
RECEIVED_PREFERENCES: null,
DELETED_PREFERENCES: null
});
export default UserTypes;

View file

@ -3,20 +3,20 @@
import React from 'react';
import Config from 'config/config.js';
import Config from 'config';
import {connect} from 'react-redux';
import {loadDevice} from 'actions/device';
import Loading from 'components/loading';
import Routes from 'routes';
import {getTranslations} from 'i18n/i18n.js';
import {getTranslations} from 'i18n';
import {IntlProvider} from 'react-intl';
class RootContainer extends React.Component {
static propTypes = {
device: React.PropTypes.object.isRequired,
loadDevice: React.PropTypes.func.isRequired
}
};
constructor(props) {
super(props);

18
src/i18n/index.js Normal file
View file

@ -0,0 +1,18 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import 'intl';
import en from './en.json';
import es from './es.json';
const DEFAULT_LOCALE = 'en';
const TRANSLATIONS = {
en,
es
};
export function getTranslations(locale) {
return TRANSLATIONS[locale] || TRANSLATIONS[DEFAULT_LOCALE];
}

18
src/mattermost.js Normal file
View file

@ -0,0 +1,18 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {Provider} from 'react-redux';
import store from 'store';
import RootContainer from 'containers/root_container.js';
export default class Mattermost extends React.Component {
render() {
return (
<Provider store={store}>
<RootContainer/>
</Provider>
);
}
}

View file

@ -1,99 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {ChannelTypes} from 'constants';
import {combineReducers} from 'redux';
export function channels(state = {}, action) {
switch (action.type) {
case ChannelTypes.CHANNEL_RECEIVED:
return {
...state,
[action.channel.id]: action.channel
};
case ChannelTypes.CHANNELS_RECEIVED: {
const nextState = {...state};
for (const channel of action.channels) {
nextState[channel.id] = channel;
}
return nextState;
}
default:
return state;
}
}
export function channelIdsByTeamId(state = {}, action) {
switch (action.type) {
case ChannelTypes.CHANNEL_RECEIVED: {
const channel = action.channel;
return {
...state,
[channel.team_id]: {
...state[channel.team_id],
[channel.id]: channel.id
}
};
}
case ChannelTypes.CHANNELS_RECEIVED: {
const nextState = {...state};
for (const channel of action.channels) {
nextState[channel.team_id] = {
...nextState[channel.team_id],
[channel.id]: channel.id
};
}
return nextState;
}
default:
return state;
}
}
export function channelMembers(state = {}, action) {
switch (action.type) {
case ChannelTypes.CHANNEL_MEMBER_RECEIVED: {
const channelMember = action.channelMember;
return {
...state,
[`${channelMember.channel_id}-${channelMember.user_id}`]: channelMember
};
}
case ChannelTypes.CHANNEL_MEMBERS_RECEIVED: {
const nextState = {...state};
for (const channelMember of action.channelMembers) {
nextState[`${channelMember.channel_id}-${channelMember.user_id}`] = channelMember;
}
return nextState;
}
default:
return state;
}
}
export default combineReducers({
// A map of channel IDs to channels
channels,
// A map of team IDs to pseudo-sets of channelIds
channelIdsByTeamId,
// A map of "channelId-userId" to channel members
channelMembers
});

View file

@ -1,16 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {handle, initialState} from './helpers.js';
import {DeviceTypes} from 'constants';
export const initState = initialState();
export default function device(state = initState, action) {
return handle(
DeviceTypes.DEVICE_REQUEST,
DeviceTypes.DEVICE_SUCCESS,
DeviceTypes.DEVICE_FAILURE,
state,
action
);
}

View file

@ -0,0 +1,146 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {ChannelTypes, UsersTypes} from 'constants';
import {combineReducers} from 'redux';
function currentId(state = '', action) {
switch (action.type) {
case UsersTypes.LOGOUT_SUCCESS:
return '';
default:
return state;
}
}
function channels(state = {}, action) {
const nextState = {...state};
switch (action.type) {
case ChannelTypes.RECEIVED_CHANNEL:
return {
...state,
[action.data.id]: action.data
};
case ChannelTypes.RECEIVED_CHANNELS: {
for (const channel of action.data) {
nextState[channel.id] = channel;
}
return nextState;
}
case ChannelTypes.LEAVE_CHANNEL:
case ChannelTypes.RECEIVED_CHANNEL_DELETED: {
Reflect.deleteProperty(nextState, action.channel_id);
return nextState;
}
case UsersTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function myMembers(state = {}, action) {
const nextState = {...state};
switch (action.type) {
case ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER: {
const channelMember = action.data;
return {
...state,
[channelMember.channel_id]: channelMember
};
}
case ChannelTypes.RECEIVED_MY_CHANNEL_MEMBERS: {
for (const cm of action.data) {
nextState[cm.channel_id] = cm;
}
return nextState;
}
case ChannelTypes.RECEIVED_CHANNEL_PROPS: {
const member = {...state[action.channel_id]};
member.notify_props = action.data;
return {
...state,
[action.channel_id]: member
};
}
case ChannelTypes.RECEIVED_LAST_VIEWED: {
const member = {...state[action.channel_id]};
member.last_viewed_at = action.last_viewed_at;
return {
...state,
[action.channel_id]: member
};
}
case ChannelTypes.LEAVE_CHANNEL:
case ChannelTypes.RECEIVED_CHANNEL_DELETED:
Reflect.deleteProperty(nextState, action.channel_id);
return nextState;
case UsersTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function moreChannels(state = {}, action) {
const nextState = {...state};
switch (action.type) {
case ChannelTypes.RECEIVED_MORE_CHANNELS: {
for (const channel of action.data) {
nextState[channel.id] = channel;
}
return nextState;
}
case ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER: {
const channelMember = action.data;
Reflect.deleteProperty(nextState, channelMember.channel_id);
return nextState;
}
case UsersTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function stats(state = {}, action) {
switch (action.type) {
case ChannelTypes.RECEIVED_CHANNEL_STATS: {
const nextState = {...state};
const stat = action.data;
nextState[stat.channel_id] = stat;
return nextState;
}
case UsersTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
export default combineReducers({
// the current selected channel
currentId,
// object where every key is the channel id and has and object with the channel detail
channels,
//object where every key is the channel id and has and object with the channel members detail
myMembers,
// object where every key is the channel id and has a object with the channel detail where the user is not a current member
moreChannels,
// object where every key is the team id and has an object with the team stats
stats
});

View file

@ -0,0 +1,32 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {combineReducers} from 'redux';
import {GeneralTypes, UsersTypes} from 'constants';
function config(state = {}, action) {
switch (action.type) {
case GeneralTypes.CLIENT_CONFIG_RECEIVED:
return Object.assign({}, state, action.data);
case UsersTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function license(state = {}, action) {
switch (action.type) {
case GeneralTypes.CLIENT_LICENSE_RECEIVED:
return Object.assign({}, state, action.data);
case UsersTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
export default combineReducers({
config,
license
});

View file

@ -0,0 +1,136 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
export function initialPagingState() {
return {
items: new Set(),
offset: 0,
count: 0
};
}
function isPostListNull(pl) {
if (!pl) {
return true;
}
if (!pl.posts) {
return true;
}
return !pl.order;
}
function makePostListNonNull(pl) {
let postList = {...pl};
if (!postList) {
postList = {order: [], posts: {}};
}
if (!postList.order) {
postList.order = [];
}
if (!postList.posts) {
postList.posts = {};
}
return postList;
}
export function addPosts(state, action) {
const newState = {...state};
const newPosts = action.data;
const id = action.channel_id;
if (isPostListNull(newPosts)) {
return state;
}
if (action.checkLatest) {
const currentLatest = newState.latestPageTime[id] || 0;
if (newPosts.order.length >= 1) {
const newLatest = newPosts.posts[newPosts.order[0]].create_at || 0;
if (newLatest > currentLatest) {
newState.latestPageTime = {
...newState.latestPageTime,
[id]: newLatest
};
}
} else if (currentLatest === 0) {
// Mark that an empty page was received
newState.latestPageTime = {
...newState.latestPageTime,
[id]: 1
};
}
}
const combinedPosts = {...makePostListNonNull(newState.postsInfo[id].postList)};
for (const pid in newPosts.posts) {
if (newPosts.posts.hasOwnProperty(pid)) {
const np = newPosts.posts[pid];
if (np.delete_at === 0) {
combinedPosts.posts[pid] = np;
if (combinedPosts.order.indexOf(pid) === -1 && newPosts.order.indexOf(pid) !== -1) {
combinedPosts.order.push(pid);
}
} else if (combinedPosts.posts.hasOwnProperty(pid)) {
combinedPosts.posts[pid] = {...np, state: 'DELETED', fileIds: []};
}
}
}
combinedPosts.order.sort((a, b) => {
if (combinedPosts.posts[a].create_at > combinedPosts.posts[b].create_at) {
return -1;
}
if (combinedPosts.posts[a].create_at < combinedPosts.posts[b].create_at) {
return 1;
}
return 0;
});
newState.postsInfo = {
...newState.postsInfo,
[id]: {
...newState.postsInfo[id],
postList: combinedPosts
}
};
return newState;
}
export function profilesToSet(state, action) {
const nextState = {...state};
if (action.offset != null && action.count != null) {
nextState.offset = action.offset + action.count;
nextState.count += action.count;
}
nextState.items = new Set(state.items);
Object.keys(action.data).forEach((key) => {
nextState.items.add(key);
});
return nextState;
}
export function addProfileToSet(state, profileId) {
const nextState = {...state};
nextState.items = new Set(state.items);
nextState.items.add(profileId);
return nextState;
}
export function removeProfileFromSet(state, profileId) {
const nextState = {...state};
nextState.items = new Set(state.items);
nextState.items.delete(profileId);
return nextState;
}

View file

@ -0,0 +1,18 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {combineReducers} from 'redux';
import channels from './channels';
import general from './general';
import users from './users';
import teams from './teams';
import posts from './posts';
export default combineReducers({
general,
users,
teams,
channels,
posts
});

View file

@ -0,0 +1,59 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {PostsTypes, UsersTypes} from 'constants';
import {combineReducers} from 'redux';
import {addPosts} from './helpers';
function selectedPostId(state = '', action) {
switch (action.type) {
case UsersTypes.LOGOUT_SUCCESS:
return '';
default:
return state;
}
}
function currentFocusedPostId(state = '', action) {
switch (action.type) {
case UsersTypes.LOGOUT_SUCCESS:
return '';
default:
return state;
}
}
function postsInfo(state = {}, action) {
switch (action.type) {
case PostsTypes.FETCH_POSTS_SUCCESS:
return addPosts(state, action);
case UsersTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function latestPageTime(state = {}, action) {
switch (action.type) {
case UsersTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
export default combineReducers({
// the current selected post
selectedPostId,
// the current selected focused post (permalink view)
currentFocusedPostId,
// object where every key is the channel id and has and object with the postList
postsInfo,
// object where the every key is the channel id and has the timestamp of the latest post created in that channel
latestPageTime
});

View file

@ -0,0 +1,100 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {combineReducers} from 'redux';
import {TeamsTypes, UsersTypes} from 'constants';
function currentId(state = '', action) {
switch (action.type) {
case TeamsTypes.SELECT_TEAM:
return action.teamId;
default:
return state;
}
}
function teams(state = {}, action) {
switch (action.type) {
case TeamsTypes.RECEIVED_ALL_TEAMS:
return Object.assign({}, state, action.data);
case UsersTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function myMembers(state = {}, action) {
const nextState = {...state};
switch (action.type) {
case TeamsTypes.RECEIVED_MY_TEAM_MEMBERS: {
const members = action.data;
for (const m of members) {
nextState[m.team_id] = m;
}
return nextState;
}
case TeamsTypes.LEAVE_TEAM: {
const data = action.team;
Reflect.deleteProperty(nextState, data.team_id);
return nextState;
}
case UsersTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function membersInTeam(state = {}, action) {
switch (action.type) {
default:
return state;
}
}
function membersNotInTeam(state = {}, action) {
switch (action.type) {
default:
return state;
}
}
function stats(state = {}, action) {
switch (action.type) {
default:
return state;
}
}
function openTeamIds(state = new Set(), action) {
switch (action.type) {
default:
return state;
}
}
export default combineReducers({
// the current selected team
currentId,
// object where every key is the team id and has and object with the team detail
teams,
//object where every key is the team id and has and object with the team members detail
myMembers,
// object where every key is the team id and has a Set of user ids that are members in the team
membersInTeam,
// object where every key is the team id and has a Set of user ids that aren't members in the team
membersNotInTeam,
// object where every key is the team id and has an object with the team stats
stats,
// Set with the team ids the user is not a member of
openTeamIds
});

View file

@ -0,0 +1,216 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {combineReducers} from 'redux';
import {UsersTypes} from 'constants';
import {initialPagingState, profilesToSet, addProfileToSet, removeProfileFromSet} from './helpers';
function currentId(state = '', action) {
switch (action.type) {
case UsersTypes.RECEIVED_ME:
return action.data.id;
case UsersTypes.LOGOUT_SUCCESS:
return '';
}
return state;
}
function myPreferences(state = {}, action) {
const nextState = {...state};
switch (action.type) {
case UsersTypes.RECEIVED_PREFERENCES: {
const preferences = action.data;
for (const p of preferences) {
nextState[`${p.category}--${p.name}`] = p.value;
}
return nextState;
}
case UsersTypes.RECEIVED_PREFERENCE: {
const p = action.data;
nextState[`${p.category}--${p.name}`] = p.value;
return nextState;
}
case UsersTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function mySessions(state = [], action) {
switch (action.type) {
case UsersTypes.RECEIVED_SESSIONS:
return [...action.data];
case UsersTypes.RECEIVED_REVOKED_SESSION: {
let index = -1;
const length = state.length;
for (let i = 0; i < length; i++) {
if (state[i].id === action.data.id) {
index = i;
break;
}
}
if (index > -1) {
return state.slice(0, index).concat(state.slice(index + 1));
}
return state;
}
case UsersTypes.LOGOUT_SUCCESS:
return [];
default:
return state;
}
}
function myAudits(state = [], action) {
switch (action.type) {
case UsersTypes.RECEIVED_AUDITS:
return [...action.data];
case UsersTypes.LOGOUT_SUCCESS:
return [];
default:
return state;
}
}
function profiles(state = {items: {}, offset: 0, count: 0}, action) {
const nextState = {...state};
switch (action.type) {
case UsersTypes.RECEIVED_ME: {
const id = action.data.id;
nextState.items = {
...nextState.items,
[id]: {
...nextState.items[id],
...action.data
}
};
return nextState;
}
case UsersTypes.RECEIVED_PROFILES:
if (action.offset != null && action.count != null) {
nextState.offset = action.offset + action.count;
nextState.count += action.count;
}
nextState.items = Object.assign({}, nextState.items, action.data);
return nextState;
case UsersTypes.LOGOUT_SUCCESS:
return {items: {}, offset: 0, count: 0};
default:
return state;
}
}
function profilesInTeam(state = initialPagingState(), action) {
switch (action.type) {
case UsersTypes.RECEIVED_PROFILES_IN_TEAM:
return profilesToSet(state, action);
case UsersTypes.LOGOUT_SUCCESS:
return initialPagingState();
default:
return state;
}
}
function profilesInChannel(state = initialPagingState(), action) {
switch (action.type) {
case UsersTypes.RECEIVED_PROFILE_IN_CHANNEL:
return addProfileToSet(state, action.user_id);
case UsersTypes.RECEIVED_PROFILES_IN_CHANNEL:
return profilesToSet(state, action);
case UsersTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL:
return removeProfileFromSet(state, action.user_id);
case UsersTypes.LOGOUT_SUCCESS:
return initialPagingState();
default:
return state;
}
}
function profilesNotInChannel(state = initialPagingState(), action) {
switch (action.type) {
case UsersTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL:
return addProfileToSet(state, action.user_id);
case UsersTypes.RECEIVED_PROFILES_NOT_IN_CHANNEL:
return profilesToSet(state, action);
case UsersTypes.RECEIVED_PROFILE_IN_CHANNEL:
return removeProfileFromSet(state, action.user_id);
case UsersTypes.LOGOUT_SUCCESS:
return initialPagingState();
default:
return state;
}
}
function statuses(state = {}, action) {
switch (action.type) {
case UsersTypes.RECEIVED_STATUSES: {
const nextState = {...state};
return Object.assign({}, nextState, action.data);
}
case UsersTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
export default combineReducers({
// the current selected user
currentId,
// object where the key is the category-name and has the corresponding value
myPreferences,
// array with the user's sessions
mySessions,
// array with the user's audits
myAudits,
// object containing items, count and offset where items is an object where every key is a user id
// and has an object with the users details
profiles,
// object containing items, count and offset where items is an object where every key is a user id
// and has a Set with the users id that are members of the team
profilesInTeam,
// object containing items, count and offset where items is an object where every key is a user id
// and has a Set with the users id that are members of the channel
profilesInChannel,
// object containing items, count and offset where items is an object where every key is a user id
// and has a Set with the users id that are members of the channel
profilesNotInChannel,
// object where every key is the user id and has a value with the current status of each user
statuses
});

View file

@ -1,60 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {combineReducers} from 'redux';
import {GeneralTypes} from 'constants';
function ping(state = {}, action) {
switch (action.type) {
case GeneralTypes.PING_REQUEST:
return {
...action.data,
loading: true
};
case GeneralTypes.PING_SUCCESS:
return {
...action.data,
loading: false,
error: null
};
case GeneralTypes.PING_FAILURE:
return {
...action.data,
loading: false,
error: action.error
};
default:
return state;
}
}
function clientConfig(state = {}, action) {
switch (action.type) {
case GeneralTypes.CLIENT_CONFIG_REQUEST:
return {
...action.data,
loading: true
};
case GeneralTypes.CLIENT_CONFIG_SUCCESS:
return {
...action.data,
loading: false,
error: null
};
case GeneralTypes.CLIENT_CONFIG_FAILURE:
return {
...action.data,
loading: false,
error: action.error
};
default:
return state;
}
}
export default combineReducers({
ping,
clientConfig
});

View file

@ -3,28 +3,12 @@
import {combineReducers} from 'redux';
import channel from './channel.js';
import device from './device.js';
import general from './general.js';
import login from './login';
import logout from './logout';
import teams from './teams';
import posts from './posts';
const entities = combineReducers({
channel,
general,
teams,
posts
});
const views = combineReducers({
device,
login,
logout
});
import entities from './entities';
import requests from './requests';
import views from './views';
export default combineReducers({
entities,
requests,
views
});

View file

@ -1,37 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {LoginTypes, LogoutTypes} from 'constants';
export const initState = {
status: 'not fetched',
error: null
};
export default function reduceLogin(state = initState, action) {
switch (action.type) {
case LoginTypes.LOGIN_REQUEST:
return {
...state,
status: 'fetching',
error: null
};
case LoginTypes.LOGIN_SUCCESS:
return {
...state,
status: 'fetched'
};
case LoginTypes.LOGIN_FAILURE:
return {
...state,
status: 'failed',
error: action.error
};
case LogoutTypes.LOGOUT_SUCCESS:
return initState;
default:
return state;
}
}

View file

@ -1,31 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {LogoutTypes} from 'constants';
export const initState = {
status: 'not fetched',
error: null
};
export default function reduceLogout(state = initState, action) {
switch (action.type) {
case LogoutTypes.LOGOUT_REQUEST:
return {...state,
status: 'fetching',
error: null
};
case LogoutTypes.LOGOUT_SUCCESS:
return {...state,
status: 'fetched'
};
case LogoutTypes.LOGOUT_FAILURE:
return {...state,
status: 'failed',
error: action.error
};
default:
return state;
}
}

View file

@ -1,36 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {PostsTypes, LogoutTypes} from 'constants';
export const initState = {
status: 'not fetched',
error: null,
data: {}
};
export default function reducePosts(state = initState, action) {
switch (action.type) {
case PostsTypes.FETCH_POSTS_REQUEST:
return {...state,
status: 'fetching',
error: null
};
case PostsTypes.FETCH_POSTS_SUCCESS:
return {...state,
status: 'fetched',
data: action.data.posts
};
case PostsTypes.FETCH_POSTS_FAILURE:
return {...state,
status: 'failed',
error: action.error
};
case LogoutTypes.LOGOUT_SUCCESS:
return initState;
default:
return state;
}
}

View file

@ -0,0 +1,164 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {handleRequest, initialRequestState} from './helpers';
import {ChannelTypes} from 'constants';
import {combineReducers} from 'redux';
function getChannel(state = initialRequestState(), action) {
return handleRequest(
ChannelTypes.CHANNEL_REQUEST,
ChannelTypes.CHANNEL_SUCCESS,
ChannelTypes.CHANNEL_FAILURE,
state,
action
);
}
function getChannels(state = initialRequestState(), action) {
return handleRequest(
ChannelTypes.CHANNELS_REQUEST,
ChannelTypes.CHANNELS_SUCCESS,
ChannelTypes.CHANNELS_FAILURE,
state,
action
);
}
function myMembers(state = initialRequestState(), action) {
return handleRequest(
ChannelTypes.CHANNEL_MEMBERS_REQUEST,
ChannelTypes.CHANNEL_MEMBERS_SUCCESS,
ChannelTypes.CHANNEL_MEMBERS_FAILURE,
state,
action
);
}
function createChannel(state = initialRequestState(), action) {
return handleRequest(
ChannelTypes.CREATE_CHANNEL_REQUEST,
ChannelTypes.CREATE_CHANNEL_SUCCESS,
ChannelTypes.CREATE_CHANNEL_FAILURE,
state,
action
);
}
function updateChannel(state = initialRequestState(), action) {
return handleRequest(
ChannelTypes.UPDATE_CHANNEL_REQUEST,
ChannelTypes.UPDATE_CHANNEL_SUCCESS,
ChannelTypes.UPDATE_CHANNEL_FAILURE,
state,
action
);
}
function updateChannelNotifyProps(state = initialRequestState(), action) {
return handleRequest(
ChannelTypes.NOTIFY_PROPS_REQUEST,
ChannelTypes.NOTIFY_PROPS_SUCCESS,
ChannelTypes.NOTIFY_PROPS_FAILURE,
state,
action
);
}
function leaveChannel(state = initialRequestState(), action) {
return handleRequest(
ChannelTypes.LEAVE_CHANNEL_REQUEST,
ChannelTypes.LEAVE_CHANNEL_SUCCESS,
ChannelTypes.LEAVE_CHANNEL_FAILURE,
state,
action
);
}
function joinChannel(state = initialRequestState(), action) {
return handleRequest(
ChannelTypes.JOIN_CHANNEL_REQUEST,
ChannelTypes.JOIN_CHANNEL_SUCCESS,
ChannelTypes.JOIN_CHANNEL_FAILURE,
state,
action
);
}
function deleteChannel(state = initialRequestState(), action) {
return handleRequest(
ChannelTypes.DELETE_CHANNEL_REQUEST,
ChannelTypes.DELETE_CHANNEL_SUCCESS,
ChannelTypes.DELETE_CHANNEL_FAILURE,
state,
action
);
}
function updateLastViewedAt(state = initialRequestState(), action) {
return handleRequest(
ChannelTypes.UPDATE_LAST_VIEWED_REQUEST,
ChannelTypes.UPDATE_LAST_VIEWED_SUCCESS,
ChannelTypes.UPDATE_LAST_VIEWED_FAILURE,
state,
action
);
}
function getMoreChannels(state = initialRequestState(), action) {
return handleRequest(
ChannelTypes.MORE_CHANNELS_REQUEST,
ChannelTypes.MORE_CHANNELS_SUCCESS,
ChannelTypes.MORE_CHANNELS_FAILURE,
state,
action
);
}
function getChannelStats(state = initialRequestState(), action) {
return handleRequest(
ChannelTypes.CHANNEL_STATS_REQUEST,
ChannelTypes.CHANNEL_STATS_SUCCESS,
ChannelTypes.CHANNEL_STATS_FAILURE,
state,
action
);
}
function addChannelMember(state = initialRequestState(), action) {
return handleRequest(
ChannelTypes.ADD_CHANNEL_MEMBER_REQUEST,
ChannelTypes.ADD_CHANNEL_MEMBER_SUCCESS,
ChannelTypes.ADD_CHANNEL_MEMBER_FAILURE,
state,
action
);
}
function removeChannelMember(state = initialRequestState(), action) {
return handleRequest(
ChannelTypes.REMOVE_CHANNEL_MEMBER_REQUEST,
ChannelTypes.REMOVE_CHANNEL_MEMBER_SUCCESS,
ChannelTypes.REMOVE_CHANNEL_MEMBER_FAILURE,
state,
action
);
}
export default combineReducers({
getChannel,
getChannels,
myMembers,
createChannel,
updateChannel,
updateChannelNotifyProps,
leaveChannel,
joinChannel,
deleteChannel,
updateLastViewedAt,
getMoreChannels,
getChannelStats,
addChannelMember,
removeChannelMember
});

View file

@ -0,0 +1,42 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {combineReducers} from 'redux';
import {GeneralTypes} from 'constants';
import {handleRequest, initialRequestState} from './helpers';
function server(state = initialRequestState(), action) {
return handleRequest(
GeneralTypes.PING_REQUEST,
GeneralTypes.PING_SUCCESS,
GeneralTypes.PING_FAILURE,
state,
action
);
}
function config(state = initialRequestState(), action) {
return handleRequest(
GeneralTypes.CLIENT_CONFIG_REQUEST,
GeneralTypes.CLIENT_CONFIG_SUCCESS,
GeneralTypes.CLIENT_CONFIG_FAILURE,
state,
action
);
}
function license(state = initialRequestState(), action) {
return handleRequest(
GeneralTypes.CLIENT_LICENSE_REQUEST,
GeneralTypes.CLIENT_LICENSE_SUCCESS,
GeneralTypes.CLIENT_LICENSE_FAILURE,
state,
action
);
}
export default combineReducers({
server,
config,
license
});

View file

@ -1,38 +1,37 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
export function initialState() {
import {RequestStatus} from 'constants';
export function initialRequestState() {
return {
data: {},
loading: false,
status: RequestStatus.NOT_STARTED,
error: null
};
}
export function handle(REQUEST, SUCCESS, FAILURE, state, action) {
export function handleRequest(REQUEST, SUCCESS, FAILURE, state, action) {
switch (action.type) {
case REQUEST:
return {
...state,
loading: true
status: RequestStatus.STARTED
};
case SUCCESS:
return {
...state,
status: RequestStatus.SUCCESS,
data: action.data,
loading: false,
error: null
};
case FAILURE:
return {
...state,
loading: false,
status: RequestStatus.FAILURE,
error: action.error
};
default:
return state;
}
}
}

View file

@ -0,0 +1,16 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {combineReducers} from 'redux';
import general from './general';
import channels from './channels';
import users from './users';
import teams from './teams';
export default combineReducers({
general,
channels,
users,
teams
});

View file

@ -0,0 +1,21 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {handleRequest, initialRequestState} from './helpers';
import {TeamsTypes} from 'constants';
import {combineReducers} from 'redux';
function allTeams(state = initialRequestState(), action) {
return handleRequest(
TeamsTypes.FETCH_TEAMS_REQUEST,
TeamsTypes.FETCH_TEAMS_SUCCESS,
TeamsTypes.FETCH_TEAMS_FAILURE,
state,
action
);
}
export default combineReducers({
allTeams
});

View file

@ -0,0 +1,129 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {handleRequest, initialRequestState} from './helpers';
import {UsersTypes, RequestStatus} from 'constants';
import {combineReducers} from 'redux';
function login(state = initialRequestState(), action) {
switch (action.type) {
case UsersTypes.LOGIN_REQUEST:
return {...state, status: RequestStatus.STARTED};
case UsersTypes.LOGIN_SUCCESS:
return {...state, status: RequestStatus.SUCCESS, error: null};
case UsersTypes.LOGIN_FAILURE:
return {...state, status: RequestStatus.FAILURE, error: action.error};
case UsersTypes.LOGOUT_SUCCESS:
return {...state, status: RequestStatus.NOT_STARTED, error: null};
default:
return state;
}
}
function logout(state = initialRequestState(), action) {
return handleRequest(
UsersTypes.LOGOUT_REQUEST,
UsersTypes.LOGOUT_SUCCESS,
UsersTypes.LOGOUT_FAILURE,
state,
action
);
}
function getProfiles(state = initialRequestState(), action) {
return handleRequest(
UsersTypes.PROFILES_REQUEST,
UsersTypes.PROFILES_SUCCESS,
UsersTypes.PROFILES_FAILURE,
state,
action
);
}
function getProfilesInTeam(state = initialRequestState(), action) {
return handleRequest(
UsersTypes.PROFILES_IN_TEAM_REQUEST,
UsersTypes.PROFILES_IN_TEAM_SUCCESS,
UsersTypes.PROFILES_IN_TEAM_FAILURE,
state,
action
);
}
function getProfilesInChannel(state = initialRequestState(), action) {
return handleRequest(
UsersTypes.PROFILES_IN_CHANNEL_REQUEST,
UsersTypes.PROFILES_IN_CHANNEL_SUCCESS,
UsersTypes.PROFILES_IN_CHANNEL_FAILURE,
state,
action
);
}
function getProfilesNotInChannel(state = initialRequestState(), action) {
return handleRequest(
UsersTypes.PROFILES_NOT_IN_CHANNEL_REQUEST,
UsersTypes.PROFILES_NOT_IN_CHANNEL_SUCCESS,
UsersTypes.PROFILES_NOT_IN_CHANNEL_FAILURE,
state,
action
);
}
function getStatusesByIds(state = initialRequestState(), action) {
return handleRequest(
UsersTypes.PROFILES_STATUSES_REQUEST,
UsersTypes.PROFILES_STATUSES_SUCCESS,
UsersTypes.PROFILES_STATUSES_FAILURE,
state,
action
);
}
function getSessions(state = initialRequestState(), action) {
return handleRequest(
UsersTypes.SESSIONS_REQUEST,
UsersTypes.SESSIONS_SUCCESS,
UsersTypes.SESSIONS_FAILURE,
state,
action
);
}
function revokeSession(state = initialRequestState(), action) {
return handleRequest(
UsersTypes.REVOKE_SESSION_REQUEST,
UsersTypes.REVOKE_SESSION_SUCCESS,
UsersTypes.REVOKE_SESSION_FAILURE,
state,
action
);
}
function getAudits(state = initialRequestState(), action) {
return handleRequest(
UsersTypes.AUDITS_REQUEST,
UsersTypes.AUDITS_SUCCESS,
UsersTypes.AUDITS_FAILURE,
state,
action
);
}
export default combineReducers({
login,
logout,
getProfiles,
getProfilesInTeam,
getProfilesInChannel,
getProfilesNotInChannel,
getStatusesByIds,
getSessions,
revokeSession,
getAudits
});

View file

@ -1,46 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {TeamsTypes, LogoutTypes} from 'constants';
export const initState = {
status: 'not fetched',
error: null,
data: {},
currentTeamId: null
};
export default function reduceTeams(state = initState, action) {
switch (action.type) {
case TeamsTypes.SELECT_TEAM:
return {
...state,
currentTeamId: action.teamId
};
case TeamsTypes.FETCH_TEAMS_REQUEST:
return {
...state,
status: 'fetching',
error: null
};
case TeamsTypes.FETCH_TEAMS_SUCCESS:
return {
...state,
status: 'fetched',
data: action.data
};
case TeamsTypes.FETCH_TEAMS_FAILURE:
return {
...state,
status: 'failed',
error: action.error
};
case LogoutTypes.LOGOUT_SUCCESS:
return initState;
default:
return state;
}
}

View file

@ -0,0 +1,36 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {DeviceTypes} from 'constants';
import RequestStatus from 'constants/request_status';
export const initState = {
status: RequestStatus.NOT_STARTED,
error: null
};
export default function device(state = initState, action) {
switch (action.type) {
case DeviceTypes.DEVICE_REQUEST:
return {
...state,
status: RequestStatus.STARTED
};
case DeviceTypes.DEVICE_SUCCESS:
return {
...state,
status: RequestStatus.SUCCESS,
data: action.data,
error: null
};
case DeviceTypes.DEVICE_FAILURE:
return {
...state,
status: RequestStatus.FAILURE,
error: action.error
};
default:
return state;
}
}

View file

@ -0,0 +1,23 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {combineReducers} from 'redux';
import Config from 'config/index';
import {UsersTypes} from 'constants';
function locale(state = Config.DefaultLocale, action) {
switch (action.type) {
case UsersTypes.RECEIVED_ME:
return action.data.locale;
case UsersTypes.LOGOUT_SUCCESS:
return Config.DefaultLocale;
}
return state;
}
export default combineReducers({
locale
});

View file

@ -0,0 +1,16 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {combineReducers} from 'redux';
import login from './login';
import selectServer from './select_server';
import device from './device';
import i18n from './i18n';
export default combineReducers({
device,
i18n,
login,
selectServer
});

View file

@ -0,0 +1,33 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {combineReducers} from 'redux';
import {UsersTypes} from 'constants';
function loginId(state = '', action) {
switch (action.type) {
case UsersTypes.LOGIN_ID_CHANGED:
return action.loginId;
case UsersTypes.LOGOUT_SUCCESS:
return '';
default:
return state;
}
}
function password(state = '', action) {
switch (action.type) {
case UsersTypes.PASSWORD_CHANGED:
return action.password;
case UsersTypes.LOGOUT_SUCCESS:
return '';
default:
return state;
}
}
export default combineReducers({
loginId,
password
});

View file

@ -0,0 +1,22 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {combineReducers} from 'redux';
import Config from 'config/index';
import {GeneralTypes} from 'constants';
function serverUrl(state = Config.DefaultServerUrl, action) {
switch (action.type) {
case GeneralTypes.SERVER_URL_CHANGED:
return action.serverUrl;
default:
return state;
}
}
export default combineReducers({
serverUrl
});

View file

@ -17,7 +17,7 @@ export default class Channel extends React.Component {
currentTeam: React.PropTypes.object.isRequired,
currentChannel: React.PropTypes.object,
channels: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
}
};
constructor(props) {
super(props);

View file

@ -7,18 +7,17 @@ import {fetchMyChannelsAndMembers} from 'actions/channels.js';
import Channel from './channel.js';
function mapStateToProps(state, ownProps) {
const currentTeamId = ownProps.currentTeamId;
const currentTeam = state.entities.teams.data[currentTeamId];
const currentTeamId = state.entities.teams.currentId;
const currentTeam = state.entities.teams.teams[currentTeamId];
let channels = state.entities.channels.channels;
let currentChannel;
if (ownProps.currentChannelId) {
currentChannel = state.entities.channel.channels[ownProps.currentChannelId];
currentChannel = state.entities.channels.channels[ownProps.currentChannelId];
} else {
// TODO figure out the town square id before this
const channelIds = state.entities.channel.channelIdsByTeamId[currentTeamId] || {};
for (const channelId of Object.keys(channelIds)) {
const channel = state.entities.channel.channels[channelId];
for (const channelId of Object.keys(channels)) {
const channel = channels[channelId];
if (channel.name === 'town-square') {
currentChannel = channel;
@ -27,8 +26,7 @@ function mapStateToProps(state, ownProps) {
}
}
const channelIdsForTeam = state.entities.channel.channelIdsByTeamId[currentTeamId] || {};
const channels = Object.keys(channelIdsForTeam).map((channelId) => state.entities.channel.channels[channelId]);
channels = Object.keys(channels).map((channelId) => channels[channelId]);
return {
...ownProps,

View file

@ -9,14 +9,14 @@ import SelectServerContainer from './select_server/select_server_container.js';
import SelectTeamContainer from './select_team/select_team_container.js';
import ChannelContainer from './channel/channel_container.js';
import Logout from 'components/logout';
import * as logout from 'actions/logout';
import {logout} from 'actions/users';
import {injectIntl, intlShape} from 'react-intl';
class Routes extends Component {
static propTypes = {
intl: intlShape.isRequired
}
};
render() {
const {formatMessage} = this.props.intl;

View file

@ -14,52 +14,55 @@ import logo from 'images/logo.png';
import {injectIntl, intlShape} from 'react-intl';
import {RequestStatus} from 'constants';
class Login extends Component {
static propTypes = {
intl: intlShape.isRequired,
clientConfig: PropTypes.object.isRequired,
login: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired
};
state = {
loginId: '',
password: ''
config: PropTypes.object.isRequired,
license: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired,
loginId: PropTypes.string.isRequired,
password: PropTypes.string.isRequired,
loginRequest: PropTypes.object.isRequired,
configRequest: PropTypes.object.isRequired,
licenseRequest: PropTypes.object.isRequired
};
componentWillMount() {
this.props.actions.getClientConfig();
this.props.actions.getLicenseConfig();
}
componentWillReceiveProps(nextProps) {
if (this.props.login.status === 'fetching' && nextProps.login.status === 'fetched') {
if (this.props.loginRequest.status === RequestStatus.STARTED && nextProps.loginRequest.status === RequestStatus.SUCCESS) {
Routes.goToSelectTeam();
}
}
signIn = () => {
if (this.props.login.status !== 'fetching') {
const {loginId, password} = this.state;
this.props.actions.login(loginId, password);
signIn() {
if (this.props.loginRequest.status !== RequestStatus.STARTED) {
this.props.actions.login(this.props.loginId, this.props.password);
}
}
createLoginPlaceholder() {
const {formatMessage} = this.props.intl;
const clientConfig = this.props.clientConfig;
const license = this.props.license;
const config = this.props.config;
const loginPlaceholders = [];
if (clientConfig.EnableSignInWithEmail === 'true') {
if (config.EnableSignInWithEmail === 'true') {
loginPlaceholders.push(formatMessage({id: 'login.email', defaultMessage: 'Email'}));
}
if (clientConfig.EnableSignInWithUsername === 'true') {
if (config.EnableSignInWithUsername === 'true') {
loginPlaceholders.push(formatMessage({id: 'login.username', defaultMessage: 'Username'}));
}
if (clientConfig.EnableLdap === 'true') { // TODO check if we're licensed once we have that
if (clientConfig.LdapLoginFieldName) {
loginPlaceholders.push(clientConfig.LdapLoginFieldName);
if (license.IsLicensed === 'true' && license.LDAP === 'true' && config.EnableLdap === 'true') {
if (config.LdapLoginFieldName) {
loginPlaceholders.push(config.LdapLoginFieldName);
} else {
loginPlaceholders.push(formatMessage({id: 'login.ldapUsername', defaultMessage: 'AD/LDAP Username'}));
}
@ -77,7 +80,7 @@ class Login extends Component {
}
render() {
if (this.props.clientConfig.loading || this.props.clientConfig.error) {
if (this.props.configRequest.status === RequestStatus.STARTED || this.props.licenseRequest.status === RequestStatus.STARTED) {
return <Loading/>;
}
@ -88,7 +91,7 @@ class Login extends Component {
source={logo}
/>
<Text style={GlobalStyles.header}>
{this.props.clientConfig.SiteName}
{this.props.config.SiteName}
</Text>
<FormattedText
style={GlobalStyles.subheader}
@ -97,8 +100,8 @@ class Login extends Component {
/>
<TextInput
ref='loginId'
value={this.state.loginId}
onChangeText={(loginId) => this.setState({loginId})}
value={this.props.loginId}
onChangeText={this.props.actions.handleLoginIdChanged}
style={GlobalStyles.inputBox}
placeholder={this.createLoginPlaceholder()}
autoCorrect={false}
@ -106,8 +109,8 @@ class Login extends Component {
underlineColorAndroid='transparent'
/>
<TextInput
value={this.state.password}
onChangeText={(password) => this.setState({password})}
value={this.props.password}
onChangeText={this.props.actions.handlePasswordChanged}
style={GlobalStyles.inputBox}
placeholder={this.props.intl.formatMessage({id: 'login.password', defaultMessage: 'Password'})}
secureTextEntry={true}
@ -115,15 +118,15 @@ class Login extends Component {
autoCapitalize='none'
underlineColorAndroid='transparent'
returnKeyType='go'
onSubmitEditing={this.signIn}
onSubmitEditing={this.signIn.bind(this)}
/>
<Button onPress={this.signIn}>
<Button onPress={this.signIn.bind(this)}>
<FormattedText
id='login.signIn'
defaultMessage='Sign in'
/>
</Button>
<ErrorText error={this.props.login.error}/>
<ErrorText error={this.props.loginRequest.error}/>
</View>
);
}

View file

@ -3,22 +3,31 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import * as loginActions from 'actions/login';
import {getClientConfig} from 'actions/general';
import {getClientConfig, getLicenseConfig} from 'actions/general';
import * as LoginActions from 'actions/views/login';
import {login} from 'actions/users';
import Login from './login.js';
function mapStateToProps(state) {
return {
clientConfig: state.entities.general.clientConfig,
login: state.views.login
...state.views.login,
loginRequest: state.requests.users.login,
configRequest: state.requests.general.config,
licenseRequest: state.requests.general.license,
config: state.entities.general.config,
license: state.entities.general.license
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
...loginActions,
getClientConfig
...LoginActions,
login,
getClientConfig,
getLicenseConfig
}, dispatch)
};
}

View file

@ -5,10 +5,8 @@ import React, {Component} from 'react';
import {View, TextInput, Image} from 'react-native';
import KeyboardSpacer from 'react-native-keyboard-spacer';
import {Actions as Routes} from 'react-native-router-flux';
import _ from 'lodash';
import Client from 'client/client_instance.js';
import Config from 'config/config.js';
import Client from 'client';
import Button from 'components/button';
import ErrorText from 'components/error_text';
import FormattedText from 'components/formatted_text';
@ -17,31 +15,25 @@ import logo from 'images/logo.png';
import {injectIntl, intlShape} from 'react-intl';
import RequestStatus from 'constants/request_status';
class SelectServer extends Component {
static propTypes = {
intl: intlShape.isRequired,
ping: React.PropTypes.object.isRequired,
device: React.PropTypes.object.isRequired,
serverUrl: React.PropTypes.string.isRequired,
server: React.PropTypes.object.isRequired,
actions: React.PropTypes.object.isRequired
}
constructor(props) {
super(props);
this.state = {
serverUrl: Config.DefaultServerUrl
};
}
};
onClick = () => {
Client.setUrl(this.state.serverUrl);
Client.setUrl(this.props.serverUrl);
this.props.actions.getPing().then(() => {
if (!this.props.ping.loading && !this.props.ping.error) {
if (this.props.server.status === RequestStatus.SUCCESS) {
Routes.goToLogin();
}
});
}
};
render() {
const {formatMessage} = this.props.intl;
@ -53,13 +45,13 @@ class SelectServer extends Component {
source={logo}
/>
<FormattedText
style={_.at(GlobalStyles, ['header', 'label'])}
style={[GlobalStyles.header, GlobalStyles.label]}
id='mobile.components.select_server_view.enterServerUrl'
defaultMessage='Enter Server URL'
/>
<TextInput
value={this.state.serverUrl}
onChangeText={(serverUrl) => this.setState({serverUrl})}
value={this.props.serverUrl}
onChangeText={this.props.actions.handleServerUrlChanged}
onSubmitEditing={this.onClick}
style={GlobalStyles.inputBox}
autoCapitalize='none'
@ -71,14 +63,14 @@ class SelectServer extends Component {
/>
<Button
onPress={this.onClick}
loading={this.props.ping.loading}
loading={this.props.server.loading}
>
<FormattedText
id='mobile.components.select_server_view.proceed'
defaultMessage='Proceed'
/>
</Button>
<ErrorText error={this.props.ping.error}/>
<ErrorText error={this.props.server.error}/>
<KeyboardSpacer/>
</View>
);

View file

@ -3,20 +3,26 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getPing} from 'actions/general';
import SelectServerView from './select_server.js';
import * as SelectServerActions from 'actions/views/select_server';
import SelectServer from './select_server';
function mapStateToProps(state) {
return {
ping: state.entities.general.ping,
device: state.views.device
...state.views.selectServer,
server: state.requests.general.server
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({getPing}, dispatch)
actions: bindActionCreators({
...SelectServerActions,
getPing
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SelectServerView);
export default connect(mapStateToProps, mapDispatchToProps)(SelectServer);

View file

@ -6,18 +6,21 @@ import React, {Component, PropTypes} from 'react';
import {View, Image, Text} from 'react-native';
import {Actions as Routes} from 'react-native-router-flux';
import Button from 'react-native-button';
import Loading from 'components/loading.js';
import Loading from 'components/loading';
import Icon from 'react-native-vector-icons/MaterialIcons';
import ErrorText from 'components/error_text';
import {GlobalStyles} from 'styles';
import logo from 'images/logo.png';
import FormattedText from 'components/formatted_text';
import {RequestStatus} from 'constants';
export default class SelectTeam extends Component {
static propTypes = {
clientConfig: PropTypes.object.isRequired,
config: PropTypes.object.isRequired,
teams: PropTypes.object.isRequired,
myMembers: PropTypes.object.isRequired,
teamsRequest: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired
};
@ -26,18 +29,21 @@ export default class SelectTeam extends Component {
}
onSelectTeam(team) {
this.props.actions.selectTeam(team);
Routes.goToMain({currentTeamId: team.id});
}
render() {
if (this.props.teams.status !== 'fetched') {
if (this.props.teamsRequest.status === RequestStatus.STARTED) {
return <Loading/>;
}
const teams = [];
for (const id of Object.keys(this.props.teams.data)) {
const team = this.props.teams.data[id];
for (const id of Object.keys(this.props.teams)) {
const team = this.props.teams[id];
// TODO: uncomment when PLT-4167 is merged
// if (this.props.myMembers.hasOwnProperty(id)) {
teams.push(
<Button
key={id}
@ -53,6 +59,8 @@ export default class SelectTeam extends Component {
/>
</Button>
);
// }
}
return (
@ -62,7 +70,7 @@ export default class SelectTeam extends Component {
source={logo}
/>
<Text style={GlobalStyles.header}>
{this.props.clientConfig.SiteName}
{this.props.config.SiteName}
</Text>
<FormattedText
style={GlobalStyles.subheader}
@ -75,7 +83,7 @@ export default class SelectTeam extends Component {
defaultMessage='Your teams:'
/>
{teams}
<ErrorText error={this.props.teams.error}/>
<ErrorText error={this.props.teamsRequest.error}/>
</View>
);
}

View file

@ -8,8 +8,10 @@ import SelectTeamView from './select_team.js';
function mapStateToProps(state) {
return {
clientConfig: state.entities.general.clientConfig,
teams: state.entities.teams
config: state.entities.general.config,
teamsRequest: state.requests.teams.allTeams,
teams: state.entities.teams.teams,
myMembers: state.entities.teams.myMembers
};
}

8
src/store/index.js Normal file
View file

@ -0,0 +1,8 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import configureStore from 'store/configureStore.js';
const store = configureStore();
export default store;

View file

@ -0,0 +1,496 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import * as Actions from 'actions/channels';
import Client from 'client';
import configureStore from 'store/configureStore';
import {RequestStatus} from 'constants';
import TestHelper from 'test_helper';
describe('Actions.Channels', () => {
it('createChannel', (done) => {
TestHelper.initBasic(Client).then(() => {
const store = configureStore();
store.subscribe(() => {
const channels = store.getState().entities.channels.channels;
const members = store.getState().entities.channels.myMembers;
const createRequest = store.getState().requests.channels.createChannel;
const membersRequest = store.getState().requests.channels.myMembers;
if (createRequest.status === RequestStatus.SUCCESS && membersRequest.status === RequestStatus.SUCCESS) {
const channelsCount = Object.keys(channels).length;
const membersCount = Object.keys(members).length;
assert.ok(channels);
assert.ok(members);
assert.ok(channels[Object.keys(members)[0]]);
assert.ok(members[Object.keys(channels)[0]]);
assert.equal(members[Object.keys(channels)[0]].user_id, TestHelper.basicUser.id);
assert.equal(channelsCount, membersCount);
assert.equal(channelsCount, 1);
assert.equal(membersCount, 1);
done();
} else if (createRequest.status === RequestStatus.FAILURE && membersRequest.status === RequestStatus.FAILURE) {
done(new Error(JSON.stringify(createRequest.error)));
}
});
const channel = {
team_id: TestHelper.basicTeam.id,
name: 'redux-test',
display_name: 'Redux Test',
purpose: 'This is to test redux',
header: 'MM with Redux',
type: 'O'
};
Actions.createChannel(channel, TestHelper.basicUser.id)(store.dispatch, store.getState);
});
});
it('createDirectChannel', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
const user = await TestHelper.basicClient.createUser(TestHelper.fakeUser());
store.subscribe(() => {
const channels = store.getState().entities.channels.channels;
const members = store.getState().entities.channels.myMembers;
const profiles = store.getState().entities.users.profiles;
const preferences = store.getState().entities.users.myPreferences;
const createRequest = store.getState().requests.channels.createChannel;
const membersRequest = store.getState().requests.channels.myMembers;
if (createRequest.status === RequestStatus.SUCCESS && membersRequest.status === RequestStatus.SUCCESS) {
const channelsCount = Object.keys(channels).length;
const membersCount = Object.keys(members).length;
assert.ok(channels);
assert.ok(members);
assert.ok(profiles.items[user.id]);
assert.ok(Object.keys(preferences).length);
assert.ok(channels[Object.keys(members)[0]]);
assert.ok(members[Object.keys(channels)[0]]);
assert.equal(members[Object.keys(channels)[0]].user_id, TestHelper.basicUser.id);
assert.equal(channelsCount, membersCount);
assert.equal(channels[Object.keys(channels)[0]].type, 'D');
assert.equal(channelsCount, 1);
assert.equal(membersCount, 1);
done();
} else if (createRequest.status === RequestStatus.FAILURE && membersRequest.status === RequestStatus.FAILURE) {
done(new Error(JSON.stringify(createRequest.error)));
}
});
Actions.createDirectChannel(TestHelper.basicUser.id, user.id)(store.dispatch, store.getState);
});
});
it('updateChannel', (done) => {
TestHelper.initBasic(Client).then(() => {
const store = configureStore();
store.subscribe(() => {
const channels = store.getState().entities.channels.channels;
const updateRequest = store.getState().requests.channels.updateChannel;
if (updateRequest.status === RequestStatus.SUCCESS) {
const channelId = Object.keys(channels)[0];
const channel = channels[channelId];
assert.ok(channelId);
assert.ok(channel);
assert.equal(channel.header, 'MM with Redux');
done();
} else if (updateRequest.status === RequestStatus.FAILURE) {
done(new Error(JSON.stringify(updateRequest.error)));
}
});
const channel = {
...TestHelper.basicChannel,
purpose: 'This is to test redux',
header: 'MM with Redux'
};
Actions.updateChannel(channel)(store.dispatch, store.getState);
});
});
it('getChannel', (done) => {
TestHelper.initBasic(Client).then(() => {
const store = configureStore();
store.subscribe(() => {
const channels = store.getState().entities.channels.channels;
const members = store.getState().entities.channels.myMembers;
const channelRequest = store.getState().requests.channels.getChannel;
if (channelRequest.status === RequestStatus.SUCCESS) {
assert.ok(channels[TestHelper.basicChannel.id]);
assert.ok(members[TestHelper.basicChannel.id]);
done();
} else if (channelRequest.status === RequestStatus.FAILURE) {
done(new Error(JSON.stringify(channelRequest.error)));
}
});
Actions.getChannel(TestHelper.basicTeam.id, TestHelper.basicChannel.id)(store.dispatch, store.getState);
});
});
it('fetchMyChannelsAndMembers', (done) => {
TestHelper.initBasic(Client).then(() => {
const store = configureStore();
store.subscribe(() => {
const channels = store.getState().entities.channels.channels;
const members = store.getState().entities.channels.myMembers;
const channelsRequest = store.getState().requests.channels.getChannels;
const membersRequest = store.getState().requests.channels.myMembers;
if (channelsRequest.status === RequestStatus.SUCCESS && membersRequest.status === RequestStatus.SUCCESS) {
assert.ok(channels);
assert.ok(members);
assert.ok(channels[Object.keys(members)[0]]);
assert.ok(members[Object.keys(channels)[0]]);
assert.equal(Object.keys(channels).length, Object.keys(members).length);
done();
} else if (channelsRequest.status === RequestStatus.FAILURE && membersRequest.status === RequestStatus.FAILURE) {
done(new Error(JSON.stringify(channelsRequest.error)));
}
});
Actions.fetchMyChannelsAndMembers(TestHelper.basicTeam.id)(store.dispatch, store.getState);
});
});
it('updateChannelNotifyProps', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
store.subscribe(() => {
const members = store.getState().entities.channels.myMembers;
const updateRequest = store.getState().requests.channels.updateChannelNotifyProps;
if (updateRequest.status === RequestStatus.SUCCESS) {
const member = members[TestHelper.basicChannel.id];
assert.ok(member);
assert.equal(member.notify_props.mark_unread, 'mention');
assert.equal(member.notify_props.desktop, 'none');
done();
} else if (updateRequest.status === RequestStatus.FAILURE) {
done(new Error(JSON.stringify(updateRequest.error)));
}
});
const notifyProps = {
mark_unread: 'mention',
desktop: 'none'
};
await Actions.fetchMyChannelsAndMembers(TestHelper.basicTeam.id)(store.dispatch, store.getState);
Actions.updateChannelNotifyProps(
TestHelper.basicUser.id,
TestHelper.basicTeam.id,
TestHelper.basicChannel.id,
notifyProps)(store.dispatch, store.getState);
});
});
it('leaveChannel', (done) => {
TestHelper.initBasic(Client).then(() => {
const store = configureStore();
store.subscribe(() => {
const channels = store.getState().entities.channels.channels;
const members = store.getState().entities.channels.myMembers;
const leaveRequest = store.getState().requests.channels.leaveChannel;
if (leaveRequest.status === RequestStatus.SUCCESS) {
const channel = channels[TestHelper.basicChannel.id];
const member = members[TestHelper.basicChannel.id];
assert.ifError(channel);
assert.ifError(member);
done();
} else if (leaveRequest.status === RequestStatus.FAILURE) {
done(new Error(JSON.stringify(leaveRequest.error)));
}
});
Actions.leaveChannel(
TestHelper.basicTeam.id,
TestHelper.basicChannel.id)(store.dispatch, store.getState);
});
});
it('joinChannel', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
const secondClient = TestHelper.createClient();
const user = await TestHelper.basicClient.createUserWithInvite(
TestHelper.fakeUser(),
null,
null,
TestHelper.basicTeam.invite_id
);
await secondClient.login(user.email, 'password1');
const secondChannel = await secondClient.createChannel(
TestHelper.fakeChannel(TestHelper.basicTeam.id));
store.subscribe(() => {
const channels = store.getState().entities.channels.channels;
const members = store.getState().entities.channels.myMembers;
const joinRequest = store.getState().requests.channels.joinChannel;
if (joinRequest.status === RequestStatus.SUCCESS) {
const channel = channels[secondChannel.id];
const member = members[secondChannel.id];
assert.ok(channel);
assert.ok(member);
done();
} else if (joinRequest.status === RequestStatus.FAILURE) {
done(new Error(JSON.stringify(joinRequest.error)));
}
});
Actions.joinChannel(
TestHelper.basicUser.id,
TestHelper.basicTeam.id,
secondChannel.id)(store.dispatch, store.getState);
});
}).timeout(3000);
it('joinChannelByName', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
const secondClient = TestHelper.createClient();
const user = await TestHelper.basicClient.createUserWithInvite(
TestHelper.fakeUser(),
null,
null,
TestHelper.basicTeam.invite_id
);
await secondClient.login(user.email, 'password1');
const secondChannel = await secondClient.createChannel(
TestHelper.fakeChannel(TestHelper.basicTeam.id));
store.subscribe(() => {
const channels = store.getState().entities.channels.channels;
const members = store.getState().entities.channels.myMembers;
const joinRequest = store.getState().requests.channels.joinChannel;
if (joinRequest.status === RequestStatus.SUCCESS) {
const channel = channels[secondChannel.id];
const member = members[secondChannel.id];
assert.ok(channel);
assert.ok(member);
done();
} else if (joinRequest.status === RequestStatus.FAILURE) {
done(new Error(JSON.stringify(joinRequest.error)));
}
});
Actions.joinChannel(
TestHelper.basicUser.id,
TestHelper.basicTeam.id,
null,
secondChannel.name)(store.dispatch, store.getState);
});
}).timeout(3000);
it('deleteChannel', (done) => {
TestHelper.initBasic(Client).then(() => {
const store = configureStore();
store.subscribe(() => {
const channels = store.getState().entities.channels.channels;
const members = store.getState().entities.channels.myMembers;
const deleteRequest = store.getState().requests.channels.deleteChannel;
if (deleteRequest.status === RequestStatus.SUCCESS) {
const channel = channels[TestHelper.basicChannel.id];
const member = members[TestHelper.basicChannel.id];
assert.ifError(channel);
assert.ifError(member);
done();
} else if (deleteRequest.status === RequestStatus.FAILURE) {
done(new Error(JSON.stringify(deleteRequest.error)));
}
});
Actions.deleteChannel(
TestHelper.basicTeam.id,
TestHelper.basicChannel.id)(store.dispatch, store.getState);
});
});
it('updateLastViewedAt', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
let lastViewed;
store.subscribe(() => {
const members = store.getState().entities.channels.myMembers;
const updateRequest = store.getState().requests.channels.updateLastViewedAt;
let member;
if (updateRequest.status === RequestStatus.STARTED) {
member = members[TestHelper.basicChannel.id];
assert.ok(member);
lastViewed = member.last_viewed_at;
} else if (updateRequest.status === RequestStatus.SUCCESS) {
member = members[TestHelper.basicChannel.id];
assert.ok(member.last_viewed_at > lastViewed);
done();
} else if (updateRequest.status === RequestStatus.FAILURE) {
done(new Error(JSON.stringify(updateRequest.error)));
}
});
await Actions.fetchMyChannelsAndMembers(TestHelper.basicTeam.id)(store.dispatch, store.getState);
Actions.updateLastViewedAt(
TestHelper.basicTeam.id,
TestHelper.basicChannel.id, true)(store.dispatch, store.getState);
});
});
it('getMoreChannels', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
const secondClient = TestHelper.createClient();
const user = await TestHelper.basicClient.createUserWithInvite(
TestHelper.fakeUser(),
null,
null,
TestHelper.basicTeam.invite_id
);
await secondClient.login(user.email, 'password1');
const secondChannel = await secondClient.createChannel(
TestHelper.fakeChannel(TestHelper.basicTeam.id));
store.subscribe(() => {
const channels = store.getState().entities.channels.moreChannels;
const moreRequest = store.getState().requests.channels.getMoreChannels;
if (moreRequest.status === RequestStatus.SUCCESS) {
const channel = channels[secondChannel.id];
assert.ok(channel);
done();
} else if (moreRequest.status === RequestStatus.FAILURE) {
done(new Error(JSON.stringify(moreRequest.error)));
}
});
Actions.getMoreChannels(TestHelper.basicTeam.id, 0)(store.dispatch, store.getState);
});
}).timeout(3000);
it('getChannelStats', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
store.subscribe(() => {
const stats = store.getState().entities.channels.stats;
const statsRequest = store.getState().requests.channels.getChannelStats;
if (statsRequest.status === RequestStatus.SUCCESS) {
const stat = stats[TestHelper.basicChannel.id];
assert.ok(stat);
assert.equal(stat.member_count, 1);
done();
} else if (statsRequest.status === RequestStatus.FAILURE) {
done(new Error(JSON.stringify(statsRequest.error)));
}
});
Actions.getChannelStats(
TestHelper.basicTeam.id,
TestHelper.basicChannel.id
)(store.dispatch, store.getState);
});
});
it('addChannelMember', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
const user = await TestHelper.basicClient.createUserWithInvite(
TestHelper.fakeUser(),
null,
null,
TestHelper.basicTeam.invite_id
);
store.subscribe(() => {
const profilesInChannel = store.getState().entities.users.profilesInChannel;
const profilesNotInChannel = store.getState().entities.users.profilesNotInChannel;
const addRequest = store.getState().requests.channels.addChannelMember;
if (addRequest.status === RequestStatus.SUCCESS) {
assert.ok(profilesInChannel.items.has(user.id));
assert.ifError(profilesNotInChannel.items.has(user.id));
done();
} else if (addRequest.status === RequestStatus.FAILURE) {
done(new Error(JSON.stringify(addRequest.error)));
}
});
Actions.addChannelMember(
TestHelper.basicTeam.id,
TestHelper.basicChannel.id,
user.id
)(store.dispatch, store.getState);
});
});
it('removeChannelMember', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
const user = await TestHelper.basicClient.createUserWithInvite(
TestHelper.fakeUser(),
null,
null,
TestHelper.basicTeam.invite_id
);
store.subscribe(() => {
const profilesInChannel = store.getState().entities.users.profilesInChannel;
const profilesNotInChannel = store.getState().entities.users.profilesNotInChannel;
const removeRequest = store.getState().requests.channels.removeChannelMember;
if (removeRequest.status === RequestStatus.SUCCESS) {
assert.ok(profilesNotInChannel.items.has(user.id));
assert.ifError(profilesInChannel.items.has(user.id));
done();
} else if (removeRequest.status === RequestStatus.FAILURE) {
done(new Error(JSON.stringify(removeRequest.error)));
}
});
await Actions.addChannelMember(
TestHelper.basicTeam.id,
TestHelper.basicChannel.id,
user.id
)(store.dispatch, store.getState);
Actions.removeChannelMember(
TestHelper.basicTeam.id,
TestHelper.basicChannel.id,
user.id
)(store.dispatch, store.getState);
});
});
});

View file

@ -3,22 +3,41 @@
import assert from 'assert';
import * as Actions from 'actions/general.js';
import Client from 'client/client_instance.js';
import configureStore from 'store/configureStore.js';
import TestHelper from 'test_helper.js';
import * as Actions from 'actions/general';
import Client from 'client';
import configureStore from 'store/configureStore';
import {RequestStatus} from 'constants';
import TestHelper from 'test_helper';
describe('Actions.General', () => {
it('getPing', (done) => {
TestHelper.initBasic(Client).then(() => {
const store = configureStore();
store.subscribe(() => {
const ping = store.getState().requests.general.server;
if (ping.error) {
done(new Error(JSON.stringify(ping.error)));
} else if (ping.status !== RequestStatus.STARTED) {
done();
}
});
Actions.getPing()(store.dispatch, store.getState);
});
});
it('getClientConfig', (done) => {
TestHelper.initBasic(Client).then(() => {
const store = configureStore();
store.subscribe(() => {
const clientConfig = store.getState().entities.general.clientConfig;
if (!clientConfig.loading) {
if (clientConfig.error) {
done(new Error(clientConfig.error));
const clientConfig = store.getState().entities.general.config;
const configRequest = store.getState().requests.general.config;
if (configRequest.status === RequestStatus.SUCCESS || configRequest.status === RequestStatus.FAILURE) {
if (configRequest.error) {
done(new Error(JSON.stringify(configRequest.error)));
} else {
// Check a few basic fields since they may change over time
assert.ok(clientConfig.Version);
@ -35,21 +54,26 @@ describe('Actions.General', () => {
});
});
it('getPing', (done) => {
it('getLicenseConfig', (done) => {
TestHelper.initBasic(Client).then(() => {
const store = configureStore();
store.subscribe(() => {
const ping = store.getState().entities.general.ping;
const licenseConfig = store.getState().entities.general.license;
const licenseRequest = store.getState().requests.general.license;
if (licenseRequest.status === RequestStatus.SUCCESS || licenseRequest.status === RequestStatus.FAILURE) {
if (licenseRequest.error) {
done(new Error(JSON.stringify(licenseRequest.error)));
} else {
// Check a few basic fields since they may change over time
assert.notStrictEqual(licenseConfig.IsLicensed, undefined);
if (ping.error) {
done(new Error(ping.error));
} else if (!ping.loading) {
done();
done();
}
}
});
Actions.getPing()(store.dispatch, store.getState);
Actions.getLicenseConfig()(store.dispatch, store.getState);
});
});
@ -58,15 +82,15 @@ describe('Actions.General', () => {
const store = configureStore();
store.subscribe(() => {
const ping = store.getState().entities.general.ping;
const ping = store.getState().requests.general.server;
if (!ping.loading && ping.error) {
if (ping.status === RequestStatus.FAILURE && ping.error) {
done();
}
});
Client.setUrl('https://example.com/fake/url');
Client.setUrl('https://google.com/fake/url');
Actions.getPing()(store.dispatch, store.getState);
});
});
}).timeout(5000);
});

View file

@ -0,0 +1,50 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import * as Actions from 'actions/teams';
import Client from 'client';
import configureStore from 'store/configureStore';
import {RequestStatus} from 'constants';
import TestHelper from 'test_helper';
describe('Actions.Teams', () => {
it('fetchTeams', (done) => {
TestHelper.initBasic(Client).then(() => {
const store = configureStore();
store.subscribe(() => {
const teamsRequest = store.getState().requests.teams.allTeams;
const teams = store.getState().entities.teams.teams;
if (teamsRequest.status === RequestStatus.SUCCESS || teamsRequest.status === RequestStatus.FAILURE) {
if (teamsRequest.error) {
done(new Error(JSON.stringify(teamsRequest.error)));
} else {
assert.ok(teams);
assert.ok(teams[TestHelper.basicTeam.id]);
done();
}
}
});
Actions.fetchTeams()(store.dispatch, store.getState);
});
});
it('selectTeam', (done) => {
TestHelper.initBasic(Client).then(() => {
const store = configureStore();
store.subscribe(() => {
const currentTeamId = store.getState().entities.teams.currentId;
assert.ok(currentTeamId);
assert.equal(currentTeamId, TestHelper.basicTeam.id);
done();
});
Actions.selectTeam(TestHelper.basicTeam)(store.dispatch, store.getState);
});
});
});

359
test/actions/users.test.js Normal file
View file

@ -0,0 +1,359 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import * as Actions from 'actions/users';
import Client from 'client';
import configureStore from 'store/configureStore';
import {RequestStatus} from 'constants';
import TestHelper from 'test_helper';
describe('Actions.Users', () => {
it('login', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
store.subscribe(() => {
const loginRequest = store.getState().requests.users.login;
const currentUserId = store.getState().entities.users.currentId;
const profiles = store.getState().entities.users.profiles;
const preferences = store.getState().entities.users.myPreferences;
// TODO: uncomment when PLT-4167 is merged
// const teamMembers = store.getState().entities.teams.myMembers;
if (loginRequest.status === RequestStatus.SUCCESS || loginRequest.status === RequestStatus.FAILURE) {
if (loginRequest.error) {
done(new Error(JSON.stringify(loginRequest.error)));
} else {
assert.ok(currentUserId);
assert.ok(profiles);
assert.ok(profiles.items[currentUserId]);
assert.ok(Object.keys(preferences).length);
// TODO: uncomment when PLT-4167 is merged
// Object.keys(teamMembers).forEach((id) => {
// assert.ok(teamMembers[id].team_id);
// assert.equal(teamMembers[id].user_id, currentUserId);
// });
done();
}
}
});
const user = TestHelper.basicUser;
await TestHelper.basicClient.logout();
Actions.login(user.email, 'password1')(store.dispatch, store.getState);
});
});
it('logout', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
store.subscribe(() => {
const logoutRequest = store.getState().requests.users.logout;
const general = store.getState().entities.general;
const users = store.getState().entities.users;
const loginView = store.getState().views.login;
const teams = store.getState().entities.teams;
const channels = store.getState().entities.channels;
const posts = store.getState().entities.posts;
if (logoutRequest.status === RequestStatus.SUCCESS || logoutRequest.status === RequestStatus.FAILURE) {
if (logoutRequest.error) {
done(new Error(JSON.stringify(logoutRequest.error)));
} else {
assert.deepStrictEqual(general.config, {}, 'config not empty');
assert.deepStrictEqual(general.license, {}, 'license not empty');
assert.strictEqual(users.currentId, '', 'current user id not empty');
assert.deepStrictEqual(users.myPreferences, {}, 'user preferences not empty');
assert.deepStrictEqual(users.mySessions, [], 'user sessions not empty');
assert.deepStrictEqual(users.myAudits, [], 'user audits not empty');
assert.deepStrictEqual(users.profiles, {items: {}, offset: 0, count: 0}, 'user profiles not empty');
assert.deepStrictEqual(users.profilesInTeam, {items: new Set(), offset: 0, count: 0}, 'users profiles in team not empty');
assert.deepStrictEqual(users.profilesInChannel, {items: new Set(), offset: 0, count: 0}, 'users profiles in channel not empty');
assert.deepStrictEqual(users.profilesNotInChannel, {items: new Set(), offset: 0, count: 0}, 'users profiles NOT in channel not empty');
assert.deepStrictEqual(users.statuses, {}, 'users statuses not empty');
assert.strictEqual(loginView.loginId, '', 'login id not empty');
assert.strictEqual(loginView.password, '', 'password not empty');
assert.strictEqual(teams.currentId, '', 'current team id is not empty');
assert.deepStrictEqual(teams.teams, {}, 'teams is not empty');
assert.deepStrictEqual(teams.myMembers, {}, 'team members is not empty');
assert.deepStrictEqual(teams.membersInTeam, {}, 'members in team is not empty');
assert.deepStrictEqual(teams.membersNotInTeam, {}, 'members NOT in team is not empty');
assert.deepStrictEqual(teams.stats, {}, 'team stats is not empty');
assert.deepStrictEqual(teams.openTeamIds, new Set(), 'team open ids is not empty');
assert.strictEqual(channels.currentId, '', 'current channel id is not empty');
assert.deepStrictEqual(channels.channels, {}, 'channels is not empty');
assert.deepStrictEqual(channels.myMembers, {}, 'channel members is not empty');
assert.deepStrictEqual(channels.moreChannels, {}, 'more channels is not empty');
assert.deepStrictEqual(channels.stats, {}, 'channel stats is not empty');
assert.strictEqual(posts.selectedPostId, '', 'selected post id is not empty');
assert.strictEqual(posts.currentFocusedPostId, '', 'current focused post id is not empty');
assert.deepStrictEqual(posts.postsInfo, {}, 'posts info is not empty');
assert.deepStrictEqual(posts.latestPageTime, {}, 'posts latest page time is not empty');
done();
}
}
});
Actions.logout()(store.dispatch, store.getState);
});
});
it('getProfiles', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
const user = await TestHelper.basicClient.createUser(TestHelper.fakeUser());
store.subscribe(() => {
const profilesRequest = store.getState().requests.users.getProfiles;
const profiles = store.getState().entities.users.profiles;
if (profilesRequest.status === RequestStatus.SUCCESS || profilesRequest.status === RequestStatus.FAILURE) {
if (profilesRequest.error) {
done(new Error(JSON.stringify(profilesRequest.error)));
} else {
assert.strictEqual(profiles.offset, 0, 'offset should be 0');
assert.strictEqual(profiles.count, 0, 'count should be 0');
assert.ok(profiles.items[user.id]);
done();
}
}
});
Actions.getProfiles(0)(store.dispatch, store.getState);
});
});
it('getProfilesInTeam', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
store.subscribe(() => {
const profilesRequest = store.getState().requests.users.getProfilesInTeam;
const profilesInTeam = store.getState().entities.users.profilesInTeam;
const profiles = store.getState().entities.users.profiles;
if (profilesRequest.status === RequestStatus.SUCCESS || profilesRequest.status === RequestStatus.FAILURE) {
if (profilesRequest.error) {
done(new Error(JSON.stringify(profilesRequest.error)));
} else {
assert.ok(profilesInTeam.offset > 0, 'offset should be > 0');
assert.ok(profilesInTeam.count > 0, 'count should be > 0');
assert.equal(profilesInTeam.count, profilesInTeam.items.size, 'count should be equal to the amount of profiles');
assert.ok(profilesInTeam.items.has(TestHelper.basicUser.id));
assert.equal(Object.keys(profiles.items).length, profilesInTeam.count, 'profiles != profiles in team');
done();
}
}
});
Actions.getProfilesInTeam(TestHelper.basicTeam.id, 0)(store.dispatch, store.getState);
});
});
it('getProfilesInChannel', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
store.subscribe(() => {
const profilesRequest = store.getState().requests.users.getProfilesInChannel;
const profilesInChannel = store.getState().entities.users.profilesInChannel;
const profiles = store.getState().entities.users.profiles;
if (profilesRequest.status === RequestStatus.SUCCESS || profilesRequest.status === RequestStatus.FAILURE) {
if (profilesRequest.error) {
done(new Error(JSON.stringify(profilesRequest.error)));
} else {
assert.ok(profilesInChannel.offset > 0, 'offset should be > 0');
assert.ok(profilesInChannel.count > 0, 'count should be > 0');
assert.equal(profilesInChannel.count, profilesInChannel.items.size, 'count should be equal to the amount of profiles');
assert.ok(profilesInChannel.items.has(TestHelper.basicUser.id));
assert.equal(Object.keys(profiles.items).length, profilesInChannel.count, 'profiles != profiles in channel');
done();
}
}
});
Actions.getProfilesInChannel(TestHelper.basicTeam.id, TestHelper.basicChannel.id, 0)(store.dispatch, store.getState);
});
});
it('getProfilesNotInChannel', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
store.subscribe(() => {
const profilesRequest = store.getState().requests.users.getProfilesNotInChannel;
const profilesNotInChannel = store.getState().entities.users.profilesNotInChannel;
const profiles = store.getState().entities.users.profiles;
if (profilesRequest.status === RequestStatus.SUCCESS || profilesRequest.status === RequestStatus.FAILURE) {
if (profilesRequest.error) {
done(new Error(JSON.stringify(profilesRequest.error)));
} else {
assert.ok(profilesNotInChannel.offset > 0, 'offset should be > 0');
assert.ok(profilesNotInChannel.count > 0, 'count should be > 0');
assert.equal(profilesNotInChannel.count, profilesNotInChannel.items.size, 'count should be equal to the amount of profiles');
assert.ok(profilesNotInChannel.items.has(TestHelper.basicUser.id));
assert.equal(Object.keys(profiles.items).length, profilesNotInChannel.count, 'profiles != profiles in channel');
done();
}
}
});
Actions.getProfilesNotInChannel(TestHelper.basicTeam.id, TestHelper.basicChannel.id, 0)(store.dispatch, store.getState);
});
});
it('getStatusesByIds', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
const user = await TestHelper.basicClient.createUser(TestHelper.fakeUser());
store.subscribe(() => {
const statusesRequest = store.getState().requests.users.getStatusesByIds;
const statuses = store.getState().entities.users.statuses;
if (statusesRequest.status === RequestStatus.SUCCESS || statusesRequest.status === RequestStatus.FAILURE) {
if (statusesRequest.error) {
done(new Error(JSON.stringify(statusesRequest.error)));
} else {
assert.ok(statuses[TestHelper.basicUser.id]);
assert.ok(statuses[user.id]);
assert.equal(Object.keys(statuses).length, 2);
done();
}
}
});
Actions.getStatusesByIds([TestHelper.basicUser.id, user.id])(store.dispatch, store.getState);
});
});
it('getSessions', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
store.subscribe(() => {
const sessionsRequest = store.getState().requests.users.getSessions;
const sessions = store.getState().entities.users.mySessions;
if (sessionsRequest.status === RequestStatus.SUCCESS || sessionsRequest.status === RequestStatus.FAILURE) {
if (sessionsRequest.error) {
done(new Error(JSON.stringify(sessionsRequest.error)));
} else {
assert.ok(sessions.length);
assert.equal(sessions[0].user_id, TestHelper.basicUser.id);
done();
}
}
});
Actions.getSessions(TestHelper.basicUser.id)(store.dispatch, store.getState);
});
});
it('revokeSession', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
store.subscribe(() => {
const sessionsRequest = store.getState().requests.users.getSessions;
const revokeRequest = store.getState().requests.users.revokeSession;
const sessions = store.getState().entities.users.mySessions;
if (revokeRequest.status === RequestStatus.SUCCESS || revokeRequest.status === RequestStatus.FAILURE) {
if (revokeRequest.error) {
done(new Error(JSON.stringify(revokeRequest.error)));
} else {
assert.ok(sessions.length === 0);
done();
}
}
if (sessionsRequest.status === RequestStatus.SUCCESS || sessionsRequest.status === RequestStatus.FAILURE) {
if (sessionsRequest.error) {
done(new Error(JSON.stringify(sessionsRequest.error)));
} else if (revokeRequest.status === RequestStatus.NOT_STARTED) {
Actions.revokeSession(sessions[0].id)(store.dispatch, store.getState);
}
}
});
Actions.getSessions(TestHelper.basicUser.id)(store.dispatch, store.getState);
});
});
it('revokeSession and logout', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
store.subscribe(() => {
const sessionsRequest = store.getState().requests.users.getSessions;
const revokeRequest = store.getState().requests.users.revokeSession;
const logoutRequest = store.getState().requests.users.logout;
const profilesRequest = store.getState().requests.users.getProfiles;
const sessions = store.getState().entities.users.mySessions;
if (logoutRequest.status === RequestStatus.SUCCESS || logoutRequest.status === RequestStatus.FAILURE) {
if (logoutRequest.error) {
done(new Error(JSON.stringify(logoutRequest.error)));
} else {
done();
}
}
if (revokeRequest.status === RequestStatus.SUCCESS || revokeRequest.status === RequestStatus.FAILURE) {
if (revokeRequest.error) {
done(new Error(JSON.stringify(revokeRequest.error)));
} else if (logoutRequest.status === RequestStatus.NOT_STARTED && profilesRequest.status === RequestStatus.NOT_STARTED) {
Actions.getProfiles(0)(store.dispatch, store.getState);
}
}
if (sessionsRequest.status === RequestStatus.SUCCESS || sessionsRequest.status === RequestStatus.FAILURE) {
if (sessionsRequest.error) {
done(new Error(JSON.stringify(sessionsRequest.error)));
} else if (revokeRequest.status === RequestStatus.NOT_STARTED) {
Actions.revokeSession(sessions[0].id)(store.dispatch, store.getState);
}
}
});
Actions.getSessions(TestHelper.basicUser.id)(store.dispatch, store.getState);
});
});
it('getAudits', (done) => {
TestHelper.initBasic(Client).then(async () => {
const store = configureStore();
store.subscribe(() => {
const auditsRequest = store.getState().requests.users.getAudits;
const audits = store.getState().entities.users.myAudits;
if (auditsRequest.status === RequestStatus.SUCCESS || auditsRequest.status === RequestStatus.FAILURE) {
if (auditsRequest.error) {
done(new Error(JSON.stringify(auditsRequest.error)));
} else {
assert.ok(audits.length);
assert.equal(audits[0].user_id, TestHelper.basicUser.id);
done();
}
}
});
Actions.getAudits(TestHelper.basicUser.id)(store.dispatch, store.getState);
});
});
});

View file

@ -0,0 +1,33 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import * as Actions from 'actions/views/login';
import configureStore from 'store/configureStore';
describe('Actions.Views.Login', () => {
it('handleLoginIdChanged', (done) => {
const store = configureStore();
store.subscribe(() => {
const loginId = store.getState().views.login.loginId;
assert.equal('email@example.com', loginId);
done();
});
Actions.handleLoginIdChanged('email@example.com')(store.dispatch, store.getState);
});
it('handlePasswordChanged', (done) => {
const store = configureStore();
store.subscribe(() => {
const password = store.getState().views.login.password;
assert.equal('password', password);
done();
});
Actions.handlePasswordChanged('password')(store.dispatch, store.getState);
});
});

View file

@ -0,0 +1,21 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import * as Actions from 'actions/views/select_server';
import configureStore from 'store/configureStore';
describe('Actions.Views.SelectServer', () => {
it('handleServerUrlChanged', (done) => {
const store = configureStore();
store.subscribe(() => {
const serverUrl = store.getState().views.selectServer.serverUrl;
assert.equal('https://mattermost.example.com', serverUrl);
done();
});
Actions.handleServerUrlChanged('https://mattermost.example.com')(store.dispatch, store.getState);
});
});

View file

@ -1,12 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import TestHelper from 'test_helper.js';
describe('Client', () => {
it('doFetch', async () => {
const client = TestHelper.createClient();
return client.doFetch(`${client.getGeneralRoute()}/ping`, {});
});
});

View file

@ -1,19 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import TestHelper from 'test_helper.js';
describe('Client.Channel', () => {
it('createChannel', async () => {
const {client, team} = await TestHelper.initBasic();
const channel = TestHelper.fakeChannel(team.id);
const rchannel = await client.createChannel(channel);
assert.ok(rchannel.id, 'id is empty');
assert.equal(rchannel.name, channel.name, 'name doesn\'t match');
assert.equal(rchannel.team_id, channel.team_id, 'team id doesn\'t match');
});
});

View file

@ -1,47 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import TestHelper from 'test_helper.js';
describe('Client.General', function() {
it('getClientConfig', async () => {
const {client} = await TestHelper.initBasic();
const clientConfig = await client.getClientConfig('foo');
assert.ok(clientConfig.Version);
assert.ok(clientConfig.BuildNumber);
assert.ok(clientConfig.BuildDate);
assert.ok(clientConfig.BuildHash);
});
it('getPing', async () => {
const {client} = await TestHelper.initBasic();
await client.getPing();
});
it('getPing - Invalid URL', async () => {
const {client} = await TestHelper.initBasic();
client.setUrl('https://example.com/fake/url');
let errored;
try {
await client.getPing();
errored = false;
} catch (err) {
errored = true;
}
assert.ok(errored, 'should have errored');
});
it('logClientError', async () => {
const {client} = await TestHelper.initBasic();
await client.logClientError('this is a test');
});
});

View file

@ -1,21 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import TestHelper from 'test_helper.js';
describe('Client.Post', () => {
it('createPost', async () => {
const {
client,
channel,
team
} = await TestHelper.initBasic();
const post = TestHelper.fakePost(channel.id);
const rpost = await client.createPost(team.id, post);
assert.ok(rpost.id, 'id is empty');
});
});

View file

@ -1,18 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import TestHelper from 'test_helper.js';
describe('Client.Team', () => {
it('createTeam', async () => {
const client = TestHelper.createClient();
const team = TestHelper.fakeTeam();
const rteam = await client.createTeam(team);
assert.equal(rteam.id.length > 0, true);
assert.equal(rteam.name, team.name);
});
});

View file

@ -1,42 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import TestHelper from 'test_helper.js';
describe('Client.User', () => {
it('createUser', async () => {
const client = TestHelper.createClient();
const user = TestHelper.fakeUser();
const ruser = await client.createUser(user);
assert.ok(ruser.id, 'id is empty');
assert.equal(ruser.email, user.email, 'email addresses aren\'t equal');
});
it('login', async () => {
const client = TestHelper.createClient();
const user = TestHelper.fakeUser();
user.password = 'password';
await client.createUser(user);
const ruser = await client.login(user.email, user.password);
assert.ok(ruser.id, 'id is empty');
assert.equal(ruser.email, user.email, 'email addresses aren\'t equal');
assert.ok(!ruser.password, 'returned password should be empty');
assert.ok(client.token, 'token is empty');
});
it('getInitialLoad', async () => {
const {client, user} = await TestHelper.initBasic();
const data = await client.getInitialLoad();
assert.ok(data.user.id.length, 'id is empty');
assert.equal(data.user.id, user.id, 'user ids don\'t match');
});
});

View file

@ -1,268 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import deepFreeze from 'deep-freeze';
import TestHelper from 'test_helper.js';
import reduceChannel from 'reducers/channel';
import {ChannelTypes} from 'constants';
describe('reducers/channel.js', () => {
it('initial state', () => {
const store = reduceChannel(undefined, {type: ''});
const expected = {
channels: {},
channelIdsByTeamId: {},
channelMembers: {}
};
assert.deepEqual(store, expected, 'initial state of store');
});
describe('CHANNEL_RECEIVED', () => {
let store = deepFreeze(reduceChannel(undefined, {type: ''}));
const teamId = TestHelper.generateId();
const channel1 = {
id: TestHelper.generateId(),
...TestHelper.fakeChannel(teamId)
};
const channel2 = {
id: TestHelper.generateId(),
...TestHelper.fakeChannel(teamId)
};
it('first channel received', () => {
store = reduceChannel(store, {
type: ChannelTypes.CHANNEL_RECEIVED,
channel: channel1
});
assert.deepEqual(store.channels, {
[channel1.id]: channel1
});
assert.deepEqual(store.channelIdsByTeamId, {
[teamId]: {
[channel1.id]: channel1.id
}
});
assert.deepEqual(store.channelMembers, {});
});
store = deepFreeze(store);
it('second channel received', () => {
store = reduceChannel(store, {
type: ChannelTypes.CHANNEL_RECEIVED,
channel: channel2
});
assert.deepEqual(store.channels, {
[channel1.id]: channel1,
[channel2.id]: channel2
});
assert.deepEqual(store.channelIdsByTeamId, {
[teamId]: {
[channel1.id]: channel1.id,
[channel2.id]: channel2.id
}
});
assert.deepEqual(store.channelMembers, {});
});
store = deepFreeze(store);
const channel1a = {
...channel1,
name: channel1.name + 'test'
};
it('first channel received again', () => {
store = reduceChannel(store, {
type: ChannelTypes.CHANNEL_RECEIVED,
channel: channel1a
});
assert.deepEqual(store.channels, {
[channel1.id]: channel1a,
[channel2.id]: channel2
});
assert.deepEqual(store.channelIdsByTeamId, {
[teamId]: {
[channel1.id]: channel1.id,
[channel2.id]: channel2.id
}
});
assert.deepEqual(store.channelMembers, {});
});
});
describe('CHANNELS_RECEIVED', () => {
let store = deepFreeze(reduceChannel(undefined, {type: ''}));
const teamId = TestHelper.generateId();
const channel1 = {
id: TestHelper.generateId(),
...TestHelper.fakeChannel(teamId)
};
const channel2 = {
id: TestHelper.generateId(),
...TestHelper.fakeChannel(teamId)
};
it('first set of channels received', () => {
store = reduceChannel(store, {
type: ChannelTypes.CHANNELS_RECEIVED,
channels: [channel1, channel2]
});
assert.deepEqual(store.channels, {
[channel1.id]: channel1,
[channel2.id]: channel2
});
assert.deepEqual(store.channelIdsByTeamId, {
[teamId]: {
[channel1.id]: channel1.id,
[channel2.id]: channel2.id
}
});
assert.deepEqual(store.channelMembers, {});
});
store = deepFreeze(store);
const channel2a = {
...channel2,
name: channel2.name + 'test'
};
const channel3 = {
id: TestHelper.generateId(),
...TestHelper.fakeChannel(teamId)
};
it('second set of channels received', () => {
store = reduceChannel(store, {
type: ChannelTypes.CHANNELS_RECEIVED,
channels: [channel2a, channel3]
});
assert.deepEqual(store.channels, {
[channel1.id]: channel1,
[channel2.id]: channel2a,
[channel3.id]: channel3
});
assert.deepEqual(store.channelIdsByTeamId, {
[teamId]: {
[channel1.id]: channel1.id,
[channel2.id]: channel2.id,
[channel3.id]: channel3.id
}
});
assert.deepEqual(store.channelMembers, {});
});
});
describe('CHANNEL_MEMBER_RECEIVED', () => {
let store = deepFreeze(reduceChannel(undefined, {type: ''}));
const member1 = TestHelper.fakeChannelMember(TestHelper.generateId(), TestHelper.generateId());
const member2 = TestHelper.fakeChannelMember(TestHelper.generateId(), TestHelper.generateId());
it('first channel member received', () => {
store = reduceChannel(store, {
type: ChannelTypes.CHANNEL_MEMBER_RECEIVED,
channelMember: member1
});
assert.deepEqual(store.channels, {});
assert.deepEqual(store.channelIdsByTeamId, {});
assert.deepEqual(store.channelMembers, {
[`${member1.channel_id}-${member1.user_id}`]: member1
});
});
store = deepFreeze(store);
it('second channel member received', () => {
store = reduceChannel(store, {
type: ChannelTypes.CHANNEL_MEMBER_RECEIVED,
channelMember: member2
});
assert.deepEqual(store.channels, {});
assert.deepEqual(store.channelIdsByTeamId, {});
assert.deepEqual(store.channelMembers, {
[`${member1.channel_id}-${member1.user_id}`]: member1,
[`${member2.channel_id}-${member2.user_id}`]: member2
});
});
store = deepFreeze(store);
const member1a = {
...member1,
roles: 'system_admin system_user'
};
it('first channel member received again', () => {
store = reduceChannel(store, {
type: ChannelTypes.CHANNEL_MEMBER_RECEIVED,
channelMember: member1a
});
assert.deepEqual(store.channels, {});
assert.deepEqual(store.channelIdsByTeamId, {});
assert.deepEqual(store.channelMembers, {
[`${member1.channel_id}-${member1.user_id}`]: member1a,
[`${member2.channel_id}-${member2.user_id}`]: member2
});
});
});
describe('CHANNEL_MEMBERS_RECEIVED', () => {
let store = deepFreeze(reduceChannel(undefined, {type: ''}));
const member1 = TestHelper.fakeChannelMember(TestHelper.generateId(), TestHelper.generateId());
const member2 = TestHelper.fakeChannelMember(TestHelper.generateId(), TestHelper.generateId());
it('first set of channel members received', () => {
store = reduceChannel(store, {
type: ChannelTypes.CHANNEL_MEMBERS_RECEIVED,
channelMembers: [member1, member2]
});
assert.deepEqual(store.channels, {});
assert.deepEqual(store.channelIdsByTeamId, {});
assert.deepEqual(store.channelMembers, {
[`${member1.channel_id}-${member1.user_id}`]: member1,
[`${member2.channel_id}-${member2.user_id}`]: member2
});
});
store = deepFreeze(store);
const member2a = {
...member2,
roles: 'system_admin system_user'
};
const member3 = TestHelper.fakeChannelMember(TestHelper.generateId(), TestHelper.generateId());
it('second set of channel members received', () => {
store = reduceChannel(store, {
type: ChannelTypes.CHANNEL_MEMBERS_RECEIVED,
channelMembers: [member2a, member3]
});
assert.deepEqual(store.channels, {});
assert.deepEqual(store.channelIdsByTeamId, {});
assert.deepEqual(store.channelMembers, {
[`${member1.channel_id}-${member1.user_id}`]: member1,
[`${member2.channel_id}-${member2.user_id}`]: member2a,
[`${member3.channel_id}-${member3.user_id}`]: member3
});
});
});
});

View file

@ -1,78 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import reduceDevice, {initState} from 'reducers/device';
import {DeviceTypes} from 'constants';
describe('device reducer', () => {
describe('Init', () => {
let store;
let expectedStore;
before(() => {
store = reduceDevice(store, {type: ''});
expectedStore = {...initState};
});
it('should be initial state', () => {
assert.equal(typeof store, 'object');
});
it('have a specifc initial state', () => {
assert.deepEqual(store, expectedStore);
});
});
describe(`when ${DeviceTypes.DEVICE_REQUEST}`, () => {
let store;
let expectedStore;
before(() => {
store = reduceDevice(store, {
type: DeviceTypes.DEVICE_REQUEST
});
expectedStore = {
...initState,
loading: true
};
});
it('should set status to fetching', () => {
assert.deepEqual(store, expectedStore);
});
});
describe(`when ${DeviceTypes.DEVICE_SUCCESS}`, () => {
let store;
let expectedStore;
const data = {some: 'data'};
before(() => {
store = reduceDevice(store, {
type: DeviceTypes.DEVICE_SUCCESS,
data
});
expectedStore = {
...initState,
loading: false,
data
};
});
it('should set status to fetched and data', () => {
assert.deepEqual(store, expectedStore);
});
});
describe(`when ${DeviceTypes.DEVICE_FAILURE}`, () => {
let store;
let error;
let expectedStore;
before(() => {
error = {id: 'the.error.id', message: 'Something went wrong'};
store = reduceDevice(store, {
type: DeviceTypes.DEVICE_FAILURE,
error
});
expectedStore = {
...initState,
loading: false,
error
};
});
it('should set status to failed and error', () => {
assert.deepEqual(store, expectedStore);
});
});
});

View file

@ -1,170 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
// import assert from 'assert';
// import reduceGeneral, {initState} from 'reducers/general';
// import {GeneralTypes} from 'constants';
// const combinedState = {
// clientConfig: {...initState},
// ping: {...initState}
// };
// describe('general reducer', () => {
// describe('PING', () => {
// describe('Init', () => {
// let store;
// let expectedStore;
// before(() => {
// store = reduceGeneral(store, {type: ''});
// expectedStore = {...combinedState};
// });
// it('should be initial state', () => {
// assert.equal(typeof store, 'object');
// });
// it('have a specifc initial state', () => {
// assert.deepEqual(store, expectedStore);
// });
// });
// describe(`when ${GeneralTypes.PING_REQUEST}`, () => {
// let store;
// let expectedStore;
// before(() => {
// store = reduceGeneral(store, {
// type: GeneralTypes.PING_REQUEST
// });
// expectedStore = {
// ...combinedState,
// ping: {
// ...combinedState.ping,
// loading: true
// }
// };
// });
// it('should set status to fetching', () => {
// assert.deepEqual(store, expectedStore);
// });
// });
// describe(`when ${GeneralTypes.PING_SUCCESS}`, () => {
// let store;
// let expectedStore;
// const data = {some: 'data'};
// before(() => {
// store = reduceGeneral(store, {
// type: GeneralTypes.PING_SUCCESS,
// data
// });
// expectedStore = {
// ...combinedState,
// ping: {
// ...combinedState.ping,
// data
// }
// };
// });
// it('should set status to fetched and data', () => {
// assert.deepEqual(store, expectedStore);
// });
// });
// describe(`when ${GeneralTypes.PING_FAILURE}`, () => {
// let store;
// let error;
// let expectedStore;
// before(() => {
// error = {id: 'the.error.id', message: 'Something went wrong'};
// store = reduceGeneral(store, {
// type: GeneralTypes.PING_FAILURE,
// error
// });
// expectedStore = {
// ...combinedState,
// ping: {
// ...combinedState.ping,
// error
// }
// };
// });
// it('should set status to failed and error', () => {
// assert.deepEqual(store, expectedStore);
// });
// });
// });
// describe('CLIENT_CONFIG', () => {
// describe('Init', () => {
// let store;
// let expectedStore;
// before(() => {
// store = reduceGeneral(store, {type: ''});
// expectedStore = {...combinedState};
// });
// it('should be initial state', () => {
// assert.equal(typeof store, 'object');
// });
// it('have a specifc initial state', () => {
// assert.deepEqual(store, expectedStore);
// });
// });
// describe(`when ${GeneralTypes.CLIENT_CONFIG_REQUEST}`, () => {
// let store;
// let expectedStore;
// before(() => {
// store = reduceGeneral(store, {
// type: GeneralTypes.CLIENT_CONFIG_REQUEST
// });
// expectedStore = {
// ...combinedState,
// clientConfig: {
// ...combinedState.clientConfig,
// loading: true
// }
// };
// });
// it('should set status to fetching', () => {
// assert.deepEqual(store, expectedStore);
// });
// });
// describe(`when ${GeneralTypes.CLIENT_CONFIG_SUCCESS}`, () => {
// let store;
// let expectedStore;
// const data = {some: 'data'};
// before(() => {
// store = reduceGeneral(store, {
// type: GeneralTypes.CLIENT_CONFIG_SUCCESS,
// data
// });
// expectedStore = {
// ...combinedState,
// clientConfig: {
// ...combinedState.clientConfig,
// data
// }
// };
// });
// it('should set status to fetched and data', () => {
// assert.deepEqual(store, expectedStore);
// });
// });
// describe(`when ${GeneralTypes.CLIENT_CONFIG_FAILURE}`, () => {
// let store;
// let error;
// let expectedStore;
// before(() => {
// error = {id: 'the.error.id', message: 'Something went wrong'};
// store = reduceGeneral(store, {
// type: GeneralTypes.CLIENT_CONFIG_FAILURE,
// error
// });
// expectedStore = {
// ...combinedState,
// clientConfig: {
// ...combinedState.clientConfig,
// error
// }
// };
// });
// it('should set status to failed and error', () => {
// assert.deepEqual(store, expectedStore);
// });
// });
// });
// });

View file

@ -1,75 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import reduceLogin, {initState} from 'reducers/login';
import {LoginTypes} from 'constants';
describe('login reducer', () => {
describe('Init', () => {
let store;
let expectedStore;
before(() => {
store = reduceLogin(store, {type: ''});
expectedStore = {...initState};
});
it('should be initial state', () => {
assert.equal(typeof store, 'object');
});
it('have a specifc initial state', () => {
assert.deepEqual(store, expectedStore);
});
});
describe(`when ${LoginTypes.LOGIN_REQUEST}`, () => {
let store;
let expectedStore;
before(() => {
store = reduceLogin(store, {
type: LoginTypes.LOGIN_REQUEST
});
expectedStore = {
...initState,
status: 'fetching'
};
});
it('should set status to fetching', () => {
assert.deepEqual(store, expectedStore);
});
});
describe(`when ${LoginTypes.LOGIN_SUCCESS}`, () => {
let store;
let expectedStore;
before(() => {
store = reduceLogin(store, {
type: LoginTypes.LOGIN_SUCCESS
});
expectedStore = {
...initState,
status: 'fetched'
};
});
it('should set status to fetched and data', () => {
assert.deepEqual(store, expectedStore);
});
});
describe(`when ${LoginTypes.LOGIN_FAILURE}`, () => {
let store;
let error;
let expectedStore;
before(() => {
error = {id: 'the.error.id', message: 'Something went wrong'};
store = reduceLogin(store, {
type: LoginTypes.LOGIN_FAILURE,
error
});
expectedStore = {
...initState,
status: 'failed',
error
};
});
it('should set status to failed and error', () => {
assert.deepEqual(store, expectedStore);
});
});
});

View file

@ -1,75 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import reduceLogout, {initState} from 'reducers/logout';
import {LogoutTypes} from 'constants';
describe('logout reducer', () => {
describe('Init', () => {
let store;
let expectedStore;
before(() => {
store = reduceLogout(store, {type: ''});
expectedStore = {...initState};
});
it('should be initial state', () => {
assert.equal(typeof store, 'object');
});
it('have a specifc initial state', () => {
assert.deepEqual(store, expectedStore);
});
});
describe(`when ${LogoutTypes.LOGOUT_REQUEST}`, () => {
let store;
let expectedStore;
before(() => {
store = reduceLogout(store, {
type: LogoutTypes.LOGOUT_REQUEST
});
expectedStore = {
...initState,
status: 'fetching'
};
});
it('should set status to fetching', () => {
assert.deepEqual(store, expectedStore);
});
});
describe(`when ${LogoutTypes.LOGOUT_SUCCESS}`, () => {
let store;
let expectedStore;
before(() => {
store = reduceLogout(store, {
type: LogoutTypes.LOGOUT_SUCCESS
});
expectedStore = {
...initState,
status: 'fetched'
};
});
it('should set status to fetched and data', () => {
assert.deepEqual(store, expectedStore);
});
});
describe(`when ${LogoutTypes.LOGOUT_FAILURE}`, () => {
let store;
let error;
let expectedStore;
before(() => {
error = {id: 'the.error.id', message: 'Something went wrong'};
store = reduceLogout(store, {
type: LogoutTypes.LOGOUT_FAILURE,
error
});
expectedStore = {
...initState,
status: 'failed',
error
};
});
it('should set status to failed and error', () => {
assert.deepEqual(store, expectedStore);
});
});
});

View file

@ -1,80 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import reducePosts, {initState} from 'reducers/posts';
import {PostsTypes} from 'constants';
describe('posts reducer', () => {
describe('Init', () => {
let store;
let expectedStore;
before(() => {
store = reducePosts(store, {type: ''});
expectedStore = {...initState};
});
it('should be initial state', () => {
assert.equal(typeof store, 'object');
});
it('have a specifc initial state', () => {
assert.deepEqual(store, expectedStore);
});
});
describe(`when ${PostsTypes.FETCH_POSTS_REQUEST}`, () => {
let store;
let expectedStore;
before(() => {
store = reducePosts(store, {
type: PostsTypes.FETCH_POSTS_REQUEST
});
expectedStore = {
...initState,
status: 'fetching'
};
});
it('should set status to fetching', () => {
assert.deepEqual(store, expectedStore);
});
});
describe(`when ${PostsTypes.FETCH_POSTS_SUCCESS}`, () => {
let store;
let expectedStore;
const data = {id: '1', attrs: 'attrs'};
before(() => {
store = reducePosts(store, {
type: PostsTypes.FETCH_POSTS_SUCCESS,
data: {
posts: data
}
});
expectedStore = {
...initState,
status: 'fetched',
data
};
});
it('should set status to fetched and data', () => {
assert.deepEqual(store, expectedStore);
});
});
describe(`when ${PostsTypes.FETCH_POSTS_FAILURE}`, () => {
let store;
let error;
let expectedStore;
before(() => {
error = {id: 'the.error.id', message: 'Something went wrong'};
store = reducePosts(store, {
type: PostsTypes.FETCH_POSTS_FAILURE,
error
});
expectedStore = {
...initState,
status: 'failed',
error
};
});
it('should set status to failed and error', () => {
assert.deepEqual(store, expectedStore);
});
});
});

View file

@ -1,95 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import reduceTeams, {initState} from 'reducers/teams';
import {TeamsTypes} from 'constants';
describe('teams reducer', () => {
describe('Init', () => {
let store;
let expectedStore;
before(() => {
store = reduceTeams(store, {type: ''});
expectedStore = {...initState};
});
it('should be initial state', () => {
assert.equal(typeof store, 'object');
});
it('have a specifc initial state', () => {
assert.deepEqual(store, expectedStore);
});
});
describe(`when ${TeamsTypes.SELECT_TEAM}`, () => {
let store;
let expectedStore;
before(() => {
store = reduceTeams(store, {
type: TeamsTypes.SELECT_TEAM,
teamId: '1'
});
expectedStore = {
...initState,
currentTeamId: '1'
};
});
it('should set status to fetching', () => {
assert.deepEqual(store, expectedStore);
});
});
describe(`when ${TeamsTypes.FETCH_TEAMS_REQUEST}`, () => {
let store;
let expectedStore;
before(() => {
store = reduceTeams(store, {
type: TeamsTypes.FETCH_TEAMS_REQUEST
});
expectedStore = {
...initState,
status: 'fetching'
};
});
it('should set status to fetching', () => {
assert.deepEqual(store, expectedStore);
});
});
describe(`when ${TeamsTypes.FETCH_TEAMS_SUCCESS}`, () => {
let store;
const data = {some: 'thing'};
let expectedStore;
before(() => {
store = reduceTeams(store, {
type: TeamsTypes.FETCH_TEAMS_SUCCESS,
data
});
expectedStore = {
...initState,
status: 'fetched',
data
};
});
it('should set status to fetched and data', () => {
assert.deepEqual(store, expectedStore);
});
});
describe(`when ${TeamsTypes.FETCH_TEAMS_FAILURE}`, () => {
let store;
let expectedStore;
let error;
before(() => {
error = {id: 'the.error.id', message: 'Something went wrong'};
store = reduceTeams(store, {
type: TeamsTypes.FETCH_TEAMS_FAILURE,
error
});
expectedStore = {
...initState,
status: 'failed',
error
};
});
it('should set status to failed and error', () => {
assert.deepEqual(store, expectedStore);
});
});
});

View file

@ -2,8 +2,8 @@
// See License.txt for license information.
import assert from 'assert';
import Client from 'client/client.js';
import Config from 'config/config.js';
import Client from 'client/client';
import Config from 'config';
const PASSWORD = 'password1';
@ -20,7 +20,7 @@ class TestHelper {
assertStatusOkay = (data) => {
assert(data);
assert(data.status === 'OK');
}
};
generateId = () => {
// Implementation taken from http://stackoverflow.com/a/2117523
@ -40,7 +40,7 @@ class TestHelper {
});
return 'uid' + id;
}
};
createClient = () => {
const client = new Client();
@ -48,11 +48,11 @@ class TestHelper {
client.setUrl(Config.DefaultServerUrl);
return client;
}
};
fakeEmail = () => {
return 'success' + this.generateId() + '@simulator.amazonses.com';
}
};
fakeUser = () => {
return {
@ -61,19 +61,24 @@ class TestHelper {
password: PASSWORD,
username: this.generateId()
};
}
};
fakeTeam = () => {
const name = this.generateId();
let inviteId = this.generateId();
if (inviteId.length > 32) {
inviteId = inviteId.substring(0, 32);
}
return {
name,
display_name: `Unit Test ${name}`,
type: 'O',
email: this.fakeEmail(),
allowed_domains: ''
allowed_domains: '',
invite_id: inviteId
};
}
};
fakeChannel = (teamId) => {
const name = this.generateId();
@ -84,7 +89,7 @@ class TestHelper {
display_name: `Unit Test ${name}`,
type: 'O'
};
}
};
fakeChannelMember = (userId, channelId) => {
return {
@ -93,14 +98,14 @@ class TestHelper {
notify_props: {},
roles: 'system_user'
};
}
};
fakePost = (channelId) => {
return {
channel_id: channelId,
message: `Unit Test ${this.generateId()}`
};
}
};
initBasic = async (client = this.createClient()) => {
client.setUrl(Config.DefaultServerUrl);
@ -121,7 +126,7 @@ class TestHelper {
channel: this.basicChannel,
post: this.basicPost
};
}
};
}
export default new TestHelper();