Browse Source

Merge pull request #8024 from ryantam626/rt.issue-7935

Add "Restart kernel, re-run whole notebook" button
T. George 5 years ago
parent
commit
d8c66c009d

+ 30 - 1
packages/notebook/src/default-toolbar.tsx

@@ -13,7 +13,9 @@ import {
   addToolbarButtonClass,
   ReactWidget,
   ToolbarButton,
-  ISessionContextDialogs
+  ISessionContextDialogs,
+  ISessionContext,
+  sessionContextDialogs
 } from '@jupyterlab/apputils';
 import { DocumentRegistry } from '@jupyterlab/docregistry';
 import * as nbformat from '@jupyterlab/nbformat';
@@ -21,6 +23,7 @@ import {
   addIcon,
   copyIcon,
   cutIcon,
+  fastForwardIcon,
   HTMLSelect,
   pasteIcon,
   runIcon,
@@ -150,6 +153,28 @@ export namespace ToolbarItems {
       tooltip: 'Run the selected cells and advance'
     });
   }
+  /**
+   * Create a restart run all toolbar item
+   */
+  export function createRestartRunAllButton(
+    panel: NotebookPanel,
+    dialogs?: ISessionContext.IDialogs
+  ): Widget {
+    return new ToolbarButton({
+      icon: fastForwardIcon,
+      onClick: () => {
+        void (dialogs ?? sessionContextDialogs)
+          .restart(panel.sessionContext)
+          .then(restarted => {
+            if (restarted) {
+              void NotebookActions.runAll(panel.content, panel.sessionContext);
+            }
+            return restarted;
+          });
+      },
+      tooltip: 'Restart the kernel, then re-run the whole notebook'
+    });
+  }
 
   /**
    * Create a cell type switcher item.
@@ -191,6 +216,10 @@ export namespace ToolbarItems {
           sessionDialogs
         )
       },
+      {
+        name: 'restart-and-run',
+        widget: createRestartRunAllButton(panel, sessionDialogs)
+      },
       { name: 'cellType', widget: createCellTypeItem(panel) },
       { name: 'spacer', widget: Toolbar.createSpacerItem() },
       {

+ 2 - 0
packages/ui-components/src/icon/iconimports.ts

@@ -31,6 +31,7 @@ import downloadSvgstr from '../../style/icons/toolbar/download.svg';
 import editSvgstr from '../../style/icons/toolbar/edit.svg';
 import ellipsesSvgstr from '../../style/icons/toolbar/ellipses.svg';
 import extensionSvgstr from '../../style/icons/sidebar/extension.svg';
+import fastForwardSvgstr from '../../style/icons/toolbar/fast-forward.svg';
 import fileSvgstr from '../../style/icons/filetype/file.svg';
 import fileUploadSvgstr from '../../style/icons/toolbar/file-upload.svg';
 import filterListSvgstr from '../../style/icons/toolbar/filter-list.svg';
@@ -98,6 +99,7 @@ export const downloadIcon = new LabIcon({ name: 'ui-components:download', svgstr
 export const editIcon = new LabIcon({ name: 'ui-components:edit', svgstr: editSvgstr });
 export const ellipsesIcon = new LabIcon({ name: 'ui-components:ellipses', svgstr: ellipsesSvgstr });
 export const extensionIcon = new LabIcon({ name: 'ui-components:extension', svgstr: extensionSvgstr });
+export const fastForwardIcon = new LabIcon({ name: 'ui-components:fast-forward', svgstr: fastForwardSvgstr });
 export const fileIcon = new LabIcon({ name: 'ui-components:file', svgstr: fileSvgstr });
 export const fileUploadIcon = new LabIcon({ name: 'ui-components:file-upload', svgstr: fileUploadSvgstr });
 export const filterListIcon = new LabIcon({ name: 'ui-components:filter-list', svgstr: filterListSvgstr });

+ 4 - 0
packages/ui-components/style/deprecated.css

@@ -35,6 +35,7 @@
   --jp-icon-edit: url('icons/toolbar/edit.svg');
   --jp-icon-ellipses: url('icons/toolbar/ellipses.svg');
   --jp-icon-extension: url('icons/sidebar/extension.svg');
+  --jp-icon-fast-forward: url('icons/toolbar/fast-forward.svg');
   --jp-icon-file-upload: url('icons/toolbar/file-upload.svg');
   --jp-icon-file: url('icons/filetype/file.svg');
   --jp-icon-filter-list: url('icons/toolbar/filter-list.svg');
@@ -150,6 +151,9 @@
 .jp-ExtensionIcon {
   background-image: var(--jp-icon-extension);
 }
+.jp-FastForwardIcon {
+  background-image: var(--jp-icon-fast-forward);
+}
 .jp-FileIcon {
   background-image: var(--jp-icon-file);
 }

+ 5 - 0
packages/ui-components/style/icons/toolbar/fast-forward.svg

@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+    <g class="jp-icon3" fill="#616161">
+        <path d="M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z"/>
+    </g>
+</svg>

+ 46 - 1
tests/test-notebook/src/default-toolbar.spec.ts

@@ -25,7 +25,8 @@ import {
   signalToPromise,
   sleep,
   NBTestUtils,
-  framePromise
+  framePromise,
+  acceptDialog
 } from '@jupyterlab/testutils';
 
 const JUPYTER_CELL_MIME = 'application/vnd.jupyter.cells';
@@ -216,6 +217,7 @@ describe('@jupyterlab/notebook', () => {
             'run',
             'interrupt',
             'restart',
+            'restart-and-run',
             'cellType',
             'spacer',
             'kernelName',
@@ -279,6 +281,49 @@ describe('@jupyterlab/notebook', () => {
           expect(button.node.querySelector("[data-icon$='run']")).to.exist;
         });
       });
+
+      describe('#createRestartRunAllButton()', () => {
+        it('should restart and run all when clicked', async () => {
+          const button = ToolbarItems.createRestartRunAllButton(panel);
+          const widget = panel.content;
+
+          // Clear the first two cells.
+          const codeCell = widget.widgets[0] as CodeCell;
+          codeCell.model.outputs.clear();
+          const mdCell = widget.widgets[1] as MarkdownCell;
+          mdCell.rendered = false;
+
+          Widget.attach(button, document.body);
+          const p = new PromiseDelegate();
+          context.sessionContext.statusChanged.connect((sender, status) => {
+            // Find the right status idle message
+            if (status === 'idle' && codeCell.model.outputs.length > 0) {
+              expect(
+                widget.widgets
+                  .filter(cell => cell.model.type === 'markdown')
+                  .every(cell => (cell as MarkdownCell).rendered)
+              );
+              expect(widget.activeCellIndex).to.equal(
+                widget.widgets.filter(cell => cell.model.type === 'code').length
+              );
+              button.dispose();
+              p.resolve(0);
+            }
+          });
+          await framePromise();
+          simulate(button.node.firstChild as HTMLElement, 'mousedown');
+          await acceptDialog();
+          await p.promise;
+        }).timeout(30000); // Allow for slower CI
+
+        it("should add an inline svg node with the 'fast-forward' icon", async () => {
+          const button = ToolbarItems.createRestartRunAllButton(panel);
+          Widget.attach(button, document.body);
+          await framePromise();
+          expect(button.node.querySelector("[data-icon$='fast-forward']")).to
+            .exist;
+        });
+      });
     });
   });
 });