Update settings api and documentation

Signed-off-by: soupette <cyril.lpz@gmail.com>
This commit is contained in:
soupette 2020-06-09 15:01:35 +02:00 committed by Alexandre Bodin
parent acb43e5aa9
commit 904eb3ab7a
11 changed files with 528 additions and 176 deletions

View File

@ -42,6 +42,7 @@ export default strapi => {
title: 'Setting page 1',
to: `${strapi.settingsBaseURL}/${pluginId}/setting1`,
name: 'setting1',
permissions: [{ action: 'plugins::my-plugin.action-name', subject: null }], // This key is required
},
{
// Using i18n with a corresponding translation key
@ -51,6 +52,7 @@ export default strapi => {
},
to: `${strapi.settingsBaseURL}/${pluginId}/setting2`,
name: 'setting2',
permissions: [{ action: 'plugins::my-plugin.action-name', subject: null }], // This key is required
},
],
};
@ -147,6 +149,7 @@ export default strapi => {
title: 'Setting page 1',
to: `${strapi.settingsBaseURL}/${pluginId}/setting1`,
name: 'setting1',
permissions: [{ action: 'plugins::my-plugin.action-name', subject: null }], // This key is required
},
{
title: {
@ -155,6 +158,7 @@ export default strapi => {
},
to: `${strapi.settingsBaseURL}/${pluginId}/setting2`,
name: 'setting2',
permissions: [{ action: 'plugins::my-plugin.action-name', subject: null }], // This key is required
},
],
};
@ -240,16 +244,19 @@ export default strapi => {
preventComponentRendering: false,
settings: {
// Add a link into the global section of the settings view
global: [
{
title: 'Setting link 1',
to: `${strapi.settingsBaseURL}/setting-link-1`,
name: 'settingLink1',
Component: SettingLink,
// Bool : https://reacttraining.com/react-router/web/api/Route/exact-bool
exact: false,
},
],
global: {
links: [
{
title: 'Setting link 1',
to: `${strapi.settingsBaseURL}/setting-link-1`,
name: 'settingLink1',
Component: SettingLink,
// Bool : https://reacttraining.com/react-router/web/api/Route/exact-bool
exact: false,
permissions: [{ action: 'plugins::my-plugin.action-name', subject: null }], // This key is required
},
],
},
mainComponent: Settings,
menuSection,
},

View File

@ -1,22 +1,6 @@
import { get, omit, set, sortBy } from 'lodash';
import { get, omit, set } from 'lodash';
import { SETTINGS_BASE_URL } from '../../config';
const getPluginsSettingsPermissions = plugins =>
Object.values(plugins).reduce((acc, current) => {
const pluginSettings = get(current, 'settings.global', []);
pluginSettings.forEach(setting => {
const permissions = get(setting, 'permissions', []);
permissions.forEach(permission => {
acc.push(permission);
});
});
return acc;
}, []);
const sortLinks = links => sortBy(links, object => object.name);
import { getPluginsSettingsPermissions, sortLinks } from './utils';
const init = (initialState, plugins = {}) => {
// For each plugin retrieve the permissions associated to each injected link
@ -50,4 +34,3 @@ const init = (initialState, plugins = {}) => {
};
export default init;
export { sortLinks };

View File

@ -1,140 +1,264 @@
import init, { sortLinks } from '../init';
import { SETTINGS_BASE_URL } from '../../../config';
import init from '../init';
describe('ADMIN | LeftMenu | init', () => {
describe('init', () => {
it('should return the initialState if the plugins are empty', () => {
const initialState = {
ok: true,
generalSectionLinks: [],
};
it('should return the initialState if the plugins are empty', () => {
const initialState = {
ok: true,
generalSectionLinks: [],
};
expect(init(initialState)).toEqual({ ok: true, generalSectionLinks: [] });
});
it('should create the pluginsSectionLinks correctly', () => {
const plugins = {
documentation: {
menu: {
pluginsSectionLinks: [
{
destination: '/plugins/documentation',
icon: 'doc',
label: {
id: 'documentation.plugin.name',
defaultMessage: 'Documentation',
},
name: 'documentation',
permissions: [{ action: 'plugins::documentation.read', subject: null }],
},
{
destination: '/plugins/documentation/test',
icon: 'doc',
label: {
id: 'documentation.plugin.name.test',
defaultMessage: 'Documentation Test',
},
name: 'documentation test',
permissions: [],
},
],
},
},
test: {},
'content-type-builder': {
menu: {
pluginsSectionLinks: [
{
destination: '/plugins/content-type-builder',
icon: 'plug',
label: {
id: 'content-type-builder.plugin.name',
defaultMessage: 'content-type-builder',
},
name: 'content-type-builder',
permissions: [{ action: 'plugins::content-type-builder.read', subject: null }],
},
],
},
},
};
const initialState = {
generalSectionLinks: [],
pluginsSectionLinks: [],
isLoading: true,
};
const expected = {
generalSectionLinks: [],
pluginsSectionLinks: [
{
destination: '/plugins/content-type-builder',
icon: 'plug',
label: {
id: 'content-type-builder.plugin.name',
defaultMessage: 'content-type-builder',
},
isDisplayed: false,
permissions: [{ action: 'plugins::content-type-builder.read', subject: null }],
},
{
destination: '/plugins/documentation',
icon: 'doc',
label: {
id: 'documentation.plugin.name',
defaultMessage: 'Documentation',
},
isDisplayed: false,
permissions: [{ action: 'plugins::documentation.read', subject: null }],
},
{
destination: '/plugins/documentation/test',
icon: 'doc',
label: {
id: 'documentation.plugin.name.test',
defaultMessage: 'Documentation Test',
},
isDisplayed: false,
permissions: [],
},
],
isLoading: true,
};
expect(init(initialState, plugins)).toEqual(expected);
});
expect(init(initialState)).toEqual({ ok: true, generalSectionLinks: [] });
});
describe('sortLinks', () => {
it('should return an empty array', () => {
expect(sortLinks([])).toEqual([]);
});
it('should create the pluginsSectionLinks correctly', () => {
const plugins = {
documentation: {
menu: {
pluginsSectionLinks: [
{
destination: '/plugins/documentation',
icon: 'doc',
label: {
id: 'documentation.plugin.name',
defaultMessage: 'Documentation',
},
name: 'documentation',
permissions: [{ action: 'plugins::documentation.read', subject: null }],
},
{
destination: '/plugins/documentation/test',
icon: 'doc',
label: {
id: 'documentation.plugin.name.test',
defaultMessage: 'Documentation Test',
},
name: 'documentation test',
permissions: [],
},
],
},
},
test: {},
'content-type-builder': {
menu: {
pluginsSectionLinks: [
{
destination: '/plugins/content-type-builder',
icon: 'plug',
label: {
id: 'content-type-builder.plugin.name',
defaultMessage: 'content-type-builder',
},
name: 'content-type-builder',
permissions: [{ action: 'plugins::content-type-builder.read', subject: null }],
},
],
},
},
};
const initialState = {
generalSectionLinks: [],
pluginsSectionLinks: [],
isLoading: true,
};
const expected = {
generalSectionLinks: [],
pluginsSectionLinks: [
{
destination: '/plugins/content-type-builder',
icon: 'plug',
label: {
id: 'content-type-builder.plugin.name',
defaultMessage: 'content-type-builder',
},
isDisplayed: false,
permissions: [{ action: 'plugins::content-type-builder.read', subject: null }],
},
{
destination: '/plugins/documentation',
icon: 'doc',
label: {
id: 'documentation.plugin.name',
defaultMessage: 'Documentation',
},
isDisplayed: false,
permissions: [{ action: 'plugins::documentation.read', subject: null }],
},
{
destination: '/plugins/documentation/test',
icon: 'doc',
label: {
id: 'documentation.plugin.name.test',
defaultMessage: 'Documentation Test',
},
isDisplayed: false,
permissions: [],
},
],
isLoading: true,
};
it('should return a sorted array', () => {
const data = [
{
name: 'un',
},
{ name: 'deux' },
{ name: 'un-un' },
{ name: 'un-deux' },
{ name: 'un un' },
];
const expected = [
{
name: 'deux',
},
{
name: 'un',
},
{ name: 'un un' },
{
name: 'un-deux',
},
{
name: 'un-un',
},
];
expect(init(initialState, plugins)).toEqual(expected);
});
expect(sortLinks(data)).toEqual(expected);
});
it('should set the permissions in the settings link correctly for the plugins', () => {
const initialState = {
generalSectionLinks: [
{
icon: 'list',
label: 'app.components.LeftMenuLinkContainer.listPlugins',
destination: '/list-plugins',
isDisplayed: false,
permissions: [
{ action: 'admin::marketplace.read', subject: null },
{ action: 'admin::marketplace.plugins.uninstall', subject: null },
],
},
{
icon: 'shopping-basket',
label: 'app.components.LeftMenuLinkContainer.installNewPlugin',
destination: '/marketplace',
isDisplayed: false,
permissions: [
{ action: 'admin::marketplace.read', subject: null },
{ action: 'admin::marketplace.plugins.install', subject: null },
],
},
{
icon: 'cog',
label: 'app.components.LeftMenuLinkContainer.settings',
isDisplayed: false,
destination: SETTINGS_BASE_URL,
permissions: [
// webhooks
{ action: 'admin::webhook.create', subject: null },
{ action: 'admin::webhook.read', subject: null },
{ action: 'admin::webhook.update', subject: null },
{ action: 'admin::webhook.delete', subject: null },
// users
{ action: 'admin::users.create', subject: null },
{ action: 'admin::users.read', subject: null },
{ action: 'admin::users.update', subject: null },
{ action: 'admin::users.delete', subject: null },
// roles
{ action: 'admin::roles.create', subject: null },
{ action: 'admin::roles.update', subject: null },
{ action: 'admin::roles.read', subject: null },
{ action: 'admin::roles.delete', subject: null },
// Here are added the plugins settings permissions during the init phase
],
},
],
pluginsSectionLinks: [],
isLoading: true,
};
const plugins = {
test: {
settings: {
global: {
links: [
{
title: {
id: 'test.plugin.name',
defaultMessage: 'Test',
},
name: 'test',
to: '/settings/test',
Component: () => null,
permissions: [{ action: 'plugins::test.settings.read', subject: null }],
},
{
title: {
id: 'test.plugin.name1',
defaultMessage: 'Test1',
},
name: 'test1',
to: '/settings/test1',
Component: () => null,
permissions: [{ action: 'plugins::test1.settings.read', subject: null }],
},
],
},
},
},
other: {},
upload: {
settings: {
global: {
links: [
{
title: {
id: 'upload.plugin.name',
defaultMessage: 'Media Library',
},
name: 'media-library',
to: 'settings/media-library',
Component: () => null,
permissions: [
{ action: 'plugins::upload.settings.read', subject: null },
{ action: 'plugins::upload.settings.read.test', subject: null },
],
},
],
},
},
},
};
const expected = {
generalSectionLinks: [
{
icon: 'list',
label: 'app.components.LeftMenuLinkContainer.listPlugins',
destination: '/list-plugins',
isDisplayed: false,
permissions: [
{ action: 'admin::marketplace.read', subject: null },
{ action: 'admin::marketplace.plugins.uninstall', subject: null },
],
},
{
icon: 'shopping-basket',
label: 'app.components.LeftMenuLinkContainer.installNewPlugin',
destination: '/marketplace',
isDisplayed: false,
permissions: [
{ action: 'admin::marketplace.read', subject: null },
{ action: 'admin::marketplace.plugins.install', subject: null },
],
},
{
icon: 'cog',
label: 'app.components.LeftMenuLinkContainer.settings',
isDisplayed: false,
destination: SETTINGS_BASE_URL,
permissions: [
// webhooks
{ action: 'admin::webhook.create', subject: null },
{ action: 'admin::webhook.read', subject: null },
{ action: 'admin::webhook.update', subject: null },
{ action: 'admin::webhook.delete', subject: null },
// users
{ action: 'admin::users.create', subject: null },
{ action: 'admin::users.read', subject: null },
{ action: 'admin::users.update', subject: null },
{ action: 'admin::users.delete', subject: null },
// roles
{ action: 'admin::roles.create', subject: null },
{ action: 'admin::roles.update', subject: null },
{ action: 'admin::roles.read', subject: null },
{ action: 'admin::roles.delete', subject: null },
{ action: 'plugins::test.settings.read', subject: null },
{ action: 'plugins::test1.settings.read', subject: null },
{ action: 'plugins::upload.settings.read', subject: null },
{ action: 'plugins::upload.settings.read.test', subject: null },
],
},
],
pluginsSectionLinks: [],
isLoading: true,
};
expect(init(initialState, plugins)).toEqual(expected);
});
});

View File

@ -0,0 +1,89 @@
// Retrieve the plugin settings object
// The settings API works as follows for a plugin
// Declare the links that will be injected into the settings menu
//
// Path: my-plugin/admin/src/index.js
/*
************************************************************
* 1. Declare a section that will be added to the setting menu
* const menuSection = {
* // Unique id of the section
* id: pluginId,
* // Title of Menu section using i18n
* title: {
* id: `${pluginId}.foo`,
* defaultMessage: 'Super cool setting',
* },
* // Array of links to be displayed
* links: [
* {
* // Using string
* title: 'Setting page 1',
* to: `${strapi.settingsBaseURL}/${pluginId}/setting1`,
* name: 'setting1',
* permissions: [{ action: 'plugins::my-plugin.action-name', subject: null }],
* },
* {
* // Using i18n with a corresponding translation key
* title: {
* id: `${pluginId}.bar`,
* defaultMessage: 'Setting page 2',
* },
* to: `${strapi.settingsBaseURL}/${pluginId}/setting2`,
* name: 'setting2',
* permissions: [{ action: 'plugins::my-plugin.action-name2', subject: null }],
* },
* ],
* };
* ************************************************************
* 2. Add a setting to the global section of the menu
* const global = {
* links: [
* {
* title: {
* id: getTrad('plugin.name'),
* defaultMessage: 'Media Library',
* },
* name: 'media-library',
* to: `${strapi.settingsBaseURL}/media-library`,
* Component: SettingsPage,
* // TODO write documentation
* permissions: [{ action: 'plugins::my-plugin.action-name', subject: null }],
* },
* ],
* };
***********************************************************
* 3. Define the settings in the plugin object
* const settings = { global, menuSection };
*/
import { get } from 'lodash';
const getPluginsSettingsPermissions = plugins => {
const globalSettingsLinksPermissions = Object.values(plugins).reduce((acc, current) => {
const pluginSettings = get(current, 'settings', {});
const getSettingsLinkPermissions = settings => {
return Object.values(settings).reduce((acc, current) => {
const links = get(current, 'links', []);
links.forEach(link => {
const permissions = get(link, 'permissions', []);
permissions.forEach(permission => {
acc.push(permission);
});
});
return acc;
}, []);
};
const pluginPermissions = getSettingsLinkPermissions(pluginSettings);
return [...acc, ...pluginPermissions];
}, []);
return [...globalSettingsLinksPermissions];
};
export default getPluginsSettingsPermissions;

View File

@ -0,0 +1,2 @@
export { default as getPluginsSettingsPermissions } from './getPluginsSettingsPermissions';
export { default as sortLinks } from './sortLinks';

View File

@ -0,0 +1,5 @@
import { sortBy } from 'lodash';
const sortLinks = links => sortBy(links, object => object.name);
export default sortLinks;

View File

@ -0,0 +1,100 @@
import getPluginsSettingsPermissions from '../getPluginsSettingsPermissions';
describe('ADMIN | LeftMenu | utils | getPluginsSettingsPermissions', () => {
it('should return an empty array', () => {
expect(getPluginsSettingsPermissions({})).toEqual([]);
});
it('should return an array containing all the permissions of the plugins settings links', () => {
const menuSection = {
// Unique id of the section
id: 'test',
// Title of Menu section using i18n
title: {
id: 'test.foo',
defaultMessage: 'Super cool setting',
},
// Array of links to be displayed
links: [
{
// Using string
title: 'Setting page 1',
to: 'settings/test/setting1',
name: 'setting1',
permissions: [{ action: 'plugins::test.action-name', subject: null }],
},
{
// Using i18n with a corresponding translation key
title: {
id: 'test.bar',
defaultMessage: 'Setting page 2',
},
to: 'settings/test/setting2',
name: 'setting2',
permissions: [{ action: 'plugins::my-plugin.action-name2', subject: null }],
},
],
};
const plugins = {
test: {
settings: {
global: {
links: [
{
title: {
id: 'test.plugin.name',
defaultMessage: 'Test',
},
name: 'test',
to: '/settings/test',
Component: () => null,
permissions: [{ action: 'plugins::test.settings.read', subject: null }],
},
{
title: {
id: 'test.plugin.name1',
defaultMessage: 'Test1',
},
name: 'test1',
to: '/settings/test1',
Component: () => null,
permissions: [{ action: 'plugins::test1.settings.read', subject: null }],
},
],
},
menuSection,
},
},
other: {},
upload: {
settings: {
global: {
links: [
{
title: {
id: 'upload.plugin.name',
defaultMessage: 'Media Library',
},
name: 'media-library',
to: 'settings/media-library',
Component: () => null,
permissions: [
{ action: 'plugins::upload.settings.read', subject: null },
{ action: 'plugins::upload.settings.read.test', subject: null },
],
},
],
},
},
},
};
const expected = [
{ action: 'plugins::test.action-name', subject: null },
{ action: 'plugins::my-plugin.action-name2', subject: null },
{ action: 'plugins::upload.settings.read', subject: null },
{ action: 'plugins::upload.settings.read.test', subject: null },
];
expect(getPluginsSettingsPermissions(plugins)).toEqual(expected);
});
});

View File

@ -0,0 +1,36 @@
import sortLinks from '../sortLinks';
describe('ADMIN | LeftMenu | utils | sortLinks', () => {
it('should return an empty array', () => {
expect(sortLinks([])).toEqual([]);
});
it('should return a sorted array', () => {
const data = [
{
name: 'un',
},
{ name: 'deux' },
{ name: 'un-un' },
{ name: 'un-deux' },
{ name: 'un un' },
];
const expected = [
{
name: 'deux',
},
{
name: 'un',
},
{ name: 'un un' },
{
name: 'un-deux',
},
{
name: 'un-un',
},
];
expect(sortLinks(data)).toEqual(expected);
});
});

View File

@ -2,7 +2,7 @@ import { get } from 'lodash';
const retrieveGlobalLinks = pluginsObj => {
return Object.values(pluginsObj).reduce((acc, current) => {
const links = get(current, ['settings', 'global'], null);
const links = get(current, ['settings', 'global', 'links'], null);
if (links) {
for (let i = 0; i < links.length; i++) {

View File

@ -10,18 +10,22 @@ describe('ADMIN | containers | SettingsPage | utils', () => {
const plugins = {
test: {
settings: {
global: [],
global: {
links: [],
},
},
},
noSettings: {},
foo: {
settings: {
global: ['test'],
global: {
links: ['test'],
},
},
},
bar: {
settings: {
global: ['test2'],
global: { links: ['test2'] },
},
},
};

View File

@ -21,6 +21,7 @@ export default strapi => {
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
const icon = pluginPkg.strapi.icon;
const name = pluginPkg.strapi.name;
const plugin = {
blockerComponent: null,
blockerComponentProps: {},
@ -41,19 +42,20 @@ export default strapi => {
pluginLogo,
preventComponentRendering: false,
settings: {
global: [
{
title: {
id: getTrad('plugin.name'),
defaultMessage: 'Media Library',
global: {
links: [
{
title: {
id: getTrad('plugin.name'),
defaultMessage: 'Media Library',
},
name: 'media-library',
to: `${strapi.settingsBaseURL}/media-library`,
Component: SettingsPage,
permissions: [{ action: 'plugins::upload.settings.read', subject: null }],
},
name: 'media-library',
to: `${strapi.settingsBaseURL}/media-library`,
Component: SettingsPage,
// TODO write documentation
permissions: [{ action: 'plugins::upload.settings.read', subject: null }],
},
],
],
},
},
trads,
menu: {