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

Merge branch '14-configuring-features' into 'master'

Resolve "Configuring features"

Closes #10 and #14

See merge request !12
parents b4ffa2fd 083460e9
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
"error", "error",
{ {
"types": { "types": {
// "object": false "object": "false"
} }
} }
], ],
...@@ -120,7 +120,7 @@ ...@@ -120,7 +120,7 @@
"import/no-internal-modules": "off", "import/no-internal-modules": "off",
"import/order": "off", "import/order": "off",
"indent": [2, 2, {"SwitchCase": 1}], "indent": [2, 2, {"SwitchCase": 1}],
// "no-restricted-imports": ["error", {"patterns": ["../*"]}], "no-restricted-imports": ["error", {"patterns": ["../*"]}],
"max-classes-per-file": [ "max-classes-per-file": [
"error", "error",
10 10
......
export class Config {
public apiUrl: string;
public clientId: string;
public clientKey: string;
public filter: Record<string, any>;
public constructor(config: Config) {
this.apiUrl = config.apiUrl;
this.clientId = config.clientId;
this.clientKey = config.clientKey;
this.filter = config.filter;
}
}
export const DefaultConfig = new Config( {
apiUrl: 'https://api.sandbox.genesys-pgr.org',
clientId: 'clientid@genesys',
clientKey: 'changeme',
filter: {},
})
import { showGenesysUI } from 'genesys'; import { Config } from 'config/config';
import {
showGenesysUI,
// showOverview, // for testing
} from 'genesys';
const queryLang = document.location.search && document.location.search.substr(1) || undefined; const queryLang = document.location.search && document.location.search.substr(1) || undefined;
showGenesysUI(document.getElementById('genesys'), { const genesysConfig: Config = {
apiUrl: 'http://localhost:8080',
clientId: 'defaultclient@localhost', clientId: 'defaultclient@localhost',
clientKey: 'changeme', clientKey: 'changeme',
apiUrl: 'http://localhost:8080',
filter: { institute: { code: [ 'COL003', 'BEL084', 'ETH013' ] } }, filter: { institute: { code: [ 'COL003', 'BEL084', 'ETH013' ] } },
}, queryLang); language: queryLang,
shoppingCart: {
enabled: true,
},
};
showGenesysUI(document.getElementById('genesys'), genesysConfig);
// showOverview(document.getElementById('genesys'), genesysConfig);
import React from 'react'; import React from 'react';
import { connect } from 'react-redux';
import { AccessionService } from '@genesys/client/service'; import { AccessionService } from '@genesys/client/service';
import { Property } from 'ui/common/Property'; import { Property } from 'ui/common/Property';
import AccessionDetails from '@genesys/client/model/accession/AccessionDetails'; import AccessionDetails from '@genesys/client/model/accession/AccessionDetails';
import { WithTranslation, withTranslation } from 'react-i18next'; import { WithTranslation, withTranslation } from 'react-i18next';
import { LocalStorageCart } from 'utilities'; import { LocalStorageCart } from 'utilities';
import { WithConfig } from 'config/config';
interface IAccessionDetailsPageState { interface IAccessionDetailsPageState {
accession: AccessionDetails; accession: AccessionDetails;
...@@ -13,10 +15,9 @@ interface IAccessionDetailsPageState { ...@@ -13,10 +15,9 @@ interface IAccessionDetailsPageState {
interface IAccessionDetailsPage { interface IAccessionDetailsPage {
match: any; match: any;
apiUrl: string;
} }
class AccessionDetailsPage extends React.Component<IAccessionDetailsPage & WithTranslation, IAccessionDetailsPageState> { class AccessionDetailsPage extends React.Component<IAccessionDetailsPage & WithTranslation & WithConfig, IAccessionDetailsPageState> {
public constructor(props) { public constructor(props) {
super(props); super(props);
} }
...@@ -91,7 +92,7 @@ class AccessionDetailsPage extends React.Component<IAccessionDetailsPage & WithT ...@@ -91,7 +92,7 @@ class AccessionDetailsPage extends React.Component<IAccessionDetailsPage & WithT
public render() { public render() {
const { accession, cartItems } = this.state; const { accession, cartItems } = this.state;
const { apiUrl, t } = this.props; const { t, appConfig: { apiUrl, shoppingCart } } = this.props;
let propertyIndex = 0; let propertyIndex = 0;
...@@ -200,25 +201,31 @@ class AccessionDetailsPage extends React.Component<IAccessionDetailsPage & WithT ...@@ -200,25 +201,31 @@ class AccessionDetailsPage extends React.Component<IAccessionDetailsPage & WithT
)) } )) }
</> </>
} }
<div className="pt-4"> { shoppingCart.enabled &&
{cartItems.includes(this.props.match.params.uuid) ? <div className="pt-4">
<button { cartItems.includes(this.props.match.params.uuid) ?
type="button" <button
name={ `button-remove-${accession.details.uuid}` } type="button"
data-uuid={ accession.details.uuid } name={ `button-remove-${accession.details.uuid}` }
className="btn btn-primary" data-uuid={ accession.details.uuid }
onClick={ this.removeFromCart } className="btn btn-primary"
> onClick={ this.removeFromCart }
{ t('cart.removeFromCart') } >
</button> { t('cart.removeFromCart') }
: </button>
this.renderAddToCart() :
} this.renderAddToCart()
</div> }
</div>
}
</> </>
); );
} }
}; };
} }
export default withTranslation()(AccessionDetailsPage); const mapStateToProps = (state) => ({
appConfig: state.appConfig.config,
});
export default connect(mapStateToProps)(withTranslation()(AccessionDetailsPage));
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { parse } from 'query-string'; import { parse } from 'query-string';
import { connect } from 'react-redux';
import { AccessionService } from '@genesys/client/service'; import { AccessionService } from '@genesys/client/service';
import AccessionFilter from '@genesys/client/model/accession/AccessionFilter'; import AccessionFilter from '@genesys/client/model/accession/AccessionFilter';
...@@ -10,6 +11,7 @@ import Pagination from 'ui/common/Pagination'; ...@@ -10,6 +11,7 @@ import Pagination from 'ui/common/Pagination';
import { AccessionFilters } from './AccessionFilters'; import { AccessionFilters } from './AccessionFilters';
import { withTranslation, WithTranslation } from 'react-i18next'; import { withTranslation, WithTranslation } from 'react-i18next';
import { LocalStorageCart } from 'utilities'; import { LocalStorageCart } from 'utilities';
import { WithConfig } from 'config/config';
interface IAccessionListPageState { interface IAccessionListPageState {
filter: AccessionFilter; filter: AccessionFilter;
...@@ -20,16 +22,15 @@ interface IAccessionListPageState { ...@@ -20,16 +22,15 @@ interface IAccessionListPageState {
} }
interface IAccessionListPageProps { interface IAccessionListPageProps {
filter: AccessionFilter;
location: any; location: any;
} }
class AccessionListPage extends React.Component<IAccessionListPageProps & WithTranslation, IAccessionListPageState> { class AccessionListPage extends React.Component<IAccessionListPageProps & WithTranslation & WithConfig, IAccessionListPageState> {
public constructor(props) { public constructor(props) {
super(props); super(props);
this.state = { this.state = {
filter: this.props.filter, filter: this.props.appConfig.filter,
accessions: null, accessions: null,
selected: [], selected: [],
isAllSelected: false, isAllSelected: false,
...@@ -68,7 +69,7 @@ class AccessionListPage extends React.Component<IAccessionListPageProps & WithTr ...@@ -68,7 +69,7 @@ class AccessionListPage extends React.Component<IAccessionListPageProps & WithTr
if (prevPage !== undefined && currentPage === 0 && prevFilterCode !== undefined && filterCode === undefined) { if (prevPage !== undefined && currentPage === 0 && prevFilterCode !== undefined && filterCode === undefined) {
// console.log('did update, reset filter); // console.log('did update, reset filter);
this.loadData(this.props.filter, {}); this.loadData(this.props.appConfig.filter, {});
} }
} }
...@@ -163,7 +164,7 @@ class AccessionListPage extends React.Component<IAccessionListPageProps & WithTr ...@@ -163,7 +164,7 @@ class AccessionListPage extends React.Component<IAccessionListPageProps & WithTr
public render() { public render() {
const { accessions, selected, isAllSelected } = this.state; const { accessions, selected, isAllSelected } = this.state;
const { t } = this.props; const { t, appConfig: { shoppingCart } } = this.props;
const selectedUUIDs = new Set(); const selectedUUIDs = new Set();
selected.forEach((uuid) => selectedUUIDs.add(uuid)); selected.forEach((uuid) => selectedUUIDs.add(uuid));
...@@ -187,48 +188,54 @@ class AccessionListPage extends React.Component<IAccessionListPageProps & WithTr ...@@ -187,48 +188,54 @@ class AccessionListPage extends React.Component<IAccessionListPageProps & WithTr
<table className="table table-striped"> <table className="table table-striped">
<thead className="thead-dark"> <thead className="thead-dark">
<tr> <tr>
<th> { shoppingCart.enabled && (
<input <th>
type="checkbox" <input
name="select-all" type="checkbox"
checked={ isAllSelected } name="select-all"
onChange={ this.onToggleAll } checked={ isAllSelected }
className="align-middle" onChange={ this.onToggleAll }
/> className="align-middle"
</th> />
</th>
) }
<th>{ t('accession.crop') }</th> <th>{ t('accession.crop') }</th>
<th>{ t('accession.acceNumb') }</th> <th>{ t('accession.acceNumb') }</th>
<th>{ t('accession.accessionName') }</th> <th>{ t('accession.accessionName') }</th>
<th>{ t('accession.taxonomy') }</th> <th>{ t('accession.taxonomy') }</th>
<th>{ t('accession.countryOfOrigin') }</th> <th>{ t('accession.countryOfOrigin') }</th>
<th>{ t('accession.sampStat') }</th> <th>{ t('accession.sampStat') }</th>
<th>{ t('list.availability') }</th> { shoppingCart.enabled && ( <th>{ t('list.availability') }</th> ) }
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{ accessions.content.map((a, i) => ( { accessions.content.map((a, i) => (
<tr key={ a.id } className={ selectedUUIDs.has(a.uuid) ? 'table-primary' : '' }> <tr key={ a.id } className={ selectedUUIDs.has(a.uuid) ? 'table-primary' : '' }>
<td> { shoppingCart.enabled && (
{this.canAddToCart(a) && <td>
<input { this.canAddToCart(a) &&
type="checkbox" <input
name={ `checkbox-${a.uuid}-${i}` } type="checkbox"
data-uuid={ a.uuid } name={ `checkbox-${a.uuid}-${i}` }
checked={ selectedUUIDs.has(a.uuid) } data-uuid={ a.uuid }
onChange={ this.toggleRowSelect } checked={ selectedUUIDs.has(a.uuid) }
className="align-middle" onChange={ this.toggleRowSelect }
/> className="align-middle"
} />
</td> }
</td>
) }
<td>{ a.cropName }</td> <td>{ a.cropName }</td>
<td><Link to={ `/a/${a.uuid}` }>{ a.accessionNumber }</Link></td> <td><Link to={ `/a/${a.uuid}` }>{ a.accessionNumber }</Link></td>
<td>{ a.accessionName }</td> <td>{ a.accessionName }</td>
<td><span dangerouslySetInnerHTML={ { __html: a.taxonomy.taxonNameHtml } } /></td> <td><span dangerouslySetInnerHTML={ { __html: a.taxonomy.taxonNameHtml } } /></td>
<td>{ a.countryOfOrigin && a.countryOfOrigin.name }</td> <td>{ a.countryOfOrigin && a.countryOfOrigin.name }</td>
<td>{ a.sampStat && t(`accession.sampleStatus.${a.sampStat}`) }</td> <td>{ a.sampStat && t(`accession.sampleStatus.${a.sampStat}`) }</td>
<td> { shoppingCart.enabled &&
{this.renderCartButton(a, i)} <td>
</td> { this.renderCartButton(a, i) }
</td>
}
</tr> </tr>
)) } )) }
</tbody> </tbody>
...@@ -240,4 +247,8 @@ class AccessionListPage extends React.Component<IAccessionListPageProps & WithTr ...@@ -240,4 +247,8 @@ class AccessionListPage extends React.Component<IAccessionListPageProps & WithTr
}; };
} }
export default withTranslation()(AccessionListPage); const mapStateToProps = (state) => ({
appConfig: state.appConfig.config,
});
export default connect(mapStateToProps)(withTranslation()(AccessionListPage));
...@@ -3,39 +3,25 @@ import { withTranslation, WithTranslation } from 'react-i18next'; ...@@ -3,39 +3,25 @@ import { withTranslation, WithTranslation } from 'react-i18next';
// model // model
import AccessionOverview from '@genesys/client/model/accession/AccessionOverview'; import AccessionOverview from '@genesys/client/model/accession/AccessionOverview';
import PropertiesCard from 'ui/common/PropertiesCard'; import PropertiesCard from 'ui/common/PropertiesCard';
import { VocabularyService } from '@genesys/client/service'; import { connect } from 'react-redux';
interface IAccessionOverviewPageState {
countryCodes: object;
}
interface IAccessionOverviewPageProps extends React.ClassAttributes<any> { interface IAccessionOverviewPageProps extends React.ClassAttributes<any> {
overview: AccessionOverview; overview: AccessionOverview;
countryCodes: Record<string, string>;
} }
class AccessionOverviewSection extends React.Component<IAccessionOverviewPageProps & WithTranslation, IAccessionOverviewPageState> { class AccessionOverviewSection extends React.Component<IAccessionOverviewPageProps & WithTranslation> {
public constructor(props) { public constructor(props) {
super(props); super(props);
this.state = {
countryCodes: null,
}
} }
private overviewKeys = ['institute.code', 'institute.country.code3', 'cropName', 'crop.shortName', 'sampStat', 'taxonomy.genus', 'taxonomy.genusSpecies', private overviewKeys = ['institute.code', 'institute.country.code3', 'cropName', 'crop.shortName', 'sampStat', 'taxonomy.genus', 'taxonomy.genusSpecies',
'taxonomy.grinTaxonomySpecies.name', 'taxonomy.currentTaxonomySpecies.name', 'taxonomy.grinTaxonomySpecies.name', 'taxonomy.currentTaxonomySpecies.name',
'countryOfOrigin.code3', 'donorCode', 'mlsStatus', 'available', 'duplSite', 'sgsv', 'storage', 'breederCode', 'aegis']; 'countryOfOrigin.code3', 'donorCode', 'mlsStatus', 'available', 'duplSite', 'sgsv', 'storage', 'breederCode', 'aegis'];
public async componentDidMount() {
const codes = await VocabularyService.decode3166Alpha3Terms(this.props.i18n.language);
this.setState({ countryCodes: codes })
}
public render() { public render() {
const { overview, t } = this.props; const { overview, t, countryCodes } = this.props;
const { countryCodes } = this.state;
if (!overview) { if (!overview) {
return null; return null;
...@@ -206,4 +192,8 @@ class AccessionOverviewSection extends React.Component<IAccessionOverviewPagePro ...@@ -206,4 +192,8 @@ class AccessionOverviewSection extends React.Component<IAccessionOverviewPagePro
} }
} }
export default withTranslation()(AccessionOverviewSection); const mapStateToProps = (state) => ({
countryCodes: state.decoding.countryCodes,
});
export default connect(mapStateToProps)(withTranslation()(AccessionOverviewSection));
import * as React from 'react'; import * as React from 'react';
import { WithTranslation, withTranslation } from 'react-i18next'; import { WithTranslation, withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
// Models // Models
import { AccessionService } from '@genesys/client/service'; import { AccessionService } from '@genesys/client/service';
...@@ -63,4 +64,8 @@ class BrowsePage extends React.Component<IOverviewPageProps, any> { ...@@ -63,4 +64,8 @@ class BrowsePage extends React.Component<IOverviewPageProps, any> {
} }
} }
export default withTranslation()(BrowsePage); const mapStateToProps = (state) => ({
filter: state.appConfig.config.filter,
});
export default connect(mapStateToProps)(withTranslation()(BrowsePage));
export interface WithConfig {
appConfig: Config;
}
export class Config {
public apiUrl?: string;
public clientId: string;
public clientKey: string;
public filter?: Record<string, any>;
// module config
public language?: string = 'en';
public accession?: BaseFeatureConfig;
public shoppingCart?: BaseFeatureConfig;
public constructor(config: Config) {
this.apiUrl = config.apiUrl || defaultConfig.apiUrl;
this.clientId = config.clientId;
this.clientKey = config.clientKey;
this.language = config.language || defaultConfig.language;
this.filter = config.filter || defaultConfig.filter;
// Merge feature config
this.accession = { ...defaultConfig.accession, ...config.accession };
this.shoppingCart = { ...defaultConfig.shoppingCart, ...config.shoppingCart };
// console.log('Source and merged configuration', config, this);
}
}
export class BaseFeatureConfig {
public enabled: boolean;
}
export const defaultConfig: Partial<Config> = {
apiUrl: 'https://api.sandbox.genesys-pgr.org',
filter: {},
language: 'en',
accession: { enabled: true },
shoppingCart: { enabled: false },
};
import { RECEIVE_APP_CONFIG } from 'core/constants/appConfig';
import { Config } from 'config/config';
export const setConfig = (config: Config) => ({
type: RECEIVE_APP_CONFIG,
payload: config,
});
import { VocabularyService } from '@genesys/client/service';
import { RECEIVE_COUNTRY_CODES_DECODED } from 'core/constants/decoding';
export const getCountryCodes = (lang: string) => (dispatch) => {
return VocabularyService
.decode3166Alpha3Terms(lang)
.then((codes) => {
dispatch({
type: RECEIVE_COUNTRY_CODES_DECODED,
payload: codes,
});
})
.catch((e) => {
console.log('Loading country codes decoding failed: ', e);
})
};
export const RECEIVE_APP_CONFIG = 'core/appConfig/RECEIVE';
export const RECEIVE_COUNTRY_CODES_DECODED = 'core/decoding/RECEIVE';
import update from 'immutability-helper';
import { RECEIVE_APP_CONFIG } from 'core/constants/appConfig';
import { Config } from 'config/config';
const INITIAL_STATE: {
config: Config,
} = {
config: null,
};
export default (state = INITIAL_STATE, action: { type?: string, payload?: any } = { type: '', payload: {} }) => {
switch (action.type) {
case RECEIVE_APP_CONFIG: {
const config = new Config(action.payload);