Skip to content

Commit

Permalink
Adding UI for publication workflow (the user part)
Browse files Browse the repository at this point in the history
  • Loading branch information
SarahBA committed Feb 1, 2018
1 parent 8711708 commit 76e7fcc
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 71 deletions.
62 changes: 35 additions & 27 deletions webui/src/components/editfiles.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,10 @@ export const EditFiles = React.createClass({
}
},

componentWillReceiveProps(props) {
props.readOnly !== this.props.readOnly;
},

handleAdd: function(fs, location) {
const files = this.state.files;
for (let i = 0; i < fs.length; ++i) {
Expand Down Expand Up @@ -547,39 +551,43 @@ export const EditFiles = React.createClass({
this.updateNext();
const b2dropZone = <B2DropZone close={e => this.props.setModal(false)}
onFiles={fs => this.handleAdd(fs, 'b2drop')} />;
// FIXME: the following doesn't work, need to fix it and change the cursor to disabled icon
const disabledCursor = this.props.readOnly ? " , cursor: 'not-allowed'" : "";
return (
<div>
<div className="row" style={{borderBottom:'1px solid #ddd'}}>
<h3 className="col-md-3">
Add files
</h3>
<div className="col-md-9" style={{margin:'1em 0'}}>
<LocalDropZone onFiles={fs => this.handleAdd(fs, 'local')}/>
</div>
<div className="col-md-offset-3 col-md-9" style={{marginBottom:'1em'}}>
<button className='b2dropbutton' onClick={e => this.props.setModal(b2dropZone)}>
<img src="/img/b2drop.png"/>
<h3>Add B2DROP files</h3>
</button>
</div>

{ !this.state.files.length ? false :
<div className="col-md-offset-3 col-md-9">
{ this.renderUploadQueue() }
</div>
}
</div>

{ !this.props.files.length ? false :
<fieldset disabled={this.props.readOnly}>
<div className="row" style={{borderBottom:'1px solid #ddd'}}>
<div className="col-md-3">
<h3> Uploaded files </h3>
<h3 className="col-md-3">
Add files
</h3>
<div className="col-md-9" style={{margin:'1em 0'}}>
<LocalDropZone onFiles={fs => this.handleAdd(fs, 'local')}/>
</div>
<div className="col-md-9" style={{marginBottom:'1em'}}>
{ this.renderRecordFiles() }
<div className="col-md-offset-3 col-md-9" style={{marginBottom:'1em'}}>
<button className='b2dropbutton' onClick={e => this.props.setModal(b2dropZone)} disabled={this.props.readOnly}>
<img src="/img/b2drop.png"/>
<h3>Add B2DROP files</h3>
</button>
</div>

{ !this.state.files.length ? false :
<div className="col-md-offset-3 col-md-9">
{ this.renderUploadQueue() }
</div>
}
</div>
}

{ !this.props.files.length ? false :
<div className="row" style={{borderBottom:'1px solid #ddd'}}>
<div className="col-md-3">
<h3> Uploaded files </h3>
</div>
<div className="col-md-9" style={{marginBottom:'1em'}}>
{ this.renderRecordFiles() }
</div>
</div>
}
</fieldset>
</div>
);
},
Expand Down
181 changes: 146 additions & 35 deletions webui/src/components/editrecord.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ const EditRecord = React.createClass({
errors: {},
dirty: false,
waitingForServer: false,
readOnly: false,
reviewOrPublishConfirmed: null,
revokeSubmitted:false,
};
},

Expand All @@ -122,7 +125,8 @@ const EditRecord = React.createClass({
<EditFiles files={files ? files.toJS() : []}
record={this.props.record}
setState={setState}
setModal={modal => this.setState({modal})} />
setModal={modal => this.setState({modal})}
readOnly={this.state.readOnly} />
);
},

Expand Down Expand Up @@ -268,7 +272,7 @@ const EditRecord = React.createClass({
};
return (
<DateTimePicker format={"LL"} time={false} finalView={"year"}
defaultValue={initial} onChange={onChange} />
defaultValue={initial} onChange={onChange} disabled={this.state.readOnly} />
);
},

Expand All @@ -293,12 +297,12 @@ const EditRecord = React.createClass({
const languages = serverCache.getLanguages();
field = (languages instanceof Error) ? <Err err={languages}/> :
<SelectBig data={languages}
onSelect={x=>this.setValue(schema, path, x)} value={this.getValue(path)} />;
onSelect={x=>this.setValue(schema, path, x)} value={this.getValue(path)} readOnly={this.state.readOnly} />;
} else if (path.length === 2 && path[0] === 'disciplines') {
const disciplines = serverCache.getDisciplines();
field = (disciplines instanceof Error) ? <Err err={disciplines}/> :
<SelectBig data={disciplines}
onSelect={x=>this.setValue(schema, path, x)} value={this.getValue(path)} />;
onSelect={x=>this.setValue(schema, path, x)} value={this.getValue(path)} readOnly={this.state.readOnly} />;
} else if (schema.get('type') === 'array') {
const itemSchema = schema.get('items');
const raw_values = this.getValue(path);
Expand Down Expand Up @@ -465,13 +469,21 @@ const EditRecord = React.createClass({
}
record = addEmptyMetadataBlocks(record, props.blockSchemas) || record;
this.setState({record});
this.setState({reviewOrPublishConfirmed:record.get('publication_state')});
} else if (this.state.record && props.blockSchemas) {
const record = addEmptyMetadataBlocks(this.state.record, props.blockSchemas);
if (record) {
this.setState({record});
}
}

// 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){
this.setState({readOnly: true});
}
}

function addEmptyMetadataBlocks(record, blockSchemas) {
if (!blockSchemas || !blockSchemas.length) {
return false;
Expand Down Expand Up @@ -550,32 +562,111 @@ const EditRecord = React.createClass({
return errors;
},

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;
}
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';
const patch = compare(original, updated);
this.updateBrowserState(patch, 'edit');
}
},

updateRecord(event) {
event.preventDefault();
const errors = this.findValidationErrors();
if (this.state.fileState !== 'done' || pairs(errors).length > 0) {
this.setState({errors});
return;
}
const original = this.props.record.get('metadata').toJS();
const updated = this.state.record.toJS();
const patch = compare(original, updated);
if (!patch || !patch.length) {
this.setState({dirty:false});
return;
}
const 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});
notifications.clearAll();
} else {
browser.gotoRecord(record.id);

if(this.state.reviewOrPublishConfirmed == 'draft'){
// Save draft
const original = this.props.record.get('metadata').toJS();
const updated = this.state.record.toJS();
const patch = compare(original, updated);
if (!patch || !patch.length) {
this.setState({dirty:false});
return;
}
this.updateBrowserState(patch, 'save_draft');
} else if(this.state.reviewOrPublishConfirmed == 'published' && this.state.record.get('publication_state') == 'published'){
// Editing metadata of a published record (workflow = direct_publish)
const original = this.props.record.get('metadata').toJS();
const updated = this.state.record.toJS();
const patch = compare(original, updated);
if (!patch || !patch.length) {
this.setState({dirty:false});
return;
}
this.updateBrowserState(patch, 'edit_metadata');
} else {
// (review workflow) submit a record for review or (direct publish workflow) Publishing a record
const record = this.state.record.set('publication_state', this.state.reviewOrPublishConfirmed);
this.setState({record});
}
},

updateBrowserState(patch, caseValue) {
let afterPatch;
switch (caseValue) {
case 'submitted':
afterPatch = (record) => {
// Sending a record for review
if (this.props.community.get("publication_workflow") == 'review_and_publish'){
this.setState({dirty:false, waitingForServer: false, readOnly: true});
notifications.warning(`This record is submitted and waiting for review by your community administrator`);
browser.gotoEditRecord(record.id);
} else {
// direct publish workflow
browser.gotoRecord(record.id);
}
}
break;

case 'edit':
afterPatch = (record) => {
this.setState({waitingForServer: false, revokeSubmitted: false});
notifications.clearAll();
browser.gotoEditRecord(record.id);
}
break;

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':
afterPatch = (record) => {
// 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});
notifications.clearAll();
browser.gotoRecord(record.id);
}
break;
}

const onError = (xhr) => {
this.setState({waitingForServer: false});
onAjaxError(xhr);
Expand All @@ -587,19 +678,24 @@ const EditRecord = React.createClass({
} catch (_) {
}
}

this.setState({waitingForServer: true});
this.props.patchFn(patch, afterPatch, onError);
},

editSubmittedRecord(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});
},

