Commit f8fc392d authored by Sam Galson's avatar Sam Galson

refactor(styleguide): include all components

parent f076a8cd
......@@ -15,7 +15,7 @@ RUN [ "rm", "-rf", "/npm-packages-offline-cache"]
ENV NODE_ENV "production"
WORKDIR ${HOME}/packages/ui
WORKDIR ${HOME}/packages/styleguide
RUN [ "npm", "run", "styleguide:build" ]
# Create file for kubernetes health checks
RUN touch ./styleguide/health
......
_build/
api/
logs/
node_modules/
uploads/
.env.*
.env
config/local*.*
\ No newline at end of file
# AWS SES Configuration
In order to use this component, the following configuration needs to be added to a PubSweet application inside a section named `pubsweet-component-aws-ses`:
* `secretAccessKey`: the `AWS_SES_SECRET_KEY` value from the app's `.env` file
* `accessKeyId`: the `AWS_SES_ACCESS_KEY` value from the app's `.env` file
* `region`: the `AWS_SES_REGION` value from the app's `.env` file
* `sender`: the `EMAIL_SENDER` value from the app's `.env` file
For example:
```json
"pubsweet-component-aws-ses": {
"secretAccessKey": "process.env.AWS_SES_SECRET_KEY",
"accessKeyId": "process.env.AWS_SES_ACCESS_KEY",
"region": "process.env.AWS_SES_REGION",
"sender": "process.env.EMAIL_SENDER",
},
```
In order to use `component-aws-ses` you first need to have a `.env` file containing AWS data in the root folder of the starting point of your application.
The `.env` file contain the following data:
```bash
AWS_SES_SECRET_KEY = <secretKey>
AWS_SES_ACCESS_KEY = <accessKey>
EMAIL_SENDER = verified_ses_sender@domain.com
AWS_SES_REGION = region-name
```
Then, as soon as possible in your app you should add the `dotenv` package:
```js
require('dotenv').config()
```
# `component-aws-ses` API
A list of endpoints that help you upload, download and delete S3 files.
## Send an email [POST]
#### Request
`POST /api/email`
#### Request body
All parameters are `required`
```json
{
"email": "to_email@domain.com",
"subject": "Example subject",
"textBody": "this is an email",
"htmlBody": "<p><b>This</b> is an <i>email</i>"
}
```
#### Response
```json
HTTP/1.1 204
```
module.exports = {
'pubsweet-component-aws-ses': {
secretAccessKey: 'secret-key',
accessKeyId: 'access-key',
region: 'region',
sender: 'test_sender@domain.com',
},
}
require('dotenv').config()
module.exports = {
server: () => app => require('./src/EmailBackend')(app),
}
{
"name": "@pubsweet/component-aws-ses",
"version": "0.1.0",
"description": "aws ses configured for pubsweet",
"license": "MIT",
"author": "Collaborative Knowledge Foundation",
"files": ["src"],
"main": "index.js",
"scripts": {
"test": "jest"
},
"dependencies": {
"aws-sdk": "^2.185.0",
"body-parser": "^1.17.2",
"nodemailer": "^4.4.2"
},
"peerDependencies": {
"@pubsweet/logger": "^0.0.1",
"pubsweet-server": "^1.0.1"
},
"devDependencies": {
"jest": "^22.1.1",
"supertest": "^3.0.0"
},
"jest": {
"verbose": true,
"testRegex": "/src/.*.test.js$"
},
"repository": {
"type": "git",
"url": "https://gitlab.coko.foundation/pubsweet/pubsweet",
"path": "AWSSES"
},
"publishConfig": {
"access": "public"
}
}
const bodyParser = require('body-parser')
const config = require('config')
const _ = require('lodash')
const logger = require('@pubsweet/logger')
const sesConfig = _.get(config, 'pubsweet-component-aws-ses')
const transporter = require('./transporter')
const EmailBackend = app => {
app.use(bodyParser.json())
const authBearer = app.locals.passport.authenticate('bearer', {
session: false,
})
app.post('/api/email', authBearer, async (req, res) => {
const { email, emailSubject, textBody, htmlBody } = req.body
if (
email === undefined ||
emailSubject === undefined ||
textBody === undefined ||
htmlBody === undefined
) {
res.status(400).json({ error: 'all parameters are required' })
logger.error('some parameters are missing')
return
}
await transporter.sendMail(
{
from: sesConfig.sender,
to: email,
subject: emailSubject,
text: textBody,
html: htmlBody,
},
(err, info) => {
if (err) {
logger.error(err)
}
logger.debug(info)
},
)
res.status(204).json()
})
}
module.exports = EmailBackend
process.env.SUPPRESS_NO_CONFIG_WARNING = true
const express = require('express')
const supertest = require('supertest')
const bodyParser = require('body-parser')
const passport = require('passport')
const BearerStrategy = require('passport-http-bearer').Strategy
jest.mock('./transporter', () => ({ sendMail: jest.fn() }))
const transporter = require('./transporter')
const component = require('..')
function makeApp(response) {
const app = express()
app.use(bodyParser.json())
const user = {
type: 'user',
username: 'testuser',
email: 'test@example.com',
password: 'test',
}
app.use(passport.initialize())
passport.use(
'bearer',
new BearerStrategy((token, done) => done(null, user, { scope: 'all' })),
)
app.locals.passport = passport
// register component
component.server()(app)
// create test wrapper
return supertest(app)
}
const emailUrl = '/api/email'
describe('/api/email route', () => {
describe('initial validation', () => {
it('errors if no params are given', () =>
makeApp()
.post(emailUrl)
.set('Authorization', 'Bearer 123')
.send({})
.expect(400, '{"error":"all parameters are required"}'))
it('errors if some params are missing', () =>
makeApp()
.post(emailUrl)
.set('Authorization', 'Bearer 123')
.send({
email: 'test@email.com',
subject: 'test subject',
})
.expect(400, '{"error":"all parameters are required"}'))
})
describe('sending email', () => {
it('sends email if all parameters are correct', () => {
const body = {
email: 'test@email.com',
emailSubject: 'test subject',
textBody: 'this is a text',
htmlBody: '<p>this is a text</p>',
}
return makeApp()
.post(emailUrl)
.set('Authorization', 'Bearer 123')
.send(body)
.expect(204)
.then(() => {
expect(transporter.sendMail).toBeCalledWith(
{
from: 'test_sender@domain.com',
html: body.htmlBody,
subject: body.emailSubject,
text: body.textBody,
to: body.email,
},
expect.any(Function),
)
})
})
})
})
const nodemailer = require('nodemailer')
const AWS = require('aws-sdk')
const config = require('config')
const _ = require('lodash')
const sesConfig = _.get(config, 'pubsweet-component-aws-ses')
AWS.config.update({
secretAccessKey: sesConfig.secretAccessKey,
accessKeyId: sesConfig.accessKeyId,
region: sesConfig.region,
})
module.exports = nodemailer.createTransport({
SES: new AWS.SES(),
})
......@@ -16,7 +16,7 @@ A sortable list implemented with `react-dnd`.
This component should be used in a React-DnD `DragDropContext` or `DragDropContextProvider`. Make sure you have `react-dnd-html5-backend` installed and wrap the parent component with `DragDropContext` decorator or add the `DragDropContextProvider` in your root component.
```js
```js static
import HTML5Backend from 'react-dnd-html5-backend'
import { DragDropContext } from 'react-dnd'
......@@ -29,7 +29,7 @@ export default DragDropContext(HTML5Backend)(YourApp)
or
```js
```js static
import HTML5Backend from 'react-dnd-html5-backend'
import { DragDropContextProvider } from 'react-dnd'
......@@ -46,7 +46,7 @@ export default class YourApp {
### Pass in a list of users
```js
```js static
const items = [
{firstName: 'John', lastName: 'Doe'},
{firstName: 'Michael', lastName: 'Jackson'},
......@@ -65,7 +65,7 @@ const Item = ({ isOver, isDragging, ...rest }) =>
### With custom drag handle
```js
```js static
const DragHandle = () => <div>Drag me!</div>
const ItemWithDragHandle = ({ dragHandle, ...rest }) => <div>
......@@ -85,7 +85,7 @@ const ItemWithDragHandle = ({ dragHandle, ...rest }) => <div>
To move items of the parent container whenever `moveItem` function is called we can use the `SortableList.moveItem` helper. More info in the example below.
```js
```js static
const Container = ({ moveItem, items }) => (
<div>
...
......@@ -97,7 +97,7 @@ const Container = ({ moveItem, items }) => (
Enhanced using recompose
```js
```js static
const MoveExample = compose(
withState('items', 'setItems', [
{ name: 'John' },
......
A drop-down menu for assigning an editor to a project.
```js
const { JournalProvider } = require('xpub-journal')
const journal = require('xpub-styleguide/src/config/journal')
const project = {
id: faker.random.uuid(),
}
......@@ -23,13 +26,14 @@ const options = [
label: faker.internet.userName(),
},
]
;<AssignEditor
project={project}
team={team}
teamName="Senior Editor"
teamTypeName="seniorEditor"
options={options}
addUserToTeam={value => console.log(value)}
/>
;<JournalProvider journal={journal}>
<AssignEditor
project={project}
team={team}
teamName="Senior Editor"
teamTypeName="seniorEditor"
options={options}
addUserToTeam={value => console.log(value)}
/>
</JournalProvider>
```
......@@ -3,8 +3,8 @@ import React from 'react'
const MetadataOwners = ({ owners }) => (
<span>
{owners.map((owner, index) => [
index === 0 ? null : <span>, </span>,
<span>{owner.username || 'Anonymous'}</span>,
index === 0 ? null : <span key={owner.username}>, </span>,
<span key={owner.username}>{owner.username || 'Anonymous'}</span>,
])}
</span>
)
......
......@@ -4,8 +4,8 @@ import { withJournal } from 'xpub-journal'
const MetadataSections = ({ journal, sections }) => (
<span>
{sections.map((section, index) => [
index === 0 ? null : <span>, </span>,
<span>
index === 0 ? null : <span key={section}>, </span>,
<span key={section}>
{journal.articleSections.find(item => item.value === section).label}
</span>,
])}
......
A dashboard item showing a project that the current user is handling as editor.
```js
const { JournalProvider } = require('xpub-journal')
const journal = require('xpub-styleguide/src/config/journal')
const AssignEditor = require('../AssignEditor').default
const project = {
......@@ -11,6 +14,7 @@ const project = {
owners: [
{
name: faker.name.findName(),
username: faker.name.findName(),
},
],
reviewers: [
......@@ -103,14 +107,27 @@ const options = [
},
]
const AssignEditorContainer = props => (
<AssignEditor team={team} options={options} {...props} />
const AssignEditorContainer = ({
project,
teamName,
teamTypeName,
addUserToTeam,
}) => (
<AssignEditor
team={team}
options={options}
project={project}
teamName={teamName}
teamTypeName={teamTypeName}
addUserToTeam={addUserToTeam}
/>
)
;<EditorItem
project={project}
version={version}
addUserToTeam={props => console.log(props)}
AssignEditor={AssignEditorContainer}
/>
;<JournalProvider journal={journal}>
<EditorItem
project={project}
version={version}
addUserToTeam={props => console.log(props)}
AssignEditor={AssignEditorContainer}
/>
</JournalProvider>
```
......@@ -15,6 +15,7 @@ import VersionTitle from './VersionTitle'
const ReviewerItem = ({ project, version, currentUser, reviewerResponse }) => {
const reviewer = getReviewerFromUser(project, version, currentUser)
const status = reviewer && reviewer.status
return (
......
A dashboard item showing a project that the current user is a reviewer of.
```js
const project = {
id: faker.random.uuid(),
title: faker.lorem.sentence(15),
fragments: [faker.random.uuid()],
}
const currentUserId = 1
const version = {
id: faker.random.uuid(),
const currentUser = {
id: currentUserId,
}
initialState = {
reviewer: {
id: currentUserId,
reviewer: currentUserId,
user: currentUserId,
status: 'invited',
},
}
const project = {
id: faker.random.uuid(),
title: faker.lorem.sentence(15),
fragments: [faker.random.uuid()],
reviewers: [state.reviewer],
}
const version = {
id: faker.random.uuid(),
reviewers: [state.reviewer],
}
;<ReviewerItem
project={project}
version={version}
reviewer={state.reviewer}
currentUser={currentUser}
reviewerResponse={(id, status) => setState({ reviewer: { status } })}
/>
```
......@@ -28,43 +38,59 @@ initialState = {
When the reviewer has accepted the invitation to review, a link to perform their review is displayed.
```js
const currentUserId = 1
const currentUser = {
id: currentUserId,
}
const reviewer = {
id: currentUserId,
reviewer: currentUserId,
user: currentUserId,
status: 'accepted',
}
const project = {
id: faker.random.uuid(),
title: faker.lorem.sentence(15),
fragments: [faker.random.uuid()],
reviewers: [reviewer],
}
const version = {
id: faker.random.uuid(),
reviewers: [reviewer],
}
initialState = {
reviewer: {
status: 'accepted',
},
}
;<ReviewerItem project={project} version={version} reviewer={state.reviewer} />
;<ReviewerItem project={project} version={version} currentUser={currentUser} />
```
When the reviewer has declined the invitation to review, they can't perform any further actions.
```js
const currentUserId = 1
const currentUser = {
id: currentUserId,
}
const reviewer = {
id: currentUserId,
user: currentUserId,
reviewer: currentUserId,
status: 'declined',
}
const project = {
id: faker.random.uuid(),
title: faker.lorem.sentence(15),
fragments: [faker.random.uuid()],
reviewers: [reviewer],
}
const version = {
id: faker.random.uuid(),
reviewers: [reviewer],
}
initialState = {
reviewer: {
status: 'declined',
},
}
;<ReviewerItem project={project} version={version} reviewer={state.reviewer} />
;<ReviewerItem project={project} version={version} currentUser={currentUser} />
```
......@@ -22,6 +22,5 @@ const author = {
},
],
}
;<Author author={author} />
;<Author author={author} toggleAuthor={() => null} />
```
......@@ -48,11 +48,11 @@ const version = {
],
},
}
;<FindReviewersLayout
authors={authors}
papers={papers}
version={version}
toggleAuthor={() => () => null}
error={null}
/>
```