Skip to content

Commit

Permalink
Clinical search continued (#56)
Browse files Browse the repository at this point in the history
* fix: styling and settings

* feat: remove DatasetIdSelect

previously needed to keep track of the datasets searched between pages. Not needed now since we want a list of patient ID instead

* feat: remove Reference Genome and VariantSets/VCFs

no longer needed

* feat: remove setDatasetId and searchVarientSets

no longer needed

* feat: remove selected for variant

* feat: add reference genome dropdownbox

* WIP: mock api for variant search

* feat: IG Viewer

add react-new-window plugin
IGV working

* Update IGViewer with alert

* feat: CramVcfInstance added

* WIP: varirant search

* WIP: variant search

- add package for new-window
- dummy data for fetchFederationClinicalData and searchVariant  in constant.js
- api calling fake dummy data
- Variant Search and Variant table working

* fix: variant search with Tyk

Variant Search now using Tyk auth correctly to query data

* Cancer type table

Signed-off-by: Courtney Gosselin <[email protected]>

* Merge clinical and genomic search

Signed-off-by: Courtney Gosselin <[email protected]>

* fix env

Signed-off-by: Courtney Gosselin <[email protected]>

* Take Son dummy data

Signed-off-by: Courtney Gosselin <[email protected]>

* Fix filtering error

Signed-off-by: Courtney Gosselin <[email protected]>

* Remove fake data

Signed-off-by: Courtney Gosselin <[email protected]>

* Remove federation/ from api calls and dummy data

Signed-off-by: Courtney Gosselin <[email protected]>

Signed-off-by: Courtney Gosselin <[email protected]>
Co-authored-by: Son Chau <[email protected]>
Co-authored-by: Courtney Gosselin <[email protected]>
  • Loading branch information
3 people authored Oct 27, 2022
1 parent fa5d6ac commit 085f60a
Show file tree
Hide file tree
Showing 13 changed files with 735 additions and 317 deletions.
2 changes: 1 addition & 1 deletion .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ REACT_APP_FEDERATION_API_SERVER=''
REACT_APP_BASE_NAME=''
REACT_APP_SITE_LOCATION = ''
REACT_APP_HTSGET_SERVER = ''
GENERATE_SOURCEMAP=false
GENERATE_SOURCEMAP=false
337 changes: 312 additions & 25 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"history": "^5.0.0",
"material-ui-popup-state": "^1.8.0",
"npm": "^8.6.0",
"papaparse": "^5.3.2",
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-apexcharts": "^1.3.7",
Expand All @@ -40,6 +41,7 @@
"react-json-view": "^1.21.3",
"react-loader-spinner": "^6.0.0-0",
"react-multi-select-component": "^4.2.3",
"react-new-window": "^0.2.2",
"react-notification-alert": "0.0.13",
"react-perfect-scrollbar": "^1.5.8",
"react-promise-tracker": "^2.1.0",
Expand Down
25 changes: 15 additions & 10 deletions src/store/api.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { sampleSearchVariantResult, sampleFederationResponse } from './constant';

// API Server constant
/* eslint-disable camelcase */
export const katsu = process.env.REACT_APP_KATSU_API_SERVER;
export const federation = process.env.REACT_APP_FEDERATION_API_SERVER;
export const BASE_URL = process.env.REACT_APP_CANDIG_SERVER;
export const htsget = process.env.REACT_APP_HTSGET_SERVER;
export const TYK_URL = process.env.REACT_APP_TYK_SERVER;

// API Calls
/*
Expand All @@ -23,7 +26,7 @@ export function fetchKatsu(URL) {
Fetch the federation service
*/
function fetchFederationStat() {
return fetch(`${federation}/federation/search`, {
return fetch(`${federation}/search`, {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
Expand All @@ -49,7 +52,7 @@ function fetchFederationStat() {
Fetch the federation service for clinical search data
*/
export function fetchFederationClinicalData() {
return fetch(`${federation}/federation/search`, {
return fetch(`${federation}/search`, {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
Expand Down Expand Up @@ -89,7 +92,7 @@ export function fetchClinicalData(URL) {
Fetch peer servers from CanDIG federation service
*/
function fetchServers() {
return fetch(`${federation}/federation/servers`, {}).then((response) => {
return fetch(`${federation}/servers`, {}).then((response) => {
if (response.ok) {
return response.json();
}
Expand Down Expand Up @@ -174,20 +177,22 @@ function getCounts(datasetId, table, field) {

/*
Fetch variant for a specific Dataset Id; start; and reference name; and returns a promise
* @param {string}... Dataset ID
* @param {number}... Start
* @param {number}... End
* @param {string}... Reference name
*/
function searchVariant(datasetId, start, end, referenceName) {
return fetch(`${BASE_URL}/variants/search`, {
function searchVariant(chromosome, start, end) {
return fetch(`${htsget}/htsget/v1/variants/search`, {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
start,
end,
referenceName,
datasetId
regions: [
{
referenceName: chromosome,
start: parseInt(start, 10),
end: parseInt(end, 10)
}
]
})
}).then((response) => {
if (response.ok) {
Expand Down
6 changes: 0 additions & 6 deletions src/store/constant.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ export const SITE = process.env.REACT_APP_SITE_LOCATION;
// API URL where the Dashboard get all the data
export const BASE_URL = process.env.REACT_APP_CANDIG_SERVER;

export const cancerType = {
'C56.9': 'Ovary',
'C50.9': 'Breast',
'C25.9': 'Pancreas'
};

export const CLIN_METADATA = [
'patients',
'samples',
Expand Down
11 changes: 9 additions & 2 deletions src/store/customizationReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,15 @@ const customizationReducer = (state = initialState, action) => {
default:
return {
...state,
selectedClinicalSearchResults: {},
clinicalSearch: {},
clinicalSearch: {
clinicalSearchDropDowns: {
selectedMedications: 'All',
selectedConditions: 'All',
selectedSex: 'All',
selectedCancerType: 'All'
},
selectedClinicalSearchResults: {}
},
selectedDataset: '',
datasets: {},
update: {
Expand Down
36 changes: 25 additions & 11 deletions src/store/mcode.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable camelcase */
import { cancerType } from './constant';
import cancerTypeCSV from '../assets/data_files/cancer_histological_codes_labels.csv';
import papa from 'papaparse';

export const subjectColumns = [
{
Expand Down Expand Up @@ -198,7 +199,6 @@ export const processProceduresData = (dataObject) => {
*/
export const processMedicationStatementData = (dataObject) => {
const rows = [];
console.log('Process Meidcation', dataObject);

// eslint-disable-next-line camelcase
const row = {};
Expand Down Expand Up @@ -286,15 +286,29 @@ export const processCondtionsListData = (dataObject) => {
*/
export const processCancerTypeListData = (dataObject) => {
const list = {};
dataObject.forEach((federatedResult) => {
federatedResult.results.forEach((patient) => {
const key = patient?.cancer_condition?.code?.id;
if (!(key in list)) {
list[key] = cancerType[patient?.cancer_condition?.code?.id] ? cancerType[patient?.cancer_condition?.code?.id] : 'NA';
// list[key] = patient?.code?.label;
}
});
// Parsing CancerType CSV into Dictionary
papa.parse(cancerTypeCSV, {
header: true,
download: true,
skipEmptyLines: true,
// eslint-disable-next-line
complete: function (results) {
const cancerType = results.data;
dataObject.forEach((federatedResult) => {
federatedResult.results.forEach((patient) => {
const key = patient?.cancer_condition?.code?.id;
if (!(key in list)) {
for (let i = 0; i < cancerType.length; i += 1) {
if (key === cancerType[i]['Cancer type code']) {
list[key] = `${cancerType[i]['Cancer type label']} ${cancerType[i]['Cancer type code']}`;
// list[key] = patient?.code?.label;
}
}
}
});
});
list.ALL = 'All';
}
});
list.ALL = 'All';
return list;
};
1 change: 1 addition & 0 deletions src/ui-component/DropDown.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Grid, Box } from '@mui/material';
const useStyles = makeStyles({
dropdownItem: {
background: 'white',
textAlign: 'left',
paddingRight: '1.25em',
paddingLeft: '1.25em',
border: 'none',
Expand Down
32 changes: 32 additions & 0 deletions src/ui-component/IGV/CramVcfInstance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useRef, useEffect } from 'react';
import PropTypes from 'prop-types';

// TODO: Importing from igv.esm.min.js is not working
import igv from '@candig/igv/dist/igv.esm';

function CramVcfInstance({ options }) {
/** *
* A functional component that returns an IGV.js instance dedicated to rendering VCF files.
*/
const igvBrowser = useRef(null);

useEffect(() => {
igv.removeAllBrowsers(); // Remove existing browser instances

if (options.tracks.length > 0) {
igv.createBrowser(igvBrowser.current, options);
}
}, [options]);

return (
<>
<div className="ml-auto mr-auto" style={{ background: 'white', marginTop: '15px' }} ref={igvBrowser} />
</>
);
}

CramVcfInstance.propTypes = {
options: PropTypes.object.isRequired
};

export default CramVcfInstance;
113 changes: 20 additions & 93 deletions src/ui-component/Tables/VariantsTable.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState, useCallback, useRef } from 'react';
import PropTypes from 'prop-types';
import { AgGridReact } from 'ag-grid-react';

Expand All @@ -8,95 +9,24 @@ import 'ag-grid-community/dist/styles/ag-grid.css';
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';
import 'assets/css/VariantsSearch.css';

function VariantsTable({ rowData, datasetId }) {
function VariantsTable({ rowData, onChange }) {
let gridOptions = {};

function getColumnDefs() {
const columnDefs = [
{ headerName: 'Chromosome', field: 'referenceName' },
{ headerName: 'Start', field: 'start' },
{ headerName: 'End', field: 'end' },
{ headerName: 'Reference Bases', field: 'referenceBases' },
{ headerName: 'Alternate Bases', field: 'alternateBases' }
];

if (rowData[0] !== undefined) {
// First population is empty
// Check if the first element contains attributes
if (Object.prototype.hasOwnProperty.call(rowData[0], 'attributes')) {
const { attr } = rowData[0].attributes;
Object.keys(attr).forEach((key) => {
columnDefs.push({
headerName: key,
valueFormatter: (params) => {
try {
let attributeValue;

if (params.value.values[0].stringValue !== undefined) {
attributeValue = params.value.values[0].stringValue;
} else if (params.value.values[0].doubleValue !== undefined) {
attributeValue = params.value.values[0].doubleValue;
} else if (params.value.values[0].int32Value !== undefined) {
attributeValue = params.value.values[0].int32Value;
}

return attributeValue;
} catch (error) {
// console.log(error);
/*
* This is to handle the case where the attribute value is not available in the select row
*/
return 'N/A';
}
},
field: `attributes.attr.${key}`, // Allows us to work with key without it retroactively changing to the last key
editable: true
});
});
}
}

return columnDefs;
}
const [columnDefs] = useState([
{ field: 'Patient ID' },
{ field: 'Genomic Sample ID' },
{ field: 'Number of Variants' },
{ field: 'VCF File' },
{ field: 'Select file(s)', headerCheckboxSelection: true, checkboxSelection: true, showDisabledCheckboxes: true }
]);
// parse rowData contains id, reference_genome, htsget, samples, variantcount to fit the table
const displayRowData = rowData.map((row) => {
const { patientId, genomicSampleId, variantCount, VCFFile } = row;
return { 'Patient ID': patientId, 'Genomic Sample ID': genomicSampleId, 'Number of Variants': variantCount, 'VCF File': VCFFile };
});

function onSelectionChanged() {
const selectedRows = gridOptions.api.getSelectedRows();

fetch(`${BASE_URL}/search`, {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
datasetId: gridOptions.context.datasetId,
logic: {
id: 'A'
},
components: [
{
id: 'A',
variants: {
start: selectedRows[0].start,
end: selectedRows[0].end,
referenceName: selectedRows[0].referenceName
}
}
],
results: [
{
table: 'patients',
fields: ['patientId']
}
]
})
})
.then((response) => response.json())
.then((data) => {
if (data.results.patients.length === 0) {
throw new Error('The variant you selected does not have associated individuals.');
}
})
.catch((err) => {
console.log(err);
});
onChange(selectedRows);
}

gridOptions = {
Expand All @@ -106,27 +36,24 @@ function VariantsTable({ rowData, datasetId }) {
resizable: true,
filter: true,
flex: 1,
minWidth: 150,
minWidth: 100,
minHeight: 300
},
onSelectionChanged,
rowSelection: 'single',
rowSelection: 'multiple',
rowData: null,
rowGroupPanelShow: 'always',
pivotPanelShow: 'always',
enableRangeSelection: true,
paginationAutoPageSize: true,
pagination: true,
valueCache: true,
frameworkComponents: {
VariantsTableButton
}
valueCache: true
};

return (
<>
<div className="ag-theme-alpine">
<AgGridReact columnDefs={getColumnDefs()} rowData={rowData} gridOptions={gridOptions} context={{ datasetId }} />
<AgGridReact columnDefs={columnDefs} rowData={displayRowData} gridOptions={gridOptions} />
</div>

<br />
Expand All @@ -136,7 +63,7 @@ function VariantsTable({ rowData, datasetId }) {

VariantsTable.propTypes = {
rowData: PropTypes.arrayOf(PropTypes.object).isRequired,
datasetId: PropTypes.string.isRequired
onChange: PropTypes.func.isRequired
};

export default VariantsTable;
Loading

0 comments on commit 085f60a

Please sign in to comment.