Commit 9a7aab6d authored by Yannis Barlas's avatar Yannis Barlas

feat(dashboard): add ability to assign science officer to manuscript

parent 5f0c733a
......@@ -5,6 +5,7 @@ import { Query, Mutation } from 'react-apollo'
import styled from 'styled-components'
import { Form, Formik } from 'formik'
import { keys, sortBy } from 'lodash'
import gql from 'graphql-tag'
import { Button, H2, H4 } from '@pubsweet/ui'
import { th } from '@pubsweet/ui-toolkit'
......@@ -16,6 +17,12 @@ import GET_USERS from '../queries/getUsers'
import Loading from './Loading'
const CLEAN_UP_GLOBAL_TEAM_MEMBERSHIP = gql`
mutation CleanUpGlobalTeamMembership {
cleanUpGlobalTeamMembership
}
`
const TeamHeadingWrapper = styled(H4)`
font-size: ${th('fontSizeHeading4')};
line-height: ${th('lineHeightHeading3')};
......@@ -136,7 +143,7 @@ class TeamManager extends React.Component {
}
handleSubmit = (formValues, formikBag) => {
const { updateTeam, teams } = this.props
const { cleanUp, updateTeam, teams } = this.props
const data = keys(formValues).map(teamType => ({
id: teams.find(t => t.teamType === teamType).id,
......@@ -158,6 +165,7 @@ class TeamManager extends React.Component {
Promise.all(promises).then(res => {
this.showRibbon()
formikBag.resetForm(formValues)
cleanUp()
})
}
......@@ -216,20 +224,25 @@ class TeamManager extends React.Component {
export default () => (
<Mutation mutation={UPDATE_TEAM} refetchQueries={[{ query: GET_TEAMS }]}>
{updateTeam => (
<Query query={GET_TEAMS}>
{({ data: { teams }, loading }) => (
<Query query={GET_USERS}>
{({ data: { users } }) => (
<TeamManager
loading={loading}
teams={teams}
updateTeam={updateTeam}
users={users}
/>
<Mutation mutation={CLEAN_UP_GLOBAL_TEAM_MEMBERSHIP}>
{cleanUp => (
<Query query={GET_TEAMS}>
{({ data: { teams }, loading }) => (
<Query query={GET_USERS}>
{({ data: { users } }) => (
<TeamManager
cleanUp={cleanUp}
loading={loading}
teams={teams}
updateTeam={updateTeam}
users={users}
/>
)}
</Query>
)}
</Query>
)}
</Query>
</Mutation>
)}
</Mutation>
)
......@@ -33,12 +33,17 @@ const mapProps = args => {
const editorTeamForArticle = getEditorTeamForArticle(
args.getTeams.data.teamsForArticle,
)
const allEditorIds = getAllEditors(args.getGlobalTeams.data.globalTeams).map(
e => e.id,
)
const editor = getEditor(editorTeamForArticle)
// console.log('editor', editorTeamForArticle)
return {
allEditors: getAllEditors(args.getGlobalTeams.data.globalTeams),
editor,
editor: editor && allEditorIds.includes(editor.id) ? editor : null,
editorTeamId: editorTeamForArticle && editorTeamForArticle.id,
loading: args.getGlobalTeams.loading || args.getTeams.loading,
updateCurrentlyWith: args.updateCurrentlyWith.updateCurrentlyWith,
......
/* eslint-disable react/prop-types */
import React from 'react'
import { adopt } from 'react-adopt'
import {
getGlobalTeams,
getTeamsForArticle,
updateCurrentlyWith,
updateTeam,
} from './pieces'
const mapper = {
getGlobalTeams,
getTeams: props => getTeamsForArticle(props),
updateCurrentlyWith,
updateTeam,
}
const getAllScienceOfficers = globalTeams =>
globalTeams && globalTeams.find(t => t.teamType === 'scienceOfficers').members
const getScienceOfficerTeamForArticle = teamsForArticle =>
teamsForArticle && teamsForArticle.find(t => t.teamType === 'scienceOfficer')
const getScienceOfficer = team => {
if (!team) return null
if (team.members.length > 0) return team.members[0]
return undefined
}
const mapProps = args => {
const scienceOfficerTeamForArticle = getScienceOfficerTeamForArticle(
args.getTeams.data.teamsForArticle,
)
const allScienceOfficerIds = getAllScienceOfficers(
args.getGlobalTeams.data.globalTeams,
).map(so => so.id)
const scienceOfficer = getScienceOfficer(scienceOfficerTeamForArticle)
// console.log('science officer', scienceOfficerTeamForArticle)
return {
allScienceOfficers: getAllScienceOfficers(
args.getGlobalTeams.data.globalTeams,
),
loading: args.getGlobalTeams.loading || args.getTeams.loading,
scienceOfficer:
scienceOfficer && allScienceOfficerIds.includes(scienceOfficer.id)
? scienceOfficer
: null,
scienceOfficerTeamId:
scienceOfficerTeamForArticle && scienceOfficerTeamForArticle.id,
updateCurrentlyWith: args.updateCurrentlyWith.updateCurrentlyWith,
updateTeam: args.updateTeam.updateTeam,
}
}
const Composed = adopt(mapper, mapProps)
const ComposedAssignScienceOfficer = props => {
const { render, ...otherProps } = props
return (
<Composed {...otherProps}>
{mappedProps => render({ ...mappedProps, ...otherProps })}
</Composed>
)
}
export default ComposedAssignScienceOfficer
......@@ -7,6 +7,7 @@ import { get, union } from 'lodash'
import {
getArticleForEditor,
getGlobalTeams,
getExternalTeamsForManuscript,
getReviewsForArticle,
getTeamsForArticle,
......@@ -26,6 +27,7 @@ import {
const mapper = {
getArticleForEditor: props => getArticleForEditor(props),
getExternalTeamsForManuscript: props => getExternalTeamsForManuscript(props),
getGlobalTeams,
getReviewsForArticle: props => getReviewsForArticle(props),
getTeamsForArticle: props => getTeamsForArticle(props),
manuscriptMetadataUpdate,
......@@ -33,6 +35,12 @@ const mapper = {
updateArticleForEditor,
}
const getAllScienceOfficers = globalTeams =>
globalTeams && globalTeams.find(t => t.teamType === 'scienceOfficers').members
const getAllEditors = globalTeams =>
globalTeams && globalTeams.find(t => t.teamType === 'editors').members
const mapProps = args => {
const teams = get(args.getTeamsForArticle, 'data.teamsForArticle')
const externalTeams = get(
......@@ -51,6 +59,20 @@ const mapProps = args => {
const acceptedReviewers = getTeamMembers('reviewersAccepted')
const rejectedReviewers = getTeamMembers('reviewersRejected')
const allScienceOfficerIds =
args.getGlobalTeams.data.globalTeams &&
getAllScienceOfficers(args.getGlobalTeams.data.globalTeams).map(so => so.id)
const scienceOfficer = getScienceOfficer(
args.getTeamsForArticle.data.teamsForArticle,
)
const allEditorIds =
args.getGlobalTeams.data.globalTeams &&
getAllEditors(args.getGlobalTeams.data.globalTeams).map(e => e.id)
const editor = getEditor(args.getTeamsForArticle.data.teamsForArticle)
const externalReviewersTeam = getExternalTeamMembers('externalReviewers')
const externalReviewersInvited = getExternalTeamMembers(
'externalReviewersInvited',
......@@ -70,7 +92,7 @@ const mapProps = args => {
return {
article: args.getArticleForEditor.data.manuscript,
editor: getEditor(args.getTeamsForArticle.data.teamsForArticle),
editor: editor && allEditorIds.includes(editor.id) ? editor : null,
editorSuggestedReviewers: union(reviewersTeam, externalReviewersTeam),
loading:
args.getTeamsForArticle.loading ||
......@@ -82,9 +104,10 @@ const mapProps = args => {
rejected: rejectedCount,
},
reviews: get(args.getReviewsForArticle, 'data.reviewsForArticle'),
scienceOfficer: getScienceOfficer(
args.getTeamsForArticle.data.teamsForArticle,
),
scienceOfficer:
scienceOfficer && allScienceOfficerIds.includes(scienceOfficer.id)
? scienceOfficer
: null,
sendChat: args.sendChatMutation.sendChat,
updateArticle: args.updateArticleForEditor.updateArticle,
updateMetadata:
......
......@@ -9,4 +9,5 @@ const getGlobalTeamsQuery = ({ render }) => (
<Query query={GET_GLOBAL_TEAMS}>{render}</Query>
)
export { GET_GLOBAL_TEAMS }
export default getGlobalTeamsQuery
......@@ -28,4 +28,5 @@ const getTeamsForArticleQuery = props => {
)
}
export { GET_TEAMS_FOR_ARTICLE }
export default getTeamsForArticleQuery
......@@ -6,6 +6,7 @@ import { Mutation } from 'react-apollo'
import { GET_TEAMS } from './getTeams'
import UPDATE_TEAM from '../../../mutations/updateTeam'
import { GET_DASHBOARD_ARTICLES } from './getDashboardArticles'
import { GET_GLOBAL_TEAMS } from './getGlobalTeams'
import { withCurrentUser } from '../../../userContext'
const updateTeamMutation = ({ currentUser, render }) => {
......@@ -13,6 +14,9 @@ const updateTeamMutation = ({ currentUser, render }) => {
{
query: GET_TEAMS,
},
{
query: GET_GLOBAL_TEAMS,
},
{
query: GET_DASHBOARD_ARTICLES,
variables: { currentUserId: currentUser.id },
......
/* eslint-disable react/prop-types */
import React from 'react'
import React, { Fragment } from 'react'
import styled from 'styled-components'
import { th } from '@pubsweet/ui-toolkit'
import ComposedAssignEditor from '../compose/AssignEditor'
import Select from '../ui/Select'
const Label = styled.div`
align-self: center;
margin-right: ${th('gridUnit')};
`
const mapUserToSelect = user =>
user && {
label: user.username,
......@@ -44,12 +52,15 @@ const AssignEditor = props => {
}
return (
<Select
isSearchable={false}
onChange={handleChange}
options={options}
value={value}
/>
<Fragment>
<Label>Editor</Label>
<Select
isSearchable={false}
onChange={handleChange}
options={options}
value={value}
/>
</Fragment>
)
}
......
/* eslint-disable react/prop-types */
import React, { Fragment } from 'react'
import styled from 'styled-components'
import { th } from '@pubsweet/ui-toolkit'
import ComposedAssignScienceOfficer from '../compose/AssignScienceOfficer'
import Select from '../ui/Select'
const Label = styled.div`
align-self: center;
margin: 0 ${th('gridUnit')};
`
const mapUserToSelect = user =>
user && {
label: user.username,
value: user.id,
}
const mapUsersToOptions = users =>
users && users.map(user => mapUserToSelect(user))
const AssignEditor = props => {
const {
allScienceOfficers,
articleId,
scienceOfficer,
scienceOfficerTeamId,
loading,
updateCurrentlyWith,
updateTeam,
} = props
if (loading) return null
const options = mapUsersToOptions(allScienceOfficers)
const value = mapUserToSelect(scienceOfficer)
const handleChange = newValue => {
const input = { members: [newValue.value] }
updateTeam({ variables: { id: scienceOfficerTeamId, input } }).then(() => {
const editorId = newValue.value
const data = {
currentlyWith: editorId,
id: articleId,
}
updateCurrentlyWith({ variables: { data } })
})
}
return (
<Fragment>
<Label>Science Officer</Label>
<Select
isSearchable={false}
onChange={handleChange}
options={options}
value={value}
/>
</Fragment>
)
}
const Composed = ({ articleId }) => (
<ComposedAssignScienceOfficer articleId={articleId} render={AssignEditor} />
)
export default Composed
......@@ -7,6 +7,7 @@ import { Action, ActionGroup } from '@pubsweet/ui'
import { th } from '@pubsweet/ui-toolkit'
import AssignEditor from '../dashboard/AssignEditor'
import AssignScienceOfficer from '../dashboard/AssignScienceOfficer'
import SectionItemWithStatus from './SectionItemWithStatus'
const Wrapper = styled.div`
......@@ -21,6 +22,10 @@ const ActionsWrapper = styled.div`
flex-shrink: 0;
`
const Separator = styled.span`
width: calc(${th('gridUnit')} * 2);
`
const Actions = ({ articleId }) => (
<ActionsWrapper>
<ActionGroup>
......@@ -39,6 +44,8 @@ const EditorSectionItem = props => {
<EditorToolRow>
<AssignEditor articleId={articleId} />
<Separator />
<AssignScienceOfficer articleId={articleId} />
</EditorToolRow>
</Wrapper>
)
......
......@@ -240,9 +240,9 @@ const permissions = {
if (!isEqual(changed, ['members'])) return false
const affectedIds = xor(current.members, update.members)
// Only global users can update the editor team members for an object
// Only global users can update the editor & SO team members for an object
const global = isGlobal(user, context)
if (teamType === 'editor') return global
if (teamType === 'editor' || teamType === 'scienceOfficer') return global
// Only editors can update the reviewer teams
const editor = isEditor(user, context)
......
......@@ -17,6 +17,7 @@ const typeDefinitionPaths = [
'./externalTeam/externalTeam.graphql',
'./externalUser/externalUser.graphql',
'./manuscript/manuscript.graphql',
'./team/team.graphql',
]
const typeDefs = loadGQLFiles(typeDefinitionPaths)
......
......@@ -30,12 +30,6 @@ const createManuscript = async (_, variables, ctx) => {
status: newStatus,
}).save()
const scienceOfficerTeams = await Team.findByField({
global: true,
teamType: 'scienceOfficers',
})
const scienceOfficerId = scienceOfficerTeams[0].members[0]
// TO DO -- change objectType to manuscript
// Create teams for manuscript
const teams = [
......@@ -50,7 +44,7 @@ const createManuscript = async (_, variables, ctx) => {
teamType: 'author',
},
// Editor team with no one assigned yet
// Editor team
{
members: [],
name: `editor-${manuscript.id}`,
......@@ -61,9 +55,9 @@ const createManuscript = async (_, variables, ctx) => {
teamType: 'editor',
},
// Science officer team with first (and only) SO pre-assigned
// Science officer team
{
members: scienceOfficerId ? [scienceOfficerId] : [],
members: [],
name: `science-officer-${manuscript.id}`,
object: {
objectId: manuscript.id,
......@@ -72,7 +66,7 @@ const createManuscript = async (_, variables, ctx) => {
teamType: 'scienceOfficer',
},
// Reviewer team with no one assigned yet
// Reviewer team
{
members: [],
name: `reviewers-${manuscript.id}`,
......@@ -83,7 +77,7 @@ const createManuscript = async (_, variables, ctx) => {
teamType: 'reviewers',
},
// Invited reviewers team with no one assigned yet
// Invited reviewers team
{
members: [],
name: `reviewers-invited-${manuscript.id}`,
......@@ -94,7 +88,7 @@ const createManuscript = async (_, variables, ctx) => {
teamType: 'reviewersInvited',
},
// Reviewers that accepted with no one assigned yet
// Reviewers that accepted
{
members: [],
name: `reviewers-accepted-${manuscript.id}`,
......@@ -105,7 +99,7 @@ const createManuscript = async (_, variables, ctx) => {
teamType: 'reviewersAccepted',
},
// Reviewers that rejected with no one assigned yet
// Reviewers that rejected
{
members: [],
name: `reviewers-rejected-${manuscript.id}`,
......
......@@ -12,6 +12,7 @@ const {
createManuscript,
sendChat,
} = require('./manuscript/manuscript.resolver')
const { cleanUpGlobalTeamMembership } = require('./team/team.resolver')
const resolvers = {
Query: {
......@@ -20,6 +21,7 @@ const resolvers = {
},
Mutation: {
addExternalReviewer,
cleanUpGlobalTeamMembership,
createManuscript,
inviteExternalReviewer,
normalizeTeamMembership,
......
extend type Mutation {
cleanUpGlobalTeamMembership: Boolean!
}
/* eslint-disable import/prefer-default-export */
const without = require('lodash/without')
const { Team } = require('pubsweet-server/src/models')
/*
This ensures that if there are any assigned editors or science officers
assigned to a manuscript that have been removed from the global teams,
they are unassigned.
*/
const cleanUpGlobalTeamMembership = async (_, __, ctx) => {
const globalTeams = await Team.findByField({ global: true })
const globalEditorTeam = globalTeams.find(t => t.teamType === 'editors')
const globalScienceOfficerTeam = globalTeams.find(
t => t.teamType === 'scienceOfficers',
)
const teams = await Team.all()
const editorTeams = teams.filter(t => t.teamType === 'editor')
const scienceOfficerTeams = teams.filter(t => t.teamType === 'scienceOfficer')
editorTeams.forEach(t => {
t.members.forEach(m => {
if (!globalEditorTeam.members.includes(m)) {
ctx.connectors.Team.update(
t.id,
{ members: without(t.members, m) },
ctx,
)
}
})
})
scienceOfficerTeams.forEach(t => {
t.members.forEach(m => {
if (!globalScienceOfficerTeam.members.includes(m)) {
ctx.connectors.Team.update(
t.id,
{ members: without(t.members, m) },
ctx,
)
}
})
})
return true
}
module.exports = { cleanUpGlobalTeamMembership }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment