import React, { FunctionComponent, useEffect } from 'react';
import { Route, Redirect, Switch } from 'react-router-dom';
import { inject } from 'mobx-react';
import Container from 'appShell/App/Container';
import {
handleIndexDO,
handleCaseDO,
restoreRouteAfterRedirect,
handleStudyDO,
handleLinkOut,
handleEncodedRedirect,
redirectTo,
} from './shared/lib/redirectHelpers';
import PageNotFound from './shared/components/pageNotFound/PageNotFound';
import { parse } from 'qs';
import _ from 'lodash';
/* HOW TO ADD A NEW ROUTE
* 1. Import the "page" component using SuspenseWrapper and React.lazy as seen in imports below
* 2. Add a Route element with `component` prop set to the component, wrapped in LocationValidationWrapper
* which validates the parameters of a page and goes to an error page if it fails. Other wrappers like
* ScrollToTop modify the page in other ways and may also be used here.
*/
// import page components here
// NOTE: to lazy load these, we use the bundle loader. what we are importing are not the components but loaders
// which are invoked at run time by the routes
// webpack knows to 'split' the code into seperate bundles accordingly
// see article http://henleyedition.com/implicit-code-splitting-with-react-router-and-webpack/
const PatientViewPage = SuspenseWrapper(
// @ts-ignore
React.lazy(() => import('./pages/patientView/PatientViewPage'))
);
const ResultsViewPage = SuspenseWrapper(
// @ts-ignore
React.lazy(() => import('./pages/resultsView/ResultsViewPage'))
);
import TestimonialsPage from 'pages/staticPages/testimonialsPage/TestimonialsPage';
import GroupComparisonLoading from './pages/groupComparison/GroupComparisonLoading';
const DatasetPage = SuspenseWrapper(
// @ts-ignore
React.lazy(() => import('./pages/staticPages/datasetView/DatasetPage'))
);
const Homepage = SuspenseWrapper(
// @ts-ignore
React.lazy(() => import('./pages/home/HomePage'))
);
const StudyViewPage = SuspenseWrapper(
// @ts-ignore
React.lazy(() => import('./pages/studyView/StudyViewPage'))
);
const MutationMapperTool = SuspenseWrapper(
React.lazy(() =>
// @ts-ignore
import('./pages/staticPages/tools/mutationMapper/MutationMapperTool')
)
);
const OncoprinterTool = SuspenseWrapper(
React.lazy(() =>
// @ts-ignore
import('./pages/staticPages/tools/oncoprinter/OncoprinterTool')
)
);
const Visualize = SuspenseWrapper(
// @ts-ignore
React.lazy(() => import('./pages/staticPages/visualize/Visualize'))
);
const InstallationMap = SuspenseWrapper(
React.lazy(() =>
// @ts-ignore
import('./pages/staticPages/installations/InstallationMap')
)
);
const Software = SuspenseWrapper(
// @ts-ignore
React.lazy(() => import('./pages/staticPages/software/Software'))
);
const GroupComparisonPage = SuspenseWrapper(
// @ts-ignore
React.lazy(() => import('./pages/groupComparison/GroupComparisonPage'))
);
const ErrorPage = SuspenseWrapper(
// @ts-ignore
React.lazy(() => import('./pages/resultsView/ErrorPage'))
);
const WebAPIPage = SuspenseWrapper(
// @ts-ignore
React.lazy(() => import('./pages/staticPages/webAPI/WebAPIPage'))
);
import $ from 'jquery';
import { getBrowserWindow } from 'cbioportal-frontend-commons';
import { seekUrlHash } from 'shared/lib/seekUrlHash';
import { PagePath } from 'shared/enums/PagePaths';
import {
LegacyResultsViewComparisonSubTab,
ResultsViewComparisonSubTab,
ResultsViewTab,
} from 'pages/resultsView/ResultsViewPageHelpers';
import {
StudyViewPageTabKeyEnum,
StudyViewResourceTabPrefix,
} from 'pages/studyView/StudyViewPageTabs';
import {
PatientViewPageTabs,
PatientViewResourceTabPrefix,
} from 'pages/patientView/PatientViewPageTabs';
import { GroupComparisonTab } from 'pages/groupComparison/GroupComparisonTabs';
import { CLIN_ATTR_DATA_TYPE } from 'pages/resultsView/plots/PlotsTabUtils';
import { SpecialAttribute } from 'shared/cache/ClinicalDataCache';
import { AlterationTypeConstants } from 'shared/constants';
import {
cnaGroup,
mutationGroup,
} from 'shared/lib/comparison/ComparisonStoreUtils';
import { MapValues } from 'shared/lib/TypeScriptUtils';
import { ResultsViewURLQuery } from 'pages/resultsView/ResultsViewURLWrapper';
import { EnumDeclaration, EnumType } from 'typescript';
function SuspenseWrapper(Component: any) {
return (props: any) => (
);
}
function LocationValidationWrapper(
Component: any,
validator: (params: any) => boolean,
queryParamsAdjuster?: (oldParams: any) => any | undefined
) {
return (props: any) => {
if (props.location) {
if (queryParamsAdjuster) {
const query = parse(props.location.search, {
depth: 0,
ignoreQueryPrefix: true,
});
const adjustedQuery = queryParamsAdjuster(query);
if (adjustedQuery) {
// redirect to adjusted query if there's a change
const RedirectingBlankPage = getBlankPage(() => {
redirectTo(adjustedQuery, props.location.pathname);
});
return ;
}
}
if (
!(
validator(props.match.params) ||
customTabParamValidator(props.location)
)
) {
return ;
}
}
return ;
};
}
function ResultsViewQueryParamsAdjuster(oldParams: ResultsViewURLQuery) {
let changeMade = false;
const newParams = _.cloneDeep(oldParams);
if (
newParams.comparison_subtab ===
LegacyResultsViewComparisonSubTab.MUTATIONS
) {
newParams.comparison_subtab = ResultsViewComparisonSubTab.ALTERATIONS;
newParams.comparison_selectedEnrichmentEventTypes = JSON.stringify([
...mutationGroup,
]);
changeMade = true;
} else if (
newParams.comparison_subtab === LegacyResultsViewComparisonSubTab.CNA
) {
newParams.comparison_subtab = ResultsViewComparisonSubTab.ALTERATIONS;
newParams.comparison_selectedEnrichmentEventTypes = JSON.stringify([
...cnaGroup,
]);
changeMade = true;
}
// we used to call the structural variant alteration class "fusions"
// we have generalized it to structural variants
const profileRegex = /fusion/;
if (profileRegex.test(oldParams.profileFilter)) {
newParams.profileFilter = oldParams.profileFilter.replace(
profileRegex,
'structural_variants'
);
changeMade = true;
}
if (changeMade) {
return newParams;
} else {
return undefined;
}
}
function ScrollToTop(Component: any, onLoad = () => {}) {
return (props: any) => {
useEffect(() => {
$(document).scrollTop(0);
onLoad();
}, []);
return ;
};
}
function GoToHashLink(Component: any) {
return (props: any) => {
useEffect(handleEnter, []);
return ;
};
}
/**
* Validates that the parameters either do not have
* a tab parameter, or have a parameter that matches a
* value in `tabEnum`
* @param tabEnum a TypeScript string enum
*/
function tabParamValidator(tabEnum: any) {
return function(params: any) {
return !params.tab || Object.values(tabEnum).indexOf(params.tab) > -1;
};
}
function comparisonTabParamValidator() {
// comparison tab includes generic assay tabs which is not predictable
// validate tabs by checking it's degined in GroupComparisonTab
// or validate generic assay tabs by checking if it starts with GroupComparisonTab.GENERIC_ASSAY_PREFIX
return function(params: any) {
return (
!params.tab ||
Object.values(GroupComparisonTab).indexOf(params.tab) > -1 ||
params.tab.startsWith(GroupComparisonTab.GENERIC_ASSAY_PREFIX)
);
};
}
/**
* Validates results page and patient page custom tabs
* @param location
*/
function customTabParamValidator(location: Location) {
const patientViewRegex = new RegExp(
`patient\/${PatientViewResourceTabPrefix}.+`
);
const studyViewRegex = new RegExp(`study\/${StudyViewResourceTabPrefix}.+`);
const customTabRegex = /.+\/customTab\d/;
return (
patientViewRegex.test(location.pathname) ||
studyViewRegex.test(location.pathname) ||
customTabRegex.test(location.pathname)
);
}
var restoreRoute = inject('routing')(restoreRouteAfterRedirect);
let getBlankPage = function(onLoad: any) {
return (props: any) => {
if (onLoad) {
useEffect(() => {
onLoad();
// make sure that useEffect argument doesn't return anything
}, []);
}
return
;
};
};
let redirectToNews: FunctionComponent = function() {
getBrowserWindow().location = 'https://docs.cbioportal.org/news';
return null;
};
const externalRedirect = function(url: string) {
let redirectComp: FunctionComponent<{}> = function() {
getBrowserWindow().location = url;
return null;
};
return redirectComp;
};
/* when route changes, we want to:
1. in spa, deep links from url (#) don't work because content is loading and thus doesn't exist to link to
at time url changes. seekHash is a somewhat dirty way of solving this issue
2, when there's no hash, we want to make sure we scroll to top because user considers herself on a "new page"
*/
function handleEnter() {
const hash = getBrowserWindow().location.hash;
if (hash.length > 0) {
seekUrlHash(hash.replace('#', ''));
} else {
$(document).scrollTop(0);
}
}
// we want to preload ResultsViewPage to prevent delay due to lazy loading bundle
// note: because we bundle, and bundles are loaded async, this does NOT affect time to render of default route
// results will load in background while user plays with query interface
function preloadImportantComponents() {
setTimeout(() => {
//@ts-ignore
import('./pages/resultsView/ResultsViewPage');
//@ts-ignore
import('./pages/studyView/StudyViewPage');
}, 2000);
}
export const makeRoutes = () => {
return (
{/* Redirect legacy survival route directly to survival tab in comparison */}
{
redirectTo(
{ comparison_subtab: 'survival' },
'/results/comparison'
);
})}
/>
{/* Redirect legacy expression route directly to plots tab with mrna vs study */}
{
redirectTo(
{
plots_horz_selection: JSON.stringify({
dataType: CLIN_ATTR_DATA_TYPE,
selectedDataSourceOption:
SpecialAttribute.StudyOfOrigin,
}),
plots_vert_selection: JSON.stringify({
dataType:
AlterationTypeConstants.MRNA_EXPRESSION,
logScale: 'true',
}),
},
`/results/${ResultsViewTab.PLOTS}`
);
})}
/>
{/* Redirect legacy enrichments route directly to mutations tab in comparison */}
{
redirectTo(
{ comparison_subtab: 'mutations' },
'/results/comparison'
);
})}
/>
// legacy pages redirect to docs site
(
))}
/>
);
};
export default makeRoutes;