mattermost-redux
Version:
Common code (API client, Redux stores, logic, utility functions) for building a Mattermost client
763 lines (660 loc) • 22.9 kB
text/typescript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Client4} from 'client';
import {General} from '../constants';
import {ChannelTypes, TeamTypes, UserTypes} from 'action_types';
import EventEmitter from 'utils/event_emitter';
import {isCompatibleWithJoinViewTeamPermissions} from 'selectors/entities/general';
import {getCurrentTeamId} from 'selectors/entities/teams';
import {getCurrentUserId} from 'selectors/entities/users';
import {GetStateFunc, DispatchFunc, ActionFunc, ActionResult, batchActions, Action} from 'types/actions';
import {Team, TeamMembership, TeamMemberWithError, GetTeamMembersOpts, TeamsWithCount, TeamSearchOpts} from 'types/teams';
import {selectChannel} from './channels';
import {logError} from './errors';
import {bindClientFunc, forceLogoutIfNecessary} from './helpers';
import {getProfilesByIds, getStatusesByIds} from './users';
import {loadRolesIfNeeded} from './roles';
import {UserProfile} from 'types/users';
async function getProfilesAndStatusesForMembers(userIds: string[], dispatch: DispatchFunc, getState: GetStateFunc) {
const {
currentUserId,
profiles,
statuses,
} = getState().entities.users;
const profilesToLoad: string[] = [];
const statusesToLoad: string[] = [];
userIds.forEach((userId) => {
if (!profiles[userId] && !profilesToLoad.includes(userId) && userId !== currentUserId) {
profilesToLoad.push(userId);
}
if (!statuses[userId] && !statusesToLoad.includes(userId) && userId !== currentUserId) {
statusesToLoad.push(userId);
}
});
const requests: Array<Promise<ActionResult|ActionResult[]>> = [];
if (profilesToLoad.length) {
requests.push(dispatch(getProfilesByIds(profilesToLoad)));
}
if (statusesToLoad.length) {
requests.push(dispatch(getStatusesByIds(statusesToLoad)));
}
await Promise.all(requests);
}
export function selectTeam(team: Team | string): ActionFunc {
return async (dispatch: DispatchFunc) => {
const teamId = (typeof team === 'string') ? team : team.id;
dispatch({
type: TeamTypes.SELECT_TEAM,
data: teamId,
});
return {data: true};
};
}
export function getMyTeams(): ActionFunc {
return bindClientFunc({
clientFunc: Client4.getMyTeams,
onRequest: TeamTypes.MY_TEAMS_REQUEST,
onSuccess: [TeamTypes.RECEIVED_TEAMS_LIST, TeamTypes.MY_TEAMS_SUCCESS],
onFailure: TeamTypes.MY_TEAMS_FAILURE,
});
}
export function getMyTeamUnreads(): ActionFunc {
return bindClientFunc({
clientFunc: Client4.getMyTeamUnreads,
onSuccess: TeamTypes.RECEIVED_MY_TEAM_UNREADS,
});
}
export function getTeam(teamId: string): ActionFunc {
return bindClientFunc({
clientFunc: Client4.getTeam,
onSuccess: TeamTypes.RECEIVED_TEAM,
params: [
teamId,
],
});
}
export function getTeamByName(teamName: string): ActionFunc {
return bindClientFunc({
clientFunc: Client4.getTeamByName,
onSuccess: TeamTypes.RECEIVED_TEAM,
params: [
teamName,
],
});
}
export function getTeams(page = 0, perPage: number = General.TEAMS_CHUNK_SIZE, includeTotalCount = false): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
let data;
dispatch({type: TeamTypes.GET_TEAMS_REQUEST, data});
try {
data = await Client4.getTeams(page, perPage, includeTotalCount) as TeamsWithCount;
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch({type: TeamTypes.GET_TEAMS_FAILURE, data});
dispatch(logError(error));
return {error};
}
const actions: Action[] = [
{
type: TeamTypes.RECEIVED_TEAMS_LIST,
data: includeTotalCount ? data.teams : data,
},
{
type: TeamTypes.GET_TEAMS_SUCCESS,
data,
},
];
if (includeTotalCount) {
actions.push({
type: TeamTypes.RECEIVED_TOTAL_TEAM_COUNT,
data: data.total_count,
});
}
dispatch(batchActions(actions));
return {data};
};
}
export function searchTeams(term: string, opts: TeamSearchOpts = {}): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
dispatch({type: TeamTypes.GET_TEAMS_REQUEST, data: null});
let response;
try {
response = await Client4.searchTeams(term, opts);
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(batchActions([
{type: TeamTypes.GET_TEAMS_FAILURE, error},
logError(error),
]));
return {error};
}
// The type of the response is determined by whether or not page/perPage were set
let teams;
if (!opts.page || !opts.per_page) {
teams = response as Team[];
} else {
teams = (response as TeamsWithCount).teams;
}
dispatch(batchActions([
{
type: TeamTypes.RECEIVED_TEAMS_LIST,
data: teams,
},
{
type: TeamTypes.GET_TEAMS_SUCCESS,
},
]));
return {data: response};
};
}
export function createTeam(team: Team): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
let created;
try {
created = await Client4.createTeam(team);
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
}
const member = {
team_id: created.id,
user_id: getState().entities.users.currentUserId,
roles: `${General.TEAM_ADMIN_ROLE} ${General.TEAM_USER_ROLE}`,
delete_at: 0,
msg_count: 0,
mention_count: 0,
};
dispatch(batchActions([
{
type: TeamTypes.CREATED_TEAM,
data: created,
},
{
type: TeamTypes.RECEIVED_MY_TEAM_MEMBER,
data: member,
},
{
type: TeamTypes.SELECT_TEAM,
data: created.id,
},
]));
dispatch(loadRolesIfNeeded(member.roles.split(' ')));
return {data: created};
};
}
export function deleteTeam(teamId: string): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
try {
await Client4.deleteTeam(teamId);
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
}
const entities = getState().entities;
const {
currentTeamId,
} = entities.teams;
const actions: Action[] = [];
if (teamId === currentTeamId) {
EventEmitter.emit('leave_team');
actions.push({type: ChannelTypes.SELECT_CHANNEL, data: ''});
}
actions.push(
{
type: TeamTypes.RECEIVED_TEAM_DELETED,
data: {id: teamId},
},
);
dispatch(batchActions(actions));
return {data: true};
};
}
export function updateTeam(team: Team): ActionFunc {
return bindClientFunc({
clientFunc: Client4.updateTeam,
onSuccess: TeamTypes.UPDATED_TEAM,
params: [
team,
],
});
}
export function patchTeam(team: Team): ActionFunc {
return bindClientFunc({
clientFunc: Client4.patchTeam,
onSuccess: TeamTypes.PATCHED_TEAM,
params: [
team,
],
});
}
export function regenerateTeamInviteId(teamId: string): ActionFunc {
return bindClientFunc({
clientFunc: Client4.regenerateTeamInviteId,
onSuccess: TeamTypes.REGENERATED_TEAM_INVITE_ID,
params: [
teamId,
],
});
}
export function getMyTeamMembers(): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
const getMyTeamMembersFunc = bindClientFunc({
clientFunc: Client4.getMyTeamMembers,
onSuccess: TeamTypes.RECEIVED_MY_TEAM_MEMBERS,
});
const teamMembers = (await getMyTeamMembersFunc(dispatch, getState)) as ActionResult;
if ('data' in teamMembers && teamMembers.data) {
const roles = new Set<string>();
for (const teamMember of teamMembers.data) {
for (const role of teamMember.roles.split(' ')) {
roles.add(role);
}
}
if (roles.size > 0) {
dispatch(loadRolesIfNeeded([...roles]));
}
}
return teamMembers;
};
}
export function getTeamMembers(teamId: string, page = 0, perPage: number = General.TEAMS_CHUNK_SIZE, options: GetTeamMembersOpts): ActionFunc {
return bindClientFunc({
clientFunc: Client4.getTeamMembers,
onRequest: TeamTypes.GET_TEAM_MEMBERS_REQUEST,
onSuccess: [TeamTypes.RECEIVED_MEMBERS_IN_TEAM, TeamTypes.GET_TEAM_MEMBERS_SUCCESS],
onFailure: TeamTypes.GET_TEAM_MEMBERS_FAILURE,
params: [
teamId,
page,
perPage,
options,
],
});
}
export function getTeamMember(teamId: string, userId: string): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
let member;
try {
const memberRequest = Client4.getTeamMember(teamId, userId);
getProfilesAndStatusesForMembers([userId], dispatch, getState);
member = await memberRequest;
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
}
dispatch({
type: TeamTypes.RECEIVED_MEMBERS_IN_TEAM,
data: [member],
});
return {data: member};
};
}
export function getTeamMembersByIds(teamId: string, userIds: string[]): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
let members;
try {
const membersRequest = Client4.getTeamMembersByIds(teamId, userIds);
getProfilesAndStatusesForMembers(userIds, dispatch, getState);
members = await membersRequest;
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
}
dispatch({
type: TeamTypes.RECEIVED_MEMBERS_IN_TEAM,
data: members,
});
return {data: members};
};
}
export function getTeamsForUser(userId: string): ActionFunc {
return bindClientFunc({
clientFunc: Client4.getTeamsForUser,
onRequest: TeamTypes.GET_TEAMS_REQUEST,
onSuccess: [TeamTypes.RECEIVED_TEAMS_LIST, TeamTypes.GET_TEAMS_SUCCESS],
onFailure: TeamTypes.GET_TEAMS_FAILURE,
params: [
userId,
],
});
}
export function getTeamMembersForUser(userId: string): ActionFunc {
return bindClientFunc({
clientFunc: Client4.getTeamMembersForUser,
onSuccess: TeamTypes.RECEIVED_TEAM_MEMBERS,
params: [
userId,
],
});
}
export function getTeamStats(teamId: string): ActionFunc {
return bindClientFunc({
clientFunc: Client4.getTeamStats,
onSuccess: TeamTypes.RECEIVED_TEAM_STATS,
params: [
teamId,
],
});
}
export function addUserToTeamFromInvite(token: string, inviteId: string): ActionFunc {
return bindClientFunc({
clientFunc: Client4.addToTeamFromInvite,
onRequest: TeamTypes.ADD_TO_TEAM_FROM_INVITE_REQUEST,
onSuccess: TeamTypes.ADD_TO_TEAM_FROM_INVITE_SUCCESS,
onFailure: TeamTypes.ADD_TO_TEAM_FROM_INVITE_FAILURE,
params: [
token,
inviteId,
],
});
}
export function addUserToTeam(teamId: string, userId: string): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
let member;
try {
member = await Client4.addToTeam(teamId, userId);
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
}
dispatch(batchActions([
{
type: UserTypes.RECEIVED_PROFILE_IN_TEAM,
data: {id: teamId, user_id: userId},
},
{
type: TeamTypes.RECEIVED_MEMBER_IN_TEAM,
data: member,
},
]));
return {data: member};
};
}
export function addUsersToTeam(teamId: string, userIds: string[]): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
let members;
try {
members = await Client4.addUsersToTeam(teamId, userIds);
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
}
const profiles: Array<Partial<UserProfile>> = [];
members.forEach((m: TeamMembership) => profiles.push({id: m.user_id}));
dispatch(batchActions([
{
type: UserTypes.RECEIVED_PROFILES_LIST_IN_TEAM,
data: profiles,
id: teamId,
},
{
type: TeamTypes.RECEIVED_MEMBERS_IN_TEAM,
data: members,
},
]));
return {data: members};
};
}
export function addUsersToTeamGracefully(teamId: string, userIds: string[]): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
let result: TeamMemberWithError[];
try {
result = await Client4.addUsersToTeamGracefully(teamId, userIds);
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
}
const addedMembers = result ? result.filter((m) => !m.error) : [];
const profiles: Array<Partial<UserProfile>> = addedMembers.map((m) => ({id: m.user_id}));
const members = addedMembers.map((m) => m.member);
dispatch(batchActions([
{
type: UserTypes.RECEIVED_PROFILES_LIST_IN_TEAM,
data: profiles,
id: teamId,
},
{
type: TeamTypes.RECEIVED_MEMBERS_IN_TEAM,
data: members,
},
]));
return {data: result};
};
}
export function removeUserFromTeam(teamId: string, userId: string): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
try {
await Client4.removeFromTeam(teamId, userId);
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
}
const member = {
team_id: teamId,
user_id: userId,
};
const actions: Action[] = [
{
type: UserTypes.RECEIVED_PROFILE_NOT_IN_TEAM,
data: {id: teamId, user_id: userId},
},
{
type: TeamTypes.REMOVE_MEMBER_FROM_TEAM,
data: member,
},
];
const state = getState();
const currentUserId = getCurrentUserId(state);
if (userId === currentUserId) {
const {channels, myMembers} = state.entities.channels;
for (const channelMember of Object.values(myMembers)) {
const channel = channels[channelMember.channel_id];
if (channel && channel.team_id === teamId) {
actions.push({
type: ChannelTypes.LEAVE_CHANNEL,
data: channel,
});
}
}
if (teamId === getCurrentTeamId(state)) {
actions.push(selectChannel(''));
}
}
dispatch(batchActions(actions));
return {data: true};
};
}
export function updateTeamMemberRoles(teamId: string, userId: string, roles: string[]): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
try {
await Client4.updateTeamMemberRoles(teamId, userId, roles);
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
}
const membersInTeam = getState().entities.teams.membersInTeam[teamId];
if (membersInTeam && membersInTeam[userId]) {
dispatch({
type: TeamTypes.RECEIVED_MEMBER_IN_TEAM,
data: {...membersInTeam[userId], roles},
});
}
return {data: true};
};
}
export function sendEmailInvitesToTeam(teamId: string, emails: string[]): ActionFunc {
return bindClientFunc({
clientFunc: Client4.sendEmailInvitesToTeam,
params: [
teamId,
emails,
],
});
}
export function sendEmailGuestInvitesToChannels(teamId: string, channelIds: string[], emails: string[], message: string): ActionFunc {
return bindClientFunc({
clientFunc: Client4.sendEmailGuestInvitesToChannels,
params: [
teamId,
channelIds,
emails,
message,
],
});
}
export function sendEmailInvitesToTeamGracefully(teamId: string, emails: string[]): ActionFunc {
return bindClientFunc({
clientFunc: Client4.sendEmailInvitesToTeamGracefully,
params: [
teamId,
emails,
],
});
}
export function sendEmailGuestInvitesToChannelsGracefully(teamId: string, channelIds: string[], emails: string[], message: string): ActionFunc {
return bindClientFunc({
clientFunc: Client4.sendEmailGuestInvitesToChannelsGracefully,
params: [
teamId,
channelIds,
emails,
message,
],
});
}
export function getTeamInviteInfo(inviteId: string): ActionFunc {
return bindClientFunc({
clientFunc: Client4.getTeamInviteInfo,
onRequest: TeamTypes.TEAM_INVITE_INFO_REQUEST,
onSuccess: TeamTypes.TEAM_INVITE_INFO_SUCCESS,
onFailure: TeamTypes.TEAM_INVITE_INFO_FAILURE,
params: [
inviteId,
],
});
}
export function checkIfTeamExists(teamName: string): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
let data;
try {
data = await Client4.checkIfTeamExists(teamName);
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
}
return {data: data.exists};
};
}
export function joinTeam(inviteId: string, teamId: string): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
dispatch({type: TeamTypes.JOIN_TEAM_REQUEST, data: null});
const state = getState();
try {
if (isCompatibleWithJoinViewTeamPermissions(state)) {
const currentUserId = state.entities.users.currentUserId;
await Client4.addToTeam(teamId, currentUserId);
} else {
await Client4.joinTeam(inviteId);
}
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(batchActions([
{type: TeamTypes.JOIN_TEAM_FAILURE, error},
logError(error),
]));
return {error};
}
getMyTeamUnreads()(dispatch, getState);
await Promise.all([
getTeam(teamId)(dispatch, getState),
getMyTeamMembers()(dispatch, getState),
]);
dispatch({type: TeamTypes.JOIN_TEAM_SUCCESS, data: null});
return {data: true};
};
}
export function setTeamIcon(teamId: string, imageData: File): ActionFunc {
return bindClientFunc({
clientFunc: Client4.setTeamIcon,
params: [
teamId,
imageData,
],
});
}
export function removeTeamIcon(teamId: string): ActionFunc {
return bindClientFunc({
clientFunc: Client4.removeTeamIcon,
params: [
teamId,
],
});
}
export function updateTeamScheme(teamId: string, schemeId: string): ActionFunc {
return bindClientFunc({
clientFunc: async () => {
await Client4.updateTeamScheme(teamId, schemeId);
return {teamId, schemeId};
},
onSuccess: TeamTypes.UPDATED_TEAM_SCHEME,
});
}
export function updateTeamMemberSchemeRoles(
teamId: string,
userId: string,
isSchemeUser: boolean,
isSchemeAdmin: boolean,
): ActionFunc {
return bindClientFunc({
clientFunc: async () => {
await Client4.updateTeamMemberSchemeRoles(teamId, userId, isSchemeUser, isSchemeAdmin);
return {teamId, userId, isSchemeUser, isSchemeAdmin};
},
onSuccess: TeamTypes.UPDATED_TEAM_MEMBER_SCHEME_ROLES,
});
}
export function invalidateAllEmailInvites(): ActionFunc {
return bindClientFunc({
clientFunc: Client4.invalidateAllEmailInvites,
});
}
export function membersMinusGroupMembers(teamID: string, groupIDs: string[], page = 0, perPage: number = General.PROFILE_CHUNK_SIZE): ActionFunc {
return bindClientFunc({
clientFunc: Client4.teamMembersMinusGroupMembers,
onSuccess: TeamTypes.RECEIVED_TEAM_MEMBERS_MINUS_GROUP_MEMBERS,
params: [
teamID,
groupIDs,
page,
perPage,
],
});
}
export function getInProductNotices(teamId: string, client: string, clientVersion: string): ActionFunc {
return bindClientFunc({
clientFunc: Client4.getInProductNotices,
params: [
teamId,
client,
clientVersion,
],
});
}
export function updateNoticesAsViewed(noticeIds: string[]): ActionFunc {
return bindClientFunc({
clientFunc: Client4.updateNoticesAsViewed,
params: [
noticeIds,
],
});
}