Fix types of various fields in GlobalState (#26125)

* Fix types of entities.admin.analytics and entities.admin.teamAnalytics

* Fix type of entities.channels.channelsInTeam

* Fix types of entities.users.stats and entities.users.filteredStats

* Fix types of various profilesInX fields in entities.users

* Fix incorrect field name last_password_update_at used when updating password

We never noticed this bug before because the settings modal reloads the current user after updating their password.

* MM-56760 Fix users not being removed from state.entities.users properly
This commit is contained in:
Harrison Healey 2024-02-15 12:25:11 -05:00 committed by GitHub
parent 64b140900e
commit 54507bb115
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 316 additions and 217 deletions

View File

@ -44,7 +44,7 @@ const initialState = {
}), }),
}, },
channelsInTeam: { channelsInTeam: {
'team-id': ['current_channel_id'], 'team-id': new Set(['asdf']),
}, },
messageCounts: { messageCounts: {
current_channel_id: {total: 10}, current_channel_id: {total: 10},

View File

@ -112,8 +112,8 @@ describe('actions/global_actions', () => {
}, },
}, },
channelsInTeam: { channelsInTeam: {
team1: ['channel-in-team-1'], team1: new Set(['channel-in-team-1']),
team2: ['channel-in-team-2'], team2: new Set(['channel-in-team-2']),
}, },
}, },
users: { users: {
@ -185,8 +185,8 @@ describe('actions/global_actions', () => {
}, },
}, },
channelsInTeam: { channelsInTeam: {
team1: ['channel-in-team-1'], team1: new Set(['channel-in-team-1']),
team2: ['channel-in-team-2'], team2: new Set(['channel-in-team-2']),
}, },
}, },
users: { users: {
@ -257,8 +257,8 @@ describe('actions/global_actions', () => {
}, },
}, },
channelsInTeam: { channelsInTeam: {
team1: ['channel-in-team-1'], team1: new Set(['channel-in-team-1']),
team2: ['channel-in-team-2'], team2: new Set(['channel-in-team-2']),
}, },
}, },
users: { users: {
@ -382,8 +382,8 @@ describe('actions/global_actions', () => {
}, },
}, },
channelsInTeam: { channelsInTeam: {
team1: ['channel-in-team-1', directChannelId], team1: new Set(['channel-in-team-1', directChannelId]),
team2: ['channel-in-team-2'], team2: new Set(['channel-in-team-2']),
}, },
}, },
users: { users: {
@ -479,8 +479,8 @@ describe('actions/global_actions', () => {
}, },
}, },
channelsInTeam: { channelsInTeam: {
team1: ['channel-in-team-1', directChannelId, groupChannelId], team1: new Set(['channel-in-team-1', directChannelId, groupChannelId]),
team2: ['channel-in-team-2'], team2: new Set(['channel-in-team-2']),
}, },
}, },
users: { users: {
@ -554,8 +554,8 @@ describe('actions/global_actions', () => {
}, },
}, },
channelsInTeam: { channelsInTeam: {
team1: ['channel-in-team-1'], team1: new Set(['channel-in-team-1']),
team2: ['channel-in-team-2'], team2: new Set(['channel-in-team-2']),
}, },
}, },
users: { users: {

View File

@ -12,8 +12,6 @@ import * as Actions from 'actions/status_actions';
import mockStore from 'tests/test_store'; import mockStore from 'tests/test_store';
import type {GlobalState} from 'types/store';
jest.mock('mattermost-redux/actions/users', () => ({ jest.mock('mattermost-redux/actions/users', () => ({
getStatusesByIds: jest.fn(() => { getStatusesByIds: jest.fn(() => {
return {type: ''}; return {type: ''};
@ -38,7 +36,7 @@ describe('actions/status_actions', () => {
currentChannelId: 'channel_id1', currentChannelId: 'channel_id1',
channels: {channel_id1: {id: 'channel_id1', name: 'channel1', team_id: 'team_id1'}, channel_id2: {id: 'channel_id2', name: 'channel2', team_id: 'team_id1'}}, channels: {channel_id1: {id: 'channel_id1', name: 'channel1', team_id: 'team_id1'}, channel_id2: {id: 'channel_id2', name: 'channel2', team_id: 'team_id1'}},
myMembers: {channel_id1: {channel_id: 'channel_id1', user_id: 'current_user_id'}}, myMembers: {channel_id1: {channel_id: 'channel_id1', user_id: 'current_user_id'}},
channelsInTeam: {team_id1: ['channel_id1']}, channelsInTeam: {team_id1: new Set(['channel_id1'])},
}, },
general: { general: {
config: { config: {
@ -76,7 +74,7 @@ describe('actions/status_actions', () => {
}, },
}, },
}, },
} as unknown as GlobalState; };
describe('loadStatusesForChannelAndSidebar', () => { describe('loadStatusesForChannelAndSidebar', () => {
test('load statuses with posts in channel and user in sidebar', () => { test('load statuses with posts in channel and user in sidebar', () => {

View File

@ -90,7 +90,7 @@ describe('Actions.User', () => {
} as Channel, } as Channel,
}, },
channelsInTeam: { channelsInTeam: {
team_1: ['current_channel_id'], team_1: new Set(['current_channel_id']),
}, },
messageCounts: { messageCounts: {
current_channel_id: {total: 10} as ChannelMessageCount, current_channel_id: {total: 10} as ChannelMessageCount,
@ -539,7 +539,7 @@ describe('Actions.User', () => {
}; };
const channelsInTeam = { const channelsInTeam = {
'': [gmChannel.id], '': new Set([gmChannel.id]),
}; };
const myMembers = { const myMembers = {

View File

@ -212,7 +212,7 @@ describe('multiSelectChannelTo', () => {
}, {}), }, {}),
}, },
channelsInTeam: { channelsInTeam: {
team1: channelIds.map((id) => `category1_${id}`).concat(channelIds.map((id) => `category2_${id}`)), team1: new Set(channelIds.map((id) => `category1_${id}`).concat(channelIds.map((id) => `category2_${id}`))),
}, },
}, },
teams: { teams: {

View File

@ -132,7 +132,7 @@ let mockState = {
}, },
}, },
channelsInTeam: { channelsInTeam: {
team: ['channel1', 'channel2'], team: new Set(['channel1', 'channel2']),
}, },
membersInChannel: { membersInChannel: {
otherChannel: {}, otherChannel: {},

View File

@ -4,7 +4,7 @@
import React from 'react'; import React from 'react';
import {FormattedMessage, useIntl} from 'react-intl'; import {FormattedMessage, useIntl} from 'react-intl';
import type {AnalyticsRow} from '@mattermost/types/admin'; import type {AnalyticsState} from '@mattermost/types/admin';
import type {CloudCustomer} from '@mattermost/types/cloud'; import type {CloudCustomer} from '@mattermost/types/cloud';
import type {ClientLicense} from '@mattermost/types/config'; import type {ClientLicense} from '@mattermost/types/config';
@ -46,7 +46,7 @@ type Props = {
prevTrialLicense: ClientLicense; prevTrialLicense: ClientLicense;
stats?: Record<string, number | AnalyticsRow[]>; stats?: AnalyticsState;
actions: { actions: {
getPrevTrialLicense: () => void; getPrevTrialLicense: () => void;
getCloudSubscription: () => void; getCloudSubscription: () => void;

View File

@ -6,7 +6,7 @@ import {bindActionCreators} from 'redux';
import type {Dispatch} from 'redux'; import type {Dispatch} from 'redux';
import type {ChannelStats} from '@mattermost/types/channels'; import type {ChannelStats} from '@mattermost/types/channels';
import type {UserProfile, UsersStats} from '@mattermost/types/users'; import type {UserProfile} from '@mattermost/types/users';
import {getChannelStats} from 'mattermost-redux/actions/channels'; import {getChannelStats} from 'mattermost-redux/actions/channels';
import {getFilteredUsersStats} from 'mattermost-redux/actions/users'; import {getFilteredUsersStats} from 'mattermost-redux/actions/users';
@ -69,10 +69,10 @@ function makeMapStateToProps() {
}; };
totalCount = stats.member_count; totalCount = stats.member_count;
} else { } else {
const filteredUserStats: UsersStats = selectFilteredUsersStats(state) || { const filteredUserStats = selectFilteredUsersStats(state) || {
total_users_count: 0, total_users_count: 0,
}; };
totalCount = filteredUserStats.total_users_count; totalCount = filteredUserStats.total_users_count ?? 0;
} }
let users = []; let users = [];

View File

@ -5,7 +5,7 @@ import {connect} from 'react-redux';
import {bindActionCreators} from 'redux'; import {bindActionCreators} from 'redux';
import type {Dispatch} from 'redux'; import type {Dispatch} from 'redux';
import type {UserProfile, UsersStats} from '@mattermost/types/users'; import type {UserProfile} from '@mattermost/types/users';
import {getTeamStats as loadTeamStats} from 'mattermost-redux/actions/teams'; import {getTeamStats as loadTeamStats} from 'mattermost-redux/actions/teams';
import {getFilteredUsersStats} from 'mattermost-redux/actions/users'; import {getFilteredUsersStats} from 'mattermost-redux/actions/users';
@ -49,10 +49,10 @@ function mapStateToProps(state: GlobalState, props: Props) {
const stats = getTeamStats(state)[teamId] || {active_member_count: 0}; const stats = getTeamStats(state)[teamId] || {active_member_count: 0};
totalCount = stats.active_member_count; totalCount = stats.active_member_count;
} else { } else {
const filteredUserStats: UsersStats = selectFilteredUsersStats(state) || { const filteredUserStats = selectFilteredUsersStats(state) || {
total_users_count: 0, total_users_count: 0,
}; };
totalCount = filteredUserStats.total_users_count; totalCount = filteredUserStats.total_users_count ?? 0;
} }
let users = []; let users = [];

View File

@ -72,7 +72,7 @@ const usesLDAP = async (
// // @see discussion here: https://github.com/mattermost/mattermost-webapp/pull/9822#discussion_r806879385 // // @see discussion here: https://github.com/mattermost/mattermost-webapp/pull/9822#discussion_r806879385
// const fetchGuestAccounts = async ( // const fetchGuestAccounts = async (
// config: Partial<AdminConfig>, // config: Partial<AdminConfig>,
// analytics: Record<string, number | AnalyticsRow[]> | undefined, // analytics: AnalyticsState | undefined,
// ) => { // ) => {
// if (config.TeamSettings?.EnableOpenServer && config.GuestAccountsSettings?.Enable) { // if (config.TeamSettings?.EnableOpenServer && config.GuestAccountsSettings?.Enable) {
// let usersArray = await fetch(`${Client4.getBaseRoute()}/users/invalid_emails`).then((result) => result.json()); // let usersArray = await fetch(`${Client4.getBaseRoute()}/users/invalid_emails`).then((result) => result.json());

View File

@ -4,7 +4,7 @@
import React from 'react'; import React from 'react';
import {FormattedMessage, defineMessages} from 'react-intl'; import {FormattedMessage, defineMessages} from 'react-intl';
import type {AnalyticsRow, PluginAnalyticsRow, IndexedPluginAnalyticsRow} from '@mattermost/types/admin'; import type {AnalyticsRow, PluginAnalyticsRow, IndexedPluginAnalyticsRow, AnalyticsState} from '@mattermost/types/admin';
import type {ClientLicense} from '@mattermost/types/config'; import type {ClientLicense} from '@mattermost/types/config';
import * as AdminActions from 'actions/admin_actions.jsx'; import * as AdminActions from 'actions/admin_actions.jsx';
@ -33,7 +33,7 @@ const StatTypes = Constants.StatTypes;
type Props = { type Props = {
isLicensed: boolean; isLicensed: boolean;
stats?: Record<string, number | AnalyticsRow[]>; stats?: AnalyticsState;
license: ClientLicense; license: ClientLicense;
pluginStatHandlers: GlobalState['plugins']['siteStatsHandlers']; pluginStatHandlers: GlobalState['plugins']['siteStatsHandlers'];
} }
@ -119,7 +119,7 @@ export default class SystemAnalytics extends React.PureComponent<Props, State> {
this.setState({pluginSiteStats: allStatsIndexed}); this.setState({pluginSiteStats: allStatsIndexed});
} }
private getStatValue(stat: number | AnalyticsRow[]): number | undefined { private getStatValue(stat: number | AnalyticsRow[] | undefined): number | undefined {
if (typeof stat === 'number') { if (typeof stat === 'number') {
return stat; return stat;
} }

View File

@ -5,7 +5,7 @@ import React from 'react';
import type {MessageDescriptor} from 'react-intl'; import type {MessageDescriptor} from 'react-intl';
import {FormattedDate, FormattedMessage, defineMessages} from 'react-intl'; import {FormattedDate, FormattedMessage, defineMessages} from 'react-intl';
import type {AnalyticsRow} from '@mattermost/types/admin'; import type {AnalyticsRow, AnalyticsState} from '@mattermost/types/admin';
import type {ClientLicense} from '@mattermost/types/config'; import type {ClientLicense} from '@mattermost/types/config';
import type {Team} from '@mattermost/types/teams'; import type {Team} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users'; import type {UserProfile} from '@mattermost/types/users';
@ -52,7 +52,7 @@ type Props = {
license: ClientLicense; license: ClientLicense;
stats: RelationOneToOne<Team, Record<string, number | AnalyticsRow[]>>; stats: RelationOneToOne<Team, AnalyticsState>;
actions: { actions: {
@ -129,7 +129,7 @@ export default class TeamAnalytics extends React.PureComponent<Props, State> {
} }
} }
private getStatValue(stat: number | AnalyticsRow[]): number | undefined { private getStatValue(stat: number | AnalyticsRow[] | undefined): number | undefined {
if (typeof stat === 'number') { if (typeof stat === 'number') {
return stat; return stat;
} }

View File

@ -44,7 +44,7 @@ const initialState: DeepPartial<GlobalState> = {
}, },
}, },
channelsInTeam: { channelsInTeam: {
'team-id': ['current_channel_id'], 'team-id': new Set(['current_channel_id']),
}, },
messageCounts: { messageCounts: {
current_channel_id: {total: 10}, current_channel_id: {total: 10},

View File

@ -46,7 +46,7 @@ const initialState: DeepPartial<GlobalState> = {
}, },
}, },
channelsInTeam: { channelsInTeam: {
'team-id': ['current_channel_id'], 'team-id': new Set(['current_channel_id']),
}, },
messageCounts: { messageCounts: {
current_channel_id: {total: 10}, current_channel_id: {total: 10},

View File

@ -45,7 +45,7 @@ const initialState: DeepPartial<GlobalState> = {
}, },
}, },
channelsInTeam: { channelsInTeam: {
'team-id': ['current_channel_id'], 'team-id': new Set(['current_channel_id']),
}, },
messageCounts: { messageCounts: {
current_channel_id: {total: 10}, current_channel_id: {total: 10},

View File

@ -59,7 +59,7 @@ describe('Actions', () => {
currentChannelId: 'channel_id1', currentChannelId: 'channel_id1',
channels: {channel_id1: channel1, channel_id2: channel2, channel_id3: channel3, channel_id4: channel4, channel_id5: channel5, channel_id6: channel6}, channels: {channel_id1: channel1, channel_id2: channel2, channel_id3: channel3, channel_id4: channel4, channel_id5: channel5, channel_id6: channel6},
myMembers: {channel_id1: {channel_id: 'channel_id1', user_id: 'current_user_id'}, channel_id2: {channel_id: 'channel_id2', user_id: 'current_user_id'}}, myMembers: {channel_id1: {channel_id: 'channel_id1', user_id: 'current_user_id'}, channel_id2: {channel_id: 'channel_id2', user_id: 'current_user_id'}},
channelsInTeam: {team_id1: ['channel_id1'], team_id2: ['channel_id2']}, channelsInTeam: {team_id1: new Set(['channel_id1']), team_id2: new Set(['channel_id2'])},
}, },
teams: { teams: {
currentTeamId: 'team_id1', currentTeamId: 'team_id1',

View File

@ -78,7 +78,7 @@ const makeMapStateToProps = () => {
currentChannelMembers, currentChannelMembers,
currentUserId, currentUserId,
restrictDirectMessage, restrictDirectMessage,
totalCount: stats.total_users_count, totalCount: stats.total_users_count ?? 0,
}; };
}; };
}; };

View File

@ -216,7 +216,7 @@ describe('makeGetOptions', () => {
[gmChannel3.id]: gmChannel3, [gmChannel3.id]: gmChannel3,
}, },
channelsInTeam: { channelsInTeam: {
'': [gmChannel1.id, gmChannel2.id, gmChannel3.id], '': new Set([gmChannel1.id, gmChannel2.id, gmChannel3.id]),
}, },
}, },
users: { users: {
@ -291,7 +291,7 @@ describe('makeGetOptions', () => {
[gmChannel2.id]: gmChannel2, [gmChannel2.id]: gmChannel2,
}, },
channelsInTeam: { channelsInTeam: {
'': [gmChannel1.id, gmChannel2.id], '': new Set([gmChannel1.id, gmChannel2.id]),
}, },
}, },
users: { users: {
@ -378,7 +378,7 @@ describe('makeGetOptions', () => {
[gmChannel3.id]: gmChannel3, [gmChannel3.id]: gmChannel3,
}, },
channelsInTeam: { channelsInTeam: {
'': [gmChannel1.id, gmChannel2.id, gmChannel3.id], '': new Set([gmChannel1.id, gmChannel2.id, gmChannel3.id]),
}, },
}, },
users: { users: {
@ -480,7 +480,7 @@ describe('makeGetOptions', () => {
[gmChannel2.id]: gmChannel2, [gmChannel2.id]: gmChannel2,
}, },
channelsInTeam: { channelsInTeam: {
'': [gmChannel1.id, gmChannel2.id], '': new Set([gmChannel1.id, gmChannel2.id]),
}, },
}, },
users: { users: {
@ -591,7 +591,7 @@ describe('makeGetOptions', () => {
[dm1.id]: dm1, [dm1.id]: dm1,
}, },
channelsInTeam: { channelsInTeam: {
'': [dm1.id], '': new Set([dm1.id]),
}, },
}, },
users: { users: {

View File

@ -35,7 +35,7 @@ describe('components/sidebar/invite_members_button', () => {
}, },
stats: { stats: {
total_users_count: 10, total_users_count: 10,
} as any, // HARRISONTODO The defined type of entities.users.stats is incorrect },
}, },
roles: { roles: {
roles: { roles: {

View File

@ -141,7 +141,7 @@ describe('components/sidebar', () => {
channel2, channel2,
}, },
channelsInTeam: { channelsInTeam: {
[currentTeamId]: [channel1.id, channel2.id], [currentTeamId]: new Set([channel1.id, channel2.id]),
}, },
messageCounts: { messageCounts: {
channel1: {total: 10}, channel1: {total: 10},

View File

@ -40,7 +40,7 @@ export const reduxTestState = {
}, },
}, },
channelsInTeam: { channelsInTeam: {
'team-id': ['current_channel_id'], 'team-id': new Set(['current_channel_id']),
}, },
messageCounts: { messageCounts: {
current_channel_id: {total: 10}, current_channel_id: {total: 10},

View File

@ -91,14 +91,14 @@ describe('components/SearchChannelWithPermissionsProvider', () => {
}), }),
}, },
channelsInTeam: { channelsInTeam: {
someTeamId: [ someTeamId: new Set([
'somePublicMemberChannelId', 'somePublicMemberChannelId',
'somePrivateMemberChannelId', 'somePrivateMemberChannelId',
'somePublicNonMemberChannelId', 'somePublicNonMemberChannelId',
'somePrivateNonMemberChannelId', 'somePrivateNonMemberChannelId',
'someDirectConversation', 'someDirectConversation',
'someGroupConversation', 'someGroupConversation',
], ]),
}, },
}, },
roles: { roles: {

View File

@ -726,7 +726,7 @@ describe('components/SwitchChannelProvider', () => {
}, },
}, },
channelsInTeam: { channelsInTeam: {
'': ['other_gm_channel'], '': new Set(['other_gm_channel']),
}, },
}, },
}, },
@ -793,7 +793,7 @@ describe('components/SwitchChannelProvider', () => {
}), }),
}, },
channelsInTeam: { channelsInTeam: {
'': ['other_gm_channel'], '': new Set(['other_gm_channel']),
}, },
}, },
preferences: { preferences: {
@ -960,7 +960,7 @@ describe('components/SwitchChannelProvider', () => {
}, },
}, },
channelsInTeam: { channelsInTeam: {
'': ['thread_gm_channel'], '': new Set(['thread_gm_channel']),
}, },
}, },
}, },

View File

@ -132,7 +132,7 @@ const notices: Notice[] = [
return false; return false;
} }
if (analytics.TOTAL_USERS < USERS_THRESHOLD) { if (analytics.TOTAL_USERS && analytics.TOTAL_USERS < USERS_THRESHOLD) {
return false; return false;
} }

View File

@ -4,7 +4,7 @@
import React from 'react'; import React from 'react';
import {FormattedMessage, injectIntl, type WrappedComponentProps} from 'react-intl'; import {FormattedMessage, injectIntl, type WrappedComponentProps} from 'react-intl';
import type {AnalyticsRow} from '@mattermost/types/admin'; import type {AnalyticsState} from '@mattermost/types/admin';
import type {Channel} from '@mattermost/types/channels'; import type {Channel} from '@mattermost/types/channels';
import type {ClientConfig, ClientLicense} from '@mattermost/types/config'; import type {ClientConfig, ClientLicense} from '@mattermost/types/config';
import type {PreferenceType} from '@mattermost/types/preferences'; import type {PreferenceType} from '@mattermost/types/preferences';
@ -23,7 +23,7 @@ export interface Props extends WrappedComponentProps {
serverVersion: string; serverVersion: string;
config: Partial<ClientConfig>; config: Partial<ClientConfig>;
license: ClientLicense; license: ClientLicense;
analytics?: Record<string, number | AnalyticsRow[]>; analytics?: AnalyticsState;
currentChannel?: Channel; currentChannel?: Channel;
actions: { actions: {
savePreferences(userId: string, preferences: PreferenceType[]): void; savePreferences(userId: string, preferences: PreferenceType[]): void;

View File

@ -3,7 +3,7 @@
import type React from 'react'; import type React from 'react';
import type {AnalyticsRow} from '@mattermost/types/admin'; import type {AnalyticsState} from '@mattermost/types/admin';
import type {Channel} from '@mattermost/types/channels'; import type {Channel} from '@mattermost/types/channels';
export type Notice = { export type Notice = {
@ -17,7 +17,7 @@ export type Notice = {
serverVersion: string, serverVersion: string,
config: any, config: any,
license: any, license: any,
analytics?: Record<string, number | AnalyticsRow[]>, analytics?: AnalyticsState,
currentChannel?: Channel, currentChannel?: Channel,
): boolean; ): boolean;
} }

View File

@ -84,7 +84,7 @@ describe('component/user_group_popover', () => {
}, },
users: { users: {
profiles, profiles,
profilesInGroup: profilesInGroup as any, // HARRISONTODO The type entities.users.profilesInGroup is incorrectly an array when it should be a Set profilesInGroup,
}, },
preferences: { preferences: {
myPreferences: {}, myPreferences: {},

View File

@ -619,12 +619,12 @@ describe('Actions.Admin', () => {
const analytics = state.entities.admin.analytics; const analytics = state.entities.admin.analytics;
expect(analytics).toBeTruthy(); expect(analytics).toBeTruthy();
expect(analytics[Stats.TOTAL_PUBLIC_CHANNELS] > 0).toBeTruthy(); expect(analytics[Stats.TOTAL_PUBLIC_CHANNELS]).toBeGreaterThan(0);
const teamAnalytics = state.entities.admin.teamAnalytics; const teamAnalytics = state.entities.admin.teamAnalytics;
expect(teamAnalytics).toBeTruthy(); expect(teamAnalytics).toBeTruthy();
expect(teamAnalytics[TestHelper.basicTeam!.id]).toBeTruthy(); expect(teamAnalytics[TestHelper.basicTeam!.id]).toBeTruthy();
expect(teamAnalytics[TestHelper.basicTeam!.id][Stats.TOTAL_PUBLIC_CHANNELS] > 0).toBeTruthy(); expect(teamAnalytics[TestHelper.basicTeam!.id][Stats.TOTAL_PUBLIC_CHANNELS]).toBeGreaterThan(0);
}); });
it('getAdvancedAnalytics', async () => { it('getAdvancedAnalytics', async () => {
@ -641,12 +641,12 @@ describe('Actions.Admin', () => {
const analytics = state.entities.admin.analytics; const analytics = state.entities.admin.analytics;
expect(analytics).toBeTruthy(); expect(analytics).toBeTruthy();
expect(analytics[Stats.TOTAL_SESSIONS] > 0).toBeTruthy(); expect(analytics[Stats.TOTAL_SESSIONS]).toBeGreaterThan(0);
const teamAnalytics = state.entities.admin.teamAnalytics; const teamAnalytics = state.entities.admin.teamAnalytics;
expect(teamAnalytics).toBeTruthy(); expect(teamAnalytics).toBeTruthy();
expect(teamAnalytics[TestHelper.basicTeam!.id]).toBeTruthy(); expect(teamAnalytics[TestHelper.basicTeam!.id]).toBeTruthy();
expect(teamAnalytics[TestHelper.basicTeam!.id][Stats.TOTAL_SESSIONS] > 0).toBeTruthy(); expect(teamAnalytics[TestHelper.basicTeam!.id][Stats.TOTAL_SESSIONS]).toBeGreaterThan(0);
}); });
it('getPostsPerDayAnalytics', async () => { it('getPostsPerDayAnalytics', async () => {

View File

@ -91,7 +91,7 @@ describe('Actions.Threads', () => {
}, },
channels: { channels: {
channelsInTeam: { channelsInTeam: {
[currentTeamId]: [channel.id], [currentTeamId]: new Set([channel.id]),
}, },
channels: { channels: {
[channel.id]: channel, [channel.id]: channel,

View File

@ -1007,7 +1007,7 @@ describe('Actions.Users', () => {
const currentUser = profiles[currentUserId]; const currentUser = profiles[currentUserId];
expect(currentUser).toBeTruthy(); expect(currentUser).toBeTruthy();
expect(currentUser.last_password_update_at > beforeTime).toBeTruthy(); expect(currentUser.last_password_update > beforeTime).toBeTruthy();
}); });
it('generateMfaSecret', async () => { it('generateMfaSecret', async () => {

View File

@ -962,7 +962,7 @@ export function updateUserPassword(userId: string, currentPassword: string, newP
const profile = getState().entities.users.profiles[userId]; const profile = getState().entities.users.profiles[userId];
if (profile) { if (profile) {
dispatch({type: UserTypes.RECEIVED_PROFILE, data: {...profile, last_password_update_at: new Date().getTime()}}); dispatch({type: UserTypes.RECEIVED_PROFILE, data: {...profile, last_password_update: new Date().getTime()}});
} }
return {data: true}; return {data: true};

View File

@ -4,7 +4,7 @@
import type {AnyAction} from 'redux'; import type {AnyAction} from 'redux';
import {combineReducers} from 'redux'; import {combineReducers} from 'redux';
import type {ClusterInfo, AnalyticsRow} from '@mattermost/types/admin'; import type {ClusterInfo, AnalyticsRow, AnalyticsState, AdminState} from '@mattermost/types/admin';
import type {Audit} from '@mattermost/types/audits'; import type {Audit} from '@mattermost/types/audits';
import type {Compliance} from '@mattermost/types/compliance'; import type {Compliance} from '@mattermost/types/compliance';
import type {AdminConfig, EnvironmentConfig} from '@mattermost/types/config'; import type {AdminConfig, EnvironmentConfig} from '@mattermost/types/config';
@ -12,7 +12,6 @@ import type {DataRetentionCustomPolicy} from '@mattermost/types/data_retention';
import type {MixedUnlinkedGroupRedux} from '@mattermost/types/groups'; import type {MixedUnlinkedGroupRedux} from '@mattermost/types/groups';
import type {PluginRedux, PluginStatusRedux} from '@mattermost/types/plugins'; import type {PluginRedux, PluginStatusRedux} from '@mattermost/types/plugins';
import type {SamlCertificateStatus, SamlMetadataResponse} from '@mattermost/types/saml'; import type {SamlCertificateStatus, SamlMetadataResponse} from '@mattermost/types/saml';
import type {Team} from '@mattermost/types/teams';
import type {UserAccessToken, UserProfile} from '@mattermost/types/users'; import type {UserAccessToken, UserProfile} from '@mattermost/types/users';
import type {RelationOneToOne, IDMappedObjects} from '@mattermost/types/utilities'; import type {RelationOneToOne, IDMappedObjects} from '@mattermost/types/utilities';
@ -161,8 +160,8 @@ function samlCertStatus(state: Partial<SamlCertificateStatus> = {}, action: AnyA
} }
} }
export function convertAnalyticsRowsToStats(data: AnalyticsRow[], name: string): Record<string, number | AnalyticsRow[]> { export function convertAnalyticsRowsToStats(data: AnalyticsRow[], name: string): AnalyticsState {
const stats: any = {}; const stats: AnalyticsState = {};
const clonedData = [...data]; const clonedData = [...data];
if (name === 'post_counts_day') { if (name === 'post_counts_day') {
@ -250,7 +249,7 @@ export function convertAnalyticsRowsToStats(data: AnalyticsRow[], name: string):
return stats; return stats;
} }
function analytics(state: Record<string, number | AnalyticsRow[]> = {}, action: AnyAction) { function analytics(state: AdminState['analytics'] = {}, action: AnyAction) {
switch (action.type) { switch (action.type) {
case AdminTypes.RECEIVED_SYSTEM_ANALYTICS: { case AdminTypes.RECEIVED_SYSTEM_ANALYTICS: {
const stats = convertAnalyticsRowsToStats(action.data, action.name); const stats = convertAnalyticsRowsToStats(action.data, action.name);
@ -264,7 +263,7 @@ function analytics(state: Record<string, number | AnalyticsRow[]> = {}, action:
} }
} }
function teamAnalytics(state: RelationOneToOne<Team, Record<string, number | AnalyticsRow[]>> = {}, action: AnyAction) { function teamAnalytics(state: AdminState['teamAnalytics'] = {}, action: AnyAction) {
switch (action.type) { switch (action.type) {
case AdminTypes.RECEIVED_TEAM_ANALYTICS: { case AdminTypes.RECEIVED_TEAM_ANALYTICS: {
const nextState = {...state}; const nextState = {...state};

View File

@ -12,11 +12,12 @@ import type {
ChannelMemberCountByGroup, ChannelMemberCountByGroup,
ChannelMemberCountsByGroup, ChannelMemberCountsByGroup,
ServerChannel, ServerChannel,
ChannelsState,
} from '@mattermost/types/channels'; } from '@mattermost/types/channels';
import type {Group} from '@mattermost/types/groups'; import type {Group} from '@mattermost/types/groups';
import type {Team} from '@mattermost/types/teams'; import type {Team} from '@mattermost/types/teams';
import type { import type {
RelationOneToMany, RelationOneToManyUnique,
RelationOneToOne, RelationOneToOne,
IDMappedObjects, IDMappedObjects,
} from '@mattermost/types/utilities'; } from '@mattermost/types/utilities';
@ -37,7 +38,7 @@ function removeMemberFromChannels(state: RelationOneToOne<Channel, Record<string
return nextState; return nextState;
} }
function channelListToSet(state: any, action: AnyAction) { function channelListToSet(state: RelationOneToManyUnique<Team, Channel>, action: AnyAction) {
const nextState = {...state}; const nextState = {...state};
action.data.forEach((channel: Channel) => { action.data.forEach((channel: Channel) => {
@ -49,7 +50,7 @@ function channelListToSet(state: any, action: AnyAction) {
return nextState; return nextState;
} }
function removeChannelFromSet(state: any, action: AnyAction) { function removeChannelFromSet(state: RelationOneToManyUnique<Team, Channel>, action: AnyAction): RelationOneToManyUnique<Team, Channel> {
const id = action.data.team_id; const id = action.data.team_id;
const nextSet = new Set(state[id]); const nextSet = new Set(state[id]);
nextSet.delete(action.data.id); nextSet.delete(action.data.id);
@ -220,7 +221,7 @@ function toClientChannel(serverChannel: ServerChannel): Channel {
return channel; return channel;
} }
function channelsInTeam(state: RelationOneToMany<Team, Channel> = {}, action: AnyAction) { function channelsInTeam(state: ChannelsState['channelsInTeam'] = {}, action: AnyAction) {
switch (action.type) { switch (action.type) {
case ChannelTypes.RECEIVED_CHANNEL: { case ChannelTypes.RECEIVED_CHANNEL: {
const nextSet = new Set(state[action.data.team_id]); const nextSet = new Set(state[action.data.team_id]);

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import type {UserProfile} from '@mattermost/types/users'; import type {UserProfile, UsersState} from '@mattermost/types/users';
import type {IDMappedObjects} from '@mattermost/types/utilities'; import type {IDMappedObjects} from '@mattermost/types/utilities';
import {UserTypes, ChannelTypes} from 'mattermost-redux/action_types'; import {UserTypes, ChannelTypes} from 'mattermost-redux/action_types';
@ -1006,4 +1006,90 @@ describe('Reducers.users', () => {
expect(newProfiles.third_user_id).toEqual(thirdUser); expect(newProfiles.third_user_id).toEqual(thirdUser);
}); });
}); });
test('PROFILE_NO_LONGER_VISIBLE should remove references to users from state', () => {
const user = TestHelper.getUserMock({id: 'user'});
let state: UsersState = {
currentUserId: '',
mySessions: [],
myAudits: [],
myUserAccessTokens: {},
profiles: {
user,
},
profilesInTeam: {
team1: new Set([user.id]),
},
profilesNotInTeam: {
team2: new Set([user.id]),
},
profilesWithoutTeam: new Set([user.id]),
profilesInChannel: {
channel1: new Set([user.id]),
},
profilesNotInChannel: {
channel2: new Set([user.id]),
},
profilesInGroup: {
group1: new Set([user.id]),
},
profilesNotInGroup: {
group2: new Set([user.id]),
},
statuses: {
[user.id]: 'online',
},
isManualStatus: {
[user.id]: true,
},
stats: {},
filteredStats: {
total_users_count: 0,
},
lastActivity: {},
};
state = deepFreezeAndThrowOnMutation(state);
const nextState = reducer(state, {
type: UserTypes.PROFILE_NO_LONGER_VISIBLE,
data: {
user_id: user.id,
},
});
expect(nextState).toEqual({
currentUserId: '',
mySessions: [],
myAudits: [],
myUserAccessTokens: {},
profiles: {},
profilesInTeam: {
team1: new Set(),
},
profilesNotInTeam: {
team2: new Set(),
},
profilesWithoutTeam: new Set(),
profilesInChannel: {
channel1: new Set(),
},
profilesNotInChannel: {
channel2: new Set(),
},
profilesInGroup: {
group1: new Set(),
},
profilesNotInGroup: {
group2: new Set(),
},
statuses: {},
isManualStatus: {},
stats: {},
filteredStats: {
total_users_count: 0,
},
lastActivity: {},
});
});
}); });

View File

@ -5,22 +5,20 @@ import isEqual from 'lodash/isEqual';
import type {AnyAction} from 'redux'; import type {AnyAction} from 'redux';
import {combineReducers} from 'redux'; import {combineReducers} from 'redux';
import type {Channel} from '@mattermost/types/channels';
import type {Group} from '@mattermost/types/groups';
import type {Team} from '@mattermost/types/teams'; import type {Team} from '@mattermost/types/teams';
import type {UserAccessToken, UserProfile, UserStatus} from '@mattermost/types/users'; import type {UserAccessToken, UserProfile, UserStatus, UsersState} from '@mattermost/types/users';
import type {RelationOneToMany, IDMappedObjects, RelationOneToOne} from '@mattermost/types/utilities'; import type {IDMappedObjects, RelationOneToManyUnique, RelationOneToOne} from '@mattermost/types/utilities';
import {UserTypes, ChannelTypes} from 'mattermost-redux/action_types'; import {UserTypes, ChannelTypes} from 'mattermost-redux/action_types';
function profilesToSet(state: RelationOneToMany<Team, UserProfile>, action: AnyAction) { function profilesToSet(state: RelationOneToManyUnique<Team, UserProfile>, action: AnyAction) {
const id = action.id; const id = action.id;
const users: UserProfile[] = Object.values(action.data); const users: UserProfile[] = Object.values(action.data);
return users.reduce((nextState, user) => addProfileToSet(nextState, id, user.id), state); return users.reduce((nextState, user) => addProfileToSet(nextState, id, user.id), state);
} }
function profileListToSet(state: RelationOneToMany<Team, UserProfile>, action: AnyAction, replace = false) { function profileListToSet(state: RelationOneToManyUnique<Team, UserProfile>, action: AnyAction, replace = false) {
const id = action.id; const id = action.id;
const users: UserProfile[] = action.data || []; const users: UserProfile[] = action.data || [];
@ -34,7 +32,7 @@ function profileListToSet(state: RelationOneToMany<Team, UserProfile>, action: A
return users.reduce((nextState, user) => addProfileToSet(nextState, id, user.id), state); return users.reduce((nextState, user) => addProfileToSet(nextState, id, user.id), state);
} }
function removeProfileListFromSet(state: RelationOneToMany<Team, UserProfile>, action: AnyAction) { function removeProfileListFromSet(state: RelationOneToManyUnique<Team, UserProfile>, action: AnyAction) {
const id = action.id; const id = action.id;
const nextSet = new Set(state[id]); const nextSet = new Set(state[id]);
if (action.data) { if (action.data) {
@ -51,50 +49,31 @@ function removeProfileListFromSet(state: RelationOneToMany<Team, UserProfile>, a
return state; return state;
} }
function addProfileToSet(state: RelationOneToMany<Team, UserProfile>, id: string, userId: string) { function addProfileToSet(state: RelationOneToManyUnique<Team, UserProfile>, id: string, userId: string) {
if (state[id]) {
// The type definitions for this function expect state[id] to be an array, but we seem to use Sets, so handle
// both of those just in case
if (Array.isArray(state[id]) && state[id].includes(userId)) {
return state;
} else if (!Array.isArray(state[id]) && (state[id] as unknown as Set<string>).has(userId)) {
return state;
}
}
const nextSet = new Set(state[id]); const nextSet = new Set(state[id]);
nextSet.add(userId); nextSet.add(userId);
return { return {
...state, ...state,
[id]: nextSet, [id]: nextSet,
} as RelationOneToMany<Team, UserProfile>; };
} }
function removeProfileFromTeams(state: RelationOneToMany<Team, UserProfile>, action: AnyAction) { function removeProfileFromSets(state: RelationOneToManyUnique<Team, UserProfile>, action: AnyAction) {
const newState = {...state}; const newState = {...state};
let removed = false; let removed = false;
Object.keys(state).forEach((key) => { Object.keys(state).forEach((key) => {
if (newState[key][action.data.user_id]) { if (newState[key].has(action.data.user_id)) {
delete newState[key][action.data.user_id]; newState[key] = new Set(newState[key]);
newState[key].delete(action.data.user_id);
removed = true; removed = true;
} }
}); });
return removed ? newState : state; return removed ? newState : state;
} }
function removeProfileFromSet(state: RelationOneToMany<Team, UserProfile>, action: AnyAction) { function removeProfileFromSet(state: RelationOneToManyUnique<Team, UserProfile>, action: AnyAction) {
const {id, user_id: userId} = action.data; const {id, user_id: userId} = action.data;
if (state[id]) {
// The type definitions for this function expect state[id] to be an array, but we seem to use Sets, so handle
// both of those just in case
if (Array.isArray(state[id]) && !state[id].includes(userId)) {
return state;
} else if (!Array.isArray(state[id]) && !(state[id] as unknown as Set<string>).has(userId)) {
return state;
}
}
const nextSet = new Set(state[id]); const nextSet = new Set(state[id]);
nextSet.delete(userId); nextSet.delete(userId);
return { return {
@ -161,7 +140,7 @@ function mySessions(state: Array<{id: string}> = [], action: AnyAction) {
} }
} }
function myAudits(state = [], action: AnyAction) { function myAudits(state: UsersState['myAudits'] = [], action: AnyAction) {
switch (action.type) { switch (action.type) {
case UserTypes.RECEIVED_AUDITS: case UserTypes.RECEIVED_AUDITS:
return [...action.data]; return [...action.data];
@ -274,7 +253,7 @@ function profiles(state: IDMappedObjects<UserProfile> = {}, action: AnyAction) {
} }
} }
function profilesInTeam(state: RelationOneToMany<Team, UserProfile> = {}, action: AnyAction) { function profilesInTeam(state: UsersState['profilesInTeam'] = {}, action: AnyAction) {
switch (action.type) { switch (action.type) {
case UserTypes.RECEIVED_PROFILE_IN_TEAM: case UserTypes.RECEIVED_PROFILE_IN_TEAM:
return addProfileToSet(state, action.data.id, action.data.user_id); return addProfileToSet(state, action.data.id, action.data.user_id);
@ -295,14 +274,14 @@ function profilesInTeam(state: RelationOneToMany<Team, UserProfile> = {}, action
return {}; return {};
case UserTypes.PROFILE_NO_LONGER_VISIBLE: case UserTypes.PROFILE_NO_LONGER_VISIBLE:
return removeProfileFromTeams(state, action); return removeProfileFromSets(state, action);
default: default:
return state; return state;
} }
} }
function profilesNotInTeam(state: RelationOneToMany<Team, UserProfile> = {}, action: AnyAction) { function profilesNotInTeam(state: UsersState['profilesNotInTeam'] = {}, action: AnyAction) {
switch (action.type) { switch (action.type) {
case UserTypes.RECEIVED_PROFILE_NOT_IN_TEAM: case UserTypes.RECEIVED_PROFILE_NOT_IN_TEAM:
return addProfileToSet(state, action.data.id, action.data.user_id); return addProfileToSet(state, action.data.id, action.data.user_id);
@ -323,14 +302,14 @@ function profilesNotInTeam(state: RelationOneToMany<Team, UserProfile> = {}, act
return {}; return {};
case UserTypes.PROFILE_NO_LONGER_VISIBLE: case UserTypes.PROFILE_NO_LONGER_VISIBLE:
return removeProfileFromTeams(state, action); return removeProfileFromSets(state, action);
default: default:
return state; return state;
} }
} }
function profilesWithoutTeam(state: Set<string> = new Set(), action: AnyAction) { function profilesWithoutTeam(state: UsersState['profilesWithoutTeam'] = new Set<string>(), action: AnyAction) {
switch (action.type) { switch (action.type) {
case UserTypes.RECEIVED_PROFILE_WITHOUT_TEAM: { case UserTypes.RECEIVED_PROFILE_WITHOUT_TEAM: {
const nextSet = new Set(state); const nextSet = new Set(state);
@ -342,21 +321,26 @@ function profilesWithoutTeam(state: Set<string> = new Set(), action: AnyAction)
action.data.forEach((user: UserProfile) => nextSet.add(user.id)); action.data.forEach((user: UserProfile) => nextSet.add(user.id));
return nextSet; return nextSet;
} }
case UserTypes.PROFILE_NO_LONGER_VISIBLE:
case UserTypes.RECEIVED_PROFILE_IN_TEAM: { case UserTypes.RECEIVED_PROFILE_IN_TEAM: {
const nextSet = new Set(state); const nextSet = new Set(state);
nextSet.delete(action.data.id); nextSet.delete(action.data.id);
return nextSet; return nextSet;
} }
case UserTypes.PROFILE_NO_LONGER_VISIBLE: {
const nextSet = new Set(state);
nextSet.delete(action.data.user_id);
return nextSet;
}
case UserTypes.LOGOUT_SUCCESS: case UserTypes.LOGOUT_SUCCESS:
return new Set(); return new Set<string>();
default: default:
return state; return state;
} }
} }
function profilesInChannel(state: RelationOneToMany<Channel, UserProfile> = {}, action: AnyAction) { function profilesInChannel(state: UsersState['profilesInChannel'] = {}, action: AnyAction) {
switch (action.type) { switch (action.type) {
case UserTypes.RECEIVED_PROFILE_IN_CHANNEL: case UserTypes.RECEIVED_PROFILE_IN_CHANNEL:
return addProfileToSet(state, action.data.id, action.data.user_id); return addProfileToSet(state, action.data.id, action.data.user_id);
@ -379,7 +363,7 @@ function profilesInChannel(state: RelationOneToMany<Channel, UserProfile> = {},
}}); }});
case UserTypes.PROFILE_NO_LONGER_VISIBLE: case UserTypes.PROFILE_NO_LONGER_VISIBLE:
return removeProfileFromTeams(state, action); return removeProfileFromSets(state, action);
case UserTypes.LOGOUT_SUCCESS: case UserTypes.LOGOUT_SUCCESS:
return {}; return {};
@ -388,7 +372,7 @@ function profilesInChannel(state: RelationOneToMany<Channel, UserProfile> = {},
} }
} }
function profilesNotInChannel(state: RelationOneToMany<Channel, UserProfile> = {}, action: AnyAction) { function profilesNotInChannel(state: UsersState['profilesNotInChannel'] = {}, action: AnyAction) {
switch (action.type) { switch (action.type) {
case UserTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL: case UserTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL:
return addProfileToSet(state, action.data.id, action.data.user_id); return addProfileToSet(state, action.data.id, action.data.user_id);
@ -417,14 +401,14 @@ function profilesNotInChannel(state: RelationOneToMany<Channel, UserProfile> = {
return {}; return {};
case UserTypes.PROFILE_NO_LONGER_VISIBLE: case UserTypes.PROFILE_NO_LONGER_VISIBLE:
return removeProfileFromTeams(state, action); return removeProfileFromSets(state, action);
default: default:
return state; return state;
} }
} }
function profilesInGroup(state: RelationOneToMany<Group, UserProfile> = {}, action: AnyAction) { function profilesInGroup(state: UsersState['profilesInGroup'] = {}, action: AnyAction) {
switch (action.type) { switch (action.type) {
case UserTypes.RECEIVED_PROFILES_LIST_IN_GROUP: { case UserTypes.RECEIVED_PROFILES_LIST_IN_GROUP: {
return profileListToSet(state, action); return profileListToSet(state, action);
@ -459,12 +443,18 @@ function profilesInGroup(state: RelationOneToMany<Group, UserProfile> = {}, acti
} }
return state; return state;
} }
case UserTypes.PROFILE_NO_LONGER_VISIBLE:
return removeProfileFromSets(state, action);
case UserTypes.LOGOUT_SUCCESS:
return {};
default: default:
return state; return state;
} }
} }
function profilesNotInGroup(state: RelationOneToMany<Group, UserProfile> = {}, action: AnyAction) { function profilesNotInGroup(state: UsersState['profilesNotInGroup'] = {}, action: AnyAction) {
switch (action.type) { switch (action.type) {
case UserTypes.RECEIVED_PROFILES_FOR_GROUP: { case UserTypes.RECEIVED_PROFILES_FOR_GROUP: {
const id = action.id; const id = action.id;
@ -484,6 +474,12 @@ function profilesNotInGroup(state: RelationOneToMany<Group, UserProfile> = {}, a
case UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_GROUP: { case UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_GROUP: {
return profileListToSet(state, action); return profileListToSet(state, action);
} }
case UserTypes.PROFILE_NO_LONGER_VISIBLE:
return removeProfileFromSets(state, action);
case UserTypes.LOGOUT_SUCCESS:
return {};
default: default:
return state; return state;
} }
@ -611,7 +607,7 @@ function myUserAccessTokens(state: Record<string, UserAccessToken> = {}, action:
} }
} }
function stats(state = {}, action: AnyAction) { function stats(state: UsersState['stats'] = {}, action: AnyAction) {
switch (action.type) { switch (action.type) {
case UserTypes.RECEIVED_USER_STATS: { case UserTypes.RECEIVED_USER_STATS: {
const stat = action.data; const stat = action.data;
@ -625,7 +621,7 @@ function stats(state = {}, action: AnyAction) {
} }
} }
function filteredStats(state = {}, action: AnyAction) { function filteredStats(state: UsersState['filteredStats'] = {}, action: AnyAction) {
switch (action.type) { switch (action.type) {
case UserTypes.RECEIVED_FILTERED_USER_STATS: { case UserTypes.RECEIVED_FILTERED_USER_STATS: {
const stat = action.data; const stat = action.data;
@ -639,7 +635,7 @@ function filteredStats(state = {}, action: AnyAction) {
} }
} }
function lastActivity(state: RelationOneToOne<UserProfile, string> = {}, action: AnyAction) { function lastActivity(state: UsersState['lastActivity'] = {}, action: AnyAction) {
switch (action.type) { switch (action.type) {
case UserTypes.RECEIVED_STATUS: { case UserTypes.RECEIVED_STATUS: {
const nextState = Object.assign({}, state); const nextState = Object.assign({}, state);

View File

@ -40,9 +40,9 @@ describe('Selectors.Channels.getChannelsInCurrentTeam', () => {
}; };
const channelsInTeam = { const channelsInTeam = {
[team1.id]: [channel1.id, channel3.id], [team1.id]: new Set([channel1.id, channel3.id]),
[team2.id]: [channel2.id], [team2.id]: new Set([channel2.id]),
'': [channel4.id], '': new Set([channel4.id]),
}; };
const testState = deepFreezeAndThrowOnMutation({ const testState = deepFreezeAndThrowOnMutation({
@ -97,7 +97,7 @@ describe('Selectors.Channels.getChannelsInCurrentTeam', () => {
}; };
const channelsInTeam = { const channelsInTeam = {
[team1.id]: [channel1.id, channel2.id], [team1.id]: new Set([channel1.id, channel2.id]),
}; };
const testStateDe = deepFreezeAndThrowOnMutation({ const testStateDe = deepFreezeAndThrowOnMutation({
@ -188,9 +188,9 @@ describe('Selectors.Channels.getMyChannels', () => {
}; };
const channelsInTeam = { const channelsInTeam = {
[team1.id]: [channel1.id, channel3.id], [team1.id]: new Set([channel1.id, channel3.id]),
[team2.id]: [channel2.id], [team2.id]: new Set([channel2.id]),
'': [channel4.id, channel5.id], '': new Set([channel4.id, channel5.id]),
}; };
const myMembers = { const myMembers = {
@ -319,9 +319,9 @@ describe('Selectors.Channels.getOtherChannels', () => {
}; };
const channelsInTeam = { const channelsInTeam = {
[team1.id]: [channel1.id, channel3.id, channel5.id, channel6.id], [team1.id]: new Set([channel1.id, channel3.id, channel5.id, channel6.id]),
[team2.id]: [channel2.id], [team2.id]: new Set([channel2.id]),
'': [channel4.id], '': new Set([channel4.id]),
}; };
const myMembers = { const myMembers = {
@ -625,8 +625,8 @@ describe('Selectors.Channels.getChannelsNameMapInCurrentTeam', () => {
}; };
const channelsInTeam = { const channelsInTeam = {
[team1.id]: [channel1.id, channel4.id], [team1.id]: new Set([channel1.id, channel4.id]),
[team2.id]: [channel2.id, channel3.id], [team2.id]: new Set([channel2.id, channel3.id]),
}; };
const testState = deepFreezeAndThrowOnMutation({ const testState = deepFreezeAndThrowOnMutation({
@ -741,8 +741,8 @@ describe('Selectors.Channels.getChannelsNameMapInTeam', () => {
}; };
const channelsInTeam = { const channelsInTeam = {
[team1.id]: [channel1.id, channel4.id], [team1.id]: new Set([channel1.id, channel4.id]),
[team2.id]: [channel2.id, channel3.id], [team2.id]: new Set([channel2.id, channel3.id]),
}; };
const testState = deepFreezeAndThrowOnMutation({ const testState = deepFreezeAndThrowOnMutation({
@ -796,8 +796,8 @@ describe('Selectors.Channels.getChannelNameToDisplayNameMap', () => {
channel4, channel4,
}, },
channelsInTeam: { channelsInTeam: {
[team1.id]: [channel1.id, channel2.id, channel3.id], [team1.id]: new Set([channel1.id, channel2.id, channel3.id]),
[team2.id]: [channel4.id], [team2.id]: new Set([channel4.id]),
}, },
}, },
teams: { teams: {
@ -913,7 +913,7 @@ describe('Selectors.Channels.getChannelNameToDisplayNameMap', () => {
newChannel, newChannel,
}, },
channelsInTeam: { channelsInTeam: {
[team1.id]: [channel1.id, channel2.id, channel3.id, newChannel.id], [team1.id]: new Set([channel1.id, channel2.id, channel3.id, newChannel.id]),
}, },
}, },
}, },
@ -980,8 +980,8 @@ describe('Selectors.Channels.getGroupChannels', () => {
}; };
const channelsInTeam = { const channelsInTeam = {
[team1.id]: [channel1.id, channel2.id], [team1.id]: new Set([channel1.id, channel2.id]),
'': [channel3.id, channel4.id, channel5.id], '': new Set([channel3.id, channel4.id, channel5.id]),
}; };
const testState = deepFreezeAndThrowOnMutation({ const testState = deepFreezeAndThrowOnMutation({
@ -1028,10 +1028,9 @@ describe('Selectors.Channels.getChannelIdsInCurrentTeam', () => {
const channel5 = TestHelper.fakeChannelWithId(''); const channel5 = TestHelper.fakeChannelWithId('');
const channelsInTeam = { const channelsInTeam = {
[team1.id]: [channel1.id, channel2.id], [team1.id]: new Set([channel1.id, channel2.id]),
[team2.id]: [channel3.id, channel4.id], [team2.id]: new Set([channel3.id, channel4.id]),
// eslint-disable-next-line no-useless-computed-key '': new Set([channel5.id]),
['']: [channel5.id],
}; };
const testState = deepFreezeAndThrowOnMutation({ const testState = deepFreezeAndThrowOnMutation({
@ -1055,10 +1054,10 @@ describe('Selectors.Channels.getChannelIdsInCurrentTeam', () => {
...testState.entities.channels, ...testState.entities.channels,
channelsInTeam: { channelsInTeam: {
...testState.entities.channels.channelsInTeam, ...testState.entities.channels.channelsInTeam,
[team2.id]: [ [team2.id]: new Set([
...testState.entities.channels.channelsInTeam[team2.id], ...testState.entities.channels.channelsInTeam[team2.id],
newChannel.id, newChannel.id,
], ]),
}, },
}, },
}, },
@ -1085,10 +1084,9 @@ describe('Selectors.Channels.getChannelIdsForCurrentTeam', () => {
const channel5 = TestHelper.fakeChannelWithId(''); const channel5 = TestHelper.fakeChannelWithId('');
const channelsInTeam = { const channelsInTeam = {
[team1.id]: [channel1.id, channel2.id], [team1.id]: new Set([channel1.id, channel2.id]),
[team2.id]: [channel3.id, channel4.id], [team2.id]: new Set([channel3.id, channel4.id]),
// eslint-disable-next-line no-useless-computed-key '': new Set([channel5.id]),
['']: [channel5.id],
}; };
const testState = deepFreezeAndThrowOnMutation({ const testState = deepFreezeAndThrowOnMutation({
@ -1112,10 +1110,10 @@ describe('Selectors.Channels.getChannelIdsForCurrentTeam', () => {
...testState.entities.channels, ...testState.entities.channels,
channelsInTeam: { channelsInTeam: {
...testState.entities.channels.channelsInTeam, ...testState.entities.channels.channelsInTeam,
[team2.id]: [ [team2.id]: new Set([
...testState.entities.channels.channelsInTeam[team2.id], ...testState.entities.channels.channelsInTeam[team2.id],
anotherChannel.id, anotherChannel.id,
], ]),
}, },
}, },
}, },
@ -1273,8 +1271,8 @@ describe('Selectors.Channels.getChannelsWithUserProfiles', () => {
}; };
const channelsInTeam = { const channelsInTeam = {
[team1.id]: [channel1.id], [team1.id]: new Set([channel1.id]),
'': [channel2.id], '': new Set([channel2.id]),
}; };
const user1 = TestHelper.fakeUserWithId(); const user1 = TestHelper.fakeUserWithId();
@ -1333,7 +1331,7 @@ describe('Selectors.Channels.getChannelsWithUserProfiles', () => {
[unloadedChannel.id]: unloadedChannel, [unloadedChannel.id]: unloadedChannel,
}, },
channelsInTeam: { channelsInTeam: {
'': [channel2.id, unloadedChannel.id], '': new Set([channel2.id, unloadedChannel.id]),
}, },
}, },
}, },
@ -2006,7 +2004,7 @@ describe('Selectors.Channels.getUnreadStatusInCurrentTeam', () => {
}; };
const channelsInTeam = { const channelsInTeam = {
[team1.id]: [channel1.id, channel2.id], [team1.id]: new Set([channel1.id, channel2.id]),
}; };
const testState = deepFreezeAndThrowOnMutation({ const testState = deepFreezeAndThrowOnMutation({
@ -3078,9 +3076,9 @@ describe('Selectors.Channels.getUnreadChannelIds', () => {
}; };
const channelsInTeam = { const channelsInTeam = {
[team1.id]: [channel1.id, channel3.id], [team1.id]: new Set([channel1.id, channel3.id]),
[team2.id]: [channel2.id], [team2.id]: new Set([channel2.id]),
'': [channel4.id], '': new Set([channel4.id]),
}; };
const messageCounts = { const messageCounts = {
@ -3152,9 +3150,9 @@ describe('Selectors.Channels.getUnreadChannelIds', () => {
}; };
const channelsInTeam = { const channelsInTeam = {
[team1.id]: [channel1.id, channel3.id], [team1.id]: new Set([channel1.id, channel3.id]),
[team2.id]: [channel2.id], [team2.id]: new Set([channel2.id]),
'': [channel4.id], '': new Set([channel4.id]),
}; };
const messageCounts = { const messageCounts = {

View File

@ -17,7 +17,6 @@ import type {Team} from '@mattermost/types/teams';
import type {UserProfile, UsersState} from '@mattermost/types/users'; import type {UserProfile, UsersState} from '@mattermost/types/users';
import type { import type {
IDMappedObjects, IDMappedObjects,
RelationOneToMany,
RelationOneToManyUnique, RelationOneToManyUnique,
RelationOneToOne, RelationOneToOne,
} from '@mattermost/types/utilities'; } from '@mattermost/types/utilities';
@ -99,7 +98,7 @@ export function getChannelsMemberCount(state: GlobalState): Record<string, numbe
return state.entities.channels.channelsMemberCount; return state.entities.channels.channelsMemberCount;
} }
export function getChannelsInTeam(state: GlobalState): RelationOneToMany<Team, Channel> { export function getChannelsInTeam(state: GlobalState): RelationOneToManyUnique<Team, Channel> {
return state.entities.channels.channelsInTeam; return state.entities.channels.channelsInTeam;
} }
@ -131,7 +130,7 @@ export function getChannelsInPolicy() {
export const getDirectChannelsSet: (state: GlobalState) => Set<string> = createSelector( export const getDirectChannelsSet: (state: GlobalState) => Set<string> = createSelector(
'getDirectChannelsSet', 'getDirectChannelsSet',
getChannelsInTeam, getChannelsInTeam,
(channelsInTeam: RelationOneToMany<Team, Channel>): Set<string> => { (channelsInTeam: RelationOneToManyUnique<Team, Channel>): Set<string> => {
if (!channelsInTeam) { if (!channelsInTeam) {
return new Set(); return new Set();
} }
@ -364,12 +363,12 @@ export function getChannelByTeamIdAndChannelName(state: GlobalState, teamId: str
); );
} }
export const getChannelSetInCurrentTeam: (state: GlobalState) => string[] = createSelector( export const getChannelSetInCurrentTeam: (state: GlobalState) => Set<string> = createSelector(
'getChannelSetInCurrentTeam', 'getChannelSetInCurrentTeam',
getCurrentTeamId, getCurrentTeamId,
getChannelsInTeam, getChannelsInTeam,
(currentTeamId: string, channelsInTeam: RelationOneToMany<Team, Channel>): string[] => { (currentTeamId: string, channelsInTeam: RelationOneToManyUnique<Team, Channel>) => {
return (channelsInTeam && channelsInTeam[currentTeamId]) || []; return (channelsInTeam && channelsInTeam[currentTeamId]) || new Set();
}, },
); );
@ -387,7 +386,7 @@ export const getChannelSetForAllTeams: (state: GlobalState) => string[] = create
}, },
); );
function sortAndInjectChannels(channels: IDMappedObjects<Channel>, channelSet: string[], locale: string): Channel[] { function sortAndInjectChannels(channels: IDMappedObjects<Channel>, channelSet: string[] | Set<string>, locale: string): Channel[] {
const currentChannels: Channel[] = []; const currentChannels: Channel[] = [];
if (typeof channelSet === 'undefined') { if (typeof channelSet === 'undefined') {
@ -406,7 +405,7 @@ export const getChannelsInCurrentTeam: (state: GlobalState) => Channel[] = creat
getAllChannels, getAllChannels,
getChannelSetInCurrentTeam, getChannelSetInCurrentTeam,
getCurrentUser, getCurrentUser,
(channels: IDMappedObjects<Channel>, currentTeamChannelSet: string[], currentUser: UserProfile): Channel[] => { (channels: IDMappedObjects<Channel>, currentTeamChannelSet: Set<string>, currentUser: UserProfile): Channel[] => {
let locale = General.DEFAULT_LOCALE; let locale = General.DEFAULT_LOCALE;
if (currentUser && currentUser.locale) { if (currentUser && currentUser.locale) {
@ -433,8 +432,8 @@ export const getChannelsNameMapInTeam: (state: GlobalState, teamId: string) => R
getAllChannels, getAllChannels,
getChannelsInTeam, getChannelsInTeam,
(state: GlobalState, teamId: string): string => teamId, (state: GlobalState, teamId: string): string => teamId,
(channels: IDMappedObjects<Channel>, channelsInTeams: RelationOneToMany<Team, Channel>, teamId: string): Record<string, Channel> => { (channels: IDMappedObjects<Channel>, channelsInTeams: RelationOneToManyUnique<Team, Channel>, teamId: string): Record<string, Channel> => {
const channelsInTeam = channelsInTeams[teamId] || []; const channelsInTeam = channelsInTeams[teamId] || new Set();
const channelMap: Record<string, Channel> = {}; const channelMap: Record<string, Channel> = {};
channelsInTeam.forEach((id) => { channelsInTeam.forEach((id) => {
const channel = channels[id]; const channel = channels[id];
@ -448,7 +447,7 @@ export const getChannelsNameMapInCurrentTeam: (state: GlobalState) => Record<str
'getChannelsNameMapInCurrentTeam', 'getChannelsNameMapInCurrentTeam',
getAllChannels, getAllChannels,
getChannelSetInCurrentTeam, getChannelSetInCurrentTeam,
(channels: IDMappedObjects<Channel>, currentTeamChannelSet: string[]): Record<string, Channel> => { (channels: IDMappedObjects<Channel>, currentTeamChannelSet: Set<string>): Record<string, Channel> => {
const channelMap: Record<string, Channel> = {}; const channelMap: Record<string, Channel> = {};
currentTeamChannelSet.forEach((id) => { currentTeamChannelSet.forEach((id) => {
const channel = channels[id]; const channel = channels[id];
@ -462,7 +461,7 @@ export const getChannelNameToDisplayNameMap: (state: GlobalState) => Record<stri
'getChannelNameToDisplayNameMap', 'getChannelNameToDisplayNameMap',
getAllChannels, getAllChannels,
getChannelSetInCurrentTeam, getChannelSetInCurrentTeam,
(channels: IDMappedObjects<Channel>, currentTeamChannelSet: string[]) => { (channels: IDMappedObjects<Channel>, currentTeamChannelSet: Set<string>) => {
const channelMap: Record<string, string> = {}; const channelMap: Record<string, string> = {};
for (const id of currentTeamChannelSet) { for (const id of currentTeamChannelSet) {
const channel = channels[id]; const channel = channels[id];
@ -948,7 +947,7 @@ export const getChannelIdsInCurrentTeam: (state: GlobalState) => string[] = crea
'getChannelIdsInCurrentTeam', 'getChannelIdsInCurrentTeam',
getCurrentTeamId, getCurrentTeamId,
getChannelsInTeam, getChannelsInTeam,
(currentTeamId: string, channelsInTeam: RelationOneToMany<Team, Channel>): string[] => { (currentTeamId: string, channelsInTeam: RelationOneToManyUnique<Team, Channel>): string[] => {
return Array.from(channelsInTeam[currentTeamId] || []); return Array.from(channelsInTeam[currentTeamId] || []);
}, },
); );

View File

@ -65,11 +65,6 @@ describe('Selectors.Roles', () => {
channels[channel11.id] = channel11; channels[channel11.id] = channel11;
channels[channel12.id] = channel12; channels[channel12.id] = channel12;
const channelsInTeam: Record<string, Array<Channel['id']>> = {};
channelsInTeam[team1.id] = [channel1.id, channel2.id, channel5.id, channel6.id, channel8.id, channel10.id, channel11.id];
channelsInTeam[team2.id] = [channel3.id];
channelsInTeam[''] = [channel4.id, channel7.id, channel9.id];
const user = TestHelper.fakeUserWithId(); const user = TestHelper.fakeUserWithId();
const profiles: Record<string, UserProfile> = {}; const profiles: Record<string, UserProfile> = {};
profiles[user.id] = user; profiles[user.id] = user;

View File

@ -49,7 +49,7 @@ describe('Selectors.Threads.getThreadOrderInCurrentTeam', () => {
}, },
channels: { channels: {
channelsInTeam: { channelsInTeam: {
[team1.id]: [post1.channel_id, post2.channel_id], [team1.id]: new Set([post1.channel_id, post2.channel_id]),
}, },
channels: { channels: {
[post1.channel_id]: { [post1.channel_id]: {
@ -116,7 +116,7 @@ describe('Selectors.Threads.getUnreadThreadOrderInCurrentTeam', () => {
}, },
channels: { channels: {
channelsInTeam: { channelsInTeam: {
[team1.id]: [post1.channel_id, post2.channel_id], [team1.id]: new Set([post1.channel_id, post2.channel_id]),
}, },
channels: { channels: {
[post1.channel_id]: { [post1.channel_id]: {

View File

@ -9,7 +9,6 @@ import type {Team, TeamMembership} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users'; import type {UserProfile} from '@mattermost/types/users';
import type { import type {
IDMappedObjects, IDMappedObjects,
RelationOneToMany,
RelationOneToManyUnique, RelationOneToManyUnique,
RelationOneToOne, RelationOneToOne,
} from '@mattermost/types/utilities'; } from '@mattermost/types/utilities';
@ -60,11 +59,11 @@ export function getUserIdsNotInChannels(state: GlobalState): RelationOneToManyUn
return state.entities.users.profilesNotInChannel; return state.entities.users.profilesNotInChannel;
} }
export function getUserIdsInTeams(state: GlobalState): RelationOneToMany<Team, UserProfile> { export function getUserIdsInTeams(state: GlobalState): RelationOneToManyUnique<Team, UserProfile> {
return state.entities.users.profilesInTeam; return state.entities.users.profilesInTeam;
} }
export function getUserIdsNotInTeams(state: GlobalState): RelationOneToMany<Team, UserProfile> { export function getUserIdsNotInTeams(state: GlobalState): RelationOneToManyUnique<Team, UserProfile> {
return state.entities.users.profilesNotInTeam; return state.entities.users.profilesNotInTeam;
} }
@ -72,11 +71,11 @@ export function getUserIdsWithoutTeam(state: GlobalState): Set<UserProfile['id']
return state.entities.users.profilesWithoutTeam; return state.entities.users.profilesWithoutTeam;
} }
export function getUserIdsInGroups(state: GlobalState): RelationOneToMany<Group, UserProfile> { export function getUserIdsInGroups(state: GlobalState): RelationOneToManyUnique<Group, UserProfile> {
return state.entities.users.profilesInGroup; return state.entities.users.profilesInGroup;
} }
export function getUserIdsNotInGroups(state: GlobalState): RelationOneToMany<Group, UserProfile> { export function getUserIdsNotInGroups(state: GlobalState): RelationOneToManyUnique<Group, UserProfile> {
return state.entities.users.profilesNotInGroup; return state.entities.users.profilesNotInGroup;
} }
@ -262,7 +261,7 @@ export const getProfileSetNotInCurrentChannel: (state: GlobalState) => Set<UserP
}, },
); );
export const getProfileSetInCurrentTeam: (state: GlobalState) => Array<UserProfile['id']> = createSelector( export const getProfileSetInCurrentTeam: (state: GlobalState) => Set<UserProfile['id']> = createSelector(
'getProfileSetInCurrentTeam', 'getProfileSetInCurrentTeam',
(state) => state.entities.teams.currentTeamId, (state) => state.entities.teams.currentTeamId,
getUserIdsInTeams, getUserIdsInTeams,
@ -271,7 +270,7 @@ export const getProfileSetInCurrentTeam: (state: GlobalState) => Array<UserProfi
}, },
); );
export const getProfileSetNotInCurrentTeam: (state: GlobalState) => Array<UserProfile['id']> = createSelector( export const getProfileSetNotInCurrentTeam: (state: GlobalState) => Set<UserProfile['id']> = createSelector(
'getProfileSetNotInCurrentTeam', 'getProfileSetNotInCurrentTeam',
(state) => state.entities.teams.currentTeamId, (state) => state.entities.teams.currentTeamId,
getUserIdsNotInTeams, getUserIdsNotInTeams,
@ -281,12 +280,12 @@ export const getProfileSetNotInCurrentTeam: (state: GlobalState) => Array<UserPr
); );
const PROFILE_SET_ALL = 'all'; const PROFILE_SET_ALL = 'all';
function sortAndInjectProfiles(profiles: IDMappedObjects<UserProfile>, profileSet?: 'all' | Array<UserProfile['id']> | Set<UserProfile['id']>): UserProfile[] { function sortAndInjectProfiles(profiles: IDMappedObjects<UserProfile>, profileSet?: 'all' | Set<UserProfile['id']>): UserProfile[] {
const currentProfiles = injectProfiles(profiles, profileSet); const currentProfiles = injectProfiles(profiles, profileSet);
return currentProfiles.sort(sortByUsername); return currentProfiles.sort(sortByUsername);
} }
function injectProfiles(profiles: IDMappedObjects<UserProfile>, profileSet?: 'all' | Array<UserProfile['id']> | Set<UserProfile['id']>): UserProfile[] { function injectProfiles(profiles: IDMappedObjects<UserProfile>, profileSet?: 'all' | Set<UserProfile['id']>): UserProfile[] {
let currentProfiles: UserProfile[] = []; let currentProfiles: UserProfile[] = [];
if (typeof profileSet === 'undefined') { if (typeof profileSet === 'undefined') {
@ -436,11 +435,11 @@ export function getStatusForUserId(state: GlobalState, userId: UserProfile['id']
return getUserStatuses(state)[userId]; return getUserStatuses(state)[userId];
} }
export function getTotalUsersStats(state: GlobalState): any { export function getTotalUsersStats(state: GlobalState) {
return state.entities.users.stats; return state.entities.users.stats;
} }
export function getFilteredUsersStats(state: GlobalState): any { export function getFilteredUsersStats(state: GlobalState) {
return state.entities.users.filteredStats; return state.entities.users.filteredStats;
} }

View File

@ -51,7 +51,7 @@ describe('utils.makeAddLastViewAtToProfiles', () => {
}; };
const channelsInTeam = { const channelsInTeam = {
'': [channel1.id, channel2.id, channel3.id], '': new Set([channel1.id, channel2.id, channel3.id]),
}; };
const testState = deepFreezeAndThrowOnMutation({ const testState = deepFreezeAndThrowOnMutation({

View File

@ -31,6 +31,7 @@ const state: GlobalState = {
profilesNotInGroup: {}, profilesNotInGroup: {},
statuses: {}, statuses: {},
stats: {}, stats: {},
filteredStats: {},
myUserAccessTokens: {}, myUserAccessTokens: {},
lastActivity: {}, lastActivity: {},
}, },
@ -110,6 +111,7 @@ const state: GlobalState = {
userAccessTokens: {}, userAccessTokens: {},
clusterInfo: [], clusterInfo: [],
analytics: {}, analytics: {},
teamAnalytics: {},
dataRetentionCustomPolicies: {}, dataRetentionCustomPolicies: {},
dataRetentionCustomPoliciesCount: 0, dataRetentionCustomPoliciesCount: 0,
prevTrialLicense: {}, prevTrialLicense: {},

View File

@ -29,7 +29,7 @@ const initialState = {
currentChannelId: {id: currentChannelId, team_id: currentTeamId}, currentChannelId: {id: currentChannelId, team_id: currentTeamId},
}, },
channelsInTeam: { channelsInTeam: {
currentTeamId: [currentChannelId], currentTeamId: new Set([currentChannelId]),
}, },
myMembers: { myMembers: {
currentChannelId: { currentChannelId: {

View File

@ -84,7 +84,7 @@ describe('getUnreadChannels', () => {
unreadChannel2, unreadChannel2,
}, },
channelsInTeam: { channelsInTeam: {
team1: ['unreadChannel1', 'unreadChannel2', 'readChannel'], team1: new Set(['unreadChannel1', 'unreadChannel2', 'readChannel']),
}, },
currentChannelId: 'currentChannel', currentChannelId: 'currentChannel',
messageCounts: { messageCounts: {
@ -375,10 +375,10 @@ describe('getUnreadChannels', () => {
}, },
channelsInTeam: { channelsInTeam: {
...baseState.entities.channels.channelsInTeam, ...baseState.entities.channels.channelsInTeam,
team1: [ team1: new Set([
...baseState.entities.channels.channelsInTeam.team1, ...baseState.entities.channels.channelsInTeam.team1,
'archivedChannel', 'archivedChannel',
], ]),
}, },
messageCounts: { messageCounts: {
...baseState.entities.channels.messageCounts, ...baseState.entities.channels.messageCounts,
@ -435,7 +435,7 @@ describe('getDisplayedChannels', () => {
unreadChannel2, unreadChannel2,
}, },
channelsInTeam: { channelsInTeam: {
team1: ['unreadChannel1', 'unreadChannel2', 'readChannel'], team1: new Set(['unreadChannel1', 'unreadChannel2', 'readChannel']),
}, },
currentChannelId: 'currentChannel', currentChannelId: 'currentChannel',
messageCounts: { messageCounts: {
@ -608,7 +608,7 @@ describe('makeGetFilteredChannelIdsForCategory', () => {
unreadChannel2, unreadChannel2,
}, },
channelsInTeam: { channelsInTeam: {
team1: ['unreadChannel1', 'unreadChannel2', 'readChannel'], team1: new Set(['unreadChannel1', 'unreadChannel2', 'readChannel']),
}, },
currentChannelId: 'currentChannel', currentChannelId: 'currentChannel',
messageCounts: { messageCounts: {

View File

@ -20,6 +20,7 @@ const emptyOtherUsersState: Omit<GlobalState['entities']['users'], 'profiles' |
profilesNotInGroup: {}, profilesNotInGroup: {},
statuses: {}, statuses: {},
stats: {}, stats: {},
filteredStats: {},
myUserAccessTokens: {}, myUserAccessTokens: {},
lastActivity: {}, lastActivity: {},
}; };

View File

@ -60,8 +60,8 @@ export type AdminState = {
userAccessTokens: Record<string, UserAccessToken>; userAccessTokens: Record<string, UserAccessToken>;
clusterInfo: ClusterInfo[]; clusterInfo: ClusterInfo[];
samlCertStatus?: SamlCertificateStatus; samlCertStatus?: SamlCertificateStatus;
analytics?: Record<string, number | AnalyticsRow[]>; analytics: AnalyticsState;
teamAnalytics?: RelationOneToOne<Team, Record<string, number | AnalyticsRow[]>>; teamAnalytics: RelationOneToOne<Team, AnalyticsState>;
userAccessTokensByUser?: RelationOneToOne<UserProfile, Record<string, UserAccessToken>>; userAccessTokensByUser?: RelationOneToOne<UserProfile, Record<string, UserAccessToken>>;
plugins?: Record<string, PluginRedux>; plugins?: Record<string, PluginRedux>;
pluginStatuses?: Record<string, PluginStatusRedux>; pluginStatuses?: Record<string, PluginStatusRedux>;
@ -71,6 +71,31 @@ export type AdminState = {
prevTrialLicense: ClientLicense; prevTrialLicense: ClientLicense;
}; };
export type AnalyticsState = {
POST_PER_DAY?: AnalyticsRow[];
BOT_POST_PER_DAY?: AnalyticsRow[];
USERS_WITH_POSTS_PER_DAY?: AnalyticsRow[];
TOTAL_PUBLIC_CHANNELS?: number;
TOTAL_PRIVATE_GROUPS?: number;
TOTAL_POSTS?: number;
TOTAL_USERS?: number;
TOTAL_INACTIVE_USERS?: number;
TOTAL_TEAMS?: number;
TOTAL_WEBSOCKET_CONNECTIONS?: number;
TOTAL_MASTER_DB_CONNECTIONS?: number;
TOTAL_READ_DB_CONNECTIONS?: number;
DAILY_ACTIVE_USERS?: number;
MONTHLY_ACTIVE_USERS?: number;
TOTAL_FILE_POSTS?: number;
TOTAL_HASHTAG_POSTS?: number;
TOTAL_IHOOKS?: number;
TOTAL_OHOOKS?: number;
TOTAL_COMMANDS?: number;
TOTAL_SESSIONS?: number;
REGISTERED_USERS?: number;
}
export type ClusterInfo = { export type ClusterInfo = {
id: string; id: string;
version: string; version: string;

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {IDMappedObjects, RelationOneToMany, RelationOneToOne} from './utilities'; import {IDMappedObjects, RelationOneToManyUnique, RelationOneToOne} from './utilities';
import {Team} from './teams'; import {Team} from './teams';
// e.g. // e.g.
@ -147,7 +147,7 @@ export type ChannelUnread = {
export type ChannelsState = { export type ChannelsState = {
currentChannelId: string; currentChannelId: string;
channels: IDMappedObjects<Channel>; channels: IDMappedObjects<Channel>;
channelsInTeam: RelationOneToMany<Team, Channel>; channelsInTeam: RelationOneToManyUnique<Team, Channel>;
myMembers: RelationOneToOne<Channel, ChannelMembership>; myMembers: RelationOneToOne<Channel, ChannelMembership>;
roles: RelationOneToOne<Channel, Set<string>>; roles: RelationOneToOne<Channel, Set<string>>;
membersInChannel: RelationOneToOne<Channel, Record<string, ChannelMembership>>; membersInChannel: RelationOneToOne<Channel, Record<string, ChannelMembership>>;

View File

@ -70,16 +70,16 @@ export type UsersState = {
mySessions: Session[]; mySessions: Session[];
myAudits: Audit[]; myAudits: Audit[];
profiles: IDMappedObjects<UserProfile>; profiles: IDMappedObjects<UserProfile>;
profilesInTeam: RelationOneToMany<Team, UserProfile>; profilesInTeam: RelationOneToManyUnique<Team, UserProfile>;
profilesNotInTeam: RelationOneToMany<Team, UserProfile>; profilesNotInTeam: RelationOneToManyUnique<Team, UserProfile>;
profilesWithoutTeam: Set<string>; profilesWithoutTeam: Set<string>;
profilesInChannel: RelationOneToManyUnique<Channel, UserProfile>; profilesInChannel: RelationOneToManyUnique<Channel, UserProfile>;
profilesNotInChannel: RelationOneToManyUnique<Channel, UserProfile>; profilesNotInChannel: RelationOneToManyUnique<Channel, UserProfile>;
profilesInGroup: RelationOneToMany<Group, UserProfile>; profilesInGroup: RelationOneToManyUnique<Group, UserProfile>;
profilesNotInGroup: RelationOneToMany<Group, UserProfile>; profilesNotInGroup: RelationOneToManyUnique<Group, UserProfile>;
statuses: RelationOneToOne<UserProfile, string>; statuses: RelationOneToOne<UserProfile, string>;
stats: RelationOneToOne<UserProfile, UsersStats>; stats: Partial<UsersStats>;
filteredStats?: UsersStats; filteredStats: Partial<UsersStats>;
myUserAccessTokens: Record<string, UserAccessToken>; myUserAccessTokens: Record<string, UserAccessToken>;
lastActivity: RelationOneToOne<UserProfile, number>; lastActivity: RelationOneToOne<UserProfile, number>;
}; };