diff --git a/.flowconfig b/.flowconfig index 7a9f3f5c2f..fa03d719ed 100644 --- a/.flowconfig +++ b/.flowconfig @@ -3,6 +3,7 @@ .*/node_modules/redux-form/.* .*/node_modules/react-motion/.* .*/node_modules/draft-js/.* +.*/node_modules/nwb/.* [include] @@ -12,4 +13,4 @@ [options] [version] -0.64.0 +0.71.0 diff --git a/package.json b/package.json index a42777822c..c9974228c9 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "private": true, + "license": "Apache-2.0", "devDependencies": { "babel-eslint": "^7.2.3", "eslint": "^4.5.0", @@ -10,6 +11,7 @@ "eslint-plugin-import": "^2.7.0", "eslint-plugin-jsx-a11y": "^6.0.2", "eslint-plugin-react": "^7.2.1", + "flow-bin": "^0.71.0", "glow": "^1.2.2", "husky": "^0.14.3", "lerna": "^2.10.2", @@ -26,7 +28,7 @@ "lint": "npm run eslint && npm run prettier && npm run flow && npm run check-license", "precommit": "lint-staged", "prettier": - "prettier --write '{.,scripts}/*.{js,json,md}' 'packages/*/src/**/*.{css,js,json,md}' 'packages/*/*.{css,js,json,md}'", + "prettier --write '{.,scripts}/*.{js,json,md}' 'packages/*/{src,demo/src}/**/*.{css,js,json,md}' 'packages/*/*.{css,js,json,md}'", "test": "lerna run test" }, "prettier": { diff --git a/packages/jaeger-ui/package.json b/packages/jaeger-ui/package.json index 4ed3dcc012..cfedcf24b2 100644 --- a/packages/jaeger-ui/package.json +++ b/packages/jaeger-ui/package.json @@ -1,8 +1,9 @@ { + "private": true, "name": "jaeger-ui", "version": "0.0.1", "main": "src/index.js", - "license": "MIT", + "license": "Apache-2.0", "proxy": { "/api": { "target": "http://localhost:16686", @@ -37,7 +38,6 @@ "d3-scale": "^1.0.6", "dagre": "^0.7.4", "deep-freeze": "^0.0.1", - "flow-bin": "^0.64.0", "fuzzy": "^0.1.3", "global": "^4.3.2", "history": "^4.6.3", @@ -51,9 +51,9 @@ "prop-types": "^15.5.10", "query-string": "^5.0.0", "raven-js": "^3.22.1", - "react": "^16.3.0", + "react": "^16.3.2", "react-dimensions": "^1.3.0", - "react-dom": "^16.3.0", + "react-dom": "^16.3.2", "react-ga": "^2.4.1", "react-helmet": "^5.1.3", "react-icons": "^2.2.7", diff --git a/packages/jaeger-ui/src/components/App/TopNav.js b/packages/jaeger-ui/src/components/App/TopNav.js index 146d105735..5cd102697c 100644 --- a/packages/jaeger-ui/src/components/App/TopNav.js +++ b/packages/jaeger-ui/src/components/App/TopNav.js @@ -72,14 +72,16 @@ export default function TopNav(props: TopNavProps) { return (
- {menuItems.map(item => { - if (item.items) { + {menuItems.map(m => { + if (m.items != null) { + const group = ((m: any): ConfigMenuGroup); return ( - - + + ); } + const item = ((m: any): ConfigMenuItem); return ( diff --git a/packages/plexus/.gitignore b/packages/plexus/.gitignore new file mode 100644 index 0000000000..45a3870978 --- /dev/null +++ b/packages/plexus/.gitignore @@ -0,0 +1,4 @@ +demo/dist +es +lib +umd diff --git a/packages/plexus/README.md b/packages/plexus/README.md new file mode 100644 index 0000000000..16824a28ad --- /dev/null +++ b/packages/plexus/README.md @@ -0,0 +1,13 @@ +# plexus + +plexus is a React component for rendering directed graphs. + +## Why? + +To support directed graphs in Jaeger-UI, we surveyed the JavaScript libraries and utilities available for rendering directed graphs. The landscape is impressive, but we concluded the venerable [GraphViz](https://graphviz.gitlab.io/) ([alt](https://www.graphviz.org/)) is the right tool for us. + +GraphViz is awesome, but the output formats don't fit our needs. We've elected to use GraphViz to generate the layouts (node positioning, edge routing) and React for rendering. + +## viz.js + +The excellent [viz.js](https://github.com/mdaines/viz.js) is used, in a WebWorker, to generate GraphViz as plain-text output which is then parsed and provided to a React component which does the rendering. diff --git a/packages/plexus/demo/src/data-dag.js b/packages/plexus/demo/src/data-dag.js new file mode 100644 index 0000000000..4b9fb82ed5 --- /dev/null +++ b/packages/plexus/demo/src/data-dag.js @@ -0,0 +1,98 @@ +const paths = ` +packages +packages/jaeger-ui +packages/jaeger-ui/build +packages/jaeger-ui/build/static +packages/jaeger-ui/build/static/css +packages/jaeger-ui/build/static/js +packages/jaeger-ui/build/static/media +packages/jaeger-ui/node_modules +packages/jaeger-ui/node_modules/.bin +packages/jaeger-ui/node_modules/bluebird +packages/jaeger-ui/node_modules/bluebird/js +packages/jaeger-ui/node_modules/bluebird/js/browser +packages/jaeger-ui/node_modules/bluebird/js/release +packages/jaeger-ui/node_modules/moment +packages/jaeger-ui/node_modules/moment/locale +packages/jaeger-ui/node_modules/moment/min +packages/jaeger-ui/node_modules/moment/src +packages/jaeger-ui/node_modules/moment/src/lib +packages/jaeger-ui/node_modules/moment/src/lib/create +packages/jaeger-ui/node_modules/moment/src/lib/duration +packages/jaeger-ui/node_modules/moment/src/lib/format +packages/jaeger-ui/node_modules/moment/src/lib/locale +packages/jaeger-ui/node_modules/moment/src/lib/moment +packages/jaeger-ui/node_modules/moment/src/lib/parse +packages/jaeger-ui/node_modules/moment/src/lib/units +packages/jaeger-ui/node_modules/moment/src/lib/utils +packages/jaeger-ui/node_modules/moment/src/locale +packages/jaeger-ui/public +packages/jaeger-ui/src +packages/jaeger-ui/src/actions +packages/jaeger-ui/src/api +packages/jaeger-ui/src/components +packages/jaeger-ui/src/components/App +packages/jaeger-ui/src/components/DependencyGraph +packages/jaeger-ui/src/components/SearchTracePage +packages/jaeger-ui/src/components/SearchTracePage/SearchResults +packages/jaeger-ui/src/components/TracePage +packages/jaeger-ui/src/components/TracePage/ArchiveNotifier +packages/jaeger-ui/src/components/TracePage/SpanGraph +packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer +packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/ListView +packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/ListView/__snapshots__ +packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail +packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow +packages/jaeger-ui/src/components/common +packages/jaeger-ui/src/constants +packages/jaeger-ui/src/demo +packages/jaeger-ui/src/img +packages/jaeger-ui/src/middlewares +packages/jaeger-ui/src/model +packages/jaeger-ui/src/propTypes +packages/jaeger-ui/src/reducers +packages/jaeger-ui/src/selectors +packages/jaeger-ui/src/types +packages/jaeger-ui/src/utils +packages/jaeger-ui/src/utils/DraggableManager +packages/jaeger-ui/src/utils/DraggableManager/demo +packages/jaeger-ui/src/utils/config +packages/jaeger-ui/src/utils/test +packages/jaeger-ui/src/utils/tracking +packages/plexus +packages/plexus/demo +packages/plexus/demo/dist +packages/plexus/demo/src +packages/plexus/lib +packages/plexus/lib/DirectedGraph +packages/plexus/lib/DirectedGraph/builtins +packages/plexus/lib/LayoutManager +packages/plexus/lib/LayoutManager/dot +packages/plexus/lib/types +packages/plexus/node_modules +packages/plexus/node_modules/.bin +packages/plexus/node_modules/.cache +packages/plexus/node_modules/.cache/babel-loader +packages/plexus/src +packages/plexus/src/DirectedGraph +packages/plexus/src/DirectedGraph/builtins +packages/plexus/src/LayoutManager +packages/plexus/src/LayoutManager/dot +packages/plexus/src/types +packages/plexus/umd +packages/plexus/umd/@jaegertracing +`; + +export const vertices = []; +export const edges = []; + +paths + .trim() + .split('\n') + .forEach(line => { + const folders = line.split('/'); + vertices.push({ key: line, label: folders.slice(-1)[0] }); + if (folders.length > 1) { + edges.push({ from: folders.slice(0, -1).join('/'), to: line }); + } + }); diff --git a/packages/plexus/demo/src/data-large.js b/packages/plexus/demo/src/data-large.js new file mode 100644 index 0000000000..d2fa1ae7a2 --- /dev/null +++ b/packages/plexus/demo/src/data-large.js @@ -0,0 +1,498 @@ +import * as React from 'react'; + +export default { + edges: [ + { + from: 'fervent::ferve/fervent', + to: 'carson::carso/carson', + }, + { + from: 'fervent::ferve/fervent', + to: 'volhard::volha/volhard', + }, + { + from: 'fervent::ferve/fervent', + to: 'yalow::yalow/yalow', + }, + { + from: 'fervent::ferve/fervent', + to: 'mystifying::mysti/mystifying', + }, + { + from: 'fervent::ferve/fervent', + to: 'meninsky::menin/meninsky', + }, + { + from: 'fervent::ferve/fervent', + to: 'admiring::admir/admiring', + }, + { + from: 'fervent::ferve/fervent', + to: 'fervent::ferve/fervent', + }, + { + from: 'fervent::ferve/fervent', + to: 'hoover::hoove/hoover', + }, + { + from: 'fervent::ferve/fervent', + to: 'brahmagupta::brahm/brahmagupta', + }, + { + from: 'fervent::ferve/fervent', + to: 'banach::banac/banach', + }, + { + from: 'fervent::ferve/fervent', + to: 'chatterjee::chatt/chatterjee', + }, + { + from: 'fervent::ferve/fervent', + to: 'unruffled::unruf/unruffled', + }, + { + from: 'fervent::ferve/fervent', + to: 'shockley::shock/shockley', + }, + { + from: 'fervent::ferve/fervent', + to: 'pasteur::paste/pasteur', + }, + { + from: 'fervent::ferve/fervent', + to: 'upbeat::upbea/upbeat', + }, + { + from: 'fervent::ferve/fervent', + to: 'darwin::darwi/darwin', + }, + { + from: 'fervent::ferve/fervent', + to: 'priceless::price/priceless', + }, + { + from: 'fervent::ferve/fervent', + to: 'sammet::samme/sammet', + }, + { + from: 'fervent::ferve/fervent', + to: 'serene::seren/serene', + }, + { + from: 'fervent::ferve/fervent', + to: 'vigorous::vigor/vigorous', + }, + { + from: 'fervent::ferve/fervent', + to: 'gracious::graci/gracious', + }, + { + from: 'fervent::ferve/fervent', + to: 'hardcore::hardc/hardcore', + }, + { + from: 'fervent::ferve/fervent', + to: 'elastic::elast/elastic', + }, + { + from: 'fervent::ferve/fervent', + to: 'dazzling::dazzl/dazzling', + }, + { + from: 'fervent::ferve/fervent', + to: 'neumann::neuma/neumann', + }, + { + from: 'keller::kelle/keller', + to: 'fervent::ferve/fervent', + }, + { + from: 'keller::kelle/keller', + to: 'shockley::shock/shockley', + }, + { + from: 'carson::carso/carson', + to: 'heuristic::heuri/heuristic', + }, + { + from: 'murdock::murdo/murdock', + to: 'fervent::ferve/fervent', + }, + { + from: 'mystifying::mysti/mystifying', + to: 'eloquent::eloqu/eloquent', + }, + { + from: 'mystifying::mysti/mystifying', + to: 'peaceful::peace/peaceful', + }, + { + from: 'shockley::shock/shockley', + to: 'fervent::ferve/fervent', + }, + { + from: 'shockley::shock/shockley', + to: 'volhard::volha/volhard', + }, + { + from: 'shockley::shock/shockley', + to: 'brahmagupta::brahm/brahmagupta', + }, + { + from: 'shockley::shock/shockley', + to: 'mystifying::mysti/mystifying', + }, + { + from: 'shockley::shock/shockley', + to: 'darwin::darwi/darwin', + }, + { + from: 'shockley::shock/shockley', + to: 'upbeat::upbea/upbeat', + }, + { + from: 'brahmagupta::brahm/brahmagupta', + to: 'infallible::infal/infallible', + }, + { + from: 'brahmagupta::brahm/brahmagupta', + to: 'heuristic::heuri/heuristic', + }, + { + from: 'brahmagupta::brahm/brahmagupta', + to: 'easley::easle/easley', + }, + { + from: 'infallible::infal/infallible', + to: 'sharp::sharp/sharp', + }, + { + from: 'banach::banac/banach', + to: 'lalande::lalan/lalande', + }, + { + from: 'banach::banac/banach', + to: 'brave::brave/brave', + }, + { + from: 'banach::banac/banach', + to: 'wright::wrigh/wright', + }, + { + from: 'banach::banac/banach', + to: 'jones::jones/jones', + }, + { + from: 'banach::banac/banach', + to: 'bose::bose/bose', + }, + { + from: 'banach::banac/banach', + to: 'ecstatic::ecsta/ecstatic', + }, + { + from: 'banach::banac/banach', + to: 'volhard::volha/volhard', + }, + { + from: 'banach::banac/banach', + to: 'tereshkova::teres/tereshkova', + }, + { + from: 'banach::banac/banach', + to: 'lichterman::licht/lichterman', + }, + { + from: 'chatterjee::chatt/chatterjee', + to: 'heuristic::heuri/heuristic', + }, + { + from: 'sharp::sharp/sharp', + to: 'sharp::sharp/sharp', + }, + { + from: 'lalande::lalan/lalande', + to: 'visvesvaraya::visve/visvesvaraya', + }, + { + from: 'upbeat::upbea/upbeat', + to: 'tereshkova::teres/tereshkova', + }, + { + from: 'upbeat::upbea/upbeat', + to: 'volhard::volha/volhard', + }, + { + from: 'priceless::price/priceless', + to: 'priceless::price/priceless', + }, + { + from: 'sammet::samme/sammet', + to: 'mclean::mclea/mclean', + }, + { + from: 'mclean::mclea/mclean', + to: 'stupefied::stupe/stupefied', + }, + { + from: 'wright::wrigh/wright', + to: 'bhabha::bhabh/bhabha', + }, + { + from: 'jones::jones/jones', + to: 'youthful::youth/youthful', + }, + { + from: 'jones::jones/jones', + to: 'heuristic::heuri/heuristic', + }, + { + from: 'youthful::youth/youthful', + to: 'lumiere::lumie/lumiere', + }, + { + from: 'lumiere::lumie/lumiere', + to: 'lumiere::lumie/lumiere', + }, + { + from: 'ecstatic::ecsta/ecstatic', + to: 'golick::golic/golick', + }, + { + from: 'ecstatic::ecsta/ecstatic', + to: 'goldstine::golds/goldstine', + }, + { + from: 'ecstatic::ecsta/ecstatic', + to: 'volhard::volha/volhard', + }, + { + from: 'golick::golic/golick', + to: 'zhukovsky::zhuko/zhukovsky', + }, + { + from: 'zhukovsky::zhuko/zhukovsky', + to: 'zhukovsky::zhuko/zhukovsky', + }, + { + from: 'goldstine::golds/goldstine', + to: 'dubinsky::dubin/dubinsky', + }, + { + from: 'dubinsky::dubin/dubinsky', + to: 'dubinsky::dubin/dubinsky', + }, + { + from: 'vigorous::vigor/vigorous', + to: 'joliot::jolio/joliot', + }, + { + from: 'vigorous::vigor/vigorous', + to: 'vigorous::vigor/vigorous', + }, + { + from: 'vigorous::vigor/vigorous', + to: 'pensive::pensi/pensive', + }, + { + from: 'jolly::jolly/jolly', + to: 'fervent::ferve/fervent', + }, + { + from: 'euclid::eucli/euclid', + to: 'jolly::jolly/jolly', + }, + { + from: 'boring::borin/boring', + to: 'fervent::ferve/fervent', + }, + { + from: 'fermi::fermi/fermi', + to: 'fervent::ferve/fervent', + }, + ], + vertices: [ + { + key: 'fervent::ferve/fervent', + }, + { + key: 'carson::carso/carson', + }, + { + key: 'volhard::volha/volhard', + }, + { + key: 'yalow::yalow/yalow', + }, + { + key: 'mystifying::mysti/mystifying', + }, + { + key: 'meninsky::menin/meninsky', + }, + { + key: 'admiring::admir/admiring', + }, + { + key: 'hoover::hoove/hoover', + }, + { + key: 'brahmagupta::brahm/brahmagupta', + }, + { + key: 'banach::banac/banach', + }, + { + key: 'chatterjee::chatt/chatterjee', + }, + { + key: 'unruffled::unruf/unruffled', + }, + { + key: 'shockley::shock/shockley', + }, + { + key: 'pasteur::paste/pasteur', + }, + { + key: 'upbeat::upbea/upbeat', + }, + { + key: 'darwin::darwi/darwin', + }, + { + key: 'priceless::price/priceless', + }, + { + key: 'sammet::samme/sammet', + }, + { + key: 'serene::seren/serene', + }, + { + key: 'vigorous::vigor/vigorous', + }, + { + key: 'gracious::graci/gracious', + }, + { + key: 'hardcore::hardc/hardcore', + }, + { + key: 'elastic::elast/elastic', + }, + { + key: 'dazzling::dazzl/dazzling', + }, + { + key: 'neumann::neuma/neumann', + }, + { + key: 'keller::kelle/keller', + }, + { + key: 'heuristic::heuri/heuristic', + }, + { + key: 'murdock::murdo/murdock', + }, + { + key: 'eloquent::eloqu/eloquent', + }, + { + key: 'peaceful::peace/peaceful', + }, + { + key: 'infallible::infal/infallible', + }, + { + key: 'easley::easle/easley', + }, + { + key: 'sharp::sharp/sharp', + }, + { + key: 'lalande::lalan/lalande', + }, + { + key: 'brave::brave/brave', + }, + { + key: 'wright::wrigh/wright', + }, + { + key: 'jones::jones/jones', + }, + { + key: 'bose::bose/bose', + }, + { + key: 'ecstatic::ecsta/ecstatic', + }, + { + key: 'tereshkova::teres/tereshkova', + }, + { + key: 'lichterman::licht/lichterman', + }, + { + key: 'visvesvaraya::visve/visvesvaraya', + }, + { + key: 'mclean::mclea/mclean', + }, + { + key: 'stupefied::stupe/stupefied', + }, + { + key: 'bhabha::bhabh/bhabha', + }, + { + key: 'youthful::youth/youthful', + }, + { + key: 'lumiere::lumie/lumiere', + }, + { + key: 'golick::golic/golick', + }, + { + key: 'goldstine::golds/goldstine', + }, + { + key: 'zhukovsky::zhuko/zhukovsky', + }, + { + key: 'dubinsky::dubin/dubinsky', + }, + { + key: 'joliot::jolio/joliot', + }, + { + key: 'pensive::pensi/pensive', + }, + { + key: 'jolly::jolly/jolly', + }, + { + key: 'euclid::eucli/euclid', + }, + { + key: 'boring::borin/boring', + }, + { + key: 'fermi::fermi/fermi', + }, + ], +}; + +export function getNodeLabel(vertex) { + const [svc, op] = vertex.key.split('::', 2); + return ( + + {svc} +
+ {op} +
+ ); +} diff --git a/packages/plexus/demo/src/data-small.js b/packages/plexus/demo/src/data-small.js new file mode 100644 index 0000000000..ff6a3bf3bb --- /dev/null +++ b/packages/plexus/demo/src/data-small.js @@ -0,0 +1,84 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as React from 'react'; + +export const varied = { + vertices: [ + { key: 'string key 0', data: { value: new Date(), message: 'vertex w a string key that has spaces' } }, + { + key: 1, + label: 'Key is the number 1Key is the number 1Key', + data: { err: new Error(9), message: 'vertex with a number key and a string label' }, + }, + { key: '2', label:

OMG an H3

, data: { message: 'label is an H3 React element' } }, + { + key: 33, + label: 'Key is the number 1Key is the number 1Key', + data: { value: /abc/, message: 'data contains a RegExp and the node lacks a label' }, + }, + ], + edges: [ + { from: 'string key 0', to: 1, label: 'The Great Edge Label', data: 'Edge with a string label' }, + { + from: 'string key 0', + to: '2', + label: Drop it like its hot, + data: 'edge with a React.Node label', + }, + { from: '1', to: '2', data: 'edge sans label' }, + { from: '2', to: 33, isBidirectional: true, data: 'A bidirection edge' }, + ], +}; + +export const colored = { + vertices: [ + { key: 'string key 0', data: 'red' }, + { + key: 1, + label: 'Key is the number 1', + data: 'blue', + }, + { key: '2', label:

OMG an H3

, data: 'green' }, + { key: 33, data: 'teal' }, + ], + edges: [ + { from: 'string key 0', to: 1, label: 'The Great Edge Label', data: '#c00' }, + { + from: 'string key 0', + to: '2', + label: Drop it like its hot, + data: '#0c0', + }, + { from: '1', to: '2', data: '#00c' }, + { from: '2', to: 33, isBidirectional: true, data: '#c0c' }, + ], +}; + +export function getColorNodeLabel(vertex) { + let { label } = vertex; + label = label == null ? String(vertex.key) : label; + if (typeof label !== 'string' && !React.isValidElement(label)) { + label = String(label); + } + return {label}; +} + +export function setOnColorNode(vertex) { + const style = { border: `1px solid ${vertex.data}` }; + return { style }; +} +export function setOnColorEdge(edge) { + return { stroke: edge.data }; +} diff --git a/packages/plexus/demo/src/index.css b/packages/plexus/demo/src/index.css new file mode 100644 index 0000000000..adf420ce7d --- /dev/null +++ b/packages/plexus/demo/src/index.css @@ -0,0 +1,10 @@ +html { + font-family: Arial, Helvetica, sans-serif; +} + +.Node { + background: #eee; + border: 1px solid #ddd; + line-height: 1.4; + padding: 0.3em 0.5em; +} diff --git a/packages/plexus/demo/src/index.js b/packages/plexus/demo/src/index.js new file mode 100644 index 0000000000..7777428609 --- /dev/null +++ b/packages/plexus/demo/src/index.js @@ -0,0 +1,90 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as React from 'react'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { render } from 'react-dom'; + +import largeDg, { getNodeLabel as getLargeNodeLabel } from './data-large'; +import { edges as dagEdges, vertices as dagVertices } from './data-dag'; +import { varied, colored, getColorNodeLabel, setOnColorEdge, setOnColorNode } from './data-small'; +import { DirectedGraph, LayoutManager } from '../../src'; + +import './index.css'; + +const addAnAttr = () => ({ 'data-rando': Math.random() }); + +const addNodeDemoCss = () => ({ className: 'Node' }); + +class Demo extends React.Component { + state = { + data: colored, + colorData: true, + }; + + constructor(props) { + super(props); + this.layoutManager = new LayoutManager(); + this.dagLayoutManager = new LayoutManager(); + this.largeLayoutManager = new LayoutManager(); + } + + handleClick = () => { + const { colorData } = this.state; + this.setState({ + colorData: !colorData, + data: colorData ? varied : colored, + }); + }; + + render() { + const { data, colorData } = this.state; + return ( +
+

+ + plexus Demo + +

+

Small graph with data driven rendering

+ +

Medium DAG

+ +

Larger directd graph with cycles

+ +
+ ); + } +} + +render(, document.querySelector('#demo')); diff --git a/packages/plexus/nwb.config.js b/packages/plexus/nwb.config.js new file mode 100644 index 0000000000..8f5673e970 --- /dev/null +++ b/packages/plexus/nwb.config.js @@ -0,0 +1,67 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// make sure the worker files are run through the babel-loader before the worker-loader +function setBabelPre(config) { + const babel = config.module.rules.find(rule => /babel-loader/.test(rule.loader)); + babel.enforce = 'pre'; + return config; +} + +module.exports = function nwbConfig() { + return { + type: 'react-component', + npm: { + esModules: false, + cjs: true, + umd: { + global: 'JaegerPlexus', + externals: { + react: 'React', + 'react-dom': 'ReactDom', + }, + }, + }, + babel: { + config(cfg) { + // eslint-disable-next-line no-param-reassign + cfg.compact = true; + return cfg; + }, + }, + devServer: { hot: false }, + webpack: { + extractText: { + filename: process.env.NODE_ENV === 'production' ? `plexus.[contenthash:8].css` : 'plexus.css', + }, + extra: { + devtool: 'source-map', + module: { + rules: [ + { + test: /\.worker\.js$/, + use: [ + { + loader: require.resolve('worker-loader'), + options: { inline: true, fallback: false, name: '[name].[hash:8].js' }, + }, + ], + }, + ], + }, + }, + config: setBabelPre, + }, + }; +}; diff --git a/packages/plexus/package.json b/packages/plexus/package.json new file mode 100644 index 0000000000..3392024839 --- /dev/null +++ b/packages/plexus/package.json @@ -0,0 +1,32 @@ +{ + "name": "@jaegertracing/plexus", + "version": "0.0.1-dev.2", + "description": "Direct Graph React component", + "main": "umd/@jaegertracing/plexus.js", + "files": ["lib", "umd"], + "scripts": { + "build": "nwb build-react-component", + "clean": "nwb clean-module && nwb clean-demo", + "start": "nwb serve-react-demo", + "test": "echo 'NO TESTS YET'", + "coverage": "echo 'NO TESTS YET'", + "test:dev": "nwb test-react --server" + }, + "dependencies": { + "viz.js": "^1.8.1" + }, + "peerDependencies": { + "react": "16.x" + }, + "devDependencies": { + "nwb": "0.21.x", + "react": "^16.3.2", + "react-dom": "^16.3.2", + "worker-loader": "^1.1.1" + }, + "author": "", + "homepage": "https://github.com/jaegertracing/jaeger-ui", + "license": "Apache-2.0", + "repository": "https://github.com/jaegertracing/jaeger-ui.git", + "keywords": ["react-component"] +} diff --git a/packages/plexus/src/DirectedGraph/DirectedGraph.js b/packages/plexus/src/DirectedGraph/DirectedGraph.js new file mode 100644 index 0000000000..80055a3bc9 --- /dev/null +++ b/packages/plexus/src/DirectedGraph/DirectedGraph.js @@ -0,0 +1,240 @@ +// @flow + +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as React from 'react'; + +import * as arrow from './builtins/EdgeArrow'; +import EdgePath from './builtins/EdgePath'; +import EdgesContainer from './builtins/EdgesContainer'; +import Node from './builtins/Node'; +import type { DirectedGraphProps, DirectedGraphState } from './types'; +import type { Edge, Vertex } from '../types/layout'; + +const PHASE_NO_DATA = 0; +const PHASE_CALC_SIZES = 1; +const PHASE_CALC_POSITIONS = 2; +const PHASE_CALC_EDGES = 3; +const PHASE_DONE = 4; + +function defaultGetEdgeLabel( + edge: Edge, + from: Vertex, + to: Vertex, + getNodeLabel: Vertex => string | React.Node +) { + const { label } = edge; + if (label != null) { + if (typeof label === 'string' || React.isValidElement(label)) { + return label; + } + return String(label); + } + return ( + + {getNodeLabel(from)} → {getNodeLabel(to)} + + ); +} + +function defaultGetNodeLabel(vertex: Vertex) { + const { label } = vertex; + if (label != null) { + if (typeof label === 'string' || React.isValidElement(label)) { + return label; + } + return String(label); + } + return String(vertex.key); +} + +export default class DirectedGraph extends React.PureComponent { + // ref API defs in flow seem to be a WIP + // https://github.com/facebook/flow/issues/6103 + vertexRefs: { current: ?HTMLElement }[]; + + static defaultProps = { + classNamePrefix: 'plexus', + getEdgeLabel: defaultGetEdgeLabel, + getNodeLabel: defaultGetNodeLabel, + }; + + state = { + edges: [], + layoutPhase: PHASE_NO_DATA, + sizeVertices: null, + layoutEdges: null, + layoutGraph: null, + layoutVertices: null, + vertexRefs: [], + vertices: [], + }; + + static getDerivedStateFromProps(nextProps: DirectedGraphProps, prevState: DirectedGraphState) { + const { edges: nxEdges, vertices: nxVertices } = nextProps; + const { edges: stEdges, vertices: stVertices } = prevState; + if (nxEdges === stEdges && nxVertices === stVertices) { + return null; + } + return { + layoutPhase: PHASE_CALC_SIZES, + edges: nxEdges, + vertices: nxVertices, + vertexRefs: nxVertices.map(React.createRef), + sizeVertices: null, + layoutEdges: null, + layoutGraph: null, + layoutVertices: null, + }; + } + + constructor(props: DirectedGraphProps) { + super(props); + const { edges, vertices } = props; + if (Array.isArray(edges) && edges.length && Array.isArray(vertices) && vertices.length) { + this.state.layoutPhase = PHASE_CALC_SIZES; + this.state.edges = edges; + this.state.vertices = vertices; + this.state.vertexRefs = vertices.map(React.createRef); + } + } + + componentDidMount() { + this._setSizeVertices(); + } + + componentDidUpdate() { + const { layoutPhase } = this.state; + if (layoutPhase === PHASE_CALC_SIZES) { + this._setSizeVertices(); + } + } + + _setSizeVertices() { + const { edges, layoutManager, vertices } = this.props; + const sizeVertices = this.state.vertexRefs + .map((ref, i) => { + const { current } = ref; + if (!current) { + return null; + } + return { + height: current.offsetHeight, + vertex: vertices[i], + width: current.offsetWidth, + }; + }) + .filter(Boolean); + const { positions, layout } = layoutManager.getLayout(edges, sizeVertices); + positions.then(({ isCancelled, graph: layoutGraph, vertices: layoutVertices }) => { + if (isCancelled) { + return; + } + this.setState({ layoutGraph, layoutVertices, layoutPhase: PHASE_CALC_EDGES }); + }); + layout.then(({ isCancelled, edges: layoutEdges, graph: layoutGraph, vertices: layoutVertices }) => { + if (isCancelled) { + return; + } + this.setState({ layoutEdges, layoutGraph, layoutVertices, layoutPhase: PHASE_DONE }); + }); + this.setState({ sizeVertices, layoutPhase: PHASE_CALC_POSITIONS }); + } + + _renderVertices() { + const { classNamePrefix, getNodeLabel, setOnNode, vertices } = this.props; + const { vertexRefs } = this.state; + const _getLabel = getNodeLabel != null ? getNodeLabel : defaultGetNodeLabel; + return vertices.map((v, i) => ( +