mattermost-redux
Version:
Common code (API client, Redux stores, logic, utility functions) for building a Mattermost client
637 lines (555 loc) • 19.1 kB
text/typescript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {combineReducers} from 'redux';
import {AdminTypes, UserTypes} from 'action_types';
import {Stats} from '../../constants';
import PluginState from '../../constants/plugins';
import {GenericAction} from 'types/actions';
import {ClusterInfo, AnalyticsRow} from 'types/admin';
import {Audit} from 'types/audits';
import {Compliance} from 'types/compliance';
import {AdminConfig, EnvironmentConfig} from 'types/config';
import {MixedUnlinkedGroupRedux} from 'types/groups';
import {PluginRedux, PluginStatusRedux} from 'types/plugins';
import {SamlCertificateStatus, SamlMetadataResponse} from 'types/saml';
import {Team} from 'types/teams';
import {UserAccessToken, UserProfile} from 'types/users';
import {Dictionary, RelationOneToOne} from 'types/utilities';
function logs(state: string[] = [], action: GenericAction) {
switch (action.type) {
case AdminTypes.RECEIVED_LOGS: {
return action.data;
}
case UserTypes.LOGOUT_SUCCESS:
return [];
default:
return state;
}
}
function audits(state: Dictionary<Audit> = {}, action: GenericAction) {
switch (action.type) {
case AdminTypes.RECEIVED_AUDITS: {
const nextState = {...state};
for (const audit of action.data) {
nextState[audit.id] = audit;
}
return nextState;
}
case UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function config(state: Partial<AdminConfig> = {}, action: GenericAction) {
switch (action.type) {
case AdminTypes.RECEIVED_CONFIG: {
return action.data;
}
case AdminTypes.ENABLED_PLUGIN: {
const nextPluginSettings = {...state.PluginSettings!};
const nextPluginStates = {...nextPluginSettings.PluginStates};
nextPluginStates[action.data] = {Enable: true};
nextPluginSettings.PluginStates = nextPluginStates;
return {...state, PluginSettings: nextPluginSettings};
}
case AdminTypes.DISABLED_PLUGIN: {
const nextPluginSettings = {...state.PluginSettings!};
const nextPluginStates = {...nextPluginSettings.PluginStates};
nextPluginStates[action.data] = {Enable: false};
nextPluginSettings.PluginStates = nextPluginStates;
return {...state, PluginSettings: nextPluginSettings};
}
case UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function environmentConfig(state: Partial<EnvironmentConfig> = {}, action: GenericAction) {
switch (action.type) {
case AdminTypes.RECEIVED_ENVIRONMENT_CONFIG: {
return action.data;
}
case UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function complianceReports(state: Dictionary<Compliance> = {}, action: GenericAction) {
switch (action.type) {
case AdminTypes.RECEIVED_COMPLIANCE_REPORT: {
const nextState = {...state};
nextState[action.data.id] = action.data;
return nextState;
}
case AdminTypes.RECEIVED_COMPLIANCE_REPORTS: {
const nextState = {...state};
for (const report of action.data) {
nextState[report.id] = report;
}
return nextState;
}
case UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function clusterInfo(state: ClusterInfo[] = [], action: GenericAction) {
switch (action.type) {
case AdminTypes.RECEIVED_CLUSTER_STATUS: {
return action.data;
}
case UserTypes.LOGOUT_SUCCESS:
return [];
default:
return state;
}
}
function samlCertStatus(state: Partial<SamlCertificateStatus> = {}, action: GenericAction) {
switch (action.type) {
case AdminTypes.RECEIVED_SAML_CERT_STATUS: {
return action.data;
}
case UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
export function convertAnalyticsRowsToStats(data: AnalyticsRow[], name: string): Dictionary<number | AnalyticsRow[]> {
const stats: any = {};
const clonedData = [...data];
if (name === 'post_counts_day') {
clonedData.reverse();
stats[Stats.POST_PER_DAY] = clonedData;
return stats;
}
if (name === 'bot_post_counts_day') {
clonedData.reverse();
stats[Stats.BOT_POST_PER_DAY] = clonedData;
return stats;
}
if (name === 'user_counts_with_posts_day') {
clonedData.reverse();
stats[Stats.USERS_WITH_POSTS_PER_DAY] = clonedData;
return stats;
}
clonedData.forEach((row) => {
let key;
switch (row.name) {
case 'channel_open_count':
key = Stats.TOTAL_PUBLIC_CHANNELS;
break;
case 'channel_private_count':
key = Stats.TOTAL_PRIVATE_GROUPS;
break;
case 'post_count':
key = Stats.TOTAL_POSTS;
break;
case 'unique_user_count':
key = Stats.TOTAL_USERS;
break;
case 'inactive_user_count':
key = Stats.TOTAL_INACTIVE_USERS;
break;
case 'team_count':
key = Stats.TOTAL_TEAMS;
break;
case 'total_websocket_connections':
key = Stats.TOTAL_WEBSOCKET_CONNECTIONS;
break;
case 'total_master_db_connections':
key = Stats.TOTAL_MASTER_DB_CONNECTIONS;
break;
case 'total_read_db_connections':
key = Stats.TOTAL_READ_DB_CONNECTIONS;
break;
case 'daily_active_users':
key = Stats.DAILY_ACTIVE_USERS;
break;
case 'monthly_active_users':
key = Stats.MONTHLY_ACTIVE_USERS;
break;
case 'file_post_count':
key = Stats.TOTAL_FILE_POSTS;
break;
case 'hashtag_post_count':
key = Stats.TOTAL_HASHTAG_POSTS;
break;
case 'incoming_webhook_count':
key = Stats.TOTAL_IHOOKS;
break;
case 'outgoing_webhook_count':
key = Stats.TOTAL_OHOOKS;
break;
case 'command_count':
key = Stats.TOTAL_COMMANDS;
break;
case 'session_count':
key = Stats.TOTAL_SESSIONS;
break;
case 'registered_users':
key = Stats.REGISTERED_USERS;
break;
}
if (key) {
stats[key] = row.value;
}
});
return stats;
}
function analytics(state: Dictionary<number | AnalyticsRow[]> = {}, action: GenericAction) {
switch (action.type) {
case AdminTypes.RECEIVED_SYSTEM_ANALYTICS: {
const stats = convertAnalyticsRowsToStats(action.data, action.name);
return {...state, ...stats};
}
case UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function teamAnalytics(state: RelationOneToOne<Team, Dictionary<number | AnalyticsRow[]>> = {}, action: GenericAction) {
switch (action.type) {
case AdminTypes.RECEIVED_TEAM_ANALYTICS: {
const nextState = {...state};
const stats = convertAnalyticsRowsToStats(action.data, action.name);
const analyticsForTeam = {...(nextState[action.teamId] || {}), ...stats};
nextState[action.teamId] = analyticsForTeam;
return nextState;
}
case UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function userAccessTokens(state: Dictionary<UserAccessToken> = {}, action: GenericAction) {
switch (action.type) {
case AdminTypes.RECEIVED_USER_ACCESS_TOKEN: {
return {...state, [action.data.id]: action.data};
}
case AdminTypes.RECEIVED_USER_ACCESS_TOKENS_FOR_USER: {
const nextState: any = {};
for (const uat of action.data) {
nextState[uat.id] = uat;
}
return {...state, ...nextState};
}
case AdminTypes.RECEIVED_USER_ACCESS_TOKENS: {
const nextState: any = {};
for (const uat of action.data) {
nextState[uat.id] = uat;
}
return {...state, ...nextState};
}
case UserTypes.REVOKED_USER_ACCESS_TOKEN: {
const nextState = {...state};
Reflect.deleteProperty(nextState, action.data);
return {...nextState};
}
case UserTypes.ENABLED_USER_ACCESS_TOKEN: {
const token = {...state[action.data], is_active: true};
return {...state, [action.data]: token};
}
case UserTypes.DISABLED_USER_ACCESS_TOKEN: {
const token = {...state[action.data], is_active: false};
return {...state, [action.data]: token};
}
case UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function userAccessTokensByUser(state: RelationOneToOne<UserProfile, Dictionary<UserAccessToken>> = {}, action: GenericAction) {
switch (action.type) {
case AdminTypes.RECEIVED_USER_ACCESS_TOKEN: { // UserAccessToken
const nextUserState: UserAccessToken | Dictionary<UserAccessToken> = {...(state[action.data.user_id] || {})};
nextUserState[action.data.id] = action.data;
return {...state, [action.data.user_id]: nextUserState};
}
case AdminTypes.RECEIVED_USER_ACCESS_TOKENS_FOR_USER: { // UserAccessToken[]
const nextUserState = {...(state[action.userId] || {})};
for (const uat of action.data) {
nextUserState[uat.id] = uat;
}
return {...state, [action.userId]: nextUserState};
}
case AdminTypes.RECEIVED_USER_ACCESS_TOKENS: { // UserAccessToken[]
const nextUserState: any = {};
for (const uat of action.data) {
nextUserState[uat.user_id] = nextUserState[uat.user_id] || {};
nextUserState[uat.user_id][uat.id] = uat;
}
return {...state, ...nextUserState};
}
case UserTypes.REVOKED_USER_ACCESS_TOKEN: {
const userIds = Object.keys(state);
for (let i = 0; i < userIds.length; i++) {
const userId = userIds[i];
if (state[userId] && state[userId][action.data]) {
const nextUserState = {...state[userId]};
Reflect.deleteProperty(nextUserState, action.data);
return {...state, [userId]: nextUserState};
}
}
return state;
}
case UserTypes.ENABLED_USER_ACCESS_TOKEN: {
const userIds = Object.keys(state);
for (let i = 0; i < userIds.length; i++) {
const userId = userIds[i];
if (state[userId] && state[userId][action.data]) {
const nextUserState = {...state[userId]};
const token = {...nextUserState[action.data], is_active: true};
nextUserState[token.id] = token;
return {...state, [userId]: nextUserState};
}
}
return state;
}
case UserTypes.DISABLED_USER_ACCESS_TOKEN: {
const userIds = Object.keys(state);
for (let i = 0; i < userIds.length; i++) {
const userId = userIds[i];
if (state[userId] && state[userId][action.data]) {
const nextUserState = {...state[userId]};
const token = {...nextUserState[action.data], is_active: false};
nextUserState[token.id] = token;
return {...state, [userId]: nextUserState};
}
}
return state;
}
case UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function plugins(state: Dictionary<PluginRedux> = {}, action: GenericAction) {
switch (action.type) {
case AdminTypes.RECEIVED_PLUGINS: {
const nextState = {...state};
const activePlugins = action.data.active;
for (const plugin of activePlugins) {
nextState[plugin.id] = {...plugin, active: true};
}
const inactivePlugins = action.data.inactive;
for (const plugin of inactivePlugins) {
nextState[plugin.id] = {...plugin, active: false};
}
return nextState;
}
case AdminTypes.REMOVED_PLUGIN: {
const nextState = {...state};
Reflect.deleteProperty(nextState, action.data);
return nextState;
}
case AdminTypes.ENABLED_PLUGIN: {
const nextState = {...state};
const plugin = nextState[action.data];
if (plugin && !plugin.active) {
nextState[action.data] = {...plugin, active: true};
return nextState;
}
return state;
}
case AdminTypes.DISABLED_PLUGIN: {
const nextState = {...state};
const plugin = nextState[action.data];
if (plugin && plugin.active) {
nextState[action.data] = {...plugin, active: false};
return nextState;
}
return state;
}
case UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function pluginStatuses(state: Dictionary<PluginStatusRedux> = {}, action: GenericAction) {
switch (action.type) {
case AdminTypes.RECEIVED_PLUGIN_STATUSES: {
const nextState: any = {};
for (const plugin of (action.data || [])) {
const id = plugin.plugin_id;
// The plugin may be in different states across the cluster. Pick the highest one to
// surface an error.
const pluginState = Math.max((nextState[id] && nextState[id].state) || 0, plugin.state);
const instances = [
...((nextState[id] && nextState[id].instances) || []),
{
cluster_id: plugin.cluster_id,
version: plugin.version,
state: plugin.state,
},
];
nextState[id] = {
id,
name: (nextState[id] && nextState[id].name) || plugin.name,
description: (nextState[id] && nextState[id].description) || plugin.description,
version: (nextState[id] && nextState[id].version) || plugin.version,
active: pluginState > 0,
state: pluginState,
instances,
};
}
return nextState;
}
case AdminTypes.ENABLE_PLUGIN_REQUEST: {
const pluginId = action.data;
if (!state[pluginId]) {
return state;
}
return {
...state,
[pluginId]: {
...state[pluginId],
state: PluginState.PLUGIN_STATE_STARTING,
},
};
}
case AdminTypes.DISABLE_PLUGIN_REQUEST: {
const pluginId = action.data;
if (!state[pluginId]) {
return state;
}
return {
...state,
[pluginId]: {
...state[pluginId],
state: PluginState.PLUGIN_STATE_STOPPING,
},
};
}
case AdminTypes.REMOVED_PLUGIN: {
const pluginId = action.data;
if (!state[pluginId]) {
return state;
}
const nextState = {...state};
Reflect.deleteProperty(nextState, pluginId);
return nextState;
}
case UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function ldapGroupsCount(state = 0, action: GenericAction) {
switch (action.type) {
case AdminTypes.RECEIVED_LDAP_GROUPS:
return action.data.count;
case UserTypes.LOGOUT_SUCCESS:
return 0;
default:
return state;
}
}
function ldapGroups(state: Dictionary<MixedUnlinkedGroupRedux> = {}, action: GenericAction) {
switch (action.type) {
case AdminTypes.RECEIVED_LDAP_GROUPS: {
const nextState: any = {};
for (const group of action.data.groups) {
nextState[group.primary_key] = group;
}
return nextState;
}
case AdminTypes.LINKED_LDAP_GROUP: {
const nextState = {...state};
if (nextState[action.data.primary_key]) {
nextState[action.data.primary_key] = action.data;
}
return nextState;
}
case AdminTypes.UNLINKED_LDAP_GROUP: {
const nextState = {...state};
if (nextState[action.data]) {
nextState[action.data] = {
...nextState[action.data],
mattermost_group_id: undefined,
has_syncables: undefined,
failed: false,
};
}
return nextState;
}
case AdminTypes.LINK_LDAP_GROUP_FAILURE: {
const nextState = {...state};
if (nextState[action.data]) {
nextState[action.data] = {
...nextState[action.data],
failed: true,
};
}
return nextState;
}
case AdminTypes.UNLINK_LDAP_GROUP_FAILURE: {
const nextState = {...state};
if (nextState[action.data]) {
nextState[action.data] = {
...nextState[action.data],
failed: true,
};
}
return nextState;
}
case UserTypes.LOGOUT_SUCCESS:
return {};
default:
return state;
}
}
function samlMetadataResponse(state: Partial<SamlMetadataResponse> = {}, action: GenericAction) {
switch (action.type) {
case AdminTypes.RECEIVED_SAML_METADATA_RESPONSE: {
return action.data;
}
default:
return state;
}
}
export default combineReducers({
// array of strings each representing a log entry
logs,
// object where every key is an audit id and has an object with audit details
audits,
// object representing the server configuration
config,
// object representing which fields of the server configuration were set through the environment config
environmentConfig,
// object where every key is a report id and has an object with report details
complianceReports,
// array of cluster status data
clusterInfo,
// object with certificate type as keys and boolean statuses as values
samlCertStatus,
// object with analytic categories as types and numbers as values
analytics,
// object with team ids as keys and analytics objects as values
teamAnalytics,
// object with user ids as keys and objects, with token ids as keys, and
// user access tokens as values without actual token
userAccessTokensByUser,
// object with token ids as keys, and user access tokens as values without actual token
userAccessTokens,
// object with plugin ids as keys and objects representing plugin manifests as values
plugins,
// object with plugin ids as keys and objects representing plugin statuses across the cluster
pluginStatuses,
// object representing the ldap groups
ldapGroups,
// total ldap groups
ldapGroupsCount,
// object representing the metadata response obtained from the IdP
samlMetadataResponse,
});