Commit 16ade351 authored by Audrey Hamelers's avatar Audrey Hamelers

Merge branch 'shared-data-model' into 'dev'

Shared data model to dev

Closes #166, #165, and #162

See merge request !22
parents 2f935b5f b57e68c7
Pipeline #10676 passed with stages
in 36 seconds
{
"presets": ["env", "react", "stage-2"]
"env": {
"production": {
"presets": ["env", "react", "stage-2"]
},
"development": {
"presets": ["env", "react", "stage-2"]
},
"test": {
}
}
}
\ No newline at end of file
......@@ -42,6 +42,7 @@ COPY test test
COPY webpack webpack
COPY server server
COPY ./*.sh ./
COPY ./*.js ./
ENV NODE_ENV ${NODE_ENV}
......@@ -50,4 +51,3 @@ RUN [ "npx", "pubsweet", "build"]
EXPOSE ${PORT:-3000}
CMD []
......@@ -20,6 +20,7 @@ MINIO_PORT=9000
MINIO_SECURITY=false
MINIO_BUCKET=manuscripts
MINIO_UPLOADS_FOLDER_NAME=uploads
MINIO_DATA_FOLDER_NAME=data
```
Configuration 2:
......@@ -34,6 +35,7 @@ MINIO_PORT=9000
MINIO_SECURITY=false
MINIO_BUCKET=manuscripts
MINIO_UPLOADS_FOLDER_NAME=uploads
MINIO_DATA_FOLDER_NAME=data
```
In both cases, be sure that the key and secret are the same as those in .env.minio
### 3. REST APIs to use
......
......@@ -3,18 +3,15 @@ import 'regenerator-runtime/runtime'
import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import createHistory from 'history/createBrowserHistory'
// import createHistory from 'history/createBrowserHistory'
import { configureStore, Root } from 'pubsweet-client'
import { Root } from 'pubsweet-client'
import { JournalProvider } from 'xpub-journal'
import theme from './epmc-theme'
import Routes from './routes'
const history = createHistory()
const store = configureStore(history, {})
const render = () => {
ReactDOM.render(
<AppContainer>
......@@ -22,12 +19,7 @@ const render = () => {
journal={{ name: 'Europe PMC plus' }}
style={{ height: '100%' }}
>
<Root
history={history}
routes={<Routes />}
store={store}
theme={theme}
/>
<Root connectToWebSocket="false" routes={<Routes />} theme={theme} />
</JournalProvider>
</AppContainer>,
document.getElementById('root'),
......
import React from 'react'
import styled, { createGlobalStyle } from 'styled-components'
import { compose } from 'recompose'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
// import PropTypes from 'prop-types'
import { withJournal } from 'xpub-journal'
import actions from 'pubsweet-client/src/actions'
import { Query } from 'react-apollo'
import { th } from '@pubsweet/ui-toolkit'
import { GlobalStyle, AppBar, Action, Icon, Link } from '@pubsweet/ui'
import { A } from './ui/'
import createHistory from 'history/createBrowserHistory'
import { Loading, LoadingIcon } from './ui/'
import mainLogo from '../assets/epmcpluslogo.png'
import { CURRENT_USER } from './helpers/AuthorizeGraphQLQueries'
const Global = createGlobalStyle`
html,
......@@ -17,7 +14,7 @@ const Global = createGlobalStyle`
#root,
#root > div,
#root > div > div { height:100%; }
body {
body {
font-family: ${th('fontInterface')};
}
div, p {
......@@ -104,7 +101,7 @@ const RightSection = styled.div`
`
const Section = styled.span`
display: inline-flex;
flex-wrap: wrap;
white-space: nowrap;
align-items: center;
height: 100%;
padding: 0 calc(${th('gridUnit')} * 2);
......@@ -112,6 +109,7 @@ const Section = styled.span`
button {
display: flex;
align-items: center;
white-space: nowrap;
&:focus {
box-shadow: none;
text-decoration: underline;
......@@ -163,24 +161,41 @@ const toggleLogout = sec => {
})
}
const logoutUser = () => {
localStorage.removeItem('token')
localStorage.removeItem('username')
createHistory({ forceRefresh: true }).push('/login')
}
const RightComponent = ({ user, loginLink, onLogoutClick }) => (
<RightSection>
{user && (
<Section>
<A>
<Icon color="currentColor" size={3}>
repeat
</Icon>
<LinkText>Helpdesk</LinkText>
</A>
</Section>
<React.Fragment>
<Section>
<Link
className={
window.location.pathname === '/admin-dashboard' && 'current'
}
to="/admin-dashboard"
>
<LinkText>Dashboard</LinkText>
</Link>
</Section>
<Section>
<Link
className={window.location.pathname === '/dashboard' && 'current'}
to="/dashboard"
>
<LinkText>My queue</LinkText>
</Link>
</Section>
</React.Fragment>
)}
<Section>
<Link to="/faq">
<Icon color="currentColor" size={3}>
help-circle
</Icon>
<LinkText>FAQs</LinkText>
<Link
className={window.location.pathname === '/user-guide' && 'current'}
to="/user-guide"
>
<LinkText>User Guide</LinkText>
</Link>
</Section>
{user ? (
......@@ -193,12 +208,13 @@ const RightComponent = ({ user, loginLink, onLogoutClick }) => (
<Icon color="currentColor" size={3}>
user
</Icon>
<LinkText>{user.username}</LinkText>
<Icon className="hidden" color="currentColor" size={3}>
log-out
</Icon>
<LinkText className="hidden">Logout</LinkText>
</Action>
<Link to="/my-account">
<LinkText>{user.identities[0].name.givenNames}</LinkText>
</Link>
</Section>
) : (
<Section>
......@@ -213,37 +229,42 @@ const RightComponent = ({ user, loginLink, onLogoutClick }) => (
</RightSection>
)
const App = ({
children,
currentUser,
journal,
logoutUser,
history,
match,
disableLinks,
}) => (
<Root>
<GlobalStyle />
<Global />
<NavSec>
<AppBar
brand={Branding()}
onLogoutClick={logoutUser}
rightComponent={RightComponent}
user={currentUser}
/>
</NavSec>
{children}
</Root>
export const UserContext = React.createContext()
const App = ({ children, journal, history, match }) => (
<Query
fetchPolicy="cache-and-network"
query={CURRENT_USER}
variables={{ email: localStorage.getItem('email') }}
>
{({ data, loading }) => {
if (loading) {
return (
<Loading>
<LoadingIcon />
</Loading>
)
}
const currentUser = data ? data.epmc_currentUser : undefined
return (
<UserContext.Provider value={currentUser}>
<Root>
<GlobalStyle />
<Global />
<NavSec>
<AppBar
brand={Branding()}
onLogoutClick={logoutUser}
rightComponent={RightComponent}
user={currentUser}
/>
</NavSec>
{children}
</Root>
</UserContext.Provider>
)
}}
</Query>
)
export default compose(
connect(
state => ({
currentUser: state.currentUser.user,
}),
{ logoutUser: actions.logoutUser },
),
withJournal,
withRouter,
)(App)
export default App
import React from 'react'
import PropTypes from 'prop-types'
import { Redirect, withRouter } from 'react-router-dom'
import { UserContext } from './App'
const AuthenticatedComponent = ({ children, location }) => (
<UserContext.Consumer>
{currentUser => {
if (!currentUser || !localStorage.getItem('token')) {
const { pathname, search = '' } = location
const url = pathname + search
return <Redirect to={`/login?next=${url}`} />
}
return children
}}
</UserContext.Consumer>
)
AuthenticatedComponent.propTypes = {
children: PropTypes.node.isRequired,
location: PropTypes.shape({
pathname: PropTypes.string.isRequired,
search: PropTypes.string.isRequired,
}).isRequired,
}
export default withRouter(AuthenticatedComponent)
import { connect } from 'react-redux'
import { compose } from 'recompose'
import { withRouter } from 'react-router-dom'
import { actions } from 'pubsweet-client'
import { ConnectPage } from 'xpub-connect'
import EPMCCreated from './Create'
import { mapStateToProps, mapDispatchToProps } from './createsubmission'
export default compose(
ConnectPage(({ match }) => [
actions.getCollection({ id: match.params.project }),
actions.getFragments({ id: match.params.project }),
actions.getTeams(),
actions.getUsers(),
]),
connect(
mapStateToProps,
mapDispatchToProps,
),
withRouter,
)(EPMCCreated)
import React from 'react'
import { th } from '@pubsweet/ui-toolkit'
import { Button, H1, H2, Link } from '@pubsweet/ui'
import styled from 'styled-components'
import { Page } from './ui/'
import DashboardItem, { QAItem } from './DashboardItem'
const UploadContainer = styled.div`
display: flex;
justify-content: center;
`
const Section = styled.div`
margin: calc(${th('gridUnit')} * 3) 0;
&:not(:last-of-type) {
margin-bottom: calc(${th('gridUnit')} * 6);
}
`
const DashboardItemList = styled.ul`
list-style-type: none;
margin: 0;
padding: 0;
background-color: ${th('colorTextReverse')};
border: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')};
`
const Dashboard = ({
currentUser,
dashboard,
fragments,
deleteProject,
createManuscript,
}) => (
<Page>
<UploadContainer>
<H1>
<Link to="/create">
<Button primary style={{ fontSize: 'inherit' }}>
Submit a new manuscript
</Button>
</Link>
</H1>
</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>
<H2>My Submissions</H2>
<DashboardItemList>
{dashboard.owner.map(project => (
<DashboardItem
currentUser={currentUser}
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}
version={fragments[project.fragments.slice(-1)[0]]}
/>
))}
</DashboardItemList>
</Section>
)}
{!!dashboard.reviewer.length && (
<Section>
<H2>To review</H2>
<DashboardItemList>
{dashboard.reviewer.map(project => (
<DashboardItem
currentUser={currentUser}
key={project.id}
project={project}
version={fragments[project.fragments.slice(-1)[0]]}
/>
))}
</DashboardItemList>
</Section>
)}
{!!dashboard.editor.length && (
<Section>
<H2>To QA</H2>
<DashboardItemList>
{dashboard.editor.map(project => (
<QAItem
currentUser={currentUser}
key={project.id}
project={project}
version={fragments[project.fragments.slice(-1)[0]]}
/>
))}
</DashboardItemList>
</Section>
)}
</Page>
)
export default Dashboard
import { compose } 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 Dashboard from './Dashboard'
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.getFragments(),
actions.getTeams(),
actions.getUsers(),
]),
connect(
state => {
const { collections, fragments } = state
// const { conversion, teams } = 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 = {
// editor: newestFirst(
// unassignedCollections
// .concat(myCollections)
// .filter(
// (collection, index, items) =>
// items.findIndex(item => item.id === collection.id) === index,
// ),
// ),
editor: sortedCollections.filter(
collection =>
collection.status === 'submitted' ||
collection.status === 'tagging' ||
collection.status === 'xml-qa' ||
collection.status === 'xml-triage',
),
owner: sortedCollections.filter(
collection =>
collection.owners &&
collection.owners.some(owner => owner.id === currentUser.id),
),
// reviewer: newestFirst(teams
// .filter(team => team.group === 'reviewer'
// && team.object.type === 'collection'
// && team.members.includes(currentUser.id))
// .map(team => collections.find(collection => {
// return collection.id === team.object
// }))),
reviewer: sortedCollections.filter(
collection =>
collection.reviewers &&
collection.reviewers.some(
reviewer => reviewer && reviewer.user === currentUser.id,
),
),
hideDashboard: false,
}
return { collections, fragments, currentUser, dashboard }
},
(dispatch, { history }) => ({
deleteProject: collection =>
dispatch(actions.deleteCollection(collection)),
reviewerResponse: (project, version, reviewer, status) =>
dispatch(reviewerResponse(project, version, reviewer, status)),
}),
),
withRouter,
)(Dashboard)
......@@ -42,25 +42,24 @@ const FileTable = ({ files }) => (
<th>Filename</th>
<th>Label</th>
</tr>
{files.map(
file =>
file.type === 'manuscript' ? (
<tr key="manuscript">
<td>manuscript</td>
<td>
<FilePreview file={file} />
</td>
<td />
</tr>
) : (
<tr key={file.url}>
<td>{file.type}</td>
<td>
<FilePreview file={file} />
</td>
<td>{file.label}</td>
</tr>
),
{files.map(file =>
file.type === 'manuscript' ? (
<tr key="manuscript">
<td>manuscript</td>
<td>
<FilePreview file={file} />
</td>
<td />
</tr>
) : (
<tr key={file.url}>
<td>{file.type}</td>
<td>
<FilePreview file={file} />
</td>
<td>{file.label}</td>
</tr>
),
)}
</tbody>
</Table>
......
......@@ -147,23 +147,19 @@ class PubMedSearch extends React.Component {
const date = new Date(result.sortpubdate)
const metadata = {
title: result.title,
articleId: [
articleIds: [
{
type: 'pmid',
pubIdType: 'pmid',
id: result.uid,
},
],
publicationDate: [
publicationDates: [
{
day: date.getDate(),
month: date.getMonth() + 1,
year: date.getFullYear(),
type: 'ppub',
date,
},
],
journalMeta: {
nlmuniqueid: result.nlmuniqueid,
title: result.source,
},
unmatchedJournal: result.source,
}
this.props.citationData(metadata)
}
......@@ -288,20 +284,18 @@ class PubMedSearch extends React.Component {
<LoadingIcon />
</Loading>
)}
{results.length < hitcount &&
!loading && (
<LoadMore onClick={this.onLoad} secondary>
Load More Results
</LoadMore>
)}
{results.length < hitcount && !loading && (
<LoadMore onClick={this.onLoad} secondary>
Load More Results
</LoadMore>
)}
</Results>
)}
{results.length === 0 &&
loading && (
<Loading>
<LoadingIcon />
</Loading>
)}
{results.length === 0 && loading && (
<Loading>
<LoadingIcon />
</Loading>
)}
</div>
)}
</SearchArea>
......
import React from 'react'
import { H4, Link } from '@pubsweet/ui'
import { A } from './ui'
const SignInFooter = () => (
<div>
<H4>Need help depositing a manuscript?</H4>
<ul>
<li>