Skip to content

Commit

Permalink
feat: provide UI for hiding/showing code cells (#870)
Browse files Browse the repository at this point in the history
  • Loading branch information
cramakri committed Apr 15, 2020
1 parent 825c8b0 commit f8cbb02
Show file tree
Hide file tree
Showing 2 changed files with 222 additions and 43 deletions.
152 changes: 110 additions & 42 deletions src/file/File.present.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,11 @@
* limitations under the License.
*/

import React from "react";
import React, { useState } from "react";
import NotebookPreview from "@nteract/notebook-render";

// Do not import the style because this does not work after webpack bundles things for production mode.
// Instead define the style below
//import './notebook.css'
import { Card, CardHeader, CardBody, Badge } from "reactstrap";
import { Button, ButtonGroup } from "reactstrap";
import { ListGroup, ListGroupItem } from "reactstrap";
import "../../node_modules/highlight.js/styles/atom-one-light.css";
import { faProjectDiagram } from "@fortawesome/free-solid-svg-icons";
Expand Down Expand Up @@ -130,11 +128,7 @@ class ShowFile extends React.Component {
return (
<FileCard
gitLabUrl={this.props.externalUrl}
filePath={this.props.gitLabFilePath
.split("\\")
.pop()
.split("/")
.pop()}
filePath={this.props.gitLabFilePath.split("\\").pop().split("/").pop()}
commit={this.props.commit}
buttonGraph={buttonGraph}
buttonGit={buttonGit}
Expand Down Expand Up @@ -178,50 +172,124 @@ class ShowFile extends React.Component {
}
}

/**
* Modes of showing notebook source code.
*/
const NotebookSourceDisplayMode = {
DEFAULT: "DEFAULT",
SHOWN: "SHOWN",
HIDDEN: "HIDDEN",
};
/**
* Modify the cell metadata according to the hidden policy
*
* @param {object} [cell] - The cell to process
* @param {array} [accum] - The place to store the result
*/
function tweakCellMetadataHidden(cell, accum) {
const clone = { ...cell };
clone.metadata = { ...cell.metadata };
clone.metadata.hide_input = true;
accum.push(clone);
}

/**
* Modify the cell metadata according to the show policy
*
* @param {object} [cell] - The cell to process
* @param {array} [accum] - The place to store the result
*/
function tweakCellMetadataShow(cell, accum) {
const clone = { ...cell };
clone.metadata = { ...cell.metadata };
clone.metadata.hide_input = false;
accum.push(clone);
}

/**
* Modify the cell metadata according to the default policy
*
* @param {object} [cell] - The cell to process
* @param {array} [accum] - The place to store the result
*/
function tweakCellMetadataDefault(cell, accum) {
if (cell.metadata.jupyter == null) {
accum.push(cell);
}
else {
const clone = { ...cell };
clone.metadata = { ...cell.metadata };
clone.metadata.hide_input = clone.metadata.jupyter.source_hidden;
accum.push(clone);
}
}

/**
* Modify the notebook metadata so it is correctly processed by the renderer.
*
* @param {object} [nb] - The notebook to process
* @param {string} [displayMode] - The mode to use to process the notebook
*/
function tweakCellMetadata(nb) {
function tweakCellMetadata(nb, displayMode = NotebookSourceDisplayMode.DEFAULT) {
// Scan the cell metadata, and, if jupyter.source_hidden === true, set hide_input = true
const result = { ...nb };
result.cells = [];
nb.cells.forEach(cell => {
if (cell.metadata.jupyter == null) {
result.cells.push(cell);
}
else {
const clone = { ...cell };
clone.metadata = { ...cell.metadata };
clone.metadata.hide_input = clone.metadata.jupyter.source_hidden;
result.cells.push(clone);
}
});
const cellMetadataFunction =
displayMode === NotebookSourceDisplayMode.DEFAULT
? tweakCellMetadataDefault
: displayMode === NotebookSourceDisplayMode.HIDDEN ?
tweakCellMetadataHidden
: tweakCellMetadataShow;
nb.cells.forEach((cell) => cellMetadataFunction(cell, result.cells));
if (displayMode === NotebookSourceDisplayMode.SHOWN) {
// Set the hide_input to false;
result["metadata"] = { ...result["metadata"] };
result["metadata"]["hide_input"] = false;
}
return result;
}

class StyledNotebook extends React.Component {
componentDidMount() {
// TODO go through the dom and modify the nodes, e.g., with D3
//this.fixUpDom(ReactDOM.findDOMNode(this.notebook));
}
function StyledNotebook(props) {
const [displayMode, setDisplayMode] = useState(NotebookSourceDisplayMode.DEFAULT);

render() {
if (this.props.notebook == null) return <div>Loading...</div>;
const notebook = tweakCellMetadata(this.props.notebook);
return [
<NotebookPreview
key="notebook"
ref={c => {
this.notebook = c;
}}
defaultStyle={false}
loadMathjax={false}
notebook={notebook}
/>
];
}
if (props.notebook == null) return <div>Loading...</div>;

const notebook = tweakCellMetadata(props.notebook, displayMode);
return [
<ListGroup key="controls" flush>
<ListGroupItem>
<div>
<span>
<b>Code Visibility &nbsp;</b>
</span>
<ButtonGroup key="controls" size="sm">
<Button
color="primary"
onClick={() => setDisplayMode(NotebookSourceDisplayMode.DEFAULT)}
active={displayMode === NotebookSourceDisplayMode.DEFAULT}
>
Default
</Button>
<Button
color="primary"
onClick={() => setDisplayMode(NotebookSourceDisplayMode.SHOWN)}
active={displayMode === NotebookSourceDisplayMode.SHOWN}
>
Visible
</Button>
<Button
color="primary"
onClick={() => setDisplayMode(NotebookSourceDisplayMode.HIDDEN)}
active={displayMode === NotebookSourceDisplayMode.HIDDEN}
>
Hidden
</Button>
</ButtonGroup>
</div>
</ListGroupItem>
</ListGroup>,
<NotebookPreview key="notebook" defaultStyle={false} loadMathjax={false} notebook={notebook} />,
];
}

class JupyterButtonPresent extends React.Component {
Expand All @@ -243,4 +311,4 @@ class JupyterButtonPresent extends React.Component {
}
}

export { StyledNotebook, JupyterButtonPresent, ShowFile, tweakCellMetadata };
export { StyledNotebook, JupyterButtonPresent, ShowFile, tweakCellMetadata, NotebookSourceDisplayMode };
113 changes: 112 additions & 1 deletion src/file/File.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { testClient as client } from "../api-client";
import { generateFakeUser } from "../user/User.test";
import { ShowFile, JupyterButton } from "./index";
import { StateModel, globalSchema } from "../model";
import { tweakCellMetadata } from "./File.present";
import { NotebookSourceDisplayMode, tweakCellMetadata } from "./File.present";

const model = new StateModel(globalSchema);

Expand Down Expand Up @@ -211,4 +211,115 @@ describe("cell metadata messaging", () => {
expect(notebook.cells[1].metadata.hide_input).toEqual(undefined);
expect(result.cells[1].metadata.hide_input).toEqual(true);
});

const modeNotebook = {
cells: [
{
cell_type: "code",
execution_count: 3,
metadata: {},
outputs: [
{
name: "stdout",
output_type: "stream",
text: ["show input\n"]
}
],
source: ['print("show input")']
},
{
cell_type: "code",
execution_count: 4,
metadata: {
jupyter: {
source_hidden: true
},
papermill: {
duration: 0.48317,
end_time: "2020-03-31T08:27:03.045655",
exception: false,
start_time: "2020-03-31T08:27:02.562485",
status: "completed"
},
tags: []
},
outputs: [
{
name: "stdout",
output_type: "stream",
text: ["hide input\n"]
}
],
source: ['print("hide input")']
},
{
cell_type: "code",
execution_count: 5,
metadata: {
hide_input: true,
papermill: {
duration: 0.48317,
end_time: "2020-03-31T08:27:03.045655",
exception: false,
start_time: "2020-03-31T08:27:02.562485",
status: "completed"
},
tags: []
},
outputs: [
{
name: "stdout",
output_type: "stream",
text: ["hide too\n"]
}
],
source: ['print("hide too")']
}
],
metadata: {
hide_input: true,
kernelspec: {
display_name: "Python 3",
language: "python",
name: "python3"
},
language_info: {
codemirror_mode: {
name: "ipython",
version: 3
},
file_extension: ".py",
mimetype: "text/x-python",
name: "python",
nbconvert_exporter: "python",
pygments_lexer: "ipython3",
version: "3.7.4"
}
},
nbformat: 4,
nbformat_minor: 4
};

it("handles default mode correctly", () => {
const result = tweakCellMetadata(modeNotebook, NotebookSourceDisplayMode.DEFAULT);
expect(result.cells[0]).toEqual(modeNotebook.cells[0]);
expect(modeNotebook.cells[1].metadata.hide_input).toEqual(undefined);
expect(result.cells[1].metadata.hide_input).toEqual(true);
});
it("handles hidden mode correctly", () => {
const result = tweakCellMetadata(modeNotebook, NotebookSourceDisplayMode.HIDDEN);
expect(modeNotebook.cells[0].metadata.hide_input).toEqual(undefined);
expect(result.cells[0].metadata.hide_input).toEqual(true);
expect(modeNotebook.cells[1].metadata.hide_input).toEqual(undefined);
expect(result.cells[1].metadata.hide_input).toEqual(true);
});
it("handles shown mode correctly", () => {
const result = tweakCellMetadata(modeNotebook, NotebookSourceDisplayMode.SHOWN);
expect(modeNotebook.cells[0].metadata.hide_input).toEqual(undefined);
expect(result.cells[0].metadata.hide_input).toEqual(false);
expect(modeNotebook.cells[1].metadata.hide_input).toEqual(undefined);
expect(result.cells[1].metadata.hide_input).toEqual(false);
expect(modeNotebook.metadata.hide_input).toEqual(true);
expect(result.metadata.hide_input).toEqual(false);
});
});

0 comments on commit f8cbb02

Please sign in to comment.