[MM-57418] Improvements to custom plugin config sections (#27831)

* Improve rendering of plugin custom admin sections

* Tests

* UX review

* Update translations

* Review logic
Этот коммит содержится в:
Claudio Costa
2024-08-14 16:36:25 +02:00
коммит произвёл GitHub
родитель ee3558079a
Коммит 061ff0fad6
4 изменённых файлов: 314 добавлений и 11 удалений

Просмотреть файл

@@ -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...",