diff --git a/.env.development b/.env.development index 25fe55b6..f0fc2042 100644 --- a/.env.development +++ b/.env.development @@ -3,4 +3,5 @@ REACT_APP_FEDERATION_API_SERVER='' REACT_APP_BASE_NAME='' REACT_APP_SITE_LOCATION = '' REACT_APP_HTSGET_SERVER = '' -GENERATE_SOURCEMAP=false +REACT_APP_INGEST_SERVER = '' +GENERATE_SOURCEMAP=false \ No newline at end of file diff --git a/.eslintrc b/.eslintrc index 46f55f4c..a294a621 100644 --- a/.eslintrc +++ b/.eslintrc @@ -44,6 +44,9 @@ "no-unused-vars": [ 1, { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_", "ignoreRestSiblings": false } ], diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f44ce857..3f5680fa 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,33 +1,41 @@ ## Ticket(s) +- ## Description +- ## Expected Behaviour +- ## Related Issues (if appropriate) +- ## Screenshots (if appropriate) + ### Before PR ### After PR ## To do/Tickets to be made before merging branch -- + +- ## Types of Change(s) -- [ ] 🪲 Bug fix (non-breaking change that fixes an issue) -- [ ] ✨ New feature (non-breaking change that adds functionality) -- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to change) + +- [ ] 🪲 Bug fix (non-breaking change that fixes an issue) +- [ ] ✨ New feature (non-breaking change that adds functionality) +- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to change) ## Has it been tested for: -- [ ] My change requires a change to the documentation -- [ ] I have updated the documentation accordingly -- [ ] Prettier linter doesn't return errors -- [ ] Production branch PR browser testing: Chrome, Firefox, Edge, etc. -- [ ] Locally tested -- [ ] Dev server tested -- [ ] Production tested when merging into stable/production branch \ No newline at end of file + +- [ ] My change requires a change to the documentation +- [ ] I have updated the documentation accordingly +- [ ] Prettier linter doesn't return errors +- [ ] Production branch PR browser testing: Chrome, Firefox, Edge, etc. +- [ ] Locally tested +- [ ] Dev server tested +- [ ] Production tested when merging into stable/production branch diff --git a/.github/workflows/candig-dispatch.yml b/.github/workflows/candig-dispatch.yml new file mode 100644 index 00000000..a5df4832 --- /dev/null +++ b/.github/workflows/candig-dispatch.yml @@ -0,0 +1,27 @@ +name: Dispatch +on: + push: + branches: [develop] +jobs: + CanDIG-dispatch: + runs-on: ubuntu-latest + env: + PARENT_REPOSITORY: 'CanDIG/CanDIGv2' + CHECKOUT_BRANCH: 'develop' + PR_AGAINST_BRANCH: 'develop' + OWNER: 'CanDIG' + steps: + - name: Check out repository code + uses: actions/checkout@v3 + - name: Create PR in CanDIGv2 + id: make_pr + uses: jman005/github-action-pr-expanded@v0 + with: + github_token: ${{ secrets.SUBMODULE_PR }} + parent_repository: ${{ env.PARENT_REPOSITORY }} + checkout_branch: ${{ env.CHECKOUT_BRANCH}} + pr_against_branch: ${{ env.PR_AGAINST_BRANCH }} + pr_description: 'PR triggered by update to develop branch on ${{ github.repository }}. Commit hash: ${{ github.sha }}' + owner: ${{ env.OWNER }} + submodule_path: lib/candig-data-portal/candig-data-portal + label: Submodule update diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..c2e26e23 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,39 @@ +name: Lint + +on: + # Trigger the workflow on push or pull request, + # but only for the develop branch + push: + branches: + - develop + pull_request: + branches: + - develop + +# Down scope as necessary via https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token +permissions: + checks: write + contents: write + +jobs: + run-linters: + name: Run linters + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v1 + with: + node-version: 12 + + - name: Install Node.js dependencies + run: npm ci + + - name: Run linters + uses: wearerequired/lint-action@v2 + with: + eslint: true + prettier: true diff --git a/.prettierrc b/.prettierrc index 2eeeb745..c6905b15 100644 --- a/.prettierrc +++ b/.prettierrc @@ -5,6 +5,5 @@ "trailingComma": "none", "tabWidth": 4, "useTabs": false, - "endOfLine": "auto", - "camelcase": "off" + "endOfLine": "auto" } diff --git a/PULL_REQUEST_TEMPLATE/stable_pr_template.md b/PULL_REQUEST_TEMPLATE/stable_pr_template.md index 80357313..1fdb79fe 100644 --- a/PULL_REQUEST_TEMPLATE/stable_pr_template.md +++ b/PULL_REQUEST_TEMPLATE/stable_pr_template.md @@ -1,6 +1,7 @@ ## vX.X.X: Description + Add a summary description of the main user-facing changes made in this release, relative to the last stable release. -- [] Tagged as a release in this repo -- [] Passes integration tests on a development instance -- [] Images pushed to Dockerhub +- [ ] Tagged as a release in this repo +- [ ] Passes integration tests on a development instance +- [ ] Images pushed to Dockerhub diff --git a/package-lock.json b/package-lock.json index 1fa64919..22b5f7fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6663,9 +6663,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001343", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001343.tgz", - "integrity": "sha512-8KeCrAtPMabo/XW14B+R9sZYoClx1n0b+WYgwDKZPtWR3TcdvWzdSy7mPyFEmR5WU1St9v1PW6sdO5dkFOEzfA==", + "version": "1.0.30001535", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001535.tgz", + "integrity": "sha512-48jLyUkiWFfhm/afF7cQPqPjaUmSraEhK4j+FCTJpgnGGEZHqyLe3hmWH7lIooZdSzXL0ReMvHz0vKDoTBsrwg==", "funding": [ { "type": "opencollective", @@ -6674,6 +6674,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, @@ -27891,9 +27895,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001343", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001343.tgz", - "integrity": "sha512-8KeCrAtPMabo/XW14B+R9sZYoClx1n0b+WYgwDKZPtWR3TcdvWzdSy7mPyFEmR5WU1St9v1PW6sdO5dkFOEzfA==" + "version": "1.0.30001535", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001535.tgz", + "integrity": "sha512-48jLyUkiWFfhm/afF7cQPqPjaUmSraEhK4j+FCTJpgnGGEZHqyLe3hmWH7lIooZdSzXL0ReMvHz0vKDoTBsrwg==" }, "case-sensitive-paths-webpack-plugin": { "version": "2.4.0", diff --git a/package.json b/package.json index df271a86..96ed34f4 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "candig-data-portal", "version": "0.1.0", "private": true, + "homepage": "/portal", "dependencies": { "@candig/igv": "^2.10.5-c.1", "@emotion/cache": "^11.4.0", @@ -30,7 +31,6 @@ "highcharts": "^10.0.0", "highcharts-react-official": "^3.1.0", "history": "^5.0.0", - "js-cookie": "^3.0.5", "material-ui-popup-state": "^1.8.0", "npm": "^8.6.0", "papaparse": "^5.3.2", @@ -97,4 +97,3 @@ "webpack-cli": "^4.9.2" } } - diff --git a/public/index.html b/public/index.html index 806160a8..d72179fa 100644 --- a/public/index.html +++ b/public/index.html @@ -22,7 +22,7 @@ diff --git a/src/layout/MainLayout/Header/NotificationSection/NotificationList.js b/src/layout/MainLayout/Header/NotificationSection/NotificationList.js index de2983fe..25a29b56 100644 --- a/src/layout/MainLayout/Header/NotificationSection/NotificationList.js +++ b/src/layout/MainLayout/Header/NotificationSection/NotificationList.js @@ -1,5 +1,3 @@ -import React from 'react'; - import { makeStyles } from '@mui/styles'; import { Avatar, diff --git a/src/layout/MainLayout/Header/ProfileSection/UpgradePlanCard.js b/src/layout/MainLayout/Header/ProfileSection/UpgradePlanCard.js index 1dee2001..9484637d 100644 --- a/src/layout/MainLayout/Header/ProfileSection/UpgradePlanCard.js +++ b/src/layout/MainLayout/Header/ProfileSection/UpgradePlanCard.js @@ -1,5 +1,3 @@ -import React from 'react'; - import { makeStyles } from '@mui/styles'; import { Button, Card, CardContent, Grid, Stack, Typography } from '@mui/material'; diff --git a/src/layout/MainLayout/Header/ProfileSection/index.js b/src/layout/MainLayout/Header/ProfileSection/index.js index 2ec415df..98d3345e 100644 --- a/src/layout/MainLayout/Header/ProfileSection/index.js +++ b/src/layout/MainLayout/Header/ProfileSection/index.js @@ -9,11 +9,9 @@ import { ClickAwayListener, Divider, Grid, - InputAdornment, List, ListItemIcon, ListItemText, - OutlinedInput, Paper, Popper, Typography @@ -29,7 +27,7 @@ import Transitions from 'ui-component/extended/Transitions'; import { SITE } from 'store/constant'; // assets -import { IconLogout, IconSearch, IconSettings } from '@tabler/icons'; +import { IconLogout, IconSettings } from '@tabler/icons'; import User1 from 'assets/images/users/user-round.svg'; import BCGSC from 'assets/images/users/bcgsc.svg'; import UHN from 'assets/images/users/UHN.svg'; @@ -77,6 +75,9 @@ const useStyles = makeStyles((theme) => ({ cardContent: { padding: '16px !important' }, + showOnTop: { + zIndex: 11001 + }, card: { backgroundColor: theme.palette.primary.light, marginBottom: '16px', @@ -108,6 +109,9 @@ const useStyles = makeStyles((theme) => ({ badgeWarning: { backgroundColor: theme.palette.warning.dark, color: '#fff' + }, + usernamePadding: { + paddingBottom: '1em' } })); @@ -118,7 +122,6 @@ const ProfileSection = () => { const theme = useTheme(); const customization = useSelector((state) => state.customization); - const [value, setValue] = React.useState(''); const [selectedIndex] = React.useState(1); const [open, setOpen] = React.useState(false); @@ -185,7 +188,7 @@ const ProfileSection = () => { anchorEl={anchorRef.current} role={undefined} transition - disablePortal + className={classes.showOnTop} popperOptions={{ modifiers: [ { @@ -210,26 +213,10 @@ const ProfileSection = () => { First Name Last Name */} - + {SITE} - setValue(e.target.value)} - placeholder="Search profile options" - startAdornment={ - - - - } - aria-describedby="search-helper-text" - inputProps={{ - 'aria-label': 'weight' - }} - /> diff --git a/src/layout/MainLayout/Header/SearchSection/index.js b/src/layout/MainLayout/Header/SearchSection/index.js index e8de0428..2f9eb180 100644 --- a/src/layout/MainLayout/Header/SearchSection/index.js +++ b/src/layout/MainLayout/Header/SearchSection/index.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; // material-ui import { makeStyles } from '@mui/styles'; diff --git a/src/layout/MainLayout/Sidebar/SidebarContext.js b/src/layout/MainLayout/Sidebar/SidebarContext.js index 557b4aca..d87e6d66 100644 --- a/src/layout/MainLayout/Sidebar/SidebarContext.js +++ b/src/layout/MainLayout/Sidebar/SidebarContext.js @@ -1,5 +1,7 @@ import React from 'react'; +import PropTypes from 'prop-types'; + const DEFAULT_STATE = false; const SidebarReaderContext = React.createContext(DEFAULT_STATE); @@ -20,6 +22,11 @@ export function SidebarProvider(props) { ); } +SidebarProvider.propTypes = { + data: PropTypes.object, + setData: PropTypes.func +}; + /** * Obtain the context reader of the federation sites query. * @returns {Object} a React context of the sidebar DOM component diff --git a/src/layout/MainLayout/index.js b/src/layout/MainLayout/index.js index bc5b0a42..7b7ca87e 100644 --- a/src/layout/MainLayout/index.js +++ b/src/layout/MainLayout/index.js @@ -91,8 +91,6 @@ const MainLayout = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [matchDownMd]); - console.log(leftDrawerOpened); - return (
diff --git a/src/layout/NavMotion.js b/src/layout/NavMotion.js index 0daa894c..f513d5f1 100644 --- a/src/layout/NavMotion.js +++ b/src/layout/NavMotion.js @@ -1,5 +1,4 @@ import PropTypes from 'prop-types'; -import React from 'react'; // third-party import { motion } from 'framer-motion'; diff --git a/src/menu-items/index.js b/src/menu-items/index.js index 072bdc66..0b14f72b 100644 --- a/src/menu-items/index.js +++ b/src/menu-items/index.js @@ -1,5 +1,6 @@ import clinicalGenomicSearch from './clinicalGenomicSearch'; import summary from './summary'; +// import ingest from './ingest'; // import pages from './pages'; // import utilities from './utilities'; @@ -8,7 +9,7 @@ import summary from './summary'; // ===========================|| MENU ITEMS ||=========================== // const menuItems = { - items: [summary, clinicalGenomicSearch /* pages, utilities, other */] + items: [summary, clinicalGenomicSearch /* , ingest, pages, utilities, other */] }; export default menuItems; diff --git a/src/menu-items/ingest.js b/src/menu-items/ingest.js new file mode 100644 index 00000000..97fdf2a3 --- /dev/null +++ b/src/menu-items/ingest.js @@ -0,0 +1,32 @@ +// assets +import { IconUpload } from '@tabler/icons'; + +// import project config +import config from 'config'; + +// constant +const { basename } = config; + +const icons = { + IconUpload +}; + +// ===========================|| Ingest MENU ITEMS ||=========================== // + +const ingest = { + id: 'ingest', + title: 'Data Ingest', + type: 'group', + children: [ + { + id: 'Data Ingest', + title: 'Data Ingest', + type: 'item', + url: `${basename}/data-ingest`, + icon: icons.IconUpload, + breadcrumbs: false + } + ] +}; + +export default ingest; diff --git a/src/routes/MainRoutes.js b/src/routes/MainRoutes.js index 363cabeb..4268863b 100644 --- a/src/routes/MainRoutes.js +++ b/src/routes/MainRoutes.js @@ -16,6 +16,9 @@ const Summary = Loadable(lazy(() => import('views/summary/summary'))); // Clinical & Genomic Search const ClinicalGenomicSearch = Loadable(lazy(() => import('views/clinicalGenomic/clinicalGenomicSearch'))); +// Ingest Portal +// const IngestPortal = Loadable(lazy(() => import('views/ingest/ingest'))); + // Error Pages const ErrorNotFoundPage = Loadable(lazy(() => import('views/errorPages/ErrorNotFoundPage'))); @@ -41,6 +44,10 @@ const MainRoutes = { path: `${basename}/clinicalGenomicSearch`, element: }, + /* { + path: `${basename}/data-ingest`, + element: + }, */ { path: '*', element: diff --git a/src/store/api.js b/src/store/api.js index 999634f8..a4f403ae 100644 --- a/src/store/api.js +++ b/src/store/api.js @@ -5,6 +5,7 @@ export const federation = `${process.env.REACT_APP_FEDERATION_API_SERVER}/v1`; 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; +export const INGEST_URL = process.env.REACT_APP_INGEST_SERVER; // API Calls /* @@ -194,3 +195,61 @@ export function searchVariantByGene(geneName) { return 'error'; }); } + +export function query(parameters, abort) { + const payload = { + ...parameters + }; + + return fetch(`${federation}/fanout`, { + method: 'post', + signal: abort, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + method: 'GET', + path: 'query', + service: 'query', + payload + }) + }) + .then((response) => { + if (response.ok) { + return response.json(); + } + return []; + }) + .catch((error) => { + console.log('Error:', error); + return 'error'; + }); +} + +/* +Post a clinical data JSON to Katsu + * @param {string}... Name of a gene +*/ +export function ingestClinicalData(data) { + return fetch(`${INGEST_URL}/ingest/clinical_donors`, { + method: 'post', + headers: { 'Content-Type': 'application/json' }, + body: data + }) + .then((response) => response.json()) + .catch((error) => { + console.log('Error:', error); + return error; + }); +} + +export function ingestGenomicData(data, program_id) { + return fetch(`${INGEST_URL}/ingest/moh_variants/${program_id}`, { + method: 'post', + headers: { 'Content-Type': 'application/json' }, + body: data + }) + .then((response) => response) + .catch((error) => { + console.log('Error:', error); + return error; + }); +} diff --git a/src/ui-component/DataRow.js b/src/ui-component/DataRow.js new file mode 100644 index 00000000..068540c1 --- /dev/null +++ b/src/ui-component/DataRow.js @@ -0,0 +1,119 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { Box, Divider, Grid, Typography } from '@mui/material'; +import { useTheme, makeStyles } from '@mui/styles'; + +function makeField(lab, val) { + return { + label: lab, + value: val + }; +} + +const useStyles = makeStyles((theme) => ({ + rowText: { + color: 'black' + }, + container: { + minHeight: 40, + marginRight: 0 + }, + locked: { + backgroundColor: theme.palette.action.disabledBackground + }, + button: { + // Right-aligned + float: 'right', + marginLeft: 'auto' + }, + divider: { + borderColor: theme.palette.primary.main, + marginTop: '0.25em', + marginBottom: '0.25em' + }, + dataBox: { + maxHeight: 'none', + height: 'fit-content', + paddingTop: '0.75em', + paddingBottom: '0.75em', + marginTop: '3em' + }, + label: { + marginTop: '-3.5em', + paddingBottom: '2em', + color: 'black' + } +})); + +function DataRow(props) { + const { fields, itemSize, rowWidth } = props; + const theme = useTheme(); + const classes = useStyles(); + const primaryField = fields.shift(); + + /* const avatarProps = locked + ? { + // If we're locked out, gray out the avatar + sx: { bgcolor: theme.palette.action.disabled } + } + : {}; */ + + return ( + + + + + {primaryField.label} + + + {primaryField.value} + + + {fields.map((field, idx) => ( + + + + + {field.label} + + + {field.value} + + + + ))} + + + ); +} + +DataRow.defaultProps = { + itemSize: '16px', + rowWidth: '100%' +}; + +DataRow.propTypes = { + fields: PropTypes.arrayOf( + PropTypes.shape({ + label: PropTypes.string.isRequired, + value: PropTypes.string.isRequired + }) + ).isRequired, + itemSize: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + rowWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]) +}; + +export { makeField, DataRow }; diff --git a/src/ui-component/PersistentFile.js b/src/ui-component/PersistentFile.js new file mode 100644 index 00000000..b9211bd2 --- /dev/null +++ b/src/ui-component/PersistentFile.js @@ -0,0 +1,60 @@ +import { useEffect, useRef, useState } from 'react'; +import { Alert, TextField } from '@mui/material'; +import PropTypes from 'prop-types'; + +const PersistentFile = ({ file, fileLoader }) => { + // A JSON file input dialog which can maintain its state after unmounting. Optionally validates before upload. + // Calls "fileLoader" with the file object as argument #1 and the JSON data as argument #2. + const [error, setError] = useState(null); + const fileRef = useRef(null); + + async function loadFile(file) { + try { + const data = await file.text().then((data) => JSON.parse(data)); + if (!(data || data !== undefined)) { + console.log(`Error parsing uploaded JSON file ${file.name}`); + setError(`Error parsing uploaded JSON "${file.name}`); + return; + } + fileLoader(file, data); + setError(null); + } catch (error) { + console.log(`Error parsing JSON ${file.name}`); + console.log(error); + setError(`Error parsing JSON "${file.name}": ${error.message}`); + fileRef.current.value = ''; + } + } + + useEffect(() => { + if (file && fileRef.current) { + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(file); + fileRef.current.files = dataTransfer.files; + } + }); + + return ( + <> + loadFile(event.target.files[0])} + /> + {error !== null && ( + + {error} + + )} + + ); +}; + +PersistentFile.propTypes = { + fileLoader: PropTypes.func.isRequired, + file: PropTypes.instanceOf(File) +}; + +export default PersistentFile; diff --git a/src/ui-component/cards/CardSecondaryAction.js b/src/ui-component/cards/CardSecondaryAction.js index 4f0aa9f2..ed5166f0 100644 --- a/src/ui-component/cards/CardSecondaryAction.js +++ b/src/ui-component/cards/CardSecondaryAction.js @@ -1,5 +1,4 @@ import PropTypes from 'prop-types'; -import React from 'react'; // mui import { useTheme } from '@mui/styles'; diff --git a/src/ui-component/cards/Skeleton/ImagePlaceholder.js b/src/ui-component/cards/Skeleton/ImagePlaceholder.js index 94aef7ee..40f2e50f 100644 --- a/src/ui-component/cards/Skeleton/ImagePlaceholder.js +++ b/src/ui-component/cards/Skeleton/ImagePlaceholder.js @@ -1,5 +1,3 @@ -import React from 'react'; - // mui import Skeleton from '@mui/material/Skeleton'; diff --git a/src/ui-component/cards/Skeleton/PopularCard.js b/src/ui-component/cards/Skeleton/PopularCard.js index d0965147..19ca6f3f 100644 --- a/src/ui-component/cards/Skeleton/PopularCard.js +++ b/src/ui-component/cards/Skeleton/PopularCard.js @@ -1,4 +1,3 @@ -import React from 'react'; // mui import { makeStyles } from '@mui/styles'; import { Card, CardContent, Grid, Skeleton } from '@mui/material'; diff --git a/src/ui-component/cards/Skeleton/TotalGrowthBarChart.js b/src/ui-component/cards/Skeleton/TotalGrowthBarChart.js index a0ea7b92..ab758bb0 100644 --- a/src/ui-component/cards/Skeleton/TotalGrowthBarChart.js +++ b/src/ui-component/cards/Skeleton/TotalGrowthBarChart.js @@ -1,5 +1,3 @@ -import React from 'react'; - // mui import { Card, CardContent, Grid } from '@mui/material'; import Skeleton from '@mui/material/Skeleton'; diff --git a/src/ui-component/extended/Avatar.js b/src/ui-component/extended/Avatar.js index 2e89a895..96891ee9 100644 --- a/src/ui-component/extended/Avatar.js +++ b/src/ui-component/extended/Avatar.js @@ -1,5 +1,4 @@ import PropTypes from 'prop-types'; -import React from 'react'; // mui import { makeStyles } from '@mui/styles'; diff --git a/src/ui-component/ingest/ClinicalIngest.js b/src/ui-component/ingest/ClinicalIngest.js new file mode 100644 index 00000000..e4857c5b --- /dev/null +++ b/src/ui-component/ingest/ClinicalIngest.js @@ -0,0 +1,151 @@ +import { Button, Grid, Typography } from '@mui/material'; +import PropTypes from 'prop-types'; +import { makeField, DataRow } from 'ui-component/DataRow'; +import { makeStyles } from '@mui/styles'; +import { useEffect, useState } from 'react'; +import { fetchFederation } from 'store/api'; + +const ClinicalIngest = ({ setTab, fileUpload, clinicalData }) => { + // setTab should be a function that sets the tab to the genomic ingest page + + const [authorizedCohorts, setAuthorizedCohorts] = useState([]); + + useEffect(() => { + function fetchPrograms() { + return fetchFederation('v2/discovery/donors/', 'katsu') + .then((result) => { + result.forEach((site) => { + const programs = site.results.discovery_donor; + const fields = []; + Object.keys(programs).forEach((program) => { + const field = [ + makeField('Cohort', program), + makeField('Clinical Patients', programs[program].toString()), + makeField('Read Access', 'Unknown') + ]; + fields.push(field); + }); + setAuthorizedCohorts(fields); + }); + }) + .catch((error) => console.log(error)); + } + fetchPrograms(); + }, []); + + const useStyles = makeStyles({ + titleText: { + color: 'black', + fontSize: '1.5em', + fontFamily: 'Roboto' + }, + bodyText: { + color: 'black', + fontSize: '1em', + fontFamily: 'Catamaran' + }, + buttonEnabled: { + position: 'absolute', + right: '0.2em', + bottom: '0.2em' + }, + buttonDisabled: { + position: 'absolute', + right: '0.2em', + bottom: '0.2em', + backgroundColor: 'grey', + '&:hover': { + backgroundColor: 'grey' + } + } + }); + + const classes = useStyles(); + + return ( + <> + + + + Your authorized cohorts + + {authorizedCohorts.length > 0 ? ( + + {authorizedCohorts.map((fields, index) => ( + + + + ))} + + ) : ( + + No cohorts found. + + )} + + +
+ + Choose a cohort for validation + + + + + Clinical data: + + + + {fileUpload} + + +
+
+ + + Live Preview Summary + + {clinicalData === undefined ? ( + + Waiting for upload... + + ) : ( + + )} + + + + Validation + + + Waiting for validation... + + +
+ {clinicalData === undefined ? ( + + ) : ( + + )} + + ); +}; + +ClinicalIngest.propTypes = { + setTab: PropTypes.func.isRequired, + fileUpload: PropTypes.element, + clinicalData: PropTypes.object +}; + +export default ClinicalIngest; diff --git a/src/ui-component/ingest/GenomicIngest.js b/src/ui-component/ingest/GenomicIngest.js new file mode 100644 index 00000000..16e5f24d --- /dev/null +++ b/src/ui-component/ingest/GenomicIngest.js @@ -0,0 +1,92 @@ +import { Button, Grid, Typography } from '@mui/material'; +import PropTypes from 'prop-types'; +import { makeField, DataRow } from 'ui-component/DataRow'; +import { makeStyles } from '@mui/styles'; +import { useEffect, useState } from 'react'; + +const GenomicIngest = ({ beginIngest, fileUpload, clinicalData, genomicData }) => { + const [ingestButtonEnabled, setIngestButtonEnabled] = useState(false); + + const cohort = [ + makeField('Cohort', clinicalData.donors[0].program_id), + makeField('Clinical Patients', clinicalData.donors.length), + makeField('Read Access', '1') + ]; + + const useStyles = makeStyles({ + titleText: { + color: 'black', + fontSize: '1.5em', + fontFamily: 'Roboto' + }, + bodyText: { + color: 'black', + fontSize: '1em', + fontFamily: 'Catamaran' + }, + ingestButton: { + backgroundColor: '#37CA50', + width: '7em', + height: '3em' + }, + ingestButtonDisabled: { + backgroundColor: 'grey', + '&:hover': { + backgroundColor: 'grey' + } + } + }); + const classes = useStyles(); + + useEffect(() => genomicData !== undefined && genomicData !== null && setIngestButtonEnabled(true), [genomicData]); + + return ( + <> + + + + Cohort for ingestion + + + + + + Upload genomic sample info + + + + + Genomic data: + + + {fileUpload} + + + + + + + + ); +}; + +GenomicIngest.propTypes = { + beginIngest: PropTypes.func.isRequired, + fileUpload: PropTypes.element, + clinicalData: PropTypes.object, + genomicData: PropTypes.object +}; + +export default GenomicIngest; diff --git a/src/ui-component/ingest/IngestMenu.js b/src/ui-component/ingest/IngestMenu.js new file mode 100644 index 00000000..bc84b3dc --- /dev/null +++ b/src/ui-component/ingest/IngestMenu.js @@ -0,0 +1,170 @@ +import { makeStyles, styled } from '@mui/styles'; +import { Alert, Box, CircularProgress, Grid, Tab, Tabs } from '@mui/material'; +import { useEffect, useState } from 'react'; + +import IngestTabPage from 'ui-component/ingest/IngestTabPage'; +import ClinicalIngest from 'ui-component/ingest/ClinicalIngest'; +import GenomicIngest from 'ui-component/ingest/GenomicIngest'; +import PersistentFile from 'ui-component/PersistentFile'; +import { ingestClinicalData, ingestGenomicData } from 'store/api'; + +const IngestTab = styled(Tab)({ + height: '2.75em', + paddingLeft: '0.77em', + paddingRight: '0.77em', + marginBottom: 0, + borderTopLeftRadius: '0.55em', + borderTopRightRadius: '0.55em', + overflow: 'hidden', + justifyContent: 'flex-start', + alignItems: 'start', + gap: '0.59em', + display: 'flex', + textAlign: 'left', + color: '#686969', + fontSize: '1.5em', + fontFamily: 'Roboto', + fontWeight: '700', + wordWrap: 'break-word', + textTransform: 'none', + verticalAlign: 'center' +}); + +function IngestMenu() { + const IngestStates = { + PENDING: 0, + STARTED_CLINICAL: 1, + STARTED_GENOMIC: 2, + ERROR: 3, + SUCCESS: 4 + }; + const [value, setValue] = useState(0); + const [clinicalFile, setClinicalFile] = useState(undefined); + const [clinicalData, setClinicalData] = useState(undefined); + const [genomicData, setGenomicData] = useState(undefined); + const [genomicFile, setGenomicFile] = useState(undefined); + const [ingestState, setIngestState] = useState(IngestStates.PENDING); + const [ingestError, setIngestError] = useState(''); + + const useStyles = makeStyles({ + tabActive: { + border: '0.125vw #2196F3 solid', + borderBottom: 'none', + background: '#FFFFFF' + }, + tabInactive: { + border: '0.1vw #2196F3 solid', + borderBottom: '0.15vw #2196F3 solid', + background: '#F4FAFF' + } + }); + + const classes = useStyles(); + + function getIngestStatusComponent(status) { + function progressRow(component) { + return ( + + + + + + {component} + + + ); + } + + switch (status) { + case IngestStates.STARTED_CLINICAL: + return progressRow(Ingesting clinical data...); + case IngestStates.SUCCESS: + return Ingest complete!; + case IngestStates.ERROR: + return Ingest encountered the following error: {ingestError}; + case IngestStates.STARTED_GENOMIC: + return progressRow(Clinical ingest complete. Beginning genomic ingest...); + default: + return Nothing to show. (You probably should not be seeing this...); + } + } + + function loadClinicalFile(file, data) { + if (!('donors' in data && Array.isArray(data.donors))) { + throw Error('Donors key not found in clinical file'); + } + setClinicalData(data); + setClinicalFile(file); + } + function loadGenomicFile(file, data) { + setGenomicData(data); + setGenomicFile(file); + } + + function beginIngest() { + console.log('Beginning ingest...'); + setIngestState(IngestStates.STARTED_CLINICAL); + ingestClinicalData(JSON.stringify(clinicalData)).then((result) => { + if (result.response_code === 0) { + setIngestState(IngestStates.STARTED_GENOMIC); + ingestGenomicData(JSON.stringify(genomicData), clinicalData.donors[0].program_id).then((response) => { + if (response.status === 200) { + setIngestState(IngestStates.SUCCESS); + } else { + setIngestError(result.json.result); + } + }); + } else { + setIngestError(result.result); + } + }); + } + + useEffect(() => ingestError && setIngestState(IngestStates.ERROR), [ingestError, IngestStates.ERROR]); + + function getPage(val) { + if (val === 0) + return ( + setValue(1)} + fileUpload={ loadClinicalFile(file, data)} />} + clinicalData={clinicalData} + /> + ); + return ( + beginIngest()} + fileUpload={ loadGenomicFile(file, data)} />} + clinicalData={clinicalData} + genomicData={genomicData} + /> + ); + } + + return ( + + setValue(value)} + variant="fullWidth" + sx={{ width: '100%' }} + TabIndicatorProps={{ + style: { display: 'none' } + }} + centered + > + + + + {getPage(value)} + {ingestState !== IngestStates.PENDING && getIngestStatusComponent(ingestState)} + + ); +} + +export default IngestMenu; diff --git a/src/ui-component/ingest/IngestTabPage.js b/src/ui-component/ingest/IngestTabPage.js new file mode 100644 index 00000000..edf7425b --- /dev/null +++ b/src/ui-component/ingest/IngestTabPage.js @@ -0,0 +1,30 @@ +import { Grid } from '@mui/material'; +import { makeStyles } from '@mui/styles'; +import PropTypes from 'prop-types'; + +const IngestTabPage = ({ children }) => { + const useStyles = makeStyles({ + ingestTabPage: { + position: 'relative', + width: '100%', + height: '100%', + minHeight: 100, + borderBottomLeftRadius: '0.75em', + borderBottomRightRadius: '0.75em', + marginTop: 0, + border: '0.125vw #2196F3 solid', + borderTop: 'none', + padding: '1em' + } + }); + + const classes = useStyles(); + + return {children}; +}; + +IngestTabPage.propTypes = { + children: PropTypes.node +}; + +export default IngestTabPage; diff --git a/src/views/clinical/mcode.js b/src/views/clinical/mcode.js deleted file mode 100644 index b6dd9569..00000000 --- a/src/views/clinical/mcode.js +++ /dev/null @@ -1,639 +0,0 @@ -import * as React from 'react'; - -// npm installs -import ReactJson from 'react-json-view'; -import cancerTypeCSV from '../../assets/data_files/cancer_histological_codes_labels.csv'; -import papa from 'papaparse'; - -// mui -import { useTheme, makeStyles } from '@mui/styles'; -import { DataGrid, GridToolbar } from '@mui/x-data-grid'; -import { Grid, Box } from '@mui/material'; - -// REDUX -import { useSelector, useDispatch } from 'react-redux'; - -// project imports -import MainCard from 'ui-component/cards/MainCard'; -import { fetchFederationClinicalData } from 'store/api'; -import { - subjectColumns, - processMCodeMainData, - processMedicationListData, - processCondtionsListData, - processSexListData, - processCancerTypeListData, - processHistologicalTypeListData -} from 'store/mcode'; -import SingleRowTable from 'ui-component/SingleRowTable'; -import { trackPromise } from 'react-promise-tracker'; -import Stack from '@mui/material/Stack'; -import Divider from '@mui/material/Divider'; -import TableContainer from '@mui/material/TableContainer'; -import Table from '@mui/material/Table'; -import DropDown from '../../ui-component/DropDown'; -import { SearchIndicator } from 'ui-component/LoadingIndicator/SearchIndicator'; - -// Styles -const useStyles = makeStyles({ - dropdownItem: { - background: 'white', - paddingRight: '1.25em', - paddingLeft: '1.25em', - border: 'none', - width: 'fit-content(5em)', - '&:hover': { - background: '#2196f3', - color: 'white' - } - }, - mobileRow: { - width: '800px' - }, - scrollbar: { - scrollbarWidth: 'thin', - '&::-webkit-scrollbar': { - height: '0.4em', - width: '0.4em' - }, - '&::-webkit-scrollbar-track': { - boxShadow: 'inset 0 0 4px rgba(0,0,0,0.00)', - webkitBoxShadow: 'inset 0 0 4px rgba(0,0,0,0.00)' - }, - '&::-webkit-scrollbar-thumb': { - backgroundColor: 'rgba(0,0,0,.1)' - } - } -}); - -function MCodeView() { - const theme = useTheme(); - const classes = useStyles(); - const events = useSelector((state) => state); - const dispatch = useDispatch(); - const clinicalSearch = useSelector((state) => state.customization.clinicalSearch); - const clinicalSearchPatients = useSelector((state) => state.customization.clinicalSearchResultPatients); - - const [isLoading, setIsLoading] = React.useState(true); - const [mcodeData, setMcodeData] = React.useState([]); - const [rows, setRows] = React.useState([]); - const [selectedPatient, setSelectedPatient] = React.useState(''); - const [selectedPatientMobileInfo, setSelectedPatientMobileInfo] = React.useState({}); - const [cancerType, setCancerType] = React.useState([]); - - // Mobile - const [desktopResolution, setdesktopResolution] = React.useState(window.innerWidth > 1200); - const [isListOpen, setListOpen] = React.useState(false); - - // Dropdown patient table open/closed - const [isListOpenMedications, setListOpenMedications] = React.useState(false); - const [isListOpenConditions, setListOpenConditions] = React.useState(false); - const [isListOpenSex, setListOpenSex] = React.useState(false); - const [isListOpenCancerType, setListOpenCancerType] = React.useState(false); - const [isListOpenHistological, setListOpenHistological] = React.useState(false); - - // Dropdown patient table filtering current selection in dropdown - const [selectedMedications, setSelectedMedications] = React.useState(clinicalSearch.clinicalSearchDropDowns.selectedMedications); - const [selectedConditions, setSelectedConditions] = React.useState(clinicalSearch.clinicalSearchDropDowns.selectedConditions); - const [selectedSex, setSelectedSex] = React.useState(clinicalSearch.clinicalSearchDropDowns.selectedSex); - const [selectedCancerType, setSelectedCancerType] = React.useState(clinicalSearch.clinicalSearchDropDowns.selectedCancerType); - const [selectedHistologicalType, setSelectedHistologicalType] = React.useState( - clinicalSearch.clinicalSearchDropDowns.selectedHistologicalType - ); - const [patientJSON, setPatientJSON] = React.useState([]); - - // Dropdown patient table list for filtering - const [medicationList, setMedicationList] = React.useState(clinicalSearch.clinicalSearchDropDowns.medicationList); - const [conditionList, setConditionList] = React.useState(clinicalSearch.clinicalSearchDropDowns.conditionList); - const [sexList, setSexList] = React.useState(clinicalSearch.clinicalSearchDropDowns.sexList); - const [cancerTypeList, setCancerTypeList] = React.useState(clinicalSearch.clinicalSearchDropDowns.cancerTypeList); - const [HistologicalList, setHistologicalList] = React.useState(clinicalSearch.clinicalSearchDropDowns.HistologicalList); - - const jsonTheme = { - base00: 'white', - base01: '#ddd', - base02: '#ddd', - base03: 'black', - base04: '#0E3E17', - base05: 'black', - base06: 'black', - base07: '#252525', - base08: '#252525', - base09: '#00418A', - base0A: '#00418A', - base0B: '#00418A', - base0C: '#00418A', - base0D: '#00418A', - base0E: '#00418A', - base0F: '#00418A' - }; - - function setClincalSearchPatients(data) { - dispatch({ - type: 'SET_CLINICAL_SEARCH_PATIENTS', - payload: { - data - } - }); - } - - function setRedux(rows) { - const tempClinicalSearchResults = []; - rows.forEach((patient) => { - tempClinicalSearchResults.push({ id: patient.id, genomicId: patient.genomic_id }); - }); - dispatch({ - type: 'SET_SELECTED_CLINICAL_SEARCH_RESULTS', - payload: { - selectedClinicalSearchResults: tempClinicalSearchResults, - clinicalSearchDropDowns: { - medicationList, - selectedMedications, - conditionList, - selectedConditions, - sexList, - selectedSex, - cancerTypeList, - selectedCancerType, - HistologicalList, - selectedHistologicalType - } - } - }); - } - // Parsing CancerType CSV into Dictionary - React.useEffect( - () => - papa.parse(cancerTypeCSV, { - header: true, - download: true, - skipEmptyLines: true, - // eslint-disable-next-line - complete: function (results) { - setCancerType(results.data); - } - }), - [] - ); - // Subtable selection of patient - const handleRowClick = (row) => { - let index; - mcodeData.results.forEach((federatedResults) => { - index = federatedResults.results.findIndex((item) => item.id === row.id); - if (index !== -1) { - setSelectedPatient(federatedResults.results[index].id); - setSelectedPatientMobileInfo({ - Ethnicity: federatedResults?.results[index]?.subject?.ethnicity - ? federatedResults?.results[index]?.subject?.ethnicity - : 'NA', - Sex: (federatedResults?.results[index]?.subject?.sex).toLowerCase() - ? (federatedResults?.results[index]?.subject?.sex).toLowerCase() - : 'NA', - Deceased: federatedResults?.results[index]?.subject?.deceased - ? federatedResults?.results[index]?.subject?.deceased - : 'NA', - Birthday: federatedResults?.results[index]?.subject?.date_of_birth - ? federatedResults?.results[index]?.subject?.date_of_birth - : 'NA', - DeathDate: federatedResults?.results[index]?.date_of_death ? federatedResults?.results[index]?.date_of_death : 'NA' - }); - - // Set patient JSON - setPatientJSON(federatedResults?.results[index], selectedPatient); - } - }); - - setListOpen(false); - }; - - const dropDownSelection = (dropDownGroup, selected) => { - if (dropDownGroup === 'CONDITIONS') { - setSelectedConditions(selected); - setListOpenConditions(false); - } else if (dropDownGroup === 'MEDICATIONS') { - setSelectedMedications(selected); - setListOpenMedications(false); - } else if (dropDownGroup === 'SEX') { - setSelectedSex(selected); - setListOpenSex(false); - } else if (dropDownGroup === 'CANCER TYPE') { - setSelectedCancerType(selected); - setListOpenCancerType(false); - } else if (dropDownGroup === 'HISTOLOGICAL') { - setSelectedHistologicalType(selected); - setListOpenHistological(false); - } - }; - - // Filtering Data - React.useEffect(() => { - if (Object.keys(clinicalSearchPatients.data).length !== 0) { - const tempRows = []; - const data = clinicalSearchPatients.data; - for (let j = 0; j < data.results.length; j += 1) { - for (let i = 0; i < data.results[j].count; i += 1) { - // Patient table filtering - if ( - selectedConditions === 'All' && - selectedMedications === 'All' && - selectedSex === 'All' && - selectedCancerType === 'All' && - selectedHistologicalType === 'All' - ) { - // All patients - if (processMCodeMainData(data.results[j].results[i], data.results[j].location[0]).id !== null) { - tempRows.push(processMCodeMainData(data.results[j].results[i], data.results[j].location[0])); - } - } else { - // Filtered patients - let patientCondition = false; - data?.results[j]?.results[i]?.cancer_condition?.body_site?.every((bodySite) => { - if (selectedConditions === 'All' || selectedConditions === bodySite.label) { - patientCondition = true; - return false; - } - return true; - }); - let patientMedication = false; - data?.results[j]?.results[i]?.medication_statement.every((medication) => { - if (selectedMedications === 'All' || selectedMedications === medication?.medication_code.label) { - patientMedication = true; - return false; - } - return true; - }); - let patientSex = false; - if (selectedSex === 'All' || selectedSex === (data?.results[j]?.results[i]?.subject.sex).toLowerCase()) { - patientSex = true; - } - let patientCancerType = false; - if (selectedCancerType === 'All') { - patientCancerType = true; - } else { - for (let k = 0; k < cancerType.length; k += 1) { - if ( - data?.results[j]?.results[i]?.cancer_condition?.code?.id !== undefined && - data?.results[j]?.results[i]?.cancer_condition?.code?.id === cancerType[k]['Cancer type code'] - ) { - if ( - selectedCancerType === - `${cancerType[k]['Cancer type label']} ${cancerType[k]['Cancer type code']}` || - selectedCancerType === 'NA' - ) { - patientCancerType = true; - } - } - } - } - let patientHistologicalType = false; - if (selectedHistologicalType === 'All') { - patientHistologicalType = true; - } else { - for (let k = 0; k < cancerType.length; k += 1) { - if ( - data?.results[j]?.results[i]?.cancer_condition?.histology_morphology_behavior?.id !== undefined && - data?.results[j]?.results[i]?.cancer_condition?.histology_morphology_behavior?.id === - cancerType[k]['Tumour histological type code'] - ) { - if ( - selectedHistologicalType === - `${cancerType[k]['Tumour histological type label']} ${cancerType[k]['Tumour histological type code']}` || - selectedHistologicalType === 'NA' - ) { - patientHistologicalType = true; - } - } - } - } - if ( - patientCondition && - patientMedication && - patientSex && - patientCancerType && - patientHistologicalType && - processMCodeMainData(data.results[j].results[i]).id !== null - ) { - tempRows.push(processMCodeMainData(data.results[j].results[i], data.results[j].location[0])); - } - } - } - } - setRows(tempRows); - // Subtables - if (tempRows.length !== 0) { - let index; - data.results.forEach((federatedResults) => { - index = federatedResults.results.findIndex((item) => item.id === tempRows[0].id); - if (index !== -1) { - setSelectedPatient(federatedResults.results[index].id); - setPatientJSON(federatedResults.results[index], selectedPatient); - if (tempRows[0].id !== null) { - setSelectedPatientMobileInfo({ - Ethnicity: tempRows[0]?.ethnicity ? tempRows[0]?.ethnicity : 'NA', - Sex: tempRows[0]?.sex ? tempRows[0]?.sex : 'NA', - Deceased: tempRows[0]?.deceased ? tempRows[0]?.deceased : 'NA', - Birthday: tempRows[0]?.date_of_birth ? tempRows[0]?.date_of_birth : 'NA', - DeathDate: tempRows[0]?.date_of_death ? tempRows[0]?.date_of_death : 'NA' - }); - } - } - }); - } else { - setSelectedPatient('None'); - setPatientJSON({}); - } - - setListOpen(false); - // Dropdown patient table list for filtering - setMedicationList(processMedicationListData(data.results)); - setConditionList(processCondtionsListData(data.results)); - setSexList(processSexListData(data.results)); - setCancerTypeList(processCancerTypeListData(data.results)); - setHistologicalList(processHistologicalTypeListData(data.results)); - setIsLoading(false); - - setRedux(tempRows); - } - }, [selectedSex, selectedConditions, selectedMedications, selectedCancerType, selectedHistologicalType]); - - // Tracks Screensize - React.useEffect(() => { - window.addEventListener('resize', () => setdesktopResolution(window.innerWidth > 1200)); - }, [desktopResolution, setdesktopResolution]); - - React.useEffect(() => { - setIsLoading(true); - const tempRows = []; - trackPromise( - fetchFederationClinicalData().then((data) => { - setMcodeData(data); - setClincalSearchPatients(data); - for (let j = 0; j < data.results.length; j += 1) { - for (let i = 0; i < data.results[j].count; i += 1) { - // Patient table filtering - if ( - selectedConditions === 'All' && - selectedMedications === 'All' && - selectedSex === 'All' && - selectedCancerType === 'All' && - selectedHistologicalType === 'All' - ) { - // All patients - if (processMCodeMainData(data.results[j].results[i], data.results[j].location[0]).id !== null) { - tempRows.push(processMCodeMainData(data.results[j].results[i], data.results[j].location[0])); - } - } else { - // Filtered patients - let patientCondition = false; - data?.results[j]?.results[i]?.cancer_condition?.body_site?.every((bodySite) => { - if (selectedConditions === 'All' || selectedConditions === bodySite.label) { - patientCondition = true; - return false; - } - return true; - }); - let patientMedication = false; - data?.results[j]?.results[i]?.medication_statement.every((medication) => { - if (selectedMedications === 'All' || selectedMedications === medication?.medication_code.label) { - patientMedication = true; - return false; - } - return true; - }); - let patientSex = false; - if (selectedSex === 'All' || selectedSex === (data?.results[j]?.results[i]?.subject.sex).toLowerCase()) { - patientSex = true; - } - let patientCancerType = false; - if (selectedCancerType === 'All') { - patientCancerType = true; - } else { - for (let k = 0; k < cancerType.length; k += 1) { - if ( - data?.results[j]?.results[i]?.cancer_condition?.code?.id !== undefined && - data?.results[j]?.results[i]?.cancer_condition?.code?.id === cancerType[k]['Cancer type code'] - ) { - if ( - selectedCancerType === - `${cancerType[k]['Cancer type label']} ${cancerType[k]['Cancer type code']}` || - selectedCancerType === 'NA' - ) { - patientCancerType = true; - } - } - } - } - let patientHistologicalType = false; - if (selectedHistologicalType === 'All') { - patientHistologicalType = true; - } else { - for (let k = 0; k < cancerType.length; k += 1) { - if ( - data?.results[j]?.results[i]?.cancer_condition?.histology_morphology_behavior?.id !== undefined && - data?.results[j]?.results[i]?.cancer_condition?.histology_morphology_behavior?.id === - cancerType[k]['Tumour histological type code'] - ) { - if ( - selectedHistologicalType === - `${cancerType[k]['Tumour histological type label']} ${cancerType[k]['Tumour histological type code']}` || - selectedHistologicalType === 'NA' - ) { - patientHistologicalType = true; - } - } - } - } - if ( - patientCondition && - patientMedication && - patientSex && - patientCancerType && - patientHistologicalType && - processMCodeMainData(data.results[j].results[i]).id !== null - ) { - tempRows.push(processMCodeMainData(data.results[j].results[i], data.results[j].location[0])); - } - } - } - } - setRows(tempRows); - // Subtables - if (tempRows.length !== 0) { - let index; - data.results.forEach((federatedResults) => { - index = federatedResults.results.findIndex((item) => item.id === tempRows[0].id); - if (index !== -1) { - setSelectedPatient(federatedResults.results[index].id); - setPatientJSON(federatedResults.results[index], selectedPatient); - if (tempRows[0].id !== null) { - setSelectedPatientMobileInfo({ - Ethnicity: tempRows[0]?.ethnicity ? tempRows[0]?.ethnicity : 'NA', - Sex: tempRows[0]?.sex ? tempRows[0]?.sex : 'NA', - Deceased: tempRows[0]?.deceased ? tempRows[0]?.deceased : 'NA', - Birthday: tempRows[0]?.date_of_birth ? tempRows[0]?.date_of_birth : 'NA', - DeathDate: tempRows[0]?.date_of_death ? tempRows[0]?.date_of_death : 'NA' - }); - } - } - }); - } else { - setSelectedPatient('None'); - setPatientJSON({}); - } - - setListOpen(false); - // Dropdown patient table list for filtering - setMedicationList(processMedicationListData(data.results)); - setConditionList(processCondtionsListData(data.results)); - setSexList(processSexListData(data.results)); - setCancerTypeList(processCancerTypeListData(data.results)); - setHistologicalList(processHistologicalTypeListData(data.results)); - setIsLoading(false); - - setRedux(tempRows); - }), - 'table' - ); - }, []); - - // JSON on bottom now const screenWidth = desktopResolution ? '48%' : '100%'; - const headerLabels = { - Ethnicity: 'Ethnicity', - Sex: 'Sex', - Deceased: 'Deceased', - Birthday: 'Date of Birth', - DeathDate: 'Date of Death' - }; - const headerWidths = { - Ethnicity: '85px', - Sex: '85px', - Deceased: '85px', - Birthday: '100px', - DeathDate: '110px' - }; - - return ( - - - {selectedPatient && desktopResolution && ( - - - }> - - - - - - -
-
- )} - {!desktopResolution && selectedPatient && ( - - )} - - {!isLoading ? ( - - {desktopResolution && ( - - handleRowClick(rowData.row)} - className={classes.scrollbar} - disableSelectionOnClick - /> - - )} - - - - Patient Id - -
- {selectedPatient} -
- - - -
-
- ) : ( - - )} -
-
- ); -} - -export default MCodeView; diff --git a/src/views/clinicalGenomic/clinicalGenomicSearch.js b/src/views/clinicalGenomic/clinicalGenomicSearch.js index bda5ddb0..49334472 100644 --- a/src/views/clinicalGenomic/clinicalGenomicSearch.js +++ b/src/views/clinicalGenomic/clinicalGenomicSearch.js @@ -1,11 +1,10 @@ import { useEffect } from 'react'; import { useSelector } from 'react-redux'; -import { AppBar, Button, Divider, Toolbar, Typography } from '@mui/material'; +import { AppBar, Button, Toolbar, Typography } from '@mui/material'; -import { makeStyles, useTheme } from '@mui/styles'; +import { makeStyles } from '@mui/styles'; import MainCard from 'ui-component/cards/MainCard'; -import VariantsSearch from '../genomicsData/VariantsSearch'; import PatientCounts from './widgets/patientCounts'; import DataVisualization from './widgets/dataVisualization'; import ClinicalData from './widgets/clinicalData'; @@ -16,7 +15,7 @@ import { COHORTS } from 'store/constant'; import SearchHandler from './search/SearchHandler'; import GenomicData from './widgets/genomicData'; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles((_) => ({ stickytop: { position: 'fixed', backgroundColor: 'white', @@ -81,12 +80,11 @@ function ClinicalGenomicSearch() { const classes = useStyles(); const sidebarWriter = useSidebarWriterContext(); const sidebarOpened = useSelector((state) => state.customization.opened); - const theme = useTheme(); // When we load, set the sidebar component useEffect(() => { sidebarWriter(); - }, []); + }, [sidebarWriter]); return ( <> @@ -121,11 +119,6 @@ function ClinicalGenomicSearch() { - {sections.map((section) => (