From bb4bb27f96aa4009262f474281dcc5da8230feb9 Mon Sep 17 00:00:00 2001 From: Diogo Castro Date: Fri, 19 Jan 2024 11:06:29 +0100 Subject: [PATCH] Linting and pretyfying Makes checks pass --- README.md | 24 +- package.json | 444 +++++++++--------- prettier.config.js | 8 +- src/components/cell-monitor.tsx | 45 +- src/components/error-boundary.tsx | 36 +- src/components/header.tsx | 187 ++++---- src/components/index.tsx | 18 +- src/components/job-table.tsx | 242 +++++----- src/components/lazy-task-chart.tsx | 14 +- src/components/lazy-timeline.tsx | 14 +- src/components/progress-bar.tsx | 46 +- src/components/task-chart.tsx | 238 +++++----- src/components/timeline.tsx | 141 +++--- src/lab-extension/current-cell.ts | 178 +++---- src/lab-extension/index.ts | 112 ++--- src/lab-extension/jupyterlab-sparkmonitor.ts | 325 ++++++------- src/notebook-extension/currentcell.ts | 70 +-- src/notebook-extension/entry.js | 4 +- src/notebook-extension/index.ts | 8 +- .../jupyter-notebook-monitor.ts | 293 ++++++------ src/notebook-extension/webpack.config.js | 103 ++-- src/store/cell.ts | 87 ++-- src/store/index.ts | 18 +- src/store/notebook.ts | 439 ++++++++--------- src/store/spark-job.ts | 92 ++-- src/store/spark-stage.ts | 36 +- src/store/task-chart-store.ts | 101 ++-- style/jobtable.css | 210 ++++----- style/lab.css | 6 +- style/lab.js | 2 +- style/notebook.css | 2 +- style/styles.css | 252 +++++----- style/taskdetails.css | 146 +++--- style/timeline.css | 132 +++--- tsconfig.lab.json | 12 +- tsconfig.notebook.json | 4 +- 36 files changed, 2102 insertions(+), 1987 deletions(-) diff --git a/README.md b/README.md index f76a379..f11e3a8 100644 --- a/README.md +++ b/README.md @@ -21,15 +21,15 @@ SparkMonitor is an extension for Jupyter Notebook & Lab that enables the live mo ## Requirements -- Jupyter Lab 4 OR Jupyter Notebook 4.4.0 or higher -- pyspark 2 or 3 +- Jupyter Lab 4 OR Jupyter Notebook 4.4.0 or higher +- pyspark 2 or 3 ## Features -- Automatically displays a live monitoring tool below cells that run Spark jobs in a Jupyter notebook -- A table of jobs and stages with progressbars -- A timeline which shows jobs, stages, and tasks -- A graph showing number of active tasks & executor cores vs time +- Automatically displays a live monitoring tool below cells that run Spark jobs in a Jupyter notebook +- A table of jobs and stages with progressbars +- A timeline which shows jobs, stages, and tasks +- A graph showing number of active tasks & executor cores vs time @@ -100,18 +100,18 @@ sbt +package ## History -- This project was originally written by krishnan-r as a [Google Summer of Code project](https://github.com/krishnan-r/sparkmonitor) for Jupyter Notebook with the [SWAN](https://swan.web.cern.ch/swan/) Notebook Service team at [CERN](http://home.cern/). +- This project was originally written by krishnan-r as a [Google Summer of Code project](https://github.com/krishnan-r/sparkmonitor) for Jupyter Notebook with the [SWAN](https://swan.web.cern.ch/swan/) Notebook Service team at [CERN](http://home.cern/). -- Further fixes and improvements were made by the team at CERN and members of the community maintained at [swan-cern/jupyter-extensions/tree/master/SparkMonitor](https://github.com/swan-cern/jupyter-extensions/tree/master/SparkMonitor) +- Further fixes and improvements were made by the team at CERN and members of the community maintained at [swan-cern/jupyter-extensions/tree/master/SparkMonitor](https://github.com/swan-cern/jupyter-extensions/tree/master/SparkMonitor) -- [Jafer Haider](https://github.com/itsjafer) created the fork [jupyterlab-sparkmonitor](https://github.com/itsjafer/jupyterlab-sparkmonitor) to update the extension to be compatible with JupyterLab as part of his internship at Yelp. - -- This repository merges all the work done above and provides support for Lab & Notebook from a single package. +- [Jafer Haider](https://github.com/itsjafer) created the fork [jupyterlab-sparkmonitor](https://github.com/itsjafer/jupyterlab-sparkmonitor) to update the extension to be compatible with JupyterLab as part of his internship at Yelp. +- This repository merges all the work done above and provides support for Lab & Notebook from a single package. ## Changelog + This repository is published to pypi as [sparkmonitor](https://pypi.org/project/sparkmonitor/) - 2.x see the [github releases page](https://github.com/swan-cern/sparkmonitor/releases) of this repository -- 1.x and below were published from [swan-cern/jupyter-extensions](https://github.com/swan-cern/jupyter-extensions) and some initial versions from [krishnan-r/sparkmonitor](https://github.com/krishnan-r/sparkmonitor) \ No newline at end of file +- 1.x and below were published from [swan-cern/jupyter-extensions](https://github.com/swan-cern/jupyter-extensions) and some initial versions from [krishnan-r/sparkmonitor](https://github.com/krishnan-r/sparkmonitor) diff --git a/package.json b/package.json index 2399d9e..a9be315 100644 --- a/package.json +++ b/package.json @@ -1,230 +1,230 @@ { - "name": "sparkmonitor", - "version": "3.0.1", - "description": "Jupyter Notebook & Lab extension to monitor Apache Spark jobs from a notebook", - "repository": { - "type": "git", - "url": "git+https://github.com/swan-cern/sparkmonitor.git" - }, - "keywords": [ - "jupyter", - "jupyterlab", - "jupyterlab-extension", - "Spark", - "sparkmonitor" - ], - "main": "lib/lab-extension/index.js", - "author": { - "name": "Krishnan R", - "email": "krishnanr1997@gmail.com" - }, - "maintainers": [ - { - "name": "SWAN Team", - "email": "swan-admins@cern.ch" - } - ], - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/swan-cern/sparkmonitor/issues" - }, - "homepage": "https://github.com/swan-cern/sparkmonitor#readme", - "scripts": { - "build": "jlpm run build:lib && jlpm run build:labextension:dev", - "build:prod": "jlpm clean && jlpm run build:lib:prod && jlpm run build:labextension && jlpm run build:nbextension && jlpm run build:scalalistener", - "build:labextension": "jupyter labextension build .", - "build:labextension:dev": "jupyter labextension build --development True .", - "build:lib": "tsc -p tsconfig.lab.json --sourceMap", - "build:lib:prod": "tsc -p tsconfig.lab.json", - "build:nbextension": "webpack --config src/notebook-extension/webpack.config.js", - "build:scalalistener": "cd scalalistener && sbt +assembly", - "clean": "jlpm clean:lib", - "clean:lib": "rimraf lib tsconfig.tsbuildinfo", - "clean:lintcache": "rimraf .eslintcache .stylelintcache", - "clean:labextension": "rimraf sparkmonitor/labextension sparkmonitor/_version.py", - "clean:nbextension": "rimraf sparkmonitor/nbextension", - "clean:scalalistener": "rimraf sparkmonitor/*.jar", - "clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:nbextension && jlpm clean:scalalistener && jlpm clean:lintcache", - "eslint": "jlpm eslint:check --fix", - "eslint:check": "eslint . --cache --ext .ts,.tsx", - "install:extension": "jlpm build", - "lint": "jlpm stylelint && jlpm prettier && jlpm eslint", - "lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check", - "prettier": "jlpm prettier:base --write --list-different", - "prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", - "prettier:check": "jlpm prettier:base --check", - "stylelint": "jlpm stylelint:check --fix", - "stylelint:check": "stylelint --cache \"style/**/*.css\"", - "watch": "run-p watch:src watch:labextension", - "watch:src": "tsc -w -p tsconfig.lab.json --sourceMap", - "watch:labextension": "jupyter labextension watch .", - "watch:nbextension": "webpack --config src/notebook-extension/webpack.config.js --mode development --watch", - "check:all": "jlpm run lint:check && jlpm run check:nbextension && jlpm run check:labextension", - "check:labextension": "tsc -p tsconfig.lab.json --noEmit", - "check:nbextension": "tsc -p tsconfig.notebook.json" - }, - "dependencies": { - "@jupyterlab/application": "^4.0.10", - "@jupyterlab/apputils": "^4.1.10", - "@jupyterlab/cells": "^4.0.10", - "@jupyterlab/mainmenu": "^4.0.10", - "@jupyterlab/notebook": "^4.0.10", - "@jupyterlab/services": "^7.0.10", - "@lumino/coreutils": "^2.0.0", - "@lumino/widgets": "^2.0.1", - "hammerjs": "^2.0.8", - "keycharm": "^0.4.0", - "mobx": "^6.12.0", - "mobx-react-lite": "^4.0.5", - "moment": "^2.29.4", - "plotly.js-basic-dist": "^2.27.1", - "pretty-ms": "^8.0.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-plotly.js": "^2.6.0", - "react-timeago": "^7.2.0", - "vis-data": "^7.1.9", - "vis-timeline": "^7.7.3", - "vis-util": "^5.0.7", - "xss": "^1.0.14" - }, - "devDependencies": { - "@babel/core": "^7.23.6", - "@babel/preset-env": "^7.23.6", - "@babel/preset-react": "^7.23.3", - "@babel/preset-typescript": "^7.23.3", - "@jupyterlab/builder": "^4.0.0", - "@types/hammerjs": "^2.0.45", - "@types/json-schema": "^7.0.11", - "@types/plotly.js-basic-dist": "^1.54.4", - "@types/react": "^18.0.26", - "@types/react-addons-linked-state-mixin": "^0.14.22", - "@types/react-dom": "^18.2.18", - "@types/react-plotly.js": "^2.6.3", - "@types/react-timeago": "^4.1.6", - "@typescript-eslint/eslint-plugin": "^6.1.0", - "@typescript-eslint/parser": "^6.1.0", - "babel-loader": "^9.1.3", - "css-loader": "^6.7.1", - "eslint": "^8.36.0", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-prettier": "^5.0.0", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.6.0", - "npm-run-all": "^4.1.5", - "prettier": "^3.0.0", - "rimraf": "^5.0.1", - "source-map-loader": "^1.0.2", - "style-loader": "^3.3.1", - "stylelint": "^15.10.1", - "stylelint-config-recommended": "^13.0.0", - "stylelint-config-standard": "^34.0.0", - "stylelint-csstree-validator": "^3.0.0", - "stylelint-prettier": "^4.0.0", - "typescript": "~5.0.2", - "webpack": "^5.89.0", - "yjs": "^13.5.40" - }, - "jupyterlab": { - "extension": "lib/lab-extension/index", - "outputDir": "sparkmonitor/labextension" - }, - "style": "style/lab.css", - "files": [ - "lib/*", - "src/*", - "style/lab.js" - ], - "eslintConfig": { - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "plugin:prettier/recommended" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "tsconfig.json", - "sourceType": "module" + "name": "sparkmonitor", + "version": "3.0.1", + "description": "Jupyter Notebook & Lab extension to monitor Apache Spark jobs from a notebook", + "repository": { + "type": "git", + "url": "git+https://github.com/swan-cern/sparkmonitor.git" }, - "plugins": [ - "@typescript-eslint" + "keywords": [ + "jupyter", + "jupyterlab", + "jupyterlab-extension", + "Spark", + "sparkmonitor" ], - "rules": { - "@typescript-eslint/naming-convention": [ - "error", - { - "selector": "interface", - "format": [ - "PascalCase" - ], - "custom": { - "regex": "^I[A-Z]", - "match": true - } - } - ], - "@typescript-eslint/no-unused-vars": [ - "warn", - { - "args": "none" - } - ], - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-namespace": "off", - "@typescript-eslint/no-use-before-define": "off", - "@typescript-eslint/quotes": [ - "error", - "single", + "main": "lib/lab-extension/index.js", + "author": { + "name": "Krishnan R", + "email": "krishnanr1997@gmail.com" + }, + "maintainers": [ { - "avoidEscape": true, - "allowTemplateLiterals": false - } - ], - "curly": [ - "error", - "all" - ], - "eqeqeq": "error", - "prefer-arrow-callback": "error" - } - }, - "eslintIgnore": [ - "node_modules", - "dist", - "coverage", - "**/*.d.ts" - ], - "prettier": { - "singleQuote": true, - "trailingComma": "none", - "arrowParens": "avoid", - "endOfLine": "auto", - "overrides": [ - { - "files": "package.json", - "options": { - "tabWidth": 4 + "name": "SWAN Team", + "email": "swan-admins@cern.ch" } - } - ] - }, - "stylelint": { - "extends": [ - "stylelint-config-recommended", - "stylelint-config-standard", - "stylelint-prettier/recommended" ], - "plugins": [ - "stylelint-csstree-validator" + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/swan-cern/sparkmonitor/issues" + }, + "homepage": "https://github.com/swan-cern/sparkmonitor#readme", + "scripts": { + "build": "jlpm run build:lib && jlpm run build:labextension:dev", + "build:prod": "jlpm clean && jlpm run build:lib:prod && jlpm run build:labextension && jlpm run build:nbextension && jlpm run build:scalalistener", + "build:labextension": "jupyter labextension build .", + "build:labextension:dev": "jupyter labextension build --development True .", + "build:lib": "tsc -p tsconfig.lab.json --sourceMap", + "build:lib:prod": "tsc -p tsconfig.lab.json", + "build:nbextension": "webpack --config src/notebook-extension/webpack.config.js", + "build:scalalistener": "cd scalalistener && sbt +assembly", + "clean": "jlpm clean:lib", + "clean:lib": "rimraf lib tsconfig.tsbuildinfo", + "clean:lintcache": "rimraf .eslintcache .stylelintcache", + "clean:labextension": "rimraf sparkmonitor/labextension sparkmonitor/_version.py", + "clean:nbextension": "rimraf sparkmonitor/nbextension", + "clean:scalalistener": "rimraf sparkmonitor/*.jar", + "clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:nbextension && jlpm clean:scalalistener && jlpm clean:lintcache", + "eslint": "jlpm eslint:check --fix", + "eslint:check": "eslint . --cache --ext .ts,.tsx", + "install:extension": "jlpm build", + "lint": "jlpm stylelint && jlpm prettier && jlpm eslint", + "lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check", + "prettier": "jlpm prettier:base --write --list-different", + "prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", + "prettier:check": "jlpm prettier:base --check", + "stylelint": "jlpm stylelint:check --fix", + "stylelint:check": "stylelint --cache \"style/**/*.css\"", + "watch": "run-p watch:src watch:labextension", + "watch:src": "tsc -w -p tsconfig.lab.json --sourceMap", + "watch:labextension": "jupyter labextension watch .", + "watch:nbextension": "webpack --config src/notebook-extension/webpack.config.js --mode development --watch", + "check:all": "jlpm run lint:check && jlpm run check:nbextension && jlpm run check:labextension", + "check:labextension": "tsc -p tsconfig.lab.json --noEmit", + "check:nbextension": "tsc -p tsconfig.notebook.json" + }, + "dependencies": { + "@jupyterlab/application": "^4.0.10", + "@jupyterlab/apputils": "^4.1.10", + "@jupyterlab/cells": "^4.0.10", + "@jupyterlab/mainmenu": "^4.0.10", + "@jupyterlab/notebook": "^4.0.10", + "@jupyterlab/services": "^7.0.10", + "@lumino/coreutils": "^2.0.0", + "@lumino/widgets": "^2.0.1", + "hammerjs": "^2.0.8", + "keycharm": "^0.4.0", + "mobx": "^6.12.0", + "mobx-react-lite": "^4.0.5", + "moment": "^2.29.4", + "plotly.js-basic-dist": "^2.27.1", + "pretty-ms": "^8.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-plotly.js": "^2.6.0", + "react-timeago": "^7.2.0", + "vis-data": "^7.1.9", + "vis-timeline": "^7.7.3", + "vis-util": "^5.0.7", + "xss": "^1.0.14" + }, + "devDependencies": { + "@babel/core": "^7.23.6", + "@babel/preset-env": "^7.23.6", + "@babel/preset-react": "^7.23.3", + "@babel/preset-typescript": "^7.23.3", + "@jupyterlab/builder": "^4.0.0", + "@types/hammerjs": "^2.0.45", + "@types/json-schema": "^7.0.11", + "@types/plotly.js-basic-dist": "^1.54.4", + "@types/react": "^18.0.26", + "@types/react-addons-linked-state-mixin": "^0.14.22", + "@types/react-dom": "^18.2.18", + "@types/react-plotly.js": "^2.6.3", + "@types/react-timeago": "^4.1.6", + "@typescript-eslint/eslint-plugin": "^6.1.0", + "@typescript-eslint/parser": "^6.1.0", + "babel-loader": "^9.1.3", + "css-loader": "^6.7.1", + "eslint": "^8.36.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "npm-run-all": "^4.1.5", + "prettier": "^3.0.0", + "rimraf": "^5.0.1", + "source-map-loader": "^1.0.2", + "style-loader": "^3.3.1", + "stylelint": "^15.10.1", + "stylelint-config-recommended": "^13.0.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-csstree-validator": "^3.0.0", + "stylelint-prettier": "^4.0.0", + "typescript": "~5.0.2", + "webpack": "^5.89.0", + "yjs": "^13.5.40" + }, + "jupyterlab": { + "extension": "lib/lab-extension/index", + "outputDir": "sparkmonitor/labextension" + }, + "style": "style/lab.css", + "files": [ + "lib/*", + "src/*", + "style/lab.js" + ], + "eslintConfig": { + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "tsconfig.json", + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "interface", + "format": [ + "PascalCase" + ], + "custom": { + "regex": "^I[A-Z]", + "match": true + } + } + ], + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "args": "none" + } + ], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/quotes": [ + "error", + "single", + { + "avoidEscape": true, + "allowTemplateLiterals": false + } + ], + "curly": [ + "error", + "all" + ], + "eqeqeq": "error", + "prefer-arrow-callback": "error" + } + }, + "eslintIgnore": [ + "node_modules", + "dist", + "coverage", + "**/*.d.ts" ], - "rules": { - "csstree/validator": true, - "property-no-vendor-prefix": null, - "selector-class-pattern": "^([a-z][A-z\\d]*)(-[A-z\\d]+)*$", - "selector-no-vendor-prefix": null, - "value-no-vendor-prefix": null - } - }, - "styleModule": "style/lab.js" + "prettier": { + "singleQuote": true, + "trailingComma": "none", + "arrowParens": "avoid", + "endOfLine": "auto", + "overrides": [ + { + "files": "package.json", + "options": { + "tabWidth": 4 + } + } + ] + }, + "stylelint": { + "extends": [ + "stylelint-config-recommended", + "stylelint-config-standard", + "stylelint-prettier/recommended" + ], + "plugins": [ + "stylelint-csstree-validator" + ], + "rules": { + "csstree/validator": true, + "property-no-vendor-prefix": null, + "selector-class-pattern": "^([a-z][A-z\\d]*)(-[A-z\\d]+)*$", + "selector-no-vendor-prefix": null, + "value-no-vendor-prefix": null + } + }, + "styleModule": "style/lab.js" } diff --git a/prettier.config.js b/prettier.config.js index c451259..9fe3afc 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -1,6 +1,6 @@ module.exports = { - printWidth: 120, - singleQuote: true, - tabWidth: 4, - trailingComma: 'all', + printWidth: 120, + singleQuote: true, + tabWidth: 4, + trailingComma: 'all' }; diff --git a/src/components/cell-monitor.tsx b/src/components/cell-monitor.tsx index 11e0806..2489b99 100644 --- a/src/components/cell-monitor.tsx +++ b/src/components/cell-monitor.tsx @@ -8,27 +8,32 @@ import { LazyTimeline } from './lazy-timeline'; import { LazyTaskChart } from './lazy-task-chart'; export const CellMonitor = observer(() => { - const notebook = useNotebookStore(); - const cell = useCellStore(); + const notebook = useNotebookStore(); + const cell = useCellStore(); - // If the cell has no spark job - if (!cell || cell?.uniqueJobIds?.length <= 0 || cell.isRemoved || notebook?.hideAllDisplays) { - return
; - } + // If the cell has no spark job + if ( + !cell || + cell?.uniqueJobIds?.length <= 0 || + cell.isRemoved || + notebook?.hideAllDisplays + ) { + return
; + } - let tabContent = <>; - if (!cell.isCollapsed && cell?.view === 'jobs') { - tabContent = ; - } else if (!cell.isCollapsed && cell?.view === 'taskchart') { - tabContent = ; - } else if (!cell.isCollapsed && cell?.view === 'timeline') { - tabContent = ; - } + let tabContent = <>; + if (!cell.isCollapsed && cell?.view === 'jobs') { + tabContent = ; + } else if (!cell.isCollapsed && cell?.view === 'taskchart') { + tabContent = ; + } else if (!cell.isCollapsed && cell?.view === 'timeline') { + tabContent = ; + } - return ( -
- -
{tabContent}
-
- ); + return ( +
+ +
{tabContent}
+
+ ); }); diff --git a/src/components/error-boundary.tsx b/src/components/error-boundary.tsx index 5461718..cc5e4fe 100644 --- a/src/components/error-boundary.tsx +++ b/src/components/error-boundary.tsx @@ -1,32 +1,32 @@ import React from 'react'; type Props = { - children: React.ReactNode; + children: React.ReactNode; }; type State = { - hasError: boolean; + hasError: boolean; }; export class ErrorBoundary extends React.Component { - constructor(props: Props) { - super(props); - this.state = { hasError: false }; - } + constructor(props: Props) { + super(props); + this.state = { hasError: false }; + } - static getDerivedStateFromError(error: Error) { - return { hasError: true }; - } + static getDerivedStateFromError(error: Error) { + return { hasError: true }; + } - componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { - console.log('SparkMonitor: Caught react error:', error, errorInfo); - } + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.log('SparkMonitor: Caught react error:', error, errorInfo); + } - render() { - if (this.state.hasError) { - return
Error: Something went wrong displaying this data.
; - } - - return this.props.children; + render() { + if (this.state.hasError) { + return
Error: Something went wrong displaying this data.
; } + + return this.props.children; + } } diff --git a/src/components/header.tsx b/src/components/header.tsx index b75a15d..77b4b20 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -3,92 +3,109 @@ import { observer } from 'mobx-react-lite'; import { useCellStore, useNotebookStore } from '../store'; export const CellMonitorHeader = observer(() => { - const notebook = useNotebookStore(); - const cell = useCellStore(); + const notebook = useNotebookStore(); + const cell = useCellStore(); - const isButtonActive = (view: string) => (!cell.isCollapsed && cell.view === view ? 'tabbuttonactive' : ''); - const jobButtonClassNames = 'jobtabletabbuttonicon tabbutton ' + isButtonActive('jobs'); - const tasksButtonClassNames = 'taskviewtabbuttonicon tabbutton ' + isButtonActive('taskchart'); - const timelineButtonClassNames = 'timelinetabbuttonicon tabbutton ' + isButtonActive('timeline'); + const isButtonActive = (view: string) => + !cell.isCollapsed && cell.view === view ? 'tabbuttonactive' : ''; + const jobButtonClassNames = + 'jobtabletabbuttonicon tabbutton ' + isButtonActive('jobs'); + const tasksButtonClassNames = + 'taskviewtabbuttonicon tabbutton ' + isButtonActive('taskchart'); + const timelineButtonClassNames = + 'timelinetabbuttonicon tabbutton ' + isButtonActive('timeline'); - return ( -
-
- { - cell.toggleCollapseCellDisplay(); - }} - > - - - - Apache Spark - - {notebook.numExecutors} EXECUTORS - - - {notebook.numTotalCores} CORES - - Jobs - - {cell.numActiveJobs ? ( - - {cell.numActiveJobs} RUNNING - - ) : ( - '' - )} - {cell.numCompletedJobs ? ( - - {cell.numCompletedJobs} COMPLETED - - ) : ( - '' - )} - {cell.numFailedJobs ? ( - - {cell.numFailedJobs} FAILED - - ) : ( - '' - )} - - -
-
-
- { - cell.setView('jobs'); - }} - /> - { - cell.setView('taskchart'); - }} - /> - { - cell.setView('timeline'); - }} - /> - {/* TODO */} - { - cell.toggleHideCellDisplay(); - }} - /> -
-
+ return ( +
+
+ { + cell.toggleCollapseCellDisplay(); + }} + > + + + + Apache Spark + + {notebook.numExecutors}{' '} + EXECUTORS + + + + {notebook.numTotalCores} + {' '} + CORES + + Jobs + + {cell.numActiveJobs ? ( + + {cell.numActiveJobs}{' '} + RUNNING + + ) : ( + '' + )} + {cell.numCompletedJobs ? ( + + + {cell.numCompletedJobs} + {' '} + COMPLETED + + ) : ( + '' + )} + {cell.numFailedJobs ? ( + + {cell.numFailedJobs}{' '} + FAILED + + ) : ( + '' + )} + + +
+
+
+ { + cell.setView('jobs'); + }} + /> + { + cell.setView('taskchart'); + }} + /> + { + cell.setView('timeline'); + }} + /> + {/* TODO */} + { + cell.toggleHideCellDisplay(); + }} + />
- ); +
+
+ ); }); diff --git a/src/components/index.tsx b/src/components/index.tsx index 7cae12f..f5ddc2b 100644 --- a/src/components/index.tsx +++ b/src/components/index.tsx @@ -4,12 +4,16 @@ import { observer } from 'mobx-react-lite'; import { CellStoreContext, NotebookStoreContext, store } from '../store'; import { CellMonitor } from './cell-monitor'; -export const CellWidget = observer((props: { notebookId: string; cellId: string }) => { +export const CellWidget = observer( + (props: { notebookId: string; cellId: string }) => { return ( - - - - - + + + + + ); -}); + } +); diff --git a/src/components/job-table.tsx b/src/components/job-table.tsx index 2854c27..99bfba6 100644 --- a/src/components/job-table.tsx +++ b/src/components/job-table.tsx @@ -8,129 +8,143 @@ import prettyMilliseconds from 'pretty-ms'; import { ErrorBoundary } from './error-boundary'; const StageItem = observer((props: { stageId: string }) => { - const notebook = useNotebookStore(); - const stage = notebook.stages[props.stageId]; - return ( -
- - - - - - - - ); + const notebook = useNotebookStore(); + const stage = notebook.stages[props.stageId]; + return ( + + + + + + + + + ); }); const StageTable = observer((props: { jobId: string }) => { - const notebook = useNotebookStore(); - const stageIds = notebook.jobs[props.jobId].uniqueStageIds; - const rows = stageIds.map((stageId) => { - return ; - }); - return ( -
{stage.stageId}{stage.name} - {stage.status} - - - - - - {stage.completionTime - ? prettyMilliseconds(stage.completionTime?.getTime() - stage.submissionTime.getTime()) - : '-'} -
{stage.stageId}{stage.name} + {stage.status} + + + + + + {stage.completionTime + ? prettyMilliseconds( + stage.completionTime?.getTime() - stage.submissionTime.getTime() + ) + : '-'} +
- - - - - - - - - - - {rows} -
IDStageStatusTasksSubmission TimeDuration
- ); + const notebook = useNotebookStore(); + const stageIds = notebook.jobs[props.jobId].uniqueStageIds; + const rows = stageIds.map(stageId => { + return ; + }); + return ( + + + + + + + + + + + + {rows} +
IDStageStatusTasksSubmission TimeDuration
+ ); }); const JobItem = observer((props: { jobId: string }) => { - const notebook = useNotebookStore(); - const job = notebook?.jobs[props.jobId]; - const [stagesCollapsed, setStageTableCollapsed] = React.useState(true); - const onClickCollapseStageTable = () => { - setStageTableCollapsed((value) => !value); - }; + const notebook = useNotebookStore(); + const job = notebook?.jobs[props.jobId]; + const [stagesCollapsed, setStageTableCollapsed] = React.useState(true); + const onClickCollapseStageTable = () => { + setStageTableCollapsed(value => !value); + }; - return ( - <> - - - - - {job.jobId} - {job.name} - - {job.status} - - - {job.numCompletedStages}/{job.numStages} - {job.numSkippedStages > 0 ? `(${job.numSkippedStages} skipped)` : ''} - {job.numActiveStages > 0 ? `(${job.numActiveStages} active)` : ''} - - - - - - - - - {job.endTime ? prettyMilliseconds(job.endTime?.getTime() - job.startTime.getTime()) : '-'} - - - {!stagesCollapsed && ( - - - - - - - )} - - ); + return ( + <> + + + + + {job.jobId} + {job.name} + + {job.status} + + + {job.numCompletedStages}/{job.numStages} + {job.numSkippedStages > 0 ? `(${job.numSkippedStages} skipped)` : ''} + {job.numActiveStages > 0 ? `(${job.numActiveStages} active)` : ''} + + + + + + + + + {job.endTime + ? prettyMilliseconds( + job.endTime?.getTime() - job.startTime.getTime() + ) + : '-'} + + + {!stagesCollapsed && ( + + + + + + + )} + + ); }); export const JobTable = observer(() => { - const cell = useCellStore(); + const cell = useCellStore(); - return ( - -
- - - - - - - - - - - - - - - {cell.uniqueJobIds.map((jobId) => ( - - ))} - -
IDJobStatusStagesTasksSubmission TimeDuration
-
-
- ); + return ( + +
+ + + + + + + + + + + + + + + {cell.uniqueJobIds.map(jobId => ( + + ))} + +
IDJobStatusStagesTasksSubmission TimeDuration
+
+
+ ); }); diff --git a/src/components/lazy-task-chart.tsx b/src/components/lazy-task-chart.tsx index 0d59a39..09632b7 100644 --- a/src/components/lazy-task-chart.tsx +++ b/src/components/lazy-task-chart.tsx @@ -1,11 +1,13 @@ import React, { Suspense } from 'react'; -const TaskChart = React.lazy(() => import(/* webpackChunkName: "sparkmonitortaskchart" */ './task-chart')); +const TaskChart = React.lazy( + () => import(/* webpackChunkName: "sparkmonitortaskchart" */ './task-chart') +); export const LazyTaskChart = () => { - return ( - loading}> - - - ); + return ( + loading}> + + + ); }; diff --git a/src/components/lazy-timeline.tsx b/src/components/lazy-timeline.tsx index 375d117..6a263f3 100644 --- a/src/components/lazy-timeline.tsx +++ b/src/components/lazy-timeline.tsx @@ -1,11 +1,13 @@ import React, { Suspense } from 'react'; -const Timeline = React.lazy(() => import(/* webpackChunkName: "sparkmonitortimeline" */ './timeline')); +const Timeline = React.lazy( + () => import(/* webpackChunkName: "sparkmonitortimeline" */ './timeline') +); export const LazyTimeline = () => { - return ( - loading}> - - - ); + return ( + loading}> + + + ); }; diff --git a/src/components/progress-bar.tsx b/src/components/progress-bar.tsx index c5795f0..a5e85a5 100644 --- a/src/components/progress-bar.tsx +++ b/src/components/progress-bar.tsx @@ -1,24 +1,28 @@ import React from 'react'; -export const ProgressBar = (props: { total: number; running: number; completed: number }) => { - return ( -
-
- {props.completed}/{props.total} - {props.running > 0 ? ` (${props.running})` : ''} -
- - -
- ); +export const ProgressBar = (props: { + total: number; + running: number; + completed: number; +}) => { + return ( +
+
+ {props.completed}/{props.total} + {props.running > 0 ? ` (${props.running})` : ''} +
+ + +
+ ); }; diff --git a/src/components/task-chart.tsx b/src/components/task-chart.tsx index 60d73c7..621fca1 100644 --- a/src/components/task-chart.tsx +++ b/src/components/task-chart.tsx @@ -8,136 +8,136 @@ import { ErrorBoundary } from './error-boundary'; const Plot = createPlotlyComponent(Plotly); const plotDefaultLayout: Partial = { - showlegend: true, - margin: { - t: 30, // top margin - l: 30, // left margin - r: 30, // right margin - b: 60, // bottom margin - }, - xaxis: { - type: 'date', - // title: 'Time', - }, - yaxis: { - fixedrange: true, - }, - dragmode: 'pan', - shapes: [], - legend: { - orientation: 'h', - x: 0, - y: 5, - // traceorder: 'normal', - font: { - family: 'sans-serif', - size: 12, - color: '#000', - }, - // bgcolor: '#E2E2E2', - // bordercolor: '#FFFFFF', - // borderwidth: 2 - }, + showlegend: true, + margin: { + t: 30, // top margin + l: 30, // left margin + r: 30, // right margin + b: 60 // bottom margin + }, + xaxis: { + type: 'date' + // title: 'Time', + }, + yaxis: { + fixedrange: true + }, + dragmode: 'pan', + shapes: [], + legend: { + orientation: 'h', + x: 0, + y: 5, + // traceorder: 'normal', + font: { + family: 'sans-serif', + size: 12, + color: '#000' + } + // bgcolor: '#E2E2E2', + // bordercolor: '#FFFFFF', + // borderwidth: 2 + } }; const plotOptions = { displaylogo: false, scrollZoom: true }; const TaskChart = observer(() => { - const cell = useCellStore(); - const taskChartStore = cell.taskChartStore; + const cell = useCellStore(); + const taskChartStore = cell.taskChartStore; - const [chartRefreshRevision, setRevision] = React.useState(1); + const [chartRefreshRevision, setRevision] = React.useState(1); - const data = React.useMemo(() => { - const tasktrace: Plotly.Data = { - x: taskChartStore.taskDataX, - y: taskChartStore.taskDataY, - fill: 'tozeroy', - type: 'scatter', - mode: 'none', - fillcolor: '#00aedb', - name: 'Active Tasks', - }; - const executortrace: Plotly.Data = { - x: taskChartStore.executorDataX, - y: taskChartStore.executorDataY, - fill: 'tozeroy', - type: 'scatter', - mode: 'none', - fillcolor: '#F5C936', - name: 'Executor Cores', - }; - const jobtrace: Plotly.Data = { - x: taskChartStore.jobDataX, - y: taskChartStore.jobDataY, - text: taskChartStore.jobDataText as any, //this.jobDataText, - type: 'scatter', - mode: 'markers', - fillcolor: '#F5C936', - // name: 'Jobs', - showlegend: false, - marker: { - symbol: 23, - color: '#4CB5AE', - size: 1, - }, - }; - return [executortrace, tasktrace, jobtrace]; - }, [ - taskChartStore.taskDataX, - taskChartStore.taskDataY, - taskChartStore.executorDataX, - taskChartStore.executorDataY, - taskChartStore.jobDataX, - taskChartStore.jobDataY, - taskChartStore.jobDataText, - ]); + const data = React.useMemo(() => { + const tasktrace: Plotly.Data = { + x: taskChartStore.taskDataX, + y: taskChartStore.taskDataY, + fill: 'tozeroy', + type: 'scatter', + mode: 'none', + fillcolor: '#00aedb', + name: 'Active Tasks' + }; + const executortrace: Plotly.Data = { + x: taskChartStore.executorDataX, + y: taskChartStore.executorDataY, + fill: 'tozeroy', + type: 'scatter', + mode: 'none', + fillcolor: '#F5C936', + name: 'Executor Cores' + }; + const jobtrace: Plotly.Data = { + x: taskChartStore.jobDataX, + y: taskChartStore.jobDataY, + text: taskChartStore.jobDataText as any, //this.jobDataText, + type: 'scatter', + mode: 'markers', + fillcolor: '#F5C936', + // name: 'Jobs', + showlegend: false, + marker: { + symbol: 23, + color: '#4CB5AE', + size: 1 + } + }; + return [executortrace, tasktrace, jobtrace]; + }, [ + taskChartStore.taskDataX, + taskChartStore.taskDataY, + taskChartStore.executorDataX, + taskChartStore.executorDataY, + taskChartStore.jobDataX, + taskChartStore.jobDataY, + taskChartStore.jobDataText + ]); - const plotLayout: Partial = React.useMemo(() => { + const plotLayout: Partial = React.useMemo(() => { + return { + ...plotDefaultLayout, + shapes: taskChartStore.jobDataX.map(job => { return { - ...plotDefaultLayout, - shapes: taskChartStore.jobDataX.map((job) => { - return { - type: 'line', - yref: 'paper', - x0: job, - y0: 0, - x1: job, - y1: 1, - line: { - color: '#4CB5AE', - width: 1.5, - }, - }; - }), - datarevision: chartRefreshRevision, + type: 'line', + yref: 'paper', + x0: job, + y0: 0, + x1: job, + y1: 1, + line: { + color: '#4CB5AE', + width: 1.5 + } }; - }, [taskChartStore.jobDataX, chartRefreshRevision]); + }), + datarevision: chartRefreshRevision + }; + }, [taskChartStore.jobDataX, chartRefreshRevision]); - // Periodically refresh the chart by updating the revision - React.useEffect(() => { - const refreshInterval = setInterval(() => { - setRevision((revision) => revision + 1); - }, 2000); - return () => { - // clean up when react component is unmounted. - clearInterval(refreshInterval); - }; - }); + // Periodically refresh the chart by updating the revision + React.useEffect(() => { + const refreshInterval = setInterval(() => { + setRevision(revision => revision + 1); + }, 2000); + return () => { + // clean up when react component is unmounted. + clearInterval(refreshInterval); + }; + }); - return ( - -
- -
-
- ); + return ( + +
+ +
+
+ ); }); export default TaskChart; diff --git a/src/components/timeline.tsx b/src/components/timeline.tsx index 4859566..c84f6de 100644 --- a/src/components/timeline.tsx +++ b/src/components/timeline.tsx @@ -1,89 +1,98 @@ import { observer } from 'mobx-react-lite'; import React from 'react'; -import { DataSet, Timeline as VisTimeline, TimelineOptions } from 'vis-timeline/standalone'; +import { + DataSet, + Timeline as VisTimeline, + TimelineOptions +} from 'vis-timeline/standalone'; import 'vis-timeline/styles/vis-timeline-graph2d.css'; import { useCellStore, useNotebookStore } from '../store'; import { ErrorBoundary } from './error-boundary'; const timelineOptions: TimelineOptions = { - margin: { - item: 2, - axis: 2, - }, - stack: true, - showTooltips: true, - minHeight: '100px', - editable: false, - tooltip: { - overflowMethod: 'cap', - }, - align: 'center', - orientation: 'top', - verticalScroll: false, + margin: { + item: 2, + axis: 2 + }, + stack: true, + showTooltips: true, + minHeight: '100px', + editable: false, + tooltip: { + overflowMethod: 'cap' + }, + align: 'center', + orientation: 'top', + verticalScroll: false }; const Timeline = observer(() => { - const notebook = useNotebookStore(); - const cell = useCellStore(); + const notebook = useNotebookStore(); + const cell = useCellStore(); - const timelineDiv = React.useRef(null); + const timelineDiv = React.useRef(null); - const timelineData = [] as any[]; - cell.jobs.forEach((job) => { + const timelineData = [] as any[]; + cell.jobs.forEach(job => { + timelineData.push({ + id: job.uniqueId, + start: job.startTime, + content: `${job.jobId}:${job.name}`, + group: 'jobs', + className: 'job ' + job.status, + mode: job.status === 'RUNNING' ? 'ongoing' : 'done', + end: job.endTime ? job.endTime : new Date() + }); + job.uniqueStageIds.forEach(uniqueStageId => { + const stage = notebook.stages[uniqueStageId]; + if (stage.submissionTime) { timelineData.push({ - id: job.uniqueId, - start: job.startTime, - content: `${job.jobId}:${job.name}`, - group: 'jobs', - className: 'job ' + job.status, - mode: job.status === 'RUNNING' ? 'ongoing' : 'done', - end: job.endTime ? job.endTime : new Date(), - }); - job.uniqueStageIds.forEach((uniqueStageId) => { - const stage = notebook.stages[uniqueStageId]; - if (stage.submissionTime) { - timelineData.push({ - id: stage.uniqueId, - start: stage.submissionTime, - content: `${stage.stageId}:${stage.name}`, - group: 'stages', - className: 'stage ' + stage.status, - mode: stage.status === 'RUNNING' ? 'ongoing' : 'done', - end: stage.completionTime ? stage.completionTime : new Date(), - }); - } + id: stage.uniqueId, + start: stage.submissionTime, + content: `${stage.stageId}:${stage.name}`, + group: 'stages', + className: 'stage ' + stage.status, + mode: stage.status === 'RUNNING' ? 'ongoing' : 'done', + end: stage.completionTime ? stage.completionTime : new Date() }); + } }); + }); - const timelineGroups = new DataSet([ - { - id: 'jobs', - content: 'Jobs', - className: 'visjobgroup', - }, - { id: 'stages', content: 'Stages' }, - ]); + const timelineGroups = new DataSet([ + { + id: 'jobs', + content: 'Jobs', + className: 'visjobgroup' + }, + { id: 'stages', content: 'Stages' } + ]); - React.useEffect(() => { - if (!timelineDiv.current) { - return; - } - const timeline = new VisTimeline(timelineDiv.current, timelineData, timelineGroups, timelineOptions); - return () => { - timeline.destroy(); - }; - }); - return ( - -
-
-
-
-
-
+ React.useEffect(() => { + if (!timelineDiv.current) { + return; + } + const timeline = new VisTimeline( + timelineDiv.current, + timelineData, + timelineGroups, + timelineOptions ); + return () => { + timeline.destroy(); + }; + }); + return ( + +
+
+
+
+
+
+ ); }); export default Timeline; diff --git a/src/lab-extension/current-cell.ts b/src/lab-extension/current-cell.ts index 3179279..202be2b 100644 --- a/src/lab-extension/current-cell.ts +++ b/src/lab-extension/current-cell.ts @@ -5,103 +5,105 @@ import { Notebook, NotebookPanel } from '@jupyterlab/notebook'; // Logic adapted from https://github.com/deshaw/jupyterlab-execute-time/blob/master/src/ExecuteTimeWidget.ts export default class CurrentCellTracker { - isReady = new PromiseDelegate(); - cellSlotMap: { [cellId: string]: () => void } = {}; - activeCell?: Cell; - lastExecutedCell?: Cell; - cellReexecuted = false; - numCellsExecuted = 0; - notebook?: Notebook; - - // because the signal is emitted 3 times for every execution. Only want to increment by 1 - lastBusySignal = ''; - - constructor(private notebookPanel: NotebookPanel) { - this.init(); + isReady = new PromiseDelegate(); + cellSlotMap: { [cellId: string]: () => void } = {}; + activeCell?: Cell; + lastExecutedCell?: Cell; + cellReexecuted = false; + numCellsExecuted = 0; + notebook?: Notebook; + + // because the signal is emitted 3 times for every execution. Only want to increment by 1 + lastBusySignal = ''; + + constructor(private notebookPanel: NotebookPanel) { + this.init(); + } + + private async init() { + await this.notebookPanel.revealed; + this.notebook = this.notebookPanel.content; + + // Set the recordTiming setting to true + this.notebook.notebookConfig.recordTiming = true; + this.registerCells(); + this.isReady.resolve(undefined); + } + + private registerMetadataChanges(cellModel: ICellModel) { + if (!(cellModel.id in this.cellSlotMap)) { + const fn = () => this.cellMetadataChanged(cellModel); + this.cellSlotMap[cellModel.id] = fn; + cellModel.metadataChanged.connect(fn); + // In case there was already metadata (do not highlight on first load) + this.cellMetadataChanged(cellModel); } + } - private async init() { - await this.notebookPanel.revealed; - this.notebook = this.notebookPanel.content; - - // Set the recordTiming setting to true - this.notebook.notebookConfig.recordTiming = true; - this.registerCells(); - this.isReady.resolve(undefined); - } - - private registerMetadataChanges(cellModel: ICellModel) { - if (!(cellModel.id in this.cellSlotMap)) { - const fn = () => this.cellMetadataChanged(cellModel); - this.cellSlotMap[cellModel.id] = fn; - cellModel.metadataChanged.connect(fn); - // In case there was already metadata (do not highlight on first load) - this.cellMetadataChanged(cellModel); - } + private deregisterMetadataChanges(cellModel: ICellModel) { + const fn = this.cellSlotMap[cellModel.id]; + if (fn) { + cellModel.metadataChanged.disconnect(fn); } - - private deregisterMetadataChanges(cellModel: ICellModel) { - const fn = this.cellSlotMap[cellModel.id]; - if (fn) { - cellModel.metadataChanged.disconnect(fn); - } - delete this.cellSlotMap[cellModel.id]; - } - - private getCodeCellFromModel(cellModel: ICellModel) { - if (cellModel.type === 'code') { - const cell = this.notebookPanel.content.widgets.find((widget) => widget.model === cellModel); - return cell; - } - return null; + delete this.cellSlotMap[cellModel.id]; + } + + private getCodeCellFromModel(cellModel: ICellModel) { + if (cellModel.type === 'code') { + const cell = this.notebookPanel.content.widgets.find( + widget => widget.model === cellModel + ); + return cell; } - - private cellMetadataChanged(cellModel: ICellModel) { - const codeCell = this.getCodeCellFromModel(cellModel); - - if (codeCell) { - const executionMetadata: any = codeCell.model.metadata['execution']; - if (executionMetadata) { - if ( - executionMetadata['iopub.status.busy'] && - this.lastBusySignal !== executionMetadata['iopub.status.busy'] - ) { - this.activeCell = codeCell; - this.cellReexecuted = this.lastExecutedCell === codeCell; - this.lastExecutedCell = codeCell; - this.numCellsExecuted += 1; - this.lastBusySignal = executionMetadata['iopub.status.busy']; - } - } + return null; + } + + private cellMetadataChanged(cellModel: ICellModel) { + const codeCell = this.getCodeCellFromModel(cellModel); + + if (codeCell) { + const executionMetadata: any = codeCell.model.metadata['execution']; + if (executionMetadata) { + if ( + executionMetadata['iopub.status.busy'] && + this.lastBusySignal !== executionMetadata['iopub.status.busy'] + ) { + this.activeCell = codeCell; + this.cellReexecuted = this.lastExecutedCell === codeCell; + this.lastExecutedCell = codeCell; + this.numCellsExecuted += 1; + this.lastBusySignal = executionMetadata['iopub.status.busy']; } + } } - - registerCells() { - const cells = this.notebookPanel.context.model.cells; - cells.changed.connect((_cells, changed) => { - // While we could look at changed.type, it's easier to just remove all - // oldValues and add back all new values - changed.oldValues.forEach(this.deregisterMetadataChanges.bind(this)); - changed.newValues.forEach(this.registerMetadataChanges.bind(this)); - }); - for (let i = 0; i < cells.length; i += 1) { - this.registerMetadataChanges(cells.get(i)); - } + } + + registerCells() { + const cells = this.notebookPanel.context.model.cells; + cells.changed.connect((_cells, changed) => { + // While we could look at changed.type, it's easier to just remove all + // oldValues and add back all new values + changed.oldValues.forEach(this.deregisterMetadataChanges.bind(this)); + changed.newValues.forEach(this.registerMetadataChanges.bind(this)); + }); + for (let i = 0; i < cells.length; i += 1) { + this.registerMetadataChanges(cells.get(i)); } + } - getActiveCell() { - return this.activeCell; - } + getActiveCell() { + return this.activeCell; + } - getCellReexecuted() { - return this.cellReexecuted; - } + getCellReexecuted() { + return this.cellReexecuted; + } - getNumCellsExecuted() { - return this.numCellsExecuted; - } + getNumCellsExecuted() { + return this.numCellsExecuted; + } - ready() { - return this.isReady.promise; - } + ready() { + return this.isReady.promise; + } } diff --git a/src/lab-extension/index.ts b/src/lab-extension/index.ts index 317eba4..e1b23c4 100644 --- a/src/lab-extension/index.ts +++ b/src/lab-extension/index.ts @@ -14,66 +14,70 @@ import { NotebookStore } from '../store/notebook'; /** Entrypoint: Called when the extension is loaded by jupyter. */ const extension = { - id: 'jupyterlab_sparkmonitor', - autoStart: true, - requires: [INotebookTracker, IMainMenu], - activate(app: JupyterFrontEnd, notebooks: NotebookTracker, mainMenu: MainMenu) { - let monitor: SparkMonitor; - console.log('JupyterLab SparkMonitor is activated!'); - notebooks.widgetAdded.connect(async (sender, nbPanel) => { - let notebookStore = store.notebooks[nbPanel.id]; - if (!notebookStore) { - notebookStore = new NotebookStore(nbPanel.id); - store.notebooks[nbPanel.id] = notebookStore; - } + id: 'jupyterlab_sparkmonitor', + autoStart: true, + requires: [INotebookTracker, IMainMenu], + activate( + app: JupyterFrontEnd, + notebooks: NotebookTracker, + mainMenu: MainMenu + ) { + let monitor: SparkMonitor; + console.log('JupyterLab SparkMonitor is activated!'); + notebooks.widgetAdded.connect(async (sender, nbPanel) => { + let notebookStore = store.notebooks[nbPanel.id]; + if (!notebookStore) { + notebookStore = new NotebookStore(nbPanel.id); + store.notebooks[nbPanel.id] = notebookStore; + } - // JupyterLab 1.0 backwards compatibility - let kernel; - let info; - if ((nbPanel as any).session) { - await (nbPanel as any).session.ready; - kernel = (nbPanel as any).session.kernel; - await kernel.ready; - info = kernel.info; - } else { - // JupyterLab 2.0 - const { sessionContext } = nbPanel; - await sessionContext.ready; - kernel = sessionContext.session?.kernel; - info = await kernel?.info; - } + // JupyterLab 1.0 backwards compatibility + let kernel; + let info; + if ((nbPanel as any).session) { + await (nbPanel as any).session.ready; + kernel = (nbPanel as any).session.kernel; + await kernel.ready; + info = kernel.info; + } else { + // JupyterLab 2.0 + const { sessionContext } = nbPanel; + await sessionContext.ready; + kernel = sessionContext.session?.kernel; + info = await kernel?.info; + } - if (info.language_info.name === 'python') { - monitor = new SparkMonitor(nbPanel, notebookStore); - console.log('Notebook kernel ready'); - monitor.startComm(); - } - }); + if (info.language_info.name === 'python') { + monitor = new SparkMonitor(nbPanel, notebookStore); + console.log('Notebook kernel ready'); + monitor.startComm(); + } + }); - const commandID = 'toggle-monitor'; - let toggled = false; + const commandID = 'toggle-monitor'; + let toggled = false; - app.commands.addCommand(commandID, { - label: 'Hide Spark Monitoring', - isEnabled: () => true, - isVisible: () => true, - isToggled: () => toggled, - execute: () => { - console.log(`Executed ${commandID}`); - toggled = !toggled; - monitor?.toggleAll(); - }, - }); + app.commands.addCommand(commandID, { + label: 'Hide Spark Monitoring', + isEnabled: () => true, + isVisible: () => true, + isToggled: () => toggled, + execute: () => { + console.log(`Executed ${commandID}`); + toggled = !toggled; + monitor?.toggleAll(); + } + }); - const menu = new Menu({ commands: app.commands }); - menu.title.label = 'Spark'; - menu.addItem({ - command: commandID, - args: {}, - }); + const menu = new Menu({ commands: app.commands }); + menu.title.label = 'Spark'; + menu.addItem({ + command: commandID, + args: {} + }); - mainMenu.addMenu(menu, false, { rank: 40 }); - }, + mainMenu.addMenu(menu, false, { rank: 40 }); + } }; export default extension; diff --git a/src/lab-extension/jupyterlab-sparkmonitor.ts b/src/lab-extension/jupyterlab-sparkmonitor.ts index 1206e65..538c517 100644 --- a/src/lab-extension/jupyterlab-sparkmonitor.ts +++ b/src/lab-extension/jupyterlab-sparkmonitor.ts @@ -1,7 +1,10 @@ import React from 'react'; import { ICellModel } from '@jupyterlab/cells'; import { NotebookPanel } from '@jupyterlab/notebook'; -import { IComm, IKernelConnection } from '@jupyterlab/services/lib/kernel/kernel'; +import { + IComm, + IKernelConnection +} from '@jupyterlab/services/lib/kernel/kernel'; import { ICommMsgMsg } from '@jupyterlab/services/lib/kernel/messages'; import { PanelLayout } from '@lumino/widgets'; import CurrentCellTracker from './current-cell'; @@ -10,172 +13,180 @@ import { ReactWidget } from '@jupyterlab/apputils'; import type { NotebookStore } from '../store/notebook'; export default class JupyterLabSparkMonitor { - currentCellTracker: CurrentCellTracker; - cellExecCountSinceSparkJobStart = 0; - kernel?: IKernelConnection; - - /** Communication object with the kernel. */ - comm?: IComm; - - constructor(private notebookPanel: NotebookPanel, private notebookStore: NotebookStore) { - this.createCellReactElements(); - this.currentCellTracker = new CurrentCellTracker(notebookPanel); - this.kernel = (notebookPanel as any).session - ? (this.notebookPanel as any).session.kernel - : this.notebookPanel.sessionContext.session?.kernel; - - // Fixes Reloading the browser + currentCellTracker: CurrentCellTracker; + cellExecCountSinceSparkJobStart = 0; + kernel?: IKernelConnection; + + /** Communication object with the kernel. */ + comm?: IComm; + + constructor( + private notebookPanel: NotebookPanel, + private notebookStore: NotebookStore + ) { + this.createCellReactElements(); + this.currentCellTracker = new CurrentCellTracker(notebookPanel); + this.kernel = (notebookPanel as any).session + ? (this.notebookPanel as any).session.kernel + : this.notebookPanel.sessionContext.session?.kernel; + + // Fixes Reloading the browser + this.startComm(); + + // Fixes Restarting the Kernel + this.kernel?.statusChanged.connect((_, status) => { + if (status === 'starting') { + this.currentCellTracker.cellReexecuted = false; this.startComm(); - - // Fixes Restarting the Kernel - this.kernel?.statusChanged.connect((_, status) => { - if (status === 'starting') { - this.currentCellTracker.cellReexecuted = false; - this.startComm(); - } + } + }); + + // listen for cell removed + this.notebookPanel.content.model?.cells.changed.connect((_, data) => { + if (data.type === 'remove') { + data.oldValues.forEach(cell => { + notebookStore.onCellRemoved(cell.id); }); + } + }); + } + + createCellReactElements() { + const createElementIfNotExists = (cellModel: ICellModel) => { + if (cellModel.type === 'code') { + const codeCell = this.notebookPanel.content.widgets.find( + widget => widget.model === cellModel + ); + if (codeCell && !codeCell.node.querySelector('.sparkMonitorCellRoot')) { + const widget = ReactWidget.create( + React.createElement(CellWidget, { + notebookId: this.notebookPanel.id, + cellId: cellModel.id + }) + ); + widget.addClass('spark-monitor-cell-widget'); + + (codeCell.layout as PanelLayout).insertWidget( + 2, // Insert the element below the input area based on position/index + widget + ); + codeCell.update(); + } + } + }; - // listen for cell removed - this.notebookPanel.content.model?.cells.changed.connect((_, data) => { - if (data.type === 'remove') { - data.oldValues.forEach((cell) => { - notebookStore.onCellRemoved(cell.id); - }); - } - }); - } + const cells = this.notebookPanel.context.model.cells; - createCellReactElements() { - const createElementIfNotExists = (cellModel: ICellModel) => { - if (cellModel.type === 'code') { - const codeCell = this.notebookPanel.content.widgets.find((widget) => widget.model === cellModel); - if (codeCell && !codeCell.node.querySelector('.sparkMonitorCellRoot')) { - const widget = ReactWidget.create( - React.createElement(CellWidget, { - notebookId: this.notebookPanel.id, - cellId: cellModel.id, - }), - ); - widget.addClass('SparkMonitorCellWidget'); - - (codeCell.layout as PanelLayout).insertWidget( - 2, // Insert the element below the input area based on position/index - widget, - ); - codeCell.update(); - } - } - }; - - const cells = this.notebookPanel.context.model.cells; - - // Ensure new cells created have a monitoring display - cells.changed.connect((cells, changed) => { - for (let i = 0; i < cells.length; i += 1) { - createElementIfNotExists(cells.get(i)); - } - }); + // Ensure new cells created have a monitoring display + cells.changed.connect((cells, changed) => { + for (let i = 0; i < cells.length; i += 1) { + createElementIfNotExists(cells.get(i)); + } + }); - // Do it the first time - for (let i = 0; i < cells.length; i += 1) { - createElementIfNotExists(cells.get(i)); - } + // Do it the first time + for (let i = 0; i < cells.length; i += 1) { + createElementIfNotExists(cells.get(i)); } - - toggleAll() { - this.notebookStore.toggleHideAllDisplays(); + } + + toggleAll() { + this.notebookStore.toggleHideAllDisplays(); + } + + startComm() { + console.log('SparkMonitor: Starting Comm with kernel.'); + this.currentCellTracker.ready().then(() => { + this.comm = + 'createComm' in (this.kernel || {}) + ? this.kernel?.createComm('SparkMonitor') + : (this.kernel as any).connectToComm('SparkMonitor'); + if (!this.comm) { + console.warn('SparkMonitor: Unable to connect to comm'); + return; + } + this.comm.open({ msgtype: 'openfromfrontend' }); + this.comm.onMsg = message => { + this.handleMessage(message); + }; + this.comm.onClose = message => { + // noop + }; + console.log('SparkMonitor: Connection with comms established'); + }); + } + + onSparkJobStart(data: any) { + const cell = this.currentCellTracker.getActiveCell(); + if (!cell) { + console.warn('SparkMonitor: Job started with no running cell.'); + return; } - - startComm() { - console.log('SparkMonitor: Starting Comm with kernel.'); - this.currentCellTracker.ready().then(() => { - this.comm = - 'createComm' in (this.kernel || {}) - ? this.kernel?.createComm('SparkMonitor') - : (this.kernel as any).connectToComm('SparkMonitor'); - if (!this.comm) { - console.warn('SparkMonitor: Unable to connect to comm'); - return; - } - this.comm.open({ msgtype: 'openfromfrontend' }); - this.comm.onMsg = (message) => { - this.handleMessage(message); - }; - this.comm.onClose = (message) => { - // noop - }; - console.log('SparkMonitor: Connection with comms established'); - }); + // See if we have a new execution. If it's new (a cell has been run again) we need to clear the cell monitor + const newExecution = + this.currentCellTracker.getNumCellsExecuted() > + this.cellExecCountSinceSparkJobStart; + if (newExecution) { + this.cellExecCountSinceSparkJobStart = + this.currentCellTracker.getNumCellsExecuted(); + this.notebookStore.onCellExecutedAgain(cell.model.id); } - - onSparkJobStart(data: any) { - const cell = this.currentCellTracker.getActiveCell(); - if (!cell) { - console.warn('SparkMonitor: Job started with no running cell.'); - return; - } - // See if we have a new execution. If it's new (a cell has been run again) we need to clear the cell monitor - const newExecution = this.currentCellTracker.getNumCellsExecuted() > this.cellExecCountSinceSparkJobStart; - if (newExecution) { - this.cellExecCountSinceSparkJobStart = this.currentCellTracker.getNumCellsExecuted(); - this.notebookStore.onCellExecutedAgain(cell.model.id); - } - this.notebookStore.onSparkJobStart(cell.model.id, data); + this.notebookStore.onSparkJobStart(cell.model.id, data); + } + + onSparkStageSubmitted(data: any) { + const cell = this.currentCellTracker.getActiveCell(); + if (!cell) { + console.warn('SparkMonitor: Stage started with no running cell.'); + return; } + this.notebookStore.onSparkStageSubmitted(cell.model.id, data); + } - onSparkStageSubmitted(data: any) { - const cell = this.currentCellTracker.getActiveCell(); - if (!cell) { - console.warn('SparkMonitor: Stage started with no running cell.'); - return; - } - this.notebookStore.onSparkStageSubmitted(cell.model.id, data); + handleMessage(msg: ICommMsgMsg) { + if (!msg.content.data.msgtype) { + console.warn('SparkMonitor: Unknown message'); } - - handleMessage(msg: ICommMsgMsg) { - if (!msg.content.data.msgtype) { - console.warn('SparkMonitor: Unknown message'); - } - if (msg.content.data.msgtype === 'fromscala') { - const data: any = JSON.parse(msg.content.data.msg as string); - switch (data.msgtype) { - case 'sparkJobStart': - this.onSparkJobStart(data); - break; - case 'sparkJobEnd': - this.notebookStore.onSparkJobEnd(data); - break; - case 'sparkStageSubmitted': - this.onSparkStageSubmitted(data); - break; - case 'sparkStageCompleted': - this.notebookStore.onSparkStageCompleted(data); - break; - case 'sparkStageActive': - this.notebookStore.onSparkStageActive(data); - break; - case 'sparkTaskStart': - this.notebookStore.onSparkTaskStart(data); - break; - case 'sparkTaskEnd': - this.notebookStore.onSparkTaskEnd(data); - break; - case 'sparkApplicationStart': - this.notebookStore.onSparkApplicationStart(data); - break; - case 'sparkApplicationEnd': - // noop - break; - case 'sparkExecutorAdded': - this.notebookStore.onSparkExecutorAdded(data); - break; - case 'sparkExecutorRemoved': - this.notebookStore.onSparkExecutorRemoved(data); - break; - default: - console.warn('SparkMonitor: Unknown message'); - break; - } - } + if (msg.content.data.msgtype === 'fromscala') { + const data: any = JSON.parse(msg.content.data.msg as string); + switch (data.msgtype) { + case 'sparkJobStart': + this.onSparkJobStart(data); + break; + case 'sparkJobEnd': + this.notebookStore.onSparkJobEnd(data); + break; + case 'sparkStageSubmitted': + this.onSparkStageSubmitted(data); + break; + case 'sparkStageCompleted': + this.notebookStore.onSparkStageCompleted(data); + break; + case 'sparkStageActive': + this.notebookStore.onSparkStageActive(data); + break; + case 'sparkTaskStart': + this.notebookStore.onSparkTaskStart(data); + break; + case 'sparkTaskEnd': + this.notebookStore.onSparkTaskEnd(data); + break; + case 'sparkApplicationStart': + this.notebookStore.onSparkApplicationStart(data); + break; + case 'sparkApplicationEnd': + // noop + break; + case 'sparkExecutorAdded': + this.notebookStore.onSparkExecutorAdded(data); + break; + case 'sparkExecutorRemoved': + this.notebookStore.onSparkExecutorRemoved(data); + break; + default: + console.warn('SparkMonitor: Unknown message'); + break; + } } + } } diff --git a/src/notebook-extension/currentcell.ts b/src/notebook-extension/currentcell.ts index 50850b9..d88dc56 100644 --- a/src/notebook-extension/currentcell.ts +++ b/src/notebook-extension/currentcell.ts @@ -17,38 +17,44 @@ const cell_queue: any[] = []; /** Called when an execute.CodeCell event occurs. This means an execute request was sent for the current cell. */ function cell_execute_called(event: any, data: any) { - const cell = data.cell; - if (cell instanceof codecell.CodeCell) { - if (cell_queue.length <= 0) { - events.trigger('started.currentcell', cell); - events.trigger('started' + cell.cell_id + 'currentcell', cell); - } - cell_queue.push(cell); - current_cell = cell_queue[0]; + const cell = data.cell; + if (cell instanceof codecell.CodeCell) { + if (cell_queue.length <= 0) { + events.trigger('started.currentcell', cell); + events.trigger('started' + cell.cell_id + 'currentcell', cell); } + cell_queue.push(cell); + current_cell = cell_queue[0]; + } } /** Called when the kernel becomes idle. This means that a cell finished executing. */ function cell_execute_finished() { - if (current_cell) { - events.trigger('finished.currentcell', current_cell); - events.trigger('finished' + current_cell.cell_id + 'currentcell', current_cell); - } - cell_queue.shift(); - current_cell = cell_queue[0]; - if (current_cell) { - events.trigger('started.currentcell', current_cell); - events.trigger('started' + current_cell.cell_id + 'currentcell', current_cell); - } + if (current_cell) { + events.trigger('finished.currentcell', current_cell); + events.trigger( + 'finished' + current_cell.cell_id + 'currentcell', + current_cell + ); + } + cell_queue.shift(); + current_cell = cell_queue[0]; + if (current_cell) { + events.trigger('started.currentcell', current_cell); + events.trigger( + 'started' + current_cell.cell_id + 'currentcell', + current_cell + ); + } } /** @return {CodeCell} - The running cell, or null. */ export function getRunningCell() { - return current_cell; + return current_cell; } /** @return {CodeCell} - The last run cell, or null. */ export function getLastCell() { - return last_cell; + return last_cell; } /** @@ -58,20 +64,20 @@ export function getLastCell() { * @param {data} data - data of the event, contains the cell */ function cell_deleted(event: any, data: any) { - const cell = data.cell; - const index = cell_queue.indexOf(cell); - if (index >= -1) { - cell_queue.splice(index, 1); - } + const cell = data.cell; + const index = cell_queue.indexOf(cell); + if (index >= -1) { + cell_queue.splice(index, 1); + } } /** Register event listeners for detecting running cells. */ export function register() { - events.on('execute.CodeCell', cell_execute_called); - events.on('kernel_idle.Kernel', cell_execute_finished); - events.on('delete.Cell', cell_deleted); - //TODO clear queue on execute error - //For Debugging purposes. Highlights the currently running cell in grey colour. - //events.on('started.currentcell', function (event, cell) { cell.element.css('background-color', '#EEEEEE') }); - //events.on('finished.currentcell', function (event, cell) { cell.element.css('background-color', 'white') }); + events.on('execute.CodeCell', cell_execute_called); + events.on('kernel_idle.Kernel', cell_execute_finished); + events.on('delete.Cell', cell_deleted); + //TODO clear queue on execute error + //For Debugging purposes. Highlights the currently running cell in grey colour. + //events.on('started.currentcell', function (event, cell) { cell.element.css('background-color', '#EEEEEE') }); + //events.on('finished.currentcell', function (event, cell) { cell.element.css('background-color', 'white') }); } diff --git a/src/notebook-extension/entry.js b/src/notebook-extension/entry.js index 7f2880d..67a2628 100644 --- a/src/notebook-extension/entry.js +++ b/src/notebook-extension/entry.js @@ -1,5 +1,7 @@ /* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable no-undef */ -__webpack_public_path__ = document.querySelector('body')?.getAttribute('data-base-url') + 'nbextensions/sparkmonitor/'; +__webpack_public_path__ = + document.querySelector('body')?.getAttribute('data-base-url') + + 'nbextensions/sparkmonitor/'; const defaultExport = require('./index'); module.exports = defaultExport; diff --git a/src/notebook-extension/index.ts b/src/notebook-extension/index.ts index e01596e..ea673fa 100644 --- a/src/notebook-extension/index.ts +++ b/src/notebook-extension/index.ts @@ -3,9 +3,9 @@ import '../../style/notebook.css'; // Entrypoint module for the SparkMonitor Jupyter Notebook extension. export function load_ipython_extension() { - console.log('SparkMonitor: Loading SparkMonitor Front-End Extension'); - const monitor = new JupyterNotebookSparkMonitor(); + console.log('SparkMonitor: Loading SparkMonitor Front-End Extension'); + const monitor = new JupyterNotebookSparkMonitor(); - // For Debugging - (window as any).sparkMonitor = monitor; + // For Debugging + (window as any).sparkMonitor = monitor; } diff --git a/src/notebook-extension/jupyter-notebook-monitor.ts b/src/notebook-extension/jupyter-notebook-monitor.ts index c5d8672..3f32615 100644 --- a/src/notebook-extension/jupyter-notebook-monitor.ts +++ b/src/notebook-extension/jupyter-notebook-monitor.ts @@ -10,154 +10,163 @@ import { NotebookStore } from '../store/notebook'; import { store } from '../store'; export class JupyterNotebookSparkMonitor { - comm: any = null; - notebookStore: NotebookStore; - - constructor() { - cellTracker.register(); - - // For jupyter notebook a single page has only one notebook. - store.notebooks['default'] = new NotebookStore('default'); - this.notebookStore = store.notebooks['default']; - - // Initialize Cell React Widgets - this.createCellReactElements(); - - // Connect on page load - this.startComm(); - - // Reconnect on Kernel restarts - events.on('kernel_connected.Kernel', () => { - this.startComm(); - }); - - events.on('clear_output.CodeCell', (event: any, data: any) => { - //Removing display when output area is cleared - const cellId = data.cell.cell_id; - this.onClearCellOutput(cellId); - }); - this.createButtons(); + comm: any = null; + notebookStore: NotebookStore; + + constructor() { + cellTracker.register(); + + // For jupyter notebook a single page has only one notebook. + store.notebooks['default'] = new NotebookStore('default'); + this.notebookStore = store.notebooks['default']; + + // Initialize Cell React Widgets + this.createCellReactElements(); + + // Connect on page load + this.startComm(); + + // Reconnect on Kernel restarts + events.on('kernel_connected.Kernel', () => { + this.startComm(); + }); + + events.on('clear_output.CodeCell', (event: any, data: any) => { + //Removing display when output area is cleared + const cellId = data.cell.cell_id; + this.onClearCellOutput(cellId); + }); + this.createButtons(); + } + + startComm() { + console.log('SparkMonitor: Starting Comm with kernel.'); + if (Jupyter.notebook.kernel) { + this.comm = Jupyter.notebook.kernel.comm_manager.new_comm( + 'SparkMonitor', + { msgtype: 'openfromfrontend' } + ); + // Register a message handler + this.comm.on_msg((msg: any) => this.handleCommMessage(msg)); + // this.comm.on_close($.proxy(that.on_comm_close, that)); // noop + } else { + console.log('SparkMonitor: No communication established, kernel null'); } - - startComm() { - console.log('SparkMonitor: Starting Comm with kernel.'); - if (Jupyter.notebook.kernel) { - this.comm = Jupyter.notebook.kernel.comm_manager.new_comm('SparkMonitor', { msgtype: 'openfromfrontend' }); - // Register a message handler - this.comm.on_msg((msg: any) => this.handleCommMessage(msg)); - // this.comm.on_close($.proxy(that.on_comm_close, that)); // noop - } else { - console.log('SparkMonitor: No communication established, kernel null'); - } - } - - createCellReactElements() { - events.on('execute.CodeCell', (event: any, data: any) => { - const cell = data.cell; - const cellDiv: HTMLDivElement = cell.element.get(0); - if (!cellDiv.querySelector('.sparkMonitorCellRoot')) { - const element = document.createElement('div'); - cellDiv.querySelector('.input')?.insertAdjacentElement('afterend', element); - this.notebookStore.onCellExecutedAgain(cell.cell_id); - const cellWidget = React.createElement(CellWidget, { - notebookId: 'default', - cellId: cell.cell_id, - }); - ReactDOM.render(cellWidget, element); - } + } + + createCellReactElements() { + events.on('execute.CodeCell', (event: any, data: any) => { + const cell = data.cell; + const cellDiv: HTMLDivElement = cell.element.get(0); + if (!cellDiv.querySelector('.sparkMonitorCellRoot')) { + const element = document.createElement('div'); + cellDiv + .querySelector('.input') + ?.insertAdjacentElement('afterend', element); + this.notebookStore.onCellExecutedAgain(cell.cell_id); + const cellWidget = React.createElement(CellWidget, { + notebookId: 'default', + cellId: cell.cell_id }); + ReactDOM.render(cellWidget, element); + } + }); + } + + createButtons() { + let isVisible = true; + + const handler = () => { + this.notebookStore.toggleHideAllDisplays(); + if ((isVisible = !isVisible)) { + button.removeClass('disable'); + } else { + button.addClass('disable'); + } + }; + + const action = { + icon: 'fa-tasks', // a font-awesome class for icon + help: 'Show/Hide Spark Monitoring', + help_index: 'zz', // Sorting Order in keyboard shortcut dialog + handler + }; + const prefix = 'SparkMonitor'; + const action_name = 'toggle-spark-monitoring'; + + const full_action_name = Jupyter.actions.register( + action, + action_name, + prefix + ); // returns 'my_extension:show-alert' + const button = Jupyter.toolbar.add_buttons_group([full_action_name]); + + button.addClass('extension_button'); + } + + handleCommMessage(msg: any) { + if (!msg.content.data.msgtype) { + console.warn('SparkMonitor: Unknown message'); } - - createButtons() { - let isVisible = true; - - const handler = () => { - this.notebookStore.toggleHideAllDisplays(); - if ((isVisible = !isVisible)) { - button.removeClass('disable'); - } else { - button.addClass('disable'); - } - }; - - const action = { - icon: 'fa-tasks', // a font-awesome class for icon - help: 'Show/Hide Spark Monitoring', - help_index: 'zz', // Sorting Order in keyboard shortcut dialog - handler, - }; - const prefix = 'SparkMonitor'; - const action_name = 'toggle-spark-monitoring'; - - const full_action_name = Jupyter.actions.register(action, action_name, prefix); // returns 'my_extension:show-alert' - const button = Jupyter.toolbar.add_buttons_group([full_action_name]); - - button.addClass('extension_button'); - } - - handleCommMessage(msg: any) { - if (!msg.content.data.msgtype) { - console.warn('SparkMonitor: Unknown message'); - } - if (msg.content.data.msgtype === 'fromscala') { - const data = JSON.parse(msg.content.data.msg); - switch (data.msgtype) { - case 'sparkJobStart': - this.onSparkJobStart(data); - break; - case 'sparkJobEnd': - this.notebookStore.onSparkJobEnd(data); - break; - case 'sparkStageSubmitted': - this.onSparkStageSubmitted(data); - break; - case 'sparkStageCompleted': - this.notebookStore.onSparkStageCompleted(data); - break; - case 'sparkStageActive': - this.notebookStore.onSparkStageActive(data); - break; - case 'sparkTaskStart': - this.notebookStore.onSparkTaskStart(data); - break; - case 'sparkTaskEnd': - this.notebookStore.onSparkTaskEnd(data); - break; - case 'sparkApplicationStart': - this.notebookStore.onSparkApplicationStart(data); - break; - case 'sparkApplicationEnd': - // noop - break; - case 'sparkExecutorAdded': - this.notebookStore.onSparkExecutorAdded(data); - break; - case 'sparkExecutorRemoved': - this.notebookStore.onSparkExecutorRemoved(data); - break; - } - } + if (msg.content.data.msgtype === 'fromscala') { + const data = JSON.parse(msg.content.data.msg); + switch (data.msgtype) { + case 'sparkJobStart': + this.onSparkJobStart(data); + break; + case 'sparkJobEnd': + this.notebookStore.onSparkJobEnd(data); + break; + case 'sparkStageSubmitted': + this.onSparkStageSubmitted(data); + break; + case 'sparkStageCompleted': + this.notebookStore.onSparkStageCompleted(data); + break; + case 'sparkStageActive': + this.notebookStore.onSparkStageActive(data); + break; + case 'sparkTaskStart': + this.notebookStore.onSparkTaskStart(data); + break; + case 'sparkTaskEnd': + this.notebookStore.onSparkTaskEnd(data); + break; + case 'sparkApplicationStart': + this.notebookStore.onSparkApplicationStart(data); + break; + case 'sparkApplicationEnd': + // noop + break; + case 'sparkExecutorAdded': + this.notebookStore.onSparkExecutorAdded(data); + break; + case 'sparkExecutorRemoved': + this.notebookStore.onSparkExecutorRemoved(data); + break; + } } + } - onClearCellOutput(cellId: string) { - this.notebookStore.onCellExecutedAgain(cellId); - } + onClearCellOutput(cellId: string) { + this.notebookStore.onCellExecutedAgain(cellId); + } - onSparkJobStart(data: any) { - const cell = cellTracker.getRunningCell(); - if (!cell) { - console.error('SparkMonitor: Job start event with no running cell.'); - return; - } - this.notebookStore.onSparkJobStart(cell.cell_id, data); + onSparkJobStart(data: any) { + const cell = cellTracker.getRunningCell(); + if (!cell) { + console.error('SparkMonitor: Job start event with no running cell.'); + return; } - - onSparkStageSubmitted(data: any) { - const cell = cellTracker.getRunningCell(); - if (!cell) { - console.error('SparkMonitor: Stage submit event with no running cell.'); - return; - } - this.notebookStore.onSparkStageSubmitted(cell.cell_id, data); + this.notebookStore.onSparkJobStart(cell.cell_id, data); + } + + onSparkStageSubmitted(data: any) { + const cell = cellTracker.getRunningCell(); + if (!cell) { + console.error('SparkMonitor: Stage submit event with no running cell.'); + return; } + this.notebookStore.onSparkStageSubmitted(cell.cell_id, data); + } } diff --git a/src/notebook-extension/webpack.config.js b/src/notebook-extension/webpack.config.js index dd4f5f6..6acae10 100644 --- a/src/notebook-extension/webpack.config.js +++ b/src/notebook-extension/webpack.config.js @@ -3,53 +3,62 @@ const path = require('path'); const config = { - mode: 'production', - entry: { - extension: path.join(__dirname, 'entry.js'), - }, - output: { - path: path.join(__dirname, '../../sparkmonitor/nbextension'), - filename: '[name].js', - libraryTarget: 'umd', - publicPath: '', - clean: true, - }, - externals: ['require', 'base/js/namespace', 'base/js/events', 'notebook/js/codecell'], - devtool: 'source-map', - resolve: { - extensions: ['.ts', '.tsx', '.js'], - }, - module: { - rules: [ - { - test: /\.(js|ts|tsx)$/, - exclude: /(node_modules|bower_components)/, - use: { - loader: 'babel-loader', - options: { - presets: ['@babel/preset-env', '@babel/preset-typescript', '@babel/preset-react'], - }, - }, - }, - { - test: /\.css$/, - use: ['style-loader', 'css-loader'], - }, - { - test: /\.(png|svg|jpg|gif)$/, - use: ['file-loader'], - }, - { - test: /\.(html)$/, - use: { - loader: 'html-loader', - options: { - attrs: [':data-src'], - }, - }, - }, - ], - }, + mode: 'production', + entry: { + extension: path.join(__dirname, 'entry.js') + }, + output: { + path: path.join(__dirname, '../../sparkmonitor/nbextension'), + filename: '[name].js', + libraryTarget: 'umd', + publicPath: '', + clean: true + }, + externals: [ + 'require', + 'base/js/namespace', + 'base/js/events', + 'notebook/js/codecell' + ], + devtool: 'source-map', + resolve: { + extensions: ['.ts', '.tsx', '.js'] + }, + module: { + rules: [ + { + test: /\.(js|ts|tsx)$/, + exclude: /(node_modules|bower_components)/, + use: { + loader: 'babel-loader', + options: { + presets: [ + '@babel/preset-env', + '@babel/preset-typescript', + '@babel/preset-react' + ] + } + } + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + }, + { + test: /\.(png|svg|jpg|gif)$/, + use: ['file-loader'] + }, + { + test: /\.(html)$/, + use: { + loader: 'html-loader', + options: { + attrs: [':data-src'] + } + } + } + ] + } }; module.exports = config; diff --git a/src/store/cell.ts b/src/store/cell.ts index 5a5d101..1506d82 100644 --- a/src/store/cell.ts +++ b/src/store/cell.ts @@ -4,46 +4,49 @@ import { TaskChartStore } from './task-chart-store'; import type { NotebookStore } from './notebook'; export class Cell { - view: 'jobs' | 'taskchart' | 'timeline' = 'jobs'; - isCollapsed = false; - isRemoved = false; - uniqueJobIds: Array = []; - taskChartStore: TaskChartStore; - constructor(public cellId: string, private notebookStore: NotebookStore) { - makeAutoObservable(this); - this.taskChartStore = new TaskChartStore(this.notebookStore); - } - - toggleCollapseCellDisplay() { - this.isCollapsed = !this.isCollapsed; - } - - toggleHideCellDisplay() { - this.isRemoved = !this.isRemoved; - } - - setView(view: 'jobs' | 'taskchart' | 'timeline') { - this.view = view; - this.isCollapsed = false; - this.isRemoved = false; - } - - get jobs() { - return this.uniqueJobIds.map((id) => this.notebookStore.jobs[id]); - } - - get numActiveJobs() { - return this.jobs.filter((job) => job.status === 'RUNNING').length; - } - get numFailedJobs() { - return this.jobs.filter((job) => job.status === 'FAILED').length; - } - - get numCompletedJobs() { - return this.jobs.filter((job) => job.status === 'COMPLETED').length; - } - - get numTotalJobs() { - return this.uniqueJobIds.length; - } + view: 'jobs' | 'taskchart' | 'timeline' = 'jobs'; + isCollapsed = false; + isRemoved = false; + uniqueJobIds: Array = []; + taskChartStore: TaskChartStore; + constructor( + public cellId: string, + private notebookStore: NotebookStore + ) { + makeAutoObservable(this); + this.taskChartStore = new TaskChartStore(this.notebookStore); + } + + toggleCollapseCellDisplay() { + this.isCollapsed = !this.isCollapsed; + } + + toggleHideCellDisplay() { + this.isRemoved = !this.isRemoved; + } + + setView(view: 'jobs' | 'taskchart' | 'timeline') { + this.view = view; + this.isCollapsed = false; + this.isRemoved = false; + } + + get jobs() { + return this.uniqueJobIds.map(id => this.notebookStore.jobs[id]); + } + + get numActiveJobs() { + return this.jobs.filter(job => job.status === 'RUNNING').length; + } + get numFailedJobs() { + return this.jobs.filter(job => job.status === 'FAILED').length; + } + + get numCompletedJobs() { + return this.jobs.filter(job => job.status === 'COMPLETED').length; + } + + get numTotalJobs() { + return this.uniqueJobIds.length; + } } diff --git a/src/store/index.ts b/src/store/index.ts index 8a48122..1599294 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -4,10 +4,10 @@ import { NotebookStore } from './notebook'; import type { Cell } from './cell'; class SparkMonitorStore { - notebooks: { [notebookId: string]: NotebookStore } = {}; - constructor() { - makeAutoObservable(this); - } + notebooks: { [notebookId: string]: NotebookStore } = {}; + constructor() { + makeAutoObservable(this); + } } export const store = new SparkMonitorStore(); @@ -15,18 +15,20 @@ export const store = new SparkMonitorStore(); const StoreContext = React.createContext(store); // Use a non-null assertion here so that we can avoid an unnecessary check for null down the component hierarchy. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -export const NotebookStoreContext = React.createContext(undefined!); +export const NotebookStoreContext = React.createContext( + undefined! +); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion export const CellStoreContext = React.createContext(undefined!); export const useStore = () => { - return React.useContext(StoreContext); + return React.useContext(StoreContext); }; export const useNotebookStore = () => { - return React.useContext(NotebookStoreContext); + return React.useContext(NotebookStoreContext); }; export const useCellStore = () => { - return React.useContext(CellStoreContext); + return React.useContext(CellStoreContext); }; diff --git a/src/store/notebook.ts b/src/store/notebook.ts index 13793d1..386b814 100644 --- a/src/store/notebook.ts +++ b/src/store/notebook.ts @@ -4,237 +4,242 @@ import { SparkJob } from './spark-job'; import { Cell } from './cell'; export class NotebookStore { - numExecutors?: number; - numTotalCores?: number; - applicationName?: string; - applicationId?: string; - applicationAttemptId?: string; - uniqueId = 'default-key'; - hideAllDisplays = false; - - cells: { [cellId: string]: Cell } = {}; - jobs: { [jobId: string]: SparkJob } = {}; - stages: { [stageId: string]: SparkStage } = {}; - - constructor(public notebookPanelId: string) { - makeAutoObservable(this); - } - - toggleHideAllDisplays() { - this.hideAllDisplays = !this.hideAllDisplays; - } - - onSparkApplicationStart(data: any) { - this.applicationId = data.appId; - this.applicationName = data.appName; - this.applicationAttemptId = data.appAttemptId; - this.uniqueId = `app${this.applicationId}-attempt${this.applicationAttemptId}`; - } - - private deleteCellData(cellId: string) { - const cell = this.cells[cellId]; - if (cell) { - cell.uniqueJobIds.forEach((uniqueJobId) => { - const job = this.jobs[uniqueJobId]; - if (job) { - job.uniqueStageIds.forEach((uniqueStageId) => { - const stage = this.stages[uniqueStageId]; - if (stage) { - delete this.stages[uniqueStageId]; - } - }); - delete this.jobs[uniqueJobId]; - } - }); - delete this.cells[cellId]; + numExecutors?: number; + numTotalCores?: number; + applicationName?: string; + applicationId?: string; + applicationAttemptId?: string; + uniqueId = 'default-key'; + hideAllDisplays = false; + + cells: { [cellId: string]: Cell } = {}; + jobs: { [jobId: string]: SparkJob } = {}; + stages: { [stageId: string]: SparkStage } = {}; + + constructor(public notebookPanelId: string) { + makeAutoObservable(this); + } + + toggleHideAllDisplays() { + this.hideAllDisplays = !this.hideAllDisplays; + } + + onSparkApplicationStart(data: any) { + this.applicationId = data.appId; + this.applicationName = data.appName; + this.applicationAttemptId = data.appAttemptId; + this.uniqueId = `app${this.applicationId}-attempt${this.applicationAttemptId}`; + } + + private deleteCellData(cellId: string) { + const cell = this.cells[cellId]; + if (cell) { + cell.uniqueJobIds.forEach(uniqueJobId => { + const job = this.jobs[uniqueJobId]; + if (job) { + job.uniqueStageIds.forEach(uniqueStageId => { + const stage = this.stages[uniqueStageId]; + if (stage) { + delete this.stages[uniqueStageId]; + } + }); + delete this.jobs[uniqueJobId]; } + }); + delete this.cells[cellId]; } - - onCellRemoved(cellId: string) { - this.deleteCellData(cellId); + } + + onCellRemoved(cellId: string) { + this.deleteCellData(cellId); + } + + onCellExecutedAgain(cellId: string) { + this.deleteCellData(cellId); + this.cells[cellId] = new Cell(cellId, this); + } + + onSparkJobStart(cellId: string, data: any) { + // These values are set here as previous messages may + // be missed if reconnecting from a browser reload. + this.numTotalCores = data.totalCores; + this.numExecutors = data.numExecutors; + + const job = new SparkJob(this); + job.uniqueId = `${this.uniqueId}-job-${data.jobId}`; + job.jobId = data.jobId; + job.status = data.status; + job.cellId = cellId; + job.name = String(data.name).split(' ')[0]; + job.startTime = new Date(data.submissionTime); + job.stageIds = data.stageIds; + job.numStages = data.stageIds.length; + job.numTasks = data.numTasks; + + data.stageIds.forEach((stageId: string) => { + const uniqueStageId = `${this.uniqueId}-stage-${stageId}`; + let stage = this.stages[uniqueStageId]; + if (!stage) { + stage = new SparkStage(); + stage.status = 'PENDING'; + this.stages[uniqueStageId] = stage; + } + stage.uniqueJobId = job.uniqueId; + stage.numTasks = data.stageInfos[stageId].numTasks; + stage.name = data.stageInfos[stageId].name; + job.uniqueStageIds.push(uniqueStageId); + }); + job.uniqueStageIds.sort((a, b) => + a.localeCompare(b, undefined, { numeric: true }) + ); + + if (job.name === 'null') { + const lastStageId = Math.max.apply(null, data.stageIds); + job.name = this.stages[`${this.uniqueId}-stage-${lastStageId}`].name; } - onCellExecutedAgain(cellId: string) { - this.deleteCellData(cellId); - this.cells[cellId] = new Cell(cellId, this); + if (!this.cells[cellId]) { + this.cells[cellId] = new Cell(cellId, this); } - - onSparkJobStart(cellId: string, data: any) { - // These values are set here as previous messages may - // be missed if reconnecting from a browser reload. - this.numTotalCores = data.totalCores; - this.numExecutors = data.numExecutors; - - const job = new SparkJob(this); - job.uniqueId = `${this.uniqueId}-job-${data.jobId}`; - job.jobId = data.jobId; - job.status = data.status; - job.cellId = cellId; - job.name = String(data.name).split(' ')[0]; - job.startTime = new Date(data.submissionTime); - job.stageIds = data.stageIds; - job.numStages = data.stageIds.length; - job.numTasks = data.numTasks; - - data.stageIds.forEach((stageId: string) => { - const uniqueStageId = `${this.uniqueId}-stage-${stageId}`; - let stage = this.stages[uniqueStageId]; - if (!stage) { - stage = new SparkStage(); - stage.status = 'PENDING'; - this.stages[uniqueStageId] = stage; - } - stage.uniqueJobId = job.uniqueId; - stage.numTasks = data.stageInfos[stageId].numTasks; - stage.name = data.stageInfos[stageId].name; - job.uniqueStageIds.push(uniqueStageId); - }); - job.uniqueStageIds.sort((a, b) => a.localeCompare(b, undefined, { numeric: true })); - - if (job.name === 'null') { - const lastStageId = Math.max.apply(null, data.stageIds); - job.name = this.stages[`${this.uniqueId}-stage-${lastStageId}`].name; - } - - if (!this.cells[cellId]) { - this.cells[cellId] = new Cell(cellId, this); + this.cells[cellId].uniqueJobIds.push(job.uniqueId); + job.cell = this.cells[cellId]; + job.cell.taskChartStore.onSparkJobStart(data); + this.jobs[job.uniqueId] = job; + } + + onSparkJobEnd(data: any) { + const uniqueId = `${this.uniqueId}-job-${data.jobId}`; + const job = this.jobs[uniqueId]; + if (job) { + job.status = data.status; + job.endTime = new Date(data.completionTime); + job.uniqueStageIds.forEach(uniqueStageId => { + if (this.stages[uniqueStageId]?.status === 'PENDING') { + this.stages[uniqueStageId].status = 'SKIPPED'; + job.numTasks -= this.stages[uniqueStageId].numTasks; } - this.cells[cellId].uniqueJobIds.push(job.uniqueId); - job.cell = this.cells[cellId]; - job.cell.taskChartStore.onSparkJobStart(data); - this.jobs[job.uniqueId] = job; + }); + job.cell?.taskChartStore.onSparkJobEnd(data); + } else { + console.warn('SparkMonitor: Could not identify job'); } - - onSparkJobEnd(data: any) { - const uniqueId = `${this.uniqueId}-job-${data.jobId}`; - const job = this.jobs[uniqueId]; - if (job) { - job.status = data.status; - job.endTime = new Date(data.completionTime); - job.uniqueStageIds.forEach((uniqueStageId) => { - if (this.stages[uniqueStageId]?.status === 'PENDING') { - this.stages[uniqueStageId].status = 'SKIPPED'; - job.numTasks -= this.stages[uniqueStageId].numTasks; - } - }); - job.cell?.taskChartStore.onSparkJobEnd(data); - } else { - console.warn('SparkMonitor: Could not identify job'); - } + } + + onSparkStageSubmitted(cellId: string, data: any) { + const submissionTime = + data.submissionTime === -1 ? new Date() : new Date(data.submissionTime); + const uniqueStageId = `${this.uniqueId}-stage-${data.stageId}`; + if (!this.stages[uniqueStageId]) { + this.stages[uniqueStageId] = new SparkStage(); + this.stages[uniqueStageId].uniqueId = uniqueStageId; } - - onSparkStageSubmitted(cellId: string, data: any) { - const submissionTime = data.submissionTime === -1 ? new Date() : new Date(data.submissionTime); - const uniqueStageId = `${this.uniqueId}-stage-${data.stageId}`; - if (!this.stages[uniqueStageId]) { - this.stages[uniqueStageId] = new SparkStage(); - this.stages[uniqueStageId].uniqueId = uniqueStageId; - } - const stage = this.stages[uniqueStageId]; - stage.cellId = cellId; - stage.stageId = data.stageId; - stage.status = 'RUNNING'; - stage.name = String(data.name).split(' ')[0]; - stage.submissionTime = submissionTime; - stage.numTasks = data.numTasks; - } - - onSparkStageCompleted(data: any) { - const uniqueStageId = `${this.uniqueId}-stage-${data.stageId}`; - const stage = this.stages[uniqueStageId]; - if (stage) { - stage.status = data.status; - stage.completionTime = new Date(data.completionTime); - stage.submissionTime = new Date(data.submissionTime); - stage.numActiveTasks = 0; - stage.numCompletedTasks = data.numCompletedTasks; - stage.numFailedTasks = data.numFailedTasks; - stage.numTasks = data.numTasks; - - const job = this.jobs[stage.uniqueJobId]; - if (job) { - job.numActiveTasks = 0; - job.numCompletedTasks = 0; - job.numFailedTasks = 0; - job.numTasks = 0; - - // Update active/completed/failed tasks number (scan all job stages tasks stats) - job.uniqueStageIds.forEach((uniqueStageId) => { - job.numActiveTasks += this.stages[uniqueStageId]?.numActiveTasks || 0; - job.numCompletedTasks += this.stages[uniqueStageId]?.numCompletedTasks || 0; - job.numFailedTasks += this.stages[uniqueStageId]?.numFailedTasks || 0; - job.numTasks += this.stages[uniqueStageId]?.numTasks || 0; - }); - } - } else { - console.warn('SparkMonitor: Unable to identify stage'); - } + const stage = this.stages[uniqueStageId]; + stage.cellId = cellId; + stage.stageId = data.stageId; + stage.status = 'RUNNING'; + stage.name = String(data.name).split(' ')[0]; + stage.submissionTime = submissionTime; + stage.numTasks = data.numTasks; + } + + onSparkStageCompleted(data: any) { + const uniqueStageId = `${this.uniqueId}-stage-${data.stageId}`; + const stage = this.stages[uniqueStageId]; + if (stage) { + stage.status = data.status; + stage.completionTime = new Date(data.completionTime); + stage.submissionTime = new Date(data.submissionTime); + stage.numActiveTasks = 0; + stage.numCompletedTasks = data.numCompletedTasks; + stage.numFailedTasks = data.numFailedTasks; + stage.numTasks = data.numTasks; + + const job = this.jobs[stage.uniqueJobId]; + if (job) { + job.numActiveTasks = 0; + job.numCompletedTasks = 0; + job.numFailedTasks = 0; + job.numTasks = 0; + + // Update active/completed/failed tasks number (scan all job stages tasks stats) + job.uniqueStageIds.forEach(uniqueStageId => { + job.numActiveTasks += this.stages[uniqueStageId]?.numActiveTasks || 0; + job.numCompletedTasks += + this.stages[uniqueStageId]?.numCompletedTasks || 0; + job.numFailedTasks += this.stages[uniqueStageId]?.numFailedTasks || 0; + job.numTasks += this.stages[uniqueStageId]?.numTasks || 0; + }); + } + } else { + console.warn('SparkMonitor: Unable to identify stage'); } + } - onSparkExecutorAdded(data: any) { - this.numTotalCores = data.totalCores; - if (!this.numExecutors) { - this.numExecutors = 0; - } - this.numExecutors += 1; + onSparkExecutorAdded(data: any) { + this.numTotalCores = data.totalCores; + if (!this.numExecutors) { + this.numExecutors = 0; } + this.numExecutors += 1; + } - onSparkExecutorRemoved(data: any) { - this.numTotalCores = data.totalCores; - if (!this.numExecutors) { - this.numExecutors = 0; - } - this.numExecutors -= 1; + onSparkExecutorRemoved(data: any) { + this.numTotalCores = data.totalCores; + if (!this.numExecutors) { + this.numExecutors = 0; } - - onSparkTaskStart(data: any) { - const uniqueStageId = `${this.uniqueId}-stage-${data.stageId}`; - const stage = this.stages[uniqueStageId]; - if (stage) { - const uniqueJobId = stage.uniqueJobId; - const job = this.jobs[uniqueJobId]; - if (job) { - job.cell?.taskChartStore.onSparkTaskStart(data); - } - } + this.numExecutors -= 1; + } + + onSparkTaskStart(data: any) { + const uniqueStageId = `${this.uniqueId}-stage-${data.stageId}`; + const stage = this.stages[uniqueStageId]; + if (stage) { + const uniqueJobId = stage.uniqueJobId; + const job = this.jobs[uniqueJobId]; + if (job) { + job.cell?.taskChartStore.onSparkTaskStart(data); + } } - - onSparkTaskEnd(data: any) { - const uniqueStageId = `${this.uniqueId}-stage-${data.stageId}`; - const stage = this.stages[uniqueStageId]; - if (stage) { - const uniqueJobId = stage.uniqueJobId; - const job = this.jobs[uniqueJobId]; - if (job) { - job.cell?.taskChartStore.onSparkTaskEnd(data); - } - } + } + + onSparkTaskEnd(data: any) { + const uniqueStageId = `${this.uniqueId}-stage-${data.stageId}`; + const stage = this.stages[uniqueStageId]; + if (stage) { + const uniqueJobId = stage.uniqueJobId; + const job = this.jobs[uniqueJobId]; + if (job) { + job.cell?.taskChartStore.onSparkTaskEnd(data); + } } - - // Periodic stage updates - onSparkStageActive(data: any) { - const uniqueStageId = `${this.uniqueId}-stage-${data.stageId}`; - const stage = this.stages[uniqueStageId]; - if (stage && stage.status === 'RUNNING') { - stage.numActiveTasks = data.numActiveTasks; - stage.numCompletedTasks = data.numCompletedTasks; - stage.numFailedTasks = data.numFailedTasks; - - const job = this.jobs[stage.uniqueJobId]; - if (job) { - job.numActiveTasks = 0; - job.numCompletedTasks = 0; - job.numFailedTasks = 0; - job.numTasks = 0; - - // Update active/completed/failed tasks number (scan all job stages tasks stats) - job.uniqueStageIds.forEach((uniqueStageId) => { - job.numActiveTasks += this.stages[uniqueStageId]?.numActiveTasks || 0; - job.numCompletedTasks += this.stages[uniqueStageId]?.numCompletedTasks || 0; - job.numFailedTasks += this.stages[uniqueStageId]?.numFailedTasks || 0; - job.numTasks += this.stages[uniqueStageId]?.numTasks || 0; - }); - } - } + } + + // Periodic stage updates + onSparkStageActive(data: any) { + const uniqueStageId = `${this.uniqueId}-stage-${data.stageId}`; + const stage = this.stages[uniqueStageId]; + if (stage && stage.status === 'RUNNING') { + stage.numActiveTasks = data.numActiveTasks; + stage.numCompletedTasks = data.numCompletedTasks; + stage.numFailedTasks = data.numFailedTasks; + + const job = this.jobs[stage.uniqueJobId]; + if (job) { + job.numActiveTasks = 0; + job.numCompletedTasks = 0; + job.numFailedTasks = 0; + job.numTasks = 0; + + // Update active/completed/failed tasks number (scan all job stages tasks stats) + job.uniqueStageIds.forEach(uniqueStageId => { + job.numActiveTasks += this.stages[uniqueStageId]?.numActiveTasks || 0; + job.numCompletedTasks += + this.stages[uniqueStageId]?.numCompletedTasks || 0; + job.numFailedTasks += this.stages[uniqueStageId]?.numFailedTasks || 0; + job.numTasks += this.stages[uniqueStageId]?.numTasks || 0; + }); + } } + } } diff --git a/src/store/spark-job.ts b/src/store/spark-job.ts index e34e824..469e3b8 100644 --- a/src/store/spark-job.ts +++ b/src/store/spark-job.ts @@ -4,50 +4,50 @@ import type { Cell } from './cell'; import type { NotebookStore } from './notebook'; export class SparkJob { - uniqueId!: string; - cellId!: string; - jobId!: string; - status: 'RUNNING' | 'COMPLETED' | 'FAILED' = 'RUNNING'; - name = 'unnamed'; - startTime!: Date; - endTime?: Date; - stageIds: string[] = []; - uniqueStageIds: string[] = []; - - numStages = 0; - - numTasks = 0; - numActiveTasks = 0; - numCompletedTasks = 0; - numFailedTasks = 0; - - cell?: Cell; - - get numActiveStages() { - return this.uniqueStageIds.filter((stageId) => { - return this.notebookStore.stages[stageId].status === 'PENDING'; - }).length; - } - - get numCompletedStages() { - return this.uniqueStageIds.filter((stageId) => { - return this.notebookStore.stages[stageId].status === 'COMPLETED'; - }).length; - } - - get numFailedStages() { - return this.uniqueStageIds.filter((stageId) => { - return this.notebookStore.stages[stageId].status === 'FAILED'; - }).length; - } - - get numSkippedStages() { - return this.uniqueStageIds.filter((stageId) => { - return this.notebookStore.stages[stageId].status === 'SKIPPED'; - }).length; - } - - constructor(private notebookStore: NotebookStore) { - makeAutoObservable(this); - } + uniqueId!: string; + cellId!: string; + jobId!: string; + status: 'RUNNING' | 'COMPLETED' | 'FAILED' = 'RUNNING'; + name = 'unnamed'; + startTime!: Date; + endTime?: Date; + stageIds: string[] = []; + uniqueStageIds: string[] = []; + + numStages = 0; + + numTasks = 0; + numActiveTasks = 0; + numCompletedTasks = 0; + numFailedTasks = 0; + + cell?: Cell; + + get numActiveStages() { + return this.uniqueStageIds.filter(stageId => { + return this.notebookStore.stages[stageId].status === 'PENDING'; + }).length; + } + + get numCompletedStages() { + return this.uniqueStageIds.filter(stageId => { + return this.notebookStore.stages[stageId].status === 'COMPLETED'; + }).length; + } + + get numFailedStages() { + return this.uniqueStageIds.filter(stageId => { + return this.notebookStore.stages[stageId].status === 'FAILED'; + }).length; + } + + get numSkippedStages() { + return this.uniqueStageIds.filter(stageId => { + return this.notebookStore.stages[stageId].status === 'SKIPPED'; + }).length; + } + + constructor(private notebookStore: NotebookStore) { + makeAutoObservable(this); + } } diff --git a/src/store/spark-stage.ts b/src/store/spark-stage.ts index 3c9073b..ddaf74c 100644 --- a/src/store/spark-stage.ts +++ b/src/store/spark-stage.ts @@ -1,21 +1,27 @@ import { makeAutoObservable } from 'mobx'; export class SparkStage { - uniqueId!: string; - uniqueJobId!: string; - cellId!: string; - stageId!: string; - status!: 'UNKNOWN' | 'COMPLETED' | 'FAILED' | 'RUNNING' | 'PENDING' | 'SKIPPED'; - name!: string; + uniqueId!: string; + uniqueJobId!: string; + cellId!: string; + stageId!: string; + status!: + | 'UNKNOWN' + | 'COMPLETED' + | 'FAILED' + | 'RUNNING' + | 'PENDING' + | 'SKIPPED'; + name!: string; - numTasks!: number; - numActiveTasks = 0; - numCompletedTasks = 0; - numFailedTasks = 0; - submissionTime!: Date; - completionTime?: Date; + numTasks!: number; + numActiveTasks = 0; + numCompletedTasks = 0; + numFailedTasks = 0; + submissionTime!: Date; + completionTime?: Date; - constructor() { - makeAutoObservable(this); - } + constructor() { + makeAutoObservable(this); + } } diff --git a/src/store/task-chart-store.ts b/src/store/task-chart-store.ts index 593d5ba..71255fe 100644 --- a/src/store/task-chart-store.ts +++ b/src/store/task-chart-store.ts @@ -1,53 +1,56 @@ import { NotebookStore } from './notebook'; export class TaskChartStore { - jobDataX: Array = []; - jobDataY: Array = []; - jobDataText: Array = []; - executorDataX: Array = []; - executorDataY: Array = []; - taskDataX: Array = []; - taskDataY: Array = []; - numActiveTasks = 0; - - constructor(private notebookStore: NotebookStore) {} - - addExecutorData(time: number, numCores: number) { - this.executorDataX.push(time); - this.executorDataY.push(numCores); - } - - addTaskData(time: number, numTasks: number) { - this.taskDataX.push(new Date(time).getTime()); - this.taskDataY.push(numTasks); - this.addExecutorData(new Date(time).getTime(), this.notebookStore.numTotalCores || 0); - } - - onSparkJobStart(data: any) { - const submissionTimestamp = new Date(data.submissionTime).getTime(); - this.jobDataX.push(submissionTimestamp); - this.jobDataY.push(0); - this.jobDataText.push(`Job ${data.jobId} started`); - - this.addExecutorData(submissionTimestamp, data.totalCores); - } - - onSparkJobEnd(data: any) { - const completionTime = new Date(data.completionTime).getTime(); - this.jobDataX.push(completionTime); - this.jobDataY.push(0); - this.jobDataText.push(`Job ${data.jobId} ended`); - } - - onSparkTaskStart(data: any) { - this.addTaskData(data.launchTime, this.numActiveTasks); - this.numActiveTasks += 1; - this.addTaskData(data.launchTime, this.numActiveTasks); - } - - onSparkTaskEnd(data: any) { - this.addTaskData(data.finishTime, this.numActiveTasks); - this.numActiveTasks -= 1; - this.addTaskData(data.finishTime, this.numActiveTasks); - } + jobDataX: Array = []; + jobDataY: Array = []; + jobDataText: Array = []; + executorDataX: Array = []; + executorDataY: Array = []; + taskDataX: Array = []; + taskDataY: Array = []; + numActiveTasks = 0; + + constructor(private notebookStore: NotebookStore) {} + + addExecutorData(time: number, numCores: number) { + this.executorDataX.push(time); + this.executorDataY.push(numCores); + } + + addTaskData(time: number, numTasks: number) { + this.taskDataX.push(new Date(time).getTime()); + this.taskDataY.push(numTasks); + this.addExecutorData( + new Date(time).getTime(), + this.notebookStore.numTotalCores || 0 + ); + } + + onSparkJobStart(data: any) { + const submissionTimestamp = new Date(data.submissionTime).getTime(); + this.jobDataX.push(submissionTimestamp); + this.jobDataY.push(0); + this.jobDataText.push(`Job ${data.jobId} started`); + + this.addExecutorData(submissionTimestamp, data.totalCores); + } + + onSparkJobEnd(data: any) { + const completionTime = new Date(data.completionTime).getTime(); + this.jobDataX.push(completionTime); + this.jobDataY.push(0); + this.jobDataText.push(`Job ${data.jobId} ended`); + } + + onSparkTaskStart(data: any) { + this.addTaskData(data.launchTime, this.numActiveTasks); + this.numActiveTasks += 1; + this.addTaskData(data.launchTime, this.numActiveTasks); + } + + onSparkTaskEnd(data: any) { + this.addTaskData(data.finishTime, this.numActiveTasks); + this.numActiveTasks -= 1; + this.addTaskData(data.finishTime, this.numActiveTasks); + } } diff --git a/style/jobtable.css b/style/jobtable.css index 46151c2..f48af85 100644 --- a/style/jobtable.css +++ b/style/jobtable.css @@ -1,217 +1,217 @@ .pm .tdstageicon { - display: block; - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAXUlEQVR4AWMAgVGwjkGKNA3/GT4wZDAwkqABDA8zaBKtAQp/MjQwsBGnAQGvMVgTpwEB/zFMZ+AjoAEDPmUIpKGGfwzTGPho5OmfDPUMbKREnAYpSSOdgZGSxDcKADdXTK1stp++AAAAAElFTkSuQmCC); - background-repeat: no-repeat; - background-position: center; - background-size: 100%; - width: 15px; - /* display: inline-block; */ - top: -2px; - height: 24px; - vertical-align: middle; - transition: transform 0.4s; - transform: rotate(0deg); + display: block; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAXUlEQVR4AWMAgVGwjkGKNA3/GT4wZDAwkqABDA8zaBKtAQp/MjQwsBGnAQGvMVgTpwEB/zFMZ+AjoAEDPmUIpKGGfwzTGPho5OmfDPUMbKREnAYpSSOdgZGSxDcKADdXTK1stp++AAAAAElFTkSuQmCC'); + background-repeat: no-repeat; + background-position: center; + background-size: 100%; + width: 15px; + + /* display: inline-block; */ + top: -2px; + height: 24px; + vertical-align: middle; + transition: transform 0.4s; + transform: rotate(0deg); } .pm .tdstageiconcollapsed { - transform: rotate(90deg); + transform: rotate(90deg); } .pm td.stagetableoffset { - background-color: #e7e7e7; + background-color: #e7e7e7; } .pm th { - font-size: small; - background-color: #eeeeee; - border: 0px; + white-space: nowrap; + font-size: small; + background-color: #eee; + border: 0; } .pm td { - background-color: white; - border: 0; - font-size: small; - border-top: 1px solid rgba(84, 84, 84, 0.08); - padding: 0; + background-color: white; + border: 0; + font-size: small; + border-top: 1px solid rgb(84 84 84 / 8%); + padding: 0; } .pm table { - border-radius: 0px; - width: 100%; - /* border: 1px solid #CFCFCF; */ - /* height: 10px; */ - border-spacing: 0; + border-radius: 0; + width: 100%; + + /* border: 1px solid #CFCFCF; */ + + /* height: 10px; */ + border-spacing: 0; } .pm tbody { - /* height: 100px; */ - overflow: auto; + /* height: 100px; */ + overflow: auto; } .pm tr { - border: 0px; + border: 0; } .pm td, .pm th { - text-align: center; - vertical-align: middle; - height: 25px; - line-height: 25px; -} -.pm th { - white-space: nowrap; + text-align: center; + vertical-align: middle; + height: 25px; + line-height: 25px; } .pm tr .tdstagebutton:hover ~ td, tr .tdstagebutton:hover { - background-color: rgba(184, 223, 255, 0.37); + background-color: rgb(184 223 255 / 37%); } .pm th.thbutton { - width: 4%; + width: 4%; } .pm th.thjobid { - width: 6%; + width: 6%; } .pm th.thjobname { - width: 10%; - white-space: nowrap; + width: 10%; + white-space: nowrap; } .pm th.thjobstatus { - width: 12%; + width: 12%; } .pm th.thjobtages { - width: 10%; + width: 10%; } .pm th.thjobtasks { - width: 28%; + width: 28%; } .pm th.thjobstart { - width: 20%; + width: 20%; } .pm th.thjobtime { - width: 10%; + width: 10%; } .pm th.thstageid { - width: 8%; + width: 8%; } .pm th.thstagename { - width: 10%; - white-space: nowrap; + width: 10%; + white-space: nowrap; } .pm th.thstagestatus { - width: 17%; + width: 17%; } .pm th.thstagetasks { - width: 33%; + width: 33%; } .pm th.thstagestart { - width: 20%; + width: 20%; } .pm th.thstageduration { - width: 12%; + width: 12%; } progress { - padding: 2px; + padding: 2px; } .pm .tdstatus { - padding: 2px; + padding: 2px; } -.pm .RUNNING { - background-color: #42a5f5; +.pm .running { + background-color: #42a5f5; } -.pm .FAILED { - background-color: #db3636; +.pm .failed { + background-color: #db3636; } -.pm .COMPLETED { - background-color: #20b520; +.pm .completed { + background-color: #20b520; } -.pm .PENDING { - background-color: #9c27b0; +.pm .pending { + background-color: #9c27b0; } -.pm .SKIPPED { - background-color: #616161; +.pm .skipped { + background-color: #616161; } -.pm .COMPLETED, -.pm .FAILED, -.pm .RUNNING, -.pm .PENDING, -.pm .SKIPPED { - font-size: 75%; - color: white; - border-radius: 2px; +.pm .completed, +.pm .failed, +.pm .running, +.pm .pending, +.pm .skipped { + font-size: 75%; + color: white; + border-radius: 2px; } + .pm .tdstagestatus > span, .pm .tdjobstatus > span { - padding: 4px 8px; + padding: 4px 8px; } .pm .stagetable tr th { - background: rgb(235, 235, 235); + background: rgb(235 235 235); } .pm .cssprogress { - overflow: hidden; - display: block; - background: #e7e7e7; - width: 100%; - height: 20px; - padding: 0; - overflow: hidden; - text-align: left; - position: relative; - font-size: 75%; - border-radius: 2px; - color: white; + display: block; + background: #e7e7e7; + width: 100%; + height: 20px; + padding: 0; + overflow: hidden; + text-align: left; + position: relative; + font-size: 75%; + border-radius: 2px; + color: white; } .pm .cssprogress .val1 { - width: 0%; - background: #20b520; + width: 0%; + background: #20b520; } .pm .cssprogress .data { - text-align: center; - width: 100%; - position: absolute; - z-index: 4; - line-height: 20px; + text-align: center; + width: 100%; + position: absolute; + z-index: 4; + line-height: 20px; } .pm .cssprogress .val2 { - background: #42a5f5; - width: 0%; + background: #42a5f5; + width: 0%; } .pm .cssprogress span { - position: relative; - height: 100%; - display: inline-block; - position: relative; - overflow: hidden; - margin: 0; - padding: 0; - transition: width 1s; + height: 100%; + display: inline-block; + position: relative; + overflow: hidden; + margin: 0; + padding: 0; + transition: width 1s; } diff --git a/style/lab.css b/style/lab.css index fffa721..e36a466 100644 --- a/style/lab.css +++ b/style/lab.css @@ -5,6 +5,8 @@ @import url('./taskdetails.css'); @import url('./timeline.css'); -.SparkMonitorCellWidget { - margin-left: calc(var(--jp-cell-collapser-width) + var(--jp-cell-prompt-width)); +.spark-monitor-cell-widget { + margin-left: calc( + var(--jp-cell-collapser-width) + var(--jp-cell-prompt-width) + ); } diff --git a/style/lab.js b/style/lab.js index b20b5c5..57d6ea5 100644 --- a/style/lab.js +++ b/style/lab.js @@ -1 +1 @@ -import './lab.css'; \ No newline at end of file +import './lab.css'; diff --git a/style/notebook.css b/style/notebook.css index 3f5ce0a..1d05c8c 100644 --- a/style/notebook.css +++ b/style/notebook.css @@ -4,5 +4,5 @@ @import url('./timeline.css'); .sparkMonitorCellRoot { - margin-left: 13ex; + margin-left: 13ex; } diff --git a/style/styles.css b/style/styles.css index 1fc150c..8a69ad2 100644 --- a/style/styles.css +++ b/style/styles.css @@ -1,257 +1,257 @@ .pm { - background-color: #f7f7f7; - margin: 8px 0px; + background-color: #f7f7f7; + margin: 8px 0; } -/*--------------Title Bar--------------------*/ +/* --------------Title Bar-------------------- */ .pm .title { - background-color: #f37600; - display: flex; - flex-flow: row wrap; - justify-content: flex-end; + background-color: #f37600; + display: flex; + flex-flow: row wrap; + justify-content: flex-end; } .pm .titleright { - display: flex; - align-items: stretch; - height: 30px; + display: flex; + align-items: stretch; + height: 30px; } .pm .titleleft { - flex-grow: 1; - height: 30px; + flex-grow: 1; + height: 30px; } .pm .tbitem { - padding: 0px 15px; - height: 30px; - text-align: center; - vertical-align: middle; - display: table-cell; + padding: 0 15px; + height: 30px; + text-align: center; + vertical-align: middle; + display: table-cell; } .pm .badgecontainer { - padding: 0px 5px; + padding: 0 5px; } .pm .titlecollapse:hover { - background-color: #c45917; + background-color: #c45917; } .headericon { - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAXUlEQVR4AWMAgVGwjkGKNA3/GT4wZDAwkqABDA8zaBKtAQp/MjQwsBGnAQGvMVgTpwEB/zFMZ+AjoAEDPmUIpKGGfwzTGPho5OmfDPUMbKREnAYpSSOdgZGSxDcKADdXTK1stp++AAAAAElFTkSuQmCC); - background-repeat: no-repeat; - background-position: center; - background-size: 100%; - width: 15px; - display: inline-block; - top: -2px; - height: 24px; - vertical-align: middle; - transition: transform 0.4s; - transform: rotate(90deg); + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAXUlEQVR4AWMAgVGwjkGKNA3/GT4wZDAwkqABDA8zaBKtAQp/MjQwsBGnAQGvMVgTpwEB/zFMZ+AjoAEDPmUIpKGGfwzTGPho5OmfDPUMbKREnAYpSSOdgZGSxDcKADdXTK1stp++AAAAAElFTkSuQmCC'); + background-repeat: no-repeat; + background-position: center; + background-size: 100%; + width: 15px; + display: inline-block; + top: -2px; + height: 24px; + vertical-align: middle; + transition: transform 0.4s; + transform: rotate(90deg); } .headericoncollapsed { - transform: rotate(0deg); + transform: rotate(0deg); } .pm .badges { - text-align: center; + text-align: center; } .badgerunning { - background-color: #42a5f5; + background-color: #42a5f5; } .badgecompleted { - background-color: #20b520; + background-color: #20b520; } .badgefailed { - background-color: #db3636; + background-color: #db3636; } .badgecompleted, .badgefailed, .badgerunning { - font-size: 80%; - padding: 5px 8px; - color: white; - border-radius: 2px; - box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.36); - white-space: nowrap; - font-weight: bold; - margin: 0 2px; + font-size: 80%; + padding: 5px 8px; + color: white; + border-radius: 2px; + box-shadow: 0 0 1px 0 rgb(0 0 0 / 36%); + white-space: nowrap; + font-weight: bold; + margin: 0 2px; } .badgeexecutor, .badgeexecutorcores { - font-size: 80%; - padding: 5px 8px; - color: white; - border-radius: 2px; - box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.36); - white-space: nowrap; - font-weight: bold; - margin: 0px 2px; - background-color: rgba(117, 57, 0, 0.65); + font-size: 80%; + padding: 5px 8px; + color: white; + border-radius: 2px; + box-shadow: 0 0 1px 0 rgb(0 0 0 / 36%); + white-space: nowrap; + font-weight: bold; + margin: 0 2px; + background-color: rgb(117 57 0 / 65%); } .uibuttondisabled { - color: #b2b2b2; + color: #b2b2b2; } .pm .cancel:active { - font: larger; + font-size: larger; } .pm .tabbutton { - display: inline-block; - width: 30px; - background-repeat: no-repeat; - background-position: center; - background-size: auto; - cursor: pointer; - cursor: hand; + display: inline-block; + width: 30px; + background-repeat: no-repeat; + background-position: center; + background-size: auto; + cursor: pointer; + cursor: hand; } .pm .timelinetabbuttonicon { - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAALklEQVR4AWMYRuA/YTjYNdA/lLCJka6BvgDF4gY4uwFZhnQN9PcDIZp0DcMGAADGCJlpwLGAQAAAAABJRU5ErkJggg==); + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAALklEQVR4AWMYRuA/YTjYNdA/lLCJka6BvgDF4gY4uwFZhnQN9PcDIZp0DcMGAADGCJlpwLGAQAAAAABJRU5ErkJggg=='); } .pm .sparkuitabbuttonicon { - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAASklEQVR4AWMYnCCQ4SXDfyLgKwZ/sHq4csLwJVg9mIkfwNUNPg3/Bp+Gv4NPwx96aXhFdOJ7AtHgT2R6fcHgg9ViNIgGCGsY/AAAawWT4i3LWOwAAAAASUVORK5CYII=); + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAASklEQVR4AWMYnCCQ4SXDfyLgKwZ/sHq4csLwJVg9mIkfwNUNPg3/Bp+Gv4NPwx96aXhFdOJ7AtHgT2R6fcHgg9ViNIgGCGsY/AAAawWT4i3LWOwAAAAASUVORK5CYII='); } .pm .taskviewtabbuttonicon { - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAQ0lEQVR4Ac3SsQ2AQAzF0LcgOrH/AuSWCHUa0pxQ7Pa7++ZyC/nhthRCNoZC9h4OMCXA9ACDg7+/tNv5o7BEM79M5QVZbBWGPizgRwAAAABJRU5ErkJggg==); + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAQ0lEQVR4Ac3SsQ2AQAzF0LcgOrH/AuSWCHUa0pxQ7Pa7++ZyC/nhthRCNoZC9h4OMCXA9ACDg7+/tNv5o7BEM79M5QVZbBWGPizgRwAAAABJRU5ErkJggg=='); } .pm .jobtabletabbuttonicon { - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAG0lEQVR4AWMY2eA/BMJYmHAQahj19Kinhz0AAOazv0F7pjjzAAAAAElFTkSuQmCC); + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAG0lEQVR4AWMY2eA/BMJYmHAQahj19Kinhz0AAOazv0F7pjjzAAAAAElFTkSuQmCC'); } .pm .stopbuttonicon { - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAr0lEQVR4Ab2TxxWDQBBDv2M3bmI7cJquoAsoiJNzAXvdAjBRzjl8XeaNJDL8nSFGyopQaFVMVmzuMGVLfqZtsb1Kl5j8hmK6XFDE7yi6vJj8gaant7pr1hkOr5DHkeleBghTxAGjpuKLCZw8Q6RaFqGmokleglhpqQqKS0tE0FIVxSXCB4UnL+nlm/7gsQ7ZPvPiXvs0Jh9+fNAluhPvfvoDiQFGwpJQaFlMVmz+zB4uewsoywq4xgAAAABJRU5ErkJggg==); + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAr0lEQVR4Ab2TxxWDQBBDv2M3bmI7cJquoAsoiJNzAXvdAjBRzjl8XeaNJDL8nSFGyopQaFVMVmzuMGVLfqZtsb1Kl5j8hmK6XFDE7yi6vJj8gaant7pr1hkOr5DHkeleBghTxAGjpuKLCZw8Q6RaFqGmokleglhpqQqKS0tE0FIVxSXCB4UnL+nlm/7gsQ7ZPvPiXvs0Jh9+fNAluhPvfvoDiQFGwpJQaFlMVmz+zB4uewsoywq4xgAAAABJRU5ErkJggg=='); } .pm .closebuttonicon { - background-size: 20px; - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAa0lEQVR4AeWSsREDIQwEtwlRhO3vP0JFPLgeHJDdnEfBh2y8F2hHnM5FJ1AayRtLshiE6F8WHUsw9kT0m8BDMFlMotZ10rzuaHtS63qo6s8HWkaLFXpo5ErXyKWukS25dRM5sXz+Pt+Ls/kBnolC6l7shJkAAAAASUVORK5CYII=); + background-size: 20px; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAa0lEQVR4AeWSsREDIQwEtwlRhO3vP0JFPLgeHJDdnEfBh2y8F2hHnM5FJ1AayRtLshiE6F8WHUsw9kT0m8BDMFlMotZ10rzuaHtS63qo6s8HWkaLFXpo5ErXyKWukS25dRM5sXz+Pt+Ls/kBnolC6l7shJkAAAAASUVORK5CYII='); } .pm .closebutton { - padding: 5px 5px; - border-left: 1px solid #c25e00; + padding: 5px; + border-left: 1px solid #c25e00; } .pm .tabbuttons { - display: flex; - align-items: stretch; + display: flex; + align-items: stretch; } .pm .tabbutton:hover { - background-color: #c45917; + background-color: #c45917; } .pm .cancelbutton:hover { - background-color: #c45917; + background-color: #c45917; } .pm .tabbuttonactive { - /*color: white;*/ - box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.46) inset; - background-color: rgba(0, 0, 0, 0.2); - font-weight: bold; - border: 0; + /* color: white; */ + box-shadow: 0 0 1px 0 rgb(0 0 0 / 46%) inset; + background-color: rgb(0 0 0 / 20%); + font-weight: bold; + border: 0; } -/*--------------Content--------------------*/ +/* --------------Content-------------------- */ .pm .tabcontent { - border: 1px solid #eeeeee; + border: 1px solid #eee; } .sparkuiframe { - width: 100%; - height: 100%; - box-sizing: border-box; - overflow: hidden; - background-repeat: no-repeat; - background-position: center center; + width: 100%; + height: 100%; + box-sizing: border-box; + overflow: hidden; + background-repeat: no-repeat; + background-position: center center; } -/*-------------Tooltip Popup---------------*/ +/* -------------Tooltip Popup--------------- */ .ui-tooltip { - background: #666; - color: white; - border: none; - padding: 0; - opacity: 1; + background: #666; + color: white; + border: none; + padding: 0; + opacity: 1; } .ui-tooltip-content { - position: relative; - padding: 5px; + position: relative; + padding: 5px; } .ui-tooltip-content::after { - content: ''; - position: absolute; - border-style: solid; - display: block; - width: 0; + content: ''; + position: absolute; + border-style: solid; + display: block; + width: 0; } .tpright .ui-tooltip-content::after { - top: 5px; - left: -10px; - border-color: transparent #666; - border-width: 10px 10px 10px 0; + top: 5px; + left: -10px; + border-color: transparent #666; + border-width: 10px 10px 10px 0; } .tpleft .ui-tooltip-content::after { - top: 5px; - right: -10px; - border-color: transparent #666; - border-width: 10px 0 10px 10px; + top: 5px; + right: -10px; + border-color: transparent #666; + border-width: 10px 0 10px 10px; } .tptop .ui-tooltip-content::after { - bottom: -10px; - left: 72px; - border-color: #666 transparent; - border-width: 10px 10px 0; + bottom: -10px; + left: 72px; + border-color: #666 transparent; + border-width: 10px 10px 0; } .tpbottom .ui-tooltip-content::after { - top: -10px; - left: 72px; - border-color: #666 transparent; - border-width: 0 10px 10px; + top: -10px; + left: 72px; + border-color: #666 transparent; + border-width: 0 10px 10px; } .sparkui-dialog { - padding: 0; - box-shadow: 0px 0px 6px 0px rgba(128, 128, 128, 0.83); + padding: 0; + box-shadow: 0 0 6px 0 rgb(128 128 128 / 83%); } .sparkui-dialog .ui-corner-all { - border-radius: 0px; + border-radius: 0; } .sparkui-dialog .ui-dialog-content.ui-widget-content { - padding: 0; + padding: 0; } .sparkui-dialog .ui-widget-header { - border: 1px solid rgb(243, 118, 0); - background: #f37600; - color: #000; - font-weight: bold; - text-align: center; + border: 1px solid rgb(243 118 0); + background: #f37600; + color: #000; + font-weight: bold; + text-align: center; } .pm .taskcontainer { - user-select: none; + user-select: none; } diff --git a/style/taskdetails.css b/style/taskdetails.css index 071fd03..9d41f90 100644 --- a/style/taskdetails.css +++ b/style/taskdetails.css @@ -1,161 +1,155 @@ .taskdetails { - font-family: sans-serif; - margin: 10px; -} - -.taskdetails .success { - display: none; + font-family: sans-serif; + margin: 10px; } .taskdetails .error { - display: none; + display: none; } .taskdetails .finish { - display: none; + display: none; } .taskdetails .metricdata { - display: none; + display: none; } .taskdetails rect.scheduler-delay-proportion { - fill: #80b1d3; - stroke: #6b94b0; + fill: #80b1d3; + stroke: #6b94b0; } .taskdetails rect.deserialization-time-proportion { - fill: #fb8072; - stroke: #d26b5f; + fill: #fb8072; + stroke: #d26b5f; } .taskdetails rect.shuffle-read-time-proportion { - fill: #fdb462; - stroke: #d39651; + fill: #fdb462; + stroke: #d39651; } .taskdetails rect.executor-runtime-proportion { - fill: #b3de69; - stroke: #95b957; + fill: #b3de69; + stroke: #95b957; } .taskdetails rect.shuffle-write-time-proportion { - fill: #ffed6f; - stroke: #d5c65c; + fill: #ffed6f; + stroke: #d5c65c; } .taskdetails rect.serialization-time-proportion { - fill: #bc80bd; - stroke: #9d6b9e; + fill: #bc80bd; + stroke: #9d6b9e; } .taskdetails rect.getting-result-time-proportion { - fill: #8dd3c7; - stroke: #75b0a6; -} - -.taskdetails .taskbarsvg { - height: 40px; - width: 100%; + fill: #8dd3c7; + stroke: #75b0a6; } .taskdetails .taskbarsvg { - box-shadow: 0 0 11px 0px rgba(119, 119, 119, 0.36); + height: 40px; + width: 100%; + box-shadow: 0 0 11px 0 rgb(119 119 119 / 36%); } .legend-area { - font-size: small; - padding: 10px; - margin-top: 5px; + font-size: small; + padding: 10px; + margin-top: 5px; } .task-dialog { - padding: 0; - box-shadow: 0px 0px 6px 0px rgba(128, 128, 128, 0.83); + padding: 0; + box-shadow: 0 0 6px 0 rgb(128 128 128 / 83%); } .task-dialog .ui-corner-all { - border-radius: 0px; + border-radius: 0; } .task-dialog .ui-dialog-content.ui-widget-content { - padding: 0; + padding: 0; } .task-dialog .ui-widget-header { - border: 1px solid rgb(243, 118, 0); - background: #f37600; - color: #000; - font-weight: bold; - text-align: center; + border: 1px solid rgb(243 118 0); + background: #f37600; + color: #000; + font-weight: bold; + text-align: center; } .taskdetails td { - text-align: center; - border: 1px solid #e4e4e4; - padding: 5px 20px; + text-align: center; + border: 1px solid #e4e4e4; + padding: 5px 20px; } .taskdetails th { - text-align: center; - border: 1px solid #e4e4e4; - padding: 5px 20px; + text-align: center; + border: 1px solid #e4e4e4; + padding: 5px 20px; } .taskdetails .tasktitle { - font-size: medium; - text-align: center; + font-size: medium; + text-align: center; } .taskdetails .tasktitlestage { - padding: 10px; - font-size: small; + padding: 10px; + font-size: small; } .taskdetails table { - width: 100%; + width: 100%; } -.taskdetails .RUNNING { - background-color: #42a5f5; +.taskdetails .running { + background-color: #42a5f5; } -.taskdetails .FAILED { - background-color: #db3636; +.taskdetails .failed { + background-color: #db3636; } -.taskdetails .KILLED { - background-color: #db3636; +.taskdetails .killed { + background-color: #db3636; } -.taskdetails .COMPLETED { - background-color: #20b520; +.taskdetails .completed { + background-color: #20b520; } -.taskdetails .SUCCESS { - background-color: #20b520; +.taskdetails .success { + display: none; + background-color: #20b520; } -.taskdetails .UNKNOWN { - background-color: #9c27b0; +.taskdetails .unknown { + background-color: #9c27b0; } -.taskdetails .COMPLETED, -.taskdetails .FAILED, -.taskdetails .KILLED, -.taskdetails .RUNNING, -.taskdetails .UNKNOWN, -.taskdetails .SUCCESS { - font-size: 75%; - padding: 4px 8px; - color: white; - border-radius: 2px; +.taskdetails .completed, +.taskdetails .failed, +.taskdetails .killed, +.taskdetails .running, +.taskdetails .unknown, +.taskdetails .success { + font-size: 75%; + padding: 4px 8px; + color: white; + border-radius: 2px; } .taskdetails .legend-area table td:nth-of-type(1) { - width: 20px; + width: 20px; } .taskdetails .legend-area svg { - height: 15px; + height: 15px; } diff --git a/style/timeline.css b/style/timeline.css index 910fcdf..7254219 100644 --- a/style/timeline.css +++ b/style/timeline.css @@ -1,141 +1,141 @@ .pm .timelinewrapper { - max-height: 400px; - overflow-y: auto; - clear: both; + max-height: 400px; + overflow-y: auto; + clear: both; } .pm .vis-labelset .vis-label .vis-inner { - width: 100px; + width: 100px; } .pm .vis-item { - border-radius: 0; - font-size: smaller; + border-radius: 0; + font-size: smaller; } .pm .vis-item .vis-item-content { - padding: 0; - width: 100%; + padding: 0; + width: 100%; } .pm .vis-time-axis { - font-size: smaller; + font-size: smaller; } .pm .vis-tooltip { - color: white; - background-color: black; + color: white; + background-color: black; } .pm .vis-item.vis-background { - background-color: rgba(191, 191, 191, 0.58); + background-color: rgb(191 191 191 / 58%); } .pm .vis-custom-time { - pointer-events: none; - background-color: #42a5f5; + pointer-events: none; + background-color: #42a5f5; } .pm .vis-item.itemfinished { - /* :not(.vis-selected) { */ - background-color: #90dc34; - border-color: #6c9d34; - border: 0; + /* :not(.vis-selected) { */ + background-color: #90dc34; + border-color: #6c9d34; + border: 0; } .pm .vis-item.itemfailed { - /* :not(.vis-selected) { */ - background-color: #f44336; - border-color: rgb(183, 13, 0); - color: white; - border: 0; + /* :not(.vis-selected) { */ + background-color: #f44336; + border-color: rgb(183 13 0); + color: white; + border: 0; } .pm .vis-item.itemfinished:active { - background-color: #6c9d34; + background-color: #6c9d34; } .pm .vis-selected { - border-color: #ffc200; - background-color: #ffc200; - border: 0; + border-color: #ffc200; + background-color: #ffc200; + border: 0; } .pm .vis-item.itemrunning { - /* :not(.vis-selected) { */ - background-color: rgb(134, 199, 251); - border: 0; + /* :not(.vis-selected) { */ + background-color: rgb(134 199 251); + border: 0; +} + +.pm .taskbarsvg { + width: 100%; + height: 20px; + vertical-align: top; } .pm .hidephases .taskbarsvg rect { - stroke: none; - fill: none; + stroke: none; + fill: none; } .pm .hidephases .taskbarsvg { - display: none; + display: none; } -.pm .hidephases .taskbardiv:before { - content: attr(data-taskid); - padding: 0px 2px; - text-align: center; - width: 100%; +.pm .hidephases .taskbardiv::before { + content: attr(data-taskid); + padding: 0 2px; + text-align: center; + width: 100%; } .pm .showphases .taskbarsvg rect.scheduler-delay-proportion { - fill: #80b1d3; - stroke: #6b94b0; + fill: #80b1d3; + stroke: #6b94b0; } .pm .showphases .taskbarsvg rect.deserialization-time-proportion { - fill: #fb8072; - stroke: #d26b5f; + fill: #fb8072; + stroke: #d26b5f; } .pm .showphases .taskbarsvg rect.shuffle-read-time-proportion { - fill: #fdb462; - stroke: #d39651; + fill: #fdb462; + stroke: #d39651; } .pm .showphases .taskbarsvg rect.executor-runtime-proportion { - fill: #b3de69; - stroke: #95b957; + fill: #b3de69; + stroke: #95b957; } .pm .showphases .taskbarsvg rect.shuffle-write-time-proportion { - fill: #ffed6f; - stroke: #d5c65c; + fill: #ffed6f; + stroke: #d5c65c; } .pm .showphases .taskbarsvg rect.serialization-time-proportion { - fill: #bc80bd; - stroke: #9d6b9e; + fill: #bc80bd; + stroke: #9d6b9e; } .pm .showphases .taskbarsvg rect.getting-result-time-proportion { - fill: #8dd3c7; - stroke: #75b0a6; -} - -.pm .taskbarsvg { - width: 100%; - height: 20px; - vertical-align: top; + fill: #8dd3c7; + stroke: #75b0a6; } -.pm taskbardiv { - width: 100%; - height: 100%; +.pm .taskbardiv { + width: 100%; + height: 100%; } .pm .timelinetoolbar { - padding: 0px 8px; - color: #444444; - font-size: small; + padding: 0 8px; + color: #444; + font-size: small; } .pm .timecheckboxspan { - margin: 0px 5px; - float: right; + margin: 0 5px; + float: right; } diff --git a/tsconfig.lab.json b/tsconfig.lab.json index ea669de..a5dd5fc 100644 --- a/tsconfig.lab.json +++ b/tsconfig.lab.json @@ -1,8 +1,8 @@ { - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "lib", - "noEmit": false - }, - "exclude": ["src/notebook-extension"] + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "lib", + "noEmit": false + }, + "exclude": ["src/notebook-extension"] } diff --git a/tsconfig.notebook.json b/tsconfig.notebook.json index 9f45a7b..5c582ad 100644 --- a/tsconfig.notebook.json +++ b/tsconfig.notebook.json @@ -1,4 +1,4 @@ { - "extends": "./tsconfig.json", - "exclude": ["src/lab-extension"] + "extends": "./tsconfig.json", + "exclude": ["src/lab-extension"] }