Unverified Commit b15f42c7 authored by Jyothish's avatar Jyothish Committed by GitHub
Browse files

Genome browser link component for entity viewer (#249)



* [ENSWEBSITES-491] genome browser link component for entity viewer

* Create ViewInApp shared component and replace all traces of app links created

* Add onClick and remove Link; 

* Add proper tests
Co-authored-by: Andrey Azov's avatarAndrey Azov <andrey@ebi.ac.uk>
parent fbac22e8
Pipeline #69280 passed with stages
in 6 minutes and 28 seconds
......@@ -11,11 +11,8 @@ import {
isFocusObjectPositionDefault
} from '../browserSelectors';
import ImageButton from 'src/shared/components/image-button/ImageButton';
import { ToggleButton as ToolboxToggleButton } from 'src/shared/components/toolbox';
import { ReactComponent as BrowserIcon } from 'static/img/launchbar/browser.svg';
import { ReactComponent as EntityViewerIcon } from 'static/img/launchbar/entity-viewer.svg';
import ViewInApp from 'src/shared/components/view-in-app/ViewInApp';
import { RootState } from 'src/store';
......@@ -54,21 +51,21 @@ const ZmenuAppLinks = (props: Props) => {
const shouldShowBrowserButton =
props.featureId !== props.activeFeatureId || !props.isInDefaultPosition;
type linkType = {
genomeBrowser?: string;
entityViewer?: string;
};
const links: Partial<linkType> = {};
if (shouldShowBrowserButton) {
links['genomeBrowser'] = getBrowserLink();
}
links['entityViewer'] = getEntityViewerLink();
return (
<div className={styles.zmenuAppLinks}>
<span>View in</span>
{shouldShowBrowserButton && (
<ImageButton
className={styles.zmenuAppButton}
image={BrowserIcon}
onClick={() => props.push(getBrowserLink())}
/>
)}
<ImageButton
className={styles.zmenuAppButton}
image={EntityViewerIcon}
onClick={() => props.push(getEntityViewerLink())}
/>
<ViewInApp links={links} />
<ToolboxToggleButton
className={styles.zmenuToggleFooter}
openElement={<span>Download</span>}
......
......@@ -17,6 +17,9 @@
.viewInLinks {
grid-area: 1 / 3 / 2 / 4;
display: grid;
grid-template-rows: 1fr 1fr;
align-items: end;
}
.geneViewTabs {
......
......@@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { connect } from 'react-redux';
import { useQuery } from '@apollo/react-hooks';
import { gql } from 'apollo-boost';
import { useParams } from 'react-router-dom';
import { getEntityViewerActiveEnsObject } from 'src/content/app/entity-viewer/state/general/entityViewerGeneralSelectors';
import { getEntityViewerActiveGeneTab } from 'src/content/app/entity-viewer/state/gene-view/entityViewerGeneViewSelectors';
......@@ -11,6 +12,8 @@ import DefaultTranscriptslist from './components/default-transcripts-list/Defaul
import GeneViewTabs from './components/gene-view-tabs/GeneViewTabs';
import GeneFunction from 'src/content/app/entity-viewer/gene-view/components/gene-function/GeneFunction';
import GeneRelationships from 'src/content/app/entity-viewer/gene-view/components/gene-relationships/GeneRelationships';
import ViewInApp from 'src/shared/components/view-in-app/ViewInApp';
import * as urlFor from 'src/shared/helpers/urlHelper';
import { Gene } from 'src/content/app/entity-viewer/types/gene';
import { TicksAndScale } from 'src/content/app/entity-viewer/gene-view/components/base-pairs-ruler/BasePairsRuler';
......@@ -102,6 +105,10 @@ const GeneViewWithData = (props: GeneViewWithDataProps) => {
setBasePairsRulerTicks
] = useState<TicksAndScale | null>(null);
const params: { [key: string]: string } = useParams();
const { genomeId, entityId } = params;
const gbUrl = urlFor.browser({ genomeId, focus: entityId });
return (
<div className={styles.geneView}>
<div className={styles.featureImage}>
......@@ -110,7 +117,9 @@ const GeneViewWithData = (props: GeneViewWithDataProps) => {
onTicksCalculated={setBasePairsRulerTicks}
/>
</div>
<div className={styles.viewInLinks}>View in GB</div>
<div className={styles.viewInLinks}>
<ViewInApp links={{ genomeBrowser: gbUrl }} />
</div>
<div className={styles.geneViewTabs}>
<GeneViewTabs />
......
......@@ -16,9 +16,8 @@ import {
} from 'src/content/home/homePageSelectors';
import ImageButton from 'src/shared/components/image-button/ImageButton';
import ViewInApp from 'src/shared/components/view-in-app/ViewInApp';
import { ReactComponent as BrowserIcon } from 'static/img/launchbar/browser.svg';
import { ReactComponent as EntityViewerIcon } from 'static/img/launchbar/entity-viewer.svg';
import { ReactComponent as HomeIcon } from 'static/img/header/home.svg';
import { RootState } from 'src/store';
......@@ -109,27 +108,12 @@ const HomepageAppLinksRow = (props: HomepageAppLinksRowProps) => {
/>
</div>
<div className={styles.homepageAppLinkButtons}>
<span className={styles.viewIn}>View in</span>
<Link className={styles.homepageAppLink} to={urlFor.browser()}>
<ImageButton
statusClasses={{
[Status.DEFAULT]: styles.homepageAppLinkButton
}}
status={Status.DEFAULT}
description="Genome browser"
image={BrowserIcon}
/>
</Link>
<Link className={styles.homepageAppLink} to={urlFor.entityViewer()}>
<ImageButton
statusClasses={{
[Status.DEFAULT]: styles.homepageAppLinkButton
}}
status={Status.DEFAULT}
description="Entity viewer"
image={EntityViewerIcon}
/>
</Link>
<ViewInApp
links={{
genomeBrowser: urlFor.browser(),
entityViewer: urlFor.entityViewer()
}}
/>
</div>
</div>
) : (
......
@import 'src/styles/common';
.viewInLabel {
color: $grey;
width: 50px;
}
.viewInAppLinkButtons {
display: flex;
align-items: center;
}
.viewInAppLink {
margin-left: 1.1em;
&:first-of-type {
margin-left: 0.6em;
}
svg {
width: 25px;
height: 25px;
fill: $white;
background-color: $blue;
}
}
import React from 'react';
import faker from 'faker';
import { mount } from 'enzyme';
import sampleSize from 'lodash/sampleSize';
import {
ViewInApp,
AppButton,
AppName,
Apps,
ViewInAppProps
} from './ViewInApp';
import ImageButton from 'src/shared/components/image-button/ImageButton';
const push = jest.fn();
const renderComponent = (props: Partial<ViewInAppProps>) => {
const defaultProps = { links: {}, push };
const completeProps = {
...defaultProps,
...props
};
const renderedComponent = <ViewInApp {...completeProps} />;
return mount(renderedComponent);
};
const appLinkTuples = Object.keys(Apps).map(
(appName) => [appName as AppName, faker.internet.url()] as const
);
const randomSampleSize = Math.ceil(Math.random() * appLinkTuples.length);
const tuplesSample = sampleSize(appLinkTuples, randomSampleSize);
const links = tuplesSample.reduce((result, tuple) => {
const [appName, link] = tuple;
return {
...result,
[appName]: link
};
}, {}) as Record<AppName, string>;
describe('<ViewInApp />', () => {
afterEach(() => {
jest.resetAllMocks();
});
it('returns null if the links prop is empty', () => {
const wrapper = renderComponent({ links: {} });
expect(wrapper.html()).toBe(null);
});
it('renders correct number of buttons', () => {
const wrapper = renderComponent({ links });
expect(wrapper.find(ImageButton)).toHaveLength(Object.keys(links).length);
});
it('switches to correct url when clicked', () => {
const wrapper = renderComponent({ links });
tuplesSample.forEach(([appName, link]) => {
const imageButton = wrapper
.find(AppButton)
.findWhere((element) => element.prop('appId') === appName)
.find(ImageButton);
imageButton.simulate('click');
expect(push).toHaveBeenCalledWith(link);
});
});
});
import React from 'react';
import { connect } from 'react-redux';
import { push, Push } from 'connected-react-router';
import { ImageButton } from 'src/shared/components/image-button/ImageButton';
import { ReactComponent as BrowserIcon } from 'static/img/launchbar/browser.svg';
import { ReactComponent as EntityViewerIcon } from 'static/img/launchbar/entity-viewer.svg';
import { Status } from 'src/shared/types/status';
import styles from './ViewInApp.scss';
export const Apps = {
genomeBrowser: {
tooltip: 'Genome Browser',
icon: BrowserIcon
},
entityViewer: {
tooltip: 'Entity Viewer',
icon: EntityViewerIcon
}
};
export type AppName = keyof typeof Apps;
export type urlObj = Record<AppName, string>;
export type ViewInAppProps = {
links: Partial<urlObj>;
push: Push;
};
export const ViewInApp = (props: ViewInAppProps) => {
if (Object.keys(props.links).length === 0) {
return null;
}
return Object.keys(props.links) ? (
<div className={styles.viewInAppLinkButtons}>
<span className={styles.viewInLabel}>View in</span>
{(Object.keys(props.links) as AppName[]).map((appId) => {
return (
<AppButton
key={appId}
appId={appId}
url={props.links[appId] as string}
push={props.push}
/>
);
})}
</div>
) : null;
};
type AppButtonProps = {
appId: AppName;
url: string;
push: Push;
};
export const AppButton = (props: AppButtonProps) => {
const handleClick = () => {
props.push(props.url);
};
return (
<div className={styles.viewInAppLink}>
<ImageButton
status={Status.DEFAULT}
description={Apps[props.appId].tooltip}
image={Apps[props.appId].icon}
onClick={handleClick}
/>
</div>
);
};
const mapDispatchToProps = {
push
};
export default connect(null, mapDispatchToProps)(ViewInApp);
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment