Commit 719846ed authored by Viacheslav Pavlov's avatar Viacheslav Pavlov Committed by Matija Obreza

Subsets: creators

parent 22e9402b
......@@ -153,6 +153,16 @@
}
}
},
"subset": {
"creator": {
"role": {
"MANAGER": "Data manager",
"DIGITIZER": "Data digitizer",
"COLLECTOR": "Data collector",
"CURATOR": "Data curator"
}
}
},
"accession": {
"pdciScore": "PDCI score of this accession is {{score, number}} of 10.0.",
"pdciInstitute": "Average PDCI score for this institute is {{score, number}}.",
......
......@@ -11,9 +11,11 @@ import Subset from 'model/Subset';
import SubsetFilter from 'model/SubsetFilter';
import {AccessionIdentifier} from 'model/dataset.model';
import {PublishState} from 'model/common.model';
import Creator from 'model/Creator';
import steps from 'ui/pages/dashboard/subsets/subset-stepper/steps';
import {RECEIVE_SUBSETS, RECEIVE_SUBSET, APPEND_SUBSETS, REMOVE_SUBSET} from 'constants/subsets';
import {RECEIVE_SUBSETS, RECEIVE_SUBSET, APPEND_SUBSETS, REMOVE_SUBSET, REMOVE_CREATOR_FROM_SUBSET, UPDATE_SUBSET_CREATOR, ADD_CREATOR_TO_SUBSET} from 'constants/subsets';
import SubsetService from 'service/genesys/SubsetService';
const receiveSubsets = (paged: FilteredPage<Subset>, error = null) => ({
type: RECEIVE_SUBSETS,
......@@ -160,3 +162,51 @@ const gotoNextStep = (subset: Subset) => {
export const remoteSubmit = (values: Subset, dispatch) => {
dispatch(saveSubset(values));
};
// Creators
const addCreatorToSubset = (creator: Creator) => ({
type: ADD_CREATOR_TO_SUBSET,
payload: { creator },
});
export const createSubsetCreator = () => (dispatch, getState) => {
const currentSubset = getState().subsets.subset;
return SubsetService.createCreator(getState().login.access_token, currentSubset.uuid)
.then((obj) => {
dispatch(addCreatorToSubset(obj));
}).catch((error) => {
console.log('Create creator error', error);
});
};
const updateCreator = (creator: Creator) => ({
type: UPDATE_SUBSET_CREATOR,
payload : { creator },
});
export const updateSubsetCreatorRequest = (creator: Creator) => (dispatch, getState) => {
const currentSubset = getState().subsets.subset;
const token = getState().login.access_token;
return SubsetService.updateCreator(token, currentSubset.uuid, creator)
.then((obj) => {
dispatch(updateCreator(obj));
}).catch((error) => {
console.log('Update creator error', error);
});
};
const removeCreator = (creator: Creator) => ({
type: REMOVE_CREATOR_FROM_SUBSET,
payload : { creator },
});
export const deleteSubsetCreatorRequest = (creator: Creator) => (dispatch, getState) => {
const token = getState().login.access_token;
const currentSubset = getState().subsets.subset;
return SubsetService.deleteCreator(token, currentSubset.uuid, creator)
.then((obj) => {
dispatch(removeCreator(obj));
}).catch((error) => {
console.log('Delete creator error', error);
});
};
......@@ -5,3 +5,8 @@ export const REMOVE_SUBSET = 'subsets/REMOVE_SUBSET';
export const SUBSET_FILTERFORM = 'Form/Subset/SUBSET_FILTERFORM';
export const SUBSET_FORM = 'Form/Subset/SUBSET_FORM';
export const SUBSET_CREATOR_FORM = 'Form/Subset/SUBSET_CREATOR_FORM';
export const ADD_CREATOR_TO_SUBSET = 'Form/Subset/ADD_CREATOR_TO_SUBSET';
export const UPDATE_SUBSET_CREATOR = 'Form/Subset/UPDATE_SUBSET_CREATOR';
export const REMOVE_CREATOR_FROM_SUBSET = 'Form/Subset/REMOVE_CREATOR_FROM_SUBSET';
import { UuidModel } from 'model/common.model';
class Creator extends UuidModel {
public fullName: string;
public email: string;
public phoneNumber: number;
public fax: number;
public institutionalAffiliation: string;
public instituteAddress: string;
public uuid: string;
public role: CreatorRole;
public constructor(obj?) {
super(obj);
}
public getClassname(): string {
return 'org.genesys.catalog.model.Creator';
}
}
enum CreatorRole {
MANAGER = 'MANAGER',
COLLECTOR = 'COLLECTOR',
DIGITIZER = 'DIGITIZER',
CURATOR = 'CURATOR',
}
export { Creator as default, CreatorRole };
......@@ -3,11 +3,13 @@
* Defined in OpenAPI as '#/definitions/Subset'
*/
import {PublishState} from 'model/common.model';
import Creator from 'model/Creator';
class Subset {
public accessionCount: number;
public accessionIds: string[];
public active: boolean;
public creators: Creator[];
public createdBy: number;
public createdDate: Date;
public dateCreated: string;
......
import update from 'immutability-helper';
import { IReducerAction } from 'model/common.model';
import { RECEIVE_SUBSETS, RECEIVE_SUBSET, REMOVE_SUBSET, APPEND_SUBSETS } from 'constants/subsets';
import {RECEIVE_SUBSETS, RECEIVE_SUBSET, REMOVE_SUBSET, APPEND_SUBSETS, ADD_CREATOR_TO_SUBSET, REMOVE_CREATOR_FROM_SUBSET, UPDATE_SUBSET_CREATOR} from 'constants/subsets';
import FilteredPage from 'model/FilteredPage';
import Subset from 'model/Subset';
......@@ -82,6 +82,36 @@ function subsets(state = INITIAL_STATE, action: IReducerAction) {
},
});
}
case ADD_CREATOR_TO_SUBSET: {
if (state.subset) {
return update(state, {
subset: { creators: { $push: [ action.payload.creator ] } },
});
} else {
return state;
}
}
case REMOVE_CREATOR_FROM_SUBSET: {
if (state.subset) {
return update(state, {
subset: { creators: { $apply: (creators) => creators.filter((creator) => creator.uuid !== action.payload.creator.uuid) } },
});
} else {
return state;
}
}
case UPDATE_SUBSET_CREATOR: {
if (state.subset) {
const index = state.subset.creators.findIndex((creator) => creator.uuid === action.payload.creator.uuid);
return update(state, {
subset: { creators: { [index]: { $set: action.payload.creator } } },
});
} else {
return state;
}
}
default:
return state;
......
......@@ -2,24 +2,32 @@
import * as UrlTemplate from 'url-template';
import * as QueryString from 'query-string';
import authenticatedRequest from 'utilities/requestUtils';
import {log} from 'utilities/debug';
import { API_ROOT } from 'constants/apiURLS';
import FilteredPage, { IPageRequest } from 'model/FilteredPage';
import Subset from 'model/Subset';
import SubsetFilter from 'model/SubsetFilter';
import {PublishState} from 'model/common.model';
const URL_ADD_ACCESSIONS = UrlTemplate.parse(`${API_ROOT}/api/v1/subset/add-accessions/{UUID},{version}`);
const URL_CREATE = `${API_ROOT}/api/v1/subset/create`;
const URL_LIST = `${API_ROOT}/api/v1/subset/list`;
const URL_LIST_MINE = `${API_ROOT}/api/v1/subset/list-mine`;
const URL_REMOVE_ACCESSIONS = UrlTemplate.parse(`${API_ROOT}/api/v1/subset/remove-accessions/{UUID},{version}`);
const URL_UPDATE = `${API_ROOT}/api/v1/subset/update`;
const URL_REJECT = `${API_ROOT}/api/v1/subset/reject`;
const URL_FOR_REVIEW = `${API_ROOT}/api/v1/subset/for-review`;
const URL_APPROVE = `${API_ROOT}/api/v1/subset/approve`;
const URL_GET = UrlTemplate.parse(`${API_ROOT}/api/v1/subset/{UUID}`);
const URL_DELETE = UrlTemplate.parse(`${API_ROOT}/api/v1/subset/{UUID},{version}`);
import Creator, {CreatorRole} from 'model/Creator';
const SUBSET_v1_API_ROOT = `${API_ROOT}/api/v1/subset`;
const URL_ADD_ACCESSIONS = UrlTemplate.parse(`${SUBSET_v1_API_ROOT}/add-accessions/{UUID},{version}`);
const URL_CREATE = `${SUBSET_v1_API_ROOT}/create`;
const URL_LIST = `${SUBSET_v1_API_ROOT}/list`;
const URL_LIST_MINE = `${SUBSET_v1_API_ROOT}/list-mine`;
const URL_REMOVE_ACCESSIONS = UrlTemplate.parse(`${SUBSET_v1_API_ROOT}/remove-accessions/{UUID},{version}`);
const URL_UPDATE = `${SUBSET_v1_API_ROOT}/update`;
const URL_REJECT = `${SUBSET_v1_API_ROOT}/reject`;
const URL_FOR_REVIEW = `${SUBSET_v1_API_ROOT}/for-review`;
const URL_APPROVE = `${SUBSET_v1_API_ROOT}/approve`;
const URL_GET = UrlTemplate.parse(`${SUBSET_v1_API_ROOT}/{UUID}`);
const URL_DELETE = UrlTemplate.parse(`${SUBSET_v1_API_ROOT}/{UUID},{version}`);
const CREATE_CREATOR_URL = UrlTemplate.parse(`${SUBSET_v1_API_ROOT}/{uuid}/subsetcreator/create`);
const DELETE_CREATOR_URL = UrlTemplate.parse(`${SUBSET_v1_API_ROOT}/{uuid}/subsetcreator/delete`);
const UPDATE_CREATOR_URL = UrlTemplate.parse(`${SUBSET_v1_API_ROOT}/{uuid}/subsetcreator/update`);
/*
* Defined in OpenAPI as 'subset'
......@@ -232,6 +240,44 @@ class SubsetService {
}).then(({ data }) => data as Subset);
}
public static createCreator(token: string, uuid: string): Promise<Creator> {
log('Create Creator');
const apiURL = CREATE_CREATOR_URL.expand({uuid});
const creator = { role: CreatorRole.CURATOR, fullName: '' };
return authenticatedRequest(token, {
url: `${apiURL}`,
method: 'POST',
data: {
...creator,
},
}).then(({data}) => new Creator(data));
}
public static deleteCreator(token: string, uuid: string, creator: Creator): Promise<Creator> {
const apiURL = DELETE_CREATOR_URL.expand({uuid});
return authenticatedRequest(token, {
url: `${apiURL}`,
method: 'POST',
data: {
...creator,
},
}).then(({data}) => new Creator(data));
}
public static updateCreator(token: string, uuid: string, creator: Creator): Promise<Creator> {
const apiURL = UPDATE_CREATOR_URL.expand({uuid});
return authenticatedRequest(token, {
url: `${apiURL}`,
method: 'POST',
data: {
...creator,
},
}).then(({data}) => new Creator(data));
}
}
export default SubsetService;
......@@ -32,7 +32,7 @@ class PropertiesCard extends React.Component<IBundledProps, any> {
<Properties>
{
propertiesList.map((property, i) => (
<PropertiesItem key={ `${i}-${property.title}` } numeric title={ property.title } { ...propertyItemProps } >
<PropertiesItem key={ `${i}-${property.title}` } numeric title={ property.title } { ...propertyItemProps }>
{ property.value && typeof property.value === 'number' ? t(`common:label.prettyNumber`, {value: property.value}) : property.value }
</PropertiesItem>
))
......
......@@ -88,7 +88,7 @@ class DashboardPage extends React.Component<IDashboardPageProps> {
}
public render() {
const {paged, pagination, login: { authorities: userRoles }} = this.props;
const {paged, pagination, login: { authorities: userRoles }, loadMySubsets} = this.props;
const renderSubset = (s: Subset, index: number) => {
return <SubsetDashboardCard key={ s.uuid } subset={ s } index={ index } isAdmin={ userRoles.findIndex((role) => role === 'ROLE_ADMINISTRATOR') !== -1 }/>;
......@@ -97,7 +97,7 @@ class DashboardPage extends React.Component<IDashboardPageProps> {
return (
<MyDataTable
onPaginationChange={ this.onPaginationChange }
promiseLoadData={ this.loadNextPage }
promiseLoadData={ loadMySubsets }
pagination={ pagination }
paged={ paged }
renderTableRow={ renderSubset }
......
......@@ -11,6 +11,11 @@ const steps = [
},
{
id: 3,
name: 'Subset creators',
link: 'edit/creators',
},
{
id: 4,
name: 'Review and publish',
link: 'edit/review-and-publish',
},
......
import * as React from 'react';
import { translate } from 'react-i18next';
import {Field, reduxForm, FieldArray} from 'redux-form';
// utilities
import {log} from 'utilities/debug';
import Validators from 'utilities/Validators';
// constants
import {SUBSET_CREATOR_FORM} from 'constants/subsets';
// models
import Subset from 'model/Subset';
import Creator, {CreatorRole} from 'model/Creator';
// ui
import {withStyles} from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import Grid from '@material-ui/core/Grid';
import IconButton from '@material-ui/core/IconButton';
import DeleteIcon from '@material-ui/icons/Delete';
import Radio from '@material-ui/core/Radio';
import RadioGroup from '@material-ui/core/RadioGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormLabel from '@material-ui/core/FormLabel';
import FormControl from 'ui/common/forms/FormControl';
import {TextField} from 'ui/common/text-field';
interface ISubsetCreatorFormProps extends React.ClassAttributes<any> {
t: any;
initialValues: any;
fields: any;
classes: any;
subset: Subset;
createSubsetCreator: () => Promise<Creator>;
deleteCreatorRequest: (creator: Creator) => Promise<Creator>;
}
const styles = (theme) => ({
/* tslint:disable */
RadioGrid: {
flexDirection: 'row' as 'row',
justifyContent: 'space-evenly' as 'space-evenly',
'& > label': {
flexBasis: '45%',
},
}
});
/* tslint:enable */
const renderRadioGroup = translate()(({input, meta, t, classes}) => {
const onInputChange = (event, value) => input.onChange(value);
return (
<FormControl fullWidth required meta={ meta }>
<FormLabel>Role:</FormLabel>
<RadioGroup
{ ...input }
value={ input.value }
onChange={ onInputChange }
className={ classes.RadioGrid }
>
{ Object.keys(CreatorRole).map((role) => (
<FormControlLabel value={ role } label={ t(`subset.creator.role.${role}`) } control={ <Radio/> } />
)) }
</RadioGroup>
</FormControl>
);
});
class SubsetCreatorForm extends React.Component<ISubsetCreatorFormProps, any> {
public createCreator = () => {
log('create creator event');
this.props.createSubsetCreator();
}
public deleteCreator = (fields, index) => () => {
this.props.deleteCreatorRequest(fields.get(index));
}
public renderCreators = ({ classes, fields, meta: { touched, error, submitFailed } }) => {
return(
<div>
{ fields.map((creator, index) => (
<div key={ index } className="m-20 p-20 even-row">
<div>
<IconButton aria-label="Delete" style={ { float: 'right' } } onClick={ this.deleteCreator(fields, index) }>
<DeleteIcon />
</IconButton>
</div>
<Field required name={ `${creator}.fullName` }
component={ TextField }
label="Full name"
placeholder="Jane A. Doe"
validate={ [ Validators.required ] }
/>
<Field required name={ `${creator}.role` }
classes={ classes }
component={ renderRadioGroup }
/>
<Field name={ `${creator}.institutionalAffiliation` }
component={ TextField }
type="text"
label="Institutional affiliation"
placeholder="Institutional affiliation"
/>
<Field name={ `${creator}.email` }
component={ TextField }
type="text" label="Email address"
placeholder="name@domain.com"
validate={ [ Validators.emailAddress ] }
/>
<Field name={ `${creator}.phoneNumber` }
component={ TextField }
type="text"
label="Phone number"
placeholder="+1 555 1231 Ext. 13"
validate={ [ Validators.phoneNumber ] }
/>
<Field name={ `${creator}.fax` }
component={ TextField }
type="text"
label="Fax"
placeholder="+1 555 1231 Ext. 42"
validate={ [ Validators.phoneNumber ] }
/>
<Field name={ `${creator}.instituteAddress` }
component={ TextField }
type="text"
label="Address"
placeholder="Address"
/>
</div>
),
) }
</div>
);
}
public render() {
return (
<Grid spacing={ 0 } container>
<form className="full-width">
<FieldArray name="creators" classes={ this.props.classes } component={ this.renderCreators } />
</form>
<Grid item xs={ 12 } className="back-white">
<Button variant="raised" type="button" onClick={ this.createCreator } style={ { margin: '20px' } }>
Add subset creator
</Button>
</Grid>
</Grid>
);
}
}
export default reduxForm({
form: SUBSET_CREATOR_FORM,
enableReinitialize: true,
})(translate()(((withStyles as any)(styles)(SubsetCreatorForm))));
import * as React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {getFormValues} from 'redux-form';
import * as _ from 'lodash';
// actions
import {createSubsetCreator, deleteSubsetCreatorRequest, updateSubsetCreatorRequest} from 'actions/subsets';
// constants
import { SUBSET_CREATOR_FORM} from 'constants/subsets';
// models
import Creator from 'model/Creator';
import Subset from 'model/Subset';
// ui
import Loading from 'ui/common/Loading';
import StepperTemplate from 'ui/common/stepper/StepperTemplate';
import SubsetCreatorForm from './SubsetCreatorForm';
interface ICreatorsStepProps extends React.ClassAttributes<any> {
subset: Subset;
createSubsetCreator: () => Promise<Location>;
updateSubsetCreatorRequest: (creator: Creator) => Promise<Creator>;
deleteSubsetCreatorRequest: (creator: Creator) => Promise<Creator>;
formValues: any;
}
class CreatorsStep extends StepperTemplate<ICreatorsStepProps> {
protected renderContent = () => {
const {item, deleteSubsetCreatorRequest} = this.props;
return !item ? <Loading /> : (
<SubsetCreatorForm
subset={ item }
initialValues={ item }
createSubsetCreator={ this.create }
deleteCreatorRequest={ deleteSubsetCreatorRequest }
/>
);
}
protected gotoStep = (id) => {
const {onGotoStep} = this.props;
if (onGotoStep) {
this.updateIfNeed();
setTimeout(() => onGotoStep(id));
}
}
protected create = () => {
this.updateIfNeed();
this.props.createSubsetCreator();
}
private updateIfNeed = () => {
const { item: subset, formValues : { creators }, updateSubsetCreatorRequest } = this.props;
if (subset) {
subset.creators.map((oldCreator) => {
const creator = creators.find((c) => c.uuid === oldCreator.uuid);
if (!_.isEqual({...creator}, {...oldCreator}) && creator.fullName) {
updateSubsetCreatorRequest({...oldCreator, ...creator});
}
});
}
}
}
const mapStateToProps = (state, ownProps) => ({
formValues: getFormValues(SUBSET_CREATOR_FORM)(state),
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
createSubsetCreator,
deleteSubsetCreatorRequest,
updateSubsetCreatorRequest,
}, dispatch);
export default connect(
mapStateToProps, mapDispatchToProps,
)(CreatorsStep);
import * as React from 'react';
import {translate} from 'react-i18next';
import {withStyles} from '@material-ui/core/styles';
import * as _ from 'lodash';
// util
import {log} from 'utilities/debug';
// models
......@@ -13,6 +12,7 @@ import Page from 'model/Page';
// ui
import SubsetCard from 'ui/genesys/subset/SubsetCard';
import AccessionCard from 'ui/genesys/accession/AccessionCard';
import PropertiesCard from 'ui/common/PropertiesCard';
import Grid from '@material-ui/core/Grid';
const styles = (theme) => ({
......@@ -67,7 +67,7 @@ class DetailInfo extends React.Component<IDetailInfoProps, any> {
}
public render() {