Commit ee6a419e authored by Sam Galson's avatar Sam Galson

Merge branch 'master' of xpub/xpub (move components from xpub monorepo)

parents a89ca0d7 93c6d1ab
Pipeline #5003 failed with stages
in 6 minutes and 24 seconds
## component-app
The aim of this package is to group things that are shared by apps.
*Note:
To be moved back into xpub-collabra.*
\ No newline at end of file
{
"name": "pubsweet-component-xpub-app",
"version": "0.0.2",
"main": "src",
"author": "Collaborative Knowledge Foundation",
"license": "MIT",
"files": [
"src",
"dist"
],
"dependencies": {
"@pubsweet/ui": "3.0.0",
"classnames": "^2.2.5",
"prop-types": "^15.5.10",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-redux": "^5.0.2",
"react-router-dom": "^4.2.2",
"recompose": "^0.26.0",
"redux": "^3.6.0",
"xpub-journal": "^0.0.2"
},
"peerDependencies": {
"prop-types": "^15.5.10",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-redux": "^5.0.2",
"react-router-dom": "^4.2.2"
}
}
import React from 'react'
import { compose } from 'recompose'
import { connect } from 'react-redux'
// import PropTypes from 'prop-types'
import { AppBar } from '@pubsweet/ui'
import { withJournal } from 'xpub-journal'
import actions from 'pubsweet-client/src/actions'
const App = ({ children, currentUser, journal, logoutUser }) => (
<div>
<AppBar
brand={journal.metadata.name}
onLogoutClick={logoutUser}
user={currentUser}
/>
<div>{children}</div>
</div>
)
export default compose(
connect(
state => ({
currentUser: state.currentUser.user,
}),
{ logoutUser: actions.logoutUser },
),
withJournal,
)(App)
import App from './App'
export default App
module.exports = {
frontend: {
components: [() => require('./components')],
},
}
## pubsweet-component-xpub-dashboard
A PubSweet component that provides the xpub dashboard, listing submissions that are available for the current user to take action on.
\ No newline at end of file
{
"name": "pubsweet-component-xpub-dashboard",
"version": "0.0.2",
"main": "src",
"author": "Collaborative Knowledge Foundation",
"license": "MIT",
"files": [
"src",
"dist"
],
"dependencies": {
"@pubsweet/ui": "3.0.0",
"classnames": "^2.2.5",
"lodash": "^4.17.4",
"prop-types": "^15.5.10",
"pubsweet-client": "^2.1.0",
"pubsweet-component-ink-frontend": "^1.0.0",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-dropzone": "^4.1.2",
"react-moment": "^0.6.1",
"react-redux": "^5.0.2",
"react-router-dom": "^4.2.2",
"recompose": "^0.26.0",
"redux": "^3.6.0",
"styled-components": "^2.4.0",
"xpub-connect": "^0.0.2",
"xpub-journal": "^0.0.2",
"xpub-selectors": "^0.0.2",
"xpub-upload": "^0.0.2"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"css-loader": "^0.28.4",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"faker": "^4.1.0",
"file-loader": "^1.1.5",
"identity-obj-proxy": "^3.0.0",
"jest": "^22.1.1",
"node-sass": "^4.5.3",
"react-styleguidist": "^6.2.5",
"react-test-renderer": "^16.2.0",
"redux-mock-store": "^1.4.0",
"sass-loader": "^6.0.6",
"style-loader": "^0.19.0",
"webpack": "^3.8.1",
"webpack-node-externals": "^1.6.0",
"xpub-styleguide": "^0.0.2"
},
"jest": {
"moduleNameMapper": {
"\\.s?css$": "identity-obj-proxy"
},
"transformIgnorePatterns": [
"/node_modules/(?!@?pubsweet)"
],
"globals": {
"PUBSWEET_COMPONENTS": []
}
},
"scripts": {
"styleguide": "styleguidist server",
"styleguide:build": "styleguidist build",
"clean": "rimraf dist",
"prebuild": "npm run clean && npm run lint",
"build": "webpack --progress --profile",
"test": "jest"
}
}
import React from 'react'
import { compose, withProps } from 'recompose'
import { Menu } from '@pubsweet/ui'
import { withJournal } from 'xpub-journal'
// TODO: select multiple editors
const AssignEditor = ({
project,
team,
teamName,
teamTypeName,
options,
addUserToTeam,
}) => (
<Menu
label={teamName}
onChange={user => {
addUserToTeam({
group: 'editor',
name: teamName,
project,
team,
teamTypeName,
user,
})
}}
options={options}
placeholder="Assign an editor…"
value={team ? team.members[0] : null}
/>
)
export default compose(
withJournal,
withProps(({ journal, teamTypeName }) => ({
teamName: journal.roles[teamTypeName],
})),
)(AssignEditor)
A drop-down menu for assigning an editor to a project.
```js
const project = {
id: faker.random.uuid(),
};
const team = {
members: []
};
const options = [
{
value: faker.random.uuid(),
label: faker.internet.userName(),
},
{
value: faker.random.uuid(),
label: faker.internet.userName(),
},
{
value: faker.random.uuid(),
label: faker.internet.userName(),
}
];
<AssignEditor
project={project}
team={team}
teamName="Senior Editor"
teamTypeName="seniorEditor"
options={options}
addUserToTeam={value => console.log(value)}
/>
```
import { connect } from 'react-redux'
import { compose } from 'recompose'
import AssignEditor from './AssignEditor'
import { addUserToTeam } from '../redux/teams'
const editorOption = user => ({
label: user.username, // TODO: name
value: user.id,
})
export default compose(
connect(
(state, { project, teamTypeName }) => ({
options:
state.users &&
!state.users.isFetching &&
state.users.users
// .filter(user => user.roles.includes(teamType)) // TODO
.map(editorOption),
team:
state.teams &&
state.teams.find(
team =>
team.object.type === 'collection' &&
team.object.id === project.id &&
team.teamType.name === teamTypeName,
),
}),
{
addUserToTeam,
},
),
)(AssignEditor)
import React from 'react'
import { Page, Section, Heading, UploadContainer } from './molecules/Page'
import UploadManuscript from './UploadManuscript'
import withVersion from './withVersion'
import EditorItem from './sections/EditorItem'
import OwnerItem from './sections/OwnerItem'
import ReviewerItem from './sections/ReviewerItem'
const OwnerItemWithVersion = withVersion(OwnerItem)
const EditorItemWithVersion = withVersion(EditorItem)
const ReviewerItemWithVersion = withVersion(ReviewerItem)
const Dashboard = ({
AssignEditor,
currentUser,
conversion,
dashboard,
deleteProject,
reviewerResponse,
uploadManuscript,
}) => (
<Page>
<UploadContainer>
<UploadManuscript
conversion={conversion}
uploadManuscript={uploadManuscript}
/>
</UploadContainer>
{!dashboard.owner.length &&
!dashboard.reviewer.length &&
!dashboard.editor.length && (
<UploadContainer>
Nothing to do at the moment. Please upload a document.
</UploadContainer>
)}
{!!dashboard.owner.length && (
<Section>
<Heading>My Submissions</Heading>
{dashboard.owner.map(project => (
<OwnerItemWithVersion
deleteProject={() =>
// eslint-disable-next-line no-alert
window.confirm(
'Are you sure you want to delete this submission?',
) && deleteProject(project)
}
key={project.id}
project={project}
/>
))}
</Section>
)}
{!!dashboard.reviewer.length && (
<Section>
<Heading>To review</Heading>
{dashboard.reviewer.map(project => (
<ReviewerItemWithVersion
currentUser={currentUser}
key={project.id}
project={project}
reviewerResponse={reviewerResponse}
/>
))}
</Section>
)}
{!!dashboard.editor.length && (
<Section>
<Heading>My Manuscripts</Heading>
{dashboard.editor.map(project => (
<EditorItemWithVersion
AssignEditor={AssignEditor}
key={project.id}
project={project}
/>
))}
</Section>
)}
</Page>
)
export default Dashboard
import React from 'react'
import Enzyme, { shallow } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Dashboard from './Dashboard'
import { Section, Heading, UploadContainer } from './molecules/Page'
// this should be elsewhere
Enzyme.configure({ adapter: new Adapter() })
jest.mock('config', () => ({ 'pubsweet-client': {} }))
const getProjects = section =>
section
.children()
.not(Heading)
.map(c => c.props().project)
describe('Dashboard', () => {
const makeWrapper = (props = {}) => {
props = Object.assign(
{
dashboard: {},
conversion: {},
},
props,
)
props.dashboard = Object.assign(
{
owner: [],
reviewer: [],
editor: [],
},
props.dashboard,
)
return shallow(<Dashboard {...props} />)
}
it('shows a message when there are no projects', () => {
const dashboard = makeWrapper()
expect(dashboard.find(UploadContainer)).toHaveLength(2)
expect(dashboard.find(Section)).toHaveLength(0)
})
it('shows a list of projects submitted by the current user', () => {
const project = { id: 1 }
const dashboard = makeWrapper({
dashboard: { owner: [project] },
})
expect(dashboard.find('.empty')).toHaveLength(0)
const section = dashboard.find(Section)
expect(section).toHaveLength(1)
expect(getProjects(section)).toEqual([project])
})
it('shows a list of projects to be reviewed', () => {
const project = { id: 1 }
const dashboard = makeWrapper({
dashboard: { reviewer: [project] },
})
expect(dashboard.find(UploadContainer)).toHaveLength(1)
const section = dashboard.find(Section)
expect(section).toHaveLength(1)
expect(getProjects(section)).toEqual([project])
})
it('shows a list of projects of which the current user is the editor', () => {
const project = { id: 1 }
const dashboard = makeWrapper({
dashboard: { editor: [project] },
})
expect(dashboard.find(UploadContainer)).toHaveLength(1)
const section = dashboard.find(Section)
expect(section).toHaveLength(1)
expect(getProjects(section)).toEqual([project])
})
})
import React from 'react'
import { MemoryRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import { combineReducers } from 'redux'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import Enzyme, { mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import { ThemeProvider } from 'styled-components'
import { reducers } from 'pubsweet-client'
import conversion from '../redux/conversion'
import DashboardPage from './DashboardPage'
import { Section, UploadContainer } from './molecules/Page'
// this should be elsewhere
Enzyme.configure({ adapter: new Adapter() })
jest.mock('config', () => ({ 'pubsweet-client': {} }))
// Mock out the API
jest.mock('pubsweet-client/src/helpers/api', () => ({
get: jest.fn(url => {
// Whatever the request is, return an empty array
const response = []
return new Promise(resolve => resolve(response))
}),
}))
global.window.localStorage = {
getItem: jest.fn(() => 'tok123'),
}
reducers.conversion = conversion
const reducer = combineReducers(reducers)
const middlewares = [thunk]
const mockStore = () =>
configureMockStore(middlewares)(actions =>
actions.reduce(reducer, {
currentUser: { isAuthenticated: true },
conversion: { converting: false },
}),
)
describe('DashboardPage', () => {
it('runs', done => {
const store = mockStore()
const page = mount(
<MemoryRouter>
<ThemeProvider theme={{ colorPrimary: 'blue' }}>
<Provider store={store}>
<DashboardPage />
</Provider>
</ThemeProvider>
</MemoryRouter>,
)
setImmediate(() => {
page.update()
expect(page.find(UploadContainer)).toHaveLength(2)
expect(page.find(Section)).toHaveLength(0)
done()
})
})
})
import { compose, withProps } from 'recompose'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { actions } from 'pubsweet-client'
import { newestFirst, selectCurrentUser } from 'xpub-selectors'
import { ConnectPage } from 'xpub-connect'
import { uploadManuscript } from '../redux/conversion'
import Dashboard from './Dashboard'
import AssignEditorContainer from './AssignEditorContainer'
const reviewerResponse = (project, version, reviewer, status) => dispatch => {
reviewer.status = status
return dispatch(
actions.updateFragment(project, {
id: version.id,
rev: version.rev,
reviewers: version.reviewers,
}),
)
}
export default compose(
ConnectPage(() => [
actions.getCollections(),
actions.getTeams(),
actions.getUsers(),
]),
connect(
state => {
const { collections } = state
// const { conversion, teams } = state
const { conversion } = state
const currentUser = selectCurrentUser(state)
const sortedCollections = newestFirst(collections)
// const unassignedCollections = sortedCollections.filter(
// collection =>
// collection.status === 'submitted' &&
// !teams.some(
// team =>
// team.object.type === 'collection' &&
// team.object.id === collection.id &&
// team.teamType.name === 'handlingEditor',
// ),
// )
// const myCollections = teams
// .filter(
// team =>
// team.group === 'editor' &&
// team.object.type === 'collection' &&
// team.members.includes(currentUser.id),
// )
// .map(team =>
// collections.find(collection => collection.id === team.object.id),
// )
const dashboard = {