diff --git a/packages/component-fixture-manager/src/fixtures/fragments.js b/packages/component-fixture-manager/src/fixtures/fragments.js index 2ee8c78db32d02ae01404cabaaae3ae88aecb113..10fcf908ed077f7ecda22e87cb745af8b0a539f1 100644 --- a/packages/component-fixture-manager/src/fixtures/fragments.js +++ b/packages/component-fixture-manager/src/fixtures/fragments.js @@ -86,6 +86,7 @@ const fragments = { ], authors: [ { + email: chance.email(), id: submittingAuthor.id, isSubmitting: true, isCorresponding: false, @@ -124,6 +125,7 @@ const fragments = { }, authors: [ { + email: chance.email(), id: submittingAuthor.id, isSubmitting: true, isCorresponding: false, @@ -162,6 +164,7 @@ const fragments = { }, authors: [ { + email: chance.email(), id: submittingAuthor.id, isSubmitting: true, isCorresponding: false, diff --git a/packages/components-faraday/package.json b/packages/components-faraday/package.json index 30188c9434fbf453d0fe3c775c6e7af55cf8cc26..729e5ebdbf6769f3f0065943e96c68f12ef0e215 100644 --- a/packages/components-faraday/package.json +++ b/packages/components-faraday/package.json @@ -18,5 +18,12 @@ "redux": "^3.6.0", "redux-form": "7.0.3", "styled-components": "^3.1.6" + }, + "scripts": { + "test": "jest" + }, + "jest": { + "verbose": true, + "testRegex": "/src/.*.test.js$" } } diff --git a/packages/components-faraday/src/components/Dashboard/DashboardFilters.js b/packages/components-faraday/src/components/Dashboard/DashboardFilters.js index 3fcb148873f588d14b77daad777a28ed68ccd1a3..06063f305074ccf1ead8d370a29b3ea418887d09 100644 --- a/packages/components-faraday/src/components/Dashboard/DashboardFilters.js +++ b/packages/components-faraday/src/components/Dashboard/DashboardFilters.js @@ -31,6 +31,7 @@ const DashboardFilters = ({ inline onChange={changeFilterValue('order')} options={getFilterOptions('order')} + value={getDefaultFilterValue('order')} /> </FilterGroup> </FiltersContainer> @@ -67,5 +68,4 @@ const FilterGroup = styled.div` flex-direction: column; margin-left: calc(${th('subGridUnit')} * 2); ` - // #endregion diff --git a/packages/components-faraday/src/components/Dashboard/DashboardPage.js b/packages/components-faraday/src/components/Dashboard/DashboardPage.js index 910f834eb180aa08002051faa1a4b70eeda37490..999e87de37369464f4a6b38a3bc45eec0e58a67e 100644 --- a/packages/components-faraday/src/components/Dashboard/DashboardPage.js +++ b/packages/components-faraday/src/components/Dashboard/DashboardPage.js @@ -10,18 +10,10 @@ import { newestFirst, selectCurrentUser } from 'xpub-selectors' import { createDraftSubmission } from 'pubsweet-component-wizard/src/redux/conversion' import { Dashboard } from './' -import withFilters from './withFilters' import { getHandlingEditors } from '../../redux/editors' -import cfg from '../../../../xpub-faraday/config/default' import { getUserPermissions } from '../../../../component-faraday-selectors/src' -const statuses = get(cfg, 'statuses') - -const getUserRole = (user, role) => { - if (user.admin) return 'admin' - if (user.editorInChief) return 'editorInChief' - return role -} +import { priorityFilter, importanceSort, withFiltersHOC } from '../Filters' export default compose( ConnectPage(() => [actions.getCollections(), actions.getUsers()]), @@ -70,36 +62,9 @@ export default compose( ), withRouter, withJournal, - withFilters({ - priority: { - options: [ - { label: 'All', value: 'all' }, - { label: 'Needs Attention', value: 'needsAttention' }, - { label: 'In Progress', value: 'inProgress' }, - ], - filterFn: (filterValue, { currentUser, userPermissions = [] }) => ({ - id, - status = 'draft', - }) => { - const permission = userPermissions.find(p => p.objectId === id) - const userRole = getUserRole(currentUser, get(permission, 'role')) - switch (filterValue) { - case 'needsAttention': - return get(statuses, `${status}.${userRole}.needsAttention`) - case 'inProgress': - return !get(statuses, `${status}.${userRole}.needsAttention`) - default: - return true - } - }, - }, - order: { - options: [ - { label: 'Newest first', value: 'newest' }, - { label: 'Oldest first', value: 'oldest' }, - ], - filterFn: () => () => true, - }, + withFiltersHOC({ + priority: priorityFilter, + order: importanceSort, }), withContext( { diff --git a/packages/components-faraday/src/components/Filters/importanceSort.js b/packages/components-faraday/src/components/Filters/importanceSort.js new file mode 100644 index 0000000000000000000000000000000000000000..b86f37c80951a9a4ebbf89bb26ea03d78acb37ca --- /dev/null +++ b/packages/components-faraday/src/components/Filters/importanceSort.js @@ -0,0 +1,31 @@ +import { get } from 'lodash' + +import { utils } from './' +import cfg from '../../../../xpub-faraday/config/default' + +const statuses = get(cfg, 'statuses') +export const SORT_VALUES = { + MORE_IMPORTANT: 'more_important', + LESS_IMPORTANT: 'less_important', +} + +const options = [ + { label: 'Imporant first', value: SORT_VALUES.MORE_IMPORTANT }, + { label: 'Less important first', value: SORT_VALUES.LESS_IMPORTANT }, +] + +const sortFn = sortValue => (item1, item2) => { + const item1Importance = utils.getCollectionImportance(statuses, item1) + const item2Importance = utils.getCollectionImportance(statuses, item2) + + if (sortValue === SORT_VALUES.MORE_IMPORTANT) { + return item2Importance - item1Importance + } + return item1Importance - item2Importance +} + +export default { + sortFn, + options, + type: 'sort', +} diff --git a/packages/components-faraday/src/components/Filters/index.js b/packages/components-faraday/src/components/Filters/index.js new file mode 100644 index 0000000000000000000000000000000000000000..cec651407a1906360e0be3b88876740fde6ba1aa --- /dev/null +++ b/packages/components-faraday/src/components/Filters/index.js @@ -0,0 +1,6 @@ +import * as utils from './utils' + +export { utils } +export { default as withFiltersHOC } from './withFilters' +export { default as priorityFilter } from './priorityFilter' +export { default as importanceSort } from './importanceSort' diff --git a/packages/components-faraday/src/components/Filters/priorityFilter.js b/packages/components-faraday/src/components/Filters/priorityFilter.js new file mode 100644 index 0000000000000000000000000000000000000000..4222f85fd52fb9caa18b561bdfb8b9960c7b28da --- /dev/null +++ b/packages/components-faraday/src/components/Filters/priorityFilter.js @@ -0,0 +1,43 @@ +import { get } from 'lodash' + +import { utils } from './' +import cfg from '../../../../xpub-faraday/config/default' + +const statuses = get(cfg, 'statuses') + +export const FILTER_VALUES = { + ALL: 'all', + NEEDS_ATTENTION: 'needsAttention', + IN_PROGRESS: 'inProgress', +} + +const options = [ + { label: 'All', value: FILTER_VALUES.ALL }, + { label: 'Needs Attention', value: FILTER_VALUES.NEEDS_ATTENTION }, + { label: 'In Progress', value: FILTER_VALUES.IN_PROGRESS }, +] + +const filterFn = (filterValue, { currentUser, userPermissions = [] }) => ({ + id = '', + fragments = [], + status = 'draft', +}) => { + const permission = userPermissions.find( + ({ objectId }) => objectId === id || fragments.includes(objectId), + ) + const userRole = utils.getUserRole(currentUser, get(permission, 'role')) + switch (filterValue) { + case FILTER_VALUES.NEEDS_ATTENTION: + return get(statuses, `${status}.${userRole}.needsAttention`) + case FILTER_VALUES.IN_PROGRESS: + return !get(statuses, `${status}.${userRole}.needsAttention`) + default: + return true + } +} + +export default { + options, + filterFn, + type: 'filter', +} diff --git a/packages/components-faraday/src/components/Filters/priorityFilter.test.js b/packages/components-faraday/src/components/Filters/priorityFilter.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ce0d9b6b0afc43dccb4174f5a7efc12717ef5046 --- /dev/null +++ b/packages/components-faraday/src/components/Filters/priorityFilter.test.js @@ -0,0 +1,358 @@ +import fixturesService from 'pubsweet-component-fixture-service' + +import { FILTER_VALUES } from './priorityFilter' +import { priorityFilter, utils } from './' + +const { + fixtures: { collections: { collection }, users, teams }, +} = fixturesService + +const { filterFn } = priorityFilter + +describe('Priority filter function for reviewersInvited status', () => { + describe('ALL', () => { + it('should return true if ALL is selected', () => { + const filterResult = filterFn(FILTER_VALUES.ALL, { currentUser: {} })({ + ...collection, + status: 'reviewersInvited', + }) + expect(filterResult).toBeTruthy() + }) + }) + + describe('NEEDS ATTENTION', () => { + it('should return falsy for AUTHOR', () => { + const filterResult = filterFn(FILTER_VALUES.NEEDS_ATTENTION, { + currentUser: users.author, + userPermissions: [utils.parsePermission(teams.authorTeam)], + })({ + ...collection, + status: 'reviewersInvited', + }) + expect(filterResult).toBeFalsy() + }) + + it('should return truthy for REVIEWER', () => { + const filterResult = filterFn(FILTER_VALUES.NEEDS_ATTENTION, { + currentUser: users.reviewer, + userPermissions: [utils.parsePermission(teams.revTeam)], + })({ + ...collection, + status: 'reviewersInvited', + }) + expect(filterResult).toBeTruthy() + }) + it('should return truthy for HANDLING EDITOR', () => { + const filterResult = filterFn(FILTER_VALUES.NEEDS_ATTENTION, { + currentUser: users.handlingEditor, + userPermissions: [utils.parsePermission(teams.heTeam)], + })({ + ...collection, + status: 'reviewersInvited', + }) + expect(filterResult).toBeTruthy() + }) + it('should return falsy for EDITOR IN CHIEF', () => { + const filterResult = filterFn(FILTER_VALUES.NEEDS_ATTENTION, { + currentUser: users.editorInChief, + })({ + ...collection, + status: 'reviewersInvited', + }) + expect(filterResult).toBeFalsy() + }) + it('should return truthy for ADMIN', () => { + const filterResult = filterFn(FILTER_VALUES.NEEDS_ATTENTION, { + currentUser: users.admin, + })({ + ...collection, + status: 'reviewersInvited', + }) + expect(filterResult).toBeTruthy() + }) + }) + + describe('IN PROGRESS', () => { + it('should return truthy for AUTHOR', () => { + const filterResult = filterFn(FILTER_VALUES.IN_PROGRESS, { + currentUser: users.author, + userPermissions: [utils.parsePermission(teams.authorTeam)], + })({ + ...collection, + status: 'reviewersInvited', + }) + expect(filterResult).toBeTruthy() + }) + + it('should return falsy for REVIEWER', () => { + const filterResult = filterFn(FILTER_VALUES.IN_PROGRESS, { + currentUser: users.reviewer, + userPermissions: [utils.parsePermission(teams.revTeam)], + })({ + ...collection, + status: 'reviewersInvited', + }) + expect(filterResult).toBeFalsy() + }) + it('should return falsy for HANDLING EDITOR', () => { + const filterResult = filterFn(FILTER_VALUES.IN_PROGRESS, { + currentUser: users.handlingEditor, + userPermissions: [utils.parsePermission(teams.heTeam)], + })({ + ...collection, + status: 'reviewersInvited', + }) + expect(filterResult).toBeFalsy() + }) + it('should return truthy for EDITOR IN CHIEF', () => { + const filterResult = filterFn(FILTER_VALUES.IN_PROGRESS, { + currentUser: users.editorInChief, + })({ + ...collection, + status: 'reviewersInvited', + }) + expect(filterResult).toBeTruthy() + }) + it('should return falsy for ADMIN', () => { + const filterResult = filterFn(FILTER_VALUES.IN_PROGRESS, { + currentUser: users.admin, + })({ + ...collection, + status: 'reviewersInvited', + }) + expect(filterResult).toBeFalsy() + }) + }) +}) + +describe('Priority filter function for technicalChecks status', () => { + describe('ALL', () => { + it('should return true if ALL is selected', () => { + const filterResult = filterFn(FILTER_VALUES.ALL, { currentUser: {} })({ + ...collection, + status: 'technicalChecks', + }) + expect(filterResult).toBeTruthy() + }) + }) + + describe('NEEDS ATTENTION', () => { + it('should return falsy for AUTHOR', () => { + const filterResult = filterFn(FILTER_VALUES.NEEDS_ATTENTION, { + currentUser: users.author, + userPermissions: [utils.parsePermission(teams.authorTeam)], + })({ + ...collection, + status: 'technicalChecks', + }) + expect(filterResult).toBeFalsy() + }) + + it('should return truthy for REVIEWER', () => { + const filterResult = filterFn(FILTER_VALUES.NEEDS_ATTENTION, { + currentUser: users.reviewer, + userPermissions: [utils.parsePermission(teams.revTeam)], + })({ + ...collection, + status: 'technicalChecks', + }) + expect(filterResult).toBeFalsy() + }) + it('should return truthy for HANDLING EDITOR', () => { + const filterResult = filterFn(FILTER_VALUES.NEEDS_ATTENTION, { + currentUser: users.handlingEditor, + userPermissions: [utils.parsePermission(teams.heTeam)], + })({ + ...collection, + status: 'technicalChecks', + }) + expect(filterResult).toBeFalsy() + }) + it('should return falsy for EDITOR IN CHIEF', () => { + const filterResult = filterFn(FILTER_VALUES.NEEDS_ATTENTION, { + currentUser: users.editorInChief, + })({ + ...collection, + status: 'technicalChecks', + }) + expect(filterResult).toBeFalsy() + }) + it('should return truthy for ADMIN', () => { + const filterResult = filterFn(FILTER_VALUES.NEEDS_ATTENTION, { + currentUser: users.admin, + })({ + ...collection, + status: 'technicalChecks', + }) + expect(filterResult).toBeTruthy() + }) + }) + + describe('IN PROGRESS', () => { + it('should return truthy for AUTHOR', () => { + const filterResult = filterFn(FILTER_VALUES.IN_PROGRESS, { + currentUser: users.author, + userPermissions: [utils.parsePermission(teams.authorTeam)], + })({ + ...collection, + status: 'technicalChecks', + }) + expect(filterResult).toBeTruthy() + }) + + it('should return falsy for REVIEWER', () => { + const filterResult = filterFn(FILTER_VALUES.IN_PROGRESS, { + currentUser: users.reviewer, + userPermissions: [utils.parsePermission(teams.revTeam)], + })({ + ...collection, + status: 'technicalChecks', + }) + expect(filterResult).toBeTruthy() + }) + it('should return falsy for HANDLING EDITOR', () => { + const filterResult = filterFn(FILTER_VALUES.IN_PROGRESS, { + currentUser: users.handlingEditor, + userPermissions: [utils.parsePermission(teams.heTeam)], + })({ + ...collection, + status: 'technicalChecks', + }) + expect(filterResult).toBeTruthy() + }) + it('should return truthy for EDITOR IN CHIEF', () => { + const filterResult = filterFn(FILTER_VALUES.IN_PROGRESS, { + currentUser: users.editorInChief, + })({ + ...collection, + status: 'technicalChecks', + }) + expect(filterResult).toBeTruthy() + }) + it('should return falsy for ADMIN', () => { + const filterResult = filterFn(FILTER_VALUES.IN_PROGRESS, { + currentUser: users.admin, + })({ + ...collection, + status: 'technicalChecks', + }) + expect(filterResult).toBeFalsy() + }) + }) +}) + +describe('Priority filter function for pendingApproval status', () => { + describe('ALL', () => { + it('should return true if ALL is selected', () => { + const filterResult = filterFn(FILTER_VALUES.ALL, { currentUser: {} })({ + ...collection, + status: 'technicalChecks', + }) + expect(filterResult).toBeTruthy() + }) + }) + + describe('NEEDS ATTENTION', () => { + it('should return falsy for AUTHOR', () => { + const filterResult = filterFn(FILTER_VALUES.NEEDS_ATTENTION, { + currentUser: users.author, + userPermissions: [utils.parsePermission(teams.authorTeam)], + })({ + ...collection, + status: 'pendingApproval', + }) + expect(filterResult).toBeFalsy() + }) + + it('should return truthy for REVIEWER', () => { + const filterResult = filterFn(FILTER_VALUES.NEEDS_ATTENTION, { + currentUser: users.reviewer, + userPermissions: [utils.parsePermission(teams.revTeam)], + })({ + ...collection, + status: 'pendingApproval', + }) + expect(filterResult).toBeFalsy() + }) + it('should return truthy for HANDLING EDITOR', () => { + const filterResult = filterFn(FILTER_VALUES.NEEDS_ATTENTION, { + currentUser: users.handlingEditor, + userPermissions: [utils.parsePermission(teams.heTeam)], + })({ + ...collection, + status: 'pendingApproval', + }) + expect(filterResult).toBeFalsy() + }) + it('should return falsy for EDITOR IN CHIEF', () => { + const filterResult = filterFn(FILTER_VALUES.NEEDS_ATTENTION, { + currentUser: users.editorInChief, + })({ + ...collection, + status: 'pendingApproval', + }) + expect(filterResult).toBeTruthy() + }) + it('should return truthy for ADMIN', () => { + const filterResult = filterFn(FILTER_VALUES.NEEDS_ATTENTION, { + currentUser: users.admin, + })({ + ...collection, + status: 'pendingApproval', + }) + expect(filterResult).toBeTruthy() + }) + }) + + describe('IN PROGRESS', () => { + it('should return truthy for AUTHOR', () => { + const filterResult = filterFn(FILTER_VALUES.IN_PROGRESS, { + currentUser: users.author, + userPermissions: [utils.parsePermission(teams.authorTeam)], + })({ + ...collection, + status: 'pendingApproval', + }) + expect(filterResult).toBeTruthy() + }) + + it('should return falsy for REVIEWER', () => { + const filterResult = filterFn(FILTER_VALUES.IN_PROGRESS, { + currentUser: users.reviewer, + userPermissions: [utils.parsePermission(teams.revTeam)], + })({ + ...collection, + status: 'pendingApproval', + }) + expect(filterResult).toBeTruthy() + }) + it('should return falsy for HANDLING EDITOR', () => { + const filterResult = filterFn(FILTER_VALUES.IN_PROGRESS, { + currentUser: users.handlingEditor, + userPermissions: [utils.parsePermission(teams.heTeam)], + })({ + ...collection, + status: 'pendingApproval', + }) + expect(filterResult).toBeTruthy() + }) + it('should return truthy for EDITOR IN CHIEF', () => { + const filterResult = filterFn(FILTER_VALUES.IN_PROGRESS, { + currentUser: users.editorInChief, + })({ + ...collection, + status: 'pendingApproval', + }) + expect(filterResult).toBeFalsy() + }) + it('should return falsy for ADMIN', () => { + const filterResult = filterFn(FILTER_VALUES.IN_PROGRESS, { + currentUser: users.admin, + })({ + ...collection, + status: 'pendingApproval', + }) + expect(filterResult).toBeFalsy() + }) + }) +}) diff --git a/packages/components-faraday/src/components/Filters/utils.js b/packages/components-faraday/src/components/Filters/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..8dabf8b2f1c77b530bf761937f6a1b8fb96329a8 --- /dev/null +++ b/packages/components-faraday/src/components/Filters/utils.js @@ -0,0 +1,42 @@ +import { get } from 'lodash' + +export const hydrateFilters = defaultValues => { + const filterValues = localStorage.getItem('filterValues') + if (filterValues) return JSON.parse(filterValues) + return defaultValues +} + +export const makeFilterFunctions = config => + Object.entries(config) + .filter(([filterKey, { type }]) => type === 'filter') + .map(([filterKey, { filterFn }]) => ({ + key: filterKey, + fn: filterFn, + })) + +export const makeSortFunction = config => { + const [sortKey, { sortFn }] = Object.entries(config).find( + ([filterKey, { type }]) => type !== 'filter', + ) + return { + sortKey, + sortFn, + } +} + +export const makeFilterValues = config => + Object.keys(config).reduce((acc, el) => ({ ...acc, [el]: '' }), {}) + +export const getUserRole = (user, role) => { + if (user.admin) return 'admin' + if (user.editorInChief) return 'editorInChief' + return role +} + +export const parsePermission = permission => ({ + objectId: permission.object.id, + role: permission.group, +}) + +export const getCollectionImportance = (statuses, item) => + get(statuses, `${get(item, 'status') || 'draft'}.importance`) diff --git a/packages/components-faraday/src/components/Dashboard/withFilters.js b/packages/components-faraday/src/components/Filters/withFilters.js similarity index 60% rename from packages/components-faraday/src/components/Dashboard/withFilters.js rename to packages/components-faraday/src/components/Filters/withFilters.js index 9f1ccb12b4f37dd11a5868175a4bb77272f081be..22c8ce546d6f8875b1e480316f5569a48cc470cc 100644 --- a/packages/components-faraday/src/components/Dashboard/withFilters.js +++ b/packages/components-faraday/src/components/Filters/withFilters.js @@ -1,23 +1,19 @@ import { get } from 'lodash' import { compose, withState, withHandlers } from 'recompose' -const hydrateFilters = defaultValues => { - const filterValues = localStorage.getItem('filterValues') - if (filterValues) return JSON.parse(filterValues) - return defaultValues -} +import { utils } from './' export default config => Component => { - const filterFns = Object.entries(config).map(([filterKey, { filterFn }]) => ({ - key: filterKey, - fn: filterFn, - })) - const filterValues = Object.keys(config).reduce( - (acc, el) => ({ ...acc, [el]: '' }), - {}, - ) + const filterFns = utils.makeFilterFunctions(config) + const filterValues = utils.makeFilterValues(config) + const { sortKey, sortFn } = utils.makeSortFunction(config) + return compose( - withState('filterValues', 'setFilterValues', hydrateFilters(filterValues)), + withState( + 'filterValues', + 'setFilterValues', + utils.hydrateFilters(filterValues), + ), withHandlers({ getFilterOptions: () => key => get(config, `${key}.options`) || [], getDefaultFilterValue: ({ filterValues }) => key => @@ -42,10 +38,12 @@ export default config => Component => { ) }, filterItems: ({ filterValues, ...props }) => items => - filterFns.reduce( - (acc, { key, fn }) => acc.filter(fn(filterValues[key], props)), - items, - ), + filterFns + .reduce( + (acc, { key, fn }) => acc.filter(fn(filterValues[key], props)), + items, + ) + .sort(sortFn(filterValues[sortKey], props)), }), )(Component) } diff --git a/packages/xpub-faraday/config/authsome-helpers.js b/packages/xpub-faraday/config/authsome-helpers.js index ad75355dd14fd8f26a70e2f75bb2f8c74a87054d..8b5a135a30aa8f7bc660d518664db6b32001017a 100644 --- a/packages/xpub-faraday/config/authsome-helpers.js +++ b/packages/xpub-faraday/config/authsome-helpers.js @@ -4,7 +4,9 @@ const config = require('config') const statuses = config.get('statuses') +const keysToOmit = ['email', 'id'] const publicStatusesPermissions = ['author', 'reviewer'] +const authorAllowedStatuses = ['revisionRequested', 'rejected', 'accepted'] const parseAuthorsData = (coll, matchingCollPerm) => { if (['reviewer'].includes(matchingCollPerm.permission)) { @@ -124,6 +126,55 @@ const hasFragmentInDraft = async ({ object, Fragment }) => { return isInDraft(fragment) } +const filterAuthorRecommendationData = recommendation => { + const { comments } = recommendation + return { + ...recommendation, + comments: comments ? comments.filter(c => c.public) : [], + } +} + +const stripeCollectionByRole = (coll = {}, role = '') => { + if (role === 'author') { + const { handlingEditor } = coll + + if (!authorAllowedStatuses.includes(coll.status)) { + coll = { + ...coll, + handlingEditor: handlingEditor && { + ...omit(handlingEditor, keysToOmit), + name: 'Assigned', + }, + } + } + } + return coll +} + +const stripeFragmentByRole = (fragment = {}, role = '', user = {}) => { + const { recommendations, files, authors } = fragment + switch (role) { + case 'author': + return { + ...fragment, + recommendations: recommendations + ? recommendations.map(filterAuthorRecommendationData) + : [], + } + case 'reviewer': + return { + ...fragment, + files: omit(files, ['coverLetter']), + authors: authors.map(a => omit(a, ['email'])), + recommendations: recommendations + ? recommendations.filter(r => r.userId === user.id) + : [], + } + default: + return fragment + } +} + module.exports = { filterObjectData, parseAuthorsData, @@ -137,4 +188,6 @@ module.exports = { hasPermissionForObject, isInDraft, hasFragmentInDraft, + stripeCollectionByRole, + stripeFragmentByRole, } diff --git a/packages/xpub-faraday/config/authsome-mode.js b/packages/xpub-faraday/config/authsome-mode.js index 043e5e8c87b8c4c89929611852dab47bbe350e86..2aada0e12aef4d742f83a5847ae150a6a58079fa 100644 --- a/packages/xpub-faraday/config/authsome-mode.js +++ b/packages/xpub-faraday/config/authsome-mode.js @@ -1,5 +1,5 @@ const config = require('config') -const { get, pickBy, omit } = require('lodash') +const { get, pickBy } = require('lodash') const statuses = config.get('statuses') const helpers = require('./authsome-helpers') @@ -103,9 +103,13 @@ async function applyAuthenticatedUserPolicy(user, operation, object, context) { collection.fragments.includes(p.objectId), ) const visibleStatus = get(statuses, `${status}.${role}.label`) + const parsedCollection = helpers.stripeCollectionByRole( + collection, + role, + ) return { - ...collection, + ...parsedCollection, visibleStatus, } }, @@ -113,11 +117,7 @@ async function applyAuthenticatedUserPolicy(user, operation, object, context) { } if (get(object, 'type') === 'fragment') { - if (helpers.isOwner({ user, object })) { - return true - } - - if (helpers.isInDraft(object)) { + if (helpers.isInDraft(object) && !helpers.isOwner({ user, object })) { return false } @@ -133,17 +133,8 @@ async function applyAuthenticatedUserPolicy(user, operation, object, context) { if (!permission) return false return { - filter: fragment => { - // handle other roles - if (permission.role === 'reviewer') { - fragment.files = omit(fragment.files, ['coverLetter']) - fragment.authors = fragment.authors.map(a => omit(a, ['email'])) - fragment.recommendations = fragment.recommendations - ? fragment.recommendations.filter(r => r.userId === user.id) - : [] - } - return fragment - }, + filter: fragment => + helpers.stripeFragmentByRole(fragment, permission.role, user), } } @@ -167,7 +158,7 @@ async function applyAuthenticatedUserPolicy(user, operation, object, context) { } if (operation === 'POST') { - // allow everytone to create manuscripts and versions + // allow everyone to create manuscripts and versions if (createPaths.includes(object.path)) { return true } diff --git a/packages/xpub-faraday/config/default.js b/packages/xpub-faraday/config/default.js index fdc085fce29a13ba103beab4140153f7b727ee84..6cb6dc5d36bc60bc761d745178c84e714937d08c 100644 --- a/packages/xpub-faraday/config/default.js +++ b/packages/xpub-faraday/config/default.js @@ -222,7 +222,7 @@ module.exports = { needsAttention: false, }, handlingEditor: { - label: 'Make Decision', + label: 'Make Recommendation', needsAttention: true, }, editorInChief: { @@ -234,7 +234,7 @@ module.exports = { needsAttention: false, }, admin: { - label: 'Make Decision', + label: 'Make Recommendation', needsAttention: true, }, }, diff --git a/packages/xpub-faraday/config/test.js b/packages/xpub-faraday/config/test.js new file mode 100644 index 0000000000000000000000000000000000000000..9950c9b354fa8710a4f043a07ac2b86a3cf6bd2f --- /dev/null +++ b/packages/xpub-faraday/config/test.js @@ -0,0 +1,3 @@ +const defaultConfig = require('xpub-faraday/config/default') + +module.exports = defaultConfig diff --git a/packages/xpub-faraday/package.json b/packages/xpub-faraday/package.json index 2460482894ba362cf6d2356471ae669c9c9c71b8..e4e829deab3a05be7c02ee7c4bd237ed68d18ca0 100644 --- a/packages/xpub-faraday/package.json +++ b/packages/xpub-faraday/package.json @@ -63,6 +63,7 @@ "file-loader": "^1.1.5", "html-webpack-plugin": "^2.24.0", "joi-browser": "^10.0.6", + "jest": "^22.1.1", "react-hot-loader": "^3.1.1", "string-replace-loader": "^1.3.0", "style-loader": "^0.19.0", @@ -71,6 +72,10 @@ "webpack-dev-middleware": "^1.12.0", "webpack-hot-middleware": "^2.20.0" }, + "jest": { + "verbose": true, + "testRegex": "/tests/.*.test.js$" + }, "scripts": { "setupdb": "pubsweet setupdb ./", "start": "pubsweet start", @@ -79,6 +84,8 @@ "start-now": "echo $secret > config/local-development.json && npm run server", "build": "NODE_ENV=production pubsweet build", - "clean": "rm -rf node_modules" + "clean": "rm -rf node_modules", + "debug": "pgrep -f startup/start.js | xargs kill -sigusr1", + "test": "jest" } } diff --git a/packages/xpub-faraday/tests/authsome-helpers.test.js b/packages/xpub-faraday/tests/authsome-helpers.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8999b892b2b80a1ef5b92ffa35f8f823d84f9580 --- /dev/null +++ b/packages/xpub-faraday/tests/authsome-helpers.test.js @@ -0,0 +1,117 @@ +const { cloneDeep, get } = require('lodash') +const fixturesService = require('pubsweet-component-fixture-service') +const ah = require('../config/authsome-helpers') + +describe('Authsome Helpers', () => { + let testFixtures = {} + beforeEach(() => { + testFixtures = cloneDeep(fixturesService.fixtures) + }) + it('stripeCollection - should return collection', () => { + const { collection } = testFixtures.collections + const result = ah.stripeCollectionByRole(collection) + expect(result).toBeTruthy() + }) + it('stripeFragment - should return fragment', () => { + const { fragment } = testFixtures.fragments + const result = ah.stripeFragmentByRole(fragment) + expect(result).toBeTruthy() + }) + + it('stripeCollection - author should not see HE name before recommendation made', () => { + const { collection } = testFixtures.collections + collection.status = 'underReview' + + const result = ah.stripeCollectionByRole(collection, 'author') + const { handlingEditor = {} } = result + + expect(handlingEditor.email).toBeFalsy() + expect(handlingEditor.name).toEqual('Assigned') + }) + + it('stripeCollection - author should see HE name after recommendation made', () => { + const { collection } = testFixtures.collections + collection.status = 'revisionRequested' + + const result = ah.stripeCollectionByRole(collection, 'author') + const { handlingEditor = {} } = result + + expect(handlingEditor.name).not.toEqual('Assigned') + }) + + it('stripeCollection - other user than author should see HE name before recommendation made', () => { + const { collection } = testFixtures.collections + collection.status = 'underReview' + + const result = ah.stripeCollectionByRole(collection, 'admin') + const { handlingEditor = {} } = result + + expect(handlingEditor.name).not.toEqual('Assigned') + }) + + it('stripeCollection - other user than author should see HE name after recommendation made', () => { + const { collection } = testFixtures.collections + collection.status = 'revisionRequested' + + const result = ah.stripeCollectionByRole(collection, 'admin') + const { handlingEditor = {} } = result + + expect(handlingEditor.name).not.toEqual('Assigned') + }) + + it('stripeCollection - returns if collection does not have HE', () => { + const { collection } = testFixtures.collections + delete collection.handlingEditor + + const result = ah.stripeCollectionByRole(collection, 'admin') + expect(result.handlingEditor).toBeFalsy() + }) + + it('stripeFragment - reviewer should not see authors email', () => { + const { fragment } = testFixtures.fragments + const result = ah.stripeFragmentByRole(fragment, 'reviewer') + const { authors = [] } = result + expect(authors[0].email).toBeFalsy() + }) + it('stripeFragment - other roles than reviewer should see authors emails', () => { + const { fragment } = testFixtures.fragments + const result = ah.stripeFragmentByRole(fragment, 'author') + const { authors = [] } = result + + expect(authors[0].email).toBeTruthy() + }) + + it('stripeFragment - reviewer should not see cover letter', () => { + const { fragment } = testFixtures.fragments + const result = ah.stripeFragmentByRole(fragment, 'reviewer') + const { files = {} } = result + expect(files.coverLetter).toBeFalsy() + }) + it('stripeFragment - reviewer should not see others reviews', () => { + const { fragment } = testFixtures.fragments + const result = ah.stripeFragmentByRole(fragment, 'reviewer') + const { recommendations } = result + expect(recommendations).toEqual([]) + }) + + it('stripeFragment - author should not see private recommendations comments', () => { + const { fragment } = testFixtures.fragments + fragment.recommendations = [ + { + comments: [ + { + content: 'private', + public: false, + }, + { + content: 'public', + public: true, + }, + ], + }, + ] + const result = ah.stripeFragmentByRole(fragment, 'author') + const privateComments = get(result, 'recommendations[0].comments') + expect(privateComments).toHaveLength(1) + }) +})