Migrate most remaining files to TypeScript (#36954)

* Migrate actions/emoji_actions to TS

* Migrate i18n/i18n to TS

* Migrate selectors/admin_console and selectors/admin_console.test to TS

* Migrate actions/views/cookie to TS

* Migrate actions/views/group to TS

* Migrate actions/admin_actions to TS

* Migrate actions/telemetry_actions, actions/announcement_bar, actions/notice, actions/search, and actions/system to TS

* Migrate actions/views/mfa to TS

* Migrate actions/views/posts and actions/views/textbox to TS

* Migrate suggestion_box/index to TS

* Rename create_selector files to TS files

* Migrate create_selector files to TS

* Move langmap out of channels and change it to an ES Module

* Revert accidentally committed changes to authorize.test.tsx

* Remove unused recomputations and resetRecomputations from createSelector

* Fix lint

* Merge ESLint changes
This commit is contained in:
Harrison Healey
2026-06-22 15:55:03 -04:00
committed by GitHub
parent 9a2ea89575
commit 08eef111b8
75 changed files with 1728 additions and 1544 deletions
+1 -1
View File
@@ -69,7 +69,7 @@ var TDefault TranslateFunc = func(translationID string, args ...any) string {
var locales = make(map[string]string)
// supportedLocales is a hard-coded list of locales considered ready for production use. It must
// be kept in sync with ../../../../webapp/channels/src/i18n/i18n.jsx.
// be kept in sync with ../../../../webapp/channels/src/i18n/i18n.ts.
var supportedLocales = []string{
"de",
"en",
@@ -3,9 +3,10 @@
import React from 'react';
import * as Actions from 'actions/admin_actions.jsx';
import configureStore from 'store';
import * as Actions from './admin_actions';
describe('Actions.Admin', () => {
let store;
beforeEach(async () => {
@@ -1,6 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {ClusterInfo} from '@mattermost/types/admin';
import type {StatusOK} from '@mattermost/types/client4';
import type {AdminConfig, AllowedIPRange, FetchIPResponse, RequestLicenseBody} from '@mattermost/types/config';
import type {Job, JobTypeBase} from '@mattermost/types/jobs';
import type {SamlCertificateStatus, SamlMetadataResponse} from '@mattermost/types/saml';
import type {AuthChangeResponse, UserProfile} from '@mattermost/types/users';
import * as AdminActions from 'mattermost-redux/actions/admin';
import {bindClientFunc} from 'mattermost-redux/actions/helpers';
import {createJob} from 'mattermost-redux/actions/jobs';
@@ -15,9 +22,15 @@ import store from 'stores/redux_store';
import {ActionTypes, JobTypes} from 'utils/constants';
import type {ThunkActionFunc} from 'types/store';
import type {AdminConsolePluginComponent} from 'types/store/plugins';
const dispatch = store.dispatch;
export async function reloadConfig(success, error) {
type SuccessCallback<T = StatusOK> = ((data: T) => void) | null | undefined;
type ErrorCallback = ((error: Error & {id: string; server_error_id: string}) => void) | null | undefined;
export async function reloadConfig(success: SuccessCallback, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.reloadConfig());
if (data && success) {
dispatch(AdminActions.getConfig());
@@ -28,7 +41,7 @@ export async function reloadConfig(success, error) {
}
}
export async function adminResetMfa(userId, success, error) {
export async function adminResetMfa(userId: string, success: SuccessCallback<boolean>, error?: ErrorCallback) {
const {data, error: err} = await dispatch(UserActions.updateUserMfa(userId, false));
if (data && success) {
success(data);
@@ -37,7 +50,7 @@ export async function adminResetMfa(userId, success, error) {
}
}
export async function getClusterStatus(success, error) {
export async function getClusterStatus(success: SuccessCallback<ClusterInfo[]>, error?: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.getClusterStatus());
if (data && success) {
success(data);
@@ -46,7 +59,7 @@ export async function getClusterStatus(success, error) {
}
}
export async function ldapTest(success, error) {
export async function ldapTest(success: SuccessCallback, error?: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.testLdap());
if (data && success) {
success(data);
@@ -55,7 +68,7 @@ export async function ldapTest(success, error) {
}
}
export async function ldapTestConnection(success, error, settings) {
export async function ldapTestConnection(success: SuccessCallback, error: ErrorCallback, settings: any) {
const {data, error: err} = await dispatch(AdminActions.testLdapConnection(settings));
if (data && success) {
success(data);
@@ -64,7 +77,7 @@ export async function ldapTestConnection(success, error, settings) {
}
}
export async function ldapTestFilters(success, error, settings) {
export async function ldapTestFilters(success: SuccessCallback, error: ErrorCallback, settings: any) {
const {data, error: err} = await dispatch(AdminActions.testLdapFilters(settings));
if (data && success) {
success(data);
@@ -73,7 +86,7 @@ export async function ldapTestFilters(success, error, settings) {
}
}
export async function ldapTestAttributes(success, error, settings) {
export async function ldapTestAttributes(success: SuccessCallback, error: ErrorCallback, settings: any) {
const {data, error: err} = await dispatch(AdminActions.testLdapAttributes(settings));
if (data && success) {
success(data);
@@ -82,7 +95,7 @@ export async function ldapTestAttributes(success, error, settings) {
}
}
export async function ldapTestGroupAttributes(success, error, settings) {
export async function ldapTestGroupAttributes(success: SuccessCallback, error: ErrorCallback, settings: any) {
const {data, error: err} = await dispatch(AdminActions.testLdapGroupAttributes(settings));
if (data && success) {
success(data);
@@ -91,7 +104,7 @@ export async function ldapTestGroupAttributes(success, error, settings) {
}
}
export async function invalidateAllCaches(success, error) {
export async function invalidateAllCaches(success: SuccessCallback, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.invalidateCaches());
if (data && success) {
success(data);
@@ -100,7 +113,7 @@ export async function invalidateAllCaches(success, error) {
}
}
export async function recycleDatabaseConnection(success, error) {
export async function recycleDatabaseConnection(success: SuccessCallback, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.recycleDatabase());
if (data && success) {
success(data);
@@ -109,7 +122,7 @@ export async function recycleDatabaseConnection(success, error) {
}
}
export async function adminResetEmail(user, success, error) {
export async function adminResetEmail(user: UserProfile, success: SuccessCallback<UserProfile>, error: ErrorCallback) {
const {data, error: err} = await dispatch(UserActions.patchUser(user));
if (data && success) {
success(data);
@@ -118,7 +131,7 @@ export async function adminResetEmail(user, success, error) {
}
}
export async function samlCertificateStatus(success, error) {
export async function samlCertificateStatus(success: SuccessCallback<SamlCertificateStatus>, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.getSamlCertificateStatus());
if (data && success) {
success(data);
@@ -127,7 +140,7 @@ export async function samlCertificateStatus(success, error) {
}
}
export async function getIPFilters(success, error) {
export async function getIPFilters(success: SuccessCallback<AllowedIPRange[]>, error?: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.getIPFilters());
if (data && success) {
success(data);
@@ -136,7 +149,7 @@ export async function getIPFilters(success, error) {
}
}
export async function getCurrentIP(success, error) {
export async function getCurrentIP(success: SuccessCallback<FetchIPResponse>, error?: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.getCurrentIP());
if (data && success) {
success(data);
@@ -145,7 +158,7 @@ export async function getCurrentIP(success, error) {
}
}
export async function applyIPFilters(ipList, success, error) {
export async function applyIPFilters(ipList: AllowedIPRange[], success: SuccessCallback<AllowedIPRange[]>, error?: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.applyIPFilters(ipList));
if (data && success) {
success(data);
@@ -154,29 +167,47 @@ export async function applyIPFilters(ipList, success, error) {
}
}
/**
* @param {string | null} clientId
* @returns {ActionResult<OAuthApp>}
*/
export function getOAuthAppInfo(clientId) {
export function getOAuthAppInfo(clientId: string) {
return bindClientFunc({
clientFunc: Client4.getOAuthAppInfo,
params: [clientId],
});
}
/**
* @param {*}
* @returns {ActionResult<{redirect: string}>}
*/
export function allowOAuth2({responseType, clientId, redirectUri, state, scope, resource, codeChallenge, codeChallengeMethod}) {
export function allowOAuth2({
responseType,
clientId,
redirectUri,
state,
scope,
resource,
codeChallenge,
codeChallengeMethod,
}: {
responseType: string | null;
clientId: string | null;
redirectUri: string | null;
state: string | null;
scope: string | null;
resource?: string | null;
codeChallenge?: string | null;
codeChallengeMethod?: string | null;
}) {
return bindClientFunc({
clientFunc: Client4.authorizeOAuthApp,
params: [responseType, clientId, redirectUri, state, scope, resource, codeChallenge, codeChallengeMethod],
});
}
export async function emailToLdap(loginId, password, token, ldapId, ldapPassword, success, error) {
export async function emailToLdap(
loginId: string,
password: string,
token: string | undefined,
ldapId: string,
ldapPassword: string,
success: SuccessCallback<AuthChangeResponse>,
error: ErrorCallback,
) {
const {data, error: err} = await dispatch(UserActions.switchEmailToLdap(loginId, password, ldapId, ldapPassword, token));
if (data && success) {
success(data);
@@ -185,7 +216,14 @@ export async function emailToLdap(loginId, password, token, ldapId, ldapPassword
}
}
export async function emailToOAuth(loginId, password, token, newType, success, error) {
export async function emailToOAuth(
loginId: string,
password: string,
token: string | undefined,
newType: string,
success: SuccessCallback<AuthChangeResponse>,
error: ErrorCallback,
) {
const {data, error: err} = await dispatch(UserActions.switchEmailToOAuth(newType, loginId, password, token));
if (data && success) {
success(data);
@@ -194,7 +232,13 @@ export async function emailToOAuth(loginId, password, token, newType, success, e
}
}
export async function oauthToEmail(currentService, email, password, success, error) {
export async function oauthToEmail(
currentService: string,
email: string,
password: string,
success: SuccessCallback<AuthChangeResponse>,
error: ErrorCallback,
) {
const {data, error: err} = await dispatch(UserActions.switchOAuthToEmail(currentService, email, password));
if (data) {
if (data.follow_link) {
@@ -208,7 +252,7 @@ export async function oauthToEmail(currentService, email, password, success, err
}
}
export async function uploadBrandImage(brandImage, success, error) {
export async function uploadBrandImage(brandImage: File, success: SuccessCallback<StatusOK>, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.uploadBrandImage(brandImage));
if (data && success) {
success(data);
@@ -217,7 +261,7 @@ export async function uploadBrandImage(brandImage, success, error) {
}
}
export async function deleteBrandImage(success, error) {
export async function deleteBrandImage(success: SuccessCallback, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.deleteBrandImage());
if (data && success) {
success(data);
@@ -226,7 +270,7 @@ export async function deleteBrandImage(success, error) {
}
}
export async function uploadPublicSamlCertificate(file, success, error) {
export async function uploadPublicSamlCertificate(file: File, success: SuccessCallback<string>, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.uploadPublicSamlCertificate(file));
if (data && success) {
success('saml-public.crt');
@@ -235,7 +279,7 @@ export async function uploadPublicSamlCertificate(file, success, error) {
}
}
export async function uploadPrivateSamlCertificate(file, success, error) {
export async function uploadPrivateSamlCertificate(file: File, success: SuccessCallback<string>, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.uploadPrivateSamlCertificate(file));
if (data && success) {
success('saml-private.key');
@@ -244,7 +288,7 @@ export async function uploadPrivateSamlCertificate(file, success, error) {
}
}
export async function uploadPublicLdapCertificate(file, success, error) {
export async function uploadPublicLdapCertificate(file: File, success: SuccessCallback<string>, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.uploadPublicLdapCertificate(file));
if (data && success) {
success('ldap-public.crt');
@@ -252,7 +296,7 @@ export async function uploadPublicLdapCertificate(file, success, error) {
error({id: err.server_error_id, ...err});
}
}
export async function uploadPrivateLdapCertificate(file, success, error) {
export async function uploadPrivateLdapCertificate(file: File, success: SuccessCallback<string>, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.uploadPrivateLdapCertificate(file));
if (data && success) {
success('ldap-private.key');
@@ -261,7 +305,7 @@ export async function uploadPrivateLdapCertificate(file, success, error) {
}
}
export async function uploadIdpSamlCertificate(file, success, error) {
export async function uploadIdpSamlCertificate(file: File, success: SuccessCallback<string>, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.uploadIdpSamlCertificate(file));
if (data && success) {
success('saml-idp.crt');
@@ -270,7 +314,7 @@ export async function uploadIdpSamlCertificate(file, success, error) {
}
}
export async function uploadAuditCertificate(fileData, success, error) {
export async function uploadAuditCertificate(fileData: File, success: SuccessCallback<string>, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.uploadAuditCertificate(fileData));
if (data && success) {
success('audit.crt');
@@ -279,7 +323,7 @@ export async function uploadAuditCertificate(fileData, success, error) {
}
}
export async function removeAuditCertificate(success, error) {
export async function removeAuditCertificate(success: SuccessCallback, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.removeAuditCertificate());
if (data && success) {
success(data);
@@ -288,7 +332,7 @@ export async function removeAuditCertificate(success, error) {
}
}
export async function removePublicSamlCertificate(success, error) {
export async function removePublicSamlCertificate(success: SuccessCallback, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.removePublicSamlCertificate());
if (data && success) {
success(data);
@@ -297,7 +341,7 @@ export async function removePublicSamlCertificate(success, error) {
}
}
export async function removePrivateSamlCertificate(success, error) {
export async function removePrivateSamlCertificate(success: SuccessCallback, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.removePrivateSamlCertificate());
if (data && success) {
success(data);
@@ -306,7 +350,7 @@ export async function removePrivateSamlCertificate(success, error) {
}
}
export async function removePublicLdapCertificate(success, error) {
export async function removePublicLdapCertificate(success: SuccessCallback, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.removePublicLdapCertificate());
if (data && success) {
success(data);
@@ -315,7 +359,7 @@ export async function removePublicLdapCertificate(success, error) {
}
}
export async function removePrivateLdapCertificate(success, error) {
export async function removePrivateLdapCertificate(success: SuccessCallback, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.removePrivateLdapCertificate());
if (data && success) {
success(data);
@@ -324,7 +368,7 @@ export async function removePrivateLdapCertificate(success, error) {
}
}
export async function removeIdpSamlCertificate(success, error) {
export async function removeIdpSamlCertificate(success: SuccessCallback, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.removeIdpSamlCertificate());
if (data && success) {
success(data);
@@ -333,7 +377,7 @@ export async function removeIdpSamlCertificate(success, error) {
}
}
export async function getStandardAnalytics(teamId) {
export async function getStandardAnalytics(teamId?: string) {
await dispatch(AdminActions.getStandardAnalytics(teamId));
}
@@ -341,23 +385,23 @@ export async function refreshServerLimits() {
await dispatch(getServerLimitsAction());
}
export async function getAdvancedAnalytics(teamId) {
export async function getAdvancedAnalytics(teamId?: string) {
await dispatch(AdminActions.getAdvancedAnalytics(teamId));
}
export async function getBotPostsPerDayAnalytics(teamId) {
export async function getBotPostsPerDayAnalytics(teamId?: string) {
await dispatch(AdminActions.getBotPostsPerDayAnalytics(teamId));
}
export async function getPostsPerDayAnalytics(teamId) {
export async function getPostsPerDayAnalytics(teamId?: string) {
await dispatch(AdminActions.getPostsPerDayAnalytics(teamId));
}
export async function getUsersPerDayAnalytics(teamId) {
export async function getUsersPerDayAnalytics(teamId?: string) {
await dispatch(AdminActions.getUsersPerDayAnalytics(teamId));
}
export async function elasticsearchTest(config, success, error) {
export async function elasticsearchTest(config: AdminConfig, success: SuccessCallback, error?: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.testElasticsearch(config));
if (data && success) {
success(data);
@@ -366,7 +410,7 @@ export async function elasticsearchTest(config, success, error) {
}
}
export async function testFileStoreConnection(success, error) {
export async function testFileStoreConnection(success: SuccessCallback, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.testFileStoreConnection());
if (data && success) {
success(data);
@@ -375,7 +419,7 @@ export async function testFileStoreConnection(success, error) {
}
}
export async function elasticsearchPurgeIndexes(success, error, indexes) {
export async function elasticsearchPurgeIndexes(success: SuccessCallback, error: ErrorCallback, indexes?: string[]) {
const {data, error: err} = await dispatch(AdminActions.purgeElasticsearchIndexes(indexes));
if (data && success) {
success(data);
@@ -384,7 +428,7 @@ export async function elasticsearchPurgeIndexes(success, error, indexes) {
}
}
export async function jobCreate(success, error, job) {
export async function jobCreate(success: SuccessCallback<Job>, error: ErrorCallback, job: JobTypeBase & {data?: any}) {
const {data, error: err} = await dispatch(createJob(job));
if (data && success) {
success(data);
@@ -393,7 +437,7 @@ export async function jobCreate(success, error, job) {
}
}
export async function rebuildChannelsIndex(success, error) {
export async function rebuildChannelsIndex(success: SuccessCallback<void>, error: ErrorCallback) {
await elasticsearchPurgeIndexes(undefined, error, ['channels']);
const job = {
type: JobTypes.ELASTICSEARCH_POST_INDEXING,
@@ -406,17 +450,17 @@ export async function rebuildChannelsIndex(success, error) {
},
};
await jobCreate(undefined, error, job);
success();
success?.();
}
export function setNavigationBlocked(blocked) {
export function setNavigationBlocked(blocked: boolean) {
return {
type: ActionTypes.SET_NAVIGATION_BLOCKED,
blocked,
};
}
export function deferNavigation(onNavigationConfirmed) {
export function deferNavigation(onNavigationConfirmed: () => void) {
return {
type: ActionTypes.DEFER_NAVIGATION,
onNavigationConfirmed,
@@ -429,7 +473,7 @@ export function cancelNavigation() {
};
}
export function confirmNavigation() {
export function confirmNavigation(): ThunkActionFunc<void> {
// have to rename these because of lint no-shadow
return (thunkDispatch, thunkGetState) => {
const callback = getOnNavigationConfirmed(thunkGetState());
@@ -444,7 +488,7 @@ export function confirmNavigation() {
};
}
export async function invalidateAllEmailInvites(success, error) {
export async function invalidateAllEmailInvites(success: SuccessCallback, error: ErrorCallback) {
const {data, error: err} = await dispatch(TeamActions.invalidateAllEmailInvites());
if (data && success) {
success(data);
@@ -453,7 +497,7 @@ export async function invalidateAllEmailInvites(success, error) {
}
}
export async function testSmtp(success, error) {
export async function testSmtp(success: SuccessCallback, error: ErrorCallback) {
const {data, error: err} = await dispatch(AdminActions.testEmail());
if (data && success) {
success(data);
@@ -462,7 +506,7 @@ export async function testSmtp(success, error) {
}
}
export function registerAdminConsolePlugin(pluginId, reducer) {
export function registerAdminConsolePlugin(pluginId: string, reducer: unknown): ThunkActionFunc<void> {
return (storeDispatch) => {
storeDispatch({
type: ActionTypes.RECEIVED_ADMIN_CONSOLE_REDUCER,
@@ -474,7 +518,7 @@ export function registerAdminConsolePlugin(pluginId, reducer) {
};
}
export function unregisterAdminConsolePlugin(pluginId) {
export function unregisterAdminConsolePlugin(pluginId: string): ThunkActionFunc<void> {
return (storeDispatch) => {
storeDispatch({
type: ActionTypes.REMOVED_ADMIN_CONSOLE_REDUCER,
@@ -485,7 +529,7 @@ export function unregisterAdminConsolePlugin(pluginId) {
};
}
export async function testSiteURL(success, error, siteURL) {
export async function testSiteURL(success: SuccessCallback, error: ErrorCallback, siteURL: string) {
const {data, error: err} = await dispatch(AdminActions.testSiteURL(siteURL));
if (data && success) {
success(data);
@@ -494,7 +538,12 @@ export async function testSiteURL(success, error, siteURL) {
}
}
export function registerAdminConsoleCustomSetting(pluginId, key, component, {showTitle}) {
export function registerAdminConsoleCustomSetting(
pluginId: string,
key: string,
component: React.Component,
{showTitle}: AdminConsolePluginComponent['options'],
): ThunkActionFunc<void> {
return (storeDispatch) => {
storeDispatch({
type: ActionTypes.RECEIVED_ADMIN_CONSOLE_CUSTOM_COMPONENT,
@@ -508,7 +557,11 @@ export function registerAdminConsoleCustomSetting(pluginId, key, component, {sho
};
}
export function registerAdminConsoleCustomSection(pluginId, key, component) {
export function registerAdminConsoleCustomSection(
pluginId: string,
key: string,
component: React.Component,
): ThunkActionFunc<void> {
return (storeDispatch) => {
storeDispatch({
type: ActionTypes.RECEIVED_ADMIN_CONSOLE_CUSTOM_SECTION,
@@ -521,7 +574,11 @@ export function registerAdminConsoleCustomSection(pluginId, key, component) {
};
}
export async function getSamlMetadataFromIdp(success, error, samlMetadataURL) {
export async function getSamlMetadataFromIdp(
success: SuccessCallback<SamlMetadataResponse>,
error: ErrorCallback,
samlMetadataURL: string,
) {
const {data, error: err} = await dispatch(AdminActions.getSamlMetadataFromIdp(samlMetadataURL));
if (data && success) {
success(data);
@@ -530,7 +587,7 @@ export async function getSamlMetadataFromIdp(success, error, samlMetadataURL) {
}
}
export async function setSamlIdpCertificateFromMetadata(success, error, certData) {
export async function setSamlIdpCertificateFromMetadata(success: SuccessCallback<string>, error: ErrorCallback, certData: string) {
const {data, error: err} = await dispatch(AdminActions.setSamlIdpCertificateFromMetadata(certData));
if (data && success) {
success('saml-idp.crt');
@@ -571,14 +628,14 @@ export function restartServer() {
};
}
export function ping(getServerStatus, deviceId) {
export function ping(getServerStatus?: boolean, deviceId?: string) {
return async () => {
const data = await Client4.ping(getServerStatus, deviceId);
return data;
};
}
export function requestTrialLicense(requestLicenseBody) {
export function requestTrialLicense(requestLicenseBody: RequestLicenseBody) {
return async () => {
try {
const response = await Client4.requestTrialLicense(requestLicenseBody);
@@ -1,6 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {Post} from '@mattermost/types/posts';
import type {IDMappedObjects} from '@mattermost/types/utilities';
import * as EmojiActions from 'mattermost-redux/actions/emojis';
import {savePreferences} from 'mattermost-redux/actions/preferences';
import {Preferences as ReduxPreferences} from 'mattermost-redux/constants';
@@ -16,8 +19,10 @@ import LocalStorageStore from 'stores/local_storage_store';
import Constants, {ActionTypes, Preferences} from 'utils/constants';
import {EmojiIndicesByAlias} from 'utils/emoji';
export function loadRecentlyUsedCustomEmojis() {
return (dispatch, getState) => {
import type {ActionFunc, ActionFuncAsync} from 'types/store';
export function loadRecentlyUsedCustomEmojis(): ActionFuncAsync {
return async (dispatch, getState) => {
const state = getState();
if (!getCustomEmojisEnabled(state)) {
@@ -31,17 +36,13 @@ export function loadRecentlyUsedCustomEmojis() {
}
export function incrementEmojiPickerPage() {
return async (dispatch) => {
dispatch({
type: ActionTypes.INCREMENT_EMOJI_PICKER_PAGE,
});
return {data: true};
return {
type: ActionTypes.INCREMENT_EMOJI_PICKER_PAGE,
};
}
export function setUserSkinTone(skin) {
return async (dispatch, getState) => {
export function setUserSkinTone(skin: string): ActionFuncAsync {
return (dispatch, getState) => {
const state = getState();
const currentUserId = getCurrentUserId(state);
const skinTonePreference = [{
@@ -50,17 +51,17 @@ export function setUserSkinTone(skin) {
category: Preferences.CATEGORY_EMOJI,
value: skin,
}];
dispatch(savePreferences(currentUserId, skinTonePreference));
return dispatch(savePreferences(currentUserId, skinTonePreference));
};
}
export function addRecentEmoji(alias) {
export function addRecentEmoji(alias: string) {
return addRecentEmojis([alias]);
}
export const MAXIMUM_RECENT_EMOJI = 27;
export function addRecentEmojis(aliases) {
export function addRecentEmojis(aliases: string[]): ActionFunc {
return (dispatch, getState) => {
const state = getState();
const currentUserId = getCurrentUserId(state);
@@ -107,9 +108,9 @@ export function addRecentEmojis(aliases) {
};
}
export function loadCustomEmojisForCustomStatusesByUserIds(userIds) {
export function loadCustomEmojisForCustomStatusesByUserIds(userIds: Set<string> | string[]): ActionFuncAsync {
const getCustomStatus = makeGetCustomStatus();
return (dispatch, getState) => {
return async (dispatch, getState) => {
const state = getState();
const customEmojiEnabled = isCustomEmojiEnabled(state);
const customStatusEnabled = isCustomStatusEnabled(state);
@@ -117,7 +118,7 @@ export function loadCustomEmojisForCustomStatusesByUserIds(userIds) {
return {data: false};
}
const emojisToLoad = new Set();
const emojisToLoad = new Set<string>();
userIds.forEach((userId) => {
const customStatus = getCustomStatus(state, userId);
@@ -132,8 +133,8 @@ export function loadCustomEmojisForCustomStatusesByUserIds(userIds) {
};
}
export function loadCustomEmojisForRecentCustomStatuses() {
return (dispatch, getState) => {
export function loadCustomEmojisForRecentCustomStatuses(): ActionFuncAsync {
return async (dispatch, getState) => {
const state = getState();
const customEmojiEnabled = isCustomEmojiEnabled(state);
const customStatusEnabled = isCustomStatusEnabled(state);
@@ -147,7 +148,7 @@ export function loadCustomEmojisForRecentCustomStatuses() {
}
const recentCustomStatuses = JSON.parse(recentCustomStatusesValue);
const emojisToLoad = new Set();
const emojisToLoad = new Set<string>();
for (const customStatus of recentCustomStatuses) {
if (!customStatus || !customStatus.emoji) {
@@ -161,8 +162,8 @@ export function loadCustomEmojisForRecentCustomStatuses() {
};
}
export function loadCustomEmojisIfNeeded(emojis) {
return (dispatch, getState) => {
export function loadCustomEmojisIfNeeded(emojis: string[]): ActionFuncAsync {
return async (dispatch, getState) => {
if (!emojis || emojis.length === 0) {
return {data: false};
}
@@ -176,7 +177,7 @@ export function loadCustomEmojisIfNeeded(emojis) {
const systemEmojis = EmojiIndicesByAlias;
const customEmojisByName = selectCustomEmojisByName(state);
const nonExistentCustomEmoji = state.entities.emojis.nonExistentEmoji;
const emojisToLoad = [];
const emojisToLoad: string[] = [];
emojis.forEach((emoji) => {
if (!emoji) {
@@ -205,13 +206,13 @@ export function loadCustomEmojisIfNeeded(emojis) {
};
}
export function loadCustomStatusEmojisForPostList(posts) {
return (dispatch) => {
if (!posts || posts.length === 0) {
export function loadCustomStatusEmojisForPostList(posts: IDMappedObjects<Post>): ActionFuncAsync {
return async (dispatch) => {
if (!posts || Object.keys(posts).length === 0) {
return {data: false};
}
const userIds = new Set();
const userIds = new Set<string>();
Object.keys(posts).forEach((postId) => {
const post = posts[postId];
if (post.user_id) {
@@ -222,7 +223,7 @@ export function loadCustomStatusEmojisForPostList(posts) {
};
}
export function migrateRecentEmojis() {
export function migrateRecentEmojis(): ActionFuncAsync {
return async (dispatch, getState) => {
const state = getState();
const currentUserId = getCurrentUserId(state);
@@ -230,7 +231,7 @@ export function migrateRecentEmojis() {
if (recentEmojisFromPreference.length === 0) {
const recentEmojisFromLocalStorage = LocalStorageStore.getRecentEmojis(currentUserId);
if (recentEmojisFromLocalStorage) {
const parsedRecentEmojisFromLocalStorage = JSON.parse(recentEmojisFromLocalStorage);
const parsedRecentEmojisFromLocalStorage = JSON.parse(recentEmojisFromLocalStorage) as string[];
const toSetRecentEmojiData = parsedRecentEmojisFromLocalStorage.map((emojiName) => ({name: emojiName, usageCount: 1}));
if (toSetRecentEmojiData.length > 0) {
dispatch(savePreferences(currentUserId, [{category: Constants.Preferences.RECENT_EMOJIS, name: currentUserId, user_id: currentUserId, value: JSON.stringify(toSetRecentEmojiData)}]));
@@ -7,15 +7,13 @@ const HEADER_X_PAGE_LOAD_CONTEXT = 'X-Page-Load-Context';
/**
* Takes an array of string names of performance markers and invokes
* performance.clearMarkers on each.
* @param {array} names of markers to clear
*
* performance.clearMarkers on each. *
*/
export function clearMarks(names) {
export function clearMarks(names: string[]) {
names.forEach((name) => performance.clearMarks(name));
}
export function mark(name) {
export function mark(name: string) {
performance.mark(name);
}
@@ -25,7 +23,7 @@ export function mark(name) {
* The setTimeout approach is a "best effort" approach that will produce false positives.
* A more accurate approach will result in more obtrusive code, which would add risk and maintenance cost.
*/
export const temporarilySetPageLoadContext = (pageLoadContext) => {
export const temporarilySetPageLoadContext = (pageLoadContext: string) => {
Client4.setHeader(HEADER_X_PAGE_LOAD_CONTEXT, pageLoadContext);
setTimeout(() => {
Client4.removeHeader(HEADER_X_PAGE_LOAD_CONTEXT);
+1 -1
View File
@@ -345,7 +345,7 @@ export async function loadProfilesForGM() {
const currentUserId = Selectors.getCurrentUserId(state);
const collapsedThreads = isCollapsedThreadsEnabled(state);
const userIdsForLoadingCustomEmojis = new Set();
const userIdsForLoadingCustomEmojis = new Set<string>();
const channelUsersToLoad: string[] = [];
for (const channel of getGMsForLoading(state)) {
const userIds = userIdsInChannels[channel.id] || new Set();
@@ -1,13 +1,22 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {GroupSearchParams} from '@mattermost/types/groups';
import {searchGroups} from 'mattermost-redux/actions/groups';
import Permissions from 'mattermost-redux/constants/permissions';
import {searchAssociatedGroupsForReferenceLocal} from 'mattermost-redux/selectors/entities/groups';
import {isCustomGroupsEnabled} from 'mattermost-redux/selectors/entities/preferences';
import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles';
export function searchAssociatedGroupsForReference(prefix, teamId, channelId, opts = {}) {
import type {ActionFuncAsync} from 'types/store';
export function searchAssociatedGroupsForReference(
prefix: string,
teamId: string,
channelId: string,
opts: Omit<GroupSearchParams, 'q'> = {},
): ActionFuncAsync {
return async (dispatch, getState) => {
const state = getState();
if (!haveIChannelPermission(state,
@@ -5,13 +5,13 @@ jest.mock('mattermost-redux/actions/users');
import * as UserActions from 'mattermost-redux/actions/users';
import configureStore from 'tests/test_store';
import {
activateMfa,
deactivateMfa,
generateMfaSecret,
} from 'actions/views/mfa';
import configureStore from 'tests/test_store';
} from './mfa';
describe('actions/views/mfa', () => {
describe('activateMfa', () => {
@@ -1,10 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {MfaSecret} from '@mattermost/types/mfa';
import * as UserActions from 'mattermost-redux/actions/users';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
export function activateMfa(code) {
import type {ActionFuncAsync} from 'types/store';
export function activateMfa(code: string): ActionFuncAsync {
return (dispatch, getState) => {
const currentUserId = getCurrentUserId(getState());
@@ -12,7 +16,7 @@ export function activateMfa(code) {
};
}
export function deactivateMfa() {
export function deactivateMfa(): ActionFuncAsync {
return (dispatch, getState) => {
const currentUserId = getCurrentUserId(getState());
@@ -20,7 +24,7 @@ export function deactivateMfa() {
};
}
export function generateMfaSecret() {
export function generateMfaSecret(): ActionFuncAsync<MfaSecret> {
return (dispatch, getState) => {
const currentUserId = getCurrentUserId(getState());
@@ -1,15 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ActionTypes} from 'utils/constants';
export function dismissNotice(type) {
return (dispatch) => {
dispatch({
type: ActionTypes.DISMISS_NOTICE,
data: type,
});
return {data: true};
};
}
@@ -0,0 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ActionTypes} from 'utils/constants';
export function dismissNotice(type: string) {
return {
type: ActionTypes.DISMISS_NOTICE,
data: type,
};
}
@@ -1,6 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {Channel} from '@mattermost/types/channels';
import type {Post, PostMetadata} from '@mattermost/types/posts';
import {logError} from 'mattermost-redux/actions/errors';
import * as PostActions from 'mattermost-redux/actions/posts';
import {Permissions} from 'mattermost-redux/constants';
@@ -18,22 +21,24 @@ import {containsAtChannel, groupsMentionedInText} from 'utils/post_utils';
import {getSiteURL} from 'utils/url';
import {getTimestamp} from 'utils/utils';
import type {ActionFuncAsync} from 'types/store';
import {runMessageWillBePostedHooks} from '../hooks';
export function editPost(post) {
export function editPost(post: Post): ActionFuncAsync<Post> {
return async (dispatch) => {
const result = await dispatch(PostActions.editPost(post));
// Send to error bar if it's an edit post error about time limit.
if (result.error && result.error.server_error_id === 'api.post.update_post.permissions_time_limit.app_error') {
dispatch(logError({type: AnnouncementBarTypes.ANNOUNCEMENT, message: result.error.message}, true));
dispatch(logError({type: AnnouncementBarTypes.ANNOUNCEMENT, message: result.error.message}));
}
return result;
};
}
export function forwardPost(post, channel, message = '') {
export function forwardPost(post: Post, channel: Channel, message = ''): ActionFuncAsync<PostActions.CreatePostReturnType> {
return async (dispatch, getState) => {
const state = getState();
const channelId = channel.id;
@@ -41,6 +46,10 @@ export function forwardPost(post, channel, message = '') {
const currentUserId = getCurrentUserId(state);
const currentTeam = getCurrentTeam(state);
if (!currentTeam) {
return {};
}
const relativePermaLink = getPermalinkURL(state, currentTeam.id, post.id);
const permaLink = `${getSiteURL()}${relativePermaLink}`;
@@ -51,7 +60,7 @@ export function forwardPost(post, channel, message = '') {
const useCustomGroupMentions = isCustomGroupsEnabled(state) && haveICurrentChannelPermission(state, Permissions.USE_GROUP_MENTIONS);
const groupsWithAllowReference = useLDAPGroupMentions || useCustomGroupMentions ? getAssociatedGroupsForReferenceByMention(state, currentTeam.id, channelId) : null;
let newPost = {};
let newPost = {} as Post;
newPost.channel_id = channelId;
@@ -62,7 +71,7 @@ export function forwardPost(post, channel, message = '') {
newPost.pending_post_id = `${userId}:${time}`;
newPost.user_id = userId;
newPost.create_at = time;
newPost.metadata = {};
newPost.metadata = {} as PostMetadata;
newPost.props = {};
if (!useChannelMentions && containsAtChannel(newPost.message, {checkAllMentions: true})) {
@@ -76,16 +85,23 @@ export function forwardPost(post, channel, message = '') {
const hookResult = await dispatch(runMessageWillBePostedHooks(newPost));
if (hookResult.error) {
return hookResult;
return hookResult as PostActions.CreatePostReturnType;
}
newPost = hookResult.data;
newPost = hookResult.data!;
return dispatch(PostActions.createPost(newPost, []));
};
}
export function selectAttachmentMenuAction(postId, actionId, cookie, dataSource, text, value) {
export function selectAttachmentMenuAction(
postId: string,
actionId: string,
cookie: string,
dataSource: string | undefined,
text: string,
value: string,
): ActionFuncAsync {
return async (dispatch) => {
dispatch({
type: ActionTypes.SELECT_ATTACHMENT_MENU_ACTION,
@@ -3,63 +3,65 @@
import {SearchTypes} from 'utils/constants';
export function setModalSearchTerm(term) {
import type {ChannelListSearchFilters, ModalFilters, UserGridSearchFilters} from 'types/store/views';
export function setModalSearchTerm(term: string) {
return {
type: SearchTypes.SET_MODAL_SEARCH,
data: term,
};
}
export function setPopoverSearchTerm(term) {
export function setPopoverSearchTerm(term: string) {
return {
type: SearchTypes.SET_POPOVER_SEARCH,
data: term,
};
}
export function setChannelMembersRhsSearchTerm(term) {
export function setChannelMembersRhsSearchTerm(term: string) {
return {
type: SearchTypes.SET_CHANNEL_MEMBERS_RHS_SEARCH,
data: term,
};
}
export function setModalFilters(filters = {}) {
export function setModalFilters(filters: ModalFilters = {}) {
return {
type: SearchTypes.SET_MODAL_FILTERS,
data: filters,
};
}
export function setUserGridSearch(term) {
export function setUserGridSearch(term: string) {
return {
type: SearchTypes.SET_USER_GRID_SEARCH,
data: term,
};
}
export function setUserGridFilters(filters = {}) {
export function setUserGridFilters(filters: UserGridSearchFilters = {}) {
return {
type: SearchTypes.SET_USER_GRID_FILTERS,
data: filters,
};
}
export function setTeamListSearch(term) {
export function setTeamListSearch(term: string) {
return {
type: SearchTypes.SET_TEAM_LIST_SEARCH,
data: term,
};
}
export function setChannelListSearch(term) {
export function setChannelListSearch(term: string) {
return {
type: SearchTypes.SET_CHANNEL_LIST_SEARCH,
data: term,
};
}
export function setChannelListFilters(filters = {}) {
export function setChannelListFilters(filters: ChannelListSearchFilters = {}) {
return {
type: SearchTypes.SET_CHANNEL_LIST_FILTERS,
data: filters,
@@ -4,17 +4,13 @@
import {ActionTypes} from 'utils/constants';
export function incrementWsErrorCount() {
return async (dispatch) => {
dispatch({
type: ActionTypes.INCREMENT_WS_ERROR_COUNT,
});
return {
type: ActionTypes.INCREMENT_WS_ERROR_COUNT,
};
}
export function resetWsErrorCount() {
return async (dispatch) => {
dispatch({
type: ActionTypes.RESET_WS_ERROR_COUNT,
});
return {
type: ActionTypes.RESET_WS_ERROR_COUNT,
};
}
@@ -3,35 +3,35 @@
import {ActionTypes} from 'utils/constants';
export function setShowPreviewOnCreateComment(showPreview) {
export function setShowPreviewOnCreateComment(showPreview: boolean) {
return {
type: ActionTypes.SET_SHOW_PREVIEW_ON_CREATE_COMMENT,
showPreview,
};
}
export function setShowPreviewOnCreatePost(showPreview) {
export function setShowPreviewOnCreatePost(showPreview: boolean) {
return {
type: ActionTypes.SET_SHOW_PREVIEW_ON_CREATE_POST,
showPreview,
};
}
export function setShowPreviewOnEditChannelHeaderModal(showPreview) {
export function setShowPreviewOnEditChannelHeaderModal(showPreview: boolean) {
return {
type: ActionTypes.SET_SHOW_PREVIEW_ON_EDIT_CHANNEL_HEADER_MODAL,
showPreview,
};
}
export function setShowPreviewOnChannelSettingsHeaderModal(showPreview) {
export function setShowPreviewOnChannelSettingsHeaderModal(showPreview: boolean) {
return {
type: ActionTypes.SET_SHOW_PREVIEW_ON_CHANNEL_SETTINGS_HEADER_MODAL,
showPreview,
};
}
export function setShowPreviewOnChannelSettingsPurposeModal(showPreview) {
export function setShowPreviewOnChannelSettingsPurposeModal(showPreview: boolean) {
return {
type: ActionTypes.SET_SHOW_PREVIEW_ON_CHANNEL_SETTINGS_PURPOSE_MODAL,
showPreview,
@@ -9,7 +9,7 @@ import {getAccessControlPolicy as fetchPolicy, createAccessControlPolicy as crea
import {createJob} from 'mattermost-redux/actions/jobs';
import {getAccessControlSettings, getAccessControlPolicy as getPolicy} from 'mattermost-redux/selectors/entities/access_control';
import {setNavigationBlocked} from 'actions/admin_actions.jsx';
import {setNavigationBlocked} from 'actions/admin_actions';
import type {GlobalState} from 'types/store';
@@ -8,7 +8,7 @@ import type {Dispatch} from 'redux';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {getMyTeams} from 'mattermost-redux/selectors/entities/teams';
import {deferNavigation} from 'actions/admin_actions.jsx';
import {deferNavigation} from 'actions/admin_actions';
import {getCurrentLocale} from 'selectors/i18n';
import {getNavigationBlocked} from 'selectors/views/admin';
@@ -11,7 +11,7 @@ import {buttonClassNames} from '@mattermost/shared/components/button';
import {getCloudCustomer, updateCloudCustomer, updateCloudCustomerAddress} from 'mattermost-redux/actions/cloud';
import {setNavigationBlocked} from 'actions/admin_actions.jsx';
import {setNavigationBlocked} from 'actions/admin_actions';
import BlockableLink from 'components/admin_console/blockable_link';
import CountrySelector from 'components/payment_form/country_selector';
@@ -9,7 +9,7 @@ import {WithTooltip} from '@mattermost/shared/components/tooltip';
import {Client4} from 'mattermost-redux/client';
import {uploadBrandImage, deleteBrandImage} from 'actions/admin_actions.jsx';
import {uploadBrandImage, deleteBrandImage} from 'actions/admin_actions';
import SettingSet from 'components/admin_console/setting_set';
import useDidUpdate from 'components/common/hooks/useDidUpdate';
@@ -57,7 +57,7 @@ const BrandImageSetting = ({
const imageRef = useRef<HTMLImageElement>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const [brandImage, setBrandImage] = useState<Blob | undefined>();
const [brandImage, setBrandImage] = useState<File | undefined>();
const [shouldDeleteBrandImage, setShouldDeleteBrandImage] = useState(false);
const [brandImageExists, setBrandImageExists] = useState(false);
const [brandImageTimestamp, setBrandImageTimestamp] = useState(Date.now());
@@ -6,7 +6,7 @@ import type {MouseEvent} from 'react';
import type {ClusterInfo} from '@mattermost/types/admin';
import {getClusterStatus} from 'actions/admin_actions.jsx';
import {getClusterStatus} from 'actions/admin_actions';
import ClusterTable from './cluster_table';
@@ -6,7 +6,6 @@ import {defineMessage} from 'react-intl';
import {connect} from 'react-redux';
import type {PluginRedux, PluginSetting, PluginSettingSection} from '@mattermost/types/plugins';
import type {GlobalState} from '@mattermost/types/store';
import {createSelector} from 'mattermost-redux/selectors/create_selector';
import {appsFeatureFlagEnabled} from 'mattermost-redux/selectors/entities/apps';
@@ -18,6 +17,7 @@ import {getAdminConsoleCustomComponents, getAdminConsoleCustomSections} from 'se
import {appsPluginID} from 'utils/apps';
import {Constants} from 'utils/constants';
import type {GlobalState} from 'types/store';
import type {AdminConsolePluginComponent, AdminConsolePluginCustomSection} from 'types/store/plugins';
import CustomPluginSettings from './custom_plugin_settings';
@@ -18,7 +18,7 @@ import {
import {getDataRetentionCustomPolicy} from 'mattermost-redux/selectors/entities/admin';
import {getTeamsInPolicy} from 'mattermost-redux/selectors/entities/teams';
import {setNavigationBlocked} from 'actions/admin_actions.jsx';
import {setNavigationBlocked} from 'actions/admin_actions';
import type {GlobalState} from 'types/store';
@@ -11,7 +11,7 @@ import {
import {getEnvironmentConfig} from 'mattermost-redux/selectors/entities/admin';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {setNavigationBlocked} from 'actions/admin_actions.jsx';
import {setNavigationBlocked} from 'actions/admin_actions';
import type {GlobalState} from 'types/store';
@@ -7,7 +7,7 @@ import DatabaseSettings from 'components/admin_console/database_settings';
import {renderWithContext} from 'tests/react_testing_utils';
jest.mock('actions/admin_actions.jsx', () => {
jest.mock('actions/admin_actions', () => {
const pingFn = () => {
return jest.fn(() => {
return {ActiveSearchBackend: 'none'};
@@ -9,7 +9,7 @@ import ElasticSearchSettings from 'components/admin_console/elasticsearch_settin
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
jest.mock('actions/admin_actions.jsx', () => {
jest.mock('actions/admin_actions', () => {
return {
elasticsearchPurgeIndexes: jest.fn(),
rebuildChannelsIndex: jest.fn(),
@@ -8,7 +8,7 @@ import {FormattedMessage, defineMessage, defineMessages} from 'react-intl';
import type {AdminConfig} from '@mattermost/types/config';
import type {Job, JobType} from '@mattermost/types/jobs';
import {elasticsearchPurgeIndexes, elasticsearchTest, rebuildChannelsIndex} from 'actions/admin_actions.jsx';
import {elasticsearchPurgeIndexes, elasticsearchTest, rebuildChannelsIndex} from 'actions/admin_actions';
import ExternalLink from 'components/external_link';
import WarningIcon from 'components/widgets/icons/fa_warning_icon';
@@ -16,7 +16,7 @@ import {getRoles} from 'mattermost-redux/selectors/entities/roles';
import {getTeam} from 'mattermost-redux/selectors/entities/teams';
import {isCurrentUserSystemAdmin, currentUserHasAnAdminRole, getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {setNavigationBlocked, deferNavigation, cancelNavigation, confirmNavigation} from 'actions/admin_actions.jsx';
import {setNavigationBlocked, deferNavigation, cancelNavigation, confirmNavigation} from 'actions/admin_actions';
import {setAdminConsoleUsersManagementTableProperties} from 'actions/views/admin';
import {selectLhsItem} from 'actions/views/lhs';
import {getAdminDefinition, getConsoleAccess} from 'selectors/admin_console';
@@ -48,7 +48,6 @@ describe('components/admin_console/license_settings/LicenseSettings', () => {
removeLicense: jest.fn(),
upgradeToE0: jest.fn(),
ping: jest.fn(),
requestTrialLicense: jest.fn(),
restartServer: jest.fn(),
getPrevTrialLicense: jest.fn(),
upgradeToE0Status: jest.fn().mockImplementation(() => Promise.resolve({percentage: 0, error: null})),
@@ -55,7 +55,6 @@ type Props = {
isAllowedToUpgradeToEnterprise: () => Promise<ActionResult>;
restartServer: () => Promise<StatusOK>;
ping: () => Promise<{status: string}>;
requestTrialLicense: (users: number, termsAccepted: boolean, receiveEmailsAccepted: boolean, featureName: string) => Promise<ActionResult>;
openModal: <P>(modalData: ModalData<P>) => void;
getServerLimits: () => Promise<ActionResult<ServerLimits, ServerError>>;
getFilteredUsersStats: (filters: GetFilteredUsersStatsOpts) => Promise<{
@@ -20,7 +20,7 @@ import useGetAgentsBridgeEnabled from 'components/common/hooks/useGetAgentsBridg
import Toggle from 'components/toggle';
import BetaTag from 'components/widgets/tag/beta_tag';
import * as I18n from 'i18n/i18n.jsx';
import * as I18n from 'i18n/i18n';
import AgentsSettings from './agents_settings';
import AutoTranslationInfo from './auto_translation_info';
@@ -16,7 +16,7 @@ import {
} from 'components/admin_console/system_properties/controls';
import ExternalLink from 'components/external_link';
import * as I18n from 'i18n/i18n.jsx';
import * as I18n from 'i18n/i18n';
import type {SystemConsoleCustomSettingsComponentProps} from '../schema_admin_settings';
import type {SearchableStrings} from '../types';
@@ -9,7 +9,7 @@ import {getAccessControlPolicy as fetchPolicy, createAccessControlPolicy as crea
import {getAccessControlSettings, getAccessControlPolicy as getPolicy} from 'mattermost-redux/selectors/entities/access_control';
import {getFeatureFlagValue} from 'mattermost-redux/selectors/entities/general';
import {setNavigationBlocked} from 'actions/admin_actions.jsx';
import {setNavigationBlocked} from 'actions/admin_actions';
import type {GlobalState} from 'types/store';
@@ -11,7 +11,7 @@ import {loadRolesIfNeeded, editRole} from 'mattermost-redux/actions/roles';
import {getLicense, getConfig} from 'mattermost-redux/selectors/entities/general';
import {getRoles} from 'mattermost-redux/selectors/entities/roles';
import {setNavigationBlocked} from 'actions/admin_actions.jsx';
import {setNavigationBlocked} from 'actions/admin_actions';
import PermissionSystemSchemeSettings from './permission_system_scheme_settings';
@@ -36,7 +36,7 @@ import AdminSectionPanel from 'components/widgets/admin_console/admin_section_pa
import WarningIcon from 'components/widgets/icons/fa_warning_icon';
import BetaTag from 'components/widgets/tag/beta_tag';
import * as I18n from 'i18n/i18n.jsx';
import * as I18n from 'i18n/i18n';
import Constants from 'utils/constants';
import {mappingValueFromRoles, rolesFromMapping} from 'utils/policy_roles_adapter';
@@ -10,7 +10,7 @@ import {updateUserRoles} from 'mattermost-redux/actions/users';
import {getLicense} from 'mattermost-redux/selectors/entities/general';
import {getRolesById} from 'mattermost-redux/selectors/entities/roles';
import {setNavigationBlocked} from 'actions/admin_actions.jsx';
import {setNavigationBlocked} from 'actions/admin_actions';
import type {GlobalState} from 'types/store';
@@ -13,7 +13,7 @@ import {updateUserActive, updateUserAuth, getUser, patchUser, updateUserMfa, get
import {getConfig, getCustomProfileAttributes, getLicense, isCustomProfileAttributesEnabled} from 'mattermost-redux/selectors/entities/general';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {setNavigationBlocked} from 'actions/admin_actions.jsx';
import {setNavigationBlocked} from 'actions/admin_actions';
import {openModal} from 'actions/views/modals';
import {getShowLockedManageUserSettings, getShowManageUserSettings} from 'selectors/admin_console';
@@ -32,7 +32,7 @@ const siteURLCheck = async (config: Partial<AdminConfig>, formatMessage: ReturnT
const onError = () => {
status = ItemStatus.ERROR;
};
await testSiteURL(onSuccess, onError, config.ServiceSettings?.SiteURL);
await testSiteURL(onSuccess, onError, config.ServiceSettings?.SiteURL ?? '');
};
await testURL();
@@ -33,7 +33,7 @@ const search = async (
check = ItemStatus.OK;
}
};
await elasticsearchTest(config, onSuccess);
await elasticsearchTest(config as AdminConfig, onSuccess);
return check;
};
@@ -6,6 +6,7 @@ import {useCallback, useMemo, useRef, useState} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import type {ServerError} from '@mattermost/types/errors';
import type {Post} from '@mattermost/types/posts';
import type {SchedulingInfo} from '@mattermost/types/schedule_post';
import {FileTypes} from 'mattermost-redux/action_types';
@@ -198,7 +199,9 @@ const useSubmit = (
try {
let response;
if (isInEditMode) {
response = await dispatch(editPost(submittingDraft));
// The types of Post and PostDraft are mostly interchangeable, but our typing doesn't make it easy to
// mix them without assertions like this
response = await dispatch(editPost(submittingDraft as unknown as Post));
handleFileChange(submittingDraft);
} else {
response = await dispatch(onSubmit(submittingDraft, options, schedulingInfo));
@@ -11,7 +11,7 @@ import type {ServerLimits} from '@mattermost/types/limits';
import {getFormattedFileSize} from 'mattermost-redux/utils/file_utils';
import * as AdminActions from 'actions/admin_actions.jsx';
import * as AdminActions from 'actions/admin_actions';
import UserSeatAlertBanner from 'components/admin_console/license_settings/user_seat_alert_banner';
import ActivatedUserCard from 'components/analytics/activated_users_card';
@@ -10,27 +10,20 @@ import type {OAuthApp} from '@mattermost/types/integrations';
import type {ActionResult} from 'mattermost-redux/types/actions';
import type {allowOAuth2} from 'actions/admin_actions';
import FormError from 'components/form_error';
import icon50 from 'images/icon50x50.png';
import {getHistory} from 'utils/browser_history';
export type Params = {
responseType: string | null;
clientId: string | null;
redirectUri: string | null;
state: string | null;
scope: string | null;
resource: string | null;
};
type Props = {
location: {
search: string;
};
actions: {
getOAuthAppInfo: (clientId: string | null) => Promise<ActionResult<OAuthApp>>;
allowOAuth2: (params: Params) => Promise<ActionResult<{redirect: string}>>;
getOAuthAppInfo: (clientId: string) => Promise<ActionResult<OAuthApp>>;
allowOAuth2: (...params: Parameters<typeof allowOAuth2>) => Promise<ActionResult<{redirect: string}>>;
};
};
@@ -53,7 +46,7 @@ export default class Authorize extends React.PureComponent<Props, State> {
blocker.parentNode.removeChild(blocker);
}
const clientId = (new URLSearchParams(this.props.location.search)).get('client_id');
if (clientId && !((/^[a-z0-9]+$/).test(clientId))) {
if (!clientId || !((/^[a-z0-9]+$/).test(clientId))) {
return;
}
@@ -5,7 +5,7 @@ import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import type {Dispatch} from 'redux';
import {allowOAuth2, getOAuthAppInfo} from 'actions/admin_actions.jsx';
import {allowOAuth2, getOAuthAppInfo} from 'actions/admin_actions';
import Authorize from './authorize';
@@ -81,7 +81,7 @@ export type Props = {
loadStatusesForProfilesList: (users: UserProfile[]) => void;
searchProfiles: (term: string, options: any) => Promise<ActionResult>;
closeModal: (modalId: string) => void;
searchAssociatedGroupsForReference: (prefix: string, teamId: string, channelId: string | undefined, opts: GroupSearchParams) => Promise<ActionResult>;
searchAssociatedGroupsForReference: (prefix: string, teamId: string, channelId: string, opts: GroupSearchParams) => Promise<ActionResult>;
getTeamMembersByIds: (teamId: string, userIds: string[]) => Promise<ActionResult>;
};
};
@@ -229,9 +229,7 @@ const ChannelInviteModalComponent = (props: Props) => {
let users: UserProfileValue[];
if (isPolicyEnforcedPrivate) {
const sourceList =
term.trim().length > 0 ?
(privateAbacSearchHits ?? []) :
abacFilteredUsers;
term.trim().length > 0 ? (privateAbacSearchHits ?? []) : abacFilteredUsers;
users = filterOutDeletedAndExcludedAndNotInTeamUsers(sourceList, excludedAndNotInTeamUserIds);
} else {
// Non-ABAC or advisory (public policy): full team list.
@@ -422,16 +420,14 @@ const ChannelInviteModalComponent = (props: Props) => {
// getOptions() reads from. Routing through Redux here would
// populate profilesNotInCurrentChannel which getOptions ignores
// on the strict-gate path, leaving subsequent pages invisible.
const fetchPage = isPolicyEnforcedPrivate ?
fetchAbacUsers(page + 1, USERS_PER_PAGE, cursorId) :
props.actions.getProfilesNotInChannel(
props.channel.team_id,
props.channel.id,
props.channel.group_constrained,
page + 1,
USERS_PER_PAGE,
cursorId,
);
const fetchPage = isPolicyEnforcedPrivate ? fetchAbacUsers(page + 1, USERS_PER_PAGE, cursorId) : props.actions.getProfilesNotInChannel(
props.channel.team_id,
props.channel.id,
props.channel.group_constrained,
page + 1,
USERS_PER_PAGE,
cursorId,
);
fetchPage.then((result) => {
// Store the cursor for the next page (ID of the last user)
@@ -63,8 +63,8 @@ export default class ClaimController extends React.PureComponent<Props> {
path={`${this.props.match.url}/oauth_to_email`}
render={() => (
<OAuthToEmail
currentType={currentType}
email={email}
currentType={currentType || ''}
email={email || ''}
siteName={this.props.siteName}
passwordConfig={this.props.passwordConfig}
/>
@@ -74,7 +74,7 @@ export default class ClaimController extends React.PureComponent<Props> {
path={`${this.props.match.url}/email_to_oauth`}
render={() => (
<EmailToOAuth
newType={newType}
newType={newType || ''}
email={email || ''}
siteName={this.props.siteName}
/>
@@ -8,7 +8,7 @@ import {FormattedMessage, useIntl} from 'react-intl';
import {Button} from '@mattermost/shared/components/button';
import type {AuthChangeResponse} from '@mattermost/types/users';
import {emailToLdap} from 'actions/admin_actions.jsx';
import {emailToLdap} from 'actions/admin_actions';
import LoginMfa from 'components/login/login_mfa';
@@ -8,7 +8,7 @@ import {FormattedMessage, useIntl} from 'react-intl';
import {Button} from '@mattermost/shared/components/button';
import type {AuthChangeResponse} from '@mattermost/types/users';
import {emailToOAuth} from 'actions/admin_actions.jsx';
import {emailToOAuth} from 'actions/admin_actions';
import LoginMfa from 'components/login/login_mfa';
@@ -19,7 +19,7 @@ import type {SubmitOptions} from './email_to_ldap';
import ErrorLabel from './error_label';
type Props = {
newType: string | null;
newType: string;
email: string;
siteName?: string;
};
@@ -10,7 +10,7 @@ import type {AuthChangeResponse} from '@mattermost/types/users';
import type {PasswordConfig} from 'mattermost-redux/selectors/entities/general';
import {oauthToEmail} from 'actions/admin_actions.jsx';
import {oauthToEmail} from 'actions/admin_actions';
import Constants from 'utils/constants';
import {isValidPassword} from 'utils/password';
@@ -19,8 +19,8 @@ import {toTitleCase} from 'utils/utils';
import ErrorLabel from './error_label';
type Props = {
currentType: string | null;
email: string | null;
currentType: string;
email: string;
siteName?: string;
passwordConfig?: PasswordConfig;
};
@@ -50,7 +50,7 @@ import './style.scss';
export type Actions = {
addMessageIntoHistory: (message: string) => void;
editPost: (input: Partial<Post>) => Promise<Post>;
editPost: (input: Post) => Promise<ActionResult<Post>>;
setDraft: (name: string, value: PostDraft | null) => void;
unsetEditingPost: () => void;
scrollPostListToBottom: () => void;
@@ -5,8 +5,11 @@ import React from 'react';
import {defineMessage, FormattedMessage} from 'react-intl';
import {Button} from '@mattermost/shared/components/button';
import type {MfaSecret} from '@mattermost/types/mfa';
import type {UserProfile} from '@mattermost/types/users';
import type {ActionResult} from 'mattermost-redux/types/actions';
import LocalizedPlaceholderInput from 'components/localized_placeholder_input';
type Props = {
@@ -14,21 +17,11 @@ type Props = {
siteName?: string;
enforceMultifactorAuthentication: boolean;
actions: {
activateMfa: (code: string) => Promise<{
error?: {
server_error_id: string;
message: string;
};
}>;
generateMfaSecret: () => Promise<{
data: {
secret: string;
qr_code: string;
};
error?: {
message: string;
};
}>;
activateMfa: (code: string) => Promise<ActionResult<unknown, {
server_error_id: string;
message: string;
}>>;
generateMfaSecret: () => Promise<ActionResult<MfaSecret>>;
};
history: {
push(path: string): void;
@@ -73,8 +66,8 @@ export default class Setup extends React.PureComponent<Props, State> {
}
this.setState({
secret: data.secret,
qrCode: data.qr_code,
secret: data!.secret,
qrCode: data!.qr_code,
});
});
}
@@ -45,7 +45,7 @@ export type Props = {
isEmbedVisible?: boolean;
toggleEmbedVisibility: () => void;
actions: {
editPost: (post: {id: string; props: Record<string, any>}) => void;
editPost: (post: Post) => void;
};
isInPermalink?: boolean;
imageCollapsed?: boolean;
@@ -115,7 +115,7 @@ const PostAttachmentOpenGraph = ({openGraphData, post, actions, link, isInPermal
props,
};
return actions.editPost(patchedPost);
return actions.editPost(patchedPost as Post);
};
const safeLink = makeUrlSafe(openGraphData?.url || link);
@@ -27,7 +27,7 @@ jest.mock('components/loading_screen', () => ({
),
}));
jest.mock('actions/telemetry_actions.jsx', () => ({
jest.mock('actions/telemetry_actions', () => ({
clearMarks: jest.fn(),
mark: jest.fn(),
}));
@@ -6,7 +6,7 @@ import React from 'react';
import type {ActionResult} from 'mattermost-redux/types/actions';
import type {updateNewMessagesAtInChannel} from 'actions/global_actions';
import {clearMarks, mark} from 'actions/telemetry_actions.jsx';
import {clearMarks, mark} from 'actions/telemetry_actions';
import type {LoadPostsParameters, LoadPostsReturnValue, CanLoadMorePosts} from 'actions/views/channel';
import LoadingScreen from 'components/loading_screen';
+1 -1
View File
@@ -12,7 +12,7 @@ import {setSystemEmojis} from 'mattermost-redux/actions/emojis';
import {setUrl} from 'mattermost-redux/actions/general';
import {Client4} from 'mattermost-redux/client';
import {temporarilySetPageLoadContext} from 'actions/telemetry_actions.jsx';
import {temporarilySetPageLoadContext} from 'actions/telemetry_actions';
import BrowserStore from 'stores/browser_store';
import {makeAsyncComponent, makeAsyncPluggableComponent} from 'components/async_load';
@@ -91,7 +91,7 @@ function StartTrialFormModal(props: Props): JSX.Element | null {
const [name, setName] = useState('');
const [email, setEmail] = useState(currentUser.email);
const [companyName, setCompanyName] = useState('');
const [orgSize, setOrgSize] = useState<OrgSize | undefined>();
const [orgSize, setOrgSize] = useState<OrgSize | ''>('');
const [country, setCountry] = useState('');
const [businessEmailError, setBusinessEmailError] = useState<CustomMessageInputType | undefined>(undefined);
const {formatMessage} = useIntl();
@@ -213,8 +213,8 @@ function StartTrialFormModal(props: Props): JSX.Element | null {
};
const getOrgSizeDropdownValue = () => {
if (typeof orgSize === 'undefined') {
return orgSize;
if (!orgSize) {
return undefined;
}
return {
value: orgSize,
@@ -49,7 +49,7 @@ export type Props = {
autocompleteUsersInChannel: (prefix: string) => Promise<ActionResult>;
useChannelMentions: boolean;
autocompleteGroups: Group[] | null;
searchAssociatedGroupsForReference: (prefix: string) => Promise<{data: Group[]}>;
searchAssociatedGroupsForReference: (prefix: string) => Promise<ActionResult<Group[]>>;
priorityProfiles: UserProfile[] | undefined;
defaultAgent?: Agent;
};
@@ -71,7 +71,7 @@ export default class AtMentionProvider extends Provider {
public autocompleteUsersInChannel: (prefix: string) => Promise<ActionResult>;
public useChannelMentions: boolean;
public autocompleteGroups: Group[] | null;
public searchAssociatedGroupsForReference: (prefix: string) => Promise<{data: Group[]}>;
public searchAssociatedGroupsForReference: (prefix: string) => Promise<ActionResult<Group[]>>;
public priorityProfiles: UserProfile[] | undefined;
public defaultAgent?: Agent;
@@ -2,13 +2,13 @@
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {bindActionCreators, type Dispatch} from 'redux';
import {addMessageIntoHistory} from 'mattermost-redux/actions/posts';
import SuggestionBox from './suggestion_box';
function mapDispatchToProps(dispatch) {
function mapDispatchToProps(dispatch: Dispatch) {
return {
actions: bindActionCreators({
addMessageIntoHistory,
@@ -64,7 +64,7 @@ export type Props = {
actions: {
autocompleteUsersInChannel: (prefix: string, channelId: string) => Promise<ActionResult>;
autocompleteChannels: (term: string, success: (channels: Channel[]) => void, error: () => void) => Promise<ActionResult>;
searchAssociatedGroupsForReference: (prefix: string, teamId: string, channelId: string | undefined) => Promise<{data: any}>;
searchAssociatedGroupsForReference: (prefix: string, teamId: string, channelId: string) => Promise<ActionResult>;
fetchAgents: () => Promise<ActionResult>;
};
useChannelMentions: boolean;
@@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {GlobalState} from 'types/store';
import {getAllLanguages, getLanguageInfo, getLanguages, isLanguageAvailable, languages} from './i18n';
jest.mock('./imports', () => ({
@@ -35,7 +37,7 @@ describe('i18n', () => {
},
},
},
};
} as GlobalState;
// no experimental languages
expect(getLanguages(state)).toBe(languages);
@@ -82,7 +84,7 @@ describe('i18n', () => {
},
},
},
};
} as GlobalState;
// no experimental languages
expect(isLanguageAvailable(state, 'cc')).toBe(false);
@@ -7,8 +7,17 @@
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import type {GlobalState} from 'types/store';
import {langFiles, langIDs, langLabels} from './imports';
export interface Language {
value: string;
name: string;
order: number;
url: string;
}
// should match the values in server/public/shared/i18n/i18n.go
export const languages = {
de: {
@@ -145,14 +154,14 @@ export const languages = {
},
};
export function getAllLanguages(includeExperimental) {
export function getAllLanguages(includeExperimental = false): Record<string, Language> {
if (includeExperimental) {
let order = Object.keys(languages).length;
return {
...langIDs.reduce((out, id) => {
...langIDs.reduce<Record<string, Language>>((out, id) => {
out[id] = {
value: id,
name: langLabels[id] + ' (Experimental)',
name: langLabels[id as keyof typeof langLabels] + ' (Experimental)',
url: langFiles[id],
order: order++,
};
@@ -164,32 +173,23 @@ export function getAllLanguages(includeExperimental) {
return languages;
}
/**
* @param {import('types/store').GlobalState} state
* @returns {Record<string, Language>}
*/
export function getLanguages(state) {
export function getLanguages(state: GlobalState) {
const config = getConfig(state);
if (!config.AvailableLocales) {
return getAllLanguages(config.EnableExperimentalLocales === 'true');
}
return config.AvailableLocales.split(',').reduce((result, l) => {
if (languages[l]) {
result[l] = languages[l];
return config.AvailableLocales.split(',').reduce<Record<string, Language>>((result, l) => {
if (Object.hasOwn(languages, l)) {
result[l] = languages[l as keyof typeof languages];
}
return result;
}, {});
}
export function getLanguageInfo(locale) {
export function getLanguageInfo(locale: string) {
return getAllLanguages(true)[locale];
}
/**
* @param {import('types/store').GlobalState} state
* @param {string} locale
* @returns {boolean}
*/
export function isLanguageAvailable(state, locale) {
export function isLanguageAvailable(state: GlobalState, locale: string) {
return Boolean(getLanguages(state)[locale]);
}
+68 -5
View File
@@ -68,12 +68,75 @@ import vi from './vi.json';
import zhCN from './zh-CN.json';
import zhTW from './zh-TW.json';
type TranslationsMap = {
[id: string]: string,
};
export const langIDs = ["am","ar","be","bg","bn","br","ca","cs","da","de","el","en-AU","es","et","eu","fa","fi","fil","fr","fy","gl","gu","he","hi","hr","hu","id","is","it","ja","ka","kk-Latn","kk","km","ko","la","lo","lt","lv","mk","ml","mn","nb-NO","ne","nl","pl","pr","pt-BR","pt","ro","ru","si","sl","sq","sr","sv","th","tr","uk","vi","zh-CN","zh-TW"];
export const langLabels = {"am":"አማርኛ","ar":"العربية","be":"Беларуская","bg":"Български","bn":"বাংলা","br":"Brezhoneg","ca":"Català","cs":"Čeština","da":"Dansk","de":"Deutsch","el":"Ελληνικά","en-AU":"English (Australia)","es":"Español","et":"eesti keel","eu":"Euskara","fa":"فارسی","fi":"Suomi","fil":"Filipino","fr":"Français","fy":"Frysk","gl":"Galego","gu":"ગુજરાતી","he":"עברית‏","hi":"हिन्दी","hr":"Hrvatski","hu":"Magyar","id":"Bahasa Indonesia","is":"Íslenska","it":"Italiano","ja":"日本語","ka":"ქართული","kk-Latn":"Қазақша","kk":"Қазақша","km":"ភាសាខ្មែរ","ko":"한국어","la":"Latin","lo":"ພາສາລາວ","lt":"Lietuvių","lv":"Latviešu","mk":"Македонски","ml":"മലയാളം","mn":"Монгол","nb-NO":"Norsk (bokmål)","ne":"नेपाली","nl":"Nederlands","pl":"Polski","pr":"pr","pt-BR":"Português (Brasil)","pt":"Português","ro":"Română","ru":"Русский","si":"සිංහල","sl":"Slovenščina","sq":"Shqip","sr":"Српски","sv":"Svenska","th":"ภาษาไทย","tr":"Türkçe","uk":"Українська","vi":"Tiếng Việt","zh-CN":"中文(中国大陆)","zh-TW":"中文(台灣)"};
export const langFiles: {[langID: string]: TranslationsMap} = {am,ar,be,bg,bn,br,ca,cs,da,de,el,'en-AU':enAU,es,et,eu,fa,fi,fil,fr,fy,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,'kk-Latn':kkLatn,kk,km,ko,la,lo,lt,lv,mk,ml,mn,'nb-NO':nbNO,ne,nl,pl,pr,'pt-BR':ptBR,pt,ro,ru,si,sl,sq,sr,sv,th,tr,uk,vi,'zh-CN':zhCN,'zh-TW':zhTW};
// TypeScript thinks it's importing these language files' contents directly, but Webpack rewrites the above imports
// to the URL the file for lazy loading. That's the reason for the ugly type assertions below.
export const langFiles: {
[langID: string]: string;
} = {
am: am as unknown as string,
ar: ar as unknown as string,
be: be as unknown as string,
bg: bg as unknown as string,
bn: bn as unknown as string,
br: br as unknown as string,
ca: ca as unknown as string,
cs: cs as unknown as string,
da: da as unknown as string,
de: de as unknown as string,
el: el as unknown as string,
'en-AU': enAU as unknown as string,
es: es as unknown as string,
et: et as unknown as string,
eu: eu as unknown as string,
fa: fa as unknown as string,
fi: fi as unknown as string,
fil: fil as unknown as string,
fr: fr as unknown as string,
fy: fy as unknown as string,
gl: gl as unknown as string,
gu: gu as unknown as string,
he: he as unknown as string,
hi: hi as unknown as string,
hr: hr as unknown as string,
hu: hu as unknown as string,
id: id as unknown as string,
is: is as unknown as string,
it: it as unknown as string,
ja: ja as unknown as string,
ka: ka as unknown as string,
'kk-Latn': kkLatn as unknown as string,
kk: kk as unknown as string,
km: km as unknown as string,
ko: ko as unknown as string,
la: la as unknown as string,
lo: lo as unknown as string,
lt: lt as unknown as string,
lv: lv as unknown as string,
mk: mk as unknown as string,
ml: ml as unknown as string,
mn: mn as unknown as string,
'nb-NO': nbNO as unknown as string,
ne: ne as unknown as string,
nl: nl as unknown as string,
pl: pl as unknown as string,
pr: pr as unknown as string,
'pt-BR': ptBR as unknown as string,
pt: pt as unknown as string,
ro: ro as unknown as string,
ru: ru as unknown as string,
si: si as unknown as string,
sl: sl as unknown as string,
sq: sq as unknown as string,
sr: sr as unknown as string,
sv: sv as unknown as string,
th: th as unknown as string,
tr: tr as unknown as string,
uk: uk as unknown as string,
vi: vi as unknown as string,
'zh-CN': zhCN as unknown as string,
'zh-TW': zhTW as unknown as string
};
@@ -1054,7 +1054,7 @@ export function updateUserRoles(userId: string, roles: string): ActionFuncAsync
};
}
export function updateUserMfa(userId: string, activate: boolean, code = ''): ActionFuncAsync {
export function updateUserMfa(userId: string, activate: boolean, code = ''): ActionFuncAsync<boolean> {
return async (dispatch, getState) => {
try {
await Client4.updateUserMfa(userId, activate, code);
@@ -83,7 +83,7 @@ export const RESOURCE_KEYS = {
DEVELOPER: 'environment.developer',
MOBILE_SECURITY: 'environment.mobile_security',
},
};
} as const;
export const ResourceToSysConsolePermissionsTable: Record<string, string[]> = {
[RESOURCE_KEYS.ABOUT.EDITION_AND_LICENSE]: [Permissions.SYSCONSOLE_READ_ABOUT_EDITION_AND_LICENSE, Permissions.SYSCONSOLE_WRITE_ABOUT_EDITION_AND_LICENSE],
File diff suppressed because it is too large Load Diff
@@ -1,110 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable */
function defaultEqualityCheck(a, b) {
return a === b;
}
function areArgumentsShallowlyEqual(equalityCheck, prev, next) {
if (prev === null || next === null || prev.length !== next.length) {
return false;
}
// Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.
const length = prev.length;
for (let i = 0; i < length; i++) {
if (!equalityCheck(prev[i], next[i])) {
return false;
}
}
return true;
}
export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
let lastArgs = null
let lastResult = null
// we reference arguments instead of spreading them for performance reasons
return function () {
if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
// apply arguments instead of spreading for performance.
lastResult = func.apply(null, arguments)
}
lastArgs = arguments
return lastResult
}
}
function getDependencies(funcs) {
const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs;
if (!dependencies.every((dep) => typeof dep === 'function')) {
const dependencyTypes = dependencies.map(
(dep) => typeof dep,
).join(', ');
throw new Error(
'Selector creators expect all input-selectors to be functions, ' +
`instead received the following types: [${dependencyTypes}]`,
);
}
return dependencies;
}
export function createSelectorCreator(memoize, ...memoizeOptions) {
return (name, ...funcs) => {
const resultFunc = funcs.pop();
const dependencies = getDependencies(funcs);
const memoizedResultFunc = memoize(
function() {
// apply arguments instead of spreading for performance.
return resultFunc?.apply(null, arguments);
},
...memoizeOptions,
);
// If a selector is called with the exact same arguments we don't need to traverse our dependencies again.
const selector = memoize(function() {
const params = [];
const length = dependencies.length;
for (let i = 0; i < length; i++) {
// apply arguments instead of spreading and mutate a local list of params for performance.
params.push(dependencies[i].apply(null, arguments));
}
// apply arguments instead of spreading for performance.
return memoizedResultFunc.apply(null, params);
});
selector.resultFunc = resultFunc;
selector.dependencies = dependencies;
return selector;
};
}
export const createSelector = /* #__PURE__ */ createSelectorCreator(defaultMemoize);
export function createStructuredSelector(selectors, selectorCreator = createSelector) {
if (typeof selectors !== 'object') {
throw new Error(
'createStructuredSelector expects first argument to be an object ' +
`where each property is a selector, instead received a ${typeof selectors}`,
);
}
const objectKeys = Object.keys(selectors);
return selectorCreator(
objectKeys.map((key) => selectors[key]),
(...values) => {
return values.reduce((composition, value, index) => {
composition[objectKeys[index]] = value;
return composition;
}, {});
},
);
}
@@ -0,0 +1,163 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
/* eslint-disable prefer-spread */
/* eslint-disable prefer-rest-params */
import type {CreateSelector, EqualityCheck, ParametricSelector, Selector} from './types';
export type {
Selector,
OutputSelector,
ParametricSelector,
OutputParametricSelector,
CreateSelector,
} from './types';
function defaultEqualityCheck(a: any, b: any): boolean {
return a === b;
}
function areArgumentsShallowlyEqual(equalityCheck: EqualityCheck, prev: IArguments | null, next: IArguments | null): boolean {
if (prev === null || next === null || prev.length !== next.length) {
return false;
}
// Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.
const length = prev.length;
for (let i = 0; i < length; i++) {
if (!equalityCheck(prev[i], next[i])) {
return false;
}
}
return true;
}
export function defaultMemoize<F extends Function>(func: F, equalityCheck: Function = defaultEqualityCheck): F {
let lastArgs: IArguments | null = null;
let lastResult: any = null;
// we reference arguments instead of spreading them for performance reasons
return function memoized() {
if (!areArgumentsShallowlyEqual(equalityCheck as EqualityCheck, lastArgs, arguments)) {
// apply arguments instead of spreading for performance.
lastResult = func.apply(null, arguments as any);
}
lastArgs = arguments;
return lastResult;
} as unknown as F;
}
function getDependencies(funcs: any[]): Function[] {
const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs;
if (!dependencies.every((dep: any) => typeof dep === 'function')) {
const dependencyTypes = dependencies.map(
(dep: any) => typeof dep,
).join(', ');
throw new Error(
'Selector creators expect all input-selectors to be functions, ' +
`instead received the following types: [${dependencyTypes}]`,
);
}
return dependencies;
}
export function createSelectorCreator(
memoize: <F extends Function>(func: F, measure: Function) => F,
): typeof createSelector;
export function createSelectorCreator<O1>(
memoize: <F extends Function>(func: F, measure: Function,
option1: O1) => F,
option1: O1,
): typeof createSelector;
export function createSelectorCreator<O1, O2>(
memoize: <F extends Function>(func: F, measure: Function,
option1: O1,
option2: O2) => F,
option1: O1,
option2: O2,
): typeof createSelector;
export function createSelectorCreator<O1, O2, O3>(
memoize: <F extends Function>(func: F, measure: Function,
option1: O1,
option2: O2,
option3: O3,
...rest: any[]) => F,
option1: O1,
option2: O2,
option3: O3,
...rest: any[]
): typeof createSelector;
export function createSelectorCreator(memoize: any, ...memoizeOptions: any[]): typeof createSelector {
return ((_name: string, ...funcs: any[]) => {
const resultFunc = funcs.pop();
const dependencies = getDependencies(funcs);
const memoizedResultFunc = memoize(
function resultFuncMemoized() {
// apply arguments instead of spreading for performance.
return resultFunc?.apply(null, arguments);
},
...memoizeOptions,
);
// If a selector is called with the exact same arguments we don't need to traverse our dependencies again.
const selector = memoize(function selectorMemoized() {
const params = [];
const length = dependencies.length;
for (let i = 0; i < length; i++) {
// apply arguments instead of spreading and mutate a local list of params for performance.
params.push(dependencies[i].apply(null, arguments));
}
// apply arguments instead of spreading for performance.
return memoizedResultFunc.apply(null, params);
});
selector.resultFunc = resultFunc;
selector.dependencies = dependencies;
return selector;
});
}
export const createSelector: CreateSelector = /* #__PURE__ */ createSelectorCreator(defaultMemoize);
export function createStructuredSelector<S, T>(
selectors: {[K in keyof T]: Selector<S, T[K]>},
selectorCreator?: typeof createSelector,
): Selector<S, T>;
export function createStructuredSelector<S, P, T>(
selectors: {[K in keyof T]: ParametricSelector<S, P, T[K]>},
selectorCreator?: typeof createSelector,
): ParametricSelector<S, P, T>;
export function createStructuredSelector(selectors: any, selectorCreator: any = createSelector): any {
if (typeof selectors !== 'object') {
throw new Error(
'createStructuredSelector expects first argument to be an object ' +
`where each property is a selector, instead received a ${typeof selectors}`,
);
}
const objectKeys = Object.keys(selectors);
return selectorCreator(
objectKeys.map((key) => selectors[key]),
(...values: any[]) => {
return values.reduce((composition: any, value: any, index: number) => {
composition[objectKeys[index]] = value;
return composition;
}, {});
},
);
}
@@ -221,9 +221,6 @@ describe('getDefaultReportAProblemMailtoLink', () => {
mockIsDesktopApp.mockReturnValue(true);
mockGetDesktopVersion.mockReturnValue('5.10.0');
// Reset selector cache so it re-runs with updated mocks
getDefaultReportAProblemMailtoLink.resetRecomputations?.();
const stateWithDifferentUser = {
...baseState,
entities: {
@@ -1,14 +1,21 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {getAdminDefinition} from 'selectors/admin_console.jsx';
import AdminDefinition from 'components/admin_console/admin_definition';
import type {GlobalState} from 'types/store';
import {getAdminDefinition} from './admin_console';
type TestReducerState = {
something?: string;
otherThing?: string;
};
describe('Selectors.AdminConsole', () => {
describe('get admin definitions', () => {
it('should return the default admin definition if there is not plugins', () => {
const state = {plugins: {adminConsoleReducers: {}}};
const state = {plugins: {adminConsoleReducers: {}}} as GlobalState;
expect(getAdminDefinition(state)).toEqual(AdminDefinition);
});
@@ -17,7 +24,7 @@ describe('Selectors.AdminConsole', () => {
plugins: {
adminConsoleReducers: {clean: () => ({})},
},
});
} as unknown as GlobalState);
expect(result).toEqual({});
});
@@ -25,31 +32,41 @@ describe('Selectors.AdminConsole', () => {
const result = getAdminDefinition({
plugins: {
adminConsoleReducers: {
'add-something': (data) => {
data.something = 'test';
return data;
'add-something': (data: TestReducerState) => {
return {
...data,
something: 'test',
};
},
},
},
});
} as unknown as GlobalState);
expect(result.something).toEqual('test');
});
it('should allow to use multiple plugins', () => {
type TestReducerState = {
something?: string;
otherThing?: string;
};
const result = getAdminDefinition({
plugins: {
adminConsoleReducers: {
'add-something': (data) => {
data.something = 'test';
return data;
'add-something': (data: TestReducerState) => {
return {
...data,
something: 'test',
};
},
'add-other-thing': (data) => {
data.otherThing = 'other-thing';
return data;
'add-other-thing': (data: TestReducerState) => {
return {
...data,
otherThing: 'other-thing',
};
},
},
},
});
} as unknown as GlobalState);
expect(result.something).toEqual('test');
expect(result.otherThing).toEqual('other-thing');
});
@@ -11,12 +11,14 @@ import {getMySystemPermissions, haveISystemPermission} from 'mattermost-redux/se
import AdminDefinition from 'components/admin_console/admin_definition';
import type {GlobalState} from 'types/store';
import {isEnterpriseLicense} from '../utils/license_utils';
export const getAdminDefinition = createSelector(
'getAdminDefinition',
() => AdminDefinition,
(state) => state.plugins.adminConsoleReducers,
(state: GlobalState) => state.plugins.adminConsoleReducers,
(adminDefinition, reducers) => {
let result = cloneDeep(AdminDefinition);
for (const reducer of Object.values(reducers)) {
@@ -26,10 +28,10 @@ export const getAdminDefinition = createSelector(
},
);
export const getAdminConsoleCustomComponents = (state, pluginId) =>
export const getAdminConsoleCustomComponents = (state: GlobalState, pluginId: string) =>
state.plugins.adminConsoleCustomComponents[pluginId] || {};
export const getAdminConsoleCustomSections = (state, pluginId) =>
export const getAdminConsoleCustomSections = (state: GlobalState, pluginId: string) =>
state.plugins.adminConsoleCustomSections[pluginId] || {};
export const getConsoleAccess = createSelector(
@@ -37,22 +39,29 @@ export const getConsoleAccess = createSelector(
getAdminDefinition,
getMySystemPermissions,
(adminDefinition, mySystemPermissions) => {
const consoleAccess = {read: {}, write: {}};
const addEntriesForKey = (entryKey) => {
const consoleAccess: {
read: Record<string, boolean>;
write: Record<string, boolean>;
} = {
read: {},
write: {},
};
const addEntriesForKey = (entryKey: string) => {
const permissions = ResourceToSysConsolePermissionsTable[entryKey].filter((x) => mySystemPermissions.has(x));
consoleAccess.read[entryKey] = permissions.length !== 0;
consoleAccess.write[entryKey] = permissions.some((permission) => permission.startsWith('sysconsole_write_'));
};
const mapAccessValuesForKey = ([key]) => {
if (typeof RESOURCE_KEYS[key.toUpperCase()] === 'object') {
Object.values(RESOURCE_KEYS[key.toUpperCase()]).forEach((entry) => {
const mapAccessValuesForKey = (key: string) => {
const upperKey = key.toUpperCase() as keyof typeof RESOURCE_KEYS;
if (upperKey in RESOURCE_KEYS && typeof RESOURCE_KEYS[upperKey] === 'object') {
Object.values(RESOURCE_KEYS[upperKey]).forEach((entry) => {
addEntriesForKey(entry);
});
} else {
addEntriesForKey(key);
}
};
Object.entries(adminDefinition).forEach(mapAccessValuesForKey);
Object.keys(adminDefinition).forEach(mapAccessValuesForKey);
return consoleAccess;
},
);
+15 -11
View File
@@ -20,6 +20,19 @@ export type ModalFilters = {
team_roles?: string[];
};
export type UserGridSearchFilters = {
roles?: string[];
channel_roles?: string[];
team_roles?: string[];
};
export type ChannelListSearchFilters = {
public?: boolean;
private?: boolean;
deleted?: boolean;
team_ids?: string[];
};
export type AdminConsoleUserManagementTableProperties = {
sortColumn: string;
sortIsDescending: boolean;
@@ -141,21 +154,12 @@ export type ViewsState = {
modalFilters: ModalFilters;
userGridSearch: {
term: string;
filters: {
roles?: string[];
channel_roles?: string[];
team_roles?: string[];
};
filters: UserGridSearchFilters;
};
teamListSearch: string;
channelListSearch: {
term: string;
filters: {
public?: boolean;
private?: boolean;
deleted?: boolean;
team_ids?: string[];
};
filters: ChannelListSearchFilters;
};
};
+12 -3
View File
@@ -1247,7 +1247,16 @@ export default class Client4 {
);
};
authorizeOAuthApp = (responseType: string, clientId: string, redirectUri: string, state: string, scope: string, resource?: string, codeChallenge?: string, codeChallengeMethod?: string) => {
authorizeOAuthApp = (
responseType: string | null,
clientId: string | null,
redirectUri: string | null,
state: string | null,
scope: string | null,
resource?: string | null,
codeChallenge?: string | null,
codeChallengeMethod?: string | null,
) => {
const body: any = {client_id: clientId, response_type: responseType, redirect_uri: redirectUri, state, scope};
// Include resource parameter if provided
@@ -1263,7 +1272,7 @@ export default class Client4 {
body.code_challenge_method = codeChallengeMethod;
}
return this.doFetch<void>(
return this.doFetch<{redirect: string}>(
`${this.url}/oauth/authorize`,
{method: 'post', body: JSON.stringify(body)},
);
@@ -2856,7 +2865,7 @@ export default class Client4 {
// General Routes
ping = (getServerStatus: boolean, deviceId?: string) => {
ping = (getServerStatus?: boolean, deviceId?: string) => {
return this.doFetch<{
status: string;
ActiveSearchBackend: string;
+21 -10
View File
@@ -1,7 +1,8 @@
#!/bin/node
import * as fs from 'fs';
import langmap from '../channels/src/i18n/langmap.js';
import langmap from './langmap.mjs';
let lines = '';
const langIDs = [];
@@ -16,7 +17,7 @@ fs.readdirSync('./channels/src/i18n').forEach(file => {
if (langID === 'en') {
return
}
langIDs.push(langID);
lines += `import ${langID.replace('-', '')} from './${file}';\n`;
langFiles[langID] = langID.replace('-', '');
@@ -38,25 +39,35 @@ fs.readdirSync('./channels/src/i18n').forEach(file => {
});
lines += `
type TranslationsMap = {
[id: string]: string,
};
export const langIDs = ${JSON.stringify(langIDs)};
export const langLabels = ${JSON.stringify(langLabels)};
`;
lines += `\nexport const langIDs = ${JSON.stringify(langIDs)};\n`
lines += `\nexport const langLabels = ${JSON.stringify(langLabels)};\n`
// To generate the file exports we need to do a bit more work to handle ids with dashes and also output a map of literals rather than strings.
lines += '\nexport const langFiles: {[langID: string]: TranslationsMap} = {' + Object.keys(langFiles).reduce((out, id, idx) => {
lines += `
// TypeScript thinks it's importing these language files' contents directly, but Webpack rewrites the above imports
// to the URL the file for lazy loading. That's the reason for the ugly type assertions below.
export const langFiles: {
[langID: string]: string;
} = {
` + Object.keys(langFiles).reduce((out, id, idx) => {
out += ' ';
if (id.includes('-')) {
out += `'${id}':${langFiles[id]}`;
out += `'${id}': ${langFiles[id]}`;
} else {
out += `${langFiles[id]}`;
out += `${id}: ${langFiles[id]}`;
}
out += ' as unknown as string';
if (idx !== (langIDs.length - 1)) {
out += ',';
}
out += '\n';
return out;
}, '') + '};';
@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
module.exports = {
export default {
ach: {
nativeName: 'Lwo',
englishName: 'Acholi',