isForPublication() {
return this.state.record.get('publication_state') == 'submitted';
return this.state.reviewOrPublishConfirmed == 'submitted';
},

setPublishedState(e) {
const state = e.target.checked ? 'submitted' : 'draft';
const record = this.state.record.set('publication_state', state);
this.setState({record});
this.setState({reviewOrPublishConfirmed:state});
},

renderUpdateRecordForm() {
Expand All @@ -620,16 +716,20 @@ const EditRecord = React.createClass({
this.isForPublication() ? 'btn-primary btn-danger' :
this.state.dirty ? 'btn-primary' : 'disabled';
const text = this.state.waitingForServer ? "Updating record, please wait..." :
this.isForPublication() ? 'Save and Publish' :
this.state.dirty ? 'Save Draft' : 'The draft is up to date';
this.props.community.get("publication_workflow") == 'review_and_publish' ? (this.isForPublication() ? 'Save and submit for review' :
this.state.dirty ? 'Save Draft' : 'The draft is up to date')
: (this.isForPublication() ? 'Save and Publish' :
this.state.dirty ? 'Save Draft' : 'The draft is up to date');
const label = this.props.community.get("publication_workflow") == 'review_and_publish' ? " Submit draft for review by your community administrator" : " Submit draft for publication";
const publicationNote = this.props.community.get("publication_workflow") == 'review_and_publish' ? "" : "When the draft is published it will be assigned a PID, making it publicly citable. But a published record's files can no longer be modified by its owner."

return (
<div className="col-sm-offset-3 col-sm-9">
<label style={{fontSize:18, fontWeight:'normal'}}>
<input type="checkbox" value={this.isForPublication} onChange={this.setPublishedState}/>
{" "}Submit draft for publication
{label}
</label>
<p>When the draft is published it will be assigned a PID, making it publicly citable.
But a published record's files can no longer be modified by its owner. </p>
<p>{publicationNote}</p>
<button type="submit" className={"btn btn-default btn-block "+klass} onClick={this.updateRecord}>{text}</button>
</div>
);
Expand Down Expand Up @@ -666,21 +766,32 @@ const EditRecord = React.createClass({
{ this.props.isDraft ? this.renderFileBlock() : false }
</div>
<div className="col-xs-12">
<fieldset disabled={this.state.readOnly}>
<form className="form-horizontal" onSubmit={this.updateRecord}>
{ this.renderFieldBlock(null, rootSchema) }

{ !blockSchemas ? false :
blockSchemas.map(([id, blockSchema]) =>
this.renderFieldBlock(id, (blockSchema||Map()).get('json_schema'))) }
</form>
</fieldset>
</div>
</div>
<div className="row">
<div className="form-group submit row" style={{margin:'2em 0', paddingTop:'2em', borderTop:'1px solid #eee'}}>
{pairs(this.state.errors).map( ([id, msg]) =>
<div className="col-sm-offset-3 col-sm-9 alert alert-warning" key={id}>{msg} </div>) }
{ this.props.isDraft ? this.renderSubmitDraftForm() : this.renderUpdateRecordForm() }
</div>
{this.state.record.get('publication_state') == 'submitted' && this.state.readOnly ?
<div className="form-group submit row" style={{margin:'2em 0', paddingTop:'2em', borderTop:'1px solid #eee'}}>
<div className="col-sm-offset-3 col-sm-9">
<p> 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. </p>
<button type="submit" className={"btn btn-default btn-block btn-primary btn-danger "} onClick={this.editSubmittedRecord}>Edit</button>
</div>
</div>
:
<div className="form-group submit row" style={{margin:'2em 0', paddingTop:'2em', borderTop:'1px solid #eee'}}>
{pairs(this.state.errors).map( ([id, msg]) =>
<div className="col-sm-offset-3 col-sm-9 alert alert-warning" key={id}>{msg} </div>) }
{ this.props.isDraft ? this.renderSubmitDraftForm() : this.renderUpdateRecordForm() }
</div>
}
</div>
</div>
);
Expand Down
4 changes: 3 additions & 1 deletion webui/src/components/search.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ export const SearchRecordRoute = React.createClass({
const communities = serverCache.getCommunities();
const location = this.props.location || {};
const drafts = (location.query.drafts == 1) ? 1 : "";
const submitted = (location.query.submitted == 1) ? 1 : "";
const result = serverCache.searchRecords(location.query || {});
const numResults = (result && result.get('total')) || 0;
const title = submitted ? 'Submitted for review' : ( drafts ? 'Drafts' : 'Records');
return (
<div>
{drafts ? <h1>Drafts</h1> : <h1>Records</h1>}
<h1>{title}</h1>
<Search location={location}
communities={communities}
drafts={drafts}
Expand Down
Loading

0 comments on commit 76e7fcc

Please sign in to comment.