diff --git a/x-pack/plugins/monitoring/common/constants.js b/x-pack/plugins/monitoring/common/constants.js index e1f1806026b1a..72edcbb3d68a1 100644 --- a/x-pack/plugins/monitoring/common/constants.js +++ b/x-pack/plugins/monitoring/common/constants.js @@ -121,27 +121,6 @@ export const LOGSTASH = { * Constants used by Logstash Pipeline Viewer code */ PIPELINE_VIEWER: { - GRAPH: { - EDGES: { - SVG_CLASS: 'lspvEdge', - LABEL_RADIUS: 8, - // This is something we may play with later. - // 1 seems to be the best value however, without it the edges sometimes make weird loops in complex graphs - ROUTING_MARGIN_PX: 1, - ARROW_START: 5 - }, - VERTICES: { - BORDER_RADIUS_PX: 4, - MARGIN_PX: 35, - WIDTH_PX: 320, - HEIGHT_PX: 85, - - /** - * Vertical distance between vertices, as measured from top-border-to-top-border - */ - VERTICAL_DISTANCE_PX: 20 - } - }, ICON: { HEIGHT_PX: 18, WIDTH_PX: 18 diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/__tests__/index.test.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/__tests__/index.test.js deleted file mode 100644 index 5e54be90afce0..0000000000000 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/__tests__/index.test.js +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { shallow } from 'enzyme'; -import { PipelineViewer } from '../index'; -import { DetailDrawer } from '../views/detail_drawer'; -import { Graph } from '../models/graph'; - - -describe('PipelineViewer component', () => { - describe('detail drawer', () => { - let graph; - let timeseriesTooltipXValueFormatter; - let pipelineState; - let wrapper; - let vertex; - - beforeEach(() => { - graph = new Graph(); - graph.update({ - vertices: [ - { - id: 'terminal_logger', - explicit_id: true, - type: 'plugin', - plugin_type: 'input', - config_name: 'stdin', - stats: {} - }, - { - id: '__QUEUE__', - explicit_id: false, - type: 'queue', - stats: {} - }, - { - id: '5y890f3e5c135c037eb40ba88d69b040faaeb954bb10510e95294259ffdd88e8', - explicit_id: false, - type: 'plugin', - plugin_type: 'output', - config_name: 'elasticsearch', - stats: {} - } - ], - edges: [ - { - id: '8f1gae28a11c3d99d1adf44f793763db6b9c61379e0ad518371b49aa67ef902f', - from: 'terminal_logger', - to: '__QUEUE__', - type: 'plain' - }, - { - id: '8ue2ae28a11c3d99d1ado9f3epq0bvjd6b9c61379e0ad518371b49aa67ef902f', - from: '__QUEUE__', - to: '5y890f3e5c135c037eb40ba88d69b040faaeb954bb10510e95294259ffdd88e8', - type: 'plain' - } - ] - }); - - timeseriesTooltipXValueFormatter = () => {}; - pipelineState = { - config: { - graph - } - }; - - wrapper = shallow(( - - )); - - vertex = graph.getVertices()[0]; - }); - - it('creates null vertex state by default', () => { - expect( - wrapper.instance().state.detailDrawer - ).toEqual({ vertex: null }); - }); - - it('is rendered for newly-selected vertex', () => { - const component = wrapper.instance(); - - component.onShowVertexDetails(vertex); - - expect(wrapper.find(DetailDrawer).length).toEqual(0); - expect(component.state.detailDrawer.vertex).toEqual(vertex); - - wrapper.update(); - - expect(wrapper.find(DetailDrawer).length).toEqual(1); - }); - - it('is hidden if current vertex is selected again', () => { - const component = wrapper.instance(); - - component.onShowVertexDetails(vertex); - - wrapper.update(); - - // showing drawer for selected vertex - expect(wrapper.find(DetailDrawer).length).toEqual(1); - - component.onShowVertexDetails(vertex); - - wrapper.update(); - - // drawer toggled off for second consecutive selection of vertex - expect(wrapper.find(DetailDrawer).length).toEqual(0); - expect(component.state.detailDrawer.vertex).toEqual(null); - }); - - it('remains visible for new vertex', () => { - const component = wrapper.instance(); - - component.onShowVertexDetails(vertex); - - wrapper.update(); - - // showing drawer for first vertex - expect(wrapper.find(DetailDrawer).length).toEqual(1); - - // select different vertex - const secondVertex = graph.getVertices()[1]; - component.onShowVertexDetails(secondVertex); - - wrapper.update(); - - // still visible for second vertex - expect(wrapper.find(DetailDrawer).length).toEqual(1); - expect(component.state.detailDrawer.vertex).toEqual(secondVertex); - }); - }); -}); diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/index.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/index.js index c4748876b85f1..df041e4edfaef 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/index.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/index.js @@ -4,77 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { ColaGraph } from './views/cola_graph'; -import { DetailDrawer } from './views/detail_drawer'; -import { PropTypes } from 'prop-types'; - -export class PipelineViewer extends React.Component { - constructor() { - super(); - this.state = { - detailDrawer: { - vertex: null - } - }; - } - - onShowVertexDetails = (vertex) => { - if (vertex === this.state.detailDrawer.vertex) { - this.onHideVertexDetails(); - } - else { - this.setState({ - detailDrawer: { - vertex - } - }); - } - } - - onHideVertexDetails = () => { - this.setState({ - detailDrawer: { - vertex: null - } - }); - } - - renderDetailDrawer = () => { - if (!this.state.detailDrawer.vertex) { - return null; - } - - return ( - - ); - } - - render() { - const graph = this.props.pipelineState.config.graph; - - return ( -
- - { this.renderDetailDrawer() } -
- ); - } -} - -PipelineViewer.propTypes = { - pipelineState: PropTypes.shape({ - config: PropTypes.shape({ - graph: PropTypes.object.isRequired - }) - }), - timeseriesTooltipXValueFormatter: PropTypes.func.isRequired -}; +export { ConfigViewer } from './views/config_viewer'; diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/boolean_edge.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/boolean_edge.js index c029891684cdf..cfe240436b550 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/boolean_edge.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/boolean_edge.js @@ -7,7 +7,6 @@ import expect from 'expect.js'; import { BooleanEdge } from '../boolean_edge'; import { Edge } from '../edge'; -import { LOGSTASH } from '../../../../../../../common/constants'; describe('BooleanEdge', () => { let graph; @@ -32,10 +31,4 @@ describe('BooleanEdge', () => { const booleanEdge = new BooleanEdge(graph, edgeJson); expect(booleanEdge).to.be.a(Edge); }); - - it('should have the correct SVG CSS class', () => { - const booleanEdge = new BooleanEdge(graph, edgeJson); - const edgeSvgClass = LOGSTASH.PIPELINE_VIEWER.GRAPH.EDGES.SVG_CLASS; - expect(booleanEdge.svgClass).to.be(`${edgeSvgClass} ${edgeSvgClass}Boolean ${edgeSvgClass}Boolean--true`); - }); }); \ No newline at end of file diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/edge.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/edge.js index 9b91b0f2f160c..35249d938f2e5 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/edge.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/edge.js @@ -6,7 +6,6 @@ import expect from 'expect.js'; import { Edge } from '../edge'; -import { LOGSTASH } from '../../../../../../../common/constants'; describe('Edge', () => { let graph; @@ -26,20 +25,6 @@ describe('Edge', () => { }; }); - it('should initialize the webcola representation', () => { - const edge = new Edge(graph, edgeJson); - expect(edge.cola).to.eql({ - edge: edge, - source: 'bar', - target: 17 - }); - }); - - it('should have a D3-friendly ID', () => { - const edge = new Edge(graph, edgeJson); - expect(edge.htmlAttrId).to.be('myif_myes'); - }); - it('should have the correct from vertex', () => { const edge = new Edge(graph, edgeJson); expect(edge.fromId).to.be('myif'); @@ -51,9 +36,4 @@ describe('Edge', () => { expect(edge.toId).to.be('myes'); expect(edge.to).to.be(graph.verticesById.myes); }); - - it('should have the correct SVG CSS class', () => { - const edge = new Edge(graph, edgeJson); - expect(edge.svgClass).to.be(LOGSTASH.PIPELINE_VIEWER.GRAPH.EDGES.SVG_CLASS); - }); }); \ No newline at end of file diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/edge_factory.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/edge_factory.js index c0704b4cf80cc..27bc53673f036 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/edge_factory.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/edge_factory.js @@ -6,7 +6,7 @@ import expect from 'expect.js'; import { edgeFactory } from '../edge_factory'; -import { PlainEdge } from '../plain_edge'; +import { Edge } from '../edge'; import { BooleanEdge } from '../boolean_edge'; describe('edgeFactory', () => { @@ -27,9 +27,9 @@ describe('edgeFactory', () => { }; }); - it('returns a PlainEdge when edge type is plain', () => { + it('returns an Edge when edge type is plain', () => { edgeJson.type = 'plain'; - expect(edgeFactory(graph, edgeJson)).to.be.a(PlainEdge); + expect(edgeFactory(graph, edgeJson)).to.be.a(Edge); }); it('returns a BooleanEdge when edge type is boolean', () => { diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/index.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/index.js index 62e7d11e7c8b2..80a8f6f038697 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/index.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/index.js @@ -144,16 +144,6 @@ describe('Graph', () => { }); }); - it('identifies cola representations of vertices correctly', () => { - const colaVertices = graph.colaVertices; - expect(colaVertices).to.be.an(Array); - expect(colaVertices.length).to.be(5); - - expect(colaVertices[0]).to.have.property('vertex'); - expect(colaVertices[0]).to.have.property('width'); - expect(colaVertices[0]).to.have.property('height'); - }); - it('identifies the correct edges', () => { const edges = graph.edges; expect(edges).to.be.an(Array); @@ -163,81 +153,6 @@ describe('Graph', () => { expect(edge).to.be.an(Edge); }); }); - - it('identifies cola representations of edges correctly', () => { - const colaEdges = graph.colaEdges; - expect(colaEdges).to.be.an(Array); - expect(colaEdges.length).to.be(4); - - colaEdges.forEach(colaEdge => { - expect(colaEdge).to.have.property('edge'); - expect(colaEdge).to.have.property('source'); - expect(colaEdge).to.have.property('target'); - }); - }); - - it('identifies its root vertices correctly', () => { - const roots = graph.roots; - expect(roots).to.be.an(Array); - expect(roots.length).to.be(1); - - roots.forEach(root => { - expect(root).to.be.a(Vertex); - expect(root.json.id).to.be('my-prefix:my-really-long-named-generator'); - }); - }); - - it('identifies its leaf vertices correctly', () => { - const leaves = graph.leaves; - expect(leaves).to.be.an(Array); - expect(leaves.length).to.be(2); - - leaves.forEach(leaf => { - expect(leaf).to.be.a(Vertex); - }); - }); - - it('identifies the highest vertex rank correctly', () => { - expect(graph.maxRank).to.be(3); - }); - - describe('vertex layout ranking', () => { - const expectedRanks = [ - ['my-prefix:my-really-long-named-generator'], - ['my-queue'], - ['my-if'], - ['my-grok', 'my-sleep'] - ]; - - it('should store a 2d array of the vertices in the expected ranks', () => { - const result = graph.verticesByLayoutRank; - expectedRanks.forEach((expectedVertexIds, rank) => { - const resultVertices = result[rank]; - expectedVertexIds.forEach(expectedVertexId => { - const expectedVertex = graph.getVertexById(expectedVertexId); - expect(resultVertices).to.contain(expectedVertex); - }); - }); - }); - - it('should add a .layoutRank property to each Vertex', () => { - expectedRanks.forEach((expectedVertexIds, rank) => { - expectedVertexIds.forEach(expectedVertexId => { - const vertex = graph.getVertexById(expectedVertexId); - expect(vertex.layoutRank).to.be(rank); - }); - }); - }); - }); - - it('should classify the if triangle correctly', () => { - expect(graph.triangularIfGroups.length).to.be(1); - expect(graph.triangularIfGroups[0]).to.eql({ - ifVertex: graph.getVertexById('my-if'), - trueVertex: graph.getVertexById('my-grok'), - falseVertex: graph.getVertexById('my-sleep'), - }); - }); }); describe('assigning pipeline stages', () => { diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/plain_edge.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/plain_edge.js deleted file mode 100644 index 1138acc41b528..0000000000000 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/plain_edge.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from 'expect.js'; -import { PlainEdge } from '../plain_edge'; -import { Edge } from '../edge'; -import { LOGSTASH } from '../../../../../../../common/constants'; - -describe('PlainEdge', () => { - let graph; - let edgeJson; - - beforeEach(() => { - graph = { - verticesById: { - mygenerator: {}, - myqueue: {} - } - }; - edgeJson = { - id: 'abcdef', - from: 'mygenerator', - to: 'myqueue' - }; - }); - - it('should be an instance of Edge', () => { - const plainEdge = new PlainEdge(graph, edgeJson); - expect(plainEdge).to.be.a(Edge); - }); - - it('should have the correct SVG CSS class', () => { - const plainEdge = new PlainEdge(graph, edgeJson); - const edgeSvgClass = LOGSTASH.PIPELINE_VIEWER.GRAPH.EDGES.SVG_CLASS; - expect(plainEdge.svgClass).to.be(`${edgeSvgClass} ${edgeSvgClass}Plain`); - }); -}); \ No newline at end of file diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/vertex.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/vertex.js index b16ba1f28f0f5..bc29533affd6a 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/vertex.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/vertex.js @@ -6,7 +6,6 @@ import expect from 'expect.js'; import { Graph } from '../'; -import { LOGSTASH } from '../../../../../../../common/constants'; describe('Vertex', () => { let graph; @@ -44,16 +43,6 @@ describe('Vertex', () => { graph.update(graphJson); }); - it('should initialize the webcola representation', () => { - const margin = LOGSTASH.PIPELINE_VIEWER.GRAPH.VERTICES.MARGIN_PX; - const vertex = graph.getVertexById('my-queue'); - expect(vertex.cola).to.eql({ - vertex: vertex, - width: LOGSTASH.PIPELINE_VIEWER.GRAPH.VERTICES.WIDTH_PX + margin, - height: LOGSTASH.PIPELINE_VIEWER.GRAPH.VERTICES.HEIGHT_PX + margin - }); - }); - it('should update the internal json property when update() is called', () => { const vertex = graph.getVertexById('my-queue'); const updatedJson = { @@ -63,18 +52,6 @@ describe('Vertex', () => { expect(vertex.json).to.eql(updatedJson); }); - it('should not change the webcola index after update', () => { - const verticesIds = graph.getVertices().map(v => [v.id, v.colaIndex]); - graph.update(graphJson); - verticesIds.forEach(idAndIndex => { - const [id, colaIndex] = idAndIndex; - const v = graph.getVertexById(id); - console.log("MATCH", v.id, id); - expect(v).not.to.be(undefined); - expect(v.colaIndex).to.be(colaIndex); - }); - }); - it('should have the correct name', () => { const vertex = graph.getVertexById('my-queue'); expect(vertex.name).to.be('some-name'); @@ -85,20 +62,12 @@ describe('Vertex', () => { expect(vertex1.id).to.be(vertex1.json.id); }); - it('should have the correct htmlAttrId', () => { - const vertex1 = graph.getVertexById('my-prefix:my-really-long-named-generator'); - expect(vertex1.htmlAttrId).to.be('my_prefix_my_really_long_named_generator'); - - const vertex2 = graph.getVertexById('my-queue'); - expect(vertex2.htmlAttrId).to.be('my_queue'); - }); - it('should have the correct subtitle', () => { const vertex1 = graph.getVertexById('my-prefix:my-really-long-named-generator'); - expect(vertex1.subtitle).to.eql({ display: 'my-prefi … enerator', complete: 'my-prefix:my-really-long-named-generator' }); + expect(vertex1.subtitle).to.eql('my-prefix:my-really-long-named-generator'); const vertex2 = graph.getVertexById('my-queue'); - expect(vertex2.subtitle).to.eql({ display: 'my-queue', complete: 'my-queue' }); + expect(vertex2.subtitle).to.eql('my-queue'); }); it('should have the correct number of incoming edges', () => { @@ -169,67 +138,6 @@ describe('Vertex', () => { expect(vertex5.outgoingVertices.length).to.be(0); }); - it('should correctly identify as a root vertex', () => { - const vertex1 = graph.getVertexById('my-prefix:my-really-long-named-generator'); - expect(vertex1.isRoot).to.be(true); - - const vertex2 = graph.getVertexById('my-queue'); - expect(vertex2.isRoot).to.be(false); - - const vertex3 = graph.getVertexById('my-if'); - expect(vertex3.isRoot).to.be(false); - - const vertex4 = graph.getVertexById('my-grok'); - expect(vertex4.isRoot).to.be(false); - - const vertex5 = graph.getVertexById('my-sleep'); - expect(vertex5.isRoot).to.be(false); - }); - - it('should correctly identify as a leaf vertex', () => { - const vertex1 = graph.getVertexById('my-prefix:my-really-long-named-generator'); - expect(vertex1.isLeaf).to.be(false); - - const vertex2 = graph.getVertexById('my-queue'); - expect(vertex2.isLeaf).to.be(false); - - const vertex3 = graph.getVertexById('my-if'); - expect(vertex3.isLeaf).to.be(false); - - const vertex4 = graph.getVertexById('my-grok'); - expect(vertex4.isLeaf).to.be(true); - - const vertex5 = graph.getVertexById('my-sleep'); - expect(vertex5.isLeaf).to.be(true); - }); - - it('should have the correct rank', () => { - const vertex1 = graph.getVertexById('my-prefix:my-really-long-named-generator'); - expect(vertex1.rank).to.be(0); - - const vertex2 = graph.getVertexById('my-queue'); - expect(vertex2.rank).to.be(1); - - const vertex3 = graph.getVertexById('my-if'); - expect(vertex3.rank).to.be(2); - - const vertex4 = graph.getVertexById('my-grok'); - expect(vertex4.rank).to.be(3); - - const vertex5 = graph.getVertexById('my-sleep'); - expect(vertex5.rank).to.be(3); - }); - - it('should have the correct source location', () => { - const vertex = graph.getVertexById('my-grok'); - expect(vertex.sourceLocation).to.be('apc.conf@33:4'); - }); - - it('should have the correct source text', () => { - const vertex = graph.getVertexById('my-grok'); - expect(vertex.sourceText).to.be('foobar'); - }); - it('should have the correct metadata', () => { const vertex = graph.getVertexById('my-grok'); expect(vertex.meta).to.eql({ source_text: 'foobar', source_line: 33, source_column: 4 }); @@ -243,43 +151,7 @@ describe('Vertex', () => { expect(vertex2.stats).to.eql({}); }); - it('should correctly identify if it has custom stats', () => { - const vertex1 = graph.getVertexById('my-sleep'); - expect(vertex1.hasCustomStats).to.be(true); - - const vertex2 = graph.getVertexById('my-grok'); - expect(vertex2.hasCustomStats).to.be(false); - }); - - it('should correctly report custom stats', () => { - const vertex1 = graph.getVertexById('my-sleep'); - expect(vertex1.customStats).to.eql({ mystat1: 100 }); - - const vertex2 = graph.getVertexById('my-grok'); - expect(vertex2.customStats).to.eql({}); - }); - describe('lineage', () => { - it('should have the correct ancestors', () => { - const vertex1 = graph.getVertexById('my-prefix:my-really-long-named-generator'); - expect(vertex1.ancestors()).to.eql({ vertices: [], edges: [] }); - - const vertex2 = graph.getVertexById('my-queue'); - expect(vertex2.ancestors().vertices.length).to.be(1); - expect(vertex2.ancestors().edges.length).to.be(1); - - const vertex3 = graph.getVertexById('my-if'); - expect(vertex3.ancestors().vertices.length).to.be(2); - expect(vertex3.ancestors().edges.length).to.be(2); - - const vertex4 = graph.getVertexById('my-grok'); - expect(vertex4.ancestors().vertices.length).to.be(3); - expect(vertex4.ancestors().edges.length).to.be(3); - - const vertex5 = graph.getVertexById('my-sleep'); - expect(vertex5.ancestors().vertices.length).to.be(3); - expect(vertex5.ancestors().edges.length).to.be(3); - }); it('should have the correct descendants', () => { const vertex1 = graph.getVertexById('my-prefix:my-really-long-named-generator'); @@ -301,28 +173,6 @@ describe('Vertex', () => { expect(vertex5.descendants()).to.eql({ vertices: [], edges: [] }); }); - it('should have the correct lineage', () => { - const vertex1 = graph.getVertexById('my-prefix:my-really-long-named-generator'); - expect(vertex1.lineage().vertices.length).to.be(5); - expect(vertex1.lineage().edges.length).to.be(4); - - const vertex2 = graph.getVertexById('my-queue'); - expect(vertex2.lineage().vertices.length).to.be(5); - expect(vertex2.lineage().edges.length).to.be(4); - - const vertex3 = graph.getVertexById('my-if'); - expect(vertex3.lineage().vertices.length).to.be(5); - expect(vertex3.lineage().edges.length).to.be(4); - - const vertex4 = graph.getVertexById('my-grok'); - expect(vertex4.lineage().vertices.length).to.be(4); - expect(vertex4.lineage().edges.length).to.be(3); - - const vertex5 = graph.getVertexById('my-sleep'); - expect(vertex5.lineage().vertices.length).to.be(4); - expect(vertex5.lineage().edges.length).to.be(3); - }); - describe('it should handle complex topologies correctly', () => { /** * I1 @@ -372,23 +222,9 @@ describe('Vertex', () => { graph = new Graph(); graph.update(complexGraphJson); }); - - it('should calculate the lineage correctly', () => { - const vertex1 = graph.getVertexById('F5'); - expect(vertex1.lineage().vertices.length).to.be(9); - expect(vertex1.lineage().edges.length).to.be(10); - }); }); }); - it('should have the correct events per current period', () => { - const vertex1 = graph.getVertexById('my-sleep'); - expect(vertex1.eventsPerCurrentPeriod).to.be(20); - - const vertex2 = graph.getVertexById('my-grok'); - expect(vertex2.eventsPerCurrentPeriod).to.be(null); - }); - it('should correctly identify if it has an explicit ID', () => { const vertex1 = graph.getVertexById('my-prefix:my-really-long-named-generator'); expect(vertex1.hasExplicitId).to.be(false); diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/boolean_edge.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/boolean_edge.js index c5debbc11f5d2..017d613fca05b 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/boolean_edge.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/boolean_edge.js @@ -18,8 +18,4 @@ export class BooleanEdge extends Edge { get isFalse() { return this.when === false; } - - get svgClass() { - return `${super.svgClass} ${super.svgClass}Boolean ${super.svgClass}Boolean--${this.when}`; - } } diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/edge.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/edge.js index 7205ffce967ac..1f48720617ff7 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/edge.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/edge.js @@ -4,22 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LOGSTASH } from '../../../../../../common/constants'; - export class Edge { constructor(graph, json) { this.graph = graph; this.update(json); - - this.cola = this._makeCola(); - } - - _makeCola() { - return { - edge: this, - source: this.from.cola, - target: this.to.cola - }; } update(json) { @@ -30,12 +18,6 @@ export class Edge { return this.json.id; } - get htmlAttrId() { - // Substitute any non-word characters with an underscore so - // D3 selections don't interpret them as special selector syntax - return this.json.id.replace(/\W/, '_'); - } - get from() { return this.graph.verticesById[this.fromId]; } @@ -51,8 +33,4 @@ export class Edge { get toId() { return this.json.to; } - - get svgClass() { - return LOGSTASH.PIPELINE_VIEWER.GRAPH.EDGES.SVG_CLASS; - } } diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/edge_factory.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/edge_factory.js index d4a5fc7e5f12d..7ddf7cd4f235c 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/edge_factory.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/edge_factory.js @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PlainEdge } from './plain_edge'; +import { Edge } from './edge'; import { BooleanEdge } from './boolean_edge'; export function edgeFactory(graph, edgeJson) { const type = edgeJson.type; switch (type) { case 'plain': - return new PlainEdge(graph, edgeJson); + return new Edge(graph, edgeJson); case 'boolean': return new BooleanEdge(graph, edgeJson); default: diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/if_vertex.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/if_vertex.js index 39d76ae6f882c..e721d43a7e97c 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/if_vertex.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/if_vertex.js @@ -26,14 +26,7 @@ export class IfVertex extends Vertex { } get subtitle() { - return { - complete: this.name, - display: this.truncateStringForDisplay(this.name, this.displaySubtitleMaxLength) - }; - } - - get displaySubtitleMaxLength() { - return 39; + return this.name; } get trueEdge() { diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/index.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/index.js index 6583a405dfc48..8c6a5c0ea5854 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/index.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/index.js @@ -7,7 +7,6 @@ import { vertexFactory } from './vertex_factory'; import { edgeFactory } from './edge_factory'; import { QueueVertex } from './queue_vertex'; -import { IfVertex } from './if_vertex'; import { PluginVertex } from './plugin_vertex'; export class Graph { @@ -24,9 +23,6 @@ export class Graph { } getVertices() { - // We need a stable order for webcola - // constraints don't work by anything other than index :( - // Its safe to cache vertices because vertices are never added or removed from the graph. This is because // such changes also result in changing the hash of the pipeline, which ends up creating a new graph altogether. if (this.vertexCache === undefined) { @@ -35,10 +31,6 @@ export class Graph { return this.vertexCache; } - get inputVertices() { - return this.getVertices().filter(v => v.isInput); - } - get queueVertex() { return this.getVertices().find(v => v instanceof QueueVertex); } @@ -47,26 +39,10 @@ export class Graph { return this.getVertices().filter(v => v.isProcessor); } - get outputVertices() { - return this.getVertices().filter(v => v.isOutput); - } - - get ifVertices() { - return this.getVertices().filter(v => v instanceof IfVertex); - } - - get colaVertices() { - return this.getVertices().map(v => v.cola); - } - get edges() { return Object.values(this.edgesById); } - get colaEdges() { - return this.edges.map(e => e.cola); - } - update(jsonRepresentation) { this.json = jsonRepresentation; @@ -99,199 +75,9 @@ export class Graph { } }); - // These maps are what the vertices use for their .rank and .reverseRank getters - this.vertexRankById = this._bfs().distances; - - // A separate rank algorithm used for formatting purposes - this.verticesByLayoutRank = this.calculateVerticesByLayoutRank(); - - // For layout purposes we treat triangular ifs, that is to say - // 'if' vertices of rank N with both T and F children at rank N+1 - // in special ways to get a clean render. - this.triangularIfGroups = this.calculateTriangularIfGroups(); - this.annotateVerticesWithStages(); } - verticesByRank() { - const byRank = []; - Object.values(this.verticesById).forEach(vertex => { - const rank = vertex.rank; - if (byRank[rank] === undefined) { - byRank[rank] = []; - } - byRank[rank].push(vertex); - }); - return byRank; - } - - // Can only be run after layout ranks are calculated! - calculateTriangularIfGroups() { - return this.getVertices().filter(v => { - return v.typeString === 'if' && - !v.outgoingVertices.find(outV => outV.layoutRank !== (v.layoutRank + 1)); - }).map(ifV => { - const trueEdge = ifV.outgoingEdges.filter(e => e.when === true)[0]; - const falseEdge = ifV.outgoingEdges.filter(e => e.when === false)[0]; - const result = { ifVertex: ifV }; - if (trueEdge) { - result.trueVertex = trueEdge.to; - } - if (falseEdge) { - result.falseVertex = falseEdge.to; - } - return result; - }); - } - - calculateVerticesByLayoutRank() { - // We will mutate this throughout this function - // to produce our output - const result = this.verticesByRank(); - - // Find the rank of a vertex in our output - // Normally you'd grab that information from `vertex.layoutRank` - // but since we're recomputing that here we need something directly linked - // to the intermediate result - const rankOf = (vertex) => { - const foundRankVertices = result.find((rankVertices) => { - return rankVertices.find(v => v === vertex); - }); - return result.indexOf(foundRankVertices); - }; - - // This function is really an engine for applying rules - // These rules are evaluated in order. Each rule can produce one 'promotion', that is it - // can specify that a single vertex of rank N be promoted to rank N+1 - // These rules will be repeatedly invoked on a rank's vertices until the rule has no effect - // which is determined by the rule returning `null` - const promotionRules = [ - // Our first rule is that vertices that are pointed to by other nodes within the rank, but do - // not point to other nodes within the rank should be promoted - // This produces a more desirable layout by mostly eliminating horizontal links, which must - // cross over other links thus creating a confusing layout most of the time. - (vertices) => { - const found = vertices.find((v) => { - const hasIncomingOfSameRank = v.incomingVertices.find(inV => rankOf(inV) === rankOf(v)); - const hasOutgoingOfSameRank = v.outgoingVertices.find(outV => rankOf(outV) === rankOf(v)); - return hasIncomingOfSameRank && hasOutgoingOfSameRank === undefined; - }); - if (found) { - return found; - } - - return null; - }, - // This rule is quite simple, simply limiting the maximum number of nodes in a rank to 3. - // Beyond this number the graph becomes too compact, links often start crossing over each other - // and readability suffers - (vertices) => { - if (vertices.length > 3) { - return vertices[0]; - } - return null; - } - ]; - - // This is the core of this function, wherein we iterate through the ranks and apply the rules - for (let rank = 0; rank < result.length; rank++) { - const vertices = result[rank]; - // Iterate through each rule - promotionRules.forEach(rule => { - let ruleConverged = false; - // Execute each rule against the vertices within the rank until the rule has no more - // mutations to make - while(!ruleConverged) { - const promotedVertex = rule(vertices, result); - // If the rule has found a vertex to promote - if (promotedVertex !== null) { - const promotedIndex = vertices.indexOf(promotedVertex); - // move the vertex found by the rule from this rank and move it to the next one - vertices.splice(promotedIndex, 1)[0]; - // We may be making a new rank, if so we'll need to seed it with an empty array - if (result[rank + 1] === undefined) { - result[rank + 1] = []; - } - result[rank + 1].push(promotedVertex); - } else { - ruleConverged = true; - } - } - }); - } - - // Set separated rank as a property on each vertex - for (let rank = 0; rank < result.length; rank++) { - const rankVertices = result[rank]; - rankVertices.forEach(v => v.layoutRank = rank); - } - - return result; - } - - get roots() { - return this.getVertices().filter((v) => v.isRoot); - } - - get leaves() { - return this.getVertices().filter((v) => v.isLeaf); - } - - get maxRank() { - return Math.max.apply(null, this.getVertices().map(v => v.rank)); - } - - _getReverseVerticesByRank() { - return this.getVertices().reduce((acc, v) => { - const rank = v.reverseRank; - if (acc.get(rank) === undefined) { - acc.set(rank, []); - } - acc.get(rank).push(v); - return acc; - }, new Map()); - } - - _bfs() { - return this._bfsTraversalUsing(this.roots, 'outgoing'); - } - - _reverseBfs() { - return this._bfsTraversalUsing(this.leaves, 'incoming'); - } - - /** - * Performs a breadth-first or reverse-breadth-first search - * @param {array} startingVertices Where to start the search - either this.roots (for breadth-first) or this.leaves (for reverse-breadth-first) - * @param {string} vertexType Either 'outgoing' (for breadth-first) or 'incoming' (for reverse-breadth-first) - */ - _bfsTraversalUsing(startingVertices, vertexType) { - const distances = {}; - const parents = {}; - const queue = []; - const vertexTypePropertyName = `${vertexType}Vertices`; - - startingVertices.forEach((v) => { - distances[v.id] = 0; - queue.push(v); - }); - while (queue.length > 0) { - const currentVertex = queue.shift(); - const currentDistance = distances[currentVertex.id]; - - currentVertex[vertexTypePropertyName].forEach((vertex) => { - if (distances[vertex.id] === undefined) { - distances[vertex.id] = currentDistance + 1; - parents[vertex.id] = currentVertex; - queue.push(vertex); - } - }); - } - - return { distances, parents }; - } - - get startVertices() { return this.getVertices().filter(v => v.incomingEdges.length === 0); } diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/plain_edge.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/plain_edge.js deleted file mode 100644 index f70e734e6e108..0000000000000 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/plain_edge.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Edge } from './edge'; - -export class PlainEdge extends Edge { - get svgClass() { - return `${super.svgClass} ${super.svgClass}Plain`; - } -} diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/vertex.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/vertex.js index feb80611e64de..82189f697e62c 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/vertex.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/vertex.js @@ -4,43 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LOGSTASH } from '../../../../../../common/constants'; - export class Vertex { constructor(graph, json) { this.graph = graph; this.update(json); - - // Version of the representation used by webcola - // this object is a bridge back to here, and also can be mutated by webcola - // and d3, which like to change objects - this.cola = this._makeCola(); } update(json) { this.json = json; } - // Should only be called by the constructor! - // There is no reason to have > 1 instance of this! - // There is really no good reason to add any additional fields here - _makeCola() { - const margin = LOGSTASH.PIPELINE_VIEWER.GRAPH.VERTICES.MARGIN_PX; - return { - vertex: this, - // The margin size must be added since this is actually the size of the bounding box - width: LOGSTASH.PIPELINE_VIEWER.GRAPH.VERTICES.WIDTH_PX + margin, - height: LOGSTASH.PIPELINE_VIEWER.GRAPH.VERTICES.HEIGHT_PX + margin - }; - } - - get colaIndex() { - if (!this._colaIndex) { - this._colaIndex = this.graph.getVertices().indexOf(this); - } - return this._colaIndex; - } - get name() { return this.json.config_name; } @@ -49,21 +22,8 @@ export class Vertex { return this.json.id; } - get htmlAttrId() { - // Substitute any non-word characters with an underscore so - // D3 selections don't interpret them as special selector syntax - return this.json.id.replace(/\W/g, '_'); - } - get subtitle() { - return { - complete: this.id, - display: this.truncateStringForDisplay(this.id, this.displaySubtitleMaxLength) - }; - } - - get displaySubtitleMaxLength() { - return 19; + return this.id; } get incomingEdges() { @@ -82,26 +42,6 @@ export class Vertex { return this.outgoingEdges.map(e => e.to); } - get isRoot() { - return this.incomingVertices.length === 0; - } - - get isLeaf() { - return this.outgoingVertices.length === 0; - } - - get rank() { - return this.graph.vertexRankById[this.id]; - } - - get sourceLocation() { - return `apc.conf@${this.meta.source_line}:${this.meta.source_column}`; - } - - get sourceText() { - return this.meta.source_text; - } - get meta() { return this.json.meta; } @@ -110,53 +50,6 @@ export class Vertex { return this.json.stats || {}; } - get hasCustomStats() { - return Object.keys(this.customStats).length > 0; - } - get customStats() { - return Object.keys(this.stats) - .filter(k => !(k.match(/^events\./))) - .filter(k => k !== 'name') - .reduce((acc, k) => { - acc[k] = this.stats[k]; - return acc; - }, {}); - } - - lineage() { - const ancestors = this.ancestors(); - const descendants = this.descendants(); - - const vertices = []; - vertices.push.apply(vertices, ancestors.vertices); - vertices.push(this); - vertices.push.apply(vertices, descendants.vertices); - - const edges = ancestors.edges.concat(descendants.edges); - - return { vertices, edges }; - } - - ancestors() { - const vertices = []; - const edges = []; - const pending = [this]; - const seen = {}; - while (pending.length > 0) { - const vertex = pending.pop(); - vertex.incomingEdges.forEach(edge => { - edges.push(edge); - const from = edge.from; - if (seen[from.id] !== true) { - vertices.push(from); - pending.push(from); - seen[from.id] = true; - } - }); - } - return { vertices, edges }; - } - descendants() { const vertices = []; const edges = []; @@ -177,26 +70,7 @@ export class Vertex { return { vertices, edges }; } - get eventsPerCurrentPeriod() { - if (!this.stats.hasOwnProperty('events.in')) { - return null; - } - - return (this.stats['events.in'].max - this.stats['events.in'].min); - } - get hasExplicitId() { return Boolean(this.json.explicit_id); } - - truncateStringForDisplay(completeString, maxDisplayLength) { - if (completeString.length <= maxDisplayLength) { - return completeString; - } - - const ellipses = ' \u2026 '; - const eachHalfMaxDisplayLength = Math.floor((maxDisplayLength - ellipses.length) / 2); - - return `${completeString.substr(0, eachHalfMaxDisplayLength)}${ellipses}${completeString.substr(-eachHalfMaxDisplayLength)}`; - } } diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/__test__/detail_drawer.test.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/__test__/detail_drawer.test.js index 74bf9b36e65f2..81e0b9b2e1f32 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/__test__/detail_drawer.test.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/__test__/detail_drawer.test.js @@ -167,9 +167,7 @@ describe('DetailDrawer component', () => { const vertex = { title: 'if', typeString: 'if', - subtitle: { - complete: '[type] == "apache_log"' - } + subtitle: '[type] == "apache_log"' }; const component = ( diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/cola_graph.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/cola_graph.js deleted file mode 100644 index d9aed7251e585..0000000000000 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/cola_graph.js +++ /dev/null @@ -1,470 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import d3 from 'd3'; -import { PluginVertex } from '../models/graph/plugin_vertex'; -import { IfVertex } from '../models/graph/if_vertex'; -import { QueueVertex } from '../models/graph/queue_vertex'; -import { - enterInputVertex, - enterProcessorVertex, - enterIfVertex, - enterQueueVertex, - updateInputVertex, - updateProcessorVertex -} from './vertex_content_renderer'; -import { LOGSTASH } from '../../../../../common/constants'; -import { makeEdgeBetween, d3adaptor } from 'webcola'; - -function makeMarker(svgDefs, id, fill) { - svgDefs.append('marker') - .attr('id', id) - .attr('viewBox', '0 -5 10 10') - .attr('refX', 5) - .attr('markerWidth', 3) - .attr('markerHeight', 3) - .attr('orient', 'auto') - .append('path') - .attr('d', 'M0,-5L10,0L0,5L2,0') - .attr('stroke-width', '0px') - .attr('fill', fill); -} - -function makeBackground(parentEl) { - return parentEl - .append('rect') - .attr('width', '100%') - .attr('height', '100%') - .attr('fill', '#efefef'); -} - -function makeGroup(parentEl) { - return parentEl - .append('g'); -} - -function makeNodes(nodesLayer, colaVertices) { - const nodes = nodesLayer - .selectAll('.lspvVertex') - .data(colaVertices, d => d.vertex.htmlAttrId); - - nodes - .enter() - .append('g') - .attr('id', d => `nodeg-${d.vertex.htmlAttrId}`) - .attr('class', d => `lspvVertex ${d.vertex.typeString}`) - .attr('width', LOGSTASH.PIPELINE_VIEWER.GRAPH.VERTICES.WIDTH_PX) - .attr('height', LOGSTASH.PIPELINE_VIEWER.GRAPH.VERTICES.HEIGHT_PX); - - nodes - .append('rect') - .attr('class', 'lspvVertexBounding') - .attr('rx', LOGSTASH.PIPELINE_VIEWER.GRAPH.VERTICES.BORDER_RADIUS_PX) - .attr('ry', LOGSTASH.PIPELINE_VIEWER.GRAPH.VERTICES.BORDER_RADIUS_PX); - - return nodes; -} - -function addNodesMouseBehaviors(nodes, onMouseover, onMouseout, onMouseclick) { - nodes.on('mouseover', onMouseover); - nodes.on('mouseout', onMouseout); - nodes.on('click', onMouseclick); -} - -function makeInputNodes(nodes) { - const inputs = nodes.filter(node => (node.vertex instanceof PluginVertex) && node.vertex.isInput); - inputs.call(enterInputVertex); - - return inputs; -} - -function makeProcessorNodes(nodes) { - const processors = nodes.filter(node => (node.vertex instanceof PluginVertex) && node.vertex.isProcessor); - processors.call(enterProcessorVertex); - - return processors; -} - -function makeIfNodes(nodes) { - const ifs = nodes.filter(d => d.vertex instanceof IfVertex); - ifs.call(enterIfVertex); - - return ifs; -} - -function makeQueueNode(nodes) { - const queue = nodes.filter(d => d.vertex instanceof QueueVertex); - queue.call(enterQueueVertex); - - return queue; -} - -// Line function for drawing paths between nodes -const lineFunction = d3.svg.line() - // Null check that handles a bug in webcola where sometimes these values are null for a tick - .x(d => d ? d.x : null) - .y(d => d ? d.y : null); - -export class ColaGraph extends React.Component { - constructor() { - super(); - this.state = {}; - - this.width = 1000; - this.height = 1000; - } - - renderGraph(svgEl) { - this.d3cola = d3adaptor() - .avoidOverlaps(true) - .size([this.width, this.height]); - - const outer = d3.select(svgEl); - const background = makeBackground(outer); - - const svgDefs = outer.append('defs'); - makeMarker(svgDefs, 'lspvPlainMarker', '#000'); - makeMarker(svgDefs, 'lspvTrueMarker', '#1BAFD2'); - makeMarker(svgDefs, 'lspvFalseMarker', '#EE408A'); - - // Set initial zoom to 100%. You need both the translate and scale options - const zoom = d3.behavior.zoom().translate([100, 100]).scale(1); - const vis = outer - .append('g') - .attr('transform', 'translate(0,0) scale(1)'); - - const redraw = () => { - vis.attr('transform', `translate(${d3.event.translate}) scale(${d3.event.scale})`); - }; - - outer.call(d3.behavior.zoom().on('zoom', redraw)); - background.call(zoom.on('zoom', redraw)); - - this.nodesLayer = makeGroup(vis); - this.nodes = makeNodes(this.nodesLayer, this.graph.colaVertices); - - this.inputs = makeInputNodes(this.nodes); - this.processors = makeProcessorNodes(this.nodes); - this.ifs = makeIfNodes(this.nodes); - this.queue = makeQueueNode(this.nodes); - - addNodesMouseBehaviors(this.nodes, this.onMouseover, this.onMouseout, this.onMouseclick); - - this.linksLayer = makeGroup(vis); - - const ifTriangleColaGroups = this.graph.triangularIfGroups.map(group => { - return { leaves: Object.values(group).map(v => v.colaIndex) }; - }); - - this.d3cola - .nodes(this.graph.colaVertices) - .links(this.graph.colaEdges) - .groups(ifTriangleColaGroups) - .constraints(this._getConstraints()) - // This number controls the max number of iterations for the layout iteration to - // solve the constraints. Higher numbers usually wind up in a better layout - .start(10000); - - this.makeLinks(); - - let tickStart; - let ticks = 0; - - // Minimum amount of time between reflows - // We want a value that looks interactive but doesn't waste CPU time rendering intermediate results - const reflowEvery = 1000; // 1s - // Amount of time to allow the solver to run - // We want a value that isn't so long that the user gets irritated using the graph due to the CPU being monopolized - // by the constraint solver - const maxDuration = 10000; // 10s - let lastReflow = new Date(); - this.d3cola - .on('tick', () => { - const now = new Date(); - - ticks++; - if (ticks === 1) { - tickStart = now; - } - - const elapsedSinceLastReflow = now - lastReflow; - if (ticks === 1 || elapsedSinceLastReflow >= reflowEvery) { - this.reflow(); - lastReflow = now; - } - const totalElapsed = now - tickStart; - if (totalElapsed >= maxDuration) { - this.d3cola.stop(); - this.reflow(); - console.log("Logstash graph visualizer constraint timeout! Rendering will stop here."); - } - }) - .on('end', this.reflow); - } - - // Actually render the latest webcola state - reflow = () => { - this.setNodeBounds(); - this.routeAndLabelEdges(); - } - - setNodeBounds = () => { - this.nodes.each((d) => d.innerBounds = d.bounds.inflate(-LOGSTASH.PIPELINE_VIEWER.GRAPH.VERTICES.MARGIN_PX)); - this.nodes.attr('transform', (d) => `translate(${d.innerBounds.x}, ${d.innerBounds.y})`); - this.nodes.select('rect') - .attr('width', (d) => d.innerBounds.width()) - .attr('height', (d) => d.innerBounds.height()); - } - - routeAndLabelEdges = () => { - this.links.attr('d', (d) => { - const arrowStart = LOGSTASH.PIPELINE_VIEWER.GRAPH.EDGES.ARROW_START; - const route = makeEdgeBetween(d.source.innerBounds, d.target.innerBounds, arrowStart); - return lineFunction([route.sourceIntersection, route.arrowStart]); - }); - this.routeEdges(); - this.labelEdges(); - } - - routeEdges() { - this.d3cola.prepareEdgeRouting(LOGSTASH.PIPELINE_VIEWER.GRAPH.EDGES.ROUTING_MARGIN_PX); - this.links.select('path').attr('d', (d) => { - try { - return lineFunction(this.d3cola.routeEdge(d)); - } catch (err) { - console.error('Could not exec line function!', err); - } - }); - } - - labelEdges() { - // Use a regular function instead of () => since we want the dom element via `this`, - // only accessible via d3 setting 'this' AFAIK - this.booleanLabels.each(function () { - const path = d3.select(this.parentNode).select('path')[0][0]; - const pathLength = path.getTotalLength(); - if (pathLength === 0) { - return; - } - - const center = path.getPointAtLength(pathLength / 2); - const group = d3.select(this); - group.select('circle') - .attr('cx', center.x) - .attr('cy', center.y); - - // Offset by to vertically center the text - const textVerticalOffset = 5; - group.select('text') - .attr('x', center.x) - .attr('y', center.y + textVerticalOffset); - }); - } - - makeLinks() { - this.links = this.linksLayer.selectAll('.link') - .data(this.graph.colaEdges); - - const linkGroup = this.links.enter() - .append('g') - .attr('id', (d) => `lspvEdge-${d.edge.htmlAttrId}`) - .attr('class', (d) => d.edge.svgClass); - linkGroup.append('path'); - - const booleanLinks = linkGroup.filter('.lspvEdgeBoolean'); - this.booleanLabels = booleanLinks - .append('g') - .attr('class', 'lspvBooleanLabel'); - - this.booleanLabels - .append('circle') - .attr('r', LOGSTASH.PIPELINE_VIEWER.GRAPH.EDGES.LABEL_RADIUS); - this.booleanLabels - .append('text') - .attr('text-anchor', 'middle') // Position the text on its vertical - .text(d => d.edge.when ? 'T' : 'F'); - } - - updateGraph(nextProps = {}, nextState = {}) { - this.processors.call(updateProcessorVertex); - this.inputs.call(updateInputVertex); - - this.nodesLayer.selectAll('.lspvVertexBounding-highlighted').classed('lspvVertexBounding-highlighted', false); - this.nodesLayer.selectAll('.lspvVertex-grayed').classed('lspvVertex-grayed', false); - this.linksLayer.selectAll('.lspvEdge-grayed').classed('lspvEdge-grayed', false); - - const hoverNode = nextState.hoverNode; - if (hoverNode) { - const selection = this.nodesLayer - .selectAll('#nodeg-' + hoverNode.vertex.htmlAttrId) - .selectAll('rect'); - selection.classed('lspvVertexBounding-highlighted', true); - - const lineage = hoverNode.vertex.lineage(); - - const lineageVertices = lineage.vertices; - const nonLineageVertices = this.graph.getVertices().filter(v => lineageVertices.indexOf(v) === -1); - const grayedVertices = this.nodesLayer.selectAll('g.lspvVertex').filter(d => nonLineageVertices.indexOf(d.vertex) >= 0); - grayedVertices.classed('lspvVertex-grayed', true); - - const lineageEdges = lineage.edges; - const nonLineageEdges = this.graph.edges.filter(e => lineageEdges.indexOf(e) === -1); - const grayedEdges = this.linksLayer.selectAll('.lspvEdge').filter(d => nonLineageEdges.indexOf(d.edge) >= 0); - grayedEdges.classed('lspvEdge-grayed', true); - } - - const detailVertex = nextProps.detailVertex; - if (detailVertex) { - const selection = this.nodesLayer - .selectAll('#nodeg-' + detailVertex.htmlAttrId) - .selectAll('rect'); - selection.classed('lspvVertexBounding-highlighted', true); - } - } - - onMouseover = (node) => { - this.setState({ hoverNode: node }); - } - - onMouseout = () => { - this.setState({ hoverNode: null }); - } - - onMouseclick = (e) => { - this.props.onShowVertexDetails(e.vertex); - } - - get graph() { - return this.props.graph; - } - - _getConstraints() { - // To understand webcola constraints please read: - // https://github.com/tgdwyer/WebCola/wiki/Constraints - const constraints = []; - const verticesByRank = this.graph.verticesByLayoutRank; - - // Lay out triangle groups as... a triangle! That is to say, - // with an if in the middle and the true on the left and the false on the right - this.graph.triangularIfGroups.forEach(group => { - if (group.trueVertex && group.falseVertex) { - Object.values(group).forEach(v => v.isInTriangleGroup = true); - constraints.push({ - type: 'alignment', - axis: 'x', - offsets: [ - { node: group.ifVertex.colaIndex, offset: 0 }, - // The offsets here are oddly sensitive. If you use lower values than the width of - // the node the layout gets all crazy and overlappy for reasons I don't understand - { node: group.trueVertex.colaIndex, offset: -LOGSTASH.PIPELINE_VIEWER.GRAPH.VERTICES.WIDTH_PX }, - { node: group.falseVertex.colaIndex, offset: LOGSTASH.PIPELINE_VIEWER.GRAPH.VERTICES.WIDTH_PX } - ] - }); - } - }); - - for (let rank = 0; rank < verticesByRank.length; rank++) { - const vertices = verticesByRank[rank]; - - // Ensure that nodes of an equal rank are aligned on the y axis. - constraints.push( - { - type: 'alignment', - axis: 'y', - offsets: vertices.map(v => { - return { node: v.colaIndex, offset: 0 }; - }) - } - ); - - if (rank > 0) { - const previousVertices = verticesByRank[rank - 1]; - - // Prevent sibling nodes from overlapping - vertices.forEach((vertex, index) => { - const previousParents = previousVertices.filter(previousVertex => { - return previousVertex.outgoingVertices.find(v => v === vertex); - }); - - const nodeXGap = LOGSTASH.PIPELINE_VIEWER.GRAPH.VERTICES.WIDTH_PX + LOGSTASH.PIPELINE_VIEWER.GRAPH.VERTICES.MARGIN_PX; - const rightSibling = vertices[index + 1]; - // We don't need to add constraints for nodes in triangle groups since they have - // a constraint that keeps them separately already - if (rightSibling && !rightSibling.isInTriangleGroup && !vertex.isInTriangleGroup) { - constraints.push({ - axis: "x", - right: vertex.colaIndex, - left: rightSibling.colaIndex, - gap: nodeXGap - }); - } - - // Ensure that nodes of rank N that have a single outbound connection to a node of rank N+1 - // are positioned vertically inline - // We start by checking if the current node has exactly one parent in the previous rank - // if it has > 1 parent then we don't really know where to put it - if (previousParents.length === 1) { - const previousParent = previousParents[0]; - // We further check that the connected parent isn't also connected to other nodes in this rank - // otherwise the nodes would have to overlap if we aligned them - if (previousParent.outgoingVertices.filter(v => v.layoutRank === rank).length === 1) { - constraints.push({ - axis: 'x', - left: previousParent.colaIndex, - right: vertex.colaIndex, - gap: 0, - equality: true - }); - } - } - - // Ensure that all nodes of a given rank are at the same exact distance below others - previousVertices.forEach(previousVertex => { - constraints.push({ - axis: 'y', - left: previousVertex.colaIndex, - right: vertex.colaIndex, - // Multiplying the gap by two works much better for large graphs giving more space to route edges - gap: LOGSTASH.PIPELINE_VIEWER.GRAPH.VERTICES.HEIGHT_PX * 2, - equality: true - }); - }); - }); - } - } - - return constraints; - } - - render() { - const viewBox = `0,0,${this.width},${this.height}`; - return ( - this.renderGraph(svgEl)} - width="100%" - height="100%" - preserveAspectRatio="xMinYMin meet" - viewBox={viewBox} - pointerEvents="all" - /> - ); - } - - componentDidMount() { - this.updateGraph(); - } - - shouldComponentUpdate(nextProps, nextState) { - // Let D3 control updates to this component's DOM. - this.updateGraph(nextProps, nextState); - - // Since D3 is controlling any updates to this component's DOM, - // we don't want React to update this component's DOM. - return false; - } -} diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/detail_drawer.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/detail_drawer.js index 607063ef4bf16..af4b9899c1939 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/detail_drawer.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/detail_drawer.js @@ -210,7 +210,7 @@ function renderPluginBasicInfo(vertex) { } function renderIfBasicInfo(vertex) { - const ifCode = `if (${vertex.subtitle.complete}) { + const ifCode = `if (${vertex.subtitle}) { ... }`; diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/vertex_content_renderer.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/vertex_content_renderer.js deleted file mode 100644 index f31c160c46e1f..0000000000000 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/vertex_content_renderer.js +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import inputIcon from '@elastic/eui/src/components/icon/assets/logstash_input.svg'; -import filterIcon from '@elastic/eui/src/components/icon/assets/logstash_filter.svg'; -import outputIcon from '@elastic/eui/src/components/icon/assets/logstash_output.svg'; -import queueIcon from '@elastic/eui/src/components/icon/assets/logstash_queue.svg'; -import ifIcon from '@elastic/eui/src/components/icon/assets/logstash_if.svg'; -import { PluginVertex } from '../models/graph/plugin_vertex'; -import { IfVertex } from '../models/graph/if_vertex'; -import { LOGSTASH } from '../../../../../common/constants'; -import { formatMetric } from '../../../../lib/format_number'; - -// Each vertex consists of two lines (rows) of text -// - The first line shows the name and ID of the vertex -// - The second line shows stats about the vertex -// There is also an icon denoting the type of vertex - -const BASE_OFFSET_LEFT_PX = 7; -const FIRST_LINE_OFFSET_TOP_PX = 18; -const SECOND_LINE_OFFSET_TOP_PX = FIRST_LINE_OFFSET_TOP_PX + 22; - -const PCT_EXECUTION_OFFSET_LEFT_PX = BASE_OFFSET_LEFT_PX; -const PCT_EXECUTION_OFFSET_TOP_PX = SECOND_LINE_OFFSET_TOP_PX; - -const PCT_EXECUTION_BG_OFFSET_LEFT_PX = PCT_EXECUTION_OFFSET_LEFT_PX - 4; -const PCT_EXECUTION_BG_OFFSET_TOP_PX = PCT_EXECUTION_OFFSET_TOP_PX - 15; -const PCT_EXECUTION_BG_WIDTH_PX = 43; -const PCT_EXECUTION_BG_HEIGHT_PX = 20; -const PCT_EXECUTION_BG_RADIUS_PX = 5; - -const EVENT_DURATION_OFFSET_LEFT_PX = BASE_OFFSET_LEFT_PX + 50; -const EVENT_DURATION_OFFSET_TOP_PX = SECOND_LINE_OFFSET_TOP_PX; - -const EVENT_DURATION_BG_OFFSET_LEFT_PX = EVENT_DURATION_OFFSET_LEFT_PX - 6; -const EVENT_DURATION_BG_OFFSET_TOP_PX = EVENT_DURATION_OFFSET_TOP_PX - 15; -const EVENT_DURATION_BG_WIDTH_PX = 89; -const EVENT_DURATION_BG_HEIGHT_PX = 20; -const EVENT_DURATION_BG_RADIUS_PX = 5; - -const EVENTS_PER_SECOND_OFFSET_LEFT_PX = BASE_OFFSET_LEFT_PX + 136; -const EVENTS_PER_SECOND_OFFSET_TOP_PX = SECOND_LINE_OFFSET_TOP_PX; - -const ICON_OFFSET_LEFT_PX = BASE_OFFSET_LEFT_PX + 258; -const ICON_OFFSET_TOP_PX = FIRST_LINE_OFFSET_TOP_PX + 9; - -function renderHeader(colaObjects, title, subtitle) { - const pluginHeader = colaObjects - .append('text') - .attr('class', 'lspvHeader') - .attr('x', BASE_OFFSET_LEFT_PX) - .attr('y', FIRST_LINE_OFFSET_TOP_PX); - - pluginHeader - .append('tspan') - .attr('class', 'lspvVertexTitle') - .text(title); - - // For plugin vertices, either we have an explicitly-set plugin ID or an - // auto-generated plugin ID. For explicitly-set plugin IDs, show the ID. - pluginHeader - .filter(d => { - const vertex = d.vertex; - return (vertex instanceof PluginVertex && vertex.hasExplicitId) || - (vertex instanceof IfVertex); - }) - .append('tspan') - .attr('class', 'lspvVertexSubtitle') - .text(d => subtitle ? ` (${subtitle(d).display})` : null) - .append('title') - .text(d => subtitle ? subtitle(d).complete : null); -} - -function renderIcon(selection, icon) { - selection - .append('image') - .attr('xlink:href', icon) - .attr('x', ICON_OFFSET_LEFT_PX) - .attr('y', ICON_OFFSET_TOP_PX) - .attr('height', LOGSTASH.PIPELINE_VIEWER.ICON.HEIGHT_PX) - .attr('width', LOGSTASH.PIPELINE_VIEWER.ICON.WIDTH_PX); -} - -export function enterInputVertex(inputs) { - renderHeader( - inputs, - (d => d.vertex.title), - (d => d.vertex.subtitle) - ); - - renderIcon(inputs, inputIcon); - - inputs - .append('text') - .attr('class', 'lspvStat') - .attr('data-lspv-events-per-second', '') - .attr('x', BASE_OFFSET_LEFT_PX) - .attr('y', SECOND_LINE_OFFSET_TOP_PX); -} - -export function enterProcessorVertex(processors) { - renderHeader( - processors, - (d => d.vertex.title), - (d => d.vertex.subtitle) - ); - - processors - .append('rect') - .attr('data-lspv-percent-execution-bg', '') - .attr('x', PCT_EXECUTION_BG_OFFSET_LEFT_PX) - .attr('y', PCT_EXECUTION_BG_OFFSET_TOP_PX) - .attr('width', PCT_EXECUTION_BG_WIDTH_PX) - .attr('height', PCT_EXECUTION_BG_HEIGHT_PX) - .attr('ry', PCT_EXECUTION_BG_RADIUS_PX) - .attr('rx', PCT_EXECUTION_BG_RADIUS_PX) - .attr('fill', 'none'); - - processors - .append('text') - .attr('class', 'lspvStat') - .attr('data-lspv-percent-execution', '') - .attr('x', PCT_EXECUTION_OFFSET_LEFT_PX) - .attr('y', PCT_EXECUTION_OFFSET_TOP_PX); - - processors - .append('rect') - .attr('data-lspv-per-event-duration-in-millis-bg', '') - .attr('x', EVENT_DURATION_BG_OFFSET_LEFT_PX) - .attr('y', EVENT_DURATION_BG_OFFSET_TOP_PX) - .attr('width', EVENT_DURATION_BG_WIDTH_PX) - .attr('height', EVENT_DURATION_BG_HEIGHT_PX) - .attr('ry', EVENT_DURATION_BG_RADIUS_PX) - .attr('rx', EVENT_DURATION_BG_RADIUS_PX) - .attr('fill', 'none'); - - processors - .append('text') - .attr('class', 'lspvStat') - .attr('data-lspv-per-event-duration-in-millis', '') - .attr('x', EVENT_DURATION_OFFSET_LEFT_PX) - .attr('y', EVENT_DURATION_OFFSET_TOP_PX); - - processors - .append('text') - .attr('class', 'lspvStat') - .attr('data-lspv-events-per-second', '') - .attr('x', EVENTS_PER_SECOND_OFFSET_LEFT_PX) - .attr('y', EVENTS_PER_SECOND_OFFSET_TOP_PX); - - renderIcon(processors, d => d.vertex.pluginType === 'filter' ? filterIcon : outputIcon); -} - -export function enterIfVertex(ifs) { - renderHeader( - ifs, - (d => d.vertex.title), - (d => d.vertex.subtitle) - ); - - renderIcon(ifs, ifIcon); -} - -export function enterQueueVertex(queueVertex) { - renderHeader( - queueVertex, - (d => d.vertex.title), - ); - - renderIcon(queueVertex, queueIcon); -} - -export function updateInputVertex(inputs) { - inputs.selectAll('[data-lspv-events-per-second]') - .text(d => formatMetric(d.vertex.latestEventsPerSecond, '0.[00]a', 'e/s emitted')); -} - -export function updateProcessorVertex(processors) { - processors.selectAll('[data-lspv-percent-execution]') - .text(d => { - const pct = d.vertex.percentOfTotalProcessorTime || 0; - return formatMetric(Math.round(pct), '0', '%', { prependSpace: false }); - }); - - processors.selectAll('[data-lspv-percent-execution-bg]') - .attr('fill', d => { - return d.vertex.isTimeConsuming() ? 'orange' : 'none'; - }); - - processors.selectAll('[data-lspv-per-event-duration-in-millis]') - .text(d => formatMetric(d.vertex.latestMillisPerEvent, '0.[00]a', 'ms/e')); - - processors.selectAll('[data-lspv-per-event-duration-in-millis-bg]') - .attr('fill', d => { - return d.vertex.isSlow() ? 'orange' : 'none'; - }); - - processors.selectAll('[data-lspv-events-per-second]') - .text(d => formatMetric(d.vertex.latestEventsPerSecond, '0.[00]a', 'e/s received')); -} diff --git a/x-pack/plugins/monitoring/public/directives/logstash/pipeline_viewer/index.js b/x-pack/plugins/monitoring/public/directives/logstash/pipeline_viewer/index.js index 4886f5c3df411..5c52c2a83ecce 100644 --- a/x-pack/plugins/monitoring/public/directives/logstash/pipeline_viewer/index.js +++ b/x-pack/plugins/monitoring/public/directives/logstash/pipeline_viewer/index.js @@ -8,7 +8,7 @@ import React from 'react'; import { render } from 'react-dom'; import moment from 'moment'; import { uiModules } from 'ui/modules'; -import { ConfigViewer } from 'plugins/monitoring/components/logstash/pipeline_viewer/views/config_viewer'; +import { ConfigViewer } from 'plugins/monitoring/components/logstash/pipeline_viewer'; import { Pipeline } from 'plugins/monitoring/components/logstash/pipeline_viewer/models/pipeline'; import { List } from 'plugins/monitoring/components/logstash/pipeline_viewer/models/list'; import { PipelineState } from 'plugins/monitoring/components/logstash/pipeline_viewer/models/pipeline_state'; diff --git a/x-pack/plugins/monitoring/public/less/components/logstash/pipeline_viewer.less b/x-pack/plugins/monitoring/public/less/components/logstash/pipeline_viewer.less index a54011a912318..fb8229739d5d9 100644 --- a/x-pack/plugins/monitoring/public/less/components/logstash/pipeline_viewer.less +++ b/x-pack/plugins/monitoring/public/less/components/logstash/pipeline_viewer.less @@ -2,106 +2,6 @@ border-bottom: 1px solid #d4d4d4; } -.lspvContainer { - // Same color as the sidebar. Needed to fill in the Y axis of the sidebar - // TODO: Do we have a color lookup variable that should be used here? - background-color: #F6F6F6; - cursor: grab; -} - -.lspvVertexTitle { - fill: black; - font-family: "Open Sans", "Lato", "Helvetica Neue", Helvetica, Arial; - font-weight: bold; - font-size: 13px; - stroke: none; -} - -.lspvVertexSubtitle { - fill: black; - font-family: "Open Sans", "Lato", "Helvetica Neue", Helvetica, Arial; - font-weight: normal; - font-size: 13px; - stroke: none; -} - -.lspvHeader { - fill: black; - font-family: "Open Sans", "Lato", "Helvetica Neue", Helvetica, Arial; - font-size: 13px; - stroke: none; -} - -.lspvVertex { - cursor: pointer; - - &.lspvVertex-grayed { - opacity: 0.3; - } -} - -.lspvVertexBounding { - fill: #fff; - stroke: #d4d4d4; - stroke-width: 1px; - vector-effect: non-scaling-stroke; - - &.lspvVertexBounding-highlighted { - stroke: #6badbf; - } -} - -.lspvStat { - fill: black; - font-family: "Open Sans", "Lato", "Helvetica Neue", Helvetica, Arial; - font-weight: normal; - font-size: 13px; - stroke: none; -} - -.lspvEdge { - stroke: #000; - stroke-width: 2px; - stroke-linejoin: round; - opacity: 0.5; - marker-end: url(#lspvPlainMarker); - fill: none; - - &.lspvEdge-grayed { - opacity: 0.1; - } -} - -.lspvEdgeBoolean { - text { - font-size: 14px; - font-weight: bold; - fill: #fff; - stroke: none; - } -} - -@trueBooleanFill: #1BAFD2; -@falseBooleanFill: #EE408A; - -.lspvEdgeBoolean--true { - stroke: @trueBooleanFill; - marker-end: url(#lspvTrueMarker); - - circle { - fill: @trueBooleanFill; - } -} - -.lspvEdgeBoolean--false { - stroke: @falseBooleanFill; - marker-end: url(#lspvFalseMarker); - - circle { - fill: @falseBooleanFill; - } -} - img.lspvDetailDrawerIcon { display: inline; margin: 0 5px 0 0;