diff --git a/Automation/src/test/java/CreateAccounts.java b/Automation/src/test/java/CreateAccounts.java index 6b02b0bc624df2120461c91c0d82dfd162bd4385..1f355202351239db98617d21a86a34a6354ebba6 100644 --- a/Automation/src/test/java/CreateAccounts.java +++ b/Automation/src/test/java/CreateAccounts.java @@ -55,7 +55,7 @@ public class CreateAccounts { try { - wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("button[data-test='new-manuscript']"))); + wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("button[data-test-id='new-manuscript']"))); } catch (NoSuchElementException e) { System.out.println(e.toString()); } @@ -110,7 +110,7 @@ public class CreateAccounts { try { - wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("button[data-test='new-manuscript']"))); + wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("button[data-test-id='new-manuscript']"))); } catch (NoSuchElementException e) { System.out.println(e.toString()); } @@ -170,12 +170,12 @@ public class CreateAccounts { driver.findElement(By.cssSelector("button[type=submit]")).click(); try { - wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("button[data-test='button-add-user']"))); + wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("button[data-test-id='button-add-user']"))); } catch (NoSuchElementException e) { System.out.println(e.toString()); } assertEquals(URL + "admin/users", driver.getCurrentUrl() ); - assertEquals(" Add User", driver.findElement(By.cssSelector("button[data-test='button-add-user']")).getText()); + assertEquals(" Add User", driver.findElement(By.cssSelector("button[data-test-id='button-add-user']")).getText()); } } diff --git a/Automation/src/test/java/LoginTest.java b/Automation/src/test/java/LoginTest.java index 99e83fb74708a01b70e64082ad1176501dac8828..d64542498ef12a79e85a8e6aee0af7bb4c36e92a 100644 --- a/Automation/src/test/java/LoginTest.java +++ b/Automation/src/test/java/LoginTest.java @@ -48,7 +48,7 @@ public class LoginTest { WebElement validLogin = null; try { - validLogin = wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("button[data-test='new-manuscript']"))); + validLogin = wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("button[data-test-id='new-manuscript']"))); } catch (NoSuchElementException e) { } diff --git a/Automation/src/test/java/ManuscriptFlow.java b/Automation/src/test/java/ManuscriptFlow.java index b51ad5808addd9f38ff1eb3609605848daf59e05..2b0351604eec583d58ae59e67442d8dfbe5b8fc9 100644 --- a/Automation/src/test/java/ManuscriptFlow.java +++ b/Automation/src/test/java/ManuscriptFlow.java @@ -78,12 +78,12 @@ public class ManuscriptFlow { driver.findElement(By.cssSelector("button[data-test-id='new-manuscript']")).click(); try { - wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("button[data-test='submission-next']"))); + wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("button[data-test-id='submission-next']"))); } catch (NoSuchElementException e) { } - assertNotNull(driver.findElement(By.cssSelector("button[data-test='submission-next']"))); + assertNotNull(driver.findElement(By.cssSelector("button[data-test-id='submission-next']"))); // First step submission flow @@ -91,7 +91,7 @@ public class ManuscriptFlow { Utils.scrollToElement(driver, subButton); driver.findElement(By.cssSelector("[data-test-id='agree-checkbox']")).click(); - subButton.click(); + driver.findElement(By.cssSelector("button[data-test-id='submission-next']")).click(); // Second step submission flow try { @@ -148,12 +148,12 @@ public class ManuscriptFlow { element1.click(); try { - wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("button[data-test='submission-next']"))); + wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("button[data-test-id='submission-next']"))); } catch (NoSuchElementException e) { } - WebElement element2 = driver.findElement(By.cssSelector("button[data-test='submission-next']")); + WebElement element2 = driver.findElement(By.cssSelector("button[data-test-id='submission-next']")); Utils.scrollToElement(driver, element2); element2.click(); @@ -178,16 +178,19 @@ public class ManuscriptFlow { manuscriptId = Utils.mID(mURL); projectId = Utils.pID(mURL); - driver.findElement(By.cssSelector(/*"[data-test='upload-manuscripts']*/"input[type=\"file\"]")).sendKeys(Constants.fileManuscript); -// driver.findElement(By.cssSelector(/*"[data-test='upload-supplementary']*/ "input[type=\"file\"]")).sendKeys(Constants.fileSupplementary); -// driver.findElement(By.cssSelector("[data-test='upload-coverLetter'] input[type=\"file\"]")).sendKeys(Constants.fileCoverLetter); + driver.findElement(By.cssSelector(/*"[data-test-id='upload-manuscripts']*/"input[type=\"file\"]")).sendKeys(Constants.fileManuscript); +// driver.findElement(By.cssSelector("[data-test-id='upload-supplementary'] input[type=\"file\"]")).sendKeys(Constants.fileSupplementary); +// driver.findElement(By.cssSelector("[data-test-id='upload-coverLetter'] input[type=\"file\"]")).sendKeys(Constants.fileCoverLetter); try { - wait.until(ExpectedConditions.visibilityOf(driver.findElement(By.cssSelector("//span[text()='Progress Saved']")))); + wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("button[data-test-id='submission-next']"))); } catch (NoSuchElementException e) { } + // Confirmation modal + WebElement element3 = driver.findElement(By.cssSelector("button[data-test-id='submission-next']")); + try { wait.until(ExpectedConditions.visibilityOf(driver.findElement(By.cssSelector("[data-test='submission-next']")))); } catch (NoSuchElementException e) { diff --git a/Automation/target/test-classes/Constants.class b/Automation/target/test-classes/Constants.class new file mode 100644 index 0000000000000000000000000000000000000000..68f5d0974f805591d39f78d43fde36cc9337ebf7 Binary files /dev/null and b/Automation/target/test-classes/Constants.class differ diff --git a/Automation/target/test-classes/CreateAuthor.class b/Automation/target/test-classes/CreateAuthor.class new file mode 100644 index 0000000000000000000000000000000000000000..d1e3a30e0b3dccaf6b734477f1b6e5c13d401eb5 Binary files /dev/null and b/Automation/target/test-classes/CreateAuthor.class differ diff --git a/Automation/target/test-classes/LoginTest.class b/Automation/target/test-classes/LoginTest.class new file mode 100644 index 0000000000000000000000000000000000000000..65f092122d8b7d9c91c03fb301a44ce88bab4701 Binary files /dev/null and b/Automation/target/test-classes/LoginTest.class differ diff --git a/Automation/target/test-classes/Utils.class b/Automation/target/test-classes/Utils.class new file mode 100644 index 0000000000000000000000000000000000000000..6aef2c6abc7823d9e0e968ba0d1661279fcbea9f Binary files /dev/null and b/Automation/target/test-classes/Utils.class differ diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js index 424a53188c3058b27cd8a94af0fb92d34112611a..243136915cb994367d43744fa0c2822263679761 100644 --- a/packages/component-faraday-selectors/src/index.js +++ b/packages/component-faraday-selectors/src/index.js @@ -366,4 +366,14 @@ export const canMakeRecommendation = (state, collection, fragment = {}) => { const status = get(collection, 'status', 'draft') return isHE && canMakeRecommendationStatuses.includes(status) } + +export const canSubmitRevision = (state, collection, fragment) => { + const userId = get(state, 'currentUser.user.id') + const fragmentAuthors = chain(fragment) + .get('authors', []) + .map(a => a.id) + .value() + + return get(fragment, 'revision', null) && fragmentAuthors.includes(userId) +} // #endregion diff --git a/packages/component-faraday-ui/src/AppBarMenu.js b/packages/component-faraday-ui/src/AppBarMenu.js index 3a3a93ba7b682469bdcd67ff340efc3ebf8996f7..a4dd501c94d504b33d7273fa54f6d17df17d9d25 100644 --- a/packages/component-faraday-ui/src/AppBarMenu.js +++ b/packages/component-faraday-ui/src/AppBarMenu.js @@ -23,21 +23,31 @@ const AppBarMenu = ({ }) => currentUser.user ? ( <Root> - <User onClick={toggleMenu}> + <User data-test-id="admin-menu-button" onClick={toggleMenu}> <Text>{username}</Text> <Icon secondary size={2}> {expanded ? 'chevron-up' : 'chevron-down'} </Icon> </User> {expanded && ( - <Dropdown> + <Dropdown data-test-id="admin-menu-dropdown"> {currentUser.user.admin && ( - <DropdownOption onClick={goTo('/admin')}> + <DropdownOption + data-test-id="admin-dropdown-dashboard" + onClick={goTo('/admin')} + > Admin Dashboard </DropdownOption> )} - <DropdownOption onClick={goTo('/profile')}>My Profile</DropdownOption> - <DropdownOption onClick={logout}>Logout</DropdownOption> + <DropdownOption + data-test-id="admin-dropdown-profile" + onClick={goTo('/profile')} + > + My Profile + </DropdownOption> + <DropdownOption data-test-id="admin-dropdown-logout" onClick={logout}> + Logout + </DropdownOption> </Dropdown> )} {expanded && <ToggleOverlay onClick={toggleMenu} />} diff --git a/packages/component-faraday-ui/src/AuthorCard.js b/packages/component-faraday-ui/src/AuthorCard.js index bb4112cc6cb8edb64be1702b5132d208737f6dc7..2ace7fa985268bb535cdeff598a1a5ac7a030433 100644 --- a/packages/component-faraday-ui/src/AuthorCard.js +++ b/packages/component-faraday-ui/src/AuthorCard.js @@ -39,14 +39,14 @@ const AuthorTitle = ({ formSubmitting, toggleEditMode, isCorresponding, - isAuthorsFetching, }) => ( <Fragment> {!editMode ? ( <Fragment> {!isSubmitting && ( <OpenModal - isFetching={isAuthorsFetching} + isFetching={isFetching} + modalKey="deleteAuthor" onConfirm={deleteAuthor} subtitle={`${get(author, 'firstName', '')} ${get( author, @@ -78,6 +78,7 @@ const AuthorTitle = ({ ) : ( <Fragment> <IconButton + data-test-id="author-card-cancel" icon="x-circle" iconSize={2} onClick={toggleEditMode} @@ -90,6 +91,7 @@ const AuthorTitle = ({ </StyledSpinner> ) : ( <IconButton + data-test-id="author-card-save" disabled={formSubmitting} icon="check-circle" iconSize={2} @@ -218,6 +220,7 @@ const EnhancedAuthorEdit = compose( const Author = ({ author, listIndex, + isFetching, isAuthorEdit, deleteAuthor, toggleEditMode, @@ -230,6 +233,7 @@ const Author = ({ isAuthorEdit={isAuthorEdit} isAuthorsFetching={isAuthorsFetching} isCorresponding={author.isCorresponding} + isFetching={isFetching} isSubmitting={author.isSubmitting} listIndex={listIndex} toggleEditMode={toggleEditMode} @@ -275,6 +279,7 @@ const AuthorCard = ({ editMode={editMode} isAuthorEdit={isAuthorEdit} isAuthorsFetching={isAuthorsFetching} + isFetching={isFetching} listIndex={index} toggleEditMode={toggleEdit(index)} /> diff --git a/packages/component-faraday-ui/src/FileSection.js b/packages/component-faraday-ui/src/FileSection.js index 0da010405bfdf934b7c65ee88d17d44e89849397..596f65e8436d8362ab1fdf506c1f5b1dd822071c 100644 --- a/packages/component-faraday-ui/src/FileSection.js +++ b/packages/component-faraday-ui/src/FileSection.js @@ -44,7 +44,7 @@ const FileSection = ({ connectDropTarget, allowedFileExtensions, files = [], - onFilePick = () => {}, + onFilePick, onPreview, onDownload, onDelete, diff --git a/packages/component-faraday-ui/src/InviteReviewers.js b/packages/component-faraday-ui/src/InviteReviewers.js index 94ce84bf2e74299f42d21386418e181c0fd527ce..7c93ebb316f9a1d7a10c664548a6ee94c6864cdd 100644 --- a/packages/component-faraday-ui/src/InviteReviewers.js +++ b/packages/component-faraday-ui/src/InviteReviewers.js @@ -23,10 +23,20 @@ const InviteReviewers = ({ countries, handleSubmit, reset }) => ( <Row justify="space-between" mb={2}> <H4>Invite reviewer</H4> <Item justify="flex-end"> - <Button onClick={reset} size="small"> + <Button + data-type-id="button-invite-reviewer-clear-fields" + onClick={reset} + size="small" + > Clear Fields </Button> - <Button ml={2} onClick={handleSubmit} primary size="small"> + <Button + data-type-id="button-invite-reviewer-invite" + ml={2} + onClick={handleSubmit} + primary + size="small" + > Invite </Button> </Item> @@ -36,6 +46,7 @@ const InviteReviewers = ({ countries, handleSubmit, reset }) => ( <Label required>Email</Label> <ValidatedField component={TextField} + data-test-id="invite-reviewer-email" name="email" validate={[required, validators.emailValidator]} /> @@ -44,6 +55,7 @@ const InviteReviewers = ({ countries, handleSubmit, reset }) => ( <Label required>First Name</Label> <ValidatedField component={TextField} + data-test-id="invite-reviewer-first-name" name="firstName" validate={[required]} /> @@ -52,6 +64,7 @@ const InviteReviewers = ({ countries, handleSubmit, reset }) => ( <Label required>Last Name</Label> <ValidatedField component={TextField} + data-test-id="invite-reviewer-last-name" name="lastName" validate={[required]} /> @@ -60,6 +73,7 @@ const InviteReviewers = ({ countries, handleSubmit, reset }) => ( <Label required>Affiliation</Label> <ValidatedField component={TextField} + data-test-id="invite-reviewer-affiliation" name="affiliation" validate={[required]} /> diff --git a/packages/component-faraday-ui/src/RadioWithComments.js b/packages/component-faraday-ui/src/RadioWithComments.js index b11595c361030e3a93b05645666c0f799db827f8..785149e94c2ca25bd7bc8b0ee30f21eb5e9b286f 100644 --- a/packages/component-faraday-ui/src/RadioWithComments.js +++ b/packages/component-faraday-ui/src/RadioWithComments.js @@ -40,7 +40,12 @@ const RadioWithComments = ({ </Item> </Row> - <Row alignItems="center" justify="flex-start" mb={1}> + <Row + alignItems="center" + data-test-id={`submission-yes-or-no-${radioFieldName}`} + justify="flex-start" + mb={1} + > <Field component={({ input }) => <YesOrNo {...input} />} name={radioFieldName} @@ -49,7 +54,7 @@ const RadioWithComments = ({ {get(formValues, radioFieldName, '') === commentsOn && ( <RowOverrideAlert alignItems="center" justify="flex-start"> - <Item data-test="submission-conflicts-text" vertical> + <Item data-test-id="submission-conflicts-text" vertical> {commentsSubtitle && ( <Text secondary> {commentsSubtitle} diff --git a/packages/component-faraday-ui/src/WizardAuthors.js b/packages/component-faraday-ui/src/WizardAuthors.js index 7ea00f08d3c6b3118b2db4edbcd84bb152977f6a..b9dbdcf9a3f93948130124018c7777d1173b643e 100644 --- a/packages/component-faraday-ui/src/WizardAuthors.js +++ b/packages/component-faraday-ui/src/WizardAuthors.js @@ -58,23 +58,23 @@ const WizardAuthors = ({ error, journal, moveAuthor, + isFetching, authors = [], isAuthorEdit, deleteAuthor, addNewAuthor, setAuthorEdit, saveNewAuthor, - isAuthorsFetching, editExistingAuthor, authorEditorSubmit, - isFetching, }) => ( <Fragment> <Row alignItems="center" justify="flex-start"> <Item> <Label>Authors</Label> <ActionLink - data-test-id="add-author" + data-test-id="submission-add-author" + disabled={isAuthorEdit} icon="plus" onClick={addNewAuthor} > @@ -103,7 +103,6 @@ const WizardAuthors = ({ dragHandle={DragHandle} editExistingAuthor={editExistingAuthor} isAuthorEdit={isAuthorEdit} - isAuthorsFetching={isAuthorsFetching} isFetching={isFetching} itemKey="id" items={authors} @@ -131,11 +130,12 @@ export default compose( setAuthors, setFetching, onAuthorEdit, + formName = 'submission', }) => authors => { setAuthors(authors) setFetching(false) - onAuthorEdit(null) - changeForm('submission', 'authors', authors) + typeof onAuthorEdit === 'function' && onAuthorEdit(null) + changeForm(formName, 'authors', authors) }, }), withHandlers({ @@ -143,7 +143,7 @@ export default compose( if (authors.some(a => a.id === 'newAuthor')) { return } - onAuthorEdit(0) + typeof onAuthorEdit === 'function' && onAuthorEdit(0) setAuthors([ ...authors, { id: 'newAuthor', isCorresponding: false, isSubmitting: false }, @@ -153,7 +153,7 @@ export default compose( setFormAuthors(SortableList.moveItem(authors, dragIndex, hoverIndex)) }, setAuthorEdit: ({ authors, setAuthors, onAuthorEdit }) => index => { - onAuthorEdit(index) + typeof onAuthorEdit === 'function' && onAuthorEdit(index) setAuthors(authors.filter(a => a.id !== 'newAuthor')) }, saveNewAuthor: ({ authors, setFormAuthors }) => author => { @@ -170,41 +170,52 @@ export default compose( }, deleteAuthor: ({ authors, - project, - version, + fragment, + collection, + setFetching, deleteAuthor, setFormAuthors, - }) => author => ({ hideModal, setModalError }) => - deleteAuthor(project.id, version.id, author.id) + }) => author => ({ hideModal, setModalError }) => { + setFetching(true) + return deleteAuthor({ + authorId: author.id, + fragmentId: fragment.id, + collectionId: collection.id, + }) .then(() => { + setFetching(false) const newAuthors = setCorrespondingAuthor( authors.filter(a => a.id !== author.id), ) setFormAuthors(newAuthors) hideModal() }) - .catch(handleError(setModalError)), + .catch(err => { + setFetching(false) + handleError(setModalError)(err) + }) + }, }), withHandlers({ authorEditorSubmit: ({ authors, - project, - version, + fragment, addAuthor, + collection, setFetching, saveNewAuthor, editExistingAuthor, }) => (values, dispatch, { toggleEditMode }) => { if (values.id === 'newAuthor') { setFetching(true) - return addAuthor( - { + return addAuthor({ + fragmentId: fragment.id, + collectionId: collection.id, + author: { ...omit(values, 'id'), isSubmitting: authors.length === 1, }, - project.id, - version.id, - ).then(saveNewAuthor) + }).then(saveNewAuthor) } editExistingAuthor(values) setTimeout(toggleEditMode, 10) diff --git a/packages/component-faraday-ui/src/WizardFiles.js b/packages/component-faraday-ui/src/WizardFiles.js index 5c08242dd168384602c091f6b982435afd147169..9bd0591a7ff21c8ca020583682a8130d7b9da290 100644 --- a/packages/component-faraday-ui/src/WizardFiles.js +++ b/packages/component-faraday-ui/src/WizardFiles.js @@ -69,19 +69,29 @@ const WizardFiles = ({ </Fragment> ) +const initialFiles = { + manuscripts: [], + coverLetter: [], + supplementary: [], +} + export default compose( withFilePreview, withFileDownload, - withState('files', 'setFiles', ({ files }) => files), + withState('files', 'setFiles', ({ files = initialFiles }) => files), withState('fetching', 'setFilesFetching', { manuscripts: false, coverLetter: false, supplementary: false, }), withHandlers({ - setFormFiles: ({ changeForm, setFiles }) => files => { + setFormFiles: ({ + setFiles, + changeForm, + formName = 'submission', + }) => files => { setFiles(files) - changeForm('submission', 'files', files) + changeForm(formName, 'files', files) }, setFilesFetching: ({ setFilesFetching }) => (type, value) => { setFilesFetching(p => ({ @@ -93,13 +103,13 @@ export default compose( withHandlers({ addFile: ({ files, - version, + fragment, uploadFile, setFormFiles, setFilesFetching, }) => type => file => { setFilesFetching(type, true) - uploadFile({ file, type, fragment: version }) + uploadFile({ file, type, fragment }) .then(f => { const newFiles = { ...files, @@ -112,17 +122,16 @@ export default compose( setFilesFetching(type, false) }) }, - downloadFile: ({ downloadFile, token }) => file => { - downloadFile(file) - }, + previewFile: ({ previewFile }) => previewFile, + downloadFile: ({ downloadFile, token }) => downloadFile, deleteFile: ({ - deleteFile, files, + deleteFile, setFormFiles, setFilesFetching, }) => type => file => { setFilesFetching(type, true) - deleteFile(file.id, type) + deleteFile({ fileId: file.id, type }) .then(() => { const newFiles = { ...files, @@ -142,9 +151,6 @@ export default compose( } setFormFiles(newFiles) }, - previewFile: ({ previewFile }) => file => { - previewFile(file) - }, changeList: ({ files, setFormFiles }) => (from, to, fileId) => { const swappedFile = files[from].find(f => f.id === fileId) diff --git a/packages/component-faraday-ui/src/contextualBoxes/AssignHE.js b/packages/component-faraday-ui/src/contextualBoxes/AssignHE.js index c449426ad91a4c5d541c75a602250c2dad27d2f9..c65487c70d9df515d6ec66f630bbef8b429d6eac 100644 --- a/packages/component-faraday-ui/src/contextualBoxes/AssignHE.js +++ b/packages/component-faraday-ui/src/contextualBoxes/AssignHE.js @@ -32,6 +32,7 @@ const AssignHE = ({ <Root pb={2}> <TextContainer> <TextField + data-test-id="manuscript-assign-he-filter" onChange={changeSearch} placeholder="Filter by name or email" value={searchValue} @@ -60,6 +61,7 @@ const AssignHE = ({ {handlingEditors.map((he, index) => ( <CustomRow alignItems="center" + data-test-id={`manuscript-assign-he-invite-${he.id}`} height={4} isFirst={index === 0} key={he.id} diff --git a/packages/component-faraday-ui/src/contextualBoxes/HERecommendation.js b/packages/component-faraday-ui/src/contextualBoxes/HERecommendation.js index e5143cfe49b9d9a77998fa1b3f9286355cb5d1d5..877243b16fc5e34b2cc9b4ccb2593124c031624e 100644 --- a/packages/component-faraday-ui/src/contextualBoxes/HERecommendation.js +++ b/packages/component-faraday-ui/src/contextualBoxes/HERecommendation.js @@ -73,7 +73,11 @@ const HERecommendation = ({ > <Root> <Row justify="flex-start"> - <ItemOverrideAlert flex={0} vertical> + <ItemOverrideAlert + data-test-id="editorial-recommendation-choose-options" + flex={0} + vertical + > <Label required>Recommendation</Label> <ValidatedField component={input => ( @@ -91,7 +95,11 @@ const HERecommendation = ({ {get(formValues, 'recommendation') === 'minor' || get(formValues, 'recommendation') === 'major' ? ( <Row mt={2}> - <ResponsiveItem mr={1} vertical> + <ResponsiveItem + data-test-id="editorial-recommendation-message-for-author" + mr={1} + vertical + > <Label> Message for Author <Text secondary>Optional</Text> </Label> @@ -100,14 +108,22 @@ const HERecommendation = ({ </Row> ) : ( <ResponsiveRow mt={2}> - <ResponsiveItem mr={1} vertical> + <ResponsiveItem + data-test-id="editorial-recommendation-message-for-author" + mr={1} + vertical + > <Label> Message for Author <Text secondary>Optional</Text> </Label> <ValidatedField component={Textarea} name="public" /> </ResponsiveItem> - <ResponsiveItem ml={1} vertical> + <ResponsiveItem + data-test-id="editorial-recommendation-message-for-eic" + ml={1} + vertical + > <Label> Message for Editor in Chief <Text secondary>Optional</Text> </Label> @@ -117,7 +133,12 @@ const HERecommendation = ({ )} <Row justify="flex-end" mt={2}> - <Button onClick={handleSubmit} primary size="medium"> + <Button + data-test-id="button-editorial-recommendation-submit" + onClick={handleSubmit} + primary + size="medium" + > { options.find( o => o.value === get(formValues, 'recommendation', 'publish'), diff --git a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js index 8e242820b8a3077267b79efe84b7355b6d7029d0..232c2798046f6b1d50feba48c94b8d46a4ba3066 100644 --- a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js +++ b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js @@ -57,6 +57,7 @@ const ReviewerDetails = ({ <Fragment> <TabsHeader> <TabButton + data-test-id="reviewer-tab-details" ml={1} mr={1} onClick={() => changeTab(0)} @@ -66,6 +67,7 @@ const ReviewerDetails = ({ </TabButton> {canInviteReviewers && ( <TabButton + data-test-id="reviewer-tab-suggestions" ml={1} mr={1} onClick={() => changeTab(2)} @@ -75,6 +77,7 @@ const ReviewerDetails = ({ </TabButton> )} <TabButton + data-test-id="reviewer-tab-reports" ml={1} mr={1} onClick={() => changeTab(1)} diff --git a/packages/component-faraday-ui/src/contextualBoxes/ReviewerReportForm.js b/packages/component-faraday-ui/src/contextualBoxes/ReviewerReportForm.js index cd568ff1c9afd710040804d97e8e1d7988c5b17d..7e98eef25418e5b6cfdf9c0050568ee58b87b1fa 100644 --- a/packages/component-faraday-ui/src/contextualBoxes/ReviewerReportForm.js +++ b/packages/component-faraday-ui/src/contextualBoxes/ReviewerReportForm.js @@ -43,7 +43,11 @@ const ReviewerReportForm = ({ > <Root> <Row justify="flex-start"> - <ItemOverrideAlert flex={0} vertical> + <ItemOverrideAlert + data-test-id="form-report-recommendation" + flex={0} + vertical + > <Label required>Recommendation</Label> <ValidatedField component={input => <Menu {...input} options={recommendations} />} @@ -53,7 +57,12 @@ const ReviewerReportForm = ({ </ItemOverrideAlert> </Row> - <Row alignItems="center" justify="space-between" mt={1}> + <Row + alignItems="center" + data-test-id="form-report-upload-file" + justify="space-between" + mt={1} + > <Item> <Label required>Your report</Label> {!formValues.file && ( @@ -71,7 +80,7 @@ const ReviewerReportForm = ({ </Row> <Row mb={1 / 2}> - <ItemOverrideAlert vertical> + <ItemOverrideAlert data-test-id="form-report-textarea" vertical> <ValidatedField component={Textarea} name="public" /> </ItemOverrideAlert> </Row> @@ -80,6 +89,7 @@ const ReviewerReportForm = ({ <Row justify="flex-start" mb={1}> <Item flex={0}> <FileItem + data-test-id="form-report-file-item-actions" item={formValues.file} onDelete={removeFile} onDownload={downloadFile} @@ -96,14 +106,18 @@ const ReviewerReportForm = ({ <Label>Confidential note for the Editorial Team</Label> </Item> <Item justify="flex-end"> - <ActionLink icon="x" onClick={removeNote}> + <ActionLink + data-test-id="form-report-remove-note" + icon="x" + onClick={removeNote} + > Remove </ActionLink> </Item> </Fragment> ) : ( <Item> - <ActionLink onClick={addNote}> + <ActionLink data-test-id="form-report-add-note" onClick={addNote}> Add Confidential note for the Editorial Team </ActionLink> </Item> @@ -112,7 +126,10 @@ const ReviewerReportForm = ({ {hasNote && ( <Row> - <ItemOverrideAlert vertical> + <ItemOverrideAlert + data-test-id="textarea-form-report-add-note" + vertical + > <ValidatedField component={Textarea} name="confidential" /> </ItemOverrideAlert> </Row> @@ -128,7 +145,12 @@ const ReviewerReportForm = ({ {isFetching ? ( <Spinner /> ) : ( - <Button onClick={handleSubmit} primary size="medium"> + <Button + data-test-id="button-submit-report" + onClick={handleSubmit} + primary + size="medium" + > Submit report </Button> )} diff --git a/packages/component-faraday-ui/src/helpers/withCountries.js b/packages/component-faraday-ui/src/helpers/withCountries.js index bd642eddd9bac3cd647a387b93179b957f9513da..fb72d5997d1270d94f7a11dd8325510a24034e1e 100644 --- a/packages/component-faraday-ui/src/helpers/withCountries.js +++ b/packages/component-faraday-ui/src/helpers/withCountries.js @@ -1,7 +1,7 @@ import { withProps } from 'recompose' import countrylist from 'country-list' -const countryMapper = c => { +const countryMapper = (c = 'GB') => { switch (c) { case 'GB': return 'UK' @@ -14,7 +14,7 @@ const countryMapper = c => { } } -const codeMapper = c => { +const codeMapper = (c = 'UK') => { switch (c) { case 'UK': return 'GB' diff --git a/packages/component-faraday-ui/src/index.js b/packages/component-faraday-ui/src/index.js index 263820a923f836e555affe74bed8bd51d6ea7b6d..a4feb94b3657fd39ecc004675bf70b84a544b20b 100644 --- a/packages/component-faraday-ui/src/index.js +++ b/packages/component-faraday-ui/src/index.js @@ -47,6 +47,8 @@ export { default as WizardFiles } from './WizardFiles' export { default as TextTooltip } from './TextTooltip' export { default as EditorialReportCard } from './EditorialReportCard' +export { SubmitRevision } from './submissionRevision' + export * from './OverrideAlert' export * from './manuscriptDetails' export * from './contextualBoxes' diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptDetailsTop.js b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptDetailsTop.js index 516a6ca9010541dc82558a0a29f46afbcf0d36cd..2564bf721c3db9d5cd719b699f1d18c57f319d5a 100644 --- a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptDetailsTop.js +++ b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptDetailsTop.js @@ -36,7 +36,7 @@ const ManuscriptDetailsTop = ({ <Item alignItems="center" justify="flex-end"> {canOverrideTechChecks && ( <ActionLink - data-test-id={`button-qa-manuscript-${fragment.id}`} + data-test-id="button-qa-manuscript-technical-checks" icon="check-square" onClick={goToTechnicalCheck(collection)} pr={2} @@ -46,7 +46,7 @@ const ManuscriptDetailsTop = ({ )} {canEditManuscript && ( <ActionLink - data-test-id={`button-qa-manuscript-${fragment.id}`} + data-test-id="button-qa-manuscript-edit" icon="edit" onClick={goToEdit(collection, fragment)} pr={2} diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js index 268d7af507e41a54af7f5dd2088c905a7681d0ca..70c2afdd45b6ddaf6027801ad72a21902afed42c 100644 --- a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js +++ b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js @@ -146,7 +146,13 @@ export default compose( if (canAssignHE) { return ( - <Button ml={1} onClick={inviteHE} primary size="small"> + <Button + data-test-id="manuscript-invite-he-button" + ml={1} + onClick={inviteHE} + primary + size="small" + > Invite </Button> ) diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ResponseToInvitation.js b/packages/component-faraday-ui/src/manuscriptDetails/ResponseToInvitation.js index 7558e95eb92beb4ceeb8b13e1c504967b82c14f2..a638617e4a1adb041b45b48c310df51e95be1c29 100644 --- a/packages/component-faraday-ui/src/manuscriptDetails/ResponseToInvitation.js +++ b/packages/component-faraday-ui/src/manuscriptDetails/ResponseToInvitation.js @@ -47,7 +47,11 @@ const ResponseToInvitation = ({ <Label required>{label}</Label> <ValidatedField component={input => ( - <Row alignItems="center" justify="space-between"> + <Row + alignItems="center" + data-test-id="radio-respond-to-invitation" + justify="space-between" + > <RadioGroup inline name="decision" options={options} {...input} /> <Button mb={1.7} diff --git a/packages/component-faraday-ui/src/submissionRevision/DetailsAndAuthors.js b/packages/component-faraday-ui/src/submissionRevision/DetailsAndAuthors.js new file mode 100644 index 0000000000000000000000000000000000000000..4b25942909135eec5dc15b08f14fe2d84162bf85 --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/DetailsAndAuthors.js @@ -0,0 +1,100 @@ +import React from 'react' +import { get } from 'lodash' +import { Field } from 'redux-form' +import { required } from 'xpub-validators' +import { + Row, + Item, + Label, + Textarea, + WizardAuthors, + RowOverrideAlert, + ItemOverrideAlert, +} from 'pubsweet-component-faraday-ui' +import { Menu, TextField, ValidatedField } from '@pubsweet/ui' +import { th } from '@pubsweet/ui-toolkit' +import styled from 'styled-components' +import { ContextualBox } from '../' + +const Empty = () => <div /> + +const DetailsAndAuthors = ({ + journal, + fragment, + addAuthor, + collection, + changeForm, + formValues, + isAuthorEdit, + authorsError, + deleteAuthor, + manuscriptTypes, + getTooltipContent, + isAuthorsFetching, + onAuthorEdit, + ...rest +}) => ( + <ContextualBox label="Details and Authors" startExpanded transparent> + <Root> + <Row mb={3}> + <Item data-test-id="submission-title" flex={3} mr={1} vertical> + <Label required>MANUSCRIPT TITLE</Label> + <ValidatedField + component={TextField} + name="metadata.title" + validate={[required]} + /> + </Item> + <ItemOverrideAlert data-test-id="submission-type" vertical> + <Label required>MANUSCRIPT TYPE</Label> + <ValidatedField + component={input => ( + <Menu + options={manuscriptTypes} + {...input} + placeholder="Please select" + /> + )} + name="metadata.type" + validate={[required]} + /> + </ItemOverrideAlert> + </Row> + + <RowOverrideAlert mb={2}> + <Item data-test-id="submission-abstract" vertical> + <Label required>ABSTRACT</Label> + <ValidatedField + component={Textarea} + minHeight={15} + name="metadata.abstract" + validate={[required]} + /> + </Item> + </RowOverrideAlert> + + <Field component={Empty} name="authors" /> + <WizardAuthors + addAuthor={addAuthor} + authors={get(fragment, 'revision.authors', [])} + changeForm={changeForm} + collection={collection} + deleteAuthor={deleteAuthor} + error={authorsError} + formName="revision" + fragment={fragment} + isAuthorEdit={isAuthorEdit} + isAuthorsFetching={isAuthorsFetching} + journal={journal} + onAuthorEdit={onAuthorEdit} + /> + </Root> + </ContextualBox> +) + +const Root = styled.div` + border-left: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')}; + padding-left: calc(${th('gridUnit')} * 1); +` + +export default DetailsAndAuthors diff --git a/packages/component-faraday-ui/src/submissionRevision/DetailsAndAuthors.md b/packages/component-faraday-ui/src/submissionRevision/DetailsAndAuthors.md new file mode 100644 index 0000000000000000000000000000000000000000..1ac22499492ee611bb322827d7f5ad34fae164b8 --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/DetailsAndAuthors.md @@ -0,0 +1,167 @@ +```js +const { compose, withProps, withHandlers } = require('recompose') +const { connect } = require('react-redux') +const { reduxForm, getFormValues, change } = require('redux-form') + +const Wrapper = compose( + withHandlers({ + onAuthorEdit: props => () => console.log('Edit author'), + }), + withProps({ + manuscriptTypes: [ + { + label: 'Research', + value: 'research', + author: true, + peerReview: true, + abstractRequired: true, + }, + { + label: 'Review', + value: 'review', + author: true, + peerReview: true, + abstractRequired: true, + }, + ], + fragment: { + id: 'a9dc38fe-5524-4728-b97f-9495a2eb4bee', + type: 'fragment', + files: { + coverLetter: [], + manuscripts: [ + { + id: + 'a9dc38fe-5524-4728-b97f-9495a2eb4bee/4a452733-e05d-485a-a0be-7c199c5eb4a1', + name: 'manuscris.pdf', + size: 39973, + originalName: 'manuscris.pdf', + }, + ], + supplementary: [], + }, + owners: [ + { + id: '81586d97-d2b4-4423-a1e3-efd228fc67b8', + username: 'mihail.hagiu+re@thinslices.com', + }, + { + id: '96673581-5916-46c5-8a57-d9e69c3e713d', + username: 'admin', + }, + ], + authors: [ + { + id: '81586d97-d2b4-4423-a1e3-efd228fc67b8', + email: 'mihail.hagiu+re@thinslices.com', + country: 'AX', + lastName: 'ihail', + firstName: 'M', + affiliation: 'TS', + isSubmitting: true, + isCorresponding: true, + }, + ], + created: '2018-10-11T08:04:47.636Z', + version: 1, + metadata: { + type: 'research', + title: 'czxcxzc', + journal: 'Bioinorganic Chemistry and Applications', + abstract: 'xdzczxc', + }, + conflicts: { + hasFunding: '', + hasConflicts: 'no', + hasDataAvailability: '', + }, + submitted: 1539606240257, + collectionId: 'e69cddda-74be-47aa-8f99-c388ef5c8a77', + declarations: { + agree: true, + }, + fragmentType: 'version', + recommendations: [], + }, + collection: { + id: 'e69cddda-74be-47aa-8f99-c388ef5c8a77', + type: 'collection', + owners: [ + '96673581-5916-46c5-8a57-d9e69c3e713d', + '81586d97-d2b4-4423-a1e3-efd228fc67b8', + ], + status: 'rejected', + created: 1539245087543, + customId: '5074586', + fragments: ['a9dc38fe-5524-4728-b97f-9495a2eb4bee'], + technicalChecks: {}, + currentVersion: { + id: 'a9dc38fe-5524-4728-b97f-9495a2eb4bee', + type: 'fragment', + files: { + coverLetter: [], + manuscripts: [ + { + id: + 'a9dc38fe-5524-4728-b97f-9495a2eb4bee/4a452733-e05d-485a-a0be-7c199c5eb4a1', + name: 'manuscris.pdf', + size: 39973, + originalName: 'manuscris.pdf', + }, + ], + supplementary: [], + }, + owners: [ + '81586d97-d2b4-4423-a1e3-efd228fc67b8', + '96673581-5916-46c5-8a57-d9e69c3e713d', + ], + authors: [ + { + id: '81586d97-d2b4-4423-a1e3-efd228fc67b8', + email: 'mihail.hagiu+re@thinslices.com', + country: 'AX', + lastName: 'ihail', + firstName: 'M', + affiliation: 'TS', + isSubmitting: true, + isCorresponding: true, + }, + ], + created: '2018-10-11T08:04:47.636Z', + version: 1, + metadata: { + type: 'research', + title: 'czxcxzc', + journal: 'Bioinorganic Chemistry and Applications', + abstract: 'xdzczxc', + }, + conflicts: { + hasFunding: '', + hasConflicts: 'no', + hasDataAvailability: '', + }, + submitted: 1539606240257, + collectionId: 'e69cddda-74be-47aa-8f99-c388ef5c8a77', + declarations: { + agree: true, + }, + fragmentType: 'version', + recommendations: [], + }, + visibleStatus: 'Rejected', + }, + }), + connect( + state => ({ + formValues: getFormValues('styleguide')(state), + }), + { + changeForm: change, + }, + ), + reduxForm({ + form: 'styleguide', + }), +)(props => console.log('Padadas', props) || <DetailsAndAuthors {...props} />) +;<Wrapper /> +``` diff --git a/packages/component-faraday-ui/src/submissionRevision/ManuscriptFiles.js b/packages/component-faraday-ui/src/submissionRevision/ManuscriptFiles.js new file mode 100644 index 0000000000000000000000000000000000000000..5edc9501d7ae2861cce821a8f8294f31b2986c97 --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/ManuscriptFiles.js @@ -0,0 +1,72 @@ +import React from 'react' +import { get } from 'lodash' +import { Field } from 'redux-form' +import styled from 'styled-components' +import { Icon } from '@pubsweet/ui' +import { Row, Text, WizardFiles } from 'pubsweet-component-faraday-ui' +import { th } from '@pubsweet/ui-toolkit' +import { ContextualBox } from '../' + +const Empty = () => <div /> + +const ManuscriptFiles = ({ + token, + fragment, + formName, + collection, + changeForm, + deleteFile, + uploadFile, + filesError, + previewFile, + getSignedUrl, +}) => ( + <ContextualBox label="Manuscript Files" startExpanded transparent> + <Root> + <Row justify="flex-start" mb={2}> + <Text secondary> + Drag & drop files in the specific section or click{' '} + <CustomIcon secondary size={2}> + plus + </CustomIcon>{' '} + to upload. Use the{' '} + <CustomIcon secondary size={2}> + menu + </CustomIcon>{' '} + icon to reorder or move files to a different type. + </Text> + </Row> + <Field component={Empty} name="files" /> + <WizardFiles + changeForm={changeForm} + collection={collection} + deleteFile={deleteFile} + files={get(fragment, 'revision.files', {})} + formName={formName} + fragment={fragment} + getSignedUrl={getSignedUrl} + previewFile={previewFile} + token={token} + uploadFile={uploadFile} + /> + {filesError && ( + <Row justify="flex-start" mt={1}> + <Text error>{filesError}</Text> + </Row> + )} + </Root> + </ContextualBox> +) + +export default ManuscriptFiles + +// #region styled-components +const Root = styled.div` + border-left: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')}; + padding-left: calc(${th('gridUnit')} * 1); +` + +const CustomIcon = styled(Icon)` + vertical-align: sub; +` +// #endregion diff --git a/packages/component-faraday-ui/src/submissionRevision/ManuscriptFiles.md b/packages/component-faraday-ui/src/submissionRevision/ManuscriptFiles.md new file mode 100644 index 0000000000000000000000000000000000000000..42a8e212005461b35ce3cedc2926dc0ffff6b7a2 --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/ManuscriptFiles.md @@ -0,0 +1,21 @@ +```js +const { compose, withHandlers } = require('recompose') +const { connect } = require('react-redux') +const { reduxForm, getFormValues } = require('redux-form') + +const Wrapper = compose( + withHandlers({ + uploadFile: props => file => Promise.resolve(file), + deleteFile: props => file => console.log('Deleted', file), + }), + connect(state => ({ + formValues: getFormValues('styleguide')(state), + })), + reduxForm({ + form: 'styleguide', + }), +)(( props ) => ( + <ManuscriptFiles {...props} /> +)) +;<Wrapper /> +``` \ No newline at end of file diff --git a/packages/component-faraday-ui/src/submissionRevision/ResponseToReviewer.js b/packages/component-faraday-ui/src/submissionRevision/ResponseToReviewer.js new file mode 100644 index 0000000000000000000000000000000000000000..f3b6c168fa2da6e0526cdd2d94f8634efa3809b6 --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/ResponseToReviewer.js @@ -0,0 +1,85 @@ +import React from 'react' +import { isEmpty } from 'lodash' +import styled from 'styled-components' +import { th } from '@pubsweet/ui-toolkit' +import { FilePicker, Spinner, ValidatedField } from '@pubsweet/ui' + +import { + Row, + Item, + Textarea, + ActionLink, +} from 'pubsweet-component-faraday-ui/src' + +import { + Label, + FileItem, + ContextualBox, + ItemOverrideAlert, + withFilePreview, + withFileDownload, +} from '../' + +const allowedFileExtensions = ['pdf', 'doc', 'docx', 'txt', 'rdf', 'odt'] +const ResponseToReviewer = ({ + file, + onDelete, + onUpload, + isFetching, + previewFile, + downloadFile, +}) => ( + <ContextualBox + label="Response to Reviewer Comments" + startExpanded + transparent + > + <Root> + <Row alignItems="center" justify="space-between"> + <Item> + <Label required>Your Reply</Label> + {isFetching ? ( + <Spinner /> + ) : ( + <FilePicker + allowedFileExtensions={allowedFileExtensions} + disabled={!isEmpty(file)} + onUpload={onUpload} + > + <ActionLink disabled={!isEmpty(file)} icon="plus"> + UPLOAD FILE + </ActionLink> + </FilePicker> + )} + </Item> + </Row> + + <Row> + <ItemOverrideAlert vertical> + <ValidatedField + component={Textarea} + name="responseToReviewers.content" + /> + </ItemOverrideAlert> + </Row> + + {!isEmpty(file) && ( + <Row justify="flex-start" mt={1}> + <FileItem + item={file} + onDelete={onDelete} + onDownload={downloadFile} + onPreview={previewFile} + /> + </Row> + )} + </Root> + </ContextualBox> +) + +const Root = styled.div` + border-left: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')}; + padding-left: calc(${th('gridUnit')} * 1); +` + +export default withFileDownload(withFilePreview(ResponseToReviewer)) diff --git a/packages/component-faraday-ui/src/submissionRevision/ResponseToReviewer.md b/packages/component-faraday-ui/src/submissionRevision/ResponseToReviewer.md new file mode 100644 index 0000000000000000000000000000000000000000..e9264445b46902365f4a0d50608d13bd67fde690 --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/ResponseToReviewer.md @@ -0,0 +1,7 @@ +```js +const allowedFileExtensions = ['pdf', 'doc', 'docx'] + +const onUpload = (f) => {console.log('Upload', f)} + +<ResponseToReviewer onUpload={onUpload} allowedFileExtensions={allowedFileExtensions}/> +``` diff --git a/packages/component-faraday-ui/src/submissionRevision/SubmitRevision.js b/packages/component-faraday-ui/src/submissionRevision/SubmitRevision.js new file mode 100644 index 0000000000000000000000000000000000000000..be61f16dd096256d2538fdcbbb4cdae27a5eeb80 --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/SubmitRevision.js @@ -0,0 +1,94 @@ +import React from 'react' +import { Button } from '@pubsweet/ui' +import styled from 'styled-components' +import { reduxForm } from 'redux-form' +import { th } from '@pubsweet/ui-toolkit' +import { Row } from 'pubsweet-component-faraday-ui/src' + +import { ContextualBox, Text } from '../' +import { ManuscriptFiles, DetailsAndAuthors, ResponseToReviewer } from './' + +const SubmitRevision = ({ + journal, + addFile, + fragment, + addAuthor, + deleteFile, + collection, + changeForm, + isFetching, + currentUser, + previewFile, + hasFormError, + deleteAuthor, + getSignedUrl, + handleSubmit, + responseFile, + downloadFile, + fetchingError, + addResponseFile, + deleteResponseFile, + // + onAuthorEdit, + isEditingAuthor, +}) => ( + <ContextualBox highlight label="Submit Revision" mb={2}> + <Root> + <DetailsAndAuthors + addAuthor={addAuthor} + changeForm={changeForm} + collection={collection} + deleteAuthor={deleteAuthor} + fragment={fragment} + isAuthorEdit={isEditingAuthor} + manuscriptTypes={journal.manuscriptTypes} + onAuthorEdit={onAuthorEdit} + /> + + <ManuscriptFiles + changeForm={changeForm} + collection={collection} + deleteFile={deleteFile} + downloadFile={downloadFile} + formName="revision" + fragment={fragment} + getSignedUrl={getSignedUrl} + previewFile={previewFile} + token={currentUser.token} + uploadFile={addFile} + /> + + <ResponseToReviewer + file={responseFile} + getSignedUrl={getSignedUrl} + isFetching={isFetching} + onDelete={deleteResponseFile} + onUpload={addResponseFile} + token={currentUser.token} + /> + + <Row> + {hasFormError && ( + <Text align="center" error mt={1}> + There are some missing required fields. + </Text> + )} + </Row> + + <Row justify="flex-end" mt={1}> + <Button ml={2} onClick={handleSubmit} primary size="medium"> + Submit revision + </Button> + </Row> + </Root> + </ContextualBox> +) + +const Root = styled.div` + background-color: ${th('colorBackgroundHue2')}; + padding: calc(${th('gridUnit')} * 2); +` + +export default reduxForm({ form: 'revision', destroyOnUnmount: false })( + SubmitRevision, +) diff --git a/packages/component-faraday-ui/src/submissionRevision/SubmitRevision.md b/packages/component-faraday-ui/src/submissionRevision/SubmitRevision.md new file mode 100644 index 0000000000000000000000000000000000000000..6c9a0bde8927225e69c55be3fa2ee2cf2f78938c --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/SubmitRevision.md @@ -0,0 +1,156 @@ +```js +const props = { + fragment: { + id: 'a9dc38fe-5524-4728-b97f-9495a2eb4bee', + type: 'fragment', + files: { + coverLetter: [], + manuscripts: [ + { + id: + 'a9dc38fe-5524-4728-b97f-9495a2eb4bee/4a452733-e05d-485a-a0be-7c199c5eb4a1', + name: 'manuscris.pdf', + size: 39973, + originalName: 'manuscris.pdf', + }, + ], + supplementary: [], + }, + owners: [ + { + id: '81586d97-d2b4-4423-a1e3-efd228fc67b8', + username: 'mihail.hagiu+re@thinslices.com', + }, + { + id: '96673581-5916-46c5-8a57-d9e69c3e713d', + username: 'admin', + }, + ], + authors: [ + { + id: '81586d97-d2b4-4423-a1e3-efd228fc67b8', + email: 'mihail.hagiu+re@thinslices.com', + country: 'RO', + lastName: 'ihail', + firstName: 'M', + affiliation: 'TS', + isSubmitting: true, + isCorresponding: true, + }, + ], + created: '2018-10-11T08:04:47.636Z', + version: 1, + metadata: { + type: 'research', + title: 'czxcxzc', + journal: 'Bioinorganic Chemistry and Applications', + abstract: 'xdzczxc', + }, + conflicts: { + hasFunding: '', + hasConflicts: 'no', + hasDataAvailability: '', + }, + submitted: 1539606240257, + collectionId: 'e69cddda-74be-47aa-8f99-c388ef5c8a77', + declarations: { + agree: true, + }, + fragmentType: 'version', + recommendations: [], + }, + collection: { + id: 'e69cddda-74be-47aa-8f99-c388ef5c8a77', + type: 'collection', + owners: [ + '96673581-5916-46c5-8a57-d9e69c3e713d', + '81586d97-d2b4-4423-a1e3-efd228fc67b8', + ], + status: 'rejected', + created: 1539245087543, + customId: '5074586', + fragments: ['a9dc38fe-5524-4728-b97f-9495a2eb4bee'], + technicalChecks: {}, + currentVersion: { + id: 'a9dc38fe-5524-4728-b97f-9495a2eb4bee', + type: 'fragment', + files: { + coverLetter: [], + manuscripts: [ + { + id: + 'a9dc38fe-5524-4728-b97f-9495a2eb4bee/4a452733-e05d-485a-a0be-7c199c5eb4a1', + name: 'manuscris.pdf', + size: 39973, + originalName: 'manuscris.pdf', + }, + ], + supplementary: [], + }, + owners: [ + '81586d97-d2b4-4423-a1e3-efd228fc67b8', + '96673581-5916-46c5-8a57-d9e69c3e713d', + ], + authors: [ + { + id: '81586d97-d2b4-4423-a1e3-efd228fc67b8', + email: 'mihail.hagiu+re@thinslices.com', + country: 'AX', + lastName: 'ihail', + firstName: 'M', + affiliation: 'TS', + isSubmitting: true, + isCorresponding: true, + }, + ], + created: '2018-10-11T08:04:47.636Z', + version: 1, + metadata: { + type: 'research', + title: 'czxcxzc', + journal: 'Bioinorganic Chemistry and Applications', + abstract: 'xdzczxc', + }, + conflicts: { + hasFunding: '', + hasConflicts: 'no', + hasDataAvailability: '', + }, + submitted: 1539606240257, + collectionId: 'e69cddda-74be-47aa-8f99-c388ef5c8a77', + declarations: { + agree: true, + }, + fragmentType: 'version', + recommendations: [], + }, + visibleStatus: 'Rejected', + }, + journal: { + manuscriptTypes: [ + { + label: 'Research Article', + value: 'research', + author: true, + peerReview: true, + abstractRequired: true, + }, + { + label: 'Review Article', + value: 'review', + author: true, + peerReview: true, + abstractRequired: true, + }, + { + label: 'Letter to the editor', + value: 'letter-to-editor', + author: true, + peerReview: false, + abstractRequired: false, + }, + ], + }, +} +;<SubmitRevision {...props} /> +``` diff --git a/packages/component-faraday-ui/src/submissionRevision/index.js b/packages/component-faraday-ui/src/submissionRevision/index.js new file mode 100644 index 0000000000000000000000000000000000000000..262a71e3f9707fbb08597535af9a3f1aef39b22f --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/index.js @@ -0,0 +1,4 @@ +export { default as ResponseToReviewer } from './ResponseToReviewer' +export { default as ManuscriptFiles } from './ManuscriptFiles' +export { default as DetailsAndAuthors } from './DetailsAndAuthors' +export { default as SubmitRevision } from './SubmitRevision' diff --git a/packages/component-manuscript-manager/src/routes/collections/get.js b/packages/component-manuscript-manager/src/routes/collections/get.js index ddeeb504d1efee96f4362a6e72455cb7529298f2..1314ffda0107a1e4c80a0edab70561f97db82093 100644 --- a/packages/component-manuscript-manager/src/routes/collections/get.js +++ b/packages/component-manuscript-manager/src/routes/collections/get.js @@ -1,3 +1,8 @@ +const { last, get } = require('lodash') + +const filterDuplicates = collection => + get(collection, 'currentVersion.id') === last(collection.fragments) + const { authsome: authsomeHelper, } = require('pubsweet-component-helper-service') @@ -16,5 +21,5 @@ module.exports = models => async (req, res) => { }) } - res.status(200).json(collections) + res.status(200).json(collections.filter(filterDuplicates)) } diff --git a/packages/component-manuscript-manager/src/routes/fragments/notifications/emailCopy.js b/packages/component-manuscript-manager/src/routes/fragments/notifications/emailCopy.js index 64f10e4fc62bc24ca139281b489037818d93ec55..ecfadb0a4ae20fb38e8e9511b10120fb33fabfaf 100644 --- a/packages/component-manuscript-manager/src/routes/fragments/notifications/emailCopy.js +++ b/packages/component-manuscript-manager/src/routes/fragments/notifications/emailCopy.js @@ -5,10 +5,12 @@ const journalName = config.get('journal.name') const getEmailCopy = ({ emailType, titleText, expectedDate, customId }) => { let paragraph const hasLink = true - const hasIntro = true - const hasSignature = true + let hasIntro = true + let hasSignature = true switch (emailType) { case 'he-new-version-submitted': + hasIntro = false + hasSignature = false paragraph = `The authors of ${titleText} have submitted a revised version. <br/><br/> To review this new submission and proceed with the review process, please visit the manuscript details page.` break diff --git a/packages/component-manuscript-manager/src/routes/fragments/notifications/notifications.js b/packages/component-manuscript-manager/src/routes/fragments/notifications/notifications.js index 75be73180ca247e1d78800a3f1ac34587cc85208..7f0b8fd3d3364ddc038385f6424ec53b9578fba3 100644 --- a/packages/component-manuscript-manager/src/routes/fragments/notifications/notifications.js +++ b/packages/component-manuscript-manager/src/routes/fragments/notifications/notifications.js @@ -21,6 +21,7 @@ module.exports = { UserModel, collection, isNewVersion = false, + previousVersion, isTechnicalCheck, isMajorRecommendation, }) { @@ -59,7 +60,6 @@ module.exports = { const heUser = await UserModel.find(handlingEditor.id) sendHandlingEditorEmail({ email, - eicName, baseUrl, customId, title: parsedFragment.title, @@ -73,8 +73,9 @@ module.exports = { baseUrl, customId, UserModel, - fragmentHelper, + previousVersion, title: parsedFragment.title, + heName: collection.handlingEditor.name, }) } @@ -104,7 +105,6 @@ module.exports = { const sendHandlingEditorEmail = ({ email, title, - eicName, baseUrl, customId, handlingEditor, @@ -113,19 +113,17 @@ const sendHandlingEditorEmail = ({ email.toUser = { email: handlingEditor.email, - name: handlingEditor.name, } email.content.unsubscribeLink = services.createUrl(baseUrl, unsubscribeSlug, { id: handlingEditor.id, token: handlingEditor.accessTokens.unsubscribe, }) - email.content.signatureName = eicName email.content.subject = `${customId}: Revision submitted` const { html, text } = email.getNotificationBody({ emailBodyProps: getEmailCopy({ emailType, - titleText: `the manuscript titled ${title}`, + titleText: `the manuscript titled "${title}"`, }), }) email.sendEmail({ html, text }) @@ -134,14 +132,18 @@ const sendHandlingEditorEmail = ({ const sendReviewersEmail = async ({ email, title, + heName, baseUrl, customId, UserModel, - fragmentHelper, + previousVersion, }) => { email.content.subject = `${customId}: A manuscript you reviewed has been revised` + email.content.signatureName = heName + email.fromEmail = `${heName} <${staffEmail}>` const emailType = 'submitted-reviewers-after-revision' + const fragmentHelper = new Fragment({ fragment: previousVersion }) const reviewers = await fragmentHelper.getReviewers({ UserModel, type: 'submitted', @@ -165,7 +167,7 @@ const sendReviewersEmail = async ({ const { html, text } = email.getNotificationBody({ emailBodyProps: getEmailCopy({ emailType, - titleText: `the manuscript titled ${title}`, + titleText: `the manuscript titled "${title}"`, expectedDate: services.getExpectedDate({ daysExpected: 14 }), }), }) diff --git a/packages/component-manuscript-manager/src/routes/fragments/patch.js b/packages/component-manuscript-manager/src/routes/fragments/patch.js index 6f4cd8a9eb18956142727537add44c9ed28035e0..09e3dbc8f106ff8414952801c8b722eb9e0d711b 100644 --- a/packages/component-manuscript-manager/src/routes/fragments/patch.js +++ b/packages/component-manuscript-manager/src/routes/fragments/patch.js @@ -116,10 +116,11 @@ module.exports = models => async (req, res) => { collection.save() notifications.sendNotifications({ - fragment, collection, isNewVersion: true, + fragment: newFragment, UserModel: models.User, + previousVersion: fragment, baseUrl: services.getBaseUrl(req), isMajorRecommendation: heRecommendation.recommendation === 'major', }) diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js index eb3581a466de4eb6213a08f54cbb7756eac811ba..38ef102fa143ae12a2a5bf78261312211e8d8083 100644 --- a/packages/component-manuscript/src/components/ManuscriptLayout.js +++ b/packages/component-manuscript/src/components/ManuscriptLayout.js @@ -12,6 +12,7 @@ import { ManuscriptDetailsTop, ResponseToInvitation, ManuscriptEicDecision, + SubmitRevision, } from 'pubsweet-component-faraday-ui' import ReviewerReportCard from './ReviewReportCard' @@ -68,6 +69,7 @@ const ManuscriptLayout = ({ editorialCommentsExpanded, toggleEditorialComments, onInvitePublonReviewer, + submitRevision, }) => ( <Root pb={30}> {!isEmpty(collection) && !isEmpty(fragment) ? ( @@ -108,6 +110,10 @@ const ManuscriptLayout = ({ /> )} + {get(currentUser, 'permissions.canSubmitRevision', false) && ( + <SubmitRevision {...submitRevision} /> + )} + {submittedOwnRecommendation && ( <ReviewerReportCard getSignedUrl={getSignedUrl} diff --git a/packages/component-manuscript/src/components/ManuscriptPage.js b/packages/component-manuscript/src/components/ManuscriptPage.js index 3304d0385603f47ed75cdc1b78bf04a6b3fa7aa7..781fb962155bac6ca78ee9eef5f51f16901d6530 100644 --- a/packages/component-manuscript/src/components/ManuscriptPage.js +++ b/packages/component-manuscript/src/components/ManuscriptPage.js @@ -42,6 +42,7 @@ import { canMakeRevision, canMakeDecision, isHEToManuscript, + canSubmitRevision, canEditManuscript, canInviteReviewers, pendingHEInvitation, @@ -65,6 +66,7 @@ import { } from 'pubsweet-component-faraday-ui' import ManuscriptLayout from './ManuscriptLayout' +import withSubmitRevision from '../submitRevision/withSubmitRevision' import { parseEicDecision, parseSearchParams, @@ -145,11 +147,11 @@ export default compose( journal, fragment, collection, + isFetching, currentUser, pendingHEInvitation, pendingOwnRecommendation, pendingReviewerInvitation, - isFetching, }, ) => ({ currentUser: { @@ -162,6 +164,7 @@ export default compose( isReviewer: currentUserIsReviewer(state, get(fragment, 'id', '')), isHEToManuscript: isHEToManuscript(state, get(collection, 'id', '')), permissions: { + canSubmitRevision: canSubmitRevision(state, collection, fragment), canMakeHERecommendation: canMakeHERecommendation(state, { collection, statuses: get(journal, 'statuses', {}), @@ -192,6 +195,7 @@ export default compose( publonsFetching: isFetching, }, formValues: { + revision: getFormValues('revision')(state), eicDecision: getFormValues('eic-decision')(state), reviewerReport: getFormValues('reviewerReport')(state), responseToInvitation: getFormValues('answer-invitation')(state), @@ -503,6 +507,7 @@ export default compose( get(currentUser, 'isReviewer', false) && isUndefined(submittedOwnRecommendation), })), + withSubmitRevision, lifecycle({ componentDidMount() { const { @@ -519,11 +524,11 @@ export default compose( fetchUpdatedCollection, editorialRecommendations, currentUser: { + isEIC, isInvitedHE, isInvitedToReview, isHEToManuscript, - isEIC, - permissions: { canInviteReviewers }, + permissions: { canInviteReviewers, canSubmitRevision }, }, } = this.props @@ -573,6 +578,10 @@ export default compose( if ((isEIC || isHEToManuscript) && !!editorialRecommendations.length) { this.props.toggleEditorialComments() } + + if (canSubmitRevision) { + this.props.toggleEditorialComments() + } }, componentDidUpdate(prevProps) { const { diff --git a/packages/component-manuscript/src/submitRevision/utils.js b/packages/component-manuscript/src/submitRevision/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..ab28ccc47a0564071bd987022aa022f5f3fe20a8 --- /dev/null +++ b/packages/component-manuscript/src/submitRevision/utils.js @@ -0,0 +1,92 @@ +import { actions } from 'pubsweet-client' +import { get, debounce, omit, set } from 'lodash' +import { handleError } from 'pubsweet-component-faraday-ui' +import { autosaveRequest } from 'pubsweet-component-wizard/src/redux/autosave' +import { submitRevision } from 'pubsweet-component-wizard/src/redux/conversion' + +const parseRevision = (values, fragment) => { + const v = omit(values, 'authorForm') + + return { + ...fragment, + revision: { + ...v, + }, + } +} + +const _onChange = (values, dispatch, { collection, fragment }) => { + dispatch(autosaveRequest()) + dispatch(actions.updateFragment(collection, parseRevision(values, fragment))) +} + +export const onChange = debounce(_onChange, 1000, { maxWait: 5000 }) + +export const onSubmit = ( + values, + dispatch, + { history, fragment, collection, showModal, setFetching, canSubmit }, +) => { + if (!canSubmit) return + + showModal({ + title: 'Ready to submit your revision?', + subtitle: `Once submitted, the submission can't be modified.`, + onConfirm: ({ hideModal, setModalError }) => { + setFetching(true) + return submitRevision({ + fragmentId: fragment.id, + collectionId: collection.id, + }) + .then( + r => { + Promise.all([ + dispatch(actions.getCollection(collection)), + dispatch(actions.getFragments({ id: collection.id })), + ]).then(() => { + setFetching(false) + hideModal() + history.push( + `/projects/${r.collectionId}/versions/${r.id}/details`, + ) + }) + }, + err => { + throw err + }, + ) + .catch(err => { + setFetching(false) + handleError(setModalError)(err) + }) + }, + }) +} + +export const getInitialValues = fragment => ({ + files: get(fragment, 'revision.files', {}), + authors: get(fragment, 'revision.authors', []), + metadata: get(fragment, 'revision.metadata', { + abstract: '', + title: '', + type: '', + }), + responseToReviewers: get(fragment, 'revision.responseToReviewers', { + content: '', + file: null, + }), +}) + +export const validate = ({ responseToReviewers }) => { + const errors = {} + + if (!responseToReviewers.content && !responseToReviewers.file) { + set( + errors, + 'responseToReviewers.content', + 'A reply or a report file is needed.', + ) + } + + return errors +} diff --git a/packages/component-manuscript/src/submitRevision/withSubmitRevision.js b/packages/component-manuscript/src/submitRevision/withSubmitRevision.js new file mode 100644 index 0000000000000000000000000000000000000000..40427a9ceed7074deb17f989c677d9f3224a7279 --- /dev/null +++ b/packages/component-manuscript/src/submitRevision/withSubmitRevision.js @@ -0,0 +1,148 @@ +import { connect } from 'react-redux' +import { get, pick, isNull } from 'lodash' +import { withRouter } from 'react-router-dom' +import { + MultiAction, + handleError, + withFetching, + withFilePreview, + withFileDownload, +} from 'pubsweet-component-faraday-ui' +import { DragDropContext } from 'react-dnd' +import { change as changeForm } from 'redux-form' +import HTML5Backend from 'react-dnd-html5-backend' +import { withModal } from 'pubsweet-component-modal/src/components' +import { + compose, + toClass, + withProps, + withHandlers, + setDisplayName, + withStateHandlers, +} from 'recompose' + +import { + addAuthor, + deleteAuthor, +} from 'pubsweet-components-faraday/src/redux/authors' + +import { + uploadFile, + deleteFile, +} from 'pubsweet-components-faraday/src/redux/files' + +import { onChange, onSubmit, getInitialValues, validate } from './utils' + +export default compose( + withRouter, + withFetching, + withFilePreview, + withFileDownload, + withStateHandlers( + { authorEditIndex: null }, + { + onAuthorEdit: () => index => ({ + authorEditIndex: index, + }), + }, + ), + connect( + state => ({ + canSubmit: !get(state, 'form.revision.syncErrors'), + hasFormError: + get(state, 'form.revision.syncErrors') && + get(state, 'form.revision.anyTouched', false), + }), + { changeForm }, + ), + withProps(() => ({ + modalKey: 'submitRevision', + })), + withModal(({ isFetching }) => ({ + isFetching, + modalComponent: MultiAction, + })), + withHandlers({ + addResponseFile: ({ + setError, + fragment, + changeForm, + setFetching, + }) => file => { + setFetching(true) + return uploadFile({ + file, + fragment, + type: 'responseToReviewers', + }) + .then(f => { + setFetching(false) + changeForm('revision', 'responseToReviewers.file', f) + }) + .catch(err => { + setFetching(false) + handleError(setError)(err) + }) + }, + deleteResponseFile: ({ setError, changeForm, setFetching }) => file => { + setFetching(true) + return deleteFile(file.id, 'responseToReviewers') + .then(r => { + setFetching(false) + changeForm('revision', 'responseToReviewers.file', null) + }) + .catch(err => { + setFetching(false) + handleError(setError)(err) + }) + }, + addFile: () => uploadFile, + addAuthor: () => addAuthor, + deleteFile: () => deleteFile, + deleteAuthor: () => deleteAuthor, + previewFile: ({ previewFile }) => previewFile, + downloadFile: ({ downloadFile }) => downloadFile, + }), + DragDropContext(HTML5Backend), + toClass, + withProps(props => ({ + submitRevision: { + initialValues: getInitialValues(props.fragment), + ...pick(props, [ + 'addFile', + 'journal', + 'history', + 'fragment', + 'canSubmit', + 'addAuthor', + 'showModal', + 'changeForm', + 'collection', + 'isFetching', + 'deleteFile', + 'currentUser', + 'setFetching', + 'previewFile', + 'downloadFile', + 'onAuthorEdit', + 'deleteAuthor', + 'getSignedUrl', + 'hasFormError', + 'fetchingError', + 'addResponseFile', + 'deleteResponseFile', + ]), + isEditingAuthor: !isNull(props.authorEditIndex), + onChange, + onSubmit, + validate, + responseFile: get( + props, + 'formValues.revision.responseToReviewers.file', + null, + ), + }, + })), + + setDisplayName('SubmitRevisionHOC'), +) diff --git a/packages/component-modal/src/components/ConfirmationModal.js b/packages/component-modal/src/components/ConfirmationModal.js index 55b2b1012309f77fdf25e892516c8c6ff9662fdf..01e52582e3327b54a579e05cf5e19973b47422cc 100644 --- a/packages/component-modal/src/components/ConfirmationModal.js +++ b/packages/component-modal/src/components/ConfirmationModal.js @@ -16,7 +16,7 @@ const ConfirmationModal = ({ cancelText = 'Cancel', }) => ( <Root> - <CloseIcon data-test="icon-modal-hide" onClick={onClose}> + <CloseIcon data-test-id="icon-modal-hide" onClick={onClose}> <Icon primary>x</Icon> </CloseIcon> {title && <Title dangerouslySetInnerHTML={{ __html: title }} />} @@ -26,7 +26,7 @@ const ConfirmationModal = ({ {modalError && <ErrorMessage>{modalError}</ErrorMessage>} <ButtonsContainer> - <Button data-test="button-modal-hide" onClick={onClose}> + <Button data-test-id="button-modal-hide" onClick={onClose}> {cancelText} </Button> {onConfirm && @@ -35,7 +35,11 @@ const ConfirmationModal = ({ <Spinner size={4} /> </SpinnerContainer> ) : ( - <Button data-test="button-modal-confirm" onClick={onConfirm} primary> + <Button + data-test-id="button-modal-confirm" + onClick={onConfirm} + primary + > {confirmText} </Button> ))} diff --git a/packages/component-modal/src/components/SubmissionModal.js b/packages/component-modal/src/components/SubmissionModal.js index e88fdf1695129a07e11d78c2cdc36f22bbb3afa1..f2223512482016ef96d14a8e077759dbbd5883d8 100644 --- a/packages/component-modal/src/components/SubmissionModal.js +++ b/packages/component-modal/src/components/SubmissionModal.js @@ -46,7 +46,7 @@ const SubmissionModal = ({ </Text> {modalError && <ErrorMessage>{modalError}</ErrorMessage>} <ButtonsContainer> - <Button data-test="button-modal-hide" onClick={hideModal}> + <Button data-test-id="button-modal-hide" onClick={hideModal}> {cancelText} </Button> {onConfirm && @@ -55,7 +55,11 @@ const SubmissionModal = ({ <Spinner size={4} /> </SpinnerContainer> ) : ( - <Button data-test="button-modal-confirm" onClick={onConfirm} primary> + <Button + data-test-id="button-modal-confirm" + onClick={onConfirm} + primary + > {confirmText} </Button> ))} diff --git a/packages/component-modal/src/components/SuccessModal.js b/packages/component-modal/src/components/SuccessModal.js index aa63fb4613805ee5cfff44883b7151b9042c4565..7e2b5752655d82ab4085f552796d7a9b3867ff0c 100644 --- a/packages/component-modal/src/components/SuccessModal.js +++ b/packages/component-modal/src/components/SuccessModal.js @@ -7,7 +7,7 @@ const SuccessModal = ({ title, confirmText = 'OK', hideModal, theme }) => ( <Root> {title && <Title dangerouslySetInnerHTML={{ __html: title }} />} <ButtonsContainer> - <Button data-test="button-modal-confirm" onClick={hideModal} primary> + <Button data-test-id="button-modal-confirm" onClick={hideModal} primary> {confirmText} </Button> </ButtonsContainer> diff --git a/packages/component-modal/src/components/withModal.js b/packages/component-modal/src/components/withModal.js index 3d7f9913d86dd3fb67526c1b0e81d958c7df2a76..a79c64635bc549950020e9251f5af02dbf82e796 100644 --- a/packages/component-modal/src/components/withModal.js +++ b/packages/component-modal/src/components/withModal.js @@ -1,7 +1,7 @@ import React, { Fragment } from 'react' -import { omit } from 'lodash' -import { connect } from 'react-redux' +import { omit, get } from 'lodash' import { compose } from 'recompose' +import { connect } from 'react-redux' import Modal from './Modal' import { showModal, hideModal, setModalError } from '../redux/modal' @@ -15,7 +15,7 @@ const mapState = state => ({ const mapDispatch = (dispatch, props) => ({ hideModal: () => dispatch(hideModal()), showModal: (modalProps = {}) => - dispatch(showModal(props.modalKey, modalProps)), + dispatch(showModal(get(props, 'modalKey'), modalProps)), setModalError: errorMessage => dispatch(setModalError(errorMessage)), }) diff --git a/packages/component-wizard/src/components/StepThree.js b/packages/component-wizard/src/components/StepThree.js index 9a4a67aabed1c0cbe89bded838d20fa29f74fc3c..4b7666551a992a1a389bfdd0e050ee00a8bd3a19 100644 --- a/packages/component-wizard/src/components/StepThree.js +++ b/packages/component-wizard/src/components/StepThree.js @@ -9,8 +9,8 @@ import { Empty } from './' const StepThree = ({ token, - version, - project, + fragment, + collection, changeForm, deleteFile, uploadFile, @@ -36,13 +36,13 @@ const StepThree = ({ <Field component={Empty} name="files" /> <WizardFiles changeForm={changeForm} + collection={collection} deleteFile={deleteFile} - files={get(version, 'files', {})} + files={get(fragment, 'files', {})} + fragment={fragment} getSignedUrl={getSignedUrl} - project={project} token={token} uploadFile={uploadFile} - version={version} /> {filesError && ( <Row justify="flex-start" mt={1}> diff --git a/packages/component-wizard/src/components/StepTwo.js b/packages/component-wizard/src/components/StepTwo.js index 659b4fccc156231e9deaf5baf8f9b042069b9005..4060c434bfab905255b55fddf63e87f9d3548a8d 100644 --- a/packages/component-wizard/src/components/StepTwo.js +++ b/packages/component-wizard/src/components/StepTwo.js @@ -20,10 +20,10 @@ import { H2, Menu, TextField, ValidatedField } from '@pubsweet/ui' import { Empty } from './' const StepTwo = ({ - version, - project, + fragment, journal, addAuthor, + collection, changeForm, formValues, isAuthorEdit, @@ -88,16 +88,16 @@ const StepTwo = ({ <Field component={Empty} name="authors" /> <WizardAuthors addAuthor={addAuthor} - authors={get(version, 'authors', [])} + authors={get(fragment, 'authors', [])} changeForm={changeForm} + collection={collection} deleteAuthor={deleteAuthor} error={authorsError} + fragment={fragment} isAuthorEdit={isAuthorEdit} isAuthorsFetching={isAuthorsFetching} journal={journal} onAuthorEdit={setAuthorEditIndex} - project={project} - version={version} /> {questions.map(q => ( diff --git a/packages/component-wizard/src/components/SubmissionWizard.js b/packages/component-wizard/src/components/SubmissionWizard.js index 74047fae170ce629995ecc945d4acecb70fc8e51..2280a49a7cf5e13c423bd0d32021021b96a06dcc 100644 --- a/packages/component-wizard/src/components/SubmissionWizard.js +++ b/packages/component-wizard/src/components/SubmissionWizard.js @@ -82,12 +82,12 @@ const Wizard = ({ ) : ( <Fragment> <Button - data-test="submission-back" + data-test-id="submission-back" mr={1} onClick={isFirstStep ? history.goBack : prevStep} >{`< BACK`}</Button> <Button - data-test="submission-next" + data-test-id="submission-next" ml={isFirstStep ? 0 : 1} onClick={handleSubmit} primary @@ -116,19 +116,17 @@ export default compose( (state, { match }) => ({ token: getUserToken(state), isFetching: getAutosaveFetching(state), + isFilesFetching: getFileFetching(state), + reduxAuthorError: getAuthorError(state), formValues: getFormValues('submission')(state), submitFailed: hasSubmitFailed('submission')(state), formSyncErrors: getFormSyncErrors('submission')(state), - version: selectFragment(state, get(match, 'params.version')), - project: selectCollection(state, get(match, 'params.project')), - isFilesFetching: getFileFetching(state), + fragment: selectFragment(state, get(match, 'params.version')), + collection: selectCollection(state, get(match, 'params.project')), isAuthorsFetching: getAuthorFetching(state) || getAutosaveFetching(state), - reduxAuthorError: getAuthorError(state), }), { - addAuthor, changeForm, - deleteAuthor, submitManuscript, updateFragment: actions.updateFragment, }, @@ -156,8 +154,10 @@ export default compose( authorEditIndex, reduxAuthorError, }) => ({ + addAuthor, deleteFile, uploadFile, + deleteAuthor, getSignedUrl, isFirstStep: step === 0, isAuthorEdit: !isNull(authorEditIndex), diff --git a/packages/component-wizard/src/components/utils.js b/packages/component-wizard/src/components/utils.js index 9b922cfc1ee32ab719b35ab431cebafdac41cceb..eb333851dd032ea8341ef8b25144b3d2a0233e0d 100644 --- a/packages/component-wizard/src/components/utils.js +++ b/packages/component-wizard/src/components/utils.js @@ -17,21 +17,21 @@ import { } from '../redux/autosave' import { SubmissionStatement } from './' -export const setInitialValues = ({ version }) => ({ +export const setInitialValues = ({ fragment }) => ({ initialValues: { - files: get(version, 'files', {}), - authors: get(version, 'authors', []), - metadata: get(version, 'metadata', {}), - conflicts: get(version, 'conflicts', { + files: get(fragment, 'files', {}), + authors: get(fragment, 'authors', []), + metadata: get(fragment, 'metadata', {}), + conflicts: get(fragment, 'conflicts', { hasConflicts: 'no', hasDataAvailability: '', hasFunding: '', }), - declarations: get(version, 'declarations', { agree: false }), + declarations: get(fragment, 'declarations', { agree: false }), }, }) -export const validate = (values, props) => { +export const validate = values => { const errors = {} if (!get(values, 'declarations.agree')) { @@ -57,8 +57,12 @@ export const validate = (values, props) => { return errors } -const _onChange = (values, dispatch, { project, version, updateFragment }) => { - const previousValues = pick(version, [ +const _onChange = ( + values, + dispatch, + { collection, fragment, updateFragment }, +) => { + const previousValues = pick(fragment, [ 'files', 'authors', 'metadata', @@ -68,8 +72,8 @@ const _onChange = (values, dispatch, { project, version, updateFragment }) => { const newValues = omit(values, ['agree', 'authorForm']) if (!isEqual(newValues, previousValues)) { dispatch(autosaveRequest()) - updateFragment(project, { - ...version, + updateFragment(collection, { + ...fragment, ...newValues, }).then( () => dispatch(autosaveSuccess()), @@ -92,8 +96,8 @@ export const onSubmit = ( isEditMode, setModalError, submitManuscript, - version: { id: fragmentId }, - project: { id: collectionId, customId }, + fragment: { id: fragmentId }, + collection: { id: collectionId, customId }, }, ) => { if (step !== 2) { @@ -107,14 +111,14 @@ export const onSubmit = ( cancelText: 'BACK TO SUBMISSION', onConfirm: () => { dispatch(autosaveRequest()) - submitManuscript(collectionId, fragmentId) + submitManuscript({ collectionId, fragmentId }) .then(r => { hideModal() dispatch(autosaveSuccess()) history.push('/confirmation-page', { customId, - version: fragmentId, - project: collectionId, + fragment: fragmentId, + collection: collectionId, }) }) .catch(err => { diff --git a/packages/component-wizard/src/redux/autosave.js b/packages/component-wizard/src/redux/autosave.js index 772755f4eb6f3f65900020f484c50bc6ee141f3b..28ec87bc0dc7586c52df6f84cb291d38cbf066af 100644 --- a/packages/component-wizard/src/redux/autosave.js +++ b/packages/component-wizard/src/redux/autosave.js @@ -28,6 +28,8 @@ export const getAutosave = state => get(state, 'autosave', initialState) export const getAutosaveFetching = state => get(state, 'autosave.isFetching', false) +// due to faulty error handing inside the updateFragment action, we have to +// handle error and success here export default (state = initialState, action) => { switch (action.type) { case AUTOSAVE_REQUEST: @@ -35,11 +37,13 @@ export default (state = initialState, action) => { ...initialState, isFetching: true, } + case 'UPDATE_FRAGMENT_FAILURE': case AUTOSAVE_FAILURE: return { ...initialState, error: action.error, } + case 'UPDATE_FRAGMENT_SUCCESS': case AUTOSAVE_SUCCESS: return { ...initialState, diff --git a/packages/component-wizard/src/redux/conversion.js b/packages/component-wizard/src/redux/conversion.js index 16a116a0dd13395cf820675a45f10e091c3afe9c..12a1703c7baaa28ba973ea8ddf3ecb8d3ed2dc31 100644 --- a/packages/component-wizard/src/redux/conversion.js +++ b/packages/component-wizard/src/redux/conversion.js @@ -1,8 +1,8 @@ import moment from 'moment' import { pick, get } from 'lodash' import { actions } from 'pubsweet-client' -import { create, update } from 'pubsweet-client/src/helpers/api' import journalConfig from 'xpub-faraday/app/config/journal' +import { create, update } from 'pubsweet-client/src/helpers/api' /* constants */ export const CREATE_DRAFT_REQUEST = 'CREATE_DRAFT_REQUEST' @@ -90,7 +90,7 @@ export const createDraftSubmission = history => (dispatch, getState) => { }) } -export const submitManuscript = (collectionId, fragmentId) => dispatch => +export const submitManuscript = ({ collectionId, fragmentId }) => dispatch => create(`/collections/${collectionId}/fragments/${fragmentId}/submit`) export const createRevision = ( @@ -122,8 +122,8 @@ export const createRevision = ( }) } -export const submitRevision = (collId, fragId) => dispatch => - update(`/collections/${collId}/fragments/${fragId}/submit`) +export const submitRevision = ({ collectionId, fragmentId }) => + update(`/collections/${collectionId}/fragments/${fragmentId}/submit`) /* reducer */ const initialState = { diff --git a/packages/components-faraday/src/components/Dashboard/DashboardFilters.js b/packages/components-faraday/src/components/Dashboard/DashboardFilters.js index 2f1794281d784ee183df364b9322a61c56c5f251..642099f9136951bec02aef4983b0a2e796eea196 100644 --- a/packages/components-faraday/src/components/Dashboard/DashboardFilters.js +++ b/packages/components-faraday/src/components/Dashboard/DashboardFilters.js @@ -8,17 +8,17 @@ const DashboardFilters = ({ changeFilterValue, getDefaultFilterValue, }) => ( - <Row - alignItems="flex-end" - data-test-id="dashboard-filters" - justify="flex-start" - mb={1} - mt={2} - > + <Row alignItems="flex-end" justify="flex-start" mb={1} mt={2}> <Text mr={1} pb={1} secondary> Filters </Text> - <Item alignItems="flex-start" flex={0} mr={1} vertical> + <Item + alignItems="flex-start" + data-test-id="dashboard-filter-priority" + flex={0} + mr={1} + vertical + > <Label>Priority</Label> <Menu inline @@ -28,7 +28,12 @@ const DashboardFilters = ({ value={getDefaultFilterValue('priority')} /> </Item> - <Item alignItems="flex-start" flex={0} vertical> + <Item + alignItems="flex-start" + data-test-id="dashboard-filter-order" + flex={0} + vertical + > <Label>Order</Label> <Menu inline diff --git a/packages/components-faraday/src/components/Dashboard/DashboardItems.js b/packages/components-faraday/src/components/Dashboard/DashboardItems.js index 97c067c08aa06b312193e906bdae3bcf11b73485..f5a5e24df3fff52a255f0a70b427cec11726a782 100644 --- a/packages/components-faraday/src/components/Dashboard/DashboardItems.js +++ b/packages/components-faraday/src/components/Dashboard/DashboardItems.js @@ -19,7 +19,7 @@ const DashboardItem = compose( )(ManuscriptCard) const DashboardItems = ({ list, onClick, deleteProject, canViewReports }) => ( - <Root> + <Root data-test-id="dashboard-list-items"> {!list.length ? ( <Row justify="center" mt={4}> <H3>Manuscripts will appear here!</H3> diff --git a/packages/components-faraday/src/components/Reviewers/InviteReviewers.js b/packages/components-faraday/src/components/Reviewers/InviteReviewers.js index bb7b1048f4f6046697f73c9daf1c811c5bfe15cc..773a148c2d0b19522f89690d3d5ac76e3e756d57 100644 --- a/packages/components-faraday/src/components/Reviewers/InviteReviewers.js +++ b/packages/components-faraday/src/components/Reviewers/InviteReviewers.js @@ -76,7 +76,7 @@ const InviteReviewersModal = compose( invitations = [], }) => ( <Root> - <CloseIcon data-test="icon-modal-hide" onClick={closeModal}> + <CloseIcon data-test-id="icon-modal-hide" onClick={closeModal}> <Icon primary>x</Icon> </CloseIcon> diff --git a/packages/components-faraday/src/components/SignUp/SignUpStep0.js b/packages/components-faraday/src/components/SignUp/SignUpStep0.js index f17aabd46a27554e01396ab0369eb5b3124769fd..a158b73643dcabe91d17db7779a17f9f67ffae42 100644 --- a/packages/components-faraday/src/components/SignUp/SignUpStep0.js +++ b/packages/components-faraday/src/components/SignUp/SignUpStep0.js @@ -134,7 +134,7 @@ const Step0 = ({ <Row mt={3}> <Text display="flex"> Already have an account? - <ActionLink internal pl={1 / 2} to="/"> + <ActionLink data-test-id="go-to-sign-in" internal pl={1 / 2} to="/"> Sign in </ActionLink> </Text> diff --git a/packages/components-faraday/src/components/UIComponents/EQSDecisionPage.js b/packages/components-faraday/src/components/UIComponents/EQSDecisionPage.js index 1b411d6170dd77f33ad83c5c14e203bbd590220b..da01cc78751def42896d1f8bb2dd4a04cb1658ba 100644 --- a/packages/components-faraday/src/components/UIComponents/EQSDecisionPage.js +++ b/packages/components-faraday/src/components/UIComponents/EQSDecisionPage.js @@ -34,6 +34,7 @@ const Enhanched = () => ( <Label required>Manuscript ID</Label> <ValidatedField component={TextField} + data-test-id="eqs-manuscript-id" name="customId" validate={[required, digitValidator, sizeValidator]} /> diff --git a/packages/components-faraday/src/redux/authors.js b/packages/components-faraday/src/redux/authors.js index c867b596751e0c7db39949b1cfa30077156c137c..308d8a6b4b7fe58ec53c093ab3e30b6af1e3c153 100644 --- a/packages/components-faraday/src/redux/authors.js +++ b/packages/components-faraday/src/redux/authors.js @@ -1,8 +1,6 @@ import { get } from 'lodash' import { create, remove, get as apiGet } from 'pubsweet-client/src/helpers/api' -import { handleError } from './utils' - // constants const REQUEST = 'authors/REQUEST' const FAILURE = 'authors/FAILURE' @@ -25,26 +23,17 @@ export const authorSuccess = () => ({ export const getAuthors = (collectionId, fragmentId) => apiGet(`/collections/${collectionId}/fragments/${fragmentId}/users`) -export const addAuthor = (author, collectionId, fragmentId) => dispatch => { - dispatch(authorRequest()) - return create(`/collections/${collectionId}/fragments/${fragmentId}/users`, { +export const addAuthor = ({ author, collectionId, fragmentId }) => + create(`/collections/${collectionId}/fragments/${fragmentId}/users`, { email: author.email, role: 'author', ...author, - }).then(author => { - dispatch(authorSuccess()) - return author - }, handleError(authorFailure, dispatch)) -} + }) -export const deleteAuthor = (collectionId, fragmentId, userId) => dispatch => { - dispatch(authorRequest()) - return remove( - `/collections/${collectionId}/fragments/${fragmentId}/users/${userId}`, +export const deleteAuthor = ({ authorId, fragmentId, collectionId }) => + remove( + `/collections/${collectionId}/fragments/${fragmentId}/users/${authorId}`, ) - .then(() => dispatch(authorSuccess())) - .catch(handleError(authorFailure, dispatch)) -} // selectors export const getFragmentAuthors = (state, fragmentId) => diff --git a/packages/components-faraday/src/redux/files.js b/packages/components-faraday/src/redux/files.js index 02a36e8517af01e2768c6868b75c67467d78e557..4e79e1f65454bfeb0ba21de652bad73557460c03 100644 --- a/packages/components-faraday/src/redux/files.js +++ b/packages/components-faraday/src/redux/files.js @@ -72,7 +72,7 @@ export const uploadFile = ({ file, type, fragment }) => { ) } -export const deleteFile = (fileId, type = 'manuscripts') => +export const deleteFile = ({ fileId, type = 'manuscripts' }) => remove(`/files/${fileId}`) export const getSignedUrl = fileId => apiGet(`/files/${fileId}`) diff --git a/packages/styleguide/styleguide.config.js b/packages/styleguide/styleguide.config.js index 1a6fe7ba50ad201d0bc3ee0a474e8ac2c01d53d9..b59df5cb30351edf8f76fe3d11ed90e15664b03a 100644 --- a/packages/styleguide/styleguide.config.js +++ b/packages/styleguide/styleguide.config.js @@ -22,6 +22,11 @@ module.exports = { sectionDepth: 1, components: ['../component-faraday-ui/src/contextualBoxes/[A-Z]*.js'], }, + { + name: 'Submit Revision', + sectionDepth: 1, + components: ['../component-faraday-ui/src/submissionRevision/[A-Z]*.js'], + }, { name: 'Pending Items', sectionDepth: 1, diff --git a/packages/xpub-faraday/config/validations.js b/packages/xpub-faraday/config/validations.js index 978904daf61d9ad44ee03ad187d33d30dc64ac83..cc116eecca70a91cb64c085c77fc0c6b6da39cfd 100644 --- a/packages/xpub-faraday/config/validations.js +++ b/packages/xpub-faraday/config/validations.js @@ -42,7 +42,18 @@ module.exports = { hasFunding: Joi.any().valid(['yes', 'no', '']), fundingMessage: Joi.string().allow(''), }), - commentsToReviewers: Joi.string(), + responseToReviewers: Joi.object({ + file: Joi.object({ + id: Joi.string(), + name: Joi.string().required(), + originalName: Joi.string(), + type: Joi.string(), + size: Joi.number(), + url: Joi.string(), + signedUrl: Joi.string(), + }).allow(null), + content: Joi.string().allow(''), + }), files: Joi.object({ manuscript: Joi.any(), manuscripts: Joi.array().items(