Commit 8ca6de1f authored by Maxym Borodenko's avatar Maxym Borodenko Committed by Matija Obreza

Subset and Dataset accession lists

- Loading accessions on demand for stepper
- Use accessionRef.accession data when available
parent a1de66c5
......@@ -8,7 +8,9 @@ import Page from 'model/Page';
import Subset from 'model/subset/Subset';
import SubsetCreator from 'model/subset/SubsetCreator';
import SubsetFilter from 'model/subset/SubsetFilter';
import {AccessionRef} from 'model/accession/AccessionRef';
const URL_LIST_ACCESSIONS = UrlTemplate.parse(`/api/v1/subset/accessions/{UUID}`);
const URL_ADD_ACCESSIONS = UrlTemplate.parse(`/api/v1/subset/add-accessions/{UUID},{version}`);
const URL_APPROVE_SUBSET = `/api/v1/subset/approve`;
const URL_CREATE = `/api/v1/subset/create`;
......@@ -32,6 +34,31 @@ const URL_LOAD_BY_UUID = UrlTemplate.parse(`/api/v1/subset/{UUID}/subsetcreator/
*/
class SubsetService {
/**
* listAccessions at /api/v1/subset/accessions/{UUID}
*
* @param UUID UUID
* @param page undefined
*/
public static listAccessions(UUID: string, page?: IPageRequest): Promise<Page<AccessionRef>> {
const qs = QueryString.stringify({
p: page.page || undefined,
l: page.size || undefined,
d: page.direction ? page.direction : undefined,
s: page.properties || undefined,
}, {});
const apiUrl = URL_LIST_ACCESSIONS.expand({ UUID }) + (qs ? `?${qs}` : '');
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return axiosBackend.request({
url: apiUrl,
method: 'GET',
...content,
}).then(({ data }) => data as Page<AccessionRef>);
}
/**
* addAccessions at /api/v1/subset/add-accessions/{UUID},{version}
*
......
// Constants
import { DASHBOARD_APPEND_SUBSETS, DASHBOARD_RECEIVE_SUBSET, DASHBOARD_RECEIVE_SUBSETS, DASHBOARD_REMOVE_SUBSET } from 'subsets/constants';
import { DASHBOARD_APPEND_SUBSETS, DASHBOARD_RECEIVE_SUBSET, DASHBOARD_RECEIVE_SUBSETS, DASHBOARD_REMOVE_SUBSET,
DASHBOARD_RECEIVE_ACCESSIONS, DASHBOARD_APPEND_ACCESSIONS} from 'subsets/constants';
// Model
import FilteredPage from 'model/FilteredPage';
import Subset from 'model/subset/Subset';
import SubsetService from 'service/genesys/SubsetService';
import Page from 'model/Page';
import {AccessionRef} from 'model/accession/AccessionRef';
const receiveSubsets = (paged: FilteredPage<Subset>, error = null) => ({
......@@ -33,6 +35,16 @@ export const createSubset = () => (dispatch) => {
dispatch(receiveSubset(new Subset()));
};
const receiveAccessions = (accessionRefs: Page<AccessionRef>, error = null) => ({
type: DASHBOARD_RECEIVE_ACCESSIONS,
payload: { accessionRefs, error },
});
const appendAccessions = (accessionRefs: Page<AccessionRef>, error = null) => ({
type: DASHBOARD_APPEND_ACCESSIONS,
payload: { accessionRefs, error },
});
export const loadSubset = (uuid: string) => (dispatch) => {
return SubsetService.get(uuid)
.then((subset) => {
......@@ -57,3 +69,16 @@ export const loadMoreSubsets = (paged?: Page<Subset>) => (dispatch) => {
});
};
export const loadMoreAccessions = (UUID: string, paged?: Page<AccessionRef>) => (dispatch, getState) => {
return SubsetService.listAccessions(UUID, Page.nextPage(paged))
.then((paged) => {
if (paged.number === 0) {
dispatch(receiveAccessions(paged));
} else {
dispatch(appendAccessions(paged));
}
}).catch((error) => {
console.log(`API error`, error);
dispatch(receiveAccessions(null, error));
});
};
// Actions
import navigateTo from 'actions/navigation';
import { receiveSubset, removeSubset } from './dashboard';
import {loadMoreAccessions, receiveSubset, removeSubset} from './dashboard';
// Constants
import { ADD_CREATOR_TO_SUBSET, REMOVE_CREATOR_FROM_SUBSET, UPDATE_SUBSET_CREATOR } from 'subsets/constants';
......@@ -24,6 +24,7 @@ export const updateSubsetAccessionRefs = (subset: Subset, accessionRefs: Accessi
SubsetService.addAccessions(subset.uuid, subset.version, Object.keys(accessionRefs))
.then((subset) => {
dispatch(receiveSubset(subset));
dispatch(loadMoreAccessions(subset.uuid));
return subset;
});
});
......
......@@ -7,7 +7,7 @@ import navigateTo from 'actions/navigation';
import { showSnackbar } from 'actions/snackbar';
// Constants
import { RECEIVE_SUBSETS, RECEIVE_SUBSET, APPEND_SUBSETS } from 'subsets/constants';
import {RECEIVE_SUBSETS, RECEIVE_SUBSET, APPEND_SUBSETS, RECEIVE_SUBSET_ACCESSIONS, APPEND_SUBSET_ACCESSIONS} from 'subsets/constants';
// Model
import FilteredPage, { IPageRequest } from 'model/FilteredPage';
......@@ -15,6 +15,7 @@ import Subset from 'model/subset/Subset';
import SubsetFilter from 'model/subset/SubsetFilter';
import SubsetService from 'service/genesys/SubsetService';
import Page from 'model/Page';
import {AccessionRef} from 'model/accession/AccessionRef';
const receiveSubsets = (paged: FilteredPage<Subset>, error = null) => ({
type: RECEIVE_SUBSETS,
......@@ -26,6 +27,16 @@ const appendSubsets = (paged: FilteredPage<Subset>, error = null) => ({
payload: { paged, error },
});
const receiveAccessions = (accessionRefs: Page<AccessionRef>, error = null) => ({
type: RECEIVE_SUBSET_ACCESSIONS,
payload: { accessionRefs, error },
});
const appendAccessions = (accessionRefs: Page<AccessionRef>, error = null) => ({
type: APPEND_SUBSET_ACCESSIONS,
payload: { accessionRefs, error },
});
const receiveSubset = (subset: Subset, error = null) => ({
type: RECEIVE_SUBSET,
payload: { subset, error },
......@@ -46,6 +57,20 @@ export const loadMoreSubsets = (paged: FilteredPage<Subset>) => (dispatch, getSt
});
};
export const loadMoreAccessions = (UUID: string, paged?: Page<AccessionRef>) => (dispatch, getState) => {
return SubsetService.listAccessions(UUID, Page.nextPage(paged))
.then((paged) => {
if (paged.number === 0) {
dispatch(receiveAccessions(paged));
} else {
dispatch(appendAccessions(paged));
}
}).catch((error) => {
console.log(`API error`, error);
dispatch(receiveAccessions(null, error));
});
};
export const listMySubsetsPromise = (filter: string | SubsetFilter, page: IPageRequest) => (dispatch, getState): Promise<FilteredPage<Subset>> => {
return SubsetService.mySubsets(filter, page);
};
......
......@@ -2,11 +2,15 @@ export const RECEIVE_SUBSETS = 'subsets/RECEIVE_SUBSETS';
export const APPEND_SUBSETS = 'subsets/APPEND_SUBSETS';
export const RECEIVE_SUBSET = 'subsets/RECEIVE_SUBSET';
export const REMOVE_SUBSET = 'subsets/REMOVE_SUBSET';
export const RECEIVE_SUBSET_ACCESSIONS = 'subsets/RECEIVE_SUBSET_ACCESSIONS';
export const APPEND_SUBSET_ACCESSIONS = 'subsets/APPEND_SUBSET_ACCESSIONS';
export const DASHBOARD_RECEIVE_SUBSETS = 'subsets/dashboard/RECEIVE_SUBSETS';
export const DASHBOARD_APPEND_SUBSETS = 'subsets/dashboard/APPEND_SUBSETS';
export const DASHBOARD_RECEIVE_SUBSET = 'subsets/dashboard/RECEIVE_SUBSET';
export const DASHBOARD_REMOVE_SUBSET = 'subsets/dashboard/REMOVE_SUBSET';
export const DASHBOARD_RECEIVE_ACCESSIONS = 'subsets/DASHBOARD_RECEIVE_ACCESSIONS';
export const DASHBOARD_APPEND_ACCESSIONS = 'subsets/DASHBOARD_APPEND_ACCESSIONS';
export const SUBSET_FILTERFORM = 'Form/Subset/SUBSET_FILTERFORM';
export const SUBSET_FORM = 'Form/Subset/SUBSET_FORM';
......
import update from 'immutability-helper';
import { IReducerAction } from 'model/common.model';
import {DASHBOARD_RECEIVE_SUBSETS, DASHBOARD_RECEIVE_SUBSET, DASHBOARD_REMOVE_SUBSET, DASHBOARD_APPEND_SUBSETS, ADD_CREATOR_TO_SUBSET, REMOVE_CREATOR_FROM_SUBSET, UPDATE_SUBSET_CREATOR} from 'subsets/constants';
import {DASHBOARD_RECEIVE_SUBSETS, DASHBOARD_RECEIVE_SUBSET, DASHBOARD_REMOVE_SUBSET, DASHBOARD_APPEND_SUBSETS, ADD_CREATOR_TO_SUBSET,
REMOVE_CREATOR_FROM_SUBSET, UPDATE_SUBSET_CREATOR, DASHBOARD_RECEIVE_ACCESSIONS, DASHBOARD_APPEND_ACCESSIONS} from 'subsets/constants';
import FilteredPage from 'model/FilteredPage';
import Subset from 'model/subset/Subset';
import Page from 'model/Page';
import {AccessionRef} from 'model/accession/AccessionRef';
import * as _ from 'lodash';
const INITIAL_STATE: {
accessionRefs: Page<AccessionRef>;
accessionsError: any;
subset: Subset;
subsetError: any;
paged: FilteredPage<Subset>;
pagedError: any;
} = {
accessionRefs: null,
accessionsError: null,
subset: null,
subsetError: null,
paged: null,
......@@ -27,6 +34,7 @@ function dashboardSubsets(state = INITIAL_STATE, action: IReducerAction) {
case DASHBOARD_RECEIVE_SUBSET: {
const { subset, error } = action.payload;
const receivedIndex = state.paged ? state.paged.content.findIndex((item) => item.uuid === subset.uuid) : -1;
const mustRemoveAccessions = state.subset && (subset.uuid !== state.subset.uuid);
if (receivedIndex !== -1) {
return update(state, {
......@@ -37,6 +45,7 @@ function dashboardSubsets(state = INITIAL_STATE, action: IReducerAction) {
},
},
subsetError: {$set: error},
accessionRefs: { $set: mustRemoveAccessions ? null : state.accessionRefs },
});
} else {
return update(state, {
......@@ -47,6 +56,31 @@ function dashboardSubsets(state = INITIAL_STATE, action: IReducerAction) {
}
}
case DASHBOARD_RECEIVE_ACCESSIONS: {
const { accessionRefs, error } = action.payload;
return update(state, {
accessionRefs: { $set: accessionRefs },
accessionsError: { $set: error },
});
}
case DASHBOARD_APPEND_ACCESSIONS: {
const {accessionRefs, error} = action.payload;
return !state.accessionRefs ? update(state, {
accessionRefs: {$set: accessionRefs},
accessionsError: {$set: error},
}) :
update(state, {
accessionRefs: {
content: {$push: accessionRefs.content},
number: {$set: accessionRefs.number},
last: {$set: accessionRefs.last},
},
accessionsError: {$set: error},
});
}
case DASHBOARD_RECEIVE_SUBSETS: {
const { paged, error } = action.payload;
return update(state, {
......
import update from 'immutability-helper';
import { IReducerAction } from 'model/common.model';
import {RECEIVE_SUBSETS, RECEIVE_SUBSET, REMOVE_SUBSET, APPEND_SUBSETS, ADD_CREATOR_TO_SUBSET, REMOVE_CREATOR_FROM_SUBSET, UPDATE_SUBSET_CREATOR} from 'subsets/constants';
import {RECEIVE_SUBSETS, RECEIVE_SUBSET, REMOVE_SUBSET, APPEND_SUBSETS, ADD_CREATOR_TO_SUBSET, REMOVE_CREATOR_FROM_SUBSET, UPDATE_SUBSET_CREATOR,
APPEND_SUBSET_ACCESSIONS, RECEIVE_SUBSET_ACCESSIONS} from 'subsets/constants';
import FilteredPage from 'model/FilteredPage';
import Subset from 'model/subset/Subset';
import {AccessionRef} from 'model/accession/AccessionRef';
import Page from 'model/Page';
import * as _ from 'lodash';
const INITIAL_STATE: {
accessionRefs: Page<AccessionRef>;
accessionsError: any;
subset: Subset;
subsetError: any;
paged: FilteredPage<Subset>;
pagedError: any;
} = {
accessionRefs: null,
accessionsError: null,
subset: null,
subsetError: null,
paged: null,
......@@ -27,6 +34,7 @@ function publicSubsets(state = INITIAL_STATE, action: IReducerAction) {
case RECEIVE_SUBSET: {
const { subset, error } = action.payload;
const receivedIndex = state.paged ? state.paged.content.findIndex((item) => item.uuid === subset.uuid) : -1;
const mustRemoveAccessions = state.subset && (subset.uuid !== state.subset.uuid);
if (receivedIndex !== -1) {
return update(state, {
......@@ -37,6 +45,7 @@ function publicSubsets(state = INITIAL_STATE, action: IReducerAction) {
},
},
subsetError: {$set: error},
accessionRefs: { $set: mustRemoveAccessions ? null : state.accessionRefs },
});
} else {
return update(state, {
......@@ -47,6 +56,31 @@ function publicSubsets(state = INITIAL_STATE, action: IReducerAction) {
}
}
case RECEIVE_SUBSET_ACCESSIONS: {
const { accessionRefs, error } = action.payload;
return update(state, {
accessionRefs: { $set: accessionRefs },
accessionsError: { $set: error },
});
}
case APPEND_SUBSET_ACCESSIONS: {
const {accessionRefs, error} = action.payload;
return !state.accessionRefs ? update(state, {
accessionRefs: {$set: accessionRefs},
accessionsError: {$set: error},
}) :
update(state, {
accessionRefs: {
content: {$push: accessionRefs.content},
number: {$set: accessionRefs.number},
last: {$set: accessionRefs.last},
},
accessionsError: {$set: error},
});
}
case RECEIVE_SUBSETS: {
const { paged, error } = action.payload;
return update(state, {
......
......@@ -3,7 +3,7 @@ import { translate } from 'react-i18next';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
// Actions
import {loadSubset} from 'subsets/actions/public';
import {loadMoreAccessions, loadSubset} from 'subsets/actions/public';
import {unpublishSubset} from 'subsets/actions/editor';
import navigateTo from 'actions/navigation';
// Models
......@@ -15,6 +15,8 @@ import Loading from 'ui/common/Loading';
import ContentHeaderWithButton from 'ui/common/heading/ContentHeaderWithButton';
import ActionButton from 'ui/common/buttons/ActionButton';
import SubsetDisplay from './c/SubsetDisplay';
import {AccessionRef} from 'model/accession/AccessionRef';
import Page from 'model/Page';
interface IDisplayPageProps extends React.ClassAttributes<any> {
uuid: string;
......@@ -24,6 +26,8 @@ interface IDisplayPageProps extends React.ClassAttributes<any> {
loadSubset: any;
navigateTo: any;
unpublishSubset: (subset: Subset) => void;
loadMoreAccessions: (UUID: string, page?: Page<AccessionRef>) => any;
accessionRefs: Page<AccessionRef>;
t: any;
}
......@@ -33,6 +37,9 @@ class DisplayPage extends React.Component<IDisplayPageProps, any> {
({ params: { uuid } }) => {
return uuid ? loadSubset(uuid) : null;
},
({ params: { uuid } }) => {
return uuid ? loadMoreAccessions(uuid) : null;
},
];
private onUnpublish = () => {
......@@ -48,14 +55,20 @@ class DisplayPage extends React.Component<IDisplayPageProps, any> {
}
public componentWillMount() {
const { subset, uuid, loadSubset } = this.props;
const { subset, uuid, loadSubset, loadMoreAccessions } = this.props;
if (uuid && (! subset || uuid !== subset.uuid)) {
loadSubset(uuid);
loadMoreAccessions(uuid);
}
}
private loadMoreAccessions = (paged: Page<AccessionRef>) => {
const { loadMoreAccessions, subset } = this.props;
loadMoreAccessions(subset.uuid, paged);
}
public render() {
const { error, subset, uuid, userRole, t } = this.props;
const { error, subset, uuid, userRole, t, accessionRefs } = this.props;
const stillLoading: boolean = ! error && (! subset || (uuid && subset && subset.uuid !== uuid));
const isActionsActive: boolean = userRole.findIndex((role) => role === 'ROLE_ADMINISTRATOR') !== -1 || (subset && subset.state === PublishState.REVIEWING);
......@@ -68,7 +81,14 @@ class DisplayPage extends React.Component<IDisplayPageProps, any> {
{ stillLoading ? <Loading /> :
<div>
{ error && <div>{ JSON.stringify(error) }</div> }
{ subset && <SubsetDisplay isActionsActive={ isActionsActive } subset={ subset }/> }
{ subset &&
<SubsetDisplay
isActionsActive={ isActionsActive }
subset={ subset }
accessions={ accessionRefs }
loadAccessions={ this.loadMoreAccessions }
/>
}
</div>
}
</PageContents>
......@@ -79,6 +99,7 @@ class DisplayPage extends React.Component<IDisplayPageProps, any> {
const mapStateToProps = (state, ownProps) => ({
subset: state.subsets.public.subset,
accessionRefs: state.subsets.public.accessionRefs,
userRole: state.login.authorities,
error: state.subsets.public.subsetError,
uuid: ownProps.match.params.uuid,
......@@ -88,6 +109,7 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({
loadSubset,
navigateTo,
unpublishSubset,
loadMoreAccessions,
}, dispatch);
......
......@@ -9,11 +9,14 @@ import Accession from 'model/accession/Accession';
import AccessionFilter from 'model/accession/AccessionFilter';
import {IPageRequest} from 'model/FilteredPage';
import Page from 'model/Page';
import {AccessionRef} from 'model/accession/AccessionRef';
// ui
import SubsetCard from 'subsets/ui/c/SubsetCard';
import AccessionCard from 'accessions/ui/c/AccessionCard';
import PropertiesCard from 'ui/common/PropertiesCard';
import Grid from '@material-ui/core/Grid';
import Loading from 'ui/common/Loading';
import PagedLoader from 'ui/common/PagedLoader';
const styles = (theme) => ({
accessionSection: {
......@@ -31,17 +34,26 @@ interface IDetailInfoProps extends React.ClassAttributes<any> {
listAccessions: (filter: string | AccessionFilter, page: IPageRequest) => Promise<Page<Accession>>;
unpublishSubset?: (subset: Subset) => any;
deleteSubset?: (subset: Subset) => any;
loadAccessions: (page: Page<AccessionRef>) => any;
accessions: Page<AccessionRef>;
}
class DetailInfo extends React.Component<IDetailInfoProps, any> {
public render() {
const {classes, subset, t} = this.props;
const {classes, subset, t, accessions, loadAccessions} = this.props;
if (!subset) {
log('Waiting for subset.');
return null;
}
const renderAccession = (accessionRef: AccessionRef, index: number) => (
<Grid key={ accessionRef.accession.uuid } item xs={ 12 } className={ classes.accessionCard }>
<AccessionCard index={ index } accession={ accessionRef.accession }/>
</Grid>
);
// TODO sometimes creator can be null
return (
<div>
......@@ -59,12 +71,13 @@ class DetailInfo extends React.Component<IDetailInfoProps, any> {
}
/>
}
{ subset.accessionRefs && subset.accessionRefs.map((accessionRef, index: number) => (
accessionRef.accession &&
<Grid key={ accessionRef.accession.uuid } item xs={ 12 } className={ classes.accessionCard }>
<AccessionCard index={ index } accession={ accessionRef.accession }/>
</Grid>
)) }
{ ! accessions ? <Loading /> :
<PagedLoader
paged={ accessions }
loadMore={ loadAccessions }
roughItemHeight={ 80 }
itemRenderer={ renderAccession } />
}
</Grid>
</div>
);
......
......@@ -17,11 +17,14 @@ import Subset from 'model/subset/Subset';
import CSVConfiguration, { CSVConfig } from 'ui/common/csv-configuration/CSVConfiguration';
import AccessionRefsTable from 'ui/catalog/accession/AccessionRefsTable';
import Loading from 'ui/common/Loading';
import Page from 'model/Page';
interface IListOfAccession extends React.ClassAttributes<any> {
classes: any;
subset: Subset;
onAccessionsUpdated: (AccessionRefs: AccessionRef[]) => Promise<Subset>;
accessionRefs: Page<AccessionRef>;
loadAccessions: (page?: Page<AccessionRef>) => any;
t: any;
}
......@@ -75,7 +78,7 @@ class ListOfAccession extends React.Component<IListOfAccession, any> {
public render() {
const { classes, subset, t } = this.props;
const { classes, subset, accessionRefs, loadAccessions, t } = this.props;
const { uploading, uploadText } = this.state;
return (
......@@ -104,18 +107,11 @@ class ListOfAccession extends React.Component<IListOfAccession, any> {
{ uploading && <Loading message={ uploadText } /> }
<h3>{ t('subsets.dashboard.p.stepper.accessionList.accessionListCount', {count: subset.accessionRefs && subset.accessionRefs.length || 0}) }</h3>
{ subset.accessionRefs &&
<AccessionRefsTable accessionRefs={ subset.accessionRefs.map((accessionRef) => (
{
doi: accessionRef.accession.doi,
instCode: accessionRef.accession.institute.code,
acceNumb: accessionRef.accession.accessionNumber,
species: accessionRef.accession.taxonomy.species,
genus: accessionRef.accession.taxonomy.genus,
}
)) }/>
}
<h3>{ t('subsets.dashboard.p.stepper.accessionList.accessionListCount', {count: subset && subset.accessionCount || 0}) }</h3>
<AccessionRefsTable
loadNextPage={ loadAccessions }
paged={ accessionRefs }
/>
</div>
);
}
......
......@@ -3,10 +3,12 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
// actions
import { updateSubsetAccessionRefs } from 'subsets/actions/editor';
import {loadMoreAccessions} from 'subsets/actions/dashboard';
// models
import Subset from 'model/subset/Subset';
import { AccessionRef } from 'model/accession/AccessionRef';
import Page from 'model/Page';
// ui
import ListOfAccessions from './ListOfAccessions';
......@@ -16,17 +18,40 @@ import Loading from 'ui/common/Loading';
interface IAccessionsListStep extends React.ClassAttributes<any> {
item: Subset;
updateSubsetAccessionRefs: (subset: Subset, accessionRefs: AccessionRef[]) => Promise<Subset>;
loadMore: (UUID: string, page?: Page<AccessionRef>) => any;
accessionRefs: Page<AccessionRef>;
uuid: string;
}
class AccessionsListStep extends StepperTemplate<IAccessionsListStep> {
protected static needs = [
({ params: { uuid } }) => {
return uuid ? loadMoreAccessions(uuid) : null;
},
];
public componentWillMount() {
const { loadMore, uuid, accessionRefs, item } = this.props;
if (! accessionRefs || (item && item.uuid !== uuid)) {
loadMore(uuid);
}
}
private loadMoreAccessions = (paged: Page<AccessionRef>) => {
const { loadMore, item } = this.props;
loadMore(item.uuid, paged);
}
protected renderContent = () => {
const { item } = this.props;
const { item, accessionRefs } = this.props;
return !item ? <Loading /> : (
<ListOfAccessions
onAccessionsUpdated={ this.updateaccessionRefs }
subset={ item }
accessionRefs={ accessionRefs }
loadAccessions={ this.loadMoreAccessions }
/>
);
}
......@@ -39,11 +64,13 @@ class AccessionsListStep extends StepperTemplate<IAccessionsListStep> {
}
const mapStateToProps = (state, ownProps) => ({
// null
accessionRefs: state.subsets.dashboard.accessionRefs,
uuid: ownProps.match.params.uuid,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
updateSubsetAccessionRefs,
loadMore: loadMoreAccessions,
}, dispatch);
export default connect(
......