import * as React from 'react';
import _ from 'lodash';
import { inject, Observer, observer } from 'mobx-react';
import { MSKTab, MSKTabs } from '../../shared/components/MSKTabs/MSKTabs';
import { action, computed, observable, makeObservable } from 'mobx';
import {
StudyViewPageStore,
StudyViewPageTabDescriptions,
StudyViewURLQuery,
} from 'pages/studyView/StudyViewPageStore';
import {
extractResourceIdFromTabId,
getStudyViewResourceTabId,
StudyViewPageTabKeyEnum,
} from 'pages/studyView/StudyViewPageTabs';
import LoadingIndicator from 'shared/components/loadingIndicator/LoadingIndicator';
import { ClinicalDataTab } from './tabs/ClinicalDataTab';
import {
DefaultTooltip,
getBrowserWindow,
remoteData,
} from 'cbioportal-frontend-commons';
import { PageLayout } from '../../shared/components/PageLayout/PageLayout';
import IFrameLoader from '../../shared/components/iframeLoader/IFrameLoader';
import { StudySummaryTab } from 'pages/studyView/tabs/SummaryTab';
import StudyPageHeader from './studyPageHeader/StudyPageHeader';
import CNSegments from './tabs/CNSegments';
import AddChartButton from './addChartButton/AddChartButton';
import { sleep } from '../../shared/lib/TimeUtils';
import { Else, If, Then } from 'react-if';
import CustomCaseSelection from './addChartButton/customCaseSelection/CustomCaseSelection';
import { AppStore } from '../../AppStore';
import ActionButtons from './studyPageHeader/ActionButtons';
import { onMobxPromise } from 'cbioportal-frontend-commons';
import {
GACustomFieldsEnum,
serializeEvent,
trackEvent,
} from '../../shared/lib/tracking';
import ComparisonGroupManager from '../groupComparison/comparisonGroupManager/ComparisonGroupManager';
import classNames from 'classnames';
import { getServerConfig, ServerConfigHelpers } from '../../config/config';
import {
AlterationMenuHeader,
getButtonNameWithDownPointer,
ChartMetaDataTypeEnum,
} from './StudyViewUtils';
import { Alert, Modal } from 'react-bootstrap';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
import styles from './styles.module.scss';
import './styles.scss';
import autobind from 'autobind-decorator';
import { BookmarkModal } from 'pages/resultsView/bookmark/BookmarkModal';
import { ShareUrls } from 'pages/resultsView/querySummary/ShareUI';
import { getBitlyShortenedUrl } from '../../shared/lib/bitly';
import { MakeMobxView } from '../../shared/components/MobxView';
import ResourceTab from '../../shared/components/resources/ResourceTab';
import StudyViewURLWrapper from './StudyViewURLWrapper';
import ResourcesTab, { RESOURCES_TAB_NAME } from './resources/ResourcesTab';
import { ResourceData } from 'cbioportal-ts-api-client';
import $ from 'jquery';
import { StudyViewComparisonGroup } from 'pages/groupComparison/GroupComparisonUtils';
import { parse } from 'query-string';
import SettingsMenu from 'shared/components/driverAnnotations/SettingsMenu';
import ErrorScreen from 'shared/components/errorScreen/ErrorScreen';
import { CustomChartData } from 'shared/api/session-service/sessionServiceModels';
import { HelpWidget } from 'shared/components/HelpWidget/HelpWidget';
import URL from 'url';
import { buildCBioPortalPageUrl } from 'shared/api/urls';
export interface IStudyViewPageProps {
routing: any;
appStore: AppStore;
}
@observer
export class StudyResultsSummary extends React.Component<
{ store: StudyViewPageStore; appStore: AppStore; loadingComplete: boolean },
{}
> {
render() {
return (
Selected:
{this.props.store.selectedPatients.length.toLocaleString()}
patients |
{this.props.store.selectedSamples.result.length.toLocaleString()}
samples
);
}
}
@inject('routing', 'appStore')
@observer
export default class StudyViewPage extends React.Component<
IStudyViewPageProps,
{}
> {
private urlWrapper: StudyViewURLWrapper;
private store: StudyViewPageStore;
private enableCustomSelectionInTabs = [
StudyViewPageTabKeyEnum.SUMMARY,
StudyViewPageTabKeyEnum.CLINICAL_DATA,
StudyViewPageTabKeyEnum.CN_SEGMENTS,
];
private enableAddChartInTabs = [
StudyViewPageTabKeyEnum.SUMMARY,
StudyViewPageTabKeyEnum.CLINICAL_DATA,
];
private toolbar: any;
private toolbarLeftUpdater: any;
@observable private toolbarLeft: number = 0;
@observable showCustomSelectTooltip = false;
@observable showAlterationFilterTooltip = false;
@observable private showReturnToDefaultChartListModal: boolean = false;
constructor(props: IStudyViewPageProps) {
super(props);
makeObservable(this);
this.urlWrapper = new StudyViewURLWrapper(this.props.routing);
this.store = new StudyViewPageStore(
this.props.appStore,
ServerConfigHelpers.sessionServiceIsEnabled(),
this.urlWrapper
);
const openResourceId =
this.urlWrapper.tabId &&
extractResourceIdFromTabId(this.urlWrapper.tabId);
if (openResourceId) {
this.store.setResourceTabOpen(openResourceId, true);
}
getBrowserWindow().studyPage = this;
if (
!getBrowserWindow().globalStores.routing.location.pathname.includes(
'/study'
)
) {
return;
}
const query = props.routing.query;
const hash = props.routing.location.hash;
// clear hash if any
props.routing.location.hash = '';
const newStudyViewFilter: StudyViewURLQuery = _.pick(query, [
'id',
'studyId',
'cancer_study_id',
'filterAttributeId',
'filterValues',
]);
newStudyViewFilter.filterJson = query['filters'];
let hashString: string = hash || getBrowserWindow().studyPageFilter;
delete (window as any).studyPageFilter;
if (hashString) {
const params = parse(hashString) as Partial;
if (params.filterJson) {
newStudyViewFilter.filterJson = params.filterJson;
}
if (params.sharedGroups) {
newStudyViewFilter.sharedGroups = params.sharedGroups;
}
if (params.sharedCustomData) {
newStudyViewFilter.sharedCustomData = params.sharedCustomData;
}
}
if (!_.isEqual(newStudyViewFilter, this.store.studyViewQueryFilter)) {
this.store.updateStoreFromURL(newStudyViewFilter);
this.store.studyViewQueryFilter = newStudyViewFilter;
}
onMobxPromise(
this.store.queriedPhysicalStudyIds,
(strArr: string[]) => {
trackEvent({
category: 'studyPage',
action: 'studyPageLoad',
label: strArr.join(',') + ',',
fieldsObject: {
[GACustomFieldsEnum.VirtualStudy]: (
this.store.filteredVirtualStudies.result!.length > 0
).toString(),
},
});
}
);
}
componentDidMount() {
// make the route as the default tab value
this.props.routing.updateRoute(
{},
`study/${this.store.currentTab}`,
false,
true
);
this.toolbarLeftUpdater = setInterval(() => {
if (this.toolbar) {
this.toolbarLeft = $(this.toolbar).position().left;
}
}, 500);
}
@autobind
private toolbarRef(ref: any) {
this.toolbar = ref;
}
private handleTabChange(id: string) {
this.urlWrapper.setTab(id);
}
@observable showBookmarkModal = false;
@action.bound
toggleBookmarkModal() {
this.showBookmarkModal = !this.showBookmarkModal;
}
@observable shareLinkModal = false;
@action.bound
toggleShareLinkModal() {
this.shareLinkModal = !this.shareLinkModal;
this.sharedGroups = [];
}
@action.bound
toggleShareCustomDataLinkModal() {
this.shareCustomDataLinkModal = !this.shareCustomDataLinkModal;
}
private getShareBookmarkUrl: Promise = Promise.resolve(null);
private sharedGroups: StudyViewComparisonGroup[] = [];
@observable shareCustomDataLinkModal = false;
private getShareCustomChartBookmarkUrl: Promise = Promise.resolve(
null
);
@action.bound
openShareUrlModal(groups: StudyViewComparisonGroup[]) {
this.shareLinkModal = true;
this.sharedGroups = groups;
const groupIds = groups.map(group => group.uid);
this.getShareBookmarkUrl = Promise.resolve({
bitlyUrl: undefined,
fullUrl: `${window.location.protocol}//${window.location.host}${
window.location.pathname
}${window.location.search}#sharedGroups=${groupIds.join(',')}`,
sessionUrl: undefined,
});
}
@action.bound
openShareCustomDataUrlModal(customDataIds: string[]) {
this.shareCustomDataLinkModal = true;
this.getShareCustomChartBookmarkUrl = Promise.resolve({
bitlyUrl: undefined,
fullUrl: `${window.location.protocol}//${window.location.host}${
window.location.pathname
}${window.location.search}#sharedCustomData=${customDataIds.join(
','
)}`,
sessionUrl: undefined,
});
}
@autobind
onBookmarkClick() {
this.toggleBookmarkModal();
}
@action.bound
private openResource(resource: ResourceData) {
// open tab
this.store.setResourceTabOpen(resource.resourceId, true);
// go to tab
this.urlWrapper.setTab(getStudyViewResourceTabId(resource.resourceId));
// deep link
this.urlWrapper.setResourceUrl(resource.url);
}
@action.bound
private closeResourceTab(tabId: string) {
// close tab
const resourceId = extractResourceIdFromTabId(tabId);
if (resourceId) {
this.store.setResourceTabOpen(resourceId, false);
// go to resources tab if we're currently on that tab
if (
this.urlWrapper.tabId === getStudyViewResourceTabId(resourceId)
) {
this.urlWrapper.setTab(StudyViewPageTabKeyEnum.FILES_AND_LINKS);
}
}
}
@computed get shouldShowResources() {
if (this.store.resourceIdToResourceData.isComplete) {
return _.some(
this.store.resourceIdToResourceData.result,
data => data.length > 0
);
} else {
return false;
}
}
@computed get studyViewFullUrlWithFilter() {
return `${window.location.protocol}//${window.location.host}${
window.location.pathname
}${window.location.search}#filterJson=${JSON.stringify(
this.store.filters
)}`;
}
async getBookmarkUrl(): Promise {
const bitlyUrl = await getBitlyShortenedUrl(
this.studyViewFullUrlWithFilter,
getServerConfig().bitly_access_token
);
return {
bitlyUrl,
fullUrl: this.studyViewFullUrlWithFilter,
sessionUrl: undefined,
};
}
private chartDataPromises = remoteData({
await: () => {
return [
..._.values(this.store.clinicalDataBinPromises),
..._.values(this.store.clinicalDataCountPromises),
..._.values(this.store.genericAssayDataCountPromises),
this.store.mutationProfiles,
this.store.cnaProfiles,
this.store.selectedSamples,
this.store.molecularProfileSampleCounts,
this.store.sampleTreatments,
this.store.patientTreatments,
this.store.patientTreatmentGroups,
this.store.sampleTreatmentGroups,
];
},
invoke: async () => {
// this gives time for charts to render
// product requirement that the summary data show after charts have rendered
// to call attention to the summary results
return await sleep(10);
},
});
@computed
get addChartButtonText() {
if (this.store.currentTab === StudyViewPageTabKeyEnum.SUMMARY) {
return getButtonNameWithDownPointer('Charts');
} else if (
this.store.currentTab === StudyViewPageTabKeyEnum.CLINICAL_DATA
) {
return getButtonNameWithDownPointer('Columns');
} else {
return '';
}
}
@computed
get groupsButton() {
return (
<>
{/* MODEL FOF USER OF INFO BEACON. YOU NEED TO CUSTOMIZE COMPONENT FOR USE CASE */}
{/**/}
{/* */}
{/* */}
{/* */}
{/* */}
{
const arrowEl = tooltipEl.querySelector(
'.rc-tooltip-arrow'
);
arrowEl.style.right = '10px';
}}
getTooltipContainer={() =>
document.getElementById(
'comparisonGroupManagerContainer'
)!
}
overlay={
}
>
{getButtonNameWithDownPointer('Groups')}
>
);
}
readonly resourceTabs = MakeMobxView({
await: () => [
this.store.resourceDefinitions,
this.store.resourceIdToResourceData,
],
render: () => {
const openDefinitions = this.store.resourceDefinitions.result!.filter(
d => this.store.isResourceTabOpen(d.resourceId)
);
const sorted = _.sortBy(openDefinitions, d => d.priority);
const resourceDataById = this.store.resourceIdToResourceData
.result!;
const tabs: JSX.Element[] = sorted.reduce((list, def) => {
const data = resourceDataById[def.resourceId];
if (data && data.length > 0) {
list.push(
);
}
return list;
}, [] as JSX.Element[]);
return tabs;
},
});
content() {
return (
{this.showBookmarkModal && (
)}
{this.shareLinkModal && (
1
? `Share ${this.sharedGroups.length} Groups`
: 'Share Group'
}
urlPromise={this.getShareBookmarkUrl}
description={
'Please send the following link to users with whom you want to share the selected group(s).'
}
/>
)}
{this.shareCustomDataLinkModal && (
)}
{this.store.comparisonConfirmationModal}
{this.store.queriedSampleIdentifiers.isComplete &&
this.store.invalidSampleIds.isComplete &&
this.store.unknownQueriedIds.isComplete &&
this.store.displayedStudies.isComplete &&
this.store.queriedPhysicalStudies.isComplete &&
this.store.queriedPhysicalStudies.result.length > 0 && (
this.handleTabChange(id)
}
className="mainTabs"
unmountOnHide={false}
getPaginationWidth={() => {
return this.toolbarLeft;
}} // dont run into other study view UI
contentWindowExtra={
}
hrefRoot={buildCBioPortalPageUrl('study')}
>
{this.resourceTabs.component}
{() => {
// create element here to get correct mobx subscriber list
const summary = (
);
const buttons = (
);
return (
{summary}
{buttons}
{buttons}
);
}}
{this.enableCustomSelectionInTabs.includes(
this.store.currentTab
) && (
<>
(this.showCustomSelectTooltip = !!visible)
}
destroyTooltipOnHide={true}
overlay={() => (
{
this.showCustomSelectTooltip = false;
this.store.updateCustomSelect(
chart
);
}}
/>
)}
>
{getButtonNameWithDownPointer(
'Custom Selection'
)}
>
)}
{getServerConfig()
.skin_show_settings_menu && (
}
customDriverSourceName={
getServerConfig()
.oncoprint_custom_driver_annotation_binary_menu_label!
}
showDriverAnnotationSection={
this.store
.doShowDriverAnnotationSectionInGlobalMenu
}
showTierAnnotationSection={
this.store
.doShowTierAnnotationSectionInGlobalMenu
}
/>
}
visible={
this
.showAlterationFilterTooltip
}
onVisibleChange={visible => {
this.showAlterationFilterTooltip = !!visible;
}}
>
{getButtonNameWithDownPointer(
'Alteration Filter'
)}
)}
{this.enableAddChartInTabs.includes(
this.store.currentTab
) && (
{
this.showReturnToDefaultChartListModal = true;
}}
openShareCustomDataUrlModal={
this
.openShareCustomDataUrlModal
}
isShareLinkModalVisible={
this
.shareCustomDataLinkModal
}
/>
)}
{
this.showReturnToDefaultChartListModal = false;
}}
keyboard
>
Reset charts
Please confirm that you
would like to replace the
current charts with the
default list.
{
this.store.resetToDefaultChartSettings();
this.showReturnToDefaultChartListModal = false;
}}
>
Confirm
{
this.showReturnToDefaultChartListModal = false;
}}
>
Cancel
{ServerConfigHelpers.sessionServiceIsEnabled() &&
this.groupsButton}
)}
);
}
private readonly body = MakeMobxView({
await: () => [
this.store.unknownQueriedIds,
this.store.queriedPhysicalStudyIds,
],
render: () => {
// we can tell if there are any valid studies
// by looking to see if there is anything in queriedPhysicalStudyIds
// we have to do this because studyIds property has the virtualStudy id (in that setting)
if (
this.store.unknownQueriedIds.result.length &&
this.store.queriedPhysicalStudyIds.result.length === 0
) {
const pluralForm =
this.store.unknownQueriedIds.result.length > 1
? 'Studies'
: 'Study';
return (
);
} else {
return this.content();
}
},
});
componentWillUnmount(): void {
this.store.destroy();
clearInterval(this.toolbarLeftUpdater);
}
render() {
return (
{this.body.component}
);
}
}