Commit 116ebc49 authored by Valeriy Panov's avatar Valeriy Panov Committed by Matija Obreza
Browse files

Edit Files section

- Display title in Dataset details
parent 1036ed5c
Pipeline #4129 passed with stages
in 2 minutes and 54 seconds
......@@ -5,6 +5,7 @@ import { Dataset, IDatasetFilter, AccessionIdentifier } from 'model/dataset.mode
import { Creator } from 'model/creator.model';
import { Descriptor } from 'model/descriptors.model';
import { Location } from 'model/location.model';
import { RepositoryFile } from 'model/repositoryFile.model';
import { DatasetService } from 'service/DatasetService';
import { CreatorService } from 'service/CreatorService';
import { LocationService } from 'service/LocationService';
......@@ -163,21 +164,32 @@ export {
addDescriptorsToDatasetRequest, removeDescriptorsFromDatasetRequest,
};
const uploadRepositoryFileRequest = (dataset: Dataset, file: File) => (dispatch, getState) => {
const token = getState().login.access_token;
export const uploadRepositoryFileRequest = (datasetUUID: string, file: File) => (dispatch, getState) => {
const token = getState().login.access_token;
return RepositoryFileService.saveRepositoryFile(token, dataset.uuid, file)
.then((dataset) => {
dispatch(receiveDataset(dataset));
}).catch((error) => {
console.log('Save error', error);
});
return RepositoryFileService.uploadRepositoryFile(token, datasetUUID, file)
.then((dataset) => {
dispatch(receiveDataset(dataset));
}).catch((error) => {
console.log('Save error', error);
});
};
const deleteRepositoryFileRequest = (dataset: Dataset, uuid: string) => (dispatch, getState) => {
export const updateRepositoryFileRequest = (datasetUUID: string, repositoryfile: RepositoryFile) => (dispatch, getState) => {
const token = getState().login.access_token;
return RepositoryFileService.updateRepositoryFile(token, datasetUUID, repositoryfile)
.then((dataset) => {
dispatch(receiveDataset(dataset));
}).catch((error) => {
console.log('Save error', error);
});
};
export const deleteRepositoryFileRequest = (datasetUUID: string, uuid: string) => (dispatch, getState) => {
const token = getState().login.access_token;
return RepositoryFileService.deleteRepositoryFile(token, dataset.uuid, uuid)
return RepositoryFileService.deleteRepositoryFile(token, datasetUUID, uuid)
.then((dataset) => {
dispatch(receiveDataset(dataset));
}).catch((error) => {
......@@ -185,8 +197,6 @@ const deleteRepositoryFileRequest = (dataset: Dataset, uuid: string) => (dispatc
});
};
export { uploadRepositoryFileRequest, deleteRepositoryFileRequest };
function addCreatorToDataset(creator: Creator, uuid: string) {
return {
type: ADD_CREATOR_TO_DATASET,
......
......@@ -57,7 +57,8 @@ export const REMOVE_DESCRIPTORS_FROM_DATASET_URL = `${DATASET_API}/remove-descri
// RepositoryFile API. Note: starts from 'GET_DATASET_URL'
// FIXME base on DATASET_API
export const SAVE_REPOSITORY_FILE_URL = '/files/add';
export const UPLOAD_REPOSITORY_FILE_URL = '/files/add';
export const UPDATE_REPOSITORY_FILE_URL = '/files/update';
export const LIST_REPOSITORY_FILE_URL = '/files/list';
export const REMOVE_REPOSITORY_FILE_URL = '/files/delete';
......
......@@ -17,3 +17,6 @@ export const TIMING_AND_LOCATION_FORM = 'Form/TIMING_AND_LOCATION_FORM';
export const RECEIVE_LOCATION = 'App/RECEIVE_LOCATION';
export const ADD_LOCATION = 'App/ADD_LOCATION';
export const REMOVE_LOCATION = 'App/REMOVE_LOCATION';
// dataset files
export const FILES_FORM = 'Form/FILES_FORM';
import { UuidModel } from './common.model';
class RepositoryFile extends UuidModel {
public title: string;
public description: string;
public path: string;
public originalFilename: string;
public contentType: string;
......
......@@ -4,7 +4,8 @@ import {Page} from 'model/common.model';
import authenticatedRequest from 'utilities/requestUtils';
import {
LIST_REPOSITORY_FILE_URL,
SAVE_REPOSITORY_FILE_URL,
UPLOAD_REPOSITORY_FILE_URL,
UPDATE_REPOSITORY_FILE_URL,
REMOVE_REPOSITORY_FILE_URL,
GET_DATASET_URL,
} from 'constants/apiURLS';
......@@ -20,19 +21,30 @@ export class RepositoryFileService {
.then(({data}) => new Page<RepositoryFile>(data, (d) => new RepositoryFile(d)));
}
public static saveRepositoryFile(token: string, datasetUUID: string, file: File): Promise<Dataset> {
public static uploadRepositoryFile(token: string, datasetUUID: string, file: File): Promise<Dataset> {
const data = new FormData();
data.append('file', file);
return authenticatedRequest(token, {
url: `${GET_DATASET_URL}/${datasetUUID}${SAVE_REPOSITORY_FILE_URL}`,
url: `${GET_DATASET_URL}/${datasetUUID}${UPLOAD_REPOSITORY_FILE_URL}`,
method: 'POST',
data,
headers: {'Content-Type': 'multipart/form-data'},
}).then(({data}) => new Dataset(data));
}
public static updateRepositoryFile(token: string, datasetUUID: string, repositoryFile: RepositoryFile): Promise<Dataset> {
return authenticatedRequest(token, {
url: `${GET_DATASET_URL}/${datasetUUID}${UPDATE_REPOSITORY_FILE_URL}`,
method: 'POST',
data: {
...repositoryFile,
},
}).then(({data}) => new Dataset(data));
}
public static deleteRepositoryFile(token: string, datasetUUID: string, uuid: string): Promise<Dataset> {
return authenticatedRequest(token, {
......
......@@ -214,27 +214,25 @@ class DetailInfo extends React.Component<IDetailInfoProps, any> {
{
dataset.repositoryFiles && dataset.repositoryFiles.map((e: RepositoryFile, i) => (
<Grid container spacing={ 0 } key={ i } className={ `${classes.dataContainer} ${classes.grayRowsEven}` }>
<Grid item xs={ 3 } className={ `${classes.dataName}` }>
<Typography type="title" component="h3" className={ `${classes.gray} ${classes.grayTitleBig}` }>
{ e.format }
</Typography>
</Grid>
<Grid item xs={ 3 } className={ `${classes.dataName}` }>
<Typography type="title" component="h3" className={ `${classes.gray} ${classes.grayTitleBig}` }>
{ e.title || e.originalFilename }
</Typography>
</Grid>
<Grid item xs={ 12 } md={ 12 } lg={ 9 } className={ `${classes.gray} ${classes.rightTextWrapper} ${classes.centerAlign}` }>
<Hidden mdDown>
<Typography type="title" component="h3" className={ `${classes.rightText} ${classes.fontNormal}` }>
{ e.originalFilename }
</Typography>
{ e.originalFilename }
</Hidden>
<Hidden mdDown>
<a href={ `/proxy/api/v0/repository/download/${e.uuid}` }><Button raised component="span" className={ `${classes.buttonGreen} ${classes.button}` }>
Download XLS
Download
</Button></a>
</Hidden>
<Hidden lgUp>
<Button raised component="span" className={ `${classes.buttonGreen} ${classes.button}` }>
Download dataset XLS
</Button>
</Hidden>
<Hidden mdUp>
<a href={ `/proxy/api/v0/repository/download/${e.uuid}` }><Button raised component="span" className={ `${classes.buttonGreen} ${classes.button}` }>
Download dataset
</Button></a>
</Hidden>
</Grid>
</Grid>
))
......
......@@ -89,12 +89,12 @@ class StepNavigation extends React.Component<IStepNavigationProps, any> {
</Button>
)
}
{ this.state.id !== 6 && (
{ this.state.id !== steps.length && (
<Button raised onClick={ onGotoStep(this.state.id + 1) } className={ classes.btnBlue }>
NEXT STEP
</Button>
) }
{ this.state.id === 6 && (
{ this.state.id === steps.length && (
<Button raised onClick={ onPublish } className={ classes.btnBlue }>
ACCEPT AND PUBLISH
</Button>
......
......@@ -20,30 +20,36 @@ const steps = [
},
{
id: 2,
name: 'Files',
link: 'edit/files',
active: false,
},
{
id: 3,
name: 'Dataset creator',
link: 'edit/dataset-creator',
active: false,
},
{
id: 3,
id: 4,
name: 'Timing and location',
link: 'edit/timing-and-location',
active: false,
},
{
id: 4,
id: 5,
name: 'List of accessions',
link: 'edit/list-of-accessions',
active: false,
},
{
id: 5,
id: 6,
name: 'Traits observed',
link: 'edit/traits-observed',
active: false,
},
{
id: 6,
id: 7,
name: 'Review and publish',
link: 'edit/review-and-publish',
active: false,
......
import * as React from 'react';
import {Field, FieldArray, reduxForm} from 'redux-form';
import {Field, reduxForm} from 'redux-form';
import {withStyles} from 'material-ui/styles';
import UploadSection from './upload-section';
import MaterialAutosuggest from 'ui/common/material-autosuggest';
import {DATASET_BASIC_INFO_FORM} from 'constants/datasets';
import languages from 'data/Languages';
......@@ -36,7 +35,7 @@ class BasicInfoStep extends React.Component<ILoginContainerProps, any> {
public render() {
const {classes, uuid, partners, initialValues} = this.props;
const {classes, partners, initialValues} = this.props;
return (
<form className={ classes.basicInfoForm }>
......@@ -89,13 +88,6 @@ class BasicInfoStep extends React.Component<ILoginContainerProps, any> {
label="Source"
onBlur={ this.save }
/>
<FieldArray
name="repositoryFiles"
component={ UploadSection }
dataset={ initialValues }
uuid={ uuid }
/>
</form>
);
}
......
import * as React from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {withStyles} from 'material-ui/styles';
import {FormLabel} from 'material-ui/Form';
import Button from 'material-ui/Button';
import Grid from 'material-ui/Grid';
import Divider from 'material-ui/Divider';
import List, {
ListItem,
ListItemSecondaryAction,
} from 'material-ui/List';
import IconButton from 'material-ui/IconButton';
import DeleteIcon from 'material-ui-icons/Delete';
import Snackbar from 'material-ui/Snackbar';
import {regexp} from 'data/acceptFileTypes';
import {uploadRepositoryFileRequest, deleteRepositoryFileRequest} from 'actions/dataset';
import {Dataset} from 'model/dataset.model';
import {RepositoryFile} from 'model/repositoryFile.model';
interface IUploadSectionProps extends React.ClassAttributes<any> {
classes: any;
fields: any;
uuid: any;
dataset: Dataset;
uploadRepositoryFileRequest: (dataset: Dataset, file: File) => any;
deleteRepositoryFileRequest: (dataset: Dataset, fileRepositoryUUID: string) => any;
}
const styleSheet: any = (theme) => ({
/* tslint:disable */
root: {
'& input[type="file"]': {
display: 'none',
},
width: '100%',
display: 'flex',
flexWrap: 'wrap',
margin: '20px 0 0 0',
},
fileLabel: {
width: '100%',
display: 'flex',
flexWrap: 'wrap',
position: 'relative',
},
field: {
flexBasis: '90%',
maxWidth: '90%',
[theme.breakpoints.down('lg')]: {
maxWidth: '80%',
},
[theme.breakpoints.down('md')]: {
maxWidth: '80%',
},
},
button: {
flexBasis: '10%',
maxWidth: '10%',
textAlign: 'center',
[theme.breakpoints.down('lg')]: {
flexBasis: '20%',
maxWidth: '20%',
},
},
/* tslint:enable */
placeholder: {
color: 'rgba(0, 0, 0, 0.87)',
fontWeight: 'normal',
padding: '8px 0 8px 0',
margin: '16px 0 0 0',
// width: '100%',
},
divider: {
backgroundColor: 'rgba(0, 0, 0, 0.42)',
},
uploadLabel: {
position: 'absolute',
top: 0,
left: 0,
transform: 'translate(-6px, 0px) scale(0.75)',
},
uploadField: {
padding: '0 !important',
},
});
class UploadSection extends React.Component<IUploadSectionProps, any> {
public constructor(props: any) {
super(props);
this.state = {
open: false,
};
}
protected onChange = (e) => {
const {dataset, uploadRepositoryFileRequest} = this.props;
const file = e.target.files[0];
if (regexp.test(file.name)) {
uploadRepositoryFileRequest(dataset, file);
} else {
this.setState({ open: true });
}
}
protected deleteFile = (fileUUID) => {
const {dataset, deleteRepositoryFileRequest} = this.props;
deleteRepositoryFileRequest(dataset, fileUUID);
}
protected handleRequestClose = () => {
this.setState({ open: false });
}
public render() {
const {classes, fields, dataset} = this.props;
if (!dataset) {
return null;
}
return (
<Grid container className={ classes.root }>
<Grid item xs={ 12 } className={ classes.uploadField }>
<input id="file" type="file" onChange={ this.onChange }/>
<label htmlFor="file" className={ classes.fileLabel }>
<Grid item xs={ 12 } md={ 11 } className={ classes.field }>
<FormLabel className={ classes.uploadLabel } >Upload*</FormLabel>
<FormLabel className={ classes.placeholder } >From file ...</FormLabel>
<Divider className={ classes.divider } />
</Grid>
<Grid item xs={ 12 } md={ 1 } className={ classes.button }>
<Button raised component="span" className={ classes.uploadButton }>
Upload
</Button>
</Grid>
</label>
</Grid>
<Grid item xs={ 12 }>
<div className={ classes.demo }>
<List>
{ fields.map((member, index, fields) => (
<ListItem divider button key={ index } >
<div>
{ index + 1 }
</div>
<div>{ (fields.get(index) as RepositoryFile).originalFilename }</div>
<ListItemSecondaryAction>
<IconButton aria-label="Delete">
<DeleteIcon onClick={ this.deleteFile.bind(this, fields.get(index).uuid) }/>
</IconButton>
</ListItemSecondaryAction>
</ListItem>
))
}
</List>
</div>
</Grid>
<Snackbar
anchorOrigin={ { vertical: 'bottom', horizontal: 'center' } }
open={ this.state.open }
onClose={ this.handleRequestClose }
SnackbarContentProps={ {
'aria-describedby': 'message-id',
} }
message={ <span id="message-id">Accepted file types: csv,xls,xlsx</span> }
/>
</Grid>
);
}
}
const mapDispatchToProps = (dispatch) => bindActionCreators({
uploadRepositoryFileRequest,
deleteRepositoryFileRequest,
}, dispatch);
export default connect(() => ({}), mapDispatchToProps)(withStyles(styleSheet)(UploadSection));
import * as React from 'react';
import {Field, reduxForm} from 'redux-form';
import {withStyles} from 'material-ui/styles';
import * as _ from 'lodash';
import Grid from 'material-ui/Grid';
import Button from 'material-ui/Button';
import {FILES_FORM} from 'constants/datasets';
import ItemsEditor from 'ui/common/ItemsEditor';
import {TextField} from 'ui/common/text-field';
import {Dataset} from 'model/dataset.model';
import {RepositoryFile} from 'model/repositoryFile.model';
interface IFilesFormProps extends React.ClassAttributes<any> {
initialValues: any;
fields: any;
classes: any;
dataset: Dataset;
uploadRepositoryFile: (datasetUUID: string, file: File) => any;
updateRepositoryFile: (datasetUUID: string, repositoryFile: RepositoryFile) => any;
deleteRepositoryFile: (datasetUUID: string, fileRepositoryUUID: string) => any;
}
const styleSheet = (theme) => ({
root: {
border: '20px solid #fff',
padding: '20px',
},
});
class FilesForm extends React.Component<IFilesFormProps, any> {
protected update = (fields, index) => () => {
const {updateRepositoryFile, dataset} = this.props;
const value = fields.get(index);
const repositoryFile = dataset.repositoryFiles.find((e) => e.uuid === value.uuid);
if (!_.isEqual({...repositoryFile}, {...value})) {
updateRepositoryFile(dataset.uuid, value);
}
}
protected upload = (e) => {
const {uploadRepositoryFile, dataset} = this.props;
const file = e.target.files[0];
uploadRepositoryFile(dataset.uuid, file);
}
protected onAddMember = () => {
return new RepositoryFile();
}
protected onRemoveMember = (item) => {
const {dataset, deleteRepositoryFile} = this.props;
const uuid = _.get(dataset, `${item}.uuid`);
if (uuid) {
deleteRepositoryFile(dataset.uuid, uuid);
}
}
private FilesEditor = (member, index, fields) => (
<Grid container key={ index } justify="space-between" alignItems="flex-end">
<Grid item xs={ 2 } md={ 2 } lg={ 1 }>
<Field
name={ `${member}.title` }
type="text"
component={ TextField }
label="Title"
onBlur={ this.update(fields, index) }
disabled={ !fields.get(`[${index}].uuid`) }
/>
</Grid>
<Grid item xs={ 10 } md={ 10 } lg={ 5 }>
<Field
name={ `${member}.description` }
type="text"
component={ TextField }
label="Description"
onBlur={ this.update(fields, index) }
disabled={ !fields.get(`[${index}].uuid`) }
/>
</Grid>
<Grid item xs={ 12 } lg={ 6 }>
{
fields.get(`[${index}].uuid`) ?
<Field
name={ `${member}.originalFilename` }
type="text"
component={ TextField }
disabled label="File Name"
/>
: <div>
<input
id="file"
type="file"
accept=".csv, .xls, .xlsx"
style={ { display: 'none' } }
onChange={ this.upload }
/>
<label htmlFor="file">
<Button raised component="span">
Upload file
</Button>
</label>
</div>
}
</Grid>
</Grid>
)
public render() {
const { classes } = this.props;
return (
<form className={ classes.root }>
<ItemsEditor
name="repositoryFiles"