Skip to content

Commit

Permalink
Add ability to spawn code notebooks from cBioPortal queries (#4856)
Browse files Browse the repository at this point in the history
* Add ability to spawn juypyeter lite code notebooks populated with data from Oncoprint
  • Loading branch information
gautamsarawagi authored Aug 19, 2024
1 parent aab9c4c commit 50adab2
Show file tree
Hide file tree
Showing 8 changed files with 397 additions and 4 deletions.
5 changes: 5 additions & 0 deletions notebook/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

## For using the notebook and its contents:

1. The code for the Jupyterlite extension and the environment can be accessed from [here](https://github.com/cBioPortal/cbio-jupyter).
2. The instructions to use it present in this [file](https://github.com/cBioPortal/cbio-jupyter/blob/main/README.md)
148 changes: 148 additions & 0 deletions src/pages/staticPages/tools/oncoprinter/JupyterNotebookModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { action, observable } from 'mobx';
import React from 'react';
import {
Modal,
Form,
FormControl,
FormGroup,
ControlLabel,
Button,
} from 'react-bootstrap';
import { buildCBioPortalPageUrl } from 'shared/api/urls';

interface FilenameModalProps {
show: boolean;
fileContent: string;
fileName: string;
handleClose: () => void;
}

interface FilenameModalState {
folderName: string;
validated: boolean;
errorMessage: string;
}

class JupyterNoteBookModal extends React.Component<
FilenameModalProps,
FilenameModalState
> {
public channel: BroadcastChannel;

constructor(props: FilenameModalProps) {
super(props);
this.state = {
folderName: '',
validated: false,
errorMessage: '',
};
this.channel = new BroadcastChannel('jupyter_channel');
}

componentDidMount() {
this.setState({ folderName: '' });
}

handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ folderName: event.target.value, errorMessage: '' });
};

@action
handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();

const { folderName } = this.state;

if (folderName.trim() === '' || /\s/.test(folderName)) {
this.setState({
validated: false,
errorMessage: 'Session name cannot be empty or contain spaces',
});
return;
}

const { fileContent, fileName } = this.props;

const data = {
type: 'from-cbioportal-to-jupyterlite',
fileContent: fileContent,
filename: `${fileName}.csv`,
folderName: folderName,
};

const jupyterNotebookTool = window.open(
'https://cbio-jupyter.netlify.app/lite/lab/index.html',
'_blank'
);

if (jupyterNotebookTool) {
const receiveMessage = (event: MessageEvent) => {
if (event.data.type === 'jupyterlite-ready') {
console.log('Now sending the data...');
jupyterNotebookTool.postMessage(data, '*');
window.removeEventListener('message', receiveMessage);
this.props.handleClose();
}
};

window.addEventListener('message', receiveMessage);
}

this.setState({ folderName: '', validated: false, errorMessage: '' });
// this.props.handleClose();
};

render() {
const { show, handleClose } = this.props;
const { folderName, errorMessage } = this.state;

return (
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Enter Session Name</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form
onSubmit={e =>
this.handleSubmit(
(e as unknown) as React.FormEvent<
HTMLFormElement
>
)
}
>
<FormGroup controlId="formFolderName">
<ControlLabel className="py-2">
Session Name
</ControlLabel>
<FormControl
type="text"
placeholder="Enter Session Name"
value={folderName}
onChange={e =>
this.handleChange(
(e as unknown) as React.ChangeEvent<
HTMLInputElement
>
)
}
required
/>
{errorMessage && (
<div style={{ color: 'red' }}>
{errorMessage}
</div>
)}
</FormGroup>
<Modal.Footer>
<Button onClick={handleClose}>Close</Button>
<Button type="submit">Open Jupyter Notebook</Button>
</Modal.Footer>
</Form>
</Modal.Body>
</Modal>
);
}
}

export default JupyterNoteBookModal;
60 changes: 60 additions & 0 deletions src/pages/staticPages/tools/oncoprinter/Oncoprinter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import ClinicalTrackColorPicker from 'shared/components/oncoprint/ClinicalTrackC
import classnames from 'classnames';
import { getDefaultClinicalAttributeColoringForStringDatatype } from './OncoprinterToolUtils';
import { OncoprintColorModal } from 'shared/components/oncoprint/OncoprintColorModal';
import JupyterNoteBookModal from './JupyterNotebookModal';
import { convertToCSV } from 'shared/lib/calculation/JSONtoCSV';

