Commit e90aa51d authored by Matija Obreza's avatar Matija Obreza
Browse files

Managing descriptors updated for un-publishing

- `<DescriptorLink` attr "descriptor" renamed to "to", children optional
- publisher field added to Vocabulary form and filter
- Allow for deleting of descriptor records
- Allow admin to un-publish a descriptor
parent baff2d20
......@@ -77,6 +77,17 @@ export const saveDescriptor = (descriptor: Descriptor) => (dispatch, getState) =
});
};
// Delete a record
export const deleteDescriptor = (descriptor: Descriptor) => (dispatch, getState) => {
return DescriptorService.deleteDescriptor(getState().login.access_token, descriptor)
.then((descriptor) => {
dispatch(push(`/descriptors`));
})
.catch((error) => {
console.log('Error', error);
});
};
export function listMyDescriptors(page?, results?, sortBy?, filter?, order?) {
return (dispatch, getState) => {
const token = getState().login.access_token;
......
......@@ -34,6 +34,7 @@ export const LIST_DESCRIPTORS_URL = `${DESCRIPTOR_API}/list`;
export const GET_DESCRIPTOR_CATEGORIES_URL = `${DESCRIPTOR_API}/categories`;
export const CREATE_DESCRIPTOR_URL = `${DESCRIPTOR_API}/create`;
export const UPDATE_DESCRIPTOR_URL = `${DESCRIPTOR_API}/update`;
export const REMOVE_DESCRIPTOR_URL = `${DESCRIPTOR_API}`;
export const PUBLISH_DESCRIPTOR_URL = `${DESCRIPTOR_API}/publish`;
// Partner API
......
......@@ -11,6 +11,7 @@ class VocabularyTerm {
class Vocabulary extends UuidModel implements IUserPermissions {
public owner: Partner;
public published: boolean;
public publisher: string;
public versionTag: string;
public title: string;
public description: string;
......@@ -41,6 +42,7 @@ class Vocabulary extends UuidModel implements IUserPermissions {
interface IVocabularyFilter extends IAuditedVersionedModelFilter {
published?: boolean;
publisher?: string[];
title?: IStringFilter;
description?: IStringFilter;
}
......
import { dereferenceReferences } from 'utilities';
import authenticatedRequest from 'utilities/requestUtils';
import { MY_DESCRIPTORS_LIST_URL, LIST_DESCRIPTORS_URL, GET_DESCRIPTOR_URL, CREATE_DESCRIPTOR_URL,
UPDATE_DESCRIPTOR_URL, PUBLISH_DESCRIPTOR_URL, GET_DESCRIPTOR_CATEGORIES_URL } from 'constants/apiURLS';
import {
MY_DESCRIPTORS_LIST_URL, LIST_DESCRIPTORS_URL, GET_DESCRIPTOR_URL, CREATE_DESCRIPTOR_URL,
UPDATE_DESCRIPTOR_URL, PUBLISH_DESCRIPTOR_URL, GET_DESCRIPTOR_CATEGORIES_URL,
REMOVE_DESCRIPTOR_URL,
} from 'constants/apiURLS';
import { Descriptor, IDescriptorFilter } from 'model/descriptor.model';
import { Partner } from 'model/partner.model';
......@@ -20,109 +23,120 @@ import { Page } from 'model/common.model';
export class DescriptorService {
// Lists my descriptors
public static listMyDescriptors(token: string, page: number = 0, results: number = 10, sortBy?: string, filter: IDescriptorFilter = {}, order?: string): Promise<Page<Descriptor>> {
return authenticatedRequest(token, {
url: `${MY_DESCRIPTORS_LIST_URL}?p=${page}&l=${results}${order ? '&d=' + order : ''}${sortBy ? '&s=' + sortBy : ''}`,
method: 'POST',
data: {
...filter,
},
})
.then(({data}) => new Page<Descriptor>(data, (dl) => new Descriptor(dl)))
.then((paged) => {
dereferenceReferences(paged.content, 'owner', (o) => new Partner(o));
return paged;
});
// Lists my descriptors
public static listMyDescriptors(token: string, page: number = 0, results: number = 10, sortBy?: string, filter: IDescriptorFilter = {}, order?: string): Promise<Page<Descriptor>> {
return authenticatedRequest(token, {
url: `${MY_DESCRIPTORS_LIST_URL}?p=${page}&l=${results}${order ? '&d=' + order : ''}${sortBy ? '&s=' + sortBy : ''}`,
method: 'POST',
data: {
...filter,
},
})
.then(({ data }) => new Page<Descriptor>(data, (dl) => new Descriptor(dl)))
.then((paged) => {
dereferenceReferences(paged.content, 'owner', (o) => new Partner(o));
return paged;
});
}
// Lists published descriptors
public static listDescriptors(token: string, page: number = 0, results: number = 10, sortBy?: string, filter: IDescriptorFilter = {}, order?: string): Promise<Page<Descriptor>> {
return authenticatedRequest(token, {
url: `${LIST_DESCRIPTORS_URL}?p=${page}&l=${results}${order ? '&d=' + order : ''}${sortBy ? '&s=' + sortBy : ''}`,
method: 'POST',
data: {
...filter,
},
})
.then(({ data }) => new Page<Descriptor>(data, (dl) => new Descriptor(dl)))
.then((paged) => {
dereferenceReferences(paged.content, 'owner', (o) => new Partner(o));
return paged;
});
}
public static listCategories(token: string): Promise<string[]> {
console.log('List descriptor categories');
return authenticatedRequest(token, {
url: `${GET_DESCRIPTOR_CATEGORIES_URL}`,
method: 'GET',
}).then(({ data }) => data);
}
public static createDescriptor(token: string, descriptor: Descriptor): Promise<Descriptor> {
console.log('Create descriptor', descriptor);
return authenticatedRequest(token, {
url: `${CREATE_DESCRIPTOR_URL}`,
method: 'POST',
data: {
...descriptor,
},
}).then(({ data }) => new Descriptor(data));
}
public static updateDescriptor(token: string, descriptor: Descriptor): Promise<Descriptor> {
console.log('Update descriptor', descriptor);
if (descriptor.owner) {
console.log('WARNING: Descriptor.owner is removed for updates');
delete descriptor.owner;
}
// Lists published descriptors
public static listDescriptors(token: string, page: number = 0, results: number = 10, sortBy?: string, filter: IDescriptorFilter = {}, order?: string): Promise<Page<Descriptor>> {
return authenticatedRequest(token, {
url: `${LIST_DESCRIPTORS_URL}?p=${page}&l=${results}${order ? '&d=' + order : ''}${sortBy ? '&s=' + sortBy : ''}`,
method: 'POST',
data: {
...filter,
},
})
.then(({data}) => new Page<Descriptor>(data, (dl) => new Descriptor(dl)))
.then((paged) => {
dereferenceReferences(paged.content, 'owner', (o) => new Partner(o));
return paged;
});
}
public static listCategories(token: string): Promise<string[]> {
console.log('List descriptor categories');
return authenticatedRequest(token, {
url: `${GET_DESCRIPTOR_CATEGORIES_URL}`,
method: 'GET',
}).then(({data}) => data);
}
public static createDescriptor(token: string, descriptor: Descriptor): Promise<Descriptor> {
console.log('Create descriptor', descriptor);
return authenticatedRequest(token, {
url: `${CREATE_DESCRIPTOR_URL}`,
method: 'POST',
data: {
...descriptor,
},
}).then(({data}) => new Descriptor(data));
}
public static updateDescriptor(token: string, descriptor: Descriptor): Promise<Descriptor> {
console.log('Update descriptor', descriptor);
if (descriptor.owner) {
console.log('WARNING: Descriptor.owner is removed for updates');
delete descriptor.owner;
}
return authenticatedRequest(token, {
url: `${UPDATE_DESCRIPTOR_URL}`,
method: 'POST',
data: {
...descriptor,
},
})
.then(({data}) => new Descriptor(data))
.then((d) => {
dereferenceReferences([ d ], 'owner', (o) => new Partner(o));
return d;
});
}
public static loadDescriptor(token: string, uuid: string): Promise<Descriptor> {
console.log('Load descriptor by UUID ', uuid);
return authenticatedRequest(token, {
url: `${GET_DESCRIPTOR_URL}/${uuid}`,
method: 'GET',
})
.then(({data}) => new Descriptor(data))
.then((descriptor) => {
dereferenceReferences([ descriptor ], 'owner', (o) => new Partner(o));
return descriptor;
});
}
public static publishDescriptor(token: string, descriptor: Descriptor, published: boolean): Promise<Descriptor> {
console.log('Publish descriptor', descriptor);
return authenticatedRequest(token, {
url: `${PUBLISH_DESCRIPTOR_URL}?uuid=${descriptor.uuid}&version=${descriptor.version}&published=${published}`,
method: 'POST',
})
.then(({data}) => new Descriptor(data))
.then((descriptor) => {
dereferenceReferences([ descriptor ], 'owner', (o) => new Partner(o));
return descriptor;
});
}
return authenticatedRequest(token, {
url: `${UPDATE_DESCRIPTOR_URL}`,
method: 'POST',
data: {
...descriptor,
},
})
.then(({ data }) => new Descriptor(data))
.then((d) => {
dereferenceReferences([d], 'owner', (o) => new Partner(o));
return d;
});
}
public static deleteDescriptor(token: string, descriptor: Descriptor): Promise<Descriptor> {
console.log('Delete descriptor', descriptor);
return authenticatedRequest(token, {
url: `${REMOVE_DESCRIPTOR_URL}/${descriptor.uuid},${descriptor.version}`,
method: 'DELETE',
})
.then(({ data }) => new Descriptor(data));
}
public static loadDescriptor(token: string, uuid: string): Promise<Descriptor> {
console.log('Load descriptor by UUID ', uuid);
return authenticatedRequest(token, {
url: `${GET_DESCRIPTOR_URL}/${uuid}`,
method: 'GET',
})
.then(({ data }) => new Descriptor(data))
.then((descriptor) => {
dereferenceReferences([descriptor], 'owner', (o) => new Partner(o));
return descriptor;
});
}
public static publishDescriptor(token: string, descriptor: Descriptor, published: boolean): Promise<Descriptor> {
console.log('Publish descriptor', descriptor);
return authenticatedRequest(token, {
url: `${PUBLISH_DESCRIPTOR_URL}?uuid=${descriptor.uuid}&version=${descriptor.version}&published=${published}`,
method: 'POST',
})
.then(({ data }) => new Descriptor(data))
.then((descriptor) => {
dereferenceReferences([descriptor], 'owner', (o) => new Partner(o));
return descriptor;
});
}
}
......@@ -171,7 +171,7 @@ const DescriptorCard = ({complete = true, compact, descriptor, classes, classNam
<Card className={ `${className}` } square>
<CardHeader
title={ (
<DescriptorLink descriptor={ descriptor }>{ descriptor.title || 'Untitled' }</DescriptorLink>
<DescriptorLink to={ descriptor }>{ descriptor.title || 'Untitled' }</DescriptorLink>
) }
/>
<Divider />
......
......@@ -39,28 +39,29 @@ function DatasetLink({
}
interface IDescriptorLinkProps extends React.Props<any> {
descriptor: Descriptor;
to: Descriptor;
edit?: boolean;
children?: any;
}
/**
* Consistently render link to descriptor
*/
function DescriptorLink({
descriptor,
to: descriptor,
edit = false,
children,
}: IDescriptorLinkProps) {
if (edit) {
return (
<Link to={ `/descriptor/${descriptor.uuid}/edit` }>
{ children }
{ children || descriptor.title }
</Link>
);
} else {
return (
<Link to={ `/descriptor/${descriptor.uuid}` }>
{ children }
{ children || descriptor.title }
</Link>
);
}
......
......@@ -8,6 +8,7 @@ interface IMarkdownTextProps extends React.HTMLProps<HTMLElement> {
source: string;
}
// TODO Add textLength attribute to trim text here, not in the parent
export default function Markdown({source, style, className}: IMarkdownTextProps) {
if (source) {
......
......@@ -64,7 +64,7 @@ export default function MyDataTable({
case 'datasets':
return <DatasetLink dataset={ row } edit={ ! row.published && row._permissions.write }>{ children }</DatasetLink>;
case 'descriptors':
return <DescriptorLink descriptor={ row } edit={ ! row.published && row._permissions.write }>{ children }</DescriptorLink>;
return <DescriptorLink to={ row } edit={ ! row.published && row._permissions.write }>{ children }</DescriptorLink>;
case 'descriptorlists':
return <DescriptorListLink to={ row } edit={ ! row.published && row._permissions.write }>{ children }</DescriptorListLink>;
default:
......
import * as React from 'react';
import { withStyles } from 'material-ui/styles';
import Grid from 'material-ui/Grid';
......@@ -385,7 +386,7 @@ class DetailInfo extends React.Component<IDetailInfoProps, any> {
<Section title="Traits observed">
{ dataset.descriptors.map((descriptor: Descriptor) => (
<div key={ descriptor.uuid } className="p-20">
<DescriptorLink descriptor={ descriptor }>{ descriptor.title }</DescriptorLink>
<DescriptorLink to={ descriptor } />
{ descriptor.description && <Markdown source={ descriptor.description }/> }
</div>
)) }
......
......@@ -2,22 +2,25 @@ import * as React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import { loadDescriptor, publishDescriptor } from 'actions/descriptors';
import { loadDescriptor, publishDescriptor, deleteDescriptor } from 'actions/descriptors';
import { Descriptor } from 'model/descriptor.model';
import { VocabularyTerm } from 'model/vocabulary.model';
import confirm from 'utilities/confirmAlert';
import Authorize from 'ui/common/authorized/Authorize';
import Loading from 'ui/common/Loading';
import Section from 'ui/common/layout/Section';
import Markdown from 'ui/common/markdown';
import ContentHeaderWithButton from 'ui/common/heading/ContentHeaderWithButton';
import { PartnerLink } from 'ui/common/Links';
import {Table, TableRow, TableCell} from 'ui/common/tables';
import { PartnerLink, DescriptorLink } from 'ui/common/Links';
import { Table, TableRow, TableCell } from 'ui/common/tables';
import VocabularyCard from 'ui/pages/vocabulary/c/VocabularyCard';
import Grid from 'material-ui/Grid';
import Card, {CardHeader, CardContent} from 'material-ui/Card';
import Card, { CardHeader, CardContent, CardActions } from 'material-ui/Card';
import Divider from 'material-ui/Divider';
import Button from 'material-ui/Button';
interface IDisplayPageProps extends React.ClassAttributes<any> {
......@@ -25,7 +28,8 @@ interface IDisplayPageProps extends React.ClassAttributes<any> {
uuid?: string;
loadDescriptor: (uuid: string) => void;
publishDescriptor: (descriptor: Descriptor) => void;
publishDescriptor: (descriptor: Descriptor, published?: boolean) => void;
deleteDescriptor: (descriptor: Descriptor) => void;
descriptor: Descriptor;
}
......@@ -43,13 +47,49 @@ class DisplayPage extends React.Component<IDisplayPageProps, any> {
}
}
public onPublish = (e) => {
private onPublish = (e) => {
const {descriptor, publishDescriptor} = this.props;
console.log('Publishing descriptor', descriptor);
publishDescriptor(descriptor);
confirm(<span>Publish <b>{ descriptor.title }</b>?</span>, {
description: `After publishing the descriptor no changes are permitted, a new version must be created.`,
confirmLabel: 'Publish',
abortLabel: 'Cancel',
}).then(() => {
console.log('Publishing descriptor', descriptor);
publishDescriptor(descriptor);
}).catch(() => {
// don't delete
});
}
private onUnpublish = (e) => {
const {descriptor, publishDescriptor} = this.props;
confirm(<span>Unpublish <b>{ descriptor.title }</b>?</span>, {
// description: `Deleting the descriptor is only possible when there is no associated data.`,
confirmLabel: 'Unpublish',
abortLabel: 'Cancel',
}).then(() => {
publishDescriptor(descriptor, false);
}).catch(() => {
// don't delete
});
}
private onDelete = (e) => {
const {descriptor, deleteDescriptor} = this.props;
confirm(<span>Delete <b>{ descriptor.title }</b>?</span>, {
description: `Deleting the descriptor is only possible when there is no associated data.`,
confirmLabel: 'Delete',
abortLabel: 'Cancel',
}).then(() => {
deleteDescriptor(descriptor);
}).catch(() => {
// don't delete
});
}
public render() {
const {uuid, descriptor} = this.props;
......@@ -76,6 +116,20 @@ class DisplayPage extends React.Component<IDisplayPageProps, any> {
<div>Record version: <b>{ descriptor.version }</b> UUID: <b>{ descriptor.uuid }</b></div>
</CardContent>
{ (descriptor._permissions.write || descriptor._permissions.delete) && (
<CardActions>
{ descriptor.published ?
<Authorize role="ROLE_ADMINISTRATOR">
<Button onClick={ this.onUnpublish } type="button">Un-publish</Button>
</Authorize>
:
<Button onClick={ this.onPublish } type="button">Publish</Button>
}
{ ! descriptor.published && descriptor._permissions.write && <DescriptorLink edit to={ descriptor }><Button>Edit</Button></DescriptorLink> }
{ ! descriptor.published && descriptor._permissions.delete && <Button onClick={ this.onDelete } type="button">Delete</Button> }
</CardActions>
) }
</Card>
</Grid>
......@@ -113,7 +167,9 @@ class DisplayPage extends React.Component<IDisplayPageProps, any> {
<Grid item xs={ 12 }>
<Section title="Controlled vocabulary">
<div className="p-20">
<p><b>Note:</b> This descriptor uses a controlled vocabulary maintained by <PartnerLink to={ descriptor.vocabulary.owner } />.</p>
<p><b>Note:</b> This descriptor uses a controlled vocabulary { descriptor.vocabulary.publisher &&
<span>published by <b>{ descriptor.vocabulary.publisher }</b></span>
} maintained by <PartnerLink to={ descriptor.vocabulary.owner } />.</p>
</div>
<VocabularyCard vocabulary={ descriptor.vocabulary } />
</Section>
......@@ -134,6 +190,7 @@ const mapStateToProps = (state, ownProps) => ({
const mapDispatchToProps = (dispatch) => bindActionCreators({
loadDescriptor,
publishDescriptor,
deleteDescriptor,
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(DisplayPage);
......@@ -139,6 +139,10 @@ class DescriptorForm extends React.Component<any, any> {
label="Descriptor title" placeholder="Color of magic"
component={ TextField }
/>
<Field name="publisher"
label="Original publisher" placeholder="IPGRI"
component={ TextField }
/>
<Field required name="description"
label="Description" placeholder="Full description of the descriptor"
component={ MarkdownField }
......@@ -168,11 +172,6 @@ class DescriptorForm extends React.Component<any, any> {
component={ TextField }
multiline
/>
<Field name="publisher"
label="Publisher" placeholder="Publisher"
component={ TextField }
/>
<Field required name="dataType"
component={ renderDataTypeRadioGroup }
/>
......
......@@ -48,7 +48,7 @@ class PartnerPage extends React.Component<IPartnerPageProps, any> {
editPartner(partner.uuid);
}
private onDeletePartner = (e) => {
private onDelete = (e) => {
const {partner, deletePartner} = this.props;
confirm(<span>Delete <b>{ partner.name }</b>?</span>, {
......@@ -108,7 +108,7 @@ class PartnerPage extends React.Component<IPartnerPageProps, any> {
{ (partner._permissions.write || partner._permissions.delete) && (
<CardActions>
{ partner._permissions.write && <Button onClick={ this.onEditPartner } type="button">Edit</Button> }
{ partner._permissions.delete && <Button onClick={ this.onDeletePartner } type="button">Delete</Button> }
{ partner._permissions.delete && <Button onClick={ this.onDelete } type="button">Delete</Button> }
</CardActions>
) }
</Card>
......
......@@ -57,6 +57,10 @@ class VocabularyForm extends React.Component<any, any> {
label="Vocabulary title" placeholder="Color of magic"
component={ TextField }
/>
<Field required name="publisher"
label="Original publisher" placeholder="IPGRI"
component={ TextField }
/>
<Field required name="description"
label="Description" placeholder="Full description of the vocabulary"
component={ MarkdownField }
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment