Commit 0d2c7117 authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch '328-filtering-in-mydatapage' into 'master'

Resolve "Filtering in MyDataPage"

Closes #328

See merge request !248
parents 755acf52 200e45e4
......@@ -9,14 +9,30 @@ const addFilter = (code: string, filterJson: string) => ({
});
export const addFilterCode = (code: string, receivedFilter: object) => (dispatch, getState) => {
// console.log('Received filter code', code, receivedFilter);
if (!code) {
return;
code = '';
}
const receivedFilterJson = cleanFilters(receivedFilter, ['published']);
const receivedFilterJson = cleanFilters(receivedFilter, []);
// console.log('Adding filter code', code, receivedFilterJson);
dispatch(addFilter(code, receivedFilterJson));
};
export const filterCodeToUrl = (code: string) => (dispatch) => {
const qs = window && QS.parse(window.location.search) || {};
qs.filter = code;
dispatch(navigateTo('', qs));
// console.log(`Should we update filter code ${code}`, qs);
code = !code ? '' : code;
if ((! qs.filter && ! code) || qs.filter === code) {
// noop
// console.log(`Not updating URL code`, qs);
} else {
if (! code) {
delete qs.filter;
} else {
qs.filter = code;
}
// console.log(`Updating URL code ${code}`, qs);
dispatch(navigateTo('', qs));
}
};
export const DASHBOARD_FILTERFORM = 'Form/DASHBOARD_FILTERFORM';
import * as update from 'immutability-helper';
import {ADD_FILTER_CODE} from 'constants/filterCode';
import * as _ from 'lodash';
const INITIAL_STATE: {
filters: Map<string, object>;
filters: object,
} = {
filters: new Map<string, object>(),
filters: {},
};
export default function filterCode(state = INITIAL_STATE, action: { type: string, payload?: any } = { type: '' }) {
......@@ -14,10 +13,8 @@ export default function filterCode(state = INITIAL_STATE, action: { type: string
// Add filter
case ADD_FILTER_CODE: {
const map = _.isEmpty(state.filters) ? new Map<string, object>() : new Map<string, object>(state.filters);
map.set(action.payload.code, action.payload.filterJson);
return update(state, {
filters: { $set: map },
filters: { [action.payload.code]: {$set: action.payload.filterJson} },
});
}
......
......@@ -4,6 +4,7 @@ import Grid from 'material-ui/Grid';
import { bindActionCreators } from 'redux';
import { parse } from 'query-string';
import { filterCodeToUrl } from 'actions/filterCode';
import { createPartner } from 'actions/partner';
import { createDataset, listMyDatasets } from 'actions/dataset';
import { createDescriptor, listMyDescriptors } from 'actions/descriptors';
......@@ -56,29 +57,11 @@ class AdministrationDashboard extends BaseMyDataPage<IAdminDashProps> {
this.props.history.push('/dashboard/data-published');
}
protected onPaginationChange = (page, results, sortBy, dir) => {
const { history, location } = this.props;
const params = new URLSearchParams(location.search);
params.set('p', page);
params.set('l', results);
if (sortBy) {
params.set('s', sortBy);
} else {
params.delete('s');
}
if (dir) {
params.set('d', dir);
} else {
params.delete('d');
}
location.search = params.toString();
history.push(location);
}
public render() {
const {tab, datasets, descriptors, descriptorLists, createPartner, createDescriptor, createDescriptorList} = this.props;
const {tab, datasets, descriptors, descriptorLists, createPartner, createDescriptor, createDescriptorList, filter, filterCode} = this.props;
// console.log('Dash', tab, filterCode, filter);
let paged: Page<any> = datasets as Page<Dataset>;
......@@ -125,7 +108,7 @@ class AdministrationDashboard extends BaseMyDataPage<IAdminDashProps> {
</Grid>
</div>
<MyDataTable tab={ tab } basePath="/dashboard"
<MyDataTable tab={ tab } basePath="/dashboard" filterCode={ filterCode } filter={ filter } onFilter={ this.onFilter }
paged={ paged } onPaginationChange={ this.onPaginationChange }/>
</div>
);
......@@ -139,6 +122,8 @@ const mapStateToProps = (state, ownProps) => ({
pageSize: +parse(ownProps.location.search).l || 20, // page size
pageSort: parse(ownProps.location.search).s || 'lastModifiedDate', // page sort
pageDir: parse(ownProps.location.search).d || 'DESC', // page sort direction
filterCode: parse(ownProps.location.search).filter, // filter code
filter: state.filterCode.filters && parse(ownProps.location.search).filter && state.filterCode.filters[parse(ownProps.location.search).filter] || null,
datasets: state.datasets.paged,
descriptors: state.descriptors.paged,
descriptorLists: state.descriptorList.paged,
......@@ -153,6 +138,7 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({
listDescriptors: listMyDescriptors,
listDescriptorLists: listMyDescriptorLists,
setPageTitle,
filterCodeToUrl,
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(AdministrationDashboard);
......@@ -8,6 +8,7 @@ import {Descriptor, DescriptorList} from 'model/descriptor.model';
import {Page} from 'model/common.model';
import { parse } from 'query-string';
import { filterCodeToUrl } from 'actions/filterCode';
import {listMyDatasets} from 'actions/dataset';
import {listMyDescriptors} from 'actions/descriptors';
import {listMyDescriptorLists} from 'actions/descriptorList';
......@@ -24,11 +25,14 @@ interface IDataPublishedContainerProps extends React.ClassAttributes<any> {
pageSize: number;
pageSort?: string;
pageDir?: string;
filterCode?: string;
filter: any;
datasets: Page<Dataset>;
descriptors: Page<Descriptor>;
descriptorLists: Page<DescriptorList>;
preFilter?: object;
basePath: string;
filterCodeToUrl: any;
listDescriptorLists: any;
listDescriptors: any;
listDatasets: any;
......@@ -44,18 +48,19 @@ class BaseMyDataPage<T> extends React.Component<T & IDataPublishedContainerProps
const pageSize = parse(search).l;
const pageSort = parse(search).s;
const pageDir = parse(search).d;
const filterCode = parse(search).filter;
switch (tab) {
case 'descriptors': return listMyDescriptors(pageCurrent, pageSize, pageSort, {}, pageDir);
case 'descriptorlists': return listMyDescriptorLists(pageCurrent, pageSize, pageSort, {}, pageDir);
case 'descriptors': return listMyDescriptors(pageCurrent, pageSize, pageSort, filterCode || {}, pageDir);
case 'descriptorlists': return listMyDescriptorLists(pageCurrent, pageSize, pageSort, filterCode || {}, pageDir);
case 'datasets':
default: return listMyDatasets(pageCurrent, pageSize, pageSort, {}, pageDir);
default: return listMyDatasets(pageCurrent, pageSize, pageSort, filterCode || {}, pageDir);
}
},
];
constructor(props) {
super(props);
this.state = {tab: ''};
this.state = {tab: '', filterCode: props.filterCode};
}
public componentDidMount() {
......@@ -64,8 +69,9 @@ class BaseMyDataPage<T> extends React.Component<T & IDataPublishedContainerProps
public componentWillMount() {
const {tab, pageCurrent, pageSize, pageSort, pageDir} = this.props;
// console.log(`willM ${filterCode}`, filters[filterCode]);
this.loadData(tab, pageCurrent, pageSize, pageSort, pageDir);
this.loadData(null, tab, pageCurrent, pageSize, pageSort, pageDir);
}
public componentWillUnmount() {
......@@ -73,9 +79,18 @@ class BaseMyDataPage<T> extends React.Component<T & IDataPublishedContainerProps
}
public componentWillReceiveProps(nextProps) {
const {tab, pageCurrent, pageSize, pageSort, pageDir} = nextProps;
const {filterCodeToUrl} = this.props;
const paged = this.getPaged(nextProps);
this.loadData(tab, pageCurrent, pageSize, pageSort, pageDir);
if (paged) {
filterCodeToUrl(paged.filterCode);
}
const {tab} = nextProps;
if (tab !== this.state.tab) {
const {pageCurrent, pageSize, pageSort, pageDir} = nextProps;
this.loadData(null, tab, pageCurrent, pageSize, pageSort, pageDir);
}
}
public componentWillUpdate(nextProps, nextState) {
......@@ -86,6 +101,15 @@ class BaseMyDataPage<T> extends React.Component<T & IDataPublishedContainerProps
// noop
}
protected getPaged(props) {
switch (props.tab) {
case 'descriptorlists': return props.descriptorLists;
case 'descriptors': return props.descriptors;
case 'datasets':
default: return props.datasets;
}
}
protected onPaginationChange = (page, results, sortBy, dir) => {
const { history, location } = this.props;
const params = new URLSearchParams(location.search);
......@@ -104,28 +128,38 @@ class BaseMyDataPage<T> extends React.Component<T & IDataPublishedContainerProps
location.search = params.toString();
history.push(location);
const { tab } = this.props;
this.loadData(null, tab, page, results, sortBy, dir);
}
protected loadData(tab, page, size, sortBy, dir) {
const {listDatasets, listDescriptors, listDescriptorLists, preFilter} = this.props;
protected onFilter = (newFilters) => {
this.setState({...this.state, filter: newFilters});
const filters = preFilter;
const {tab, pageCurrent, pageSize, pageSort, pageDir} = this.props;
this.loadData(newFilters, tab, pageCurrent, pageSize, pageSort, pageDir);
}
if (this.state.tab !== tab || this.state.pageCurrent !== page || this.state.pageSize !== size || this.state.pageSort !== sortBy || this.state.pageDir !== dir) {
protected loadData(filter, tab, page, size, sortBy, dir) {
const {listDatasets, listDescriptors, listDescriptorLists, preFilter, filterCode} = this.props;
const newFilters = filter && { ...preFilter, ...filter };
// console.log(`Filters code=${filterCode}`, newFilters, preFilter);
if (newFilters || this.state.tab !== tab || this.state.pageCurrent !== page || this.state.pageSize !== size || this.state.pageSort !== sortBy || this.state.pageDir !== dir) {
// console.log('Reloading');
this.setState(update(this.state, {
tab: {$set: tab},
pageCurrent: {$set: page},
pageSize: {$set: size},
pageSort: {$set: sortBy},
pageDir: {$set: dir},
filterCode: {$set: filterCode},
}));
switch (tab) {
case 'descriptors': listDescriptors(page, size, sortBy, filters, dir); break;
case 'descriptorlists': listDescriptorLists(page, size, sortBy, filters, dir); break;
case 'descriptors': listDescriptors(page, size, sortBy, newFilters || filterCode, dir); break;
case 'descriptorlists': listDescriptorLists(page, size, sortBy, newFilters || filterCode, dir); break;
case 'datasets':
default: listDatasets(page, size, sortBy, filters, dir); break;
default: listDatasets(page, size, sortBy, newFilters || filterCode, dir); break;
}
} else {
// noop
......@@ -142,21 +176,14 @@ class DP extends BaseMyDataPage<any> {
}
public render() {
const {title, tab, basePath, datasets, descriptors, descriptorLists} = this.props;
const {title, tab, basePath, filterCode, filter} = this.props;
let paged: Page<any> = datasets;
switch (tab) {
case 'descriptorlists': paged = descriptorLists; break;
case 'descriptors': paged = descriptors; break;
case 'datasets':
default: break;
}
const paged = this.getPaged(this.props);
return (
<div>
<ContentHeaderWithButton title={ title } buttons={ <BackButton defaultTarget="/dashboard" defaultBackText="BACK TO DASHBOARD" preferDefaultTarget="true" /> }/>
<MyDataTable tab={ tab } basePath={ basePath }
<MyDataTable tab={ tab } basePath={ basePath } filterCode={ filterCode } filter={ filter } onFilter={ this.onFilter }
paged={ paged } onPaginationChange={ this.onPaginationChange } />
</div>
);
......@@ -172,6 +199,8 @@ const mapStateToProps = (state, ownProps) => ({
pageSize: +parse(ownProps.location.search).l || 20, // page size
pageSort: parse(ownProps.location.search).s, // page sort
pageDir: parse(ownProps.location.search).d, // page sort direction
filterCode: parse(ownProps.location.search).filter, // filter code
filter: state.filterCode.filters && parse(ownProps.location.search).filter && state.filterCode.filters[parse(ownProps.location.search).filter] || null,
datasets: state.datasets.paged,
descriptors: state.descriptors.paged,
descriptorLists: state.descriptorList.paged,
......@@ -182,6 +211,7 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({
listDescriptors: listMyDescriptors,
listDescriptorLists: listMyDescriptorLists,
setPageTitle,
filterCodeToUrl,
}, dispatch);
const MyDataPage = connect(
......
import * as React from 'react';
import { reduxForm } from 'redux-form';
import { DASHBOARD_FILTERFORM } from 'constants/dashboard';
import FiltersBlock from 'ui/common/filter/FiltersBlock';
import CollapsibleComponentSearch from 'ui/common/filter/CollapsibleComponentSearch';
import CropFilter from 'ui/catalog/crop/CropFilter';
import TextFilter from 'ui/common/filter/TextFilter';
import Authorize from 'ui/common/authorized/Authorize';
import PartnerFilter from 'ui/catalog/partner/PartnerFilter';
const DashboardFilters = ({handleSubmit, initialize, ...other}) => (
<FiltersBlock title="Filters" handleSubmit={ handleSubmit } initialize={ initialize } { ...other }>
<TextFilter name="_text" label="Keyword search" placeholder="mardi rice" className="p-20" />
<Authorize role="ROLE_ADMINISTRATOR">
<PartnerFilter name="owner" label="Select owner" className="p-20" />
</Authorize>
<CollapsibleComponentSearch title="Crop">
<CropFilter />
</CollapsibleComponentSearch>
</FiltersBlock>
);
export default reduxForm({
enableReinitialize: true,
form: DASHBOARD_FILTERFORM,
})(DashboardFilters);
import * as React from 'react';
import { withStyles } from 'material-ui/styles';
import {Page} from 'model/common.model';
......@@ -10,17 +11,28 @@ import {DatasetLink, DescriptorLink, DescriptorListLink} from 'ui/catalog/Links'
import PrettyDate from 'ui/common/time/PrettyDate';
import Markdown from 'ui/catalog/markdown';
import Permissions from 'ui/common/permission/Permissions';
import { ScrollToTopOnMount } from 'ui/common/page/scrollers';
import DashboardFilters from './Filters';
import Paper from 'material-ui/Paper';
import Button from 'material-ui/Button';
import Grid from 'material-ui/Grid';
interface IDatasetLinkProps extends React.Props<any> {
const styles = (theme) => ({
filterSection: theme.leftPanel.root,
});
interface IMyDataTableProps extends React.Props<any> {
classes?: any;
tab: string;
basePath: string;
paged: Page<any>;
onPaginationChange: (page: number, results: number, sortBy: string, dir?: string) => void;
pageSort?: string;
onFilter: (filter) => void;
filter: any;
filterCode?: string;
}
const defaultSortOptions = {
......@@ -41,12 +53,16 @@ const descriptorListSortOptions = {
...defaultSortOptions,
};
export default function MyDataTable({
function MyDataTable({
classes,
tab,
basePath,
paged,
onPaginationChange,
}: IDatasetLinkProps) {
onFilter,
filter,
filterCode,
}: IMyDataTableProps) {
if (! paged) {
return null;
......@@ -74,55 +90,67 @@ export default function MyDataTable({
}
};
const query = filterCode ? `?filter=${filterCode}` : '';
// console.log(`DF initialValues ${query}`, filter);
return (
<div>
<Tabs tab={ tab }>
<Tab name="datasets" to={ basePath }>Datasets</Tab>
<Tab name="descriptors" to={ `${basePath}/descriptors` }>Descriptors</Tab>
<Tab name="descriptorlists" to={ `${basePath}/descriptorlists` }>Descriptor lists</Tab>
<Tab name="datasets" to={ `${basePath}${query}` }>Datasets</Tab>
<Tab name="descriptors" to={ `${basePath}/descriptors${query}` }>Descriptors</Tab>
<Tab name="descriptorlists" to={ `${basePath}/descriptorlists${query}` }>Descriptor lists</Tab>
</Tabs>
<PaginationComponent displayName="records" pageObj={ paged } onChange={ onPaginationChange } sortOptions={ sortOptions } />
<Grid container spacing={ 0 }>
<ContentContainer>
<Paper>
<Table widths={ [ '10px', '30%', null, null, null, null, '210px' ] } headers={ (
<TableRow>
<TableCell style={ { maxWidth: '10px' } }>No.</TableCell>
<TableCell>Title</TableCell>
<TableCell style={ { maxWidth: '30px' } }>Owner</TableCell>
<TableCell>Created</TableCell>
<TableCell>Modified</TableCell>
<TableCell style={ { maxWidth: '60px' } }>Status</TableCell>
<TableCell style={ { maxWidth: '210px' } } />
</TableRow>
) }>
{ paged && paged.content.map((row, i) => (
<TableRow key={ row.id }>
<TableCell className="font-bold" style={ { fontSize: '1rem' } }>{ paged.size * paged.number + i + 1 }</TableCell>
<TableCell className="font-bold" style={ { fontSize: '1rem' } }>
<ResolveLink row={ row }>{ <Markdown basic source={ row.title } /> || (<i>Untitled</i>) }</ResolveLink>
</TableCell>
<TableCell style={ { fontSize: '1rem' } }>{ row.owner.shortName }</TableCell>
<TableCell style={ { fontSize: '1rem' } }>{ row.createdDate && <PrettyDate value={ row.createdDate } /> }</TableCell>
<TableCell style={ { fontSize: '1rem' } }>{ row.lastModifiedDate && <PrettyDate value={ row.lastModifiedDate } /> }</TableCell>
<TableCell style={ { color: row.published ? '#88bb41' : '#ed9506' , fontSize: '1rem' } }>{ row.published ? 'Published' : 'In progress' }</TableCell>
<TableCell style={ { fontSize: '1rem' } }>
<ResolveLink row={ row }>
<Button raised>{ row.published && row._permissions.write ? 'VIEW' : 'EDIT' }</Button>
</ResolveLink>
{ row._permissions.manage && <Permissions clazz={ row.clazz } id={ row.id } /> }
</TableCell>
</TableRow>
),
) }
</Table>
</Paper>
</ContentContainer>
<ScrollToTopOnMount />
<Grid container spacing={ 0 }>
<Grid item xs={ 12 } md={ 3 } lg={ 2 } className={ classes.filterSection }>
<DashboardFilters initialValues={ filter } onSubmit={ onFilter } />
</Grid>
<PaginationComponent displayName="records" pageObj={ paged } onChange={ onPaginationChange } sortOptions={ sortOptions } />
<Grid item xs={ 12 } md={ 9 } lg={ 10 } className="back-gray">
<PaginationComponent displayName="records" pageObj={ paged } onChange={ onPaginationChange } sortOptions={ sortOptions } />
<Grid container spacing={ 0 }>
<ContentContainer>
<Paper>
<Table widths={ [ '10px', '30%', null, null, null, null, '210px' ] } headers={ (
<TableRow>
<TableCell style={ { maxWidth: '10px' } }>No.</TableCell>
<TableCell>Title</TableCell>
<TableCell style={ { maxWidth: '30px' } }>Owner</TableCell>
<TableCell>Created</TableCell>
<TableCell>Modified</TableCell>
<TableCell style={ { maxWidth: '60px' } }>Status</TableCell>
<TableCell style={ { maxWidth: '210px' } } />
</TableRow>
) }>
{ paged && paged.content.map((row, i) => (
<TableRow key={ row.id }>
<TableCell className="font-bold" style={ { fontSize: '1rem' } }>{ paged.size * paged.number + i + 1 }</TableCell>
<TableCell className="font-bold" style={ { fontSize: '1rem' } }>
<ResolveLink row={ row }>{ <Markdown basic source={ row.title } /> || (<i>Untitled</i>) }</ResolveLink>
</TableCell>
<TableCell style={ { fontSize: '1rem' } }>{ row.owner.shortName }</TableCell>
<TableCell style={ { fontSize: '1rem' } }>{ row.createdDate && <PrettyDate value={ row.createdDate } /> }</TableCell>
<TableCell style={ { fontSize: '1rem' } }>{ row.lastModifiedDate && <PrettyDate value={ row.lastModifiedDate } /> }</TableCell>
<TableCell style={ { color: row.published ? '#88bb41' : '#ed9506' , fontSize: '1rem' } }>{ row.published ? 'Published' : 'In progress' }</TableCell>
<TableCell style={ { fontSize: '1rem' } }>
<ResolveLink row={ row }>
<Button raised>{ row.published && row._permissions.write ? 'VIEW' : 'EDIT' }</Button>
</ResolveLink>
{ row._permissions.manage && <Permissions clazz={ row.clazz } id={ row.id } /> }
</TableCell>
</TableRow>
),
) }
</Table>
</Paper>
</ContentContainer>
</Grid>
<PaginationComponent displayName="records" pageObj={ paged } onChange={ onPaginationChange } sortOptions={ sortOptions } />
</Grid>
</Grid>
</div>
);
}
export default withStyles(styles)(MyDataTable);
......@@ -6,6 +6,7 @@ import { withStyles } from 'material-ui/styles';
import {log} from 'utilities/debug';
import { parse } from 'query-string';
import { filterCodeToUrl } from 'actions/filterCode';
import { listCrops } from 'actions/crop';
import { listDatasetsRequest, promiselistDatasets, listDatasetsByCodeRequest } from 'actions/dataset';
......@@ -31,6 +32,7 @@ interface IDatasetsProps extends React.ClassAttributes<any> {
pagination: Pagination<IDatasetFilter>;
paged: Page<Dataset>;
filterCodeToUrl: any;
listDatasetsRequest: any;
listDatasetsByCodeRequest: any;
promiselistDatasets: any;
......@@ -60,16 +62,11 @@ class BrowsePage extends React.Component<IDatasetsProps, any> {
}
public componentWillReceiveProps(nextProps) {
const {listDatasetsRequest, pagination: oldPagination, listDatasetsByCodeRequest} = this.props;
const {pagination} = nextProps;
if (! oldPagination.equals(pagination)) {
log('Paginations differ!', pagination);
if (pagination.filterCode) {
listDatasetsByCodeRequest(pagination.page, pagination.size, pagination.sort, pagination.filterCode, pagination.dir);
} else {
listDatasetsRequest(pagination.page, pagination.size, pagination.sort, pagination.filter, pagination.dir);
}
const {filterCodeToUrl} = this.props;
const {paged} = nextProps;
if (paged) {
filterCodeToUrl(paged.filterCode);
}
}
......@@ -174,7 +171,7 @@ const mapStateToProps = (state, ownProps) => ({
size: +parse(ownProps.location.search).l || 50, // page size
sort: parse(ownProps.location.search).s, // page sorts
dir: parse(ownProps.location.search).d, // page sort directions
filter: state.datasets.pagedQuery && parse(ownProps.location.search).filter && state.datasets.pagedQuery.filter || null,
filter: state.filterCode.filters && parse(ownProps.location.search).filter && state.filterCode.filters[parse(ownProps.location.search).filter] || null,
filterCode: parse(ownProps.location.search).filter,
}),
paged: state.datasets.paged,
......@@ -185,6 +182,7 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({
promiselistDatasets,
listDatasetsByCodeRequest,
listCrops,
filterCodeToUrl,
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(BrowsePage));
......@@ -6,6 +6,7 @@ import { withStyles } from 'material-ui/styles';
import {log} from 'utilities/debug';
import { parse } from 'query-string';
import { filterCodeToUrl } from 'actions/filterCode';
import { loadDescriptors, promiseLoadDescriptors, loadDescriptorsByCode } from 'actions/descriptors';
import { listCrops } from 'actions/crop';
import { Descriptor, IDescriptorFilter } from 'model/descriptor.model';
......@@ -27,6 +28,7 @@ interface IDescriptorListsPageProps extends React.ClassAttributes<any> {
loadDescriptors: (page?: number, results?: number, sortBy?: string, filter?: IDescriptorFilter) => void;
loadDescriptorsByCode: (page?: number, results?: number, sortBy?: string, filterCode?: string) => void;
listCrops: () => any;
filterCodeToUrl: any;
loading: any;
}