interface IOncoprinterProps {
divId: string;
Expand Down Expand Up @@ -73,6 +75,20 @@ export default class Oncoprinter extends React.Component<

@observable.ref public oncoprint: OncoprintJS | undefined = undefined;

@observable public showJupyterNotebookModal = false;
@observable private jupyterFileContent = '';
@observable private jupyterFileName = '';

@action
private openJupyterNotebookModal = () => {
this.showJupyterNotebookModal = true;
};

@action
private closeJupyterNotebookModal = () => {
this.showJupyterNotebookModal = false;
};

constructor(props: IOncoprinterProps) {
super(props);

Expand Down Expand Up @@ -280,6 +296,44 @@ export default class Oncoprinter extends React.Component<
file += `${caseId}\n`;
}
fileDownload(file, `OncoPrintSamples.txt`);
break;
case 'jupyterNoteBook':
const fieldsToKeep = [
'hugoGeneSymbol',
'alterationType',
'chr',
'startPosition',
'endPosition',
'referenceAllele',
'variantAllele',
'proteinChange',
'proteinPosStart',
'proteinPosEnd',
'mutationType',
'oncoKbOncogenic',
'patientId',
'sampleId',
'isHotspot',
];

if (
this.props.store._mutations &&
this.props.store._studyIds
) {
const allGenesMutationsCsv = convertToCSV(
this.props.store.mutationsDataProps,
fieldsToKeep
);

this.jupyterFileContent = allGenesMutationsCsv;

this.jupyterFileName = this.props.store.studyIdProps.join(
'&'
);

this.openJupyterNotebookModal();
}

break;
}
},
Expand Down Expand Up @@ -569,6 +623,12 @@ export default class Oncoprinter extends React.Component<
</div>
</div>
</div>
<JupyterNoteBookModal
show={this.showJupyterNotebookModal}
handleClose={this.closeJupyterNotebookModal}
fileContent={this.jupyterFileContent}
fileName={this.jupyterFileName}
/>
</div>
);
}
Expand Down
16 changes: 16 additions & 0 deletions src/pages/staticPages/tools/oncoprinter/OncoprinterStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ export default class OncoprinterStore {
@observable hideGermlineMutations = false;
@observable customDriverWarningHidden: boolean;

@observable _mutations: string | undefined = undefined;
@observable _studyIds: string | undefined = undefined;

@observable _userSelectedClinicalTracksColors: {
[trackLabel: string]: {
[attributeValue: string]: RGBAColor;
Expand Down Expand Up @@ -205,6 +208,19 @@ export default class OncoprinterStore {
this.initialize();
}

@action public setJupyterInput(mutations: string, studyIds: string) {
this._mutations = mutations;
this._studyIds = studyIds;
}

@computed get mutationsDataProps() {
if (this._mutations) return JSON.parse(this._mutations);
}

@computed get studyIdProps() {
if (this._studyIds) return JSON.parse(this._studyIds);
}

@computed get parsedGeneticInputLines() {
if (!this._geneticDataInput) {
return {
Expand Down
14 changes: 14 additions & 0 deletions src/pages/staticPages/tools/oncoprinter/OncoprinterTool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export default class OncoprinterTool extends React.Component<
@observable geneOrderInput = '';
@observable sampleOrderInput = '';

// jupyter incoming data
@observable mutations = '';
@observable studyIds = '';

constructor(props: IOncoprinterToolProps) {
super(props);
makeObservable(this);
Expand All @@ -76,11 +80,17 @@ export default class OncoprinterTool extends React.Component<
this.geneticDataInput = postData.genetic;
this.clinicalDataInput = postData.clinical;
this.heatmapDataInput = postData.heatmap;
this.studyIds = postData.studyIds;
this.mutations = postData.mutations;

this.doSubmit(
this.geneticDataInput,
this.clinicalDataInput,
this.heatmapDataInput
);

this.handleJupyterData(this.mutations, this.studyIds);

getBrowserWindow().clientPostedData = null;
}
}
Expand Down Expand Up @@ -163,6 +173,10 @@ export default class OncoprinterTool extends React.Component<
}
}

@action private handleJupyterData(mutations: string, studyIds: string) {
this.store.setJupyterInput(mutations, studyIds);
}

@autobind private geneticFileInputRef(input: HTMLInputElement | null) {
this.geneticFileInput = input;
}
Expand Down
Loading

0 comments on commit 50adab2

Please sign in to comment.