Skip to content

Commit 7ad6777

Browse files
committed
Write backend and frontend for variant recommendation page
1 parent 634ebb3 commit 7ad6777

File tree

19 files changed

+964
-5
lines changed

19 files changed

+964
-5
lines changed

src/main/java/org/mskcc/oncokb/curation/config/Constants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,7 @@ public final class Constants {
2222

2323
public static final String DEFAULT_GENE_SYNONMN_SOURCE = "cBioPortal";
2424

25+
public static final String ONCOKB_S3_BUCKET = "oncokb-bucket";
26+
2527
private Constants() {}
2628
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,22 @@ public class ApplicationProperties extends org.mskcc.oncokb.meta.model.applicati
1919

2020
private FirebaseProperties firebase;
2121

22+
private AwsProperties aws;
23+
2224
private String oncokbDataRepoDir;
2325

2426
private OncoKbCoreConfig oncokbCore;
2527

2628
private String nihEutilsToken;
2729

30+
public AwsProperties getAws() {
31+
return aws;
32+
}
33+
34+
public void setAws(AwsProperties aws) {
35+
this.aws = aws;
36+
}
37+
2838
public OncoKbCoreConfig getOncokbCore() {
2939
return oncokbCore;
3040
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.mskcc.oncokb.curation.config.application;
2+
3+
public class AwsProperties {
4+
5+
// Copy from oncoKb-Public
6+
private String accessKeyId;
7+
private String secretAccessKey;
8+
private String region;
9+
10+
public String getAccessKeyId() {
11+
return this.accessKeyId;
12+
}
13+
14+
public void setAccessKeyId(String accessKeyId) {
15+
this.accessKeyId = accessKeyId;
16+
}
17+
18+
public String getSecretAccessKey() {
19+
return this.secretAccessKey;
20+
}
21+
22+
public void setSecretAccessKey(String secretAccessKey) {
23+
this.secretAccessKey = secretAccessKey;
24+
}
25+
26+
public String getRegion() {
27+
return this.region;
28+
}
29+
30+
public void setRegion(String region) {
31+
this.region = region;
32+
}
33+
}

src/main/java/org/mskcc/oncokb/curation/config/security/SecurityConfigurationOAuth.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ public void configure(HttpSecurity http) throws Exception {
9191
.antMatchers("/api/authenticate").permitAll()
9292
.antMatchers("/api/auth-info").permitAll()
9393
.antMatchers("/api/logout").permitAll()
94+
.antMatchers("/api/variant-recommendation/**").permitAll()
9495
.antMatchers("/api/admin/**").hasAuthority(AuthoritiesConstants.ADMIN)
9596
.antMatchers("/api/audit/**").hasAuthority(AuthoritiesConstants.ADMIN)
9697
.antMatchers("/api/account/firebase-token").hasAnyAuthority(AuthoritiesConstants.CURATOR)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.mskcc.oncokb.curation.service;
2+
3+
import java.util.Optional;
4+
import org.mskcc.oncokb.curation.config.application.ApplicationProperties;
5+
import org.mskcc.oncokb.curation.config.application.AwsProperties;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
import org.springframework.stereotype.Service;
9+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
10+
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
11+
import software.amazon.awssdk.core.ResponseInputStream;
12+
import software.amazon.awssdk.core.sync.ResponseTransformer;
13+
import software.amazon.awssdk.regions.Region;
14+
import software.amazon.awssdk.services.s3.S3Client;
15+
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
16+
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
17+
18+
@Service
19+
public class S3Service {
20+
21+
private final Logger log = LoggerFactory.getLogger(S3Service.class);
22+
private final ApplicationProperties applicationProperties;
23+
private S3Client s3Client;
24+
25+
public S3Service(ApplicationProperties applicationProperties) {
26+
this.applicationProperties = applicationProperties;
27+
AwsProperties awsProperties = applicationProperties.getAws();
28+
if (awsProperties != null) {
29+
String accessKeyId = awsProperties.getAccessKeyId();
30+
String secretAccessKey = awsProperties.getSecretAccessKey();
31+
String region = awsProperties.getRegion();
32+
AwsBasicCredentials awsCreds = AwsBasicCredentials.create(accessKeyId, secretAccessKey);
33+
s3Client = S3Client.builder().region(Region.of(region)).credentialsProvider(StaticCredentialsProvider.create(awsCreds)).build();
34+
log.info("S3 Client successfully initialized");
35+
} else {
36+
log.error("AWS credentials not configured properly");
37+
}
38+
}
39+
40+
/**
41+
* Get an object from aws s3
42+
* @param bucket s3 bucket name
43+
* @param objectPath the path of the object
44+
* @return a S3 object
45+
*/
46+
public Optional<ResponseInputStream<GetObjectResponse>> getObject(String bucket, String objectPath) {
47+
try {
48+
ResponseInputStream<GetObjectResponse> s3object = s3Client.getObject(
49+
GetObjectRequest.builder().bucket(bucket).key(objectPath).build(),
50+
ResponseTransformer.toInputStream()
51+
);
52+
return Optional.of(s3object);
53+
} catch (Exception e) {
54+
log.error(e.getMessage(), e);
55+
return Optional.empty();
56+
}
57+
}
58+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.mskcc.oncokb.curation.web.rest;
2+
3+
import java.io.BufferedReader;
4+
import java.io.IOException;
5+
import java.io.InputStreamReader;
6+
import java.nio.charset.StandardCharsets;
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import java.util.Optional;
10+
import org.json.simple.JSONObject;
11+
import org.mskcc.oncokb.curation.config.Constants;
12+
import org.mskcc.oncokb.curation.service.S3Service;
13+
import org.mskcc.oncokb.curation.web.rest.errors.BadRequestAlertException;
14+
import org.mskcc.oncokb.curation.web.rest.errors.ResourceNotFoundException;
15+
import org.slf4j.Logger;
16+
import org.slf4j.LoggerFactory;
17+
import org.springframework.beans.factory.annotation.Autowired;
18+
import org.springframework.http.ResponseEntity;
19+
import org.springframework.web.bind.annotation.GetMapping;
20+
import org.springframework.web.bind.annotation.PathVariable;
21+
import org.springframework.web.bind.annotation.RequestMapping;
22+
import org.springframework.web.bind.annotation.RestController;
23+
import software.amazon.awssdk.core.ResponseInputStream;
24+
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
25+
26+
@RestController
27+
@RequestMapping("/api")
28+
public class VariantRecommendController {
29+
30+
private final Logger log = LoggerFactory.getLogger(VariantRecommendController.class);
31+
32+
private static final String ENTITY_NAME = "variant-recommendation";
33+
34+
@Autowired
35+
private S3Service s3Service;
36+
37+
@GetMapping("/variant-recommendation/{file}")
38+
public ResponseEntity<List<JSONObject>> requestData(@PathVariable String file) throws IOException {
39+
Optional<ResponseInputStream<GetObjectResponse>> s3object = s3Service.getObject(Constants.ONCOKB_S3_BUCKET, file);
40+
if (s3object.isPresent()) {
41+
BufferedReader reader = new BufferedReader(new InputStreamReader(s3object.get(), StandardCharsets.UTF_8));
42+
String headerLine = reader.readLine();
43+
if (headerLine == null) {
44+
throw new BadRequestAlertException("File is empty", ENTITY_NAME, "fileempty");
45+
}
46+
String[] headers = headerLine.split("\t");
47+
List<JSONObject> jsonList = new ArrayList<>();
48+
String line;
49+
while ((line = reader.readLine()) != null) {
50+
String[] data = line.split("\t");
51+
JSONObject jsonObject = new JSONObject();
52+
for (int i = 0; i < headers.length; i++) {
53+
jsonObject.put(headers[i], "n/a".equals(data[i]) ? null : data[i]);
54+
}
55+
jsonList.add(jsonObject);
56+
}
57+
58+
return ResponseEntity.ok(jsonList);
59+
} else {
60+
throw new ResourceNotFoundException();
61+
}
62+
}
63+
}

src/main/java/org/mskcc/oncokb/curation/web/rest/errors/ErrorConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public final class ErrorConstants {
1212
public static final URI USER_NOT_APPROVED = URI.create(PROBLEM_BASE_URL + "user-not-approved");
1313
public static final URI EMAIL_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/email-already-used");
1414
public static final URI LOGIN_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/login-already-used");
15+
public static final URI RESOURCE_NOT_FOUND = URI.create(PROBLEM_BASE_URL + "/resource-not-found");
1516

1617
public ErrorConstants() {}
1718
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.mskcc.oncokb.curation.web.rest.errors;
2+
3+
import org.zalando.problem.AbstractThrowableProblem;
4+
import org.zalando.problem.Status;
5+
6+
public class ResourceNotFoundException extends AbstractThrowableProblem {
7+
8+
private static final long serialVersionUID = 1L;
9+
10+
public ResourceNotFoundException() {
11+
super(ErrorConstants.RESOURCE_NOT_FOUND, "Resource Not Found", Status.NOT_FOUND);
12+
}
13+
}

src/main/webapp/app/config/constants/constants.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,8 @@ export enum FEATURE_PAGE_ROUTE {
107107
ACCOUNT = '/account',
108108
OAUTH = '/oauth2/authorization/oidc',
109109
SEARCH = '/search',
110-
/* Below are variant related paths */
110+
/* Below are variant recommendation related paths */
111111
VARIANR_REC = '/variant-recommendation',
112-
VARIANR_MERGE_DATA = '/variant-recommendation/:hugoSymbol}/:variantName',
113112
/* Below are curation related paths */
114113
CURATION = '/curation',
115114
CURATION_GENE = '/curation/:hugoSymbol/somatic',
@@ -427,3 +426,8 @@ export const DEFAULT_TOAST_ERROR_OPTIONS: ToastOptions = {
427426
export const KEYCLOAK_LOGOUT_REDIRECT_PARAM = 'post_logout_redirect_uri';
428427
export const KEYCLOAK_SESSION_TERMINATED_PARAM = 'session_terminated';
429428
export const KEYCLOAK_UNAUTHORIZED_PARAM = 'unauthorized';
429+
430+
/* Files name in S3 bucket */
431+
export const ALL_FREQUENCY_FILE = 'msk_impact_2017_all_frequency.txt';
432+
export const TYPE_FREQUENCY_FILE = 'msk_impact_2017_type_frequency.txt';
433+
export const DETAILED_FREQUENCY_FILE = 'msk_impact_2017_detailed_frequency.txt';
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* eslint-disable no-console */
2+
import React, { useState } from 'react';
3+
import formatPercentage from 'app/pages/variantRecommendation/FormatPercentage';
4+
import classnames from 'classnames';
5+
import * as styles from './styles.module.scss';
6+
7+
interface RangeSliderProps {
8+
min: number;
9+
max: number;
10+
range: [number, number];
11+
step: number;
12+
onChange: (newRange: [number, number]) => void;
13+
}
14+
15+
export const TwoRangeSlider = ({ min, max, range, step, onChange }: RangeSliderProps) => {
16+
const [minValue, setMinValue] = useState(range ? range[0] : min);
17+
const [maxValue, setMaxValue] = useState(range ? range[1] : max);
18+
19+
const handleMinChange = e => {
20+
e.preventDefault();
21+
const newMinVal = Math.min(+e.target.value, maxValue);
22+
setMinValue(newMinVal);
23+
onChange([newMinVal, maxValue]);
24+
};
25+
26+
const handleMaxChange = e => {
27+
e.preventDefault();
28+
const newMaxVal = Math.max(+e.target.value, minValue);
29+
setMaxValue(newMaxVal);
30+
onChange([minValue, newMaxVal]);
31+
};
32+
33+
const minPos = ((minValue - min) / ((max ?? 100) - min)) * 100;
34+
const maxPos = ((maxValue - min) / ((max ?? 100) - min)) * 100;
35+
36+
return (
37+
<div className={styles.wrapper}>
38+
<div className={styles['range-slider']}>
39+
<div className={styles['range-labels']}>
40+
<span className={classnames(styles['range-label'], styles['range-label-start'])}>{formatPercentage(minValue)}</span>
41+
<span className={classnames(styles['range-label'], styles['range-label-end'])}>{formatPercentage(maxValue)}</span>
42+
</div>
43+
<input type="range" value={minValue} min={min} max={max} step={step} onChange={handleMinChange} />
44+
<input type="range" value={maxValue} min={min} max={max} step={step} onChange={handleMaxChange} />
45+
<div className={styles['track-wrapper']}>
46+
<div className={styles['track']} />
47+
<div className={styles['range-between']} style={{ left: `${minPos}%`, right: `${100 - maxPos}%` }} />
48+
<div className={styles['control']} style={{ left: `${minPos}%` }} />
49+
<div className={styles['control']} style={{ left: `${maxPos}%` }} />
50+
</div>
51+
</div>
52+
</div>
53+
);
54+
};
55+
56+
export default TwoRangeSlider;

0 commit comments

Comments
 (0)