mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Monitoring] [Logstash] Add Config View (#18597)
* Patch ConfigView changes from x-pack-kibana to OSS Kibana. * Remove old css. * Update style for queue statement. * WIP modifying based on designer feedback. * Add flatten function and list class. * Rename functions to be more descriptive. * WIP checkin. Modify components to handle flattened object list. * Finish moving flatten logic into classes, add tests. * Simplify flattening, remove non-native dependency. Add more tests. * Add defaults to simplify function call. * Refactor two blocks into a function. * Begin adapting components for list. * Add collapse functionality. * Add expand functionality. * Nested collapsed elements remain collapsed on parent expansion. * Style section headers. * Update Plugin statement styles. * Add DetailDrawer support. * Update statement formatting. * Add stats to plugin element rows. * Resolve conflicts. * Remove obsolete code. * Reorganize code. * Remove warnings. * Add PropTypes. Add keys to arrays and iterables. * Update color for borders/buttons. * Add stat class. Clean up code. * Convert plugin statement component from class to function. * Update queue metrics message. * Update style to make view more responsive. * Change section heading size. * Remove gutter from metrics group. * Change name "stat" to "metric". * Remove obsolete export line, simplify declaration, based on PR feedback. * Add new functional component in place of model class. * Add PropTypes to several components. Rename a function. Add keys to metrics. * Convert stateless classes to functional components. * Prefer ES6 syntax over bindings for Component methods. * Do not render statement section if there are no statements. * design cleanup for pipeline viewer * Update CSS to add min-height to page. * Rename "elements" to "statements". Delete unused LESS variables. * Revert naming of "statements" to "elements" for StatementList component. * Update jest snapshots for DetailDrawer.
This commit is contained in:
parent
5be1989c52
commit
b01dc53a9d
14 changed files with 893 additions and 53 deletions
|
@ -7,14 +7,24 @@ exports[`DetailDrawer component If vertices shows basic info and no stats for if
|
|||
>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
alignItems="baseline"
|
||||
component="div"
|
||||
direction="row"
|
||||
gutterSize="l"
|
||||
gutterSize="s"
|
||||
justifyContent="flexStart"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<img
|
||||
className="lspvDetailDrawerIcon"
|
||||
height={18}
|
||||
width={18}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
|
@ -23,11 +33,6 @@ exports[`DetailDrawer component If vertices shows basic info and no stats for if
|
|||
size="m"
|
||||
>
|
||||
<h2>
|
||||
<img
|
||||
className="lspvDetailDrawerIcon"
|
||||
height={18}
|
||||
width={18}
|
||||
/>
|
||||
if
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
|
@ -38,7 +43,7 @@ exports[`DetailDrawer component If vertices shows basic info and no stats for if
|
|||
>
|
||||
<EuiButtonIcon
|
||||
aria-label="Close"
|
||||
color="primary"
|
||||
color="text"
|
||||
iconType="cross"
|
||||
onClick={[MockFunction]}
|
||||
type="button"
|
||||
|
@ -78,14 +83,24 @@ exports[`DetailDrawer component Plugin vertices Plugin does not have explicit ID
|
|||
>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
alignItems="baseline"
|
||||
component="div"
|
||||
direction="row"
|
||||
gutterSize="l"
|
||||
gutterSize="s"
|
||||
justifyContent="flexStart"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<img
|
||||
className="lspvDetailDrawerIcon"
|
||||
height={18}
|
||||
width={18}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
|
@ -94,11 +109,6 @@ exports[`DetailDrawer component Plugin vertices Plugin does not have explicit ID
|
|||
size="m"
|
||||
>
|
||||
<h2>
|
||||
<img
|
||||
className="lspvDetailDrawerIcon"
|
||||
height={18}
|
||||
width={18}
|
||||
/>
|
||||
grok filter
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
|
@ -109,7 +119,7 @@ exports[`DetailDrawer component Plugin vertices Plugin does not have explicit ID
|
|||
>
|
||||
<EuiButtonIcon
|
||||
aria-label="Close"
|
||||
color="primary"
|
||||
color="text"
|
||||
iconType="cross"
|
||||
onClick={[MockFunction]}
|
||||
type="button"
|
||||
|
@ -371,14 +381,24 @@ exports[`DetailDrawer component Plugin vertices Plugin has explicit ID shows bas
|
|||
>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
alignItems="baseline"
|
||||
component="div"
|
||||
direction="row"
|
||||
gutterSize="l"
|
||||
gutterSize="s"
|
||||
justifyContent="flexStart"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<img
|
||||
className="lspvDetailDrawerIcon"
|
||||
height={18}
|
||||
width={18}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
|
@ -387,11 +407,6 @@ exports[`DetailDrawer component Plugin vertices Plugin has explicit ID shows bas
|
|||
size="m"
|
||||
>
|
||||
<h2>
|
||||
<img
|
||||
className="lspvDetailDrawerIcon"
|
||||
height={18}
|
||||
width={18}
|
||||
/>
|
||||
grok filter
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
|
@ -402,7 +417,7 @@ exports[`DetailDrawer component Plugin vertices Plugin has explicit ID shows bas
|
|||
>
|
||||
<EuiButtonIcon
|
||||
aria-label="Close"
|
||||
color="primary"
|
||||
color="text"
|
||||
iconType="cross"
|
||||
onClick={[MockFunction]}
|
||||
type="button"
|
||||
|
@ -418,9 +433,12 @@ exports[`DetailDrawer component Plugin vertices Plugin has explicit ID shows bas
|
|||
This
|
||||
plugin
|
||||
's ID is
|
||||
<strong>
|
||||
<EuiBadge
|
||||
color="default"
|
||||
iconSide="left"
|
||||
>
|
||||
parse_apache_logline
|
||||
</strong>
|
||||
</EuiBadge>
|
||||
.
|
||||
</p>
|
||||
<EuiTable
|
||||
|
@ -657,14 +675,24 @@ exports[`DetailDrawer component Queue vertices shows basic info and no stats for
|
|||
>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
alignItems="baseline"
|
||||
component="div"
|
||||
direction="row"
|
||||
gutterSize="l"
|
||||
gutterSize="s"
|
||||
justifyContent="flexStart"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<img
|
||||
className="lspvDetailDrawerIcon"
|
||||
height={18}
|
||||
width={18}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
|
@ -673,11 +701,6 @@ exports[`DetailDrawer component Queue vertices shows basic info and no stats for
|
|||
size="m"
|
||||
>
|
||||
<h2>
|
||||
<img
|
||||
className="lspvDetailDrawerIcon"
|
||||
height={18}
|
||||
width={18}
|
||||
/>
|
||||
queue
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
|
@ -688,7 +711,7 @@ exports[`DetailDrawer component Queue vertices shows basic info and no stats for
|
|||
>
|
||||
<EuiButtonIcon
|
||||
aria-label="Close"
|
||||
color="primary"
|
||||
color="text"
|
||||
iconType="cross"
|
||||
onClick={[MockFunction]}
|
||||
type="button"
|
||||
|
@ -718,14 +741,24 @@ exports[`DetailDrawer component shows vertex title 1`] = `
|
|||
>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
alignItems="baseline"
|
||||
component="div"
|
||||
direction="row"
|
||||
gutterSize="l"
|
||||
gutterSize="s"
|
||||
justifyContent="flexStart"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<img
|
||||
className="lspvDetailDrawerIcon"
|
||||
height={18}
|
||||
width={18}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
|
@ -733,13 +766,7 @@ exports[`DetailDrawer component shows vertex title 1`] = `
|
|||
<EuiTitle
|
||||
size="m"
|
||||
>
|
||||
<h2>
|
||||
<img
|
||||
className="lspvDetailDrawerIcon"
|
||||
height={18}
|
||||
width={18}
|
||||
/>
|
||||
</h2>
|
||||
<h2 />
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
|
@ -748,7 +775,7 @@ exports[`DetailDrawer component shows vertex title 1`] = `
|
|||
>
|
||||
<EuiButtonIcon
|
||||
aria-label="Close"
|
||||
color="primary"
|
||||
color="text"
|
||||
iconType="cross"
|
||||
onClick={[MockFunction]}
|
||||
type="button"
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* 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,
|
||||
EuiButtonIcon,
|
||||
EuiCodeBlock,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem
|
||||
} from '@elastic/eui';
|
||||
|
||||
function renderStatementName(name, onVertexSelected) {
|
||||
return (
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
key="statementName"
|
||||
>
|
||||
<EuiButtonEmpty
|
||||
color="text"
|
||||
size="xs"
|
||||
onClick={onVertexSelected}
|
||||
flush="left"
|
||||
>
|
||||
<span className="configViewer__conditional">{name}</span>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
|
||||
function renderIfStatement({ condition }, onVertexSelected) {
|
||||
return [
|
||||
renderStatementName('if', onVertexSelected),
|
||||
(
|
||||
<EuiFlexItem
|
||||
key="ifContent"
|
||||
grow={false}
|
||||
>
|
||||
<EuiCodeBlock
|
||||
fontSize="s"
|
||||
paddingSize="none"
|
||||
transparentBackground={true}
|
||||
>
|
||||
{condition}
|
||||
</EuiCodeBlock>
|
||||
</EuiFlexItem>
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
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 (
|
||||
<EuiFlexGroup
|
||||
responsive={false}
|
||||
gutterSize="none"
|
||||
alignItems="center"
|
||||
className="configViewer__statement"
|
||||
>
|
||||
<EuiFlexItem
|
||||
key={id}
|
||||
grow={false}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label
|
||||
color="text"
|
||||
iconType={getToggleIconType(isCollapsed)}
|
||||
onClick={toggleClicked}
|
||||
size="s"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{getStatementBody(props)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
|
@ -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 (
|
||||
<DetailDrawer
|
||||
vertex={this.state.detailDrawer.vertex}
|
||||
onHide={this.onHideVertexDetails}
|
||||
timeseriesTooltipXValueFormatter={this.props.timeseriesTooltipXValueFormatter}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
inputs,
|
||||
filters,
|
||||
outputs,
|
||||
queue
|
||||
} = this.props.pipeline;
|
||||
|
||||
return (
|
||||
<EuiPage>
|
||||
<EuiPageContent verticalPosition="center" horizontalPosition="center" className="configViewer">
|
||||
<StatementSection
|
||||
iconType="logstashInput"
|
||||
headingText="Inputs"
|
||||
elements={inputs}
|
||||
onShowVertexDetails={this.onShowVertexDetails}
|
||||
detailVertex={this.state.detailDrawer.vertex}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<Queue queue={queue} />
|
||||
<EuiSpacer />
|
||||
<StatementSection
|
||||
iconType="logstashFilter"
|
||||
headingText="Filters"
|
||||
elements={filters}
|
||||
onShowVertexDetails={this.onShowVertexDetails}
|
||||
detailVertex={this.state.detailDrawer.vertex}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<StatementSection
|
||||
iconType="logstashOutput"
|
||||
headingText="Outputs"
|
||||
elements={outputs}
|
||||
onShowVertexDetails={this.onShowVertexDetails}
|
||||
detailVertex={this.state.detailDrawer.vertex}
|
||||
/>
|
||||
{ this.renderDetailDrawer() }
|
||||
</EuiPageContent>
|
||||
</EuiPage>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigViewer.propTypes = {
|
||||
pipeline: PropTypes.shape({
|
||||
inputs: PropTypes.array.isRequired,
|
||||
filters: PropTypes.array.isRequired,
|
||||
outputs: PropTypes.array.isRequired,
|
||||
queue: PropTypes.object.isRequired,
|
||||
}).isRequired
|
||||
};
|
|
@ -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';
|
|
@ -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 = (
|
||||
<EuiBadge color="warning" classname={className}>
|
||||
{value}
|
||||
</EuiBadge>
|
||||
);
|
||||
} else {
|
||||
stylizedValue = (
|
||||
<EuiText size="s" color="subdued" className={classes}>
|
||||
<span>
|
||||
{value}
|
||||
</span>
|
||||
</EuiText>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<EuiFlexItem
|
||||
className="configViewer__metricFlexItem"
|
||||
grow={false}
|
||||
>
|
||||
{stylizedValue}
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
|
||||
Metric.propTypes = {
|
||||
className: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
};
|
|
@ -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 [(
|
||||
<Metric
|
||||
key="eventsEmitted"
|
||||
className="configViewer__metric--eventsEmitted"
|
||||
value={formatMetric(latestEventsPerSecond, '0.[00]a', 'e/s emitted')}
|
||||
/>
|
||||
)];
|
||||
}
|
||||
|
||||
function getProcessorStatementMetrics(processorVertex) {
|
||||
const {
|
||||
latestMillisPerEvent,
|
||||
latestEventsPerSecond,
|
||||
percentOfTotalProcessorTime,
|
||||
} = processorVertex;
|
||||
|
||||
return [
|
||||
(
|
||||
<Metric
|
||||
key="cpuMetric"
|
||||
className="configViewer__metric--cpuTime"
|
||||
warning={processorVertex.isTimeConsuming()}
|
||||
value={formatMetric(Math.round(percentOfTotalProcessorTime || 0), '0', '%', { prependSpace: false })}
|
||||
/>
|
||||
),
|
||||
(
|
||||
<Metric
|
||||
key="eventMillis"
|
||||
className="configViewer__metric--eventMillis"
|
||||
warning={processorVertex.isSlow()}
|
||||
value={formatMetric(latestMillisPerEvent, '0.[00]a', 'ms/e')}
|
||||
/>
|
||||
),
|
||||
(
|
||||
<Metric
|
||||
key="eventsReceived"
|
||||
className="configViewer__metric--events"
|
||||
value={formatMetric(latestEventsPerSecond, '0.[00]a', 'e/s received')}
|
||||
/>
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
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 (
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
justifyContent="spaceBetween"
|
||||
alignItems="center"
|
||||
className="configViewer__statement"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup
|
||||
gutterSize="xs"
|
||||
responsive={false}
|
||||
alignItems="center"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
color="primary"
|
||||
iconType="dot"
|
||||
flush="left"
|
||||
className="configViewer__plugin"
|
||||
onClick={onNameButtonClick}
|
||||
>
|
||||
<span>{name}</span>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
{
|
||||
hasExplicitId &&
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge
|
||||
onClick={onNameButtonClick}
|
||||
onClickAriaLabel="View details"
|
||||
>
|
||||
{id}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{
|
||||
statementMetrics &&
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
>
|
||||
{statementMetrics}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
|
@ -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 (
|
||||
<div className="configStatementList">
|
||||
<StatementListHeading
|
||||
iconType="logstashQueue"
|
||||
title="Queue"
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText className="configViewer__queueMessage">
|
||||
Queue metrics not available
|
||||
</EuiText>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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(<div key={`spacer_${i}`} className="configViewer__spacer" />);
|
||||
}
|
||||
return spacers;
|
||||
}
|
||||
|
||||
function renderStatement({
|
||||
collapse,
|
||||
element,
|
||||
element: {
|
||||
id,
|
||||
statement,
|
||||
},
|
||||
expand,
|
||||
isCollapsed,
|
||||
onShowVertexDetails
|
||||
}) {
|
||||
if (statement instanceof PluginStatementModel) {
|
||||
return (
|
||||
<PluginStatement
|
||||
statement={statement}
|
||||
onShowVertexDetails={onShowVertexDetails}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CollapsibleStatement
|
||||
expand={expand}
|
||||
collapse={collapse}
|
||||
statement={statement}
|
||||
isIf={element instanceof IfElement}
|
||||
isCollapsed={isCollapsed}
|
||||
id={id}
|
||||
onShowVertexDetails={onShowVertexDetails}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function Statement(props) {
|
||||
const { depth } = props.element;
|
||||
|
||||
return (
|
||||
<li className={`configViewer__listItem`}>
|
||||
<div className="configViewer__spaceContainer">
|
||||
{renderNestingSpacers(depth)}
|
||||
</div>
|
||||
{renderStatement(props)}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
};
|
|
@ -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 (
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
alignItems="baseline"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon
|
||||
type={iconType}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="s">
|
||||
<h4>{title}</h4>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
StatementListHeading.propTypes = {
|
||||
iconType: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
};
|
|
@ -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 (
|
||||
<div className="configStatementList">
|
||||
<StatementListHeading
|
||||
iconType={iconType}
|
||||
title={headingText}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<StatementList
|
||||
elements={elements}
|
||||
onShowVertexDetails={onShowVertexDetails}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
: (
|
||||
<Statement
|
||||
key={id}
|
||||
element={element}
|
||||
collapse={this.collapse}
|
||||
expand={this.expand}
|
||||
isCollapsed={this.elementIsCollapsed(id)}
|
||||
onShowVertexDetails={onShowVertexDetails}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { elements } = this.props;
|
||||
|
||||
return (
|
||||
<ul className="configViewer__list">
|
||||
{
|
||||
elements.map(this.renderStatement)
|
||||
}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
|
@ -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 (
|
||||
<p>This {vertex.typeString}'s ID is <strong>{ vertex.id }</strong>.</p>
|
||||
<p>This {vertex.typeString}'s ID is <EuiBadge>{ vertex.id }</EuiBadge>.</p>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -268,17 +269,22 @@ export function DetailDrawer({ vertex, onHide, timeseriesTooltipXValueFormatter
|
|||
>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
alignItems="baseline"
|
||||
gutterSize="s"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
{ renderIcon(vertex) }
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h2>{ renderIcon(vertex) }{ renderTitle(vertex) }</h2>
|
||||
<h2>{ renderTitle(vertex) }</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
onClick={onHide}
|
||||
iconType="cross"
|
||||
color="text"
|
||||
aria-label="Close"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -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 = (
|
||||
<PipelineViewer
|
||||
pipelineState={pipelineState}
|
||||
const configViewer = (
|
||||
<ConfigViewer
|
||||
pipeline={
|
||||
List.fromPipeline(
|
||||
Pipeline.fromPipelineGraph(
|
||||
pipelineState.config.graph
|
||||
)
|
||||
)
|
||||
}
|
||||
timeseriesTooltipXValueFormatter={timeseriesTooltipXValueFormatter}
|
||||
/>
|
||||
);
|
||||
render(pipelineViewer, $el[0]);
|
||||
render(configViewer, $el[0]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue