Commit 755acf52 authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch '331-self-managed-selectpartner' into 'master'

Resolve "Self-managed SelectPartner"

Closes #331

See merge request !249
parents 7a460efa dabcd93b
......@@ -14,7 +14,6 @@ import {log} from 'utilities/debug';
import { CREATE_DATASET, RECEIVE_DATASET, RECEIVE_DATASET_PAGE, ADD_CREATOR_TO_DATASET, REMOVE_CREATOR_FROM_DATASET, UPDATE_DATASET_CREATOR, ADD_LOCATION, RECEIVE_LOCATION, REMOVE_LOCATION } from 'constants/datasets';
import {addFilterCode} from 'actions/filterCode';
import {cleanFilters} from 'utilities';
import steps from 'ui/pages/dataset/dataset-stepper/steps';
import { navigateTo } from 'actions/navigation';
import * as _ from 'lodash';
......@@ -44,13 +43,14 @@ export const applyDatasetFilters = (filters?: any) => (dispatch) => {
dispatch(listDatasetsRequest(undefined, undefined, undefined, filters));
};
function listMyDatasets(page?, results?, sortBy?, filter?, order?) {
function listMyDatasets(page?, results?, sortBy?, filter?: string | IDatasetFilter, order?) {
return (dispatch, getState) => {
const token = getState().login.access_token;
return DatasetService.listMyDatasets(token, page, results, sortBy, filter, order)
.then((paged) => {
dispatch(receiveDatasetPage(paged, page, results, sortBy, filter, order));
dispatch(receiveDatasetPage(paged, page, results, sortBy, paged.filter, order));
return dispatch(addFilterCode(paged.filterCode, paged.filter));
})
.catch((error) => {
log('Error', error);
......@@ -89,8 +89,7 @@ function listDatasetsByCodeRequest(page?, results?, sortBy?, filterCode?, order?
return DatasetService.listDatasetsByCode(token, page, results, sortBy, filterCode, order)
.then((paged) => {
const filter = cleanFilters(paged.filter, ['published']);
dispatch(receiveDatasetPage(paged, page, results, sortBy, filter as IDatasetFilter, order));
dispatch(receiveDatasetPage(paged, page, results, sortBy, paged.filter, order));
return dispatch(addFilterCode(paged.filterCode, paged.filter));
})
.catch((error) => {
......
......@@ -54,7 +54,7 @@ export const loadDescriptorList = (uuid: string, success?: (d) => any, fail?: (e
};
// List current user's descriptor lists
export const listMyDescriptorLists = (page?, results?, sortBy?, filter?, order?) => (dispatch, getState) => {
export const listMyDescriptorLists = (page?, results?, sortBy?, filter?: string | IDescriptorListFilter, order?) => (dispatch, getState) => {
log('Loading my descriptor lists');
const token = getState().login.access_token;
......@@ -63,7 +63,8 @@ export const listMyDescriptorLists = (page?, results?, sortBy?, filter?, order?)
return DescriptorListService.listMyDescriptorLists(token, page, results, sortBy, filter, order)
// receive the current descriptor list
.then((descriptorLists) => {
return dispatch(receiveDescriptorLists(descriptorLists, page, results, sortBy, filter, order));
dispatch(receiveDescriptorLists(descriptorLists, page, results, sortBy, descriptorLists.filter, order));
return dispatch(addFilterCode(descriptorLists.filterCode, descriptorLists.filter));
}).catch((error) => {
log(`Error loading my descriptor lists`, error);
});
......
......@@ -125,7 +125,7 @@ export const deleteDescriptor = (descriptor: Descriptor) => (dispatch, getState)
});
};
export function listMyDescriptors(page?, results?, sortBy?, filter?, order?) {
export function listMyDescriptors(page?, results?, sortBy?, filter?: string | IDescriptorFilter, order?) {
return (dispatch, getState) => {
const token = getState().login.access_token;
......@@ -134,7 +134,8 @@ export function listMyDescriptors(page?, results?, sortBy?, filter?, order?) {
return DescriptorService.listMyDescriptors(token, page, results, sortBy, filter, order)
.then((paged) => {
dereferenceReferences(paged.content, 'owner', (o) => new Partner(o));
return dispatch(receiveDescriptorPage(paged, page, results, sortBy, filter, order));
dispatch(receiveDescriptorPage(paged, page, results, sortBy, paged.filter, order));
return dispatch(addFilterCode(paged.filterCode, paged.filter));
})
.catch((error) => {
log('Error', error);
......
import * as update from 'immutability-helper';
import { IReducerAction } from 'model/common.model';
import { Partner } from 'model/partner.model';
import {log} from 'utilities/debug';
import {
CREATE_PARTNER, GET_PARTNER, RECEIVE_PARTNER, LIST_PARTNERS, RECEIVE_PARTNERS,
......@@ -48,7 +47,7 @@ export default function partner(state = INITIAL_STATE, action: IReducerAction =
// set myPartners
case RECEIVE_MY_PARTNERLIST: {
log('My partners', action.payload);
// log('My partners', action.payload);
return update(state, {
myPartners: { $set: action.payload },
});
......@@ -56,7 +55,7 @@ export default function partner(state = INITIAL_STATE, action: IReducerAction =
// set the paged to whatever came in
case RECEIVE_PARTNERS: {
log('Marking loaded pagedQuery', action.payload);
// log('Marking loaded pagedQuery', action.payload);
return update(state, {
loading: { $set: null },
paged: { $set: action.payload.paged },
......
import authenticatedRequest from 'utilities/requestUtils';
import { dereferenceReferences } from 'utilities';
import {log} from 'utilities/debug';
import { stringify } from 'query-string';
import { Page } from 'model/common.model';
import { Dataset, IDatasetFilter, AccessionIdentifier } from 'model/dataset.model';
......@@ -14,12 +15,19 @@ import {
export class DatasetService {
// List my datasets
public static listMyDatasets(token: string, page: number = 0, results: number = 10, sortBy: string = 'title', filter: IDatasetFilter = {}, order: string = 'ASC'): Promise<Page<Dataset>> {
public static listMyDatasets(token: string, page: number = 0, results: number = 10, sortBy: string = 'title', filter: string | IDatasetFilter = {}, order: string = 'ASC'): Promise<Page<Dataset>> {
const query = stringify({
p: page,
l: results,
d: order,
s: sortBy,
f: typeof filter === 'string' ? filter : null,
});
return authenticatedRequest(token, {
url: `${MY_DATASETS_LIST_URL}?p=${page}&l=${results}${order ? '&d=' + order : ''}${sortBy ? '&s=' + sortBy : ''}`,
url: `${MY_DATASETS_LIST_URL}?${query}`,
method: 'POST',
data: filter,
headers: {'Content-Type': 'application/json'},
data: typeof filter === 'string' ? null : filter,
})
.then(({ data }) => new Page<Dataset>(data, (d) => new Dataset(d)))
.then((paged) => {
......@@ -46,6 +54,7 @@ export class DatasetService {
}
// List published datasets by filter code
// TODO Remove
public static listDatasetsByCode(token: string, page: number = 0, results: number = 10, sortBy: string = 'title', filterCode: string, order: string = 'ASC'): Promise<Page<Dataset>> {
const code = filterCode ? `/${filterCode}` : '';
......
import authenticatedRequest from 'utilities/requestUtils';
import { dereferenceReferences } from 'utilities';
import {log} from 'utilities/debug';
import { stringify } from 'query-string';
import {
MY_LIST_DESCRIPTORSLISTS_URL, GET_DESCRIPTORLIST_URL, REMOVE_DESCRIPTORLIST_URL,
......@@ -16,16 +17,22 @@ import { Page } from 'model/common.model';
export class DescriptorListService {
// Lists my descriptor lists
public static listMyDescriptorLists(token: string, page: number = 0, results: number = 10, sortBy: string = 'lastModifiedDate', filter: IDescriptorListFilter = {}, order: string = 'ASC'): Promise<Page<DescriptorList>> {
const sortParam = sortBy ? `&s=${sortBy}` : '';
public static listMyDescriptorLists(token: string, page: number = 0, results: number = 10, sortBy: string = 'lastModifiedDate', filter: string | IDescriptorListFilter = {}, order: string = 'ASC'): Promise<Page<DescriptorList>> {
const query = stringify({
p: page,
l: results,
d: order,
s: sortBy,
f: typeof filter === 'string' ? filter : null,
});
// console.log('Query', query);
return authenticatedRequest(token, {
url: `${MY_LIST_DESCRIPTORSLISTS_URL}?p=${page}&l=${results}&d=${order}${sortParam}`,
url: `${MY_LIST_DESCRIPTORSLISTS_URL}?${query}`,
method: 'POST',
data: {
...filter,
},
headers: {'Content-Type': 'application/json'},
data: typeof filter === 'string' ? null : filter,
})
.then(({ data }) => new Page<DescriptorList>(data, (dl) => new DescriptorList(dl)))
.then((paged) => {
......@@ -54,6 +61,7 @@ export class DescriptorListService {
}
// Lists published descriptor lists by filter code
// TODO Remove
public static listDescriptorListsByCode(token: string, page: number = 0, results: number = 10, sortBy: string = 'title', filterCode: string, order: string = 'ASC'): Promise<Page<DescriptorList>> {
const sortParam = sortBy ? `&s=${sortBy}` : '';
const code = filterCode ? `/${filterCode}` : '';
......
import { dereferenceReferences } from 'utilities';
import authenticatedRequest from 'utilities/requestUtils';
import { log } from 'utilities/debug';
import { stringify } from 'query-string';
import {
MY_DESCRIPTORS_LIST_URL, LIST_DESCRIPTORS_URL, GET_DESCRIPTOR_URL, CREATE_DESCRIPTOR_URL, COPY_DESCRIPTOR_URL,
......@@ -25,12 +26,21 @@ 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>> {
public static listMyDescriptors(token: string, page: number = 0, results: number = 10, sortBy?: string, filter: string | IDescriptorFilter = {}, order?: string): Promise<Page<Descriptor>> {
const query = stringify({
p: page,
l: results,
d: order,
s: sortBy,
f: typeof filter === 'string' ? filter : null,
});
// console.log('Query', query);
return authenticatedRequest(token, {
url: `${MY_DESCRIPTORS_LIST_URL}?p=${page}&l=${results}${order ? '&d=' + order : ''}${sortBy ? '&s=' + sortBy : ''}`,
url: `${MY_DESCRIPTORS_LIST_URL}?${query}`,
method: 'POST',
data: {
headers: {'Content-Type': 'application/json'},
data: typeof filter === 'string' ? null : {
...filter,
},
})
......@@ -59,6 +69,7 @@ export class DescriptorService {
}
// Lists published descriptors by filter code
// TODO Remove
public static listDescriptorsByCode(token: string, page: number = 0, results: number = 50, sortBy?: string, filterCode?: string, order?: string): Promise<Page<Descriptor>> {
const code = filterCode ? `/${filterCode}` : '';
......
import * as React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {log} from 'utilities/debug';
import {logoutRequest, loginAppRequest} from 'actions/login';
import {setAppMounted} from 'actions/appMounted';
......@@ -52,7 +51,6 @@ class App extends React.Component<IAppProps, any> {
public render() {
const { login, logoutRequest, loginAppRequest, route: { routes } } = this.props;
log('Rendering App view');
return (
<div>
<Header login={ login } logoutRequest={ logoutRequest } loginAppRequest={ loginAppRequest } />
......
......@@ -11,6 +11,7 @@ import CropFilter from 'ui/catalog/crop/CropFilter';
import {DESCRIPTOR_FILTER_FORM} from 'constants/filter';
import {Descriptor} from 'model/descriptor.model';
import DescriptorListPicker from './DescriptorListPicker';
import PartnerFilter from 'ui/catalog/partner/PartnerFilter';
// <CollapsibleComponentSearch title="Descriptions">
// <StringFilter name="title" searchType="contains" label="Descriptor title" placeholder="Title"/>
......@@ -25,6 +26,7 @@ import DescriptorListPicker from './DescriptorListPicker';
const DescriptorFilters = ({handleSubmit, initialize, ...other}) => (
<FiltersBlock title="Descriptors" handleSubmit={ handleSubmit } initialize={ initialize } { ...other }>
<TextFilter name="_text" label="Keyword search" placeholder="mardi rice" className="p-20" />
<PartnerFilter name="owner" label="Partner" className="p-20" />
<DescriptorListPicker name="list" label="Select descriptor list" className="p-20" />
<CollapsibleComponentSearch title="Crop">
<CropFilter />
......
import * as React from 'react';
import { Field } from 'redux-form';
import SelectPartner from './SelectPartner';
interface IPartnerFilter extends React.ClassAttributes<any> {
name: string;
label: string;
className?: string;
}
class PartnerFilter extends React.Component<IPartnerFilter, any> {
public render() {
const { name , label, className, ...other } = this.props;
return (
<div className={ className }>
<Field
name={ `${name}.uuid` }
component={ SelectPartner }
label={ label }
useUuid
allowNull
multiple
{ ...other }
/>
</div>
);
}
}
export default PartnerFilter;
import * as React from 'react';
import {withStyles} from 'material-ui/styles';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {log} from 'utilities/debug';
import {Partner} from 'model/partner.model';
import {loadPartners, loadMyPartners} from 'actions/partner';
import Input from 'material-ui/Input';
import FormControl from 'ui/common/forms/FormControl';
......@@ -22,43 +25,77 @@ const styles = (theme) => ({
interface ISelectPartnerProps extends React.ClassAttributes<any> {
classes: any;
input: any;
partners: Partner[];
label: string;
editable?: boolean;
required?: boolean;
meta?: any;
loadPartners: any;
loadMyPartners: any;
partners: Partner[];
onlyMine?: boolean; // list only my partners
useUuid?: boolean; // toggle this if input contains the UUID instead of Partner object
allowNull?: boolean; // add blank option (null) to selection
multiple?: boolean; // allow multiple
}
class SelectPartner extends React.Component<ISelectPartnerProps, any> {
public constructor(props: any) {
super(props);
}
public componentWillMount() {
const { onlyMine, loadPartners, loadMyPartners, partners } = this.props;
// console.log(`SelectPartner`, onlyMine, partners);
if (! partners || partners.length === 0) {
onlyMine ? loadMyPartners() : loadPartners();
}
}
public handleChange = ({target: {value}}) => {
const { editable, input, partners } = this.props;
log(`Partner selection editable=${editable}`, value);
const p = partners.find((p) => p.uuid === value);
if (editable === undefined || editable) {
input.onChange(p);
const { partners, editable, input, useUuid, multiple } = this.props;
value = value === '' ? null : value;
log(`Partner selection editable=${editable} value=${value}`, value);
if (useUuid) {
if (multiple) {
input.onChange(value ? [ value ] : null);
} else {
input.onChange(value);
}
} else {
const p = partners.find((p) => p.uuid === value);
if (editable === undefined || editable) {
if (multiple) {
input.onChange(p ? [ p ] : null);
} else {
input.onChange(p);
}
}
}
}
public render() {
const {classes, input, partners, label, editable, required, meta } = this.props;
const { partners, classes, input, label, editable, required, meta, useUuid, allowNull, multiple } = this.props;
const allowsEdit = editable === undefined || editable;
const val = input && input.value ?
(multiple ? input.value[0] : input.value) : null;
if (! allowsEdit) {
return (
<FormControl fullWidth label={ label }>
<Input value={ input.value && input.value.name } className={ classes.input } disabled />
<Input value={ val && val.name } className={ classes.input } disabled />
</FormControl>
);
}
// console.log(`Partner req=${required}`, meta);
// <MenuItem value="" />
return (
<FormControl fullWidth required={ required } meta={ meta } label={ label }>
<Select error={ meta.touched && meta.error } className={ classes.select } value={ input.value && input.value.uuid } onChange={ this.handleChange }
<Select error={ meta.touched && meta.error } className={ classes.select } value={ useUuid ? val || '' : val && val.uuid || '' } onChange={ this.handleChange }
input={ <Input /> }
>
{ allowNull && <MenuItem key="noPartner" value=""><em>Select partner</em></MenuItem> }
{ partners && partners.map((p) => <MenuItem key={ p.uuid } value={ p.uuid }>{ p.name }</MenuItem>) }
</Select>
</FormControl>
......@@ -66,4 +103,16 @@ class SelectPartner extends React.Component<ISelectPartnerProps, any> {
}
}
export default withStyles(styles)(SelectPartner);
const mapStateToProps = (state, ownProps) => ({
partners: ownProps.onlyMine ?
state.partner.myPartners || null
: state.partner.paged && state.partner.paged.content || null,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
loadPartners,
loadMyPartners,
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(SelectPartner));
......@@ -31,7 +31,7 @@ export default class PagedLoader<T> extends React.Component<IProps<T>, any> {
}
public componentWillReceiveProps(nextProps) {
log('nextProps', nextProps);
// log('nextProps', nextProps);
const { paged: { number: pageNumber, content: newContent } } = nextProps;
if (newContent) {
......@@ -81,12 +81,12 @@ export default class PagedLoader<T> extends React.Component<IProps<T>, any> {
if (! list || list.length === 0) {
return null;
}
log(`Rendering ${list.length} items`);
// log(`Rendering ${list.length} items`);
const inTable = colSpan ? true : false;
const visibilityOffset = (roughItemHeight && roughItemHeight || 50) * 10;
const myLoadingIndicator = loadingIndicator || <Loading />;
log(`Visibility offset bottom: ${-visibilityOffset}`);
// log(`Visibility offset bottom: ${-visibilityOffset}`);
const result = [
...list.map((item: T, index) => itemRenderer(item, index)),
......@@ -102,7 +102,7 @@ export default class PagedLoader<T> extends React.Component<IProps<T>, any> {
</div>
),
];
log('Done rendering');
// log('Done rendering');
return result;
}
}
......@@ -34,9 +34,10 @@ const FiltersBlock = ({ title, children, handleSubmit, onSubmit, initialize, cla
const processSubmit = handleSubmit((data) => {
showSnackbar('Applying filters...');
console.log('Submitting', data);
const clean = cleanFilters(data);
log('Submitting filters', clean);
scrollToTop();
return onSubmit(cleanFilters(data));
return onSubmit(clean);
});
return (
......
......@@ -14,7 +14,7 @@ interface IProps {
}
const styles = (theme) => {
console.log(theme);
// console.log(theme);
return ({
helper: {
paddingTop: '0.5rem',
......
......@@ -18,14 +18,14 @@ class MarkdownField extends React.Component<any, any> {
}
public render() {
const {basicMarkdown, input, label, required, meta, meta: {touched, error}, ...custom} = this.props;
const {basicMarkdown, input, label, required, meta, meta: {touched, error}} = this.props;
const basic: boolean = basicMarkdown === undefined || null ? false : basicMarkdown;
return (
<div>
{ (basic || !this.state.previewMode) ?
<FormControl fullWidth required={ required } meta={ meta } label={ label }>
<Input error={ touched && error } multiline={ !basic } { ...input } { ...custom } />
<Input error={ touched && error } multiline={ !basic } { ...input } />
<h6>
{ ! basic && <a onClick={ this.onChangePreviewMode }>Preview Markdown</a> }
<span> { basic ? 'Basic markdown supported: * **' : 'Full markdown supported' }</span>
......
......@@ -22,13 +22,13 @@ class Snackbar extends React.Component<ISnacks, any> {
public componentWillMount() {
const { snack } = this.props;
console.log('Snack willMount', snack);
// console.log('Snack willMount', snack);
this.pushSnack(snack);
}
public componentWillReceiveProps(nextProps) {
const { snack } = nextProps;
console.log('Snack willReceive', snack);
// console.log('Snack willReceive', snack);
this.pushSnack(snack);
}
......
......@@ -9,12 +9,14 @@ import StringFilter from 'ui/common/filter/StringFilter';
import StringArrFilter from 'ui/common/filter/StringArrFilter';
import TextFilter from 'ui/common/filter/TextFilter';
import CropFilter from 'ui/catalog/crop/CropFilter';
import PartnerFilter from 'ui/catalog/partner/PartnerFilter';
// <StringArrFilter name="language" label="Language" placeholder="Language"/>
const DatasetFilters = ({handleSubmit, initialize, ...other}) => (
<FiltersBlock title="Datasets" handleSubmit={ handleSubmit } initialize={ initialize } { ...other }>
<TextFilter name="_text" label="Keyword search" placeholder="mardi rice" className="p-20" />
<PartnerFilter name="owner" label="Partner" className="p-20" />
<CollapsibleComponentSearch title="Crop">
<CropFilter />
</CollapsibleComponentSearch>
......
......@@ -3,7 +3,6 @@ import {Field, reduxForm, FieldArray} from 'redux-form';
import {DATASET_BASIC_INFO_FORM} from 'constants/datasets';
import languages from 'data/Languages';
import {Partner} from 'model/partner.model';
import {TextField} from 'ui/common/text-field';
import {MarkdownField} from 'ui/catalog/markdown';
......@@ -21,23 +20,22 @@ interface ILoginContainerProps extends React.ClassAttributes<any> {
initialValues: any;
classes: any;
uuid: string;
partners: Partner[];
}
class BasicInfoStep extends React.Component<ILoginContainerProps, any> {
public render() {
const {partners, initialValues, handleSubmit} = this.props;
const {initialValues, handleSubmit} = this.props;
return (
<form onSubmit={ handleSubmit } className="p-20 m-20 even-row">
<Field required
name="owner"
component={ SelectPartner }
onlyMine
label="Select Partner"
placeholder="Partner"
partners={ partners }
editable={ ! (initialValues.uuid && initialValues.version) }
validate={ [ Validators.required ] }
/>
......
......@@ -67,7 +67,6 @@ class BasicInfoStep extends React.Component<IDatasetProps, any> {
}
const mapStateToProps = (state, ownProps) => ({
myPartners: state.partner.myPartners,
dataset: state.datasets.currentDataset,
stillLoading: ownProps.stillLoading,
location: ownProps.location,
......
......@@ -5,10 +5,8 @@ import {bindActionCreators} from 'redux';
import {log} from 'utilities/debug';
import {loadDescriptor, saveDescriptor, publishDescriptor} from 'actions/descriptors';
import {loadMyPartners} from 'actions/partner';
import { Descriptor } from 'model/descriptor.model';
import { Partner } from 'model/partner.model';
import DescriptorForm from './c/DescriptorForm';
import VocabularyCard from 'ui/pages/vocabulary/c/VocabularyCard';
......@@ -20,9 +18,6 @@ interface IDescriptorEditPageProps extends React.ClassAttributes<any> {
classes: any;
uuid?: string;
loadMyPartners: () => void;