From 151ee419c5a8e08c3b5f155c1f9919bb74b893e9 Mon Sep 17 00:00:00 2001 From: Sarah Berenji Date: Thu, 1 Mar 2018 13:22:21 +0100 Subject: [PATCH] Adding UI for publication workflow (the admin part) --- webui/src/components/editrecord.jsx | 195 +++++++++++++++++++++++----- webui/src/components/user.jsx | 31 ++++- 2 files changed, 190 insertions(+), 36 deletions(-) diff --git a/webui/src/components/editrecord.jsx b/webui/src/components/editrecord.jsx index cafff8ef18..b8d9db77f9 100644 --- a/webui/src/components/editrecord.jsx +++ b/webui/src/components/editrecord.jsx @@ -61,6 +61,11 @@ export const EditRecordRoute = React.createClass({ } }, + currentUser(){ + const { id } = this.props.params; + return serverCache.getUser(); + }, + render() { const record = this.getRecordOrDraft(); if (!record) { @@ -74,15 +79,21 @@ export const EditRecordRoute = React.createClass({ return ; } const community = serverCache.getCommunity(record.getIn(['metadata', 'community'])); + if (community instanceof Error) { return ; } + + const user = this.currentUser(); + return ( ); @@ -102,6 +113,9 @@ const EditRecord = React.createClass({ readOnly: false, reviewOrPublishConfirmed: null, revokeSubmitted:false, + isResponsibleAdmin:false, + adminUserIsEditing:false, + checkPermission: false, }; }, @@ -461,7 +475,26 @@ const EditRecord = React.createClass({ this.componentWillReceiveProps(this.props); }, + + getCurrentUserRoles(currentUser, currentDraftCommunityID){ + var check = false; + currentUser.get('roles').forEach(role => { + if(currentDraftCommunityID == role.get('id')){ + check = true; + } + }); + return check; + }, + componentWillReceiveProps(props) { + // The current user should be abble to only see his/her own drafts and submitted records + if (props.record && this.props.currentUser){ + const ownerID = props.record.getIn(['metadata', 'owners']).first(); + if(ownerID == this.props.currentUser.get('id')){ + this.setState({checkPermission:true}); + } + } + if (props.record && !this.state.record) { let record = props.record.get('metadata'); if (!record.has('community_specific')) { @@ -477,10 +510,18 @@ const EditRecord = React.createClass({ } } - // Record is submitted for review: the publication_state is 'submitted' and readonly should be True - if(this.props.community && this.state.record){ - if(this.props.community.get("publication_workflow") == 'review_and_publish' && this.state.record.get('publication_state')=='submitted' && !this.state.revokeSubmitted){ + // Record is submitted for review: the publication_state is 'submitted' and readonly should be True. + // Also, check if the current user is the community administrator who should review the + // submitted draft and if so, set the isResponsibleAdmin state to true. + if(this.props.community){ + if(this.props.community.get("publication_workflow") == 'review_and_publish' && this.props.publicationState=='submitted' && !this.state.revokeSubmitted){ this.setState({readOnly: true}); + // Check if the current user is an admin of this draft's community who can review this draft + const isResponsibleAdmin = this.getCurrentUserRoles(this.props.currentUser, this.props.community.get('roles').get('admin').get('id')); + this.setState({isResponsibleAdmin}); + } + if(this.state.adminUserIsEditing){ + this.setState({readOnly: false}); } } @@ -565,15 +606,18 @@ const EditRecord = React.createClass({ componentDidUpdate(prevProps, prevState) { const original = this.props.record.get('metadata').toJS(); let updated = this.state.record.toJS(); - // checkbox is enabled and record is going to be "submitted" for review by community admin - if (prevState.record.get('publication_state') !== this.state.record.get('publication_state') && !this.state.revokeSubmitted){ - const patch = compare(original, updated); - if (!patch || !patch.length) { - this.setState({dirty:false}); - return; + // The checkbox at the end of the page is enabled and record is going to be "submitted" for review by community admin + if(prevState.record){ + if (prevState.record.get('publication_state') !== this.state.record.get('publication_state') && !this.state.revokeSubmitted){ + const patch = compare(original, updated); + if (!patch || !patch.length) { + this.setState({dirty:false}); + return; + } + this.updateBrowserState(patch, 'submitted'); } - this.updateBrowserState(patch, 'submitted'); } + // Revoking the submitted record for review. Going back to the draft mode. if(prevState.revokeSubmitted !== this.state.revokeSubmitted && this.state.revokeSubmitted){ updated['publication_state'] = 'draft'; @@ -634,7 +678,7 @@ const EditRecord = React.createClass({ } break; - case 'edit': + case 'edit_or_reject': afterPatch = (record) => { this.setState({waitingForServer: false, revokeSubmitted: false}); notifications.clearAll(); @@ -644,15 +688,13 @@ const EditRecord = React.createClass({ case 'save_draft': afterPatch = (record) => { - // if (this.props.isDraft && !this.isForPublication()) { this.props.refreshCache(); // TODO(edima): when a draft is publised, clean the state of // records in versioned chain, to trigger a refetch of // versioning data this.setState({dirty: false, waitingForServer: false, reviewOrPublishConfirmed: 'draft'}); notifications.clearAll(); - // } - } + } break; case 'edit_metadata': @@ -665,6 +707,20 @@ const EditRecord = React.createClass({ browser.gotoRecord(record.id); } break; + + case 'accept_and_publish': + afterPatch = (record) =>{ + browser.gotoRecord(record.id); + // TODO: Send an email to the record owner and notify her + } + break; + + case 'reject': + afterPatch = (record) =>{ + browser.gotoSearch({"community":this.props.community.get("id"),"submitted":1,"drafts":1}); + // TODO: Send an email to the record owner and notify her + } + break; } const onError = (xhr) => { @@ -682,13 +738,19 @@ const EditRecord = React.createClass({ this.props.patchFn(patch, afterPatch, onError); }, - editSubmittedRecord(event){ + editSubmittedDraft(event){ event.preventDefault(); const record = this.state.record.set('publication_state', "draft"); this.setState({record}); this.setState({dirty:false, reviewOrPublishConfirmed:'draft', revokeSubmitted:true, readOnly:false}); }, + adminEditSubmittedDraft(event){ + // When the admin user is editting the sumbitted draft, the 'publication_state' should still be 'submitted' + event.preventDefault(); + this.setState({dirty:false, readOnly:false, adminUserIsEditing:true}); + }, + isForPublication() { return this.state.reviewOrPublishConfirmed == 'submitted'; }, @@ -737,12 +799,100 @@ const EditRecord = React.createClass({ } }, + rejectSubmittedDraft(event){ + event.preventDefault(); + // Change the "record_state" from "submitted" to "draft" + const original = this.props.record.get('metadata').toJS(); + const updated = this.state.record.toJS(); + updated['publication_state'] = 'draft'; + const patch = compare(original, updated); + if (!patch || !patch.length) { + return; + } + this.updateBrowserState(patch, 'reject'); + }, + + acceptSubmittedDraft(event){ + event.preventDefault(); + // Changes the "record_state" from "submitted" to "published" + const original = this.props.record.get('metadata').toJS(); + const updated = this.state.record.toJS(); + updated['publication_state'] = 'published'; + const patch = compare(original, updated); + if (!patch || !patch.length) { + return; + } + this.updateBrowserState(patch, 'accept_and_publish'); + }, + + renderButtons(){ + if(this.state.record.get('publication_state') == 'submitted'){ + if(this.state.isResponsibleAdmin){ + // Current user is a responsible admin to approve or reject the submitted draft + // Then there will be two cases: either the admin is editing the submitted draft or not + if(this.state.readOnly){ + return( +
+
+
+ +
+
+

+
+
+ +
+
+ +
+
+
+ ) + } else { + // The admin is editing the submitted draft + return( +
+
+
+ +
+
+ +
+
+
+ ) + } + } else { + // A non admin user is submitting a draft for review + return( +
+
+

Note that by editing the record, it will be revoked and won't being reviewd by your community admin anymore. You will need to submit it again.

+ +
+
) + } + } else { + // The workflow is direct publish, only needs an 'Edit' button + return (
+ {pairs(this.state.errors).map( ([id, msg]) => +
{msg}
) } + { this.props.isDraft ? this.renderSubmitDraftForm() : this.renderUpdateRecordForm() } +
) + } + }, + render() { const rootSchema = this.props.rootSchema; const blockSchemas = this.props.blockSchemas; if (!this.state.record || !rootSchema) { return ; } + if (!this.state.checkPermission){ + return ; + } const editTitle = "Editing " + (this.props.isDraft ? "draft" : "record") + (this.props.isVersion ? " version": ""); return (
@@ -780,20 +930,7 @@ const EditRecord = React.createClass({
- {this.state.record.get('publication_state') == 'submitted' && this.state.readOnly ? -
-
-

Note that by editing the record, it will be revoked and won't being reviewd by your community admin anymore. You will need to submit it again.

- -
-
- : -
- {pairs(this.state.errors).map( ([id, msg]) => -
{msg}
) } - { this.props.isDraft ? this.renderSubmitDraftForm() : this.renderUpdateRecordForm() } -
- } + {this.renderButtons()}
); diff --git a/webui/src/components/user.jsx b/webui/src/components/user.jsx index 5fddf7711a..f2285ca6b0 100644 --- a/webui/src/components/user.jsx +++ b/webui/src/components/user.jsx @@ -98,7 +98,7 @@ export const UserProfile = React.createClass({ ); }, - createLink(communitiesList, name){ + createAdminPageLink(communitiesList, name){ return (admin page) }, @@ -106,7 +106,18 @@ export const UserProfile = React.createClass({ return roles.map(r =>
  • {r.get('description')} - {r.get('name').includes(':admin')? this.createLink(communitiesList, r.get('name')) : ""} + {r.get('name').includes(':admin')? this.createAdminPageLink(communitiesList, r.get('name')) : ""} +
  • ) + }, + + listRecordsForReview(roles, communitiesList){ + const adminRolesOnly = roles.filter( r => { + return r.get('name').includes('admin') + }); + + return adminRolesOnly.map(r => +
  • + {communitiesList[r.get('name').replace(new RegExp("(^com:|:[^:]*$)",'g'),"")]} community
  • ) }, @@ -117,10 +128,14 @@ export const UserProfile = React.createClass({ } const roles = user.get('roles'); const communitiesListTemp = serverCache.getCommunities(); - var communitiesList = communitiesListTemp.reduce(function(map, community){ - map[community.get('id').replace(new RegExp("-",'g'),"")] = community.get('name'); - return map; - }); + let communitiesList; + if(communitiesListTemp.size > 0){ + communitiesList = communitiesListTemp.reduce(function(map, community){ + map[community.get('id').replace(new RegExp("-",'g'),"")] = community.get('name'); + return map; + },{}); + } + return (

    User Profile

    @@ -135,7 +150,9 @@ export const UserProfile = React.createClass({

    Submitted Records for Review

    -

    List of submitted records waiting for review by your community administrator

    +

    List of your submitted records waiting for review by your community administrator

    + {roles && roles.count() && typeof(communitiesList) !== "undefined" ?

    List of submitted records waiting for your review:

    : "" } + {roles && roles.count() && typeof(communitiesList) !== "undefined" ? this.listRecordsForReview(roles, communitiesList) :

    There is no submitted draft for review

    }

    Own records