From f34445a6f4946ad19c24064d23ebe196f76bc7cf Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Fri, 8 Mar 2024 21:21:23 +0530 Subject: [PATCH] MM-56625: Use patchConfig to update configs from system console (#26332) While updating config from admin console, the webapp would set any config param to null if the user doesn't have permission to edit that setting. This is common in cloud environments where a lot of config settings are set to `cloud_restrictable`. The problem due to that is since the client uses the updateConfig endpoint, this acts as a full config replace and therefore, the null fields get replaced by their default values. To fix this, we use the patch endpoint which only updates the fields that are actually set. https://mattermost.atlassian.net/browse/MM-56625 ```release-note Fix a bug where config cannot be updated from admin console in cloud environments. ``` Co-authored-by: Mattermost Build --- .../schema_admin_settings.test.tsx.snap | 2 +- .../admin_console/admin_console.tsx | 7 +++--- .../admin_console/admin_settings.tsx | 8 +++---- .../custom_plugin_settings.test.tsx | 6 ++--- .../custom_terms_of_service_settings.test.tsx | 2 +- .../custom_terms_of_service_settings.tsx | 4 ++-- .../data_retention_settings.test.tsx | 2 +- .../data_retention_settings.tsx | 4 ++-- .../global_policy_form.test.tsx | 2 +- .../global_policy_form/global_policy_form.tsx | 4 ++-- .../global_policy_form/index.ts | 4 ++-- .../data_retention_settings/index.ts | 4 ++-- .../src/components/admin_console/index.ts | 4 ++-- .../admin_console/openid_convert/index.ts | 4 ++-- .../openid_convert/openid_convert.test.tsx | 2 +- .../openid_convert/openid_convert.tsx | 5 +++-- .../edit_post_time_limit_modal.tsx | 5 +++-- .../edit_post_time_limit_modal/index.tsx | 4 ++-- .../schema_admin_settings.test.tsx | 14 ++++++------ .../admin_console/schema_admin_settings.tsx | 5 +++-- .../src/actions/admin.test.ts | 22 ++++++++++++++----- .../mattermost-redux/src/actions/admin.ts | 5 +++-- 22 files changed, 67 insertions(+), 52 deletions(-) diff --git a/webapp/channels/src/components/admin_console/__snapshots__/schema_admin_settings.test.tsx.snap b/webapp/channels/src/components/admin_console/__snapshots__/schema_admin_settings.test.tsx.snap index b22a9ba75a..a51c522f48 100644 --- a/webapp/channels/src/components/admin_console/__snapshots__/schema_admin_settings.test.tsx.snap +++ b/webapp/channels/src/components/admin_console/__snapshots__/schema_admin_settings.test.tsx.snap @@ -77,6 +77,7 @@ exports[`components/admin_console/SchemaAdminSettings should match snapshot with isCurrentUserSystemAdmin={false} isDisabled={false} license={Object {}} + patchConfig={[MockFunction]} roles={Object {}} schema={ Object { @@ -84,7 +85,6 @@ exports[`components/admin_console/SchemaAdminSettings should match snapshot with } } setNavigationBlocked={[MockFunction]} - updateConfig={[MockFunction]} /> `; diff --git a/webapp/channels/src/components/admin_console/admin_console.tsx b/webapp/channels/src/components/admin_console/admin_console.tsx index 49abfe768d..a080dad058 100644 --- a/webapp/channels/src/components/admin_console/admin_console.tsx +++ b/webapp/channels/src/components/admin_console/admin_console.tsx @@ -8,6 +8,7 @@ import type {RouteComponentProps} from 'react-router-dom'; import type {CloudState} from '@mattermost/types/cloud'; import type {AdminConfig, ClientLicense, EnvironmentConfig} from '@mattermost/types/config'; import type {Role} from '@mattermost/types/roles'; +import type {DeepPartial} from '@mattermost/types/utilities'; import type {ActionResult} from 'mattermost-redux/types/actions'; @@ -43,7 +44,7 @@ type ExtraProps = { setNavigationBlocked: (blocked: boolean) => void; roles: Record; editRole: (role: Role) => void; - updateConfig: (config: AdminConfig) => Promise; + patchConfig: (config: DeepPartial) => Promise; cloud: CloudState; isCurrentUserSystemAdmin: boolean; } @@ -173,7 +174,7 @@ class AdminConsole extends React.PureComponent { showNavigationPrompt, roles, } = this.props; - const {setNavigationBlocked, cancelNavigation, confirmNavigation, editRole, updateConfig} = this.props.actions; + const {setNavigationBlocked, cancelNavigation, confirmNavigation, editRole, patchConfig} = this.props.actions; if (!this.props.currentUserHasAnAdminRole) { return ( @@ -203,7 +204,7 @@ class AdminConsole extends React.PureComponent { setNavigationBlocked, roles, editRole, - updateConfig, + patchConfig, cloud: this.props.cloud, isCurrentUserSystemAdmin: this.props.isCurrentUserSystemAdmin, }; diff --git a/webapp/channels/src/components/admin_console/admin_settings.tsx b/webapp/channels/src/components/admin_console/admin_settings.tsx index cb35553f9b..ccdba2f255 100644 --- a/webapp/channels/src/components/admin_console/admin_settings.tsx +++ b/webapp/channels/src/components/admin_console/admin_settings.tsx @@ -19,7 +19,7 @@ export type BaseProps = { environmentConfig?: EnvironmentConfig; setNavigationBlocked?: (blocked: boolean) => void; isDisabled?: boolean; - updateConfig?: (config: AdminConfig) => {data: AdminConfig; error: ClientErrorPlaceholder}; + patchConfig?: (config: DeepPartial) => {data: AdminConfig; error: ClientErrorPlaceholder}; } export type BaseState = { @@ -31,7 +31,7 @@ export type BaseState = { } // Placeholder type until ClientError is exported from redux. -// TODO: remove ClientErrorPlaceholder and change the return type of updateConfig +// TODO: remove ClientErrorPlaceholder and change the return type of patchConfig type ClientErrorPlaceholder = { message: string; server_error_id: string; @@ -107,8 +107,8 @@ export default abstract class AdminSettings { {...baseProps} config={config} schema={{...plugin.settings_schema, id: plugin.id, name: plugin.name, settings}} - updateConfig={jest.fn()} + patchConfig={jest.fn()} />, ); expect(wrapper).toMatchSnapshot(); @@ -152,7 +152,7 @@ describe('components/admin_console/CustomPluginSettings', () => { id: 'testplugin', name: 'testplugin', }} - updateConfig={jest.fn()} + patchConfig={jest.fn()} />, ); expect(wrapper).toMatchSnapshot(); @@ -171,7 +171,7 @@ describe('components/admin_console/CustomPluginSettings', () => { } as PluginSettings, }} schema={{...plugin.settings_schema, id: plugin.id, name: plugin.name, settings}} - updateConfig={jest.fn()} + patchConfig={jest.fn()} />, ); expect(wrapper).toMatchSnapshot(); diff --git a/webapp/channels/src/components/admin_console/custom_terms_of_service_settings/custom_terms_of_service_settings.test.tsx b/webapp/channels/src/components/admin_console/custom_terms_of_service_settings/custom_terms_of_service_settings.test.tsx index 84cb48b369..3230b6f0a4 100644 --- a/webapp/channels/src/components/admin_console/custom_terms_of_service_settings/custom_terms_of_service_settings.test.tsx +++ b/webapp/channels/src/components/admin_console/custom_terms_of_service_settings/custom_terms_of_service_settings.test.tsx @@ -25,7 +25,7 @@ describe('components/admin_console/CustomTermsOfServiceSettings', () => { CustomTermsOfService: 'true', }, setNavigationBlocked: jest.fn(), - updateConfig: jest.fn(), + patchConfig: jest.fn(), }; test('should match snapshot', () => { diff --git a/webapp/channels/src/components/admin_console/custom_terms_of_service_settings/custom_terms_of_service_settings.tsx b/webapp/channels/src/components/admin_console/custom_terms_of_service_settings/custom_terms_of_service_settings.tsx index f1bcccaa9c..a59f98bdc0 100644 --- a/webapp/channels/src/components/admin_console/custom_terms_of_service_settings/custom_terms_of_service_settings.tsx +++ b/webapp/channels/src/components/admin_console/custom_terms_of_service_settings/custom_terms_of_service_settings.tsx @@ -31,7 +31,7 @@ type Props = BaseProps & { /* * Action to save config file */ - updateConfig: () => void; + patchConfig: () => void; }; type State = BaseState & { @@ -120,7 +120,7 @@ export default class CustomTermsOfServiceSettings extends AdminSettings Promise; getJobsByType: (job: JobType) => Promise; deleteDataRetentionCustomPolicy: (id: string) => Promise; - updateConfig: (config: AdminConfig) => Promise; + patchConfig: (config: DeepPartial) => Promise; }; } & WrappedComponentProps; @@ -437,7 +437,7 @@ class DataRetentionSettings extends React.PureComponent { const newConfig = JSON.parse(JSON.stringify(this.props.config)); newConfig.DataRetentionSettings.DeletionJobStartTime = value; - await this.props.actions.updateConfig(newConfig); + await this.props.actions.patchConfig(newConfig); this.inputRef.current?.blur(); }; diff --git a/webapp/channels/src/components/admin_console/data_retention_settings/global_policy_form/global_policy_form.test.tsx b/webapp/channels/src/components/admin_console/data_retention_settings/global_policy_form/global_policy_form.test.tsx index 51bd23ea8d..225eb6d3cc 100644 --- a/webapp/channels/src/components/admin_console/data_retention_settings/global_policy_form/global_policy_form.test.tsx +++ b/webapp/channels/src/components/admin_console/data_retention_settings/global_policy_form/global_policy_form.test.tsx @@ -21,7 +21,7 @@ describe('components/PluginManagement', () => { fileRetentionHours: '2400', environmentConfig: {}, actions: { - updateConfig: jest.fn(), + patchConfig: jest.fn(), setNavigationBlocked: jest.fn(), }, }; diff --git a/webapp/channels/src/components/admin_console/data_retention_settings/global_policy_form/global_policy_form.tsx b/webapp/channels/src/components/admin_console/data_retention_settings/global_policy_form/global_policy_form.tsx index 1658cd29bd..afe8e1b7ed 100644 --- a/webapp/channels/src/components/admin_console/data_retention_settings/global_policy_form/global_policy_form.tsx +++ b/webapp/channels/src/components/admin_console/data_retention_settings/global_policy_form/global_policy_form.tsx @@ -32,7 +32,7 @@ type Props = { fileRetentionHours: string | undefined; environmentConfig: Partial; actions: { - updateConfig: (config: AdminConfig) => Promise; + patchConfig: (config: DeepPartial) => Promise; setNavigationBlocked: (blocked: boolean) => void; }; }; @@ -121,7 +121,7 @@ export default class GlobalPolicyForm extends React.PureComponent newConfig.DataRetentionSettings.FileRetentionHours = this.setRetentionHours(fileRetentionDropdownValue.value, fileRetentionInputValue); } - const {error} = await this.props.actions.updateConfig(newConfig); + const {error} = await this.props.actions.patchConfig(newConfig); if (error) { this.setState({serverError: error.message, saving: false}); diff --git a/webapp/channels/src/components/admin_console/data_retention_settings/global_policy_form/index.ts b/webapp/channels/src/components/admin_console/data_retention_settings/global_policy_form/index.ts index 89d8670a8b..24e528ba6f 100644 --- a/webapp/channels/src/components/admin_console/data_retention_settings/global_policy_form/index.ts +++ b/webapp/channels/src/components/admin_console/data_retention_settings/global_policy_form/index.ts @@ -6,7 +6,7 @@ import {bindActionCreators} from 'redux'; import type {Dispatch} from 'redux'; import { - updateConfig, + patchConfig, } from 'mattermost-redux/actions/admin'; import {getEnvironmentConfig} from 'mattermost-redux/selectors/entities/admin'; import {getConfig} from 'mattermost-redux/selectors/entities/general'; @@ -31,7 +31,7 @@ function mapStateToProps(state: GlobalState) { function mapDispatchToProps(dispatch: Dispatch) { return { actions: bindActionCreators({ - updateConfig, + patchConfig, setNavigationBlocked, }, dispatch), }; diff --git a/webapp/channels/src/components/admin_console/data_retention_settings/index.ts b/webapp/channels/src/components/admin_console/data_retention_settings/index.ts index aa3e990f01..1c77fb7ef0 100644 --- a/webapp/channels/src/components/admin_console/data_retention_settings/index.ts +++ b/webapp/channels/src/components/admin_console/data_retention_settings/index.ts @@ -5,7 +5,7 @@ import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; import type {Dispatch} from 'redux'; -import {getDataRetentionCustomPolicies as fetchDataRetentionCustomPolicies, deleteDataRetentionCustomPolicy, updateConfig} from 'mattermost-redux/actions/admin'; +import {getDataRetentionCustomPolicies as fetchDataRetentionCustomPolicies, deleteDataRetentionCustomPolicy, patchConfig} from 'mattermost-redux/actions/admin'; import {createJob, getJobsByType} from 'mattermost-redux/actions/jobs'; import {getDataRetentionCustomPolicies, getDataRetentionCustomPoliciesCount} from 'mattermost-redux/selectors/entities/admin'; import {getConfig} from 'mattermost-redux/selectors/entities/general'; @@ -35,7 +35,7 @@ function mapDispatchToProps(dispatch: Dispatch) { createJob, getJobsByType, deleteDataRetentionCustomPolicy, - updateConfig, + patchConfig, }, dispatch), }; } diff --git a/webapp/channels/src/components/admin_console/index.ts b/webapp/channels/src/components/admin_console/index.ts index 1924478042..2b5b7b0298 100644 --- a/webapp/channels/src/components/admin_console/index.ts +++ b/webapp/channels/src/components/admin_console/index.ts @@ -6,7 +6,7 @@ import type {ConnectedProps} from 'react-redux'; import {bindActionCreators} from 'redux'; import type {Dispatch} from 'redux'; -import {getConfig, getEnvironmentConfig, updateConfig} from 'mattermost-redux/actions/admin'; +import {getConfig, getEnvironmentConfig, patchConfig} from 'mattermost-redux/actions/admin'; import {loadRolesIfNeeded, editRole} from 'mattermost-redux/actions/roles'; import {selectTeam} from 'mattermost-redux/actions/teams'; import {General} from 'mattermost-redux/constants'; @@ -60,7 +60,7 @@ function mapDispatchToProps(dispatch: Dispatch) { actions: bindActionCreators({ getConfig, getEnvironmentConfig, - updateConfig, + patchConfig, setNavigationBlocked, deferNavigation, cancelNavigation, diff --git a/webapp/channels/src/components/admin_console/openid_convert/index.ts b/webapp/channels/src/components/admin_console/openid_convert/index.ts index 67abedc589..c3e9572970 100644 --- a/webapp/channels/src/components/admin_console/openid_convert/index.ts +++ b/webapp/channels/src/components/admin_console/openid_convert/index.ts @@ -5,14 +5,14 @@ import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; import type {Dispatch} from 'redux'; -import {updateConfig} from 'mattermost-redux/actions/admin'; +import {patchConfig} from 'mattermost-redux/actions/admin'; import OpenIdConvert from './openid_convert'; function mapDispatchToProps(dispatch: Dispatch) { return { actions: bindActionCreators({ - updateConfig, + patchConfig, }, dispatch), }; } diff --git a/webapp/channels/src/components/admin_console/openid_convert/openid_convert.test.tsx b/webapp/channels/src/components/admin_console/openid_convert/openid_convert.test.tsx index 91f2ba3258..8a6c9402d8 100644 --- a/webapp/channels/src/components/admin_console/openid_convert/openid_convert.test.tsx +++ b/webapp/channels/src/components/admin_console/openid_convert/openid_convert.test.tsx @@ -9,7 +9,7 @@ import OpenIdConvert from 'components/admin_console/openid_convert/openid_conver describe('components/OpenIdConvert', () => { const baseProps = { actions: { - updateConfig: jest.fn(), + patchConfig: jest.fn(), }, }; diff --git a/webapp/channels/src/components/admin_console/openid_convert/openid_convert.tsx b/webapp/channels/src/components/admin_console/openid_convert/openid_convert.tsx index 95dfc332ed..7f461e85a5 100644 --- a/webapp/channels/src/components/admin_console/openid_convert/openid_convert.tsx +++ b/webapp/channels/src/components/admin_console/openid_convert/openid_convert.tsx @@ -5,6 +5,7 @@ import React from 'react'; import {FormattedMessage} from 'react-intl'; import type {AdminConfig} from '@mattermost/types/config'; +import type {DeepPartial} from '@mattermost/types/utilities'; import type {ActionResult} from 'mattermost-redux/types/actions'; @@ -21,7 +22,7 @@ import './openid_convert.scss'; type Props = BaseProps & { disabled?: boolean; actions: { - updateConfig: (config: AdminConfig) => Promise; + patchConfig: (config: DeepPartial) => Promise; }; }; type State = { @@ -59,7 +60,7 @@ export default class OpenIdConvert extends React.PureComponent { newConfig[setting].TokenEndpoint = ''; }); - const {error: err} = await this.props.actions.updateConfig(newConfig); + const {error: err} = await this.props.actions.patchConfig(newConfig); if (err) { this.setState({serverError: err.message}); } else { diff --git a/webapp/channels/src/components/admin_console/permission_schemes_settings/edit_post_time_limit_modal/edit_post_time_limit_modal.tsx b/webapp/channels/src/components/admin_console/permission_schemes_settings/edit_post_time_limit_modal/edit_post_time_limit_modal.tsx index bb3d2acb26..7e14696773 100644 --- a/webapp/channels/src/components/admin_console/permission_schemes_settings/edit_post_time_limit_modal/edit_post_time_limit_modal.tsx +++ b/webapp/channels/src/components/admin_console/permission_schemes_settings/edit_post_time_limit_modal/edit_post_time_limit_modal.tsx @@ -6,6 +6,7 @@ import {Modal} from 'react-bootstrap'; import {FormattedMessage} from 'react-intl'; import type {AdminConfig} from '@mattermost/types/config'; +import type {DeepPartial} from '@mattermost/types/utilities'; import type {ActionResult} from 'mattermost-redux/types/actions'; @@ -22,7 +23,7 @@ type Props ={ show: boolean; onClose: () => void; actions: { - updateConfig: (config: AdminConfig) => Promise; + patchConfig: (config: DeepPartial) => Promise; }; } @@ -48,7 +49,7 @@ export default function EditPostTimeLimitModal(props: Props) { const newConfig = JSON.parse(JSON.stringify(props.config)); newConfig.ServiceSettings.PostEditTimeLimit = alwaysAllowPostEditing ? Constants.UNSET_POST_EDIT_TIME_LIMIT : postEditTimeLimit; - const {error} = await props.actions.updateConfig(newConfig); + const {error} = await props.actions.patchConfig(newConfig); if (error) { setErrorMessage(error.message); setSaving(false); diff --git a/webapp/channels/src/components/admin_console/permission_schemes_settings/edit_post_time_limit_modal/index.tsx b/webapp/channels/src/components/admin_console/permission_schemes_settings/edit_post_time_limit_modal/index.tsx index 054a8b1ba8..9c80e36bd1 100644 --- a/webapp/channels/src/components/admin_console/permission_schemes_settings/edit_post_time_limit_modal/index.tsx +++ b/webapp/channels/src/components/admin_console/permission_schemes_settings/edit_post_time_limit_modal/index.tsx @@ -5,7 +5,7 @@ import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; import type {Dispatch} from 'redux'; -import {updateConfig} from 'mattermost-redux/actions/admin'; +import {patchConfig} from 'mattermost-redux/actions/admin'; import {getConfig} from 'mattermost-redux/selectors/entities/admin'; import type {GlobalState} from 'types/store'; @@ -20,7 +20,7 @@ function mapStateToProps(state: GlobalState) { function mapDispatchToProps(dispatch: Dispatch) { return { - actions: bindActionCreators({updateConfig}, dispatch), + actions: bindActionCreators({patchConfig}, dispatch), }; } diff --git a/webapp/channels/src/components/admin_console/schema_admin_settings.test.tsx b/webapp/channels/src/components/admin_console/schema_admin_settings.test.tsx index 1f6f4fd482..7fef7bc3a5 100644 --- a/webapp/channels/src/components/admin_console/schema_admin_settings.test.tsx +++ b/webapp/channels/src/components/admin_console/schema_admin_settings.test.tsx @@ -256,7 +256,7 @@ describe('components/admin_console/SchemaAdminSettings', () => { config={config} environmentConfig={environmentConfig} schema={{...schema} as AdminDefinitionSubSectionSchema} - updateConfig={jest.fn()} + patchConfig={jest.fn()} />, ); expect(wrapper).toMatchSnapshot(); @@ -269,7 +269,7 @@ describe('components/admin_console/SchemaAdminSettings', () => { config={config} environmentConfig={environmentConfig} schema={{component: () =>

{'Test'}

} as AdminDefinitionSubSectionSchema} - updateConfig={jest.fn()} + patchConfig={jest.fn()} />, ); expect(wrapper).toMatchSnapshot(); @@ -285,7 +285,7 @@ describe('components/admin_console/SchemaAdminSettings', () => { ...schema, header: headerText, } as AdminDefinitionSubSectionSchema, - updateConfig: jest.fn(), + patchConfig: jest.fn(), }; const wrapper = shallowWithIntl(); @@ -308,7 +308,7 @@ describe('components/admin_console/SchemaAdminSettings', () => { ...schema, footer: footerText, } as AdminDefinitionSubSectionSchema, - updateConfig: jest.fn(), + patchConfig: jest.fn(), }; const wrapper = shallowWithIntl(); @@ -327,7 +327,7 @@ describe('components/admin_console/SchemaAdminSettings', () => { config, environmentConfig, schema: null, - updateConfig: jest.fn(), + patchConfig: jest.fn(), }; const wrapper = shallowWithIntl(); @@ -360,7 +360,7 @@ describe('components/admin_console/SchemaAdminSettings', () => { id: '', environmentConfig, schema: localSchema, - updateConfig: jest.fn(), + patchConfig: jest.fn(), }; const wrapper = shallowWithIntl(); @@ -389,7 +389,7 @@ describe('components/admin_console/SchemaAdminSettings', () => { config, environmentConfig, schema: localSchema, - updateConfig: jest.fn(), + patchConfig: jest.fn(), }; const wrapper = shallowWithIntl(); diff --git a/webapp/channels/src/components/admin_console/schema_admin_settings.tsx b/webapp/channels/src/components/admin_console/schema_admin_settings.tsx index 8349c92236..1d5fa4aed2 100644 --- a/webapp/channels/src/components/admin_console/schema_admin_settings.tsx +++ b/webapp/channels/src/components/admin_console/schema_admin_settings.tsx @@ -10,6 +10,7 @@ import {Link} from 'react-router-dom'; import type {CloudState} from '@mattermost/types/cloud'; import type {AdminConfig, ClientLicense, EnvironmentConfig} from '@mattermost/types/config'; import type {Role} from '@mattermost/types/roles'; +import type {DeepPartial} from '@mattermost/types/utilities'; import type {ActionResult} from 'mattermost-redux/types/actions'; @@ -53,7 +54,7 @@ type Props = { roles: Record; license: ClientLicense; editRole: (role: Role) => void; - updateConfig: (config: AdminConfig) => Promise; + patchConfig: (config: DeepPartial) => Promise; isDisabled: boolean; consoleAccess: ConsoleAccess; cloud: CloudState; @@ -1145,7 +1146,7 @@ export class SchemaAdminSettings extends React.PureComponent { let config = JSON.parse(JSON.stringify(this.props.config)); config = this.getConfigFromState(config); - const {error} = await this.props.updateConfig(config); + const {error} = await this.props.patchConfig(config); if (error) { this.setState({ serverError: error.message, diff --git a/webapp/channels/src/packages/mattermost-redux/src/actions/admin.test.ts b/webapp/channels/src/packages/mattermost-redux/src/actions/admin.test.ts index 03ed66b6e4..9919e4c803 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/actions/admin.test.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/actions/admin.test.ts @@ -112,26 +112,35 @@ describe('Actions.Admin', () => { expect(config.TeamSettings.SiteName === 'Mattermost').toBeTruthy(); }); - it('updateConfig', async () => { + it('patchConfig', async () => { nock(Client4.getBaseRoute()). get('/config'). reply(200, { TeamSettings: { SiteName: 'Mattermost', + TeammateNameDisplay: 'username', }, }); const {data} = await store.dispatch(Actions.getConfig()); const updated = JSON.parse(JSON.stringify(data)); + + // Creating a copy. + const reply = JSON.parse(JSON.stringify(data)); const oldSiteName = updated.TeamSettings.SiteName; + const oldNameDisplay = updated.TeamSettings.TeammateNameDisplay; const testSiteName = 'MattermostReduxTest'; updated.TeamSettings.SiteName = testSiteName; + reply.TeamSettings.SiteName = testSiteName; + + // Testing partial config patch. + updated.TeamSettings.TeammateNameDisplay = null; nock(Client4.getBaseRoute()). - put('/config'). - reply(200, updated); + put('/config/patch'). + reply(200, reply); - await store.dispatch(Actions.updateConfig(updated)); + await store.dispatch(Actions.patchConfig(updated)); let state = store.getState(); @@ -139,14 +148,15 @@ describe('Actions.Admin', () => { expect(config).toBeTruthy(); expect(config.TeamSettings).toBeTruthy(); expect(config.TeamSettings.SiteName === testSiteName).toBeTruthy(); + expect(config.TeamSettings.TeammateNameDisplay === oldNameDisplay).toBeTruthy(); updated.TeamSettings.SiteName = oldSiteName; nock(Client4.getBaseRoute()). - put('/config'). + put('/config/patch'). reply(200, updated); - await store.dispatch(Actions.updateConfig(updated)); + await store.dispatch(Actions.patchConfig(updated)); state = store.getState(); diff --git a/webapp/channels/src/packages/mattermost-redux/src/actions/admin.ts b/webapp/channels/src/packages/mattermost-redux/src/actions/admin.ts index a74478359f..6fd97bf18b 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/actions/admin.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/actions/admin.ts @@ -23,6 +23,7 @@ import type { Team, TeamSearchOpts, } from '@mattermost/types/teams'; +import type {DeepPartial} from '@mattermost/types/utilities'; import {AdminTypes} from 'mattermost-redux/action_types'; import {getUsersLimits} from 'mattermost-redux/actions/limits'; @@ -79,9 +80,9 @@ export function getConfig() { }); } -export function updateConfig(config: AdminConfig) { +export function patchConfig(config: DeepPartial) { return bindClientFunc({ - clientFunc: Client4.updateConfig, + clientFunc: Client4.patchConfig, onSuccess: [AdminTypes.RECEIVED_CONFIG], params: [ config,