Commit f97e7501 authored by Imran Salam's avatar Imran Salam Committed by Andrey Azov
Browse files

Refactor Redux (especially the Browser reducer/actions) (#21)

- Slice the browser reducer into smaller reducers
- Separate reducers, actions et al into their own files for better maintenance
- Add api-service that contains the code for data fetching
parent 10ad8d99
Pipeline #15622 failed with stage
in 2 minutes and 53 seconds
......@@ -5,6 +5,7 @@ module.exports = {
[
'@babel/env',
{
useBuiltIns: "usage",
modules: false
}
]
......
module.exports = {
globals: {
'ts-jest': {
diagnostics: false
}
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
moduleNameMapper: {
'\\.svg': '<rootDir>/tests/svgrMock.js',
'^config$': '<rootDir>/config.ts',
'(tests/.*)$$': '<rootDir>/$1',
'(src/.*)$': '<rootDir>/$1',
'(static/.*)$': '<rootDir>/$1',
'(static/browser/.*)$': '<rootDir>/$1.stub.js',
......
export enum AnalyticsCategory {
GLOBAL = 'Global'
BROWSER = 'Browser',
DRAWER = 'Drawer',
GLOBAL = 'Global',
HEADER = 'Header',
ENS_OBJECT = 'Ensembl Object',
TRACK_PANEL = 'Track Panel'
}
export type AnalyticsOptions = {
......@@ -28,6 +33,26 @@ export const buildAnalyticsObject = (category: AnalyticsCategory) => (
};
};
export const getBrowserAnalyticsObject = buildAnalyticsObject(
AnalyticsCategory.BROWSER
);
export const getDrawerAnalyticsObject = buildAnalyticsObject(
AnalyticsCategory.DRAWER
);
export const getGlobalAnalyticsObject = buildAnalyticsObject(
AnalyticsCategory.GLOBAL
);
export const getHeaderAnalyticsObject = buildAnalyticsObject(
AnalyticsCategory.HEADER
);
export const getEnsObjectAnalyticsObject = buildAnalyticsObject(
AnalyticsCategory.ENS_OBJECT
);
export const getTrackPanelAnalyticsObject = buildAnalyticsObject(
AnalyticsCategory.TRACK_PANEL
);
import { LOCATION_CHANGE } from 'connected-react-router';
import GoogleAnalyticsTracking from './services/analytics-service';
export const analyticsMiddleWare = (store: any) => (next: any) => (
action: any
) => {
// If we have the google anlytics meta data passed in
// We need to track this event
if (action.meta && action.meta.ga && action.meta.ga.category) {
// The action and category fields are mandatory
GoogleAnalyticsTracking.trackEvent(action);
} else if (
action.type === LOCATION_CHANGE &&
action.payload.location.pathname !==
store.getState().router.location.pathname
) {
// If the location history has been changed, track it as a pageview
GoogleAnalyticsTracking.trackPageView(
action.payload.location.pathname +
action.payload.location.search +
action.payload.location.hash
);
}
next(action);
};
......@@ -23,26 +23,27 @@ import {
} from './browserState';
import {
changeBrowserLocation,
fetchExampleObjectsData,
fetchObjectData,
toggleDrawer,
updateChrLocation,
updateBrowserNavStates
} from './browserActions';
import {
getBrowserOpenState,
getDrawerOpened,
getBrowserNavOpened,
getChrLocation,
getGenomeSelectorActive,
getBrowserActivated,
getExampleObjects
getBrowserActivated
} from './browserSelectors';
import { getChrLocationFromStr, getChrLocationStr } from './browserHelper';
import { getDrawerOpened } from './drawer/drawerSelectors';
import {
fetchExampleEnsObjectsData,
fetchEnsObjectData
} from 'src/ens-object/ensObjectActions';
import { toggleDrawer } from './drawer/drawerActions';
import styles from './Browser.scss';
import 'static/browser/browser.js';
import { getChrLocationFromStr, getChrLocationStr } from './browserHelper';
type StateProps = {
browserActivated: boolean;
......@@ -50,7 +51,6 @@ type StateProps = {
browserOpenState: BrowserOpenState;
chrLocation: ChrLocation;
drawerOpened: boolean;
exampleObjects: {};
genomeSelectorActive: boolean;
};
......@@ -59,8 +59,8 @@ type DispatchProps = {
chrLocation: ChrLocation,
browserEl: HTMLDivElement
) => void;
fetchExampleObjectsData: () => void;
fetchObjectData: (stableId: string) => void;
fetchExampleEnsObjectsData: () => void;
fetchEnsObjectData: (stableId: string) => void;
replace: Replace;
toggleDrawer: (drawerOpened: boolean) => void;
updateBrowserNavStates: (browserNavStates: BrowserNavStates) => void;
......@@ -98,7 +98,7 @@ export const Browser: FunctionComponent<BrowserProps> = (
dispatchBrowserLocation(chrLocation);
props.fetchObjectData(stableId);
props.fetchEnsObjectData(stableId);
}, [props.match.params.stableId]);
useEffect(() => {
......@@ -160,14 +160,13 @@ const mapStateToProps = (state: RootState): StateProps => ({
browserOpenState: getBrowserOpenState(state),
chrLocation: getChrLocation(state),
drawerOpened: getDrawerOpened(state),
exampleObjects: getExampleObjects(state),
genomeSelectorActive: getGenomeSelectorActive(state)
});
const mapDispatchToProps: DispatchProps = {
changeBrowserLocation,
fetchExampleObjectsData,
fetchObjectData,
fetchEnsObjectData,
fetchExampleEnsObjectsData,
replace,
toggleDrawer,
updateBrowserNavStates,
......
......@@ -4,25 +4,24 @@ import { connect } from 'react-redux';
import { browserInfoConfig } from '../browserConfig';
import { TrackType } from '../track-panel/trackPanelConfig';
import {
toggleBrowserNav,
toggleGenomeSelector,
selectBrowserTab,
toggleDrawer
} from '../browserActions';
import { toggleBrowserNav, toggleGenomeSelector } from '../browserActions';
import { ChrLocation } from '../browserState';
import {
getBrowserNavOpened,
getChrLocation,
getBrowserActivated,
getDefaultChrLocation,
getGenomeSelectorActive,
getDrawerOpened,
getGenomeSelectorActive
} from '../browserSelectors';
import { getDrawerOpened } from '../drawer/drawerSelectors';
import { getEnsObjectInfo } from 'src/ens-object/ensObjectSelectors';
import {
getSelectedBrowserTab,
getObjectInfo,
getTrackPanelModalOpened,
getTrackPanelOpened
} from '../browserSelectors';
} from '../track-panel/trackPanelSelectors';
import { selectBrowserTab } from '../track-panel/trackPanelActions';
import { toggleDrawer } from '../drawer/drawerActions';
import { RootState } from 'src/store';
import BrowserReset from '../browser-reset/BrowserReset';
......@@ -38,7 +37,7 @@ type StateProps = {
defaultChrLocation: ChrLocation;
drawerOpened: boolean;
genomeSelectorActive: boolean;
objectInfo: any;
ensObjectInfo: any;
selectedBrowserTab: TrackType;
trackPanelModalOpened: boolean;
trackPanelOpened: boolean;
......@@ -61,7 +60,7 @@ export const BrowserBar: FunctionComponent<BrowserBarProps> = (
props: BrowserBarProps
) => {
const { navigator, reset } = browserInfoConfig;
const { objectInfo } = props;
const { ensObjectInfo } = props;
const [showBrowserInfo, toggleShowBrowserInfo] = useState(true);
const changeBrowserInfoToggle = () => {
......@@ -132,24 +131,24 @@ export const BrowserBar: FunctionComponent<BrowserBarProps> = (
<Fragment>
<dd className={styles.geneSymbol}>
<label>Gene</label>
<span className={styles.value}>{objectInfo.obj_symbol}</span>
<span className={styles.value}>{ensObjectInfo.obj_symbol}</span>
</dd>
<dd>
<label>Stable ID</label>
<span className={styles.value}>{objectInfo.stable_id}</span>
<span className={styles.value}>{ensObjectInfo.stable_id}</span>
</dd>
<dd className="show-for-large">
<label>Spliced mRNA length</label>
<span className={styles.value}>
{objectInfo.spliced_length}
{ensObjectInfo.spliced_length}
</span>
<label>bp</label>
</dd>
<dd className={`show-for-large ${styles.nonLabelValue}`}>
{objectInfo.bio_type}
{ensObjectInfo.bio_type}
</dd>
<dd className={`show-for-large ${styles.nonLabelValue}`}>
{objectInfo.strand} strand
{ensObjectInfo.strand} strand
</dd>
</Fragment>
) : null}
......@@ -192,8 +191,8 @@ const mapStateToProps = (state: RootState): StateProps => ({
chrLocation: getChrLocation(state),
defaultChrLocation: getDefaultChrLocation(state),
drawerOpened: getDrawerOpened(state),
ensObjectInfo: getEnsObjectInfo(state),
genomeSelectorActive: getGenomeSelectorActive(state),
objectInfo: getObjectInfo(state),
selectedBrowserTab: getSelectedBrowserTab(state),
trackPanelModalOpened: getTrackPanelModalOpened(state),
trackPanelOpened: getTrackPanelOpened(state)
......
......@@ -4,7 +4,8 @@ import { connect } from 'react-redux';
import { browserNavConfig, BrowserNavItem } from '../browserConfig';
import { RootState } from 'src/store';
import { getBrowserNavStates, getTrackPanelOpened } from '../browserSelectors';
import { getBrowserNavStates } from '../browserSelectors';
import { getTrackPanelOpened } from '../track-panel/trackPanelSelectors';
import { BrowserNavStates } from '../browserState';
import BrowserNavIcon from './BrowserNavIcon';
......
import config from 'config';
import { createAction, createAsyncAction } from 'typesafe-actions';
import { createAction } from 'typesafe-actions';
import { Dispatch } from 'redux';
import config from 'config';
import { BrowserNavStates, ChrLocation, CogList } from './browserState';
import { TrackType } from './track-panel/trackPanelConfig';
import { getBrowserAnalyticsObject } from 'src/analyticsHelper';
export const updateBrowserActivated = createAction(
'browser/update-browser-activated',
(resolve) => {
return (browserActivated: boolean) =>
resolve(browserActivated, {
ga: {
category: 'Browser',
label: 'Default Action'
}
});
resolve(browserActivated, getBrowserAnalyticsObject('Default Action'));
}
);
......@@ -37,52 +32,10 @@ export const activateBrowser = (browserEl: HTMLDivElement) => {
};
};
export const toggleTrackPanel = createAction(
'browser/toggle-track-panel',
(resolve) => {
return (trackPanelOpened?: boolean) =>
resolve(trackPanelOpened, {
ga: {
category: 'Track Panel',
label: 'User Interaction'
}
});
}
);
export const changeDrawerView = createAction(
'browser/change-drawer-view',
(resolve) => {
return (drawerView: string) =>
resolve(drawerView, {
ga: {
category: 'Drawer',
label: 'User Interaction'
}
});
}
);
export const toggleDrawer = createAction('browser/toggle-drawer', (resolve) => {
return (drawerOpened?: boolean) =>
resolve(drawerOpened, {
ga: {
category: 'Drawer',
label: 'User Interaction'
}
});
});
export const toggleBrowserNav = createAction(
'browser/toggle-browser-navigation',
(resolve) => {
return () =>
resolve(undefined, {
ga: {
category: 'Browser',
label: 'Navigation'
}
});
return () => resolve(undefined, getBrowserAnalyticsObject('Navigation'));
}
);
......@@ -90,12 +43,7 @@ export const updateBrowserNavStates = createAction(
'browser/update-browser-nav-states',
(resolve) => {
return (browserNavStates: BrowserNavStates) =>
resolve(browserNavStates, {
ga: {
category: 'Browser',
label: 'Navigation'
}
});
resolve(browserNavStates, getBrowserAnalyticsObject('Navigation'));
}
);
......@@ -110,12 +58,7 @@ export const updateDefaultChrLocation = createAction(
'browser/update-default-chromosome-location',
(resolve) => {
return (chrLocation: ChrLocation) =>
resolve(chrLocation, {
ga: {
category: 'Browser',
label: 'User Interaction'
}
});
resolve(chrLocation, getBrowserAnalyticsObject('User Interaction'));
}
);
......@@ -150,75 +93,11 @@ export const changeBrowserLocation = (
};
};
export const fetchObject = createAsyncAction(
'browser/fetch_object_request',
'browser/fetch_object_success',
'browser/fetch_object_failure'
)<string, {}, Error>();
export const fetchObjectData = (objectId: string) => {
return (dispatch: Dispatch) => {
dispatch(fetchObject.request(objectId));
return fetch(`${config.apiHost}/browser/get_object_info/${objectId}`)
.then(
(response) => response.json(),
(error) => dispatch(fetchObject.failure(error))
)
.then((json) => dispatch(fetchObject.success(json)));
};
};
export const fetchExampleObjects = createAsyncAction(
'browser/fetch_example_objects_request',
'browser/fetch_example_objects_success',
'browser/fetch_example_objects_failure'
)<null, {}, Error>();
export const fetchExampleObjectsData = () => {
return (dispatch: Dispatch) => {
dispatch(fetchExampleObjects.request(null));
return fetch(`${config.apiHost}/browser/example_objects`)
.then(
(response) => response.json(),
(error) => dispatch(fetchExampleObjects.failure(error))
)
.then((json) => dispatch(fetchExampleObjects.success(json)));
};
};
export const openTrackPanelModal = createAction(
'browser/open-track-panel-modal',
(resolve) => {
return (trackPanelModalView: string) =>
resolve(trackPanelModalView, {
ga: {
category: 'Track Panel Modal',
label: 'User Interaction'
}
});
}
);
export const closeTrackPanelModal = createAction(
'browser/close-track-panel-modal',
(resolve) => {
return () =>
resolve(undefined, {
ga: {
category: 'Track Panel Modal',
label: 'Navigation'
}
});
}
);
export const updateCogList = createAction(
'browser/update-cog-list',
(resolve) => {
return (cogList: number) => {
return resolve(cogList);
return resolve(cogList, getBrowserAnalyticsObject('User Interaction'));
};
}
);
......@@ -227,7 +106,7 @@ export const updateCogTrackList = createAction(
'browser/update-cog-track-list',
(resolve) => {
return (trackY: CogList) => {
return resolve(trackY);
return resolve(trackY, getBrowserAnalyticsObject('User Interaction'));
};
}
);
......@@ -236,7 +115,7 @@ export const updateSelectedCog = createAction(
'browser/update-selected-cog',
(resolve) => {
return (index: string) => {
return resolve(index);
return resolve(index, getBrowserAnalyticsObject('User Interaction'));
};
}
);
......@@ -245,7 +124,10 @@ export const updateTrackConfigNames = createAction(
'browser/update-track-config-names',
(resolve) => {
return (selectedCog: any, sense: boolean) => {
return resolve([selectedCog, sense]);
return resolve(
[selectedCog, sense],
getBrowserAnalyticsObject('User Interaction')
);
};
}
);
......@@ -254,7 +136,10 @@ export const updateTrackConfigLabel = createAction(
'browser/update-track-config-label',
(resolve) => {
return (selectedCog: any, sense: boolean) => {
return resolve([selectedCog, sense]);
return resolve(
[selectedCog, sense],
getBrowserAnalyticsObject('User Interaction')
);
};
}
);
......@@ -263,7 +148,7 @@ export const updateApplyToAll = createAction(
'browser/update-apply-to-all',
(resolve) => {
return (yn: boolean) => {
return resolve(yn);
return resolve(yn, getBrowserAnalyticsObject('User Interaction'));
};
}
);
......@@ -271,13 +156,10 @@ export const updateApplyToAll = createAction(
export const toggleGenomeSelector = createAction(
'toggle-genome-selector',
(resolve) => {
return (genomeSelectorActive: boolean) => resolve(genomeSelectorActive);
}
);
export const selectBrowserTab = createAction(
'select-browser-tab',
(resolve) => {
return (selectedBrowserTab: TrackType) => resolve(selectedBrowserTab);
return (genomeSelectorActive: boolean) =>
resolve(
genomeSelectorActive,
getBrowserAnalyticsObject('User Interaction')
);
}
);
import { combineReducers } from 'redux';
import { ActionType, getType } from 'typesafe-actions';
import { RootAction } from 'src/objects';
import * as browserActions from './browserActions';
import * as drawerActions from './drawer/drawerActions';
import * as trackPanelActions from './track-panel/trackPanelActions';
import {
BrowserState,
defaultBrowserState,
ExampleObjectState,
defaultExampleObjectState,
ObjectState,
defaultObjectState,
BrowserOpenState,
TrackPanelState,
defaultTrackPanelState,
DrawerState,
defaultDrawerState,
BrowserLocationState,
defaultBrowserLocationState,
BrowserNavState,
......@@ -24,19 +19,19 @@ import {
function browserInfo(
state: BrowserState = defaultBrowserState,
action: ActionType<typeof browserActions>
action: ActionType<RootAction>
): BrowserState {
switch (action.type) {
case getType(browserActions.updateBrowserActivated):
return { ...state, browserActivated: action.payload };
case getType(browserActions.toggleTrackPanel):
case getType(trackPanelActions.toggleTrackPanel):
return {
...state,
browserOpenState: action.payload
? BrowserOpenState.SEMI_EXPANDED
: BrowserOpenState.EXPANDED
};
case getType(browserActions.toggleDrawer):
case getType(drawerActions.toggleDrawer):
return {
...state,
browserOpenState: action.payload
......@@ -116,135 +111,9 @@ export function trackConfig(
}
}
export function trackPanel(
state: TrackPanelState = defaultTrackPanelState,
action: ActionType<typeof browserActions>
): TrackPanelState {
switch (action.type) {
case getType(browserActions.toggleTrackPanel):
const trackPanelOpened =
action.payload === undefined ? !state.trackPanelOpened : action.payload;
return {
...state,
trackPanelOpened
};
case getType(browserActions.openTrackPanelModal):
return {
...state,
trackPanelModalOpened: true,
trackPanelModalView: action.payload
};