Add email test in plugin settings (#8156)

* Add email test route and controller

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Add email settings page

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Add email Settings Container

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Remove unused getProviderConfig email service

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Add email (disabled) config fields

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update email settings config form

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update email settings container

Move Test button
Add testEmail input
Update config fields

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update email settings form

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update email plugin settings

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update email plugin docs

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update email settings page

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update email settings permissions

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update settings container form

* Update mail text
* Fix alignment
* Add yup validation
* Update form submission

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Fix e2e test

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Fix e2e test

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Fix Baseline

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update email plugin docs

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update (temp) BaselineAlignment component

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update text and button styles and placement

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update email routes and permissions

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update email controller and service

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update email admin permissions

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update test permissions snapshot

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update email admin permissions

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update all test snapshot

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update email settings permissions

Signed-off-by: MattieBelt <mattiasvandenbelt@gmail.com>

* Update test snapshots

* Fix text width

* Update styling, baseline, and docs link

Co-authored-by: Alexandre BODIN <alexandrebodin@users.noreply.github.com>
This commit is contained in:
Mattias van den Belt 2021-03-05 10:37:33 +01:00 committed by GitHub
parent 8cbb8b9f9c
commit 8aaf797a1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 686 additions and 6 deletions

View File

@ -0,0 +1,299 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Role CRUD End to End Can get the existing permissions 1`] = `
Object {
"conditions": Array [
Object {
"category": "default",
"displayName": "Is creator",
"id": "admin::is-creator",
},
Object {
"category": "default",
"displayName": "Has same role as creator",
"id": "admin::has-same-role-as-creator",
},
],
"sections": Object {
"contentTypes": Array [
Object {
"action": "plugins::content-manager.explorer.create",
"displayName": "Create",
"subjects": Array [
"plugins::users-permissions.user",
],
},
Object {
"action": "plugins::content-manager.explorer.delete",
"displayName": "Delete",
"subjects": Array [
"plugins::users-permissions.user",
],
},
Object {
"action": "plugins::content-manager.explorer.publish",
"displayName": "Publish",
"subjects": Array [],
},
Object {
"action": "plugins::content-manager.explorer.read",
"displayName": "Read",
"subjects": Array [
"plugins::users-permissions.user",
],
},
Object {
"action": "plugins::content-manager.explorer.update",
"displayName": "Update",
"subjects": Array [
"plugins::users-permissions.user",
],
},
],
"plugins": Array [
Object {
"action": "plugins::content-manager.collection-types.configure-view",
"displayName": "Configure view",
"plugin": "plugin::content-manager",
"subCategory": "collection types",
},
Object {
"action": "plugins::content-manager.components.configure-layout",
"displayName": "Configure Layout",
"plugin": "plugin::content-manager",
"subCategory": "components",
},
Object {
"action": "plugins::content-manager.single-types.configure-view",
"displayName": "Configure view",
"plugin": "plugin::content-manager",
"subCategory": "single types",
},
Object {
"action": "plugins::content-type-builder.read",
"displayName": "Read",
"plugin": "plugin::content-type-builder",
"subCategory": "general",
},
Object {
"action": "plugins::documentation.read",
"displayName": "Access the Documentation",
"plugin": "plugin::documentation",
"subCategory": "general",
},
Object {
"action": "plugins::documentation.settings.regenerate",
"displayName": "Regenerate",
"plugin": "plugin::documentation",
"subCategory": "settings",
},
Object {
"action": "plugins::documentation.settings.update",
"displayName": "Update and delete",
"plugin": "plugin::documentation",
"subCategory": "settings",
},
Object {
"action": "plugins::upload.assets.copy-link",
"displayName": "Copy link",
"plugin": "plugin::upload",
"subCategory": "assets",
},
Object {
"action": "plugins::upload.assets.create",
"displayName": "Create (upload)",
"plugin": "plugin::upload",
"subCategory": "assets",
},
Object {
"action": "plugins::upload.assets.download",
"displayName": "Download",
"plugin": "plugin::upload",
"subCategory": "assets",
},
Object {
"action": "plugins::upload.assets.update",
"displayName": "Update (crop, details, replace) + delete",
"plugin": "plugin::upload",
"subCategory": "assets",
},
Object {
"action": "plugins::upload.read",
"displayName": "Access the Media Library",
"plugin": "plugin::upload",
"subCategory": "general",
},
Object {
"action": "plugins::users-permissions.advanced-settings.read",
"displayName": "Read",
"plugin": "plugin::users-permissions",
"subCategory": "advancedSettings",
},
Object {
"action": "plugins::users-permissions.advanced-settings.update",
"displayName": "Edit",
"plugin": "plugin::users-permissions",
"subCategory": "advancedSettings",
},
Object {
"action": "plugins::users-permissions.email-templates.read",
"displayName": "Read",
"plugin": "plugin::users-permissions",
"subCategory": "emailTemplates",
},
Object {
"action": "plugins::users-permissions.email-templates.update",
"displayName": "Edit",
"plugin": "plugin::users-permissions",
"subCategory": "emailTemplates",
},
Object {
"action": "plugins::users-permissions.providers.read",
"displayName": "Read",
"plugin": "plugin::users-permissions",
"subCategory": "providers",
},
Object {
"action": "plugins::users-permissions.providers.update",
"displayName": "Edit",
"plugin": "plugin::users-permissions",
"subCategory": "providers",
},
Object {
"action": "plugins::users-permissions.roles.create",
"displayName": "Create",
"plugin": "plugin::users-permissions",
"subCategory": "roles",
},
Object {
"action": "plugins::users-permissions.roles.delete",
"displayName": "Delete",
"plugin": "plugin::users-permissions",
"subCategory": "roles",
},
Object {
"action": "plugins::users-permissions.roles.read",
"displayName": "Read",
"plugin": "plugin::users-permissions",
"subCategory": "roles",
},
Object {
"action": "plugins::users-permissions.roles.update",
"displayName": "Update",
"plugin": "plugin::users-permissions",
"subCategory": "roles",
},
],
"settings": Array [
Object {
"action": "admin::marketplace.plugins.install",
"category": "plugins and marketplace",
"displayName": "Install (only for dev env)",
"subCategory": "plugins",
},
Object {
"action": "admin::marketplace.plugins.uninstall",
"category": "plugins and marketplace",
"displayName": "Uninstall (only for dev env)",
"subCategory": "plugins",
},
Object {
"action": "admin::marketplace.read",
"category": "plugins and marketplace",
"displayName": "Access the marketplace",
"subCategory": "marketplace",
},
Object {
"action": "admin::roles.create",
"category": "users and roles",
"displayName": "Create",
"subCategory": "roles",
},
Object {
"action": "admin::roles.delete",
"category": "users and roles",
"displayName": "Delete",
"subCategory": "roles",
},
Object {
"action": "admin::roles.read",
"category": "users and roles",
"displayName": "Read",
"subCategory": "roles",
},
Object {
"action": "admin::roles.update",
"category": "users and roles",
"displayName": "Update",
"subCategory": "roles",
},
Object {
"action": "admin::users.create",
"category": "users and roles",
"displayName": "Create (invite)",
"subCategory": "users",
},
Object {
"action": "admin::users.delete",
"category": "users and roles",
"displayName": "Delete",
"subCategory": "users",
},
Object {
"action": "admin::users.read",
"category": "users and roles",
"displayName": "Read",
"subCategory": "users",
},
Object {
"action": "admin::users.update",
"category": "users and roles",
"displayName": "Update",
"subCategory": "users",
},
Object {
"action": "admin::webhooks.create",
"category": "webhooks",
"displayName": "Create",
"subCategory": "general",
},
Object {
"action": "admin::webhooks.delete",
"category": "webhooks",
"displayName": "Delete",
"subCategory": "general",
},
Object {
"action": "admin::webhooks.read",
"category": "webhooks",
"displayName": "Read",
"subCategory": "general",
},
Object {
"action": "admin::webhooks.update",
"category": "webhooks",
"displayName": "Update",
"subCategory": "general",
},
Object {
"action": "plugins::email.settings.read",
"category": "email",
"displayName": "Read",
"subCategory": "settings",
},
Object {
"action": "plugins::email.settings.test",
"category": "email",
"displayName": "Send test email",
"subCategory": "settings",
},
Object {
"action": "plugins::upload.settings.read",
"category": "media library",
"displayName": "Access the Media Library settings page",
"subCategory": "general",
},
],
},
}
`;

View File

@ -311,6 +311,12 @@ describe('Role CRUD End to End', () => {
"displayName": "Update",
"subCategory": "general",
},
Object {
"action": "plugins::email.settings.read",
"category": "email",
"displayName": "Access the Email Settings page",
"subCategory": "general",
},
Object {
"action": "plugins::upload.settings.read",
"category": "media library",

View File

@ -0,0 +1,16 @@
import styled from 'styled-components';
import { Button, Text as TextBase } from '@buffetjs/core';
const Text = styled(TextBase)`
width: 100%;
padding: 0 15px 17px 15px;
`;
const AlignedButton = styled(Button)`
height: 34px;
padding-top: 3px;
margin: 29px 15px 0 15px;
min-width: unset;
`;
export { Text, AlignedButton };

View File

@ -0,0 +1,201 @@
import React, { useState, useEffect, useRef } from 'react';
import { useIntl, FormattedMessage } from 'react-intl';
import { get } from 'lodash';
import { Header } from '@buffetjs/custom';
import { Envelope } from '@buffetjs/icons';
import { colors } from '@buffetjs/styles';
import {
FormBloc,
request,
SettingsPageTitle,
SizedInput,
getYupInnerErrors,
BaselineAlignment,
CheckPagePermissions,
} from 'strapi-helper-plugin';
import getTrad from '../../utils/getTrad';
import { AlignedButton, Text } from './components';
import schema from '../../utils/schema';
import pluginPermissions from '../../permissions';
const SettingsPage = () => {
const { formatMessage } = useIntl();
const [formErrors, setFormErrors] = useState({});
const [isTestButtonLoading, setIsTestButtonLoading] = useState(false);
const [showLoader, setShowLoader] = useState(false);
const [config, setConfig] = useState({
provider: '',
settings: { defaultFrom: '', defaultReplyTo: '', testAddress: '' },
});
const [providers, setProviders] = useState([]);
const [testAddress, setTestAddress] = useState();
const [testSuccess, setTestSuccess] = useState(false);
const isMounted = useRef(true);
const title = formatMessage({ id: getTrad('Settings.title') });
const handleSubmit = async event => {
event.preventDefault();
let errors = {};
try {
await schema.validate({ email: testAddress }, { abortEarly: false });
try {
setIsTestButtonLoading(true);
await request('/email/test', {
method: 'POST',
body: { to: testAddress },
});
setTestSuccess(true);
strapi.notification.success(
formatMessage({ id: getTrad('Settings.notification.test.success') }, { to: testAddress })
);
} catch (err) {
strapi.notification.error(
formatMessage({ id: getTrad('Settings.notification.test.error') }, { to: testAddress })
);
} finally {
if (isMounted.current) {
setIsTestButtonLoading(false);
}
}
} catch (error) {
errors = getYupInnerErrors(error);
setFormErrors(errors);
console.log(errors);
}
};
useEffect(() => {
const fetchEmailSettings = () => {
setShowLoader(true);
request('/email/settings', {
method: 'GET',
})
.then(data => {
setConfig(data.config);
setProviders([data.config.provider]);
setTestAddress(get(data, 'config.settings.testAddress'));
})
.catch(() =>
strapi.notification.error(
formatMessage({ id: getTrad('Settings.notification.config.error') })
)
)
.finally(() => setShowLoader(false));
};
fetchEmailSettings();
}, [formatMessage]);
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
return (
<>
<CheckPagePermissions permissions={pluginPermissions.settings}>
<SettingsPageTitle name={title} />
<div>
<form onSubmit={handleSubmit}>
<Header
title={{ label: title }}
content={formatMessage({ id: getTrad('Settings.subTitle') })}
isLoading={showLoader}
/>
<BaselineAlignment top size="3px" />
<FormBloc
title={formatMessage({ id: getTrad('Settings.form.title.config') })}
isLoading={showLoader}
>
<Text fontSize="md" lineHeight="18px">
<FormattedMessage
id={getTrad('Settings.form.text.configuration')}
values={{
file: <code>./config/plugins.js</code>,
link: (
<a
href="https://strapi.io/documentation/developer-docs/latest/plugins/email.html#configure-the-plugin"
target="_blank"
rel="noopener noreferrer"
>
link
</a>
),
}}
/>
</Text>
<SizedInput
disabled
label={getTrad('Settings.form.label.defaultFrom')}
name="default-from"
placeholder={getTrad('Settings.form.placeholder.defaultFrom')}
size={{ xs: 6 }}
type="email"
value={config.settings.defaultFrom}
/>
<SizedInput
disabled
label={getTrad('Settings.form.label.defaultReplyTo')}
name="default-reply-to"
placeholder={getTrad('Settings.form.placeholder.defaultReplyTo')}
size={{ xs: 6 }}
type="email"
value={config.settings.defaultReplyTo}
/>
<SizedInput
disabled
label={getTrad('Settings.form.label.provider')}
name="provider"
options={providers}
size={{ xs: 6 }}
type="select"
value={`strapi-provider-email-${config.provider}`}
/>
</FormBloc>
<BaselineAlignment top size="32px" />
<FormBloc
title={formatMessage({ id: getTrad('Settings.form.title.test') })}
isLoading={showLoader}
>
<SizedInput
label={getTrad('Settings.form.label.testAddress')}
name="test-address"
placeholder={getTrad('Settings.form.placeholder.testAddress')}
onChange={event => setTestAddress(event.target.value)}
size={{ xs: 6 }}
type="email"
value={testAddress}
error={formErrors.email}
/>
<AlignedButton
color="success"
disabled={testSuccess}
icon={(
<Envelope
fill={testSuccess ? colors.button.disabled.color : null}
style={{ verticalAlign: 'middle', marginRight: '10px' }}
/>
)}
isLoading={isTestButtonLoading}
style={{ fontWeight: 600 }}
type="submit"
>
{formatMessage({ id: getTrad('Settings.button.test-email') })}
</AlignedButton>
</FormBloc>
</form>
</div>
</CheckPagePermissions>
</>
);
};
export default SettingsPage;

View File

@ -1,14 +1,19 @@
// NOTE TO PLUGINS DEVELOPERS:
// If you modify this file by adding new options to the plugin entry point
// Here's the file: strapi/docs/3.0.0-beta.x/plugin-development/frontend-field-api.md
// Here's the file: strapi/docs/3.0.0-beta.x/guides/registering-a-field-in-admin.md
// Here's the file: strapi/docs/3.x/plugin-development/frontend-field-api.md
// Here's the file: strapi/docs/3.x/guides/registering-a-field-in-admin.md
// Also the strapi-generate-plugins/files/admin/src/index.js needs to be updated
// IF THE DOC IS NOT UPDATED THE PULL REQUEST WILL NOT BE MERGED
import React from 'react';
import { CheckPagePermissions } from 'strapi-helper-plugin';
import pluginPkg from '../../package.json';
import pluginId from './pluginId';
import pluginLogo from './assets/images/logo.svg';
import pluginPermissions from './permissions';
import trads from './translations';
import getTrad from './utils/getTrad';
import SettingsPage from './containers/Settings';
export default strapi => {
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
@ -30,6 +35,28 @@ export default strapi => {
pluginLogo,
preventComponentRendering: false,
trads,
settings: {
menuSection: {
id: pluginId,
title: getTrad('SettingsNav.section-label'),
links: [
{
title: {
id: getTrad('SettingsNav.link.settings'),
defaultMessage: 'Settings',
},
name: 'settings',
to: `${strapi.settingsBaseURL}/${pluginId}`,
Component: () => (
<CheckPagePermissions permissions={pluginPermissions.settings}>
<SettingsPage />
</CheckPagePermissions>
),
permissions: pluginPermissions.settings,
},
],
},
},
};
return strapi.registerPlugin(plugin);

View File

@ -0,0 +1,9 @@
const pluginPermissions = {
// This permission regards the main component (App) and is used to tell
// If the plugin link should be displayed in the menu
// And also if the plugin is accessible. This use case is found when a user types the url of the
// plugin directly in the browser
settings: [{ action: 'plugins::email.settings.read', subject: null }],
};
export default pluginPermissions;

View File

@ -1,4 +1,22 @@
{
"plugin.description.long": "Send emails.",
"plugin.description.short": "Send emails."
"plugin.description.short": "Send emails.",
"SettingsNav.section-label": "Email plugin",
"SettingsNav.link.settings": "Email settings",
"Settings.title": "Email settings",
"Settings.subTitle": "Test the settings for the email plugin",
"Settings.button.test-email": "Test email",
"Settings.notification.test.success": "Email test succeeded, check the {to} mailbox",
"Settings.notification.test.error": "Failed to send a test mail to {to}",
"Settings.notification.config.error": "Failed to retrieve the email config",
"Settings.form.title.config": "Configuration",
"Settings.form.title.test": "Send a test email",
"Settings.form.label.provider": "Email provider",
"Settings.form.label.defaultFrom": "Default shipper email",
"Settings.form.label.defaultReplyTo": "Default response email",
"Settings.form.label.testAddress": "Test delivery email address",
"Settings.form.placeholder.defaultFrom": "ex: Strapi No-Reply <no-reply@strapi.io>",
"Settings.form.placeholder.defaultReplyTo": "ex: Strapi <example@strapi.io>",
"Settings.form.placeholder.testAddress": "ex: developer@example.com",
"Settings.form.text.configuration": "The plugin is configured through the {file} file, checkout this {link} for the documentation."
}

View File

@ -0,0 +1,5 @@
import pluginId from '../pluginId';
const getTrad = id => `${pluginId}.${id}`;
export default getTrad;

View File

@ -0,0 +1,11 @@
import * as yup from 'yup';
import { translatedErrors } from 'strapi-helper-plugin';
const schema = yup.object().shape({
email: yup
.string()
.email(translatedErrors.email)
.required(translatedErrors.required),
});
export default schema;

View File

@ -18,4 +18,18 @@ const createProvider = emailConfig => {
module.exports = async () => {
const emailConfig = _.get(strapi.plugins, 'email.config', {});
strapi.plugins.email.provider = createProvider(emailConfig);
// Add permissions
const actions = [
{
section: 'settings',
category: 'email',
displayName: 'Access the Email Settings page',
uid: 'settings.read',
pluginName: 'email',
},
];
const { actionProvider } = strapi.admin.services.permission;
actionProvider.register(actions);
};

View File

@ -12,6 +12,38 @@
"name": "Email"
}
}
},
{
"method": "POST",
"path": "/test",
"handler": "Email.test",
"config": {
"policies": [
"admin::isAuthenticatedAdmin",
["admin::hasPermissions", ["plugins::email.settings.read"]]
],
"description": "Send an test email",
"tag": {
"plugin": "email",
"name": "Email"
}
}
},
{
"method": "GET",
"path": "/settings",
"handler": "Email.getSettings",
"config": {
"policies": [
"admin::isAuthenticatedAdmin",
["admin::hasPermissions", ["plugins::email.settings.read"]]
],
"description": "Get the email settings",
"tag": {
"plugin": "email",
"name": "Email"
}
}
}
]
}

View File

@ -1,12 +1,14 @@
'use strict';
const { isNil, pick } = require('lodash/fp');
/**
* Email.js controller
*
* @description: A set of functions called "actions" of the `email` plugin.
*/
module.exports = {
send: async ctx => {
async send(ctx) {
let options = ctx.request.body;
try {
await strapi.plugins.email.services.email.send(options);
@ -21,4 +23,44 @@ module.exports = {
// Send 200 `ok`
ctx.send({});
},
async test(ctx) {
const { to } = ctx.request.body;
if (isNil(to)) {
throw strapi.errors.badRequest(null, {
errors: [{ id: 'Email.to.empty', message: 'No recipient(s) are given' }],
});
}
const email = {
to: to,
subject: `Strapi test mail to: ${to}`,
text: `Great! You have correctly configured the Strapi email plugin with the ${strapi.plugins.email.config.provider} provider. \r\nFor documentation on how to use the email plugin checkout: https://strapi.io/documentation/developer-docs/latest/plugins/email.html`,
};
try {
await strapi.plugins.email.services.email.send(email);
} catch (e) {
if (e.statusCode === 400) {
return ctx.badRequest(e.message);
} else {
throw new Error(`Couldn't send test email: ${e.message}.`);
}
}
// Send 200 `ok`
ctx.send({});
},
async getSettings(ctx) {
const config = strapi.plugins.email.services.email.getProviderSettings();
ctx.send({
config: pick(
['provider', 'settings.defaultFrom', 'settings.defaultReplyTo', 'settings.testAddress'],
config
),
});
},
};

View File

@ -2,7 +2,7 @@
const _ = require('lodash');
const getProviderConfig = () => {
const getProviderSettings = () => {
return strapi.plugins.email.config;
};
@ -38,7 +38,7 @@ const sendTemplatedEmail = (emailOptions = {}, emailTemplate = {}, data = {}) =>
};
module.exports = {
getProviderConfig,
getProviderSettings,
send,
sendTemplatedEmail,
};