Skip to content

Commit 067dcd3

Browse files
zhx828jfkonecn
andauthored
Add somatic/germline gene page (#1181)
- Update gene info section - Add external link icon - Add InfoTile & GeneOriginTabs - Add genomic indicators - Add germline core proxy config --------- Co-authored-by: John Konecny <[email protected]>
1 parent 1b362f3 commit 067dcd3

File tree

95 files changed

+3176
-419
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+3176
-419
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
"@types/react-select": "^3.0.21",
102102
"@types/react-spinkit": "^3.0.5",
103103
"@types/reactstrap": "^8.0.4",
104+
"@types/resize-observer-browser": "^0.1.11",
104105
"@types/webpack-env": "1.15.2",
105106
"@typescript-eslint/eslint-plugin": "2.29.0",
106107
"@typescript-eslint/parser": "2.29.0",

src/main/java/org/mskcc/cbio/oncokb/config/application/ApplicationProperties.java

+9
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class ApplicationProperties {
2222
private String name;
2323
private String baseUrl = "";
2424
private String apiProxyUrl;
25+
private String apiProxyGermlineUrl;
2526
private SlackProperties slack;
2627
private ProjectProfile profile;
2728
private Boolean sitemapEnabled;
@@ -62,6 +63,14 @@ public void setApiProxyUrl(String apiProxyUrl) {
6263
this.apiProxyUrl = apiProxyUrl;
6364
}
6465

66+
public String getApiProxyGermlineUrl() {
67+
return apiProxyGermlineUrl;
68+
}
69+
70+
public void setApiProxyGermlineUrl(String apiProxyGermlineUrl) {
71+
this.apiProxyGermlineUrl = apiProxyGermlineUrl;
72+
}
73+
6574
public SlackProperties getSlack() { return slack; }
6675

6776
public void setSlack( SlackProperties slack ) { this.slack = slack; }

src/main/java/org/mskcc/cbio/oncokb/service/ApiProxyService.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.mskcc.cbio.oncokb.service;
22

3+
import org.apache.commons.lang3.StringUtils;
34
import org.mskcc.cbio.oncokb.config.application.ApplicationProperties;
5+
import org.mskcc.cbio.oncokb.security.SecurityUtils;
46
import org.slf4j.Logger;
57
import org.slf4j.LoggerFactory;
68
import org.springframework.beans.factory.annotation.Autowired;
@@ -27,7 +29,13 @@ public class ApiProxyService {
2729

2830
public URI prepareURI(HttpServletRequest request) throws URISyntaxException {
2931
String queryString = request.getQueryString();
30-
return new URI(applicationProperties.getApiProxyUrl() + request.getRequestURI() + (queryString == null ? "" : "?" + queryString));
32+
String defaultApiProxyUrl = applicationProperties.getApiProxyUrl();
33+
String germlineParam = request.getParameter("germline");
34+
if (germlineParam != null && Boolean.TRUE.equals(Boolean.parseBoolean(germlineParam))
35+
&& SecurityUtils.isAuthenticated() && StringUtils.isNotEmpty(applicationProperties.getApiProxyGermlineUrl())) {
36+
defaultApiProxyUrl = applicationProperties.getApiProxyGermlineUrl();
37+
}
38+
return new URI(defaultApiProxyUrl + request.getRequestURI() + (queryString == null ? "" : "?" + queryString));
3139
}
3240

3341
public URI prepareURI(String apiRequest) throws URISyntaxException {

src/main/webapp/app/Main.tsx

+12-11
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ import * as React from 'react';
22
import Footer from './components/Footer';
33
import Header from './components/Header';
44
import { observer } from 'mobx-react';
5-
import AppRouts from 'app/routes/routes';
5+
import AppRoutes from 'app/routes/routes';
66
import { isAuthorized } from 'app/shared/auth/AuthUtils';
7-
import { Container, Row, Col, Modal, Button } from 'react-bootstrap';
87
import { Stores } from 'app/App';
98
import { Prompt, withRouter } from 'react-router';
109
import {
@@ -19,6 +18,7 @@ import { FeedbackModal } from './components/feedback/FeedbackModal';
1918
import { FdaModal } from 'app/components/fdaModal/FdaModal';
2019
import { Location } from 'history';
2120
import autobind from 'autobind-decorator';
21+
import PageContainer from 'app/components/PageContainer';
2222

2323
export type IMainPage = Stores;
2424

@@ -104,15 +104,16 @@ class Main extends React.Component<IMainPage> {
104104
routing={this.props.routing}
105105
appStore={this.props.appStore}
106106
/>
107-
<div className={'view-wrapper'}>
108-
<Container fluid={!this.props.windowStore.isXLscreen}>
109-
<AppRouts
110-
authenticationStore={this.props.authenticationStore}
111-
appStore={this.props.appStore}
112-
routing={this.props.routing}
113-
/>
114-
</Container>
115-
</div>
107+
<PageContainer
108+
routing={this.props.routing}
109+
windowStore={this.props.windowStore}
110+
>
111+
<AppRoutes
112+
authenticationStore={this.props.authenticationStore}
113+
appStore={this.props.appStore}
114+
routing={this.props.routing}
115+
/>
116+
</PageContainer>
116117
<FeedbackModal
117118
showModal={this.props.appStore.showFeedbackFormModal}
118119
feedback={this.feedbackAnnotation}

src/main/webapp/app/components/Footer.module.scss

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
}
1919

2020
.footerAList > a {
21-
color: white;
21+
line-height: 1rem;
2222
border-right: 0.5px solid #fff;
23-
padding: 0 5px;
23+
padding: 0 0.5rem;
2424

2525
&:last-child {
2626
border: 0;

src/main/webapp/app/components/Footer.tsx

+13-22
Original file line numberDiff line numberDiff line change
@@ -32,28 +32,19 @@ class Footer extends React.Component<{ lastDataUpdate: string }> {
3232
<div className={'mb-2'}>
3333
<CitationText highlightLinkout={true} boldLinkout />
3434
</div>
35-
<div className={classnames(styles.footerAList, 'mb-2')}>
36-
<a
37-
href="https://www.mskcc.org"
38-
target="_blank"
39-
rel="noopener noreferrer"
40-
>
41-
MSK <ExternalLinkIcon />
42-
</a>
43-
<a
44-
href="https://www.mskcc.org/research-areas/programs-centers/molecular-oncology"
45-
target="_blank"
46-
rel="noopener noreferrer"
47-
>
48-
CMO <ExternalLinkIcon />
49-
</a>
50-
<a
51-
href="https://www.cbioportal.org"
52-
target="_blank"
53-
rel="noopener noreferrer"
54-
>
55-
cBioPortal <ExternalLinkIcon />
56-
</a>
35+
<div
36+
className={classnames(
37+
styles.footerAList,
38+
'mb-2 d-flex justify-content-center'
39+
)}
40+
>
41+
<ExternalLinkIcon link="https://www.mskcc.org">MSK</ExternalLinkIcon>
42+
<ExternalLinkIcon link="https://www.mskcc.org/research-areas/programs-centers/molecular-oncology">
43+
CMO
44+
</ExternalLinkIcon>
45+
<ExternalLinkIcon link="https://www.cbioportal.org">
46+
cBioPortal
47+
</ExternalLinkIcon>
5748
<OncoTreeLink />
5849
</div>
5950
</>
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,44 @@
1-
import React from 'react';
1+
import React, { FunctionComponent } from 'react';
22
import { Col, Row } from 'react-bootstrap';
3+
import WindowStore from 'app/store/WindowStore';
4+
import { RouterStore } from 'mobx-react-router';
5+
import { PAGE_ROUTE } from 'app/config/constants';
6+
import { GENETIC_TYPE } from 'app/components/geneticTypeTabs/GeneticTypeTabs';
7+
import { parseGenePagePath } from 'app/shared/utils/UrlUtils';
38

9+
const Container: FunctionComponent<{
10+
inGenePage: boolean;
11+
}> = props => {
12+
if (props.inGenePage) {
13+
return <div>{props.children}</div>;
14+
} else {
15+
return (
16+
<Row className={`justify-content-center`}>
17+
<Col md={11}>{props.children}</Col>
18+
</Row>
19+
);
20+
}
21+
};
422
const PageContainer: React.FunctionComponent<{
5-
className?: string;
23+
routing: RouterStore;
24+
windowStore: WindowStore;
625
}> = props => {
26+
const genePagePath = parseGenePagePath(props.routing.location.pathname);
27+
const inGenePage = genePagePath.geneticType !== undefined;
728
return (
8-
<Row className={`justify-content-center ${props.className}`}>
9-
<Col xl={10} lg={11}>
10-
{props.children}
11-
</Col>
12-
</Row>
29+
<div className={'view-wrapper'}>
30+
<div
31+
className={
32+
inGenePage
33+
? ''
34+
: props.windowStore.isXLscreen
35+
? 'container'
36+
: 'container-fluid'
37+
}
38+
>
39+
<Container inGenePage={inGenePage}>{props.children}</Container>
40+
</div>
41+
</div>
1342
);
1443
};
1544
export default PageContainer;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React, { FunctionComponent, useState } from 'react';
2+
import styles from './genetic-type-tabs.module.scss';
3+
import classnames from 'classnames';
4+
import { RouterStore } from 'mobx-react-router';
5+
6+
export enum GENETIC_TYPE {
7+
SOMATIC = 'somatic',
8+
GERMLINE = 'germline',
9+
}
10+
11+
const GeneticTypeTabs: FunctionComponent<{
12+
routing: RouterStore;
13+
hugoSymbol: string;
14+
geneticType?: GENETIC_TYPE;
15+
onChange: (status: string) => void;
16+
}> = props => {
17+
const [selected, setSelected] = useState<GENETIC_TYPE>(
18+
props.geneticType || GENETIC_TYPE.SOMATIC
19+
);
20+
21+
const ontoggle = (status: GENETIC_TYPE) => {
22+
setSelected(status);
23+
props.onChange(status);
24+
};
25+
26+
return (
27+
<div className={styles.tabs}>
28+
{[GENETIC_TYPE.SOMATIC, GENETIC_TYPE.GERMLINE].map((geneOrigin, idx) => (
29+
<div
30+
key={idx}
31+
style={{ width: '50%' }}
32+
className={
33+
selected === geneOrigin
34+
? classnames(styles.tab, styles.selectedTab, 'font-bold')
35+
: classnames(styles.tab, styles.unselectedTab)
36+
}
37+
onClick={() => ontoggle(geneOrigin)}
38+
>
39+
{geneOrigin.substring(0, 1).toUpperCase()}
40+
{geneOrigin.toLowerCase().slice(1)}
41+
</div>
42+
))}
43+
</div>
44+
);
45+
};
46+
47+
export default GeneticTypeTabs;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
@import '../../variables';
2+
3+
.tabs {
4+
display: flex;
5+
margin: 1.5rem 0 0 0;
6+
}
7+
8+
.tab {
9+
text-align: center;
10+
padding: 0.75rem 0;
11+
border: 1px solid $oncokb-darker-blue;
12+
cursor: pointer;
13+
}
14+
15+
.tab:first-child {
16+
border-left: 0;
17+
border-top-right-radius: 3px;
18+
}
19+
20+
.tab:last-child {
21+
border-right: 0;
22+
border-top-left-radius: 3px;
23+
}
24+
25+
.selectedTab {
26+
padding-top: 0.5rem;
27+
border-width: 3px 1px 0 1px;
28+
}
29+
30+
.unselectedTab {
31+
background-color: $inactive;
32+
border-width: 0 0 1px 0;
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React, { FunctionComponent } from 'react';
2+
import { GENETIC_TYPE } from 'app/components/geneticTypeTabs/GeneticTypeTabs';
3+
import classnames from 'classnames';
4+
import styles from './genetic-type-tag.module.scss';
5+
import { capitalize } from 'cbioportal-frontend-commons';
6+
7+
const GeneticTypeTag: FunctionComponent<{
8+
geneticType: GENETIC_TYPE;
9+
className?: string;
10+
}> = props => {
11+
return (
12+
<span
13+
className={classnames(
14+
props.className,
15+
props.geneticType === GENETIC_TYPE.GERMLINE
16+
? styles.germlineTag
17+
: styles.somaticTag
18+
)}
19+
>
20+
{capitalize(props.geneticType)}
21+
</span>
22+
);
23+
};
24+
25+
export default GeneticTypeTag;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
@import '../../variables';
2+
3+
.geneticTypeTag {
4+
font-weight: 500 !important;
5+
font-family: "Gotham Book";
6+
display: inline-flex;
7+
padding: 0 var(--radius-8, 8px);
8+
justify-content: center;
9+
align-items: center;
10+
gap: var(--radius-8, 8px);
11+
border-radius: 56px;
12+
}
13+
14+
.germlineTag {
15+
@extend .geneticTypeTag;
16+
color: $primary;
17+
border: var(--radius-2, 2px) solid var(--Color-11, $primary);
18+
background: #F0F5FF;
19+
}
20+
21+
.somaticTag {
22+
@extend .geneticTypeTag;
23+
color: #FFFFFF;
24+
background: $primary;
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React, { CSSProperties } from 'react';
2+
import styles from './info-tile.module.scss';
3+
import { COLOR_GREY } from 'app/config/theme';
4+
import classnames from 'classnames';
5+
6+
export type Category = {
7+
title: string;
8+
content?: JSX.Element | string;
9+
className?: string;
10+
};
11+
export type InfoTile = {
12+
title: string;
13+
categories: Category[];
14+
className?: string;
15+
};
16+
17+
const Category: React.FunctionComponent<Category> = props => {
18+
const isNa = props.content === undefined || props.content === null;
19+
const isText = isNa || typeof props.content === 'string';
20+
const style: CSSProperties = { height: '3rem' };
21+
if (isText) {
22+
style.fontSize = '1.5rem';
23+
style.fontFamily = 'Gotham Bold';
24+
}
25+
return (
26+
<div className={classnames(props.className, 'flex-grow-1')}>
27+
<div style={{ color: COLOR_GREY }} className={'mb-1'}>
28+
{props.title}
29+
</div>
30+
<div className={classnames('d-flex align-items-center')} style={style}>
31+
{props.content}
32+
</div>
33+
</div>
34+
);
35+
};
36+
37+
const InfoTile: React.FunctionComponent<InfoTile> = props => {
38+
return props.categories.length > 0 ? (
39+
<div className={classnames(styles.tile, 'mr-2', props.className)}>
40+
<div className={'h6 font-bold mb-2'}>{props.title}</div>
41+
<div className={'d-flex'}>
42+
{props.categories.map((category, idx) => (
43+
<Category key={idx} {...category} className={styles.category} />
44+
))}
45+
</div>
46+
</div>
47+
) : (
48+
<></>
49+
);
50+
};
51+
52+
export default InfoTile;

0 commit comments

Comments
 (0)