mattermost-redux
Version:
Common code (API client, Redux stores, logic, utility functions) for building a Mattermost client
643 lines (538 loc) • 23.2 kB
text/typescript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {createSelector} from 'reselect';
import {
getCurrentChannelId,
getCurrentUser,
getCurrentUserId,
getMyCurrentChannelMembership,
getUsers,
getMembersInTeam,
getMembersInChannel,
} from 'selectors/entities/common';
import {getConfig, getLicense} from 'selectors/entities/general';
import {getDirectShowPreferences, getTeammateNameDisplaySetting} from 'selectors/entities/preferences';
import {
displayUsername,
filterProfilesStartingWithTerm,
filterProfilesMatchingWithTerm,
isSystemAdmin,
includesAnAdminRole,
profileListToMap,
sortByUsername,
applyRolesFilters,
} from 'utils/user_utils';
import {Channel, ChannelMembership} from 'types/channels';
import {Reaction} from 'types/reactions';
import {GlobalState} from 'types/store';
import {Team, TeamMembership} from 'types/teams';
import {Group} from 'types/groups';
import {UserProfile} from 'types/users';
import {
$Email,
$ID,
$Username,
Dictionary,
EmailMappedObjects,
IDMappedObjects,
RelationOneToMany,
RelationOneToOne,
UsernameMappedObjects,
} from 'types/utilities';
export {getCurrentUser, getCurrentUserId, getUsers};
type Filters = {
role?: string;
inactive?: boolean;
active?: boolean;
roles?: string[];
exclude_roles?: string[];
channel_roles?: string[];
team_roles?: string[];
};
export function getUserIdsInChannels(state: GlobalState): RelationOneToMany<Channel, UserProfile> {
return state.entities.users.profilesInChannel;
}
export function getUserIdsNotInChannels(state: GlobalState): RelationOneToMany<Channel, UserProfile> {
return state.entities.users.profilesNotInChannel;
}
export function getUserIdsInTeams(state: GlobalState): RelationOneToMany<Team, UserProfile> {
return state.entities.users.profilesInTeam;
}
export function getUserIdsNotInTeams(state: GlobalState): RelationOneToMany<Team, UserProfile> {
return state.entities.users.profilesNotInTeam;
}
export function getUserIdsWithoutTeam(state: GlobalState): Set<$ID<UserProfile>> {
return state.entities.users.profilesWithoutTeam;
}
export function getUserIdsInGroups(state: GlobalState): RelationOneToMany<Group, UserProfile> {
return state.entities.users.profilesInGroup;
}
export function getUserStatuses(state: GlobalState): RelationOneToOne<UserProfile, string> {
return state.entities.users.statuses;
}
export function getUserSessions(state: GlobalState): any[] {
return state.entities.users.mySessions;
}
export function getUserAudits(state: GlobalState): any[] {
return state.entities.users.myAudits;
}
export function getUser(state: GlobalState, id: $ID<UserProfile>): UserProfile {
return state.entities.users.profiles[id];
}
export const getUsersByUsername: (a: GlobalState) => UsernameMappedObjects<UserProfile> = createSelector(
getUsers,
(users) => {
const usersByUsername: Dictionary<UserProfile> = {};
for (const id in users) {
if (users.hasOwnProperty(id)) {
const user = users[id];
usersByUsername[user.username] = user;
}
}
return usersByUsername;
},
);
export function getUserByUsername(state: GlobalState, username: $Username<UserProfile>): UserProfile {
return getUsersByUsername(state)[username];
}
export const getUsersByEmail: (a: GlobalState) => EmailMappedObjects<UserProfile> = createSelector(
getUsers,
(users) => {
const usersByEmail: Dictionary<UserProfile> = {};
for (const user of Object.keys(users).map((key) => users[key])) {
usersByEmail[user.email] = user;
}
return usersByEmail;
},
);
export function getUserByEmail(state: GlobalState, email: $Email<UserProfile>): UserProfile {
return getUsersByEmail(state)[email];
}
export const isCurrentUserSystemAdmin: (state: GlobalState) => boolean = createSelector(
getCurrentUser,
(user) => {
const roles = user?.roles || '';
return isSystemAdmin(roles);
},
);
export const currentUserHasAnAdminRole: (state: GlobalState) => boolean = createSelector(
getCurrentUser,
(user) => {
const roles = user.roles || '';
return includesAnAdminRole(roles);
},
);
export const getCurrentUserRoles: (a: GlobalState) => UserProfile['roles'] = createSelector(
getMyCurrentChannelMembership,
(state) => state.entities.teams.myMembers[state.entities.teams.currentTeamId],
getCurrentUser,
(currentChannelMembership, currentTeamMembership, currentUser) => {
let roles = '';
if (currentTeamMembership) {
roles += `${currentTeamMembership.roles} `;
}
if (currentChannelMembership) {
roles += `${currentChannelMembership.roles} `;
}
if (currentUser) {
roles += currentUser.roles;
}
return roles.trim();
},
);
export type UserMentionKey= {
key: string;
caseSensitive?: boolean;
}
export const getCurrentUserMentionKeys: (state: GlobalState) => UserMentionKey[] = createSelector(
getCurrentUser,
(user: UserProfile) => {
let keys: UserMentionKey[] = [];
if (!user || !user.notify_props) {
return keys;
}
if (user.notify_props.mention_keys) {
keys = keys.concat(user.notify_props.mention_keys.split(',').map((key) => {
return {key};
}));
}
if (user.notify_props.first_name === 'true' && user.first_name) {
keys.push({key: user.first_name, caseSensitive: true});
}
if (user.notify_props.channel === 'true') {
keys.push({key: '@channel'});
keys.push({key: '@all'});
keys.push({key: '@here'});
}
const usernameKey = '@' + user.username;
if (keys.findIndex((key) => key.key === usernameKey) === -1) {
keys.push({key: usernameKey});
}
return keys;
},
);
export const getProfileSetInCurrentChannel: (state: GlobalState) => Array<$ID<UserProfile>> = createSelector(
getCurrentChannelId,
getUserIdsInChannels,
(currentChannel, channelProfiles) => {
return channelProfiles[currentChannel];
},
);
export const getProfileSetNotInCurrentChannel: (state: GlobalState) => Array<$ID<UserProfile>> = createSelector(
getCurrentChannelId,
getUserIdsNotInChannels,
(currentChannel, channelProfiles) => {
return channelProfiles[currentChannel];
},
);
export const getProfileSetInCurrentTeam: (state: GlobalState) => Array<$ID<UserProfile>> = createSelector(
(state) => state.entities.teams.currentTeamId,
getUserIdsInTeams,
(currentTeam, teamProfiles) => {
return teamProfiles[currentTeam];
},
);
export const getProfileSetNotInCurrentTeam: (state: GlobalState) => Array<$ID<UserProfile>> = createSelector(
(state) => state.entities.teams.currentTeamId,
getUserIdsNotInTeams,
(currentTeam, teamProfiles) => {
return teamProfiles[currentTeam];
},
);
const PROFILE_SET_ALL = 'all';
function sortAndInjectProfiles(profiles: IDMappedObjects<UserProfile>, profileSet?: 'all' | Array<$ID<UserProfile>> | Set<$ID<UserProfile>>): UserProfile[] {
let currentProfiles: UserProfile[] = [];
if (typeof profileSet === 'undefined') {
return currentProfiles;
} else if (profileSet === PROFILE_SET_ALL) {
currentProfiles = Object.keys(profiles).map((key) => profiles[key]);
} else {
currentProfiles = Array.from(profileSet).map((p) => profiles[p]);
}
currentProfiles = currentProfiles.filter((profile) => Boolean(profile));
return currentProfiles.sort(sortByUsername);
}
export const getProfiles: (state: GlobalState, filters: Filters) => UserProfile[] = createSelector(
getUsers,
(state: GlobalState, filters: Filters) => filters,
(profiles, filters) => {
return sortAndInjectProfiles(filterProfiles(profiles, filters), PROFILE_SET_ALL);
},
);
export function filterProfiles(profiles: IDMappedObjects<UserProfile>, filters?: Filters, memberships?: RelationOneToOne<UserProfile, TeamMembership> | RelationOneToOne<UserProfile, ChannelMembership>): IDMappedObjects<UserProfile> {
if (!filters) {
return profiles;
}
let users = Object.keys(profiles).map((key) => profiles[key]);
const filterRole = (filters.role && filters.role !== '') ? [filters.role] : [];
const filterRoles = [...filterRole, ...(filters.roles || []), ...(filters.team_roles || []), ...(filters.channel_roles || [])];
const excludeRoles = filters.exclude_roles || [];
if (filterRoles.length > 0 || excludeRoles.length > 0) {
users = users.filter((user) => {
return user.roles.length > 0 && applyRolesFilters(user, filterRoles, excludeRoles, memberships?.[user.id]);
});
}
if (filters.inactive) {
users = users.filter((user) => user.delete_at !== 0);
} else if (filters.active) {
users = users.filter((user) => user.delete_at === 0);
}
return users.reduce((acc, user) => {
acc[user.id] = user;
return acc;
}, {} as IDMappedObjects<UserProfile>);
}
export function getIsManualStatusForUserId(state: GlobalState, userId: $ID<UserProfile>): boolean {
return state.entities.users.isManualStatus[userId];
}
export const getProfilesInCurrentChannel: (state: GlobalState) => UserProfile[] = createSelector(
getUsers,
getProfileSetInCurrentChannel,
(profiles, currentChannelProfileSet) => {
return sortAndInjectProfiles(profiles, currentChannelProfileSet);
},
);
export const getProfilesNotInCurrentChannel: (state: GlobalState) => UserProfile[] = createSelector(
getUsers,
getProfileSetNotInCurrentChannel,
(profiles, notInCurrentChannelProfileSet) => {
return sortAndInjectProfiles(profiles, notInCurrentChannelProfileSet);
},
);
export const getProfilesInCurrentTeam: (state: GlobalState) => UserProfile[] = createSelector(
getUsers,
getProfileSetInCurrentTeam,
(profiles, currentTeamProfileSet) => {
return sortAndInjectProfiles(profiles, currentTeamProfileSet);
},
);
export const getProfilesInTeam: (state: GlobalState, teamId: $ID<Team>, filters?: Filters) => UserProfile[] = createSelector(
getUsers,
getUserIdsInTeams,
getMembersInTeam,
(state: GlobalState, teamId: string) => teamId,
(state: GlobalState, teamId: string, filters: Filters) => filters,
(profiles, usersInTeams, memberships, teamId, filters) => {
return sortAndInjectProfiles(filterProfiles(profiles, filters, memberships), usersInTeams[teamId] || new Set());
},
);
export const getProfilesNotInTeam: (state: GlobalState, teamId: $ID<Team>, filters?: Filters) => UserProfile[] = createSelector(
getUsers,
getUserIdsNotInTeams,
(state: GlobalState, teamId: string) => teamId,
(state: GlobalState, teamId: string, filters: Filters) => filters,
(profiles, usersNotInTeams, teamId, filters) => {
return sortAndInjectProfiles(filterProfiles(profiles, filters), usersNotInTeams[teamId] || new Set());
},
);
export const getProfilesNotInCurrentTeam: (state: GlobalState) => UserProfile[] = createSelector(
getUsers,
getProfileSetNotInCurrentTeam,
(profiles, notInCurrentTeamProfileSet) => {
return sortAndInjectProfiles(profiles, notInCurrentTeamProfileSet);
},
);
export const getProfilesWithoutTeam: (state: GlobalState, filters?: Filters) => UserProfile[] = createSelector(
getUsers,
getUserIdsWithoutTeam,
(state: GlobalState, filters: Filters) => filters,
(profiles, withoutTeamProfileSet, filters) => {
return sortAndInjectProfiles(filterProfiles(profiles, filters), withoutTeamProfileSet);
},
);
export function getStatusForUserId(state: GlobalState, userId: $ID<UserProfile>): string {
return getUserStatuses(state)[userId];
}
export function getTotalUsersStats(state: GlobalState): any {
return state.entities.users.stats;
}
export function getFilteredUsersStats(state: GlobalState): any {
return state.entities.users.filteredStats;
}
function filterFromProfiles(currentUserId: $ID<UserProfile>, profiles: UserProfile[], skipCurrent = false, filters?: Filters): UserProfile[] {
const filteredProfilesMap = filterProfiles(profileListToMap(profiles), filters);
const filteredProfiles = Object.keys(filteredProfilesMap).map((key) => filteredProfilesMap[key]);
if (skipCurrent) {
removeCurrentUserFromList(filteredProfiles, currentUserId);
}
return filteredProfiles;
}
export function makeSearchProfilesStartingWithTerm(): (state: GlobalState, term: string, skipCurrent?: boolean, filters?: Filters) => UserProfile[] {
return createSelector(
getUsers,
getCurrentUserId,
(state: GlobalState, term: string) => term,
(state: GlobalState, term: string, skipCurrent?: boolean) => skipCurrent || false,
(stateGlobalState, term: string, skipCurrent?: boolean, filters?: Filters) => filters,
(users, currentUserId, term, skipCurrent, filters) => {
const profiles = filterProfilesStartingWithTerm(Object.values(users), term);
return filterFromProfiles(currentUserId, profiles, skipCurrent, filters);
},
);
}
export function makeSearchProfilesMatchingWithTerm(): (state: GlobalState, term: string, skipCurrent?: boolean, filters?: Filters) => UserProfile[] {
return createSelector(
getUsers,
getCurrentUserId,
(state: GlobalState, term: string) => term,
(state: GlobalState, term: string, skipCurrent?: boolean) => skipCurrent || false,
(stateGlobalState, term: string, skipCurrent?: boolean, filters?: Filters) => filters,
(users, currentUserId, term, skipCurrent, filters) => {
const profiles = filterProfilesMatchingWithTerm(Object.values(users), term);
return filterFromProfiles(currentUserId, profiles, skipCurrent, filters);
},
);
}
export function makeSearchProfilesInChannel() {
const doGetProfilesInChannel = makeGetProfilesInChannel();
return (state: GlobalState, channelId: $ID<Channel>, term: string, skipCurrent = false, filters?: Filters): UserProfile[] => {
const profiles = filterProfilesStartingWithTerm(doGetProfilesInChannel(state, channelId, filters), term);
if (skipCurrent) {
removeCurrentUserFromList(profiles, getCurrentUserId(state));
}
return profiles;
};
}
export function searchProfilesInCurrentChannel(state: GlobalState, term: string, skipCurrent = false): UserProfile[] {
const profiles = filterProfilesStartingWithTerm(getProfilesInCurrentChannel(state), term);
if (skipCurrent) {
removeCurrentUserFromList(profiles, getCurrentUserId(state));
}
return profiles;
}
export function searchProfilesNotInCurrentChannel(state: GlobalState, term: string, skipCurrent = false): UserProfile[] {
const profiles = filterProfilesStartingWithTerm(getProfilesNotInCurrentChannel(state), term);
if (skipCurrent) {
removeCurrentUserFromList(profiles, getCurrentUserId(state));
}
return profiles;
}
export function searchProfilesInCurrentTeam(state: GlobalState, term: string, skipCurrent = false): UserProfile[] {
const profiles = filterProfilesStartingWithTerm(getProfilesInCurrentTeam(state), term);
if (skipCurrent) {
removeCurrentUserFromList(profiles, getCurrentUserId(state));
}
return profiles;
}
export function searchProfilesInTeam(state: GlobalState, teamId: $ID<Team>, term: string, skipCurrent = false, filters?: Filters): UserProfile[] {
const profiles = filterProfilesStartingWithTerm(getProfilesInTeam(state, teamId, filters), term);
if (skipCurrent) {
removeCurrentUserFromList(profiles, getCurrentUserId(state));
}
return profiles;
}
export function searchProfilesNotInCurrentTeam(state: GlobalState, term: string, skipCurrent = false): UserProfile[] {
const profiles = filterProfilesStartingWithTerm(getProfilesNotInCurrentTeam(state), term);
if (skipCurrent) {
removeCurrentUserFromList(profiles, getCurrentUserId(state));
}
return profiles;
}
export function searchProfilesWithoutTeam(state: GlobalState, term: string, skipCurrent = false, filters?: Filters): UserProfile[] {
const filteredProfiles = filterProfilesStartingWithTerm(getProfilesWithoutTeam(state, filters), term);
if (skipCurrent) {
removeCurrentUserFromList(filteredProfiles, getCurrentUserId(state));
}
return filteredProfiles;
}
function removeCurrentUserFromList(profiles: UserProfile[], currentUserId: $ID<UserProfile>) {
const index = profiles.findIndex((p) => p.id === currentUserId);
if (index >= 0) {
profiles.splice(index, 1);
}
}
export const shouldShowTermsOfService: (state: GlobalState) => boolean = createSelector(
getConfig,
getCurrentUser,
getLicense,
(config, user, license) => {
// Defaults to false if the user is not logged in or the setting doesn't exist
const acceptedTermsId = user ? user.terms_of_service_id : '';
const acceptedAt = user ? user.terms_of_service_create_at : 0;
const featureEnabled = license.IsLicensed === 'true' && config.EnableCustomTermsOfService === 'true';
const reacceptanceTime = parseInt(config.CustomTermsOfServiceReAcceptancePeriod!, 10) * 1000 * 60 * 60 * 24;
const timeElapsed = new Date().getTime() - acceptedAt;
return Boolean(user && featureEnabled && (config.CustomTermsOfServiceId !== acceptedTermsId || timeElapsed > reacceptanceTime));
},
);
export const getUsersInVisibleDMs: (state: GlobalState) => UserProfile[] = createSelector(
getUsers,
getDirectShowPreferences,
(users, preferences) => {
const dmUsers: UserProfile[] = [];
preferences.forEach((pref) => {
if (pref.value === 'true' && users[pref.name]) {
dmUsers.push(users[pref.name]);
}
});
return dmUsers;
},
);
export function makeGetProfilesForReactions(): (state: GlobalState, reactions: Reaction[]) => UserProfile[] {
return createSelector(
getUsers,
(state: GlobalState, reactions: Reaction[]) => reactions,
(users, reactions) => {
const profiles: UserProfile[] = [];
reactions.forEach((r) => {
if (users[r.user_id]) {
profiles.push(users[r.user_id]);
}
});
return profiles;
},
);
}
export function makeGetProfilesInChannel(): (state: GlobalState, channelId: $ID<Channel>, filters?: Filters) => UserProfile[] {
return createSelector(
getUsers,
getUserIdsInChannels,
getMembersInChannel,
(state: GlobalState, channelId: string) => channelId,
(state, channelId, filters) => filters,
(users, userIds, membersInChannel, channelId, filters = {}) => {
const userIdsInChannel = userIds[channelId];
if (!userIdsInChannel) {
return [];
}
return sortAndInjectProfiles(filterProfiles(users, filters, membersInChannel), userIdsInChannel);
},
);
}
export function makeGetProfilesNotInChannel(): (state: GlobalState, channelId: $ID<Channel>, filters?: Filters) => UserProfile[] {
return createSelector(
getUsers,
getUserIdsNotInChannels,
(state: GlobalState, channelId: string) => channelId,
(state, channelId, filters) => filters,
(users, userIds, channelId, filters = {}) => {
const userIdsInChannel = userIds[channelId];
if (!userIdsInChannel) {
return [];
} else if (filters) {
return sortAndInjectProfiles(filterProfiles(users, filters), userIdsInChannel);
}
return sortAndInjectProfiles(users, userIdsInChannel);
},
);
}
export function makeGetProfilesByIdsAndUsernames(): (
state: GlobalState,
props: {
allUserIds: Array<$ID<UserProfile>>;
allUsernames: Array<$Username<UserProfile>>;
}
) => UserProfile[] {
return createSelector(
getUsers,
getUsersByUsername,
(state: GlobalState, props: {allUserIds: Array<$ID<UserProfile>>; allUsernames: Array<$Username<UserProfile>>}) => props.allUserIds,
(state, props) => props.allUsernames,
(allProfilesById: Dictionary<UserProfile>, allProfilesByUsername: Dictionary<UserProfile>, allUserIds: string[], allUsernames: string[]) => {
const userProfiles: UserProfile[] = [];
if (allUserIds && allUserIds.length > 0) {
const profilesById = allUserIds.
filter((userId) => allProfilesById[userId]).
map((userId) => allProfilesById[userId]);
if (profilesById && profilesById.length > 0) {
userProfiles.push(...profilesById);
}
}
if (allUsernames && allUsernames.length > 0) {
const profilesByUsername = allUsernames.
filter((username) => allProfilesByUsername[username]).
map((username) => allProfilesByUsername[username]);
if (profilesByUsername && profilesByUsername.length > 0) {
userProfiles.push(...profilesByUsername);
}
}
return userProfiles;
},
);
}
export function makeGetDisplayName(): (state: GlobalState, userId: $ID<UserProfile>, useFallbackUsername: boolean) => string {
return createSelector(
(state: GlobalState, userId: string) => getUser(state, userId),
getTeammateNameDisplaySetting,
(state, userId, useFallbackUsername = true) => useFallbackUsername,
(user, teammateNameDisplaySetting, useFallbackUsername) => {
return displayUsername(user, teammateNameDisplaySetting!, useFallbackUsername);
},
);
}
export const getProfilesInGroup: (state: GlobalState, groupId: $ID<Group>, filters?: Filters) => UserProfile[] = createSelector(
getUsers,
getUserIdsInGroups,
(state: GlobalState, groupId: string) => groupId,
(state: GlobalState, groupId: string, filters: Filters) => filters,
(profiles, usersInGroups, groupId, filters) => {
return sortAndInjectProfiles(filterProfiles(profiles, filters), usersInGroups[groupId] || new Set());
},
);
export function searchProfilesInGroup(state: GlobalState, groupId: $ID<Group>, term: string, skipCurrent = false, filters?: Filters): UserProfile[] {
const profiles = filterProfilesStartingWithTerm(getProfilesInGroup(state, groupId, filters), term);
if (skipCurrent) {
removeCurrentUserFromList(profiles, getCurrentUserId(state));
}
return profiles;
}