diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/__test__/__snapshots__/detail_drawer.test.js.snap b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/__test__/__snapshots__/detail_drawer.test.js.snap
index a332f27d9062..337b7738fd92 100644
--- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/__test__/__snapshots__/detail_drawer.test.js.snap
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/__test__/__snapshots__/detail_drawer.test.js.snap
@@ -7,14 +7,24 @@ exports[`DetailDrawer component If vertices shows basic info and no stats for if
>
+
+
+
-
if
@@ -38,7 +43,7 @@ exports[`DetailDrawer component If vertices shows basic info and no stats for if
>
+
+
+
-
grok filter
@@ -109,7 +119,7 @@ exports[`DetailDrawer component Plugin vertices Plugin does not have explicit ID
>
+
+
+
-
grok filter
@@ -402,7 +417,7 @@ exports[`DetailDrawer component Plugin vertices Plugin has explicit ID shows bas
>
+
parse_apache_logline
-
+
.
+
+
+
-
queue
@@ -688,7 +711,7 @@ exports[`DetailDrawer component Queue vertices shows basic info and no stats for
>
+
+
+
-
-
-
+
+
+ {name}
+
+
+ );
+}
+
+function renderIfStatement({ condition }, onVertexSelected) {
+ return [
+ renderStatementName('if', onVertexSelected),
+ (
+
+
+ {condition}
+
+
+ )
+ ];
+}
+
+function getStatementBody({
+ isIf,
+ statement,
+ statement: { vertex },
+ onShowVertexDetails
+}) {
+ const showVertexDetailsClicked = () => { onShowVertexDetails(vertex); };
+
+ return isIf
+ ? renderIfStatement(statement, showVertexDetailsClicked)
+ : renderStatementName('else', showVertexDetailsClicked);
+}
+
+function getToggleIconType(isCollapsed) {
+ return isCollapsed ? 'arrowRight' : 'arrowDown';
+}
+
+export function CollapsibleStatement(props) {
+ const {
+ collapse,
+ expand,
+ id,
+ isCollapsed
+ } = props;
+
+ const toggleClicked = () => {
+ if (isCollapsed) {
+ expand(id);
+ } else {
+ collapse(id);
+ }
+ };
+
+ return (
+
+
+
+
+ {getStatementBody(props)}
+
+ );
+}
+
+CollapsibleStatement.propTypes = {
+ collapse: PropTypes.func.isRequired,
+ expand: PropTypes.func.isRequired,
+ id: PropTypes.string.isRequired,
+ isIf: PropTypes.bool.isRequired,
+ isCollapsed: PropTypes.bool.isRequired,
+ onShowVertexDetails: PropTypes.func.isRequired,
+ statement: PropTypes.object.isRequired,
+};
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/config_viewer.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/config_viewer.js
new file mode 100644
index 000000000000..417f76da5f08
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/config_viewer.js
@@ -0,0 +1,113 @@
+/*
+ * 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 PropTypes from 'prop-types';
+import { DetailDrawer } from '../detail_drawer';
+import { Queue } from './queue';
+import { StatementSection } from './statement_section';
+import {
+ EuiSpacer,
+ EuiPage,
+ EuiPageContent,
+} from '@elastic/eui';
+
+export class ConfigViewer 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 {
+ inputs,
+ filters,
+ outputs,
+ queue
+ } = this.props.pipeline;
+
+ return (
+
+
+
+
+
+
+
+
+
+ { this.renderDetailDrawer() }
+
+
+ );
+ }
+}
+
+ConfigViewer.propTypes = {
+ pipeline: PropTypes.shape({
+ inputs: PropTypes.array.isRequired,
+ filters: PropTypes.array.isRequired,
+ outputs: PropTypes.array.isRequired,
+ queue: PropTypes.object.isRequired,
+ }).isRequired
+};
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/index.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/index.js
new file mode 100644
index 000000000000..4ac6722ff5c8
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/index.js
@@ -0,0 +1,7 @@
+/*
+ * 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.
+ */
+
+export { ConfigViewer } from './config_viewer';
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/metric.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/metric.js
new file mode 100644
index 000000000000..feb74298458f
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/metric.js
@@ -0,0 +1,52 @@
+/*
+ * 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 PropTypes from 'prop-types';
+import {
+ EuiFlexItem,
+ EuiBadge,
+ EuiText,
+} from '@elastic/eui';
+import classNames from 'classnames';
+
+export function Metric({ className, value, warning }) {
+
+ const classes = classNames(
+ 'configViewer__metric',
+ className,
+ );
+
+ let stylizedValue;
+ if (warning) {
+ stylizedValue = (
+
+ {value}
+
+ );
+ } else {
+ stylizedValue = (
+
+
+ {value}
+
+
+ );
+ }
+ return (
+
+ {stylizedValue}
+
+ );
+}
+
+Metric.propTypes = {
+ className: PropTypes.string.isRequired,
+ value: PropTypes.string.isRequired,
+};
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/plugin_statement.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/plugin_statement.js
new file mode 100644
index 000000000000..1c89e2c17f61
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/plugin_statement.js
@@ -0,0 +1,142 @@
+/*
+ * 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 PropTypes from 'prop-types';
+import {
+ EuiButtonEmpty,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiBadge,
+} from '@elastic/eui';
+import { formatMetric } from '../../../../../lib/format_number';
+import { Metric } from './metric';
+
+function getInputStatementMetrics({ latestEventsPerSecond }) {
+ return [(
+
+ )];
+}
+
+function getProcessorStatementMetrics(processorVertex) {
+ const {
+ latestMillisPerEvent,
+ latestEventsPerSecond,
+ percentOfTotalProcessorTime,
+ } = processorVertex;
+
+ return [
+ (
+
+ ),
+ (
+
+ ),
+ (
+
+ )
+ ];
+}
+
+function renderPluginStatementMetrics(pluginType, vertex) {
+ return pluginType === 'input'
+ ? getInputStatementMetrics(vertex)
+ : getProcessorStatementMetrics(vertex);
+}
+
+export function PluginStatement({
+ statement: {
+ hasExplicitId,
+ id,
+ name,
+ pluginType,
+ vertex
+ },
+ onShowVertexDetails
+}) {
+ const statementMetrics = renderPluginStatementMetrics(pluginType, vertex);
+ const onNameButtonClick = () => { onShowVertexDetails(vertex); };
+
+ return (
+
+
+
+
+
+ {name}
+
+
+ {
+ hasExplicitId &&
+
+
+ {id}
+
+
+ }
+
+
+ {
+ statementMetrics &&
+
+
+ {statementMetrics}
+
+
+ }
+
+ );
+}
+
+PluginStatement.propTypes = {
+ statement: PropTypes.shape({
+ hasExplicitId: PropTypes.bool.isRequired,
+ id: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ pluginType: PropTypes.string.isRequired,
+ vertex: PropTypes.object.isRequired,
+ }).isRequired,
+ onShowVertexDetails: PropTypes.func.isRequired,
+};
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/queue.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/queue.js
new file mode 100644
index 000000000000..8dc5971857c6
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/queue.js
@@ -0,0 +1,24 @@
+/*
+ * 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 { StatementListHeading } from './statement_list_heading';
+import { EuiSpacer, EuiText } from '@elastic/eui';
+
+export function Queue() {
+ return (
+
+
+
+
+ Queue metrics not available
+
+
+ );
+}
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/statement.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/statement.js
new file mode 100644
index 000000000000..545c7934db8e
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/statement.js
@@ -0,0 +1,78 @@
+/*
+ * 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 PropTypes from 'prop-types';
+import { PluginStatement as PluginStatementModel } from '../../models/pipeline/plugin_statement';
+import { CollapsibleStatement } from './collapsible_statement';
+import { IfElement } from '../../models/list/if_element';
+import { PluginStatement } from './plugin_statement';
+
+function renderNestingSpacers(depth) {
+ const spacers = [];
+ for (let i = 0; i < depth; i += 1) {
+ spacers.push();
+ }
+ return spacers;
+}
+
+function renderStatement({
+ collapse,
+ element,
+ element: {
+ id,
+ statement,
+ },
+ expand,
+ isCollapsed,
+ onShowVertexDetails
+}) {
+ if (statement instanceof PluginStatementModel) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
+
+export function Statement(props) {
+ const { depth } = props.element;
+
+ return (
+
+
+ {renderNestingSpacers(depth)}
+
+ {renderStatement(props)}
+
+ );
+}
+
+Statement.propTypes = {
+ collapse: PropTypes.func.isRequired,
+ element: PropTypes.shape({
+ depth: PropTypes.number.isRequired,
+ id: PropTypes.string.isRequired,
+ statement: PropTypes.object.isRequired
+ }).isRequired,
+ expand: PropTypes.func.isRequired,
+ isCollapsed: PropTypes.bool.isRequired,
+ onShowVertexDetails: PropTypes.func.isRequired
+};
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/statement_list_heading.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/statement_list_heading.js
new file mode 100644
index 000000000000..18ae8c3277a4
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/statement_list_heading.js
@@ -0,0 +1,43 @@
+/*
+ * 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 PropTypes from 'prop-types';
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiIcon,
+ EuiTitle
+} from '@elastic/eui';
+
+export function StatementListHeading({
+ iconType,
+ title
+}) {
+ return (
+
+
+
+
+
+
+ {title}
+
+
+
+ );
+}
+
+StatementListHeading.propTypes = {
+ iconType: PropTypes.string.isRequired,
+ title: PropTypes.string.isRequired,
+};
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/statement_section.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/statement_section.js
new file mode 100644
index 000000000000..f0f165c82ea7
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/config_viewer/statement_section.js
@@ -0,0 +1,120 @@
+/*
+ * 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 PropTypes from 'prop-types';
+import { StatementListHeading } from './statement_list_heading';
+import { Statement } from './statement';
+import { EuiSpacer } from '@elastic/eui';
+
+export function StatementSection({
+ iconType,
+ headingText,
+ elements,
+ onShowVertexDetails
+}) {
+ if (!elements.length) { return null; }
+
+ return (
+
+
+
+
+
+ );
+}
+
+function getCollapsedChildIds(elements, collapsedIds) {
+ const collapsedChildIds = new Set();
+ elements.forEach(({ id, parentId }) => {
+ if (collapsedIds.has(parentId) || collapsedChildIds.has(parentId)) {
+ collapsedChildIds.add(id);
+ }
+ });
+ return collapsedChildIds;
+}
+
+class StatementList extends React.PureComponent {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ collapsedIds: new Set(),
+ collapsedChildIds: new Set()
+ };
+ }
+
+ expand = elementId => {
+ const collapsedIds = new Set(this.state.collapsedIds);
+ collapsedIds.delete(elementId);
+ this.updateCollapsedElement(collapsedIds);
+ }
+
+ collapse = elementId => {
+ const collapsedIds = new Set(this.state.collapsedIds);
+ collapsedIds.add(elementId);
+ this.updateCollapsedElement(collapsedIds);
+ }
+
+ updateCollapsedElement = collapsedIds => {
+ const { elements } = this.props;
+ const collapsedChildIds = getCollapsedChildIds(elements, collapsedIds);
+
+ this.setState({
+ collapsedIds,
+ collapsedChildIds
+ });
+ }
+
+ elementIsCollapsed = elementId => this.state.collapsedIds.has(elementId);
+
+ renderStatement = element => {
+ const { id, parentId } = element;
+ const { onShowVertexDetails } = this.props;
+
+ return this.state.collapsedIds.has(parentId) || this.state.collapsedChildIds.has(parentId)
+ ? null
+ : (
+
+ );
+ }
+
+ render() {
+ const { elements } = this.props;
+
+ return (
+
+ {
+ elements.map(this.renderStatement)
+ }
+
+ );
+ }
+}
+
+StatementList.propTypes = {
+ elements: PropTypes.arrayOf(
+ PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ // top-level elements have null parentId
+ parentId: PropTypes.string
+ })
+ ).isRequired,
+ onShowVertexDetails: PropTypes.func.isRequired,
+};
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 c384802c4f45..b1b5d70827f2 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
@@ -20,7 +20,8 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiButtonIcon,
- EuiSpacer
+ EuiSpacer,
+ EuiBadge,
} from '@elastic/eui';
import { Sparkline } from '../../../sparkline';
import { formatMetric } from '../../../../lib/format_number';
@@ -189,7 +190,7 @@ function renderBasicStats(vertex, timeseriesTooltipXValueFormatter) {
function renderPluginBasicInfo(vertex) {
if (vertex.hasExplicitId) {
return (
- This {vertex.typeString}'s ID is { vertex.id }.
+ This {vertex.typeString}'s ID is { vertex.id }.
);
}
@@ -268,17 +269,22 @@ export function DetailDrawer({ vertex, onHide, timeseriesTooltipXValueFormatter
>
+
+ { renderIcon(vertex) }
+
- { renderIcon(vertex) }{ renderTitle(vertex) }
+ { renderTitle(vertex) }
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 7a50219686e3..4886f5c3df41 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,9 @@ import React from 'react';
import { render } from 'react-dom';
import moment from 'moment';
import { uiModules } from 'ui/modules';
-import { PipelineViewer } from 'plugins/monitoring/components/logstash/pipeline_viewer';
+import { ConfigViewer } from 'plugins/monitoring/components/logstash/pipeline_viewer/views/config_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';
const uiModule = uiModules.get('monitoring/directives', []);
@@ -28,13 +30,19 @@ uiModule.directive('monitoringLogstashPipelineViewer', ($injector) => {
scope.$watch('pipeline', (updatedPipeline) => {
pipelineState.update(updatedPipeline);
- const pipelineViewer = (
-
);
- render(pipelineViewer, $el[0]);
+ render(configViewer, $el[0]);
});
}
};
diff --git a/x-pack/plugins/monitoring/public/less/components/logstash/config_viewer.less b/x-pack/plugins/monitoring/public/less/components/logstash/config_viewer.less
new file mode 100644
index 000000000000..b237affc0c53
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/less/components/logstash/config_viewer.less
@@ -0,0 +1,98 @@
+@import (reference) '~ui/styles/variables/colors';
+
+monitoring-main[page="pipeline"] {
+ background: @globalColorLightestGray;
+ min-height: 100vh;
+}
+
+.configViewer {
+ max-width: 1000px;
+}
+
+.configViewer__statement {
+ padding-left: 12px;
+}
+
+.configViewer__plugin {
+ margin-left: 4px;
+}
+
+.configViewer__spaceContainer {
+ background-color: white;
+ align-self: stretch;
+ display: flex;
+ // Separates the left border spaces properly
+ border-bottom: solid 2px white;
+}
+
+.configViewer__spacer {
+ width: 12px;
+ align-self: stretch;
+ margin-left: 12px;
+ border-left: 1px @globalColorMediumGray dashed;
+
+ // This allows the border to be flush
+ &:last-child {
+ width: 0px;
+ }
+
+ &:first-child {
+ // Odd number is because of the single pixel border.
+ margin-left: 23px;
+ }
+}
+
+.configViewer__metric {
+ text-align: right;
+
+ &--cputTime {
+ width: 40px;
+ }
+
+ &--events, &--eventsEmitted {
+ width: 160px;
+ }
+
+ &--eventMillis {
+ width: 80px;
+ }
+}
+
+.configViewer__queueMessage {
+ margin-left: 24px;
+ color: @globalColorDarkGray;
+}
+
+.configViewer__list {
+ .configViewer__listItem {
+ display: flex;
+ min-height: 32px;
+ align-items: center;
+ padding-right: 12px;
+
+ &:nth-child(2n+1) {
+ background: #fafafa;
+ }
+ }
+}
+
+.configViewer__conditional {
+ font-weight: bold;
+}
+
+@media (max-width: 768px) {
+ .configViewer {
+ .configViewer__spacer {
+ border: none;
+ }
+
+ .configViewer__metricFlexItem {
+ margin-bottom: 4px !important;
+ }
+
+ .configViewer__metric {
+ text-align: left;
+ padding-left: 32px;
+ }
+ }
+}
diff --git a/x-pack/plugins/monitoring/public/less/main.less b/x-pack/plugins/monitoring/public/less/main.less
index c140aabfc0a0..08be28be7d69 100644
--- a/x-pack/plugins/monitoring/public/less/main.less
+++ b/x-pack/plugins/monitoring/public/less/main.less
@@ -11,6 +11,7 @@
@import './components/chart';
@import './components/sparkline';
@import './components/status_icon';
+@import './components/logstash/config_viewer';
@import './components/logstash/pipeline_viewer';
@import './components/logstash/pipeline_card_group';
@import './components/logstash/beta_icon';