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={
} >
); } 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 ); }} />
)} >
)} {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; }} > )} {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.
{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} ); } }