diff --git a/js/notebook/src/SparkUI.ts b/js/notebook/src/SparkUI.ts index b7f036cbab..633b780a0c 100644 --- a/js/notebook/src/SparkUI.ts +++ b/js/notebook/src/SparkUI.ts @@ -17,6 +17,8 @@ import {Widget} from "@phosphor/widgets"; import BeakerXApi from "./tree/Utils/BeakerXApi"; import widgets from './widgets'; +import Timer = NodeJS.Timer; +import {ToolbarSparkConnectionStatus} from "./sparkUI/toolbarSparkConnectionStatus"; const bkUtils = require("./shared/bkUtils"); @@ -37,20 +39,20 @@ export class SparkUIModel extends widgets.VBoxModel { } export class SparkUIView extends widgets.VBoxView { - private sparkStats: Widget; - private toolbarSparkStats: Widget; + sparkStats: Widget; + connectionStatusElement: HTMLElement; + private sparkAppId: string; private sparkUiWebUrl: string; private sparkMasterUrl: string; - private apiCallIntervalId: number; - private toolbarStatusContainer: HTMLElement|null; + private apiCallIntervalId: Timer; private connectionLabelActive: HTMLElement; private connectionLabelMemory: HTMLElement; private connectionLabelDead: HTMLElement; - private connectionStatusElement: HTMLElement; private masterUrlInput: HTMLInputElement; private executorCoresInput: HTMLInputElement; private executorMemoryInput: HTMLInputElement; + private toolbarSparkConnectionStatus: ToolbarSparkConnectionStatus; initialize(parameters) { super.initialize(parameters); @@ -59,7 +61,8 @@ export class SparkUIView extends widgets.VBoxView { this.openExecutors = this.openExecutors.bind(this); this.updateChildren = this.updateChildren.bind(this); this.toggleExecutorConfigInputs = this.toggleExecutorConfigInputs.bind(this); - this.handleToolbarSparkClick = this.handleToolbarSparkClick.bind(this); + + this.toolbarSparkConnectionStatus = new ToolbarSparkConnectionStatus(this); } public render(): void { @@ -79,6 +82,14 @@ export class SparkUIView extends widgets.VBoxView { this.updateChildren(); } + public openWebUi(): void { + window.open(this.sparkUiWebUrl, '_blank'); + } + + public openExecutors(): void { + window.open(`${this.sparkUiWebUrl}/executors`, '_blank'); + } + private addSparkUrls() { if (!this.connectionStatusElement) { this.connectionStatusElement = this.el.querySelector('.bx-connection-status'); @@ -88,11 +99,11 @@ export class SparkUIView extends widgets.VBoxView { return; } - this.addSparUiWebUrl(); + this.addSparkUiWebUrl(); this.addMasterUrl(); } - private addSparUiWebUrl(): void { + private addSparkUiWebUrl(): void { this.sparkUiWebUrl = this.model.get("sparkUiWebUrl"); if (!this.sparkUiWebUrl) { @@ -105,28 +116,7 @@ export class SparkUIView extends widgets.VBoxView { this.sparkStats.node.addEventListener('click', this.openExecutors); this.connectionStatusElement.style.cursor = 'pointer'; this.sparkStats.node.style.cursor = 'pointer'; - - this.bindToolbarSparkEvents(); - } - - private bindToolbarSparkEvents(): void { - if (!this.toolbarSparkStats) { - return; - } - - this.toolbarSparkStats.node.addEventListener('click', this.handleToolbarSparkClick); - } - - private handleToolbarSparkClick(event): void { - const relatedTarget = (event.relatedTarget || event.target) as HTMLElement; - - if (relatedTarget.classList.contains('bx-connection-status')) { - return this.openWebUi(); - } - - if (relatedTarget.classList.contains('label')) { - return this.openExecutors(); - } + this.toolbarSparkConnectionStatus.bindToolbarSparkEvents(); } private addMasterUrl() { @@ -161,14 +151,6 @@ export class SparkUIView extends widgets.VBoxView { } } - private openWebUi() { - window.open(this.sparkUiWebUrl, '_blank'); - } - - private openExecutors() { - window.open(`${this.sparkUiWebUrl}/executors`, '_blank'); - } - private updateChildren() { const noop = () => {}; @@ -179,7 +161,7 @@ export class SparkUIView extends widgets.VBoxView { this.resolveChildren(view) .then((views) => { this.handleLocalMasterUrl(); - this.appendToToolbar(); + this.toolbarSparkConnectionStatus.append(); this.addSparkUrls(); }) .catch(noop); @@ -190,7 +172,7 @@ export class SparkUIView extends widgets.VBoxView { } private resolveChildren(view) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { if (!view || !view.children_views) { reject(); } @@ -224,53 +206,6 @@ export class SparkUIView extends widgets.VBoxView { this.connectionStatusElement.insertAdjacentElement('afterend', this.sparkStats.node); } - private appendToToolbar(): void { - if (!this.el.querySelector('.bx-status-panel')) { - return; - } - - if (this.toolbarStatusContainer && this.toolbarStatusContainer.contains(this.toolbarSparkStats.node)) { - this.propagateToolbarWidget(); - - return; - } - - const toolbarContainer = this.el.closest('.jp-NotebookPanel') || this.el.closest('.notebook_app'); - - if (!toolbarContainer) { - return; - } - - const toolbar = toolbarContainer.querySelector('#maintoolbar-container') || toolbarContainer.querySelector('.jp-NotebookPanel-toolbar'); - - if (!toolbar) { - return; - } - - this.toolbarSparkStats = new Widget(); - this.toolbarStatusContainer = toolbar; - this.toolbarSparkStats.node.classList.add('bx-toolbar-spark-widget'); - this.propagateToolbarWidget(); - this.appendToolbarSparkStats(); - } - - private propagateToolbarWidget() { - this.toolbarSparkStats.node.innerHTML = `
- ${this.connectionStatusElement.outerHTML} - ${this.sparkStats.node.outerHTML} -
`; - } - - private appendToolbarSparkStats() { - const spacer: HTMLElement|null = this.toolbarStatusContainer.querySelector('.jp-Toolbar-spacer'); - - if (spacer) { - spacer.insertAdjacentElement("afterend", this.toolbarSparkStats.node); - } else { - this.toolbarStatusContainer.appendChild(this.toolbarSparkStats.node); - } - } - private connectToApi() { let baseUrl; let api; @@ -300,12 +235,14 @@ export class SparkUIView extends widgets.VBoxView { const response = await fetch(sparkUrl, { method: 'GET', credentials: 'include' }); if (!response.ok) { + this.toolbarSparkConnectionStatus.destroy(); return this.clearApiCallInterval(); } const data = await response.json(); this.updateMetrics(data); } catch(error) { + this.toolbarSparkConnectionStatus.destroy(); this.clearApiCallInterval(); } }; @@ -319,7 +256,7 @@ export class SparkUIView extends widgets.VBoxView { this.sparkAppId = null; if (!this.el.querySelector('.bx-status-panel')) { - this.toolbarSparkStats.node.innerHTML = ''; + this.toolbarSparkConnectionStatus.clear(); } } @@ -340,7 +277,7 @@ export class SparkUIView extends widgets.VBoxView { this.connectionLabelActive.innerText = `${activeTasks}`; this.connectionLabelMemory.innerText = `${bkUtils.formatBytes(storageMemory)}`; this.connectionLabelDead.innerText = `${deadExecutors}`; - this.propagateToolbarWidget(); + this.toolbarSparkConnectionStatus.propagateToolbarWidget(); } private addSparkMetricsWidget() { @@ -350,7 +287,7 @@ export class SparkUIView extends widgets.VBoxView { views.forEach((view) => { if (view instanceof widgets.LabelView && view.el.classList.contains('bx-connection-status')) { this.createSparkMetricsWidget(); - this.appendToToolbar(); + this.toolbarSparkConnectionStatus.append(); this.addSparkUrls(); } }); @@ -363,7 +300,11 @@ export class SparkUIView extends widgets.VBoxView { super.dispose(); this.clearApiCallInterval(); this.sparkStats && this.sparkStats.isAttached && this.sparkStats.dispose(); - this.toolbarSparkStats && this.toolbarSparkStats.isAttached && this.toolbarSparkStats.dispose(); + this.toolbarSparkConnectionStatus.destroy(); + } + + remove() { + this.toolbarSparkConnectionStatus.destroy(); } } diff --git a/js/notebook/src/sparkUI/toolbarSparkConnectionStatus.ts b/js/notebook/src/sparkUI/toolbarSparkConnectionStatus.ts new file mode 100644 index 0000000000..de5d9d6bd0 --- /dev/null +++ b/js/notebook/src/sparkUI/toolbarSparkConnectionStatus.ts @@ -0,0 +1,114 @@ +/* + * Copyright 2018 TWO SIGMA OPEN SOURCE, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {SparkUIView} from "../SparkUI"; +import {Widget} from "@phosphor/widgets"; + +export class ToolbarSparkConnectionStatus { + private sparkUIView: SparkUIView; + private toolbarSparkStats: Widget; + private toolbarStatusContainer: HTMLElement|null; + + constructor(sparkUIView: SparkUIView) { + this.sparkUIView = sparkUIView; + + this.handleToolbarSparkClick = this.handleToolbarSparkClick.bind(this); + } + + bindToolbarSparkEvents(): void { + if (!this.toolbarSparkStats) { + return; + } + + this.toolbarSparkStats.node.addEventListener('click', this.handleToolbarSparkClick); + } + + destroy() { + this.clear(); + this.toolbarSparkStats && this.toolbarSparkStats.isAttached && this.toolbarSparkStats.dispose(); + } + + clear() { + this.toolbarSparkStats.node.innerHTML = ''; + } + + propagateToolbarWidget() { + this.toolbarSparkStats.node.innerHTML = `
+ ${this.sparkUIView.connectionStatusElement.outerHTML} + ${this.sparkUIView.sparkStats.node.outerHTML} +
`; + } + + append(): void { + if (!this.sparkUIView.el.querySelector('.bx-status-panel')) { + return; + } + + if ( + this.toolbarStatusContainer + && this.toolbarStatusContainer.contains(this.toolbarSparkStats.node) + ) { + this.propagateToolbarWidget(); + + return; + } + + const toolbarContainer = this.sparkUIView.el.closest('.jp-NotebookPanel') + || this.sparkUIView.el.closest('.notebook_app'); + + if (!toolbarContainer) { + return; + } + + const toolbar = toolbarContainer.querySelector('#maintoolbar-container') + || toolbarContainer.querySelector('.jp-NotebookPanel-toolbar'); + + if (!toolbar) { + return; + } + + this.toolbarSparkStats = new Widget(); + this.toolbarStatusContainer = toolbar; + this.toolbarSparkStats.node.classList.add('bx-toolbar-spark-widget'); + this.propagateToolbarWidget(); + this.appendToolbarSparkStats(); + } + + private handleToolbarSparkClick(event): void { + const relatedTarget = (event.relatedTarget || event.target) as HTMLElement; + + if (relatedTarget.classList.contains('bx-connection-status')) { + return this.sparkUIView.openWebUi(); + } + + if (relatedTarget.classList.contains('label')) { + return this.sparkUIView.openExecutors(); + } + } + + private appendToolbarSparkStats() { + const previous = this.toolbarStatusContainer.querySelector('.bx-toolbar-spark-widget'); + const spacer: HTMLElement|null = this.toolbarStatusContainer.querySelector('.jp-Toolbar-spacer'); + + previous && this.toolbarStatusContainer.removeChild(previous); + + if (spacer) { + spacer.insertAdjacentElement("afterend", this.toolbarSparkStats.node); + } else { + this.toolbarStatusContainer.appendChild(this.toolbarSparkStats.node); + } + } +}