[MM-57418] Improvements to custom plugin config sections (#27831)
* Improve rendering of plugin custom admin sections * Tests * UX review * Update translations * Review logic
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
ee3558079a
Коммит
061ff0fad6
@@ -0,0 +1,268 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import type {match} from 'react-router-dom';
|
||||
|
||||
import type {CloudState} from '@mattermost/types/cloud';
|
||||
import type {PluginSettings} from '@mattermost/types/config';
|
||||
|
||||
import CustomPluginSettings from 'components/admin_console/custom_plugin_settings';
|
||||
|
||||
import {screen, renderWithContext} from 'tests/react_testing_utils';
|
||||
|
||||
describe('custom plugin sections and settings', () => {
|
||||
const plugin = {
|
||||
id: 'testplugin',
|
||||
name: 'testplugin',
|
||||
description: '',
|
||||
version: '',
|
||||
active: true,
|
||||
webapp: {
|
||||
bundle_path: '/static/testplugin_bundle.js',
|
||||
},
|
||||
settings_schema: {
|
||||
header: 'This is the header',
|
||||
footer: 'This is the footer',
|
||||
settings: [],
|
||||
sections: [],
|
||||
},
|
||||
};
|
||||
|
||||
const baseProps = {
|
||||
isDisabled: false,
|
||||
environmentConfig: {},
|
||||
setNavigationBlocked: jest.fn(),
|
||||
roles: {},
|
||||
cloud: {} as CloudState,
|
||||
license: {},
|
||||
editRole: jest.fn(),
|
||||
isCurrentUserSystemAdmin: false,
|
||||
enterpriseReady: false,
|
||||
match: {params: {plugin_id: 'testplugin'}} as match<{ plugin_id: string }>,
|
||||
config: {
|
||||
PluginSettings: {
|
||||
Plugins: {
|
||||
testplugin: {
|
||||
},
|
||||
},
|
||||
} as unknown as PluginSettings,
|
||||
},
|
||||
consoleAccess: {
|
||||
read: {
|
||||
about: true,
|
||||
reporting: true,
|
||||
environment: true,
|
||||
site_configuration: true,
|
||||
authentication: true,
|
||||
plugins: true,
|
||||
integrations: true,
|
||||
compliance: true,
|
||||
},
|
||||
write: {
|
||||
about: true,
|
||||
reporting: true,
|
||||
environment: true,
|
||||
site_configuration: true,
|
||||
authentication: true,
|
||||
plugins: true,
|
||||
integrations: true,
|
||||
compliance: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const baseState = {
|
||||
entities: {
|
||||
admin: {
|
||||
plugins: {
|
||||
testplugin: plugin,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
it('empty sections and settings', () => {
|
||||
renderWithContext(
|
||||
<CustomPluginSettings
|
||||
{...baseProps}
|
||||
patchConfig={jest.fn()}
|
||||
/>
|
||||
, {...baseState});
|
||||
|
||||
expect(screen.getByText('testplugin')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('PluginSettings.PluginStates.testplugin.Enable')).toBeInTheDocument();
|
||||
expect(screen.getByText('This is the header')).toBeInTheDocument();
|
||||
expect(screen.getByText('This is the footer')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('all custom sections with plugin disabled should show single warning', () => {
|
||||
const state = {
|
||||
...baseState,
|
||||
entities: {
|
||||
admin: {
|
||||
plugins: {
|
||||
testplugin: {
|
||||
...plugin,
|
||||
settings_schema: {
|
||||
...plugin.settings_schema,
|
||||
sections: [
|
||||
{
|
||||
key: 'section1',
|
||||
title: 'Custom Section 1',
|
||||
settings: [
|
||||
{
|
||||
key: 'customsection1numbersetting',
|
||||
label: 'Custom Section Number Setting',
|
||||
type: 'number' as const,
|
||||
help_text: 'Custom Section Number Setting Help Text',
|
||||
},
|
||||
],
|
||||
custom: true,
|
||||
},
|
||||
{
|
||||
key: 'section2',
|
||||
title: 'Custom Section 2',
|
||||
settings: [
|
||||
{
|
||||
key: 'customsection2numbersetting',
|
||||
label: 'Custom Section Number Setting',
|
||||
type: 'number' as const,
|
||||
help_text: 'Custom Section Number Setting Help Text',
|
||||
},
|
||||
],
|
||||
custom: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const props = {
|
||||
...baseProps,
|
||||
config: {
|
||||
...baseProps.config,
|
||||
PluginStates: {
|
||||
testplugin: {
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
renderWithContext(
|
||||
<CustomPluginSettings
|
||||
{...props}
|
||||
patchConfig={jest.fn()}
|
||||
/>
|
||||
, {...state});
|
||||
|
||||
expect(screen.getByText('testplugin')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('PluginSettings.PluginStates.testplugin.Enable')).toBeInTheDocument();
|
||||
expect(screen.getByText('In order to view and configure plugin settings, enable the plugin and click Save.')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Custom Section 1')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Custom Section 2')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('custom sections with plugin enabled should render as expected', () => {
|
||||
const CustomSection1 = () => {
|
||||
return (
|
||||
<div>{'Custom Component Section 1'}</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomSection2 = () => {
|
||||
return (
|
||||
<div>{'Custom Component Section 2'}</div>
|
||||
);
|
||||
};
|
||||
|
||||
const state = {
|
||||
...baseState,
|
||||
entities: {
|
||||
admin: {
|
||||
plugins: {
|
||||
testplugin: {
|
||||
...plugin,
|
||||
settings_schema: {
|
||||
...plugin.settings_schema,
|
||||
sections: [
|
||||
{
|
||||
key: 'section1',
|
||||
title: 'Custom Section 1',
|
||||
settings: [
|
||||
{
|
||||
key: 'customsection1numbersetting',
|
||||
label: 'Custom Section Number Setting',
|
||||
type: 'number' as const,
|
||||
help_text: 'Custom Section Number Setting Help Text',
|
||||
},
|
||||
],
|
||||
custom: true,
|
||||
},
|
||||
{
|
||||
key: 'section2',
|
||||
title: 'Custom Section 2',
|
||||
settings: [
|
||||
{
|
||||
key: 'customsection2numbersetting',
|
||||
label: 'Custom Section Number Setting',
|
||||
type: 'number' as const,
|
||||
help_text: 'Custom Section Number Setting Help Text',
|
||||
},
|
||||
],
|
||||
custom: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
adminConsoleCustomSections: {
|
||||
testplugin: {
|
||||
section1: {
|
||||
pluginId: 'testplugin',
|
||||
key: 'section1',
|
||||
component: CustomSection1 as unknown as React.Component,
|
||||
},
|
||||
section2: {
|
||||
pluginId: 'testplugin',
|
||||
key: 'section2',
|
||||
component: CustomSection2 as unknown as React.Component,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const props = {
|
||||
...baseProps,
|
||||
config: {
|
||||
...baseProps.config,
|
||||
PluginStates: {
|
||||
testplugin: {
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
renderWithContext(
|
||||
<CustomPluginSettings
|
||||
{...props}
|
||||
patchConfig={jest.fn()}
|
||||
/>
|
||||
, {...state});
|
||||
|
||||
expect(screen.getByText('testplugin')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('PluginSettings.PluginStates.testplugin.Enable')).toBeInTheDocument();
|
||||
expect(screen.queryByText('In order to view and configure plugin settings, enable the plugin and click Save.')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Custom Component Section 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Custom Component Section 2')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -94,9 +94,11 @@ function makeGetPluginSchema() {
|
||||
if (section.custom) {
|
||||
if (customSections[key]) {
|
||||
component = customSections[key]?.component;
|
||||
settings = parsePluginSettings(section.settings);
|
||||
} else {
|
||||
// Show warning banner for custom sections when the plugin is disabled.
|
||||
settings = [{
|
||||
key: key + 'disabledWarning',
|
||||
type: Constants.SettingsTypes.TYPE_BANNER,
|
||||
label: defineMessage({
|
||||
id: 'admin.plugin.customSection.pluginDisabledWarning',
|
||||
@@ -105,9 +107,7 @@ function makeGetPluginSchema() {
|
||||
banner_type: 'warning',
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.length === 0) {
|
||||
} else {
|
||||
settings = parsePluginSettings(section.settings);
|
||||
}
|
||||
|
||||
@@ -132,16 +132,33 @@ function makeGetPluginSchema() {
|
||||
}
|
||||
|
||||
if (plugin.id !== appsPluginID || appsFeatureFlagIsEnabled) {
|
||||
const pluginEnableSetting = getEnablePluginSetting(plugin);
|
||||
if (pluginEnableSetting.isDisabled) {
|
||||
pluginEnableSetting.isDisabled = it.any(pluginEnableSetting.isDisabled, it.not(it.userHasWritePermissionOnResource('plugins')));
|
||||
} else {
|
||||
pluginEnableSetting.isDisabled = it.not(it.userHasWritePermissionOnResource('plugins'));
|
||||
}
|
||||
const pluginEnableSetting = getEnablePluginSetting(plugin) as AdminDefinitionSetting;
|
||||
|
||||
if (sections.length > 0) {
|
||||
sections[0].settings.unshift(pluginEnableSetting as AdminDefinitionSetting);
|
||||
if (plugin.settings_schema && plugin.settings_schema.sections?.every((s) => s.custom && !customSections[s.key.toLowerCase()])) {
|
||||
// If the plugin is composed of purely custom sections (e.g. Calls) and it's disabled (custom components are not found), we show a single warning.
|
||||
const warningBanner = {
|
||||
key: 'admin.plugin.customSections.pluginDisabledWarning',
|
||||
type: Constants.SettingsTypes.TYPE_BANNER,
|
||||
label: defineMessage({id: 'admin.plugin.customSections.pluginDisabledWarning', defaultMessage: 'In order to view and configure plugin settings, enable the plugin and click Save.'}),
|
||||
banner_type: 'warning' as const,
|
||||
};
|
||||
|
||||
sections = [{
|
||||
key: pluginEnabledConfigKey + '.Section',
|
||||
header: plugin.settings_schema?.header,
|
||||
footer: plugin.settings_schema?.footer,
|
||||
settings: [pluginEnableSetting, warningBanner],
|
||||
}];
|
||||
} else if (sections.length > 0) {
|
||||
// Have a separate section on top with the plugin enable/disable setting.
|
||||
sections.unshift({
|
||||
key: pluginEnabledConfigKey + '.Section',
|
||||
header: plugin.settings_schema?.header,
|
||||
footer: plugin.settings_schema?.footer,
|
||||
settings: [pluginEnableSetting],
|
||||
});
|
||||
} else {
|
||||
// Otherwise we retain existing behaviour and add the setting in front.
|
||||
settings.unshift(pluginEnableSetting);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1108,6 +1108,23 @@ export class SchemaAdminSettings extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
// This is a bit of special case since designs for plugin config expect the Enable/Disable setting
|
||||
// to be on top and out of the sections.
|
||||
if (section.key.startsWith('PluginSettings.PluginStates') && section.key.endsWith('Enable.Section')) {
|
||||
sections.push(
|
||||
<SettingsGroup
|
||||
container={false}
|
||||
key={section.key}
|
||||
>
|
||||
{header}
|
||||
{settingsList}
|
||||
{footer}
|
||||
</SettingsGroup>,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sections.push(
|
||||
<div
|
||||
className={'config-section'}
|
||||
|
||||
@@ -1918,6 +1918,7 @@
|
||||
"admin.plugin.choose": "Choose File",
|
||||
"admin.plugin.cluster_instance": "Cluster Instance",
|
||||
"admin.plugin.customSection.pluginDisabledWarning": "In order to view this section, enable the plugin and click Save.",
|
||||
"admin.plugin.customSections.pluginDisabledWarning": "In order to view and configure plugin settings, enable the plugin and click Save.",
|
||||
"admin.plugin.customSetting.pluginDisabledWarning": "In order to view this setting, enable the plugin and click Save.",
|
||||
"admin.plugin.disable": "Disable",
|
||||
"admin.plugin.disabling": "Disabling...",
|
||||
|
||||
Ссылка в новой задаче
Block a user