[Stack Monitoring] Visual refresh changes (#204258)

## Summary

This PR introduces changes to `x-pack/plugins/monitoring` necessary for
the Visual Refresh project
(https://github.com/elastic/kibana/issues/199715):

- replacing `euiThemeVars` with `euiTheme` context
- replacing old tokens with `euiTheme`
- making sure all color palette functions are run within the context of
the `EuiProvider`

Additionally:

- I migrated Sass to `@emotion/react`
- I migrated `euiStyled` to `@emotion/react`
- I extended `emotion.d.ts` in `tsconfig.json` for typing of the EUI
theme

closes [#8228](https://github.com/elastic/eui/issues/8228)

### QA

We need to test the critical paths in the Stack monitoring, paying close
attention to:

- [ ] color palette, visibility and contrast ratio of elements in
Amsterdam / Borealis

Specific paths:
- [ ] Monitoring time-series "Zoom out" button hover behavior -
`x-pack/plugins/monitoring/public/components/chart/monitoring_timeseries_container.tsx`
- [ ] Shard allocation (especially color mapping with shard type and
status):
- [ ]
`x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/assigned.js`
- [ ]
`x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/shard.js`
- [ ] Kuery bar suggestions and autocomplete field:
- [ ]
`x-pack/plugins/monitoring/public/components/kuery_bar/autocomplete_field.tsx`
- [ ]
`x-pack/plugins/monitoring/public/components/kuery_bar/suggestion_item.tsx`

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Weronika Olejniczak 2025-01-06 21:57:07 +01:00 committed by GitHub
parent 750ccb10f0
commit b95211bc50
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
75 changed files with 1169 additions and 969 deletions

1
.github/CODEOWNERS vendored
View file

@ -2543,7 +2543,6 @@ x-pack/solutions/security/plugins/security_solution/server/lib/security_integrat
# Observability design
/x-pack/platform/plugins/shared/fleet/**/*.scss @elastic/observability-design
/x-pack/platform/plugins/private/monitoring/**/*.scss @elastic/observability-design
# Ent. Search design
/x-pack/solutions/search/plugins/enterprise_search/**/*.scss @elastic/search-design

View file

@ -99,7 +99,7 @@ export const LogStashNodePipelinesPage: React.FC<ComponentProps> = ({ clusters }
{data.pipelines && (
<div data-test-subj="logstashPipelinesListing">
<PipelineListing
className="monitoringLogstashPipelinesTable"
data-test-subj="monitoringLogstashPipelinesTable"
onBrush={onBrush}
zoomInfo={zoomInfo}
stats={data.nodeSummary}

View file

@ -87,7 +87,7 @@ export const LogStashPipelinesPage: React.FC<ComponentProps> = ({ clusters }) =>
const upgradeMessage = pageData ? makeUpgradeMessage(clusterStatus.versions) : null;
return (
<PipelineListing
className="monitoringLogstashPipelinesTable"
data-test-subj="monitoringLogstashPipelinesTable"
onBrush={(xaxis: any) => onBrush({ xaxis })}
stats={clusterStatus}
data={pipelines}

View file

@ -65,14 +65,12 @@ function getColumns(setupMode: SetupMode, cgroup: unknown) {
};
setupModeStatus = (
<div className="monTableCell__setupModeStatus">
<SetupModeBadge
setupMode={setupMode}
status={status}
instance={instance}
productName={APM_SYSTEM_ID}
/>
</div>
<SetupModeBadge
setupMode={setupMode}
status={status}
instance={instance}
productName={APM_SYSTEM_ID}
/>
);
}
@ -187,7 +185,7 @@ export function ApmServerInstances({ apms, setupMode, alerts }: Props) {
<EuiPanel>
{setupModeCallout}
<EuiMonitoringTable
className="apmInstancesTable"
data-test-subj="apmInstancesTable"
rows={data.apms}
columns={getColumns(setupMode, data.cgroup)}
sorting={sorting}

View file

@ -48,14 +48,12 @@ export class Listing extends PureComponent {
};
setupModeStatus = (
<div className="monTableCell__setupModeStatus">
<SetupModeBadge
setupMode={setupMode}
status={status}
instance={instance}
productName={BEATS_SYSTEM_ID}
/>
</div>
<SetupModeBadge
setupMode={setupMode}
status={status}
instance={instance}
productName={BEATS_SYSTEM_ID}
/>
);
}
@ -161,7 +159,7 @@ export class Listing extends PureComponent {
<EuiPanel>
{setupModeCallOut}
<EuiMonitoringTable
className="beatsTable"
data-test-subj="beatsTable"
rows={data}
setupMode={setupMode}
productName={BEATS_SYSTEM_ID}

View file

@ -1,30 +0,0 @@
.monRhythmChart__legendItem {
font-size: $euiFontSizeXS;
cursor: pointer;
color: $euiTextColor;
display: flex;
flex-direction: row;
align-items: center;
&-isDisabled {
opacity: .5;
}
}
.monRhythmChart__legendHorizontal {
margin-top: $euiSizeXS;
}
.monRhythmChart__legendLabel {
overflow: hidden;
white-space: nowrap;
display: flex;
flex-direction: row;
align-items: center;
}
.monRhythmChart__legendValue {
overflow: hidden;
white-space: nowrap;
margin-left: $euiSizeXS;
}

View file

@ -5,16 +5,71 @@
* 2.0.
*/
import React from 'react';
import React, { MouseEvent } from 'react';
import {
EuiFlexItem,
EuiFlexGroup,
EuiIcon,
UseEuiTheme,
euiFontSize,
logicalCSS,
} from '@elastic/eui';
import { css } from '@emotion/react';
import { includes, isFunction } from 'lodash';
import { EuiFlexItem, EuiFlexGroup, EuiIcon } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import './horizontal_legend.scss';
export class HorizontalLegend extends React.Component {
constructor() {
super();
const legendItemStyle = (isDisabled: boolean) => (theme: UseEuiTheme) =>
css`
display: flex;
font-size: ${euiFontSize(theme, 'xs').fontSize};
cursor: pointer;
color: ${theme.euiTheme.colors.textParagraph};
display: flex;
flex-direction: row;
align-items: center;
${isDisabled ? 'opacity: 0.5;' : ''}
`;
const legendHorizontalStyle = ({ euiTheme }: UseEuiTheme) => css`
${logicalCSS('margin-top', euiTheme.size.xs)}
`;
const legendLabelStyle = css`
overflow: hidden;
white-space: nowrap;
display: flex;
flex-direction: row;
align-items: center;
`;
const legendValueStyle = ({ euiTheme }: UseEuiTheme) => css`
overflow: hidden;
white-space: nowrap;
${logicalCSS('margin-left', euiTheme.size.xs)}
`;
interface Row {
id: string;
label: string;
legend?: boolean;
color?: string;
tickFormatter?: (arg: number) => number;
}
interface Props {
series: Row[];
seriesValues: { [key: string]: number };
seriesFilter: string[];
onToggle: (event: MouseEvent<HTMLButtonElement>, id: string) => void;
legendFormatter?: (value: number) => number;
tickFormatter?: (value: number) => number;
}
export class HorizontalLegend extends React.Component<Props> {
constructor(props: Props) {
super(props);
this.formatter = this.formatter.bind(this);
this.createSeries = this.createSeries.bind(this);
}
@ -22,14 +77,14 @@ export class HorizontalLegend extends React.Component {
/**
* @param {Number} value Final value to display
*/
displayValue(value) {
return <span className="monRhythmChart__legendValue">{value}</span>;
displayValue(value: number) {
return <span css={legendValueStyle}>{value}</span>;
}
/**
* @param {Number} value True if value is falsy and/or not a number
*/
validValue(value) {
validValue(value: number) {
return value !== null && value !== undefined && (typeof value === 'string' || !isNaN(value));
}
@ -38,7 +93,7 @@ export class HorizontalLegend extends React.Component {
* A null means no data for the time bucket and will be formatted as 'N/A'
* @param {Object} row Props passed form a parent by row index
*/
formatter(value, row) {
formatter(value: number, row: Row) {
if (!this.validValue(value)) {
return (
<FormattedMessage
@ -48,7 +103,7 @@ export class HorizontalLegend extends React.Component {
);
}
if (row && row.tickFormatter) {
if (row?.tickFormatter) {
return this.displayValue(row.tickFormatter(value));
}
@ -60,12 +115,7 @@ export class HorizontalLegend extends React.Component {
return this.displayValue(value);
}
createSeries(row, rowIdx) {
const classes = ['monRhythmChart__legendItem'];
if (!includes(this.props.seriesFilter, row.id)) {
classes.push('monRhythmChart__legendItem-isDisabled');
}
createSeries(row: Row, rowIdx: number) {
if (!row.label || row.legend === false) {
return <div key={rowIdx} style={{ display: 'none' }} />;
}
@ -73,12 +123,11 @@ export class HorizontalLegend extends React.Component {
return (
<EuiFlexItem grow={false} key={rowIdx}>
<button
className={classes.join(' ')}
css={legendItemStyle(!includes(this.props.seriesFilter, row.id))}
onClick={(event) => this.props.onToggle(event, row.id)}
>
<span className="monRhythmChart__legendLabel">
<span css={legendLabelStyle}>
<EuiIcon
className="monRhythmChart__legendIndicator"
aria-label={i18n.translate(
'xpack.monitoring.chart.horizontalLegend.toggleButtonAriaLabel',
{ defaultMessage: 'toggle button' }
@ -87,7 +136,7 @@ export class HorizontalLegend extends React.Component {
type="dot"
color={row.color}
/>
{' ' + row.label + ' '}
{` ${row.label} `}
</span>
{this.formatter(this.props.seriesValues[row.id], row)}
</button>
@ -99,8 +148,8 @@ export class HorizontalLegend extends React.Component {
const rows = this.props.series.map(this.createSeries);
return (
<div className="monRhythmChart__legendHorizontal">
<EuiFlexGroup wrap={true} gutterSize="s" className="monRhythmChart__legendSeries">
<div css={legendHorizontalStyle}>
<EuiFlexGroup wrap={true} gutterSize="s">
{rows}
</EuiFlexGroup>
</div>

View file

@ -1,12 +0,0 @@
.monChart__tooltipLabel,
.monChart__tooltipValue {
text-align: left;
font-size: $euiFontSizeXS;
padding: $euiSizeXS;
word-wrap: break-word;
white-space: normal;
}
.monChart__tooltipLabel {
font-weight: $euiFontWeightBold;
}

View file

@ -6,10 +6,36 @@
*/
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import './info_tooltip.scss';
import { css } from '@emotion/react';
import { euiFontSize, UseEuiTheme } from '@elastic/eui';
export function InfoTooltip({ series, bucketSize }) {
import { FormattedMessage } from '@kbn/i18n-react';
import { Series } from './types';
const tooltipLabelStyle = (theme: UseEuiTheme) => css`
text-align: left;
font-size: ${euiFontSize(theme, 'xs').fontSize};
padding: ${theme.euiTheme.size.xs};
word-wrap: break-word;
white-space: normal;
font-weight: ${theme.euiTheme.font.weight.bold};
`;
const tooltipValueStyle = (theme: UseEuiTheme) => css`
text-align: left;
font-size: ${euiFontSize(theme, 'xs').fontSize};
padding: ${theme.euiTheme.size.xs};
word-wrap: break-word;
white-space: normal;
`;
interface Props {
series: Series[];
bucketSize?: string;
}
export function InfoTooltip({ series, bucketSize }: Props) {
const tableRows = series.map((item, index) => {
return (
<tr
@ -19,8 +45,8 @@ export function InfoTooltip({ series, bucketSize }) {
data-debug-metric-is-derivative={item.metric.isDerivative}
data-debug-metric-has-calculation={item.metric.hasCalculation}
>
<td className="monChart__tooltipLabel">{item.metric.label}</td>
<td className="monChart__tooltipValue">{item.metric.description}</td>
<td css={tooltipLabelStyle}>{item.metric.label}</td>
<td css={tooltipValueStyle}>{item.metric.description}</td>
</tr>
);
});
@ -29,13 +55,13 @@ export function InfoTooltip({ series, bucketSize }) {
<table>
<tbody>
<tr>
<td className="monChart__tooltipLabel">
<td css={tooltipLabelStyle}>
<FormattedMessage
id="xpack.monitoring.chart.infoTooltip.intervalLabel"
defaultMessage="Interval"
/>
</td>
<td className="monChart__tooltipValue">{bucketSize}</td>
<td css={tooltipValueStyle}>{bucketSize}</td>
</tr>
{tableRows}
</tbody>

View file

@ -1,8 +0,0 @@
.monRhythmChart__wrapper .monRhythmChart__zoom {
visibility: hidden;
padding-right: $euiSizeM;
}
.monRhythmChart__wrapper:hover .monRhythmChart__zoom {
visibility: visible;
}

View file

@ -6,9 +6,8 @@
*/
import React, { Fragment } from 'react';
import { css } from '@emotion/react';
import { get, first } from 'lodash';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import {
EuiBadge,
EuiIconTip,
@ -18,7 +17,13 @@ import {
EuiScreenReaderOnly,
EuiTextAlign,
EuiButtonEmpty,
UseEuiTheme,
logicalCSS,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { getTechnicalPreview } from './get_technical_preview';
import { getTitle } from './get_title';
import { getUnits } from './get_units';
@ -26,8 +31,18 @@ import { MonitoringTimeseries } from './monitoring_timeseries';
import { InfoTooltip } from './info_tooltip';
import { AlertsBadge } from '../../alerts/badge';
import type { AlertsByName } from '../../alerts/types';
import { Series } from './types';
import './monitoring_timeseries_container.scss';
const zoomStyle = ({ euiTheme }: UseEuiTheme) => css`
visibility: hidden;
${logicalCSS('padding-right', euiTheme.size.m)}
`;
const wrapperStyle = css`
&:hover .rhythmChart__zoom {
visibility: visible;
}
`;
interface ZoomInfo {
showZoomOutBtn: () => boolean;
@ -37,9 +52,7 @@ interface ZoomInfo {
interface SeriesAlert {
alerts: AlertsByName;
}
interface Series {
metric: { title: string; label: string; description: string };
}
interface Props {
series?: Series[] | SeriesAlert;
onBrush?: ({ xaxis }: any) => void;
@ -56,7 +69,7 @@ const zoomOutBtn = (zoomInfo?: ZoomInfo) => {
}
return (
<EuiFlexItem className="monRhythmChart__zoom">
<EuiFlexItem className="rhythmChart__zoom" css={zoomStyle}>
<EuiTextAlign textAlign="right">
<EuiButtonEmpty
color="primary"
@ -114,6 +127,7 @@ export function MonitoringTimeseriesContainer({ series, onBrush, zoomInfo }: Pro
let alertStatus = null;
const seriesAlert = isSeriesAlert(series) ? series : undefined;
if (seriesAlert?.alerts) {
alertStatus = (
<EuiFlexItem grow={false}>
@ -123,7 +137,7 @@ export function MonitoringTimeseriesContainer({ series, onBrush, zoomInfo }: Pro
}
return (
<EuiFlexGroup direction="column" gutterSize="s" className={`monRhythmChart__wrapper`}>
<EuiFlexGroup direction="column" gutterSize="s" css={wrapperStyle}>
<EuiFlexItem grow={false}>
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
@ -147,7 +161,7 @@ export function MonitoringTimeseriesContainer({ series, onBrush, zoomInfo }: Pro
<EuiFlexItem grow={false}>
<Fragment>
<EuiIconTip
anchorClassName="eui-textRight eui-alignMiddle monChart__tooltipTrigger"
anchorClassName="eui-textRight eui-alignMiddle"
type="iInCircle"
position="right"
content={<InfoTooltip series={seriesMetrics} bucketSize={bucketSize} />}

View file

@ -5,14 +5,50 @@
* 2.0.
*/
import { debounce, keys, has, includes, isFunction, difference, assign } from 'lodash';
import React from 'react';
import { css } from '@emotion/react';
import { debounce, keys, has, includes, isFunction, difference, assign } from 'lodash';
import { getLastValue } from './get_last_value';
import { TimeseriesContainer } from './timeseries_container';
import { HorizontalLegend } from './horizontal_legend';
import { getValuesForSeriesIndex, getValuesByX } from './get_values_for_legend';
import { DEBOUNCE_SLOW_MS } from '../../../common/constants';
import './timeseries_visualization.scss';
const rhythmChartStyle = css`
position: relative;
display: flex;
flex-direction: column;
flex: 1 0 auto;
`;
const rhythmChartContentStyle = css`
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
flex: 1 0 auto;
flex-direction: column;
`;
const rhythmChartVisualizationStyle = css`
position: relative;
display: flex;
flex-direction: column;
flex: 1 0 auto;
& > div {
min-width: 1px;
width: 100%;
height: 100%;
}
div {
user-select: none;
}
`;
export class TimeseriesVisualization extends React.Component {
constructor(props) {
@ -116,11 +152,6 @@ export class TimeseriesVisualization extends React.Component {
}
render() {
const className = 'monRhythmChart';
const style = {
flexDirection: 'column', // for legend position = bottom
};
const legend = this.props.hasLegend ? (
<HorizontalLegend
seriesFilter={this.state.seriesToShow}
@ -131,9 +162,9 @@ export class TimeseriesVisualization extends React.Component {
) : null;
return (
<div className={className}>
<div style={style} className="monRhythmChart__content">
<div className="monRhythmChart__visualization">
<div css={rhythmChartStyle}>
<div css={rhythmChartContentStyle}>
<div css={rhythmChartVisualizationStyle}>
<TimeseriesContainer
seriesToShow={this.state.seriesToShow}
updateLegend={this.debouncedUpdateLegend}

View file

@ -1,39 +0,0 @@
.monRhythmChart {
position: relative;
display: flex;
flex-direction: column;
flex: 1 0 auto;
}
.monRhythmChart__content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
flex: 1 0 auto;
}
@mixin monitoringNoUserSelect {
user-select: none;
}
.monRhythmChart__visualization {
display: flex;
flex-direction: column;
flex: 1 0 auto;
position: relative;
// SASSTODO: generic selector
& > div {
min-width: 1px;
width: 100%;
height: 100%;
}
// SASSTODO: generic selector
div {
@include monitoringNoUserSelect;
}
}

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export interface Series {
metric: {
description: string;
field: any;
hasCalculation: any;
isDerivative: any;
label: string;
metricAgg: any;
title: string;
};
}

View file

@ -6,9 +6,9 @@
*/
import React, { Fragment } from 'react';
import { Legacy } from '../../../legacy_shims';
import moment from 'moment';
import numeral from '@elastic/numeral';
import { css } from '@emotion/react';
import { capitalize, partial } from 'lodash';
import {
EuiHealth,
@ -20,17 +20,32 @@ import {
EuiSpacer,
EuiIcon,
EuiToolTip,
euiFontSize,
} from '@elastic/eui';
import { EuiMonitoringTable } from '../../table';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { Legacy } from '../../../legacy_shims';
import { EuiMonitoringTable } from '../../table';
import { AlertsStatus } from '../../../alerts/status';
import { STANDALONE_CLUSTER_CLUSTER_UUID } from '../../../../common/constants';
import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link';
import './listing.scss';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
const clusterCellExpiredStyle = ({ euiTheme }) => css`
color: ${euiTheme.colors.textParagraph};
`;
const clusterCellLicenseStyle = (theme) => css`
font-size: ${euiFontSize(theme, 'm').fontSize};
`;
const clusterCellExpirationStyle = ({ euiTheme }) => css`
color: ${euiTheme.colors.darkShade};
`;
const IsClusterSupported = ({ isSupported, children }) => {
return isSupported ? children : '-';
};
@ -64,7 +79,7 @@ const STANDALONE_CLUSTER_STORAGE_KEY = 'viewedStandaloneCluster';
const getColumns = (
showLicenseExpiration,
changeCluster,
_changeCluster,
handleClickIncompatibleLicense,
handleClickInvalidLicense
) => {
@ -193,18 +208,14 @@ const getColumns = (
const license = cluster.license;
if (!licenseType) {
return (
<div>
<div className="monTableCell__clusterCellLicense">N/A</div>
</div>
);
return <div css={clusterCellLicenseStyle}>N/A</div>;
}
if (license) {
const licenseExpiry = () => {
if (license.expiry_date_in_millis < moment().valueOf()) {
// license is expired
return <span className="monTableCell__clusterCellExpired">Expired</span>;
return <span css={clusterCellExpiredStyle}>Expired</span>;
}
// license is fine
@ -213,8 +224,8 @@ const getColumns = (
return (
<div>
<div className="monTableCell__clusterCellLicense">{capitalize(licenseType)}</div>
<div className="monTableCell__clusterCellExpiration">
<div css={clusterCellLicenseStyle}>{capitalize(licenseType)}</div>
<div css={clusterCellExpirationStyle}>
{showLicenseExpiration ? licenseExpiry() : null}
</div>
</div>
@ -419,7 +430,7 @@ export const Listing = ({ angular, clusters, sorting, pagination, onTableChange
<StandaloneClusterCallout changeCluster={_changeCluster} storage={storage} />
) : null}
<EuiMonitoringTable
className="clusterTable"
data-test-subj="clusterTable"
rows={clusters}
columns={getColumns(
showLicenseExpiration,

View file

@ -1,41 +0,0 @@
/*
* A table that stretches the full window width and columns size appropriately to content.
* The .monitoringTable class is on the KuiControlledTable instance.
* The table within it requires the shrinkToContent flag as well as width set to 100%
*/
.monTableCell__clusterCellExpired,
.monTableCell__offline {
color: $euiTextColor;
}
.monTableCell__clusterCellLicense {
font-size: $euiFontSize;
}
.monTableCell__clusterCellExpiration {
color: $euiColorDarkShade;
}
.monTableCell__name,
.monTableCell__status {
@include euiFontSizeM;
}
.monTableCell__status {
overflow-x: hidden;
white-space: nowrap;
}
.monTableCell__transportAddress {
@include euiFontSizeS;
color: $euiColorDarkShade;
}
.monTableCell__number {
@include euiFontSizeL;
}
.monTableCell__splitNumber {
@include euiFontSizeM;
}

View file

@ -13,7 +13,6 @@ exports[`Ccr that it renders normally 1`] = `
</EuiScreenReaderOnly>
<EuiPanel>
<EuiInMemoryTable
className="monitoringElasticsearchCcrListingTable"
columns={
Array [
Object {
@ -57,6 +56,19 @@ exports[`Ccr that it renders normally 1`] = `
},
]
}
css={
Object {
"map": undefined,
"name": "f6xtqm",
"next": undefined,
"styles": "
.euiTableRow-isExpandedRow > .euiTableRowCell > .euiTableCellContent {
padding: 0;
}
",
"toString": [Function],
}
}
executeQueryOptions={
Object {
"defaultFields": Array [

View file

@ -6,6 +6,7 @@
*/
import React, { Fragment, useState } from 'react';
import { css } from '@emotion/react';
import {
EuiInMemoryTable,
EuiLink,
@ -17,11 +18,22 @@ import {
EuiTextColor,
EuiScreenReaderOnly,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link';
import { AlertsStatus } from '../../../alerts/status';
import './ccr.scss';
/**
* We want the collapsed table (that shows the shard data) to be inline
* with the columns from the main table so we need to remove the padding
*/
const ccrListingTableStyle = css`
.euiTableRow-isExpandedRow > .euiTableRowCell > .euiTableCellContent {
padding: 0;
}
`;
function toSeconds(ms) {
return Math.floor(ms / 1000) + 's';
@ -194,7 +206,7 @@ export const Ccr = (props) => {
return (
<EuiInMemoryTable
className="monitoringElasticsearchCcrListingTable"
css={ccrListingTableStyle}
columns={[
{
field: 'index',

View file

@ -1,7 +0,0 @@
/**
* [1] - We want the collapsed table (that shows the shard data) to be inline
* with the columns from the main table so we need to remove the padding
*/
.monitoringElasticsearchCcrListingTable .euiTableRow-isExpandedRow > .euiTableRowCell > .euiTableCellContent {
padding: 0; /* [1] */
}

View file

@ -7,12 +7,7 @@
import React from 'react';
import { capitalize } from 'lodash';
import { LARGE_FLOAT, LARGE_BYTES, LARGE_ABBREVIATED } from '../../../../common/formatting';
import { formatMetric } from '../../../lib/format_number';
import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link';
import { ElasticsearchStatusIcon } from '../status_icon';
import { ClusterStatus } from '../cluster_status';
import { EuiMonitoringTable } from '../../table';
import { css } from '@emotion/react';
import {
EuiLink,
EuiPage,
@ -22,10 +17,22 @@ import {
EuiSpacer,
EuiScreenReaderOnly,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { AlertsStatus } from '../../../alerts/status';
import './indices.scss';
import { ClusterStatus } from '../cluster_status';
import { ElasticsearchStatusIcon } from '../status_icon';
import { EuiMonitoringTable } from '../../table';
import { LARGE_FLOAT, LARGE_BYTES, LARGE_ABBREVIATED } from '../../../../common/formatting';
import { formatMetric } from '../../../lib/format_number';
import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link';
const statusStyle = css`
display: flex;
align-items: center;
`;
const getColumns = (alerts) => {
return [
@ -70,7 +77,7 @@ const getColumns = (alerts) => {
field: 'status',
sortable: true,
render: (value) => (
<div className="monElasticsearchIndicesTable__status" title={`Index status: ${value}`}>
<div css={statusStyle} title={`Index status: ${value}`}>
<ElasticsearchStatusIcon status={value} />
&nbsp;
{capitalize(value)}
@ -183,7 +190,7 @@ export const ElasticsearchIndices = ({
/>
<EuiSpacer size="m" />
<EuiMonitoringTable
className="elasticsearchIndicesTable"
data-test-subj="elasticsearchIndicesTable"
rows={indices}
columns={getColumns(alerts)}
sorting={sorting}

View file

@ -1,4 +0,0 @@
.monElasticsearchIndicesTable__status {
display: flex;
align-items: center;
}

View file

@ -53,7 +53,7 @@ export const ElasticsearchMLJobs = ({
<EuiSpacer size="m" />
<EuiPanel>
<EuiMonitoringTable
className="mlJobsTable"
data-test-subj="mlJobsTable"
rows={jobs}
columns={columns}
sorting={{

View file

@ -1,9 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Node Listing Metric Cell should format N/A as the metric for an offline node 1`] = `
<div
class="monTableCell__offline"
>
<div>
N/A
</div>
`;

View file

@ -7,7 +7,7 @@
import React, { useState } from 'react';
import { get } from 'lodash';
import { formatMetric } from '../../../lib/format_number';
import { css } from '@emotion/react';
import {
EuiText,
EuiPopover,
@ -17,8 +17,15 @@ import {
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { formatMetric } from '../../../lib/format_number';
const offlineStyle = ({ euiTheme }) => css`
color: ${euiTheme.colors.textParagraph};
`;
const TRENDING_DOWN = i18n.translate('xpack.monitoring.elasticsearch.node.cells.trendingDownText', {
defaultMessage: 'down',
});
@ -27,7 +34,7 @@ const TRENDING_UP = i18n.translate('xpack.monitoring.elasticsearch.node.cells.tr
});
function OfflineCell() {
return <div className="monTableCell__offline">N/A</div>;
return <div css={offlineStyle}>N/A</div>;
}
const getDirection = (slope) => {

View file

@ -5,6 +5,9 @@
* 2.0.
*/
import React, { Fragment } from 'react';
import { css } from '@emotion/react';
import { get } from 'lodash';
import {
EuiBadge,
EuiBadgeGroup,
@ -20,11 +23,12 @@ import {
EuiSpacer,
EuiText,
EuiToolTip,
euiFontSize,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { get } from 'lodash';
import React, { Fragment } from 'react';
import { ELASTICSEARCH_SYSTEM_ID } from '../../../../common/constants';
import { SetupModeFeature } from '../../../../common/enums';
import { AlertsStatus } from '../../../alerts/status';
@ -37,6 +41,15 @@ import { EuiMonitoringSSPTable } from '../../table';
import { ClusterStatus } from '../cluster_status';
import { MetricCell, OfflineCell } from './cells';
const tableCellNameStyle = (theme) => css`
${euiFontSize(theme, 'm')}
`;
const tableCellTransportAddressStyle = (theme) => css`
${euiFontSize(theme, 's')}
color: ${theme.euiTheme.colors.darkShade};
`;
const getNodeTooltip = (node) => {
const { nodeTypeLabel, nodeTypeClass } = node;
@ -97,15 +110,13 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler
};
setupModeStatus = (
<div className="monTableCell__setupModeStatus">
<SetupModeBadge
setupMode={setupMode}
status={status}
instance={instance}
productName={ELASTICSEARCH_SYSTEM_ID}
clusterUuid={clusterUuid}
/>
</div>
<SetupModeBadge
setupMode={setupMode}
status={status}
instance={instance}
productName={ELASTICSEARCH_SYSTEM_ID}
clusterUuid={clusterUuid}
/>
);
if (status.isNetNewUser) {
nameLink = value;
@ -114,13 +125,13 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler
return (
<div>
<div className="monTableCell__name">
<div css={tableCellNameStyle}>
<EuiText size="m">
{getNodeTooltip(node)}
<span data-test-subj="name">{nameLink}</span>
</EuiText>
</div>
<div className="monTableCell__transportAddress">{extractIp(node.transport_address)}</div>
<div css={tableCellTransportAddressStyle}>{extractIp(node.transport_address)}</div>
{setupModeStatus}
</div>
);
@ -502,7 +513,7 @@ export function ElasticsearchNodes({ clusterStatus, showCgroupMetricsElasticsear
{setupModeCallout}
<EuiPanel>
<EuiMonitoringSSPTable
className="elasticsearchNodesTable"
data-test-subj="elasticsearchNodesTable"
rows={nodes}
columns={columns}
sorting={sorting}

View file

@ -139,7 +139,7 @@ export const ShardActivity = (props) => {
/>
<EuiSpacer />
<EuiMonitoringTable
className="esShardActivityTable"
data-test-subj="esShardActivityTable"
rows={rows}
columns={columns}
message={getNoDataMessage()}

View file

@ -5,13 +5,67 @@
* 2.0.
*/
import { get, sortBy } from 'lodash';
import React from 'react';
import { get, sortBy } from 'lodash';
import { css } from '@emotion/react';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, euiFontSize, logicalCSS } from '@elastic/eui';
import { Shard } from './shard';
import { calculateClass } from '../lib/calculate_class';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink } from '@elastic/eui';
import { getSafeForExternalLink } from '../../../../lib/get_safe_for_external_link';
const assignedChildrenStyle = ({ euiTheme }) => css`
${logicalCSS('padding-top', euiTheme.size.l)}
`;
const childTitleStyle = (theme) => css`
${logicalCSS('padding', `${theme.euiTheme.size.l} ${theme.euiTheme.size.s}`)}
text-align: center;
font-size: ${euiFontSize(theme, 'xs').fontSize};
color: ${theme.euiTheme.colors.ghost};
display: flex;
flex-direction: row;
align-items: center;
`;
const shardStyle = (theme) => css`
align-self: center;
${logicalCSS('padding', `${theme.euiTheme.size.xs} ${theme.euiTheme.size.s}`)}
font-size: ${euiFontSize(theme, 'xs').fontSize};
position: relative;
display: inline-block;
`;
const childStyle = (data, shardStats) => (theme) =>
css`
float: left;
align-self: center;
background-color: ${theme.euiTheme.colors.lightestShade};
margin: ${theme.euiTheme.size.s};
border: 1px solid ${theme.euiTheme.colors.mediumShade};
border-radius: ${theme.euiTheme.size.xs};
${logicalCSS('padding', `calc(${theme.euiTheme.size.xs} / 2) 0`)}
${data.type === 'index' &&
logicalCSS(
'border-left',
`${theme.euiTheme.size.xs} solid ${theme.euiTheme.colors.borderStrongSuccess}`
)}
${shardStats?.status === 'red' &&
logicalCSS(
'border-left',
`${theme.euiTheme.size.xs} solid ${theme.euiTheme.colors.borderStrongDanger}`
)}
${shardStats?.status === 'yellow' &&
logicalCSS(
'border-left',
`${theme.euiTheme.size.xs} solid ${theme.euiTheme.colors.borderStrongWarning}`
)}
${data.type === 'shard' && shardStyle(theme)}
`;
const generateQueryAndLink = (data) => {
let type = 'indices';
let ident = data.name;
@ -46,21 +100,7 @@ export class Assigned extends React.Component {
createChild = (data) => {
const key = data.id;
const initialClasses = ['monChild'];
if (data.type === 'index') {
initialClasses.push('monChild--index');
}
const shardStats = get(this.props.shardStats.indices, key);
if (shardStats) {
switch (shardStats.status) {
case 'red':
initialClasses.push('monChild--danger');
break;
case 'yellow':
initialClasses.push('monChild--warning');
break;
}
}
// TODO: redesign for shard allocation
const name = <EuiLink href={generateQueryAndLink(data)}>{data.name}</EuiLink>;
@ -71,13 +111,13 @@ export class Assigned extends React.Component {
return (
<EuiFlexItem
grow={false}
className={calculateClass(data, initialClasses.join(' '))}
css={childStyle(data, shardStats)}
key={key}
data-test-subj={`clusterView-Assigned-${key}`}
data-status={shardStats && shardStats.status}
data-status={shardStats?.status}
>
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem grow={false} className="monChild__title eui-textNoWrap">
<EuiFlexItem css={childTitleStyle} grow={false} className="eui-textNoWrap">
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem grow={false}>{name}</EuiFlexItem>
<EuiFlexItem grow={false}>{master}</EuiFlexItem>
@ -93,9 +133,10 @@ export class Assigned extends React.Component {
render() {
const data = sortBy(this.props.data, sortByName).map(this.createChild);
return (
<td className="monAssigned">
<EuiFlexGroup wrap className="monAssigned__children">
<td>
<EuiFlexGroup wrap css={assignedChildrenStyle}>
{data}
</EuiFlexGroup>
</td>

View file

@ -6,11 +6,22 @@
*/
import React from 'react';
import { css } from '@emotion/react';
import { get } from 'lodash';
import { EuiToolTip, EuiBadge, euiFontSize, logicalCSS } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { calculateClass } from '../lib/calculate_class';
import { vents } from '../lib/vents';
import { i18n } from '@kbn/i18n';
import { EuiToolTip, EuiBadge } from '@elastic/eui';
const shardStyle = (theme) => css`
${logicalCSS('padding', `${theme.euiTheme.size.xs} ${theme.euiTheme.size.s}`)}
align-self: center;
font-size: ${euiFontSize(theme, 'xs').fontSize};
position: relative;
display: inline-block;
`;
function getColor(classes) {
const classList = classes.split(' ');
@ -117,6 +128,7 @@ export class Shard extends React.Component {
<div
onMouseEnter={this.toggle}
onMouseLeave={this.toggle}
css={shard.type === 'shard' && shardStyle}
className={classes}
data-shard-tooltip={tooltipContent}
data-shard-classification={classification}

View file

@ -5,18 +5,28 @@
* 2.0.
*/
import { sortBy } from 'lodash';
import React from 'react';
import { Shard } from './shard';
import { sortBy } from 'lodash';
import { css } from '@emotion/react';
import { EuiFlexGroup, logicalCSS } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { EuiFlexGroup } from '@elastic/eui';
import { Shard } from './shard';
export const unassignedStyle = ({ euiTheme }) => css`
vertical-align: middle;
width: calc(${euiTheme.size.l} * 10);
`;
export const unassignedChildrenStyle = ({ euiTheme }) => css`
${logicalCSS('padding-top', euiTheme.size.l)}
`;
export class Unassigned extends React.Component {
static displayName = i18n.translate(
'xpack.monitoring.elasticsearch.shardAllocation.unassignedDisplayName',
{
defaultMessage: 'Unassigned',
}
{ defaultMessage: 'Unassigned' }
);
createShard = (shard) => {
@ -33,14 +43,16 @@ export class Unassigned extends React.Component {
'.' +
shard.shard +
additionId;
return <Shard shard={shard} key={key} />;
};
render() {
const shards = sortBy(this.props.shards, 'shard').map(this.createShard);
return (
<td className="monUnassigned" data-test-subj="clusterView-Unassigned">
<EuiFlexGroup wrap className="monUnassigned__children">
<td css={unassignedStyle} data-test-subj="clusterView-Unassigned">
<EuiFlexGroup wrap css={unassignedChildrenStyle}>
{shards}
</EuiFlexGroup>
</td>

View file

@ -6,12 +6,24 @@
*/
import React from 'react';
import { css } from '@emotion/react';
import { EuiTitle, EuiBadge, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import './shard_allocation.scss';
import { ClusterView } from './components/cluster_view';
const clusterStyle = ({ euiTheme }) => css`
th {
text-align: left;
}
td:first-child {
width: calc(${euiTheme.base} * 12.5);
}
`;
export const ShardAllocation = (props) => {
const types = [
{
@ -59,7 +71,7 @@ export const ShardAllocation = (props) => {
];
return (
<div className="monCluster">
<div css={clusterStyle}>
<EuiTitle>
<h1>
<FormattedMessage

View file

@ -1,71 +0,0 @@
.monClusterTitle {
font-size: $euiFontSizeL;
margin: 0;
}
// SASSTODO: This needs a full rewrite / redesign
.monCluster {
.monUnassigned {
vertical-align: middle;
width: $euiSize * 10;
}
.monUnassigned__children,
.monAssigned__children {
padding-top: $euiSizeL;
}
.monChild {
float: left;
align-self: center;
background-color: $euiColorLightestShade;
margin: $euiSizeS;
border: 1px solid $euiColorMediumShade;
border-radius: $euiSizeXS;
padding: calc($euiSizeXS / 2) 0;
&.monChild--index {
border-left: $euiSizeXS solid $euiColorSuccess;
&.monChild--danger {
border-left: $euiSizeXS solid $euiColorDanger;
}
&.monChild--warning {
border-left: $euiSizeXS solid $euiColorWarning;
}
}
.monChild__title {
padding: $euiSizeXS $euiSizeS;
text-align: center;
font-size: $euiFontSizeXS;
color: $euiColorGhost;
display: flex;
flex-direction: row;
align-items: center;
}
&.monClusterUnassigned {
.title {
display: none;
}
}
}
th {
text-align: left;
}
td:first-child {
width: $euiSize * 12.5;
}
.monShard {
align-self: center;
padding: $euiSizeXS $euiSizeS;
font-size: $euiFontSizeXS;
position: relative;
display: inline-block;
}
}

View file

@ -5,10 +5,13 @@
* 2.0.
*/
import { EuiBadge, EuiLink, EuiStat, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { useLocation } from 'react-router-dom';
import { css } from '@emotion/react';
import { EuiBadge, EuiLink, EuiStat, euiTextTruncate, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import type { AlertsByName } from '../../../alerts/types';
import { ExternalConfigContext } from '../../../application/contexts/external_config_context';
import { formatMetric } from '../../../lib/format_number';
@ -16,6 +19,12 @@ import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'
import { DefaultStatusIndicator, SummaryStatus } from '../../summary_status';
import { KibanaStatusIcon } from '../status_icon';
const summaryStatusNoWrapStatStyle = css`
p {
${euiTextTruncate()}
}
`;
interface ClusterStatusProps {
stats: {
concurrent_connections: number;
@ -159,7 +168,7 @@ function OverviewPageStatusIndicator({ staleMessage }: IndicatorProps) {
title={title}
titleSize="xxxs"
textAlign="left"
className="monSummaryStatusNoWrap__stat"
css={summaryStatusNoWrapStatStyle}
/>
);
}
@ -187,7 +196,7 @@ function InstancesPageStatusIndicator({ staleMessage }: IndicatorProps) {
title={title}
titleSize="xxxs"
textAlign="left"
className="monSummaryStatusNoWrap__stat"
css={summaryStatusNoWrapStatStyle}
/>
);
}

View file

@ -5,17 +5,26 @@
* 2.0.
*/
import { EuiBadge, EuiStat, EuiToolTip } from '@elastic/eui';
import React from 'react';
import { capitalize } from 'lodash';
import { css } from '@emotion/react';
import { EuiBadge, EuiStat, EuiToolTip, euiTextTruncate } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useUiSetting } from '@kbn/kibana-react-plugin/public';
import { capitalize } from 'lodash';
import React from 'react';
import { ExternalConfigContext } from '../../../application/contexts/external_config_context';
import { formatMetric } from '../../../lib/format_number';
import { DefaultStatusIndicator, SummaryStatus } from '../../summary_status';
import { formatLastSeenTimestamp } from '../format_last_seen_timestamp';
import { KibanaStatusIcon } from '../status_icon';
const summaryStatusNoWrapStatStyle = css`
p {
${euiTextTruncate()}
}
`;
export function DetailStatus({ stats }) {
const {
transport_address: transportAddress,
@ -77,12 +86,12 @@ export function DetailStatus({ stats }) {
return (
<EuiStat
css={summaryStatusNoWrapStatStyle}
data-test-subj="status"
description={description}
textAlign="left"
title={title}
titleSize="xxxs"
textAlign="left"
className="monSummaryStatusNoWrap__stat"
/>
);
};

View file

@ -5,6 +5,9 @@
* 2.0.
*/
import React, { Fragment } from 'react';
import { css } from '@emotion/react';
import { capitalize, get } from 'lodash';
import {
EuiCallOut,
EuiHealth,
@ -16,12 +19,14 @@ import {
EuiScreenReaderOnly,
EuiSpacer,
EuiToolTip,
UseEuiTheme,
euiFontSize,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { useUiSetting } from '@kbn/kibana-react-plugin/public';
import { capitalize, get } from 'lodash';
import React, { Fragment } from 'react';
import type { TableChange, Sorting, Pagination } from '../../../application/hooks/use_table';
import type { AlertsByName } from '../../../alerts/types';
import { KIBANA_SYSTEM_ID } from '../../../../common/constants';
@ -40,6 +45,10 @@ import { ClusterStatus } from '../cluster_status';
import { formatLastSeenTimestamp } from '../format_last_seen_timestamp';
import type { SetupMode } from '../../setup_mode/types';
const tableCellSplitNumber = (theme: UseEuiTheme) => css`
font-size: ${euiFontSize(theme, 'm').fontSize};
`;
const getColumns = (
setupMode: SetupMode,
alerts: AlertsByName,
@ -64,14 +73,12 @@ const getColumns = (
};
setupModeStatus = (
<div className="monTableCell__setupModeStatus">
<SetupModeBadge
setupMode={setupMode}
status={status}
instance={instance}
productName={KIBANA_SYSTEM_ID}
/>
</div>
<SetupModeBadge
setupMode={setupMode}
status={status}
instance={instance}
productName={KIBANA_SYSTEM_ID}
/>
);
if (status.isNetNewUser) {
return (
@ -188,10 +195,8 @@ const getColumns = (
return (
<div>
<div className="monTableCell__splitNumber">
{formatNumber(value, 'int_commas') + ' ms avg'}
</div>
<div className="monTableCell__splitNumber">
<div css={tableCellSplitNumber}>{formatNumber(value, 'int_commas') + ' ms avg'}</div>
<div css={tableCellSplitNumber}>
{formatNumber(kibana?.response_times?.max, 'int_commas')} ms max
</div>
</div>
@ -319,7 +324,7 @@ export const KibanaInstances: React.FC<Props> = (props: Props) => {
{setupModeCallOut}
<EuiPanel>
<EuiMonitoringTable
className="kibanaInstancesTable"
data-test-subj="kibanaInstancesTable"
rows={dataFlattened}
columns={getColumns(setupMode, alerts, dateFormat, staleStatusThresholdSeconds)}
sorting={sorting}

View file

@ -5,11 +5,18 @@
* 2.0.
*/
import { EuiFieldSearch, EuiOutsideClickDetector, EuiPanel } from '@elastic/eui';
import React from 'react';
import { css } from '@emotion/react';
import {
EuiFieldSearch,
EuiOutsideClickDetector,
EuiPanel,
logicalCSS,
UseEuiTheme,
} from '@elastic/eui';
import { QuerySuggestion } from '@kbn/unified-search-plugin/public';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { euiThemeVars } from '@kbn/ui-theme';
import { composeStateUpdaters } from '../../lib/typed_react';
import { SuggestionItem } from './suggestion_item';
@ -59,7 +66,7 @@ export class AutocompleteField extends React.Component<
return (
<EuiOutsideClickDetector onOutsideClick={this.handleBlur}>
<AutocompleteContainer>
<div css={autocompleteContainerCss}>
<EuiFieldSearch
fullWidth
disabled={disabled}
@ -76,7 +83,7 @@ export class AutocompleteField extends React.Component<
aria-label={ariaLabel}
/>
{areSuggestionsVisible && !isLoadingSuggestions && suggestions.length > 0 ? (
<SuggestionsPanel>
<EuiPanel css={suggestionsPanelCss} paddingSize="none" hasShadow>
{suggestions.map((suggestion, suggestionIndex) => (
<SuggestionItem
key={suggestion.text}
@ -86,9 +93,9 @@ export class AutocompleteField extends React.Component<
onClick={this.applySuggestionAt(suggestionIndex)}
/>
))}
</SuggestionsPanel>
</EuiPanel>
) : null}
</AutocompleteContainer>
</div>
</EuiOutsideClickDetector>
);
}
@ -299,19 +306,16 @@ const withUnfocused = (state: AutocompleteFieldState) => ({
isFocused: false,
});
const AutocompleteContainer = euiStyled.div`
const autocompleteContainerCss = css`
position: relative;
`;
const SuggestionsPanel = euiStyled(EuiPanel).attrs(() => ({
paddingSize: 'none',
hasShadow: true,
}))`
const suggestionsPanelCss = ({ euiTheme }: UseEuiTheme) => css`
position: absolute;
width: 100%;
margin-top: 2px;
${logicalCSS('margin-top', euiTheme.size.xxs)}
overflow-x: hidden;
overflow-y: scroll;
z-index: ${euiThemeVars.euiZLevel1};
z-index: ${euiTheme.levels.maskBelowHeader};
max-height: 322px;
`;

View file

@ -5,11 +5,12 @@
* 2.0.
*/
import { EuiIcon } from '@elastic/eui';
import { transparentize } from 'polished';
import React from 'react';
import { EuiIcon, UseEuiTheme, euiFontSize } from '@elastic/eui';
import { css } from '@emotion/react';
import { transparentize } from 'polished';
import { QuerySuggestion, QuerySuggestionTypes } from '@kbn/unified-search-plugin/public';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
interface Props {
isSelected?: boolean;
@ -18,69 +19,74 @@ interface Props {
suggestion: QuerySuggestion;
}
export const SuggestionItem: React.FC<Props> = (props) => {
const { isSelected, onClick, onMouseEnter, suggestion } = props;
export const SuggestionItem: React.FC<Props> = ({
isSelected = false,
onClick,
onMouseEnter,
suggestion,
}) => {
return (
<SuggestionItemContainer isSelected={isSelected} onClick={onClick} onMouseEnter={onMouseEnter}>
<SuggestionItemIconField suggestionType={suggestion.type}>
// TODO: should be focusable and have relevant key events; try using an existing component from EUI
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
<div
css={suggestionItemContainerStyle(isSelected)}
onClick={onClick}
onMouseEnter={onMouseEnter}
>
<div css={suggestionItemIconFieldStyle(suggestion.type)}>
<EuiIcon type={getEuiIconType(suggestion.type)} />
</SuggestionItemIconField>
<SuggestionItemTextField>{suggestion.text}</SuggestionItemTextField>
<SuggestionItemDescriptionField>{suggestion.description}</SuggestionItemDescriptionField>
</SuggestionItemContainer>
</div>
<div css={suggestionItemTextFieldStyle}>{suggestion.text}</div>
<div css={suggestionItemDescriptionFieldStyle}>{suggestion.description}</div>
</div>
);
};
SuggestionItem.defaultProps = {
isSelected: false,
};
const suggestionItemContainerStyle = (isSelected?: boolean) => (theme: UseEuiTheme) =>
css`
display: flex;
flex-direction: row;
font-size: ${euiFontSize(theme, 's').fontSize};
height: ${theme.euiTheme.size.xl};
white-space: nowrap;
background-color: ${isSelected ? theme.euiTheme.colors.lightestShade : 'transparent'};
`;
const SuggestionItemContainer = euiStyled.div<{
isSelected?: boolean;
}>`
display: flex;
flex-direction: row;
font-size: ${(props) => props.theme.eui.euiFontSizeS};
height: ${(props) => props.theme.eui.euiSizeXL};
white-space: nowrap;
background-color: ${(props) =>
props.isSelected ? props.theme.eui.euiColorLightestShade : 'transparent'};
`;
const SuggestionItemField = euiStyled.div`
const suggestionItemFieldStyle = ({ euiTheme }: UseEuiTheme) => css`
align-items: center;
cursor: pointer;
display: flex;
flex-direction: row;
height: ${(props) => props.theme.eui.euiSizeXL};
padding: ${(props) => props.theme.eui.euiSizeXS};
height: ${euiTheme.size.xl};
padding: ${euiTheme.size.xs};
`;
const SuggestionItemIconField = euiStyled(SuggestionItemField)<{
suggestionType: QuerySuggestionTypes;
}>`
background-color: ${(props) =>
transparentize(0.9, getEuiIconColor(props.theme, props.suggestionType))};
color: ${(props) => getEuiIconColor(props.theme, props.suggestionType)};
flex: 0 0 auto;
justify-content: center;
width: ${(props) => props.theme.eui.euiSizeXL};
`;
const suggestionItemIconFieldStyle =
(suggestionType: QuerySuggestionTypes) => (theme: UseEuiTheme) =>
css`
${suggestionItemFieldStyle(theme)};
background-color: ${transparentize(0.9, getEuiIconColor(theme, suggestionType))};
color: ${getEuiIconColor(theme, suggestionType)};
flex: 0 0 auto;
justify-content: center;
width: ${theme.euiTheme.size.xl};
`;
const SuggestionItemTextField = euiStyled(SuggestionItemField)`
const suggestionItemTextFieldStyle = (theme: UseEuiTheme) => css`
${suggestionItemFieldStyle(theme)};
flex: 2 0 0;
font-family: ${(props) => props.theme.eui.euiCodeFontFamily};
font-family: ${theme.euiTheme.font.familyCode};
`;
const SuggestionItemDescriptionField = euiStyled(SuggestionItemField)`
const suggestionItemDescriptionFieldStyle = (theme: UseEuiTheme) => css`
${suggestionItemFieldStyle(theme)};
flex: 3 0 0;
p {
display: inline;
span {
font-family: ${(props) => props.theme.eui.euiCodeFontFamily};
font-family: ${theme.euiTheme.font.familyCode};
}
}
`;
@ -102,18 +108,21 @@ const getEuiIconType = (suggestionType: QuerySuggestionTypes) => {
}
};
const getEuiIconColor = (theme: any, suggestionType: QuerySuggestionTypes): string => {
const getEuiIconColor = (
{ euiTheme }: UseEuiTheme,
suggestionType: QuerySuggestionTypes
): string => {
switch (suggestionType) {
case QuerySuggestionTypes.Field:
return theme?.eui.euiColorVis7;
return euiTheme.colors.vis.euiColorVis7;
case QuerySuggestionTypes.Value:
return theme?.eui.euiColorVis0;
return euiTheme.colors.vis.euiColorVis0;
case QuerySuggestionTypes.Operator:
return theme?.eui.euiColorVis1;
return euiTheme.colors.vis.euiColorVis1;
case QuerySuggestionTypes.Conjunction:
return theme?.eui.euiColorVis2;
return euiTheme.colors.vis.euiColorVis2;
case QuerySuggestionTypes.RecentSearch:
default:
return theme?.eui.euiColorMediumShade;
return euiTheme.colors.mediumShade;
}
};

View file

@ -2,7 +2,6 @@
exports[`Listing should render with certain data pieces missing 1`] = `
<EuiMonitoringTable
className="logstashNodesTable"
columns={
Array [
Object {
@ -55,6 +54,7 @@ exports[`Listing should render with certain data pieces missing 1`] = `
},
]
}
data-test-subj="logstashNodesTable"
executeQueryOptions={
Object {
"defaultFields": Array [
@ -104,7 +104,6 @@ exports[`Listing should render with certain data pieces missing 1`] = `
exports[`Listing should render with expected props 1`] = `
<EuiMonitoringTable
className="logstashNodesTable"
columns={
Array [
Object {
@ -157,6 +156,7 @@ exports[`Listing should render with expected props 1`] = `
},
]
}
data-test-subj="logstashNodesTable"
executeQueryOptions={
Object {
"defaultFields": Array [

View file

@ -52,14 +52,12 @@ export class Listing extends PureComponent {
};
setupModeStatus = (
<div className="monTableCell__setupModeStatus">
<SetupModeBadge
setupMode={setupMode}
status={status}
instance={instance}
productName={LOGSTASH_SYSTEM_ID}
/>
</div>
<SetupModeBadge
setupMode={setupMode}
status={status}
instance={instance}
productName={LOGSTASH_SYSTEM_ID}
/>
);
}
@ -202,7 +200,7 @@ export class Listing extends PureComponent {
{setupModeCallOut}
<EuiPanel>
<EuiMonitoringTable
className="logstashNodesTable"
data-test-subj="logstashNodesTable"
rows={flattenedData}
setupMode={setupMode}
productName={LOGSTASH_SYSTEM_ID}

View file

@ -7,6 +7,7 @@
import React, { Component } from 'react';
import moment from 'moment';
import { css } from '@emotion/react';
import { partialRight } from 'lodash';
import {
EuiPage,
@ -17,15 +18,22 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiScreenReaderOnly,
euiFontSize,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { formatMetric } from '../../../lib/format_number';
import { ClusterStatus } from '../cluster_status';
import { Sparkline } from '../../sparkline';
import { EuiMonitoringSSPTable } from '../../table';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link';
const tableCellNumberStyle = (theme) => css`
font-size: ${euiFontSize(theme, 'l').fontSize};
`;
export class PipelineListing extends Component {
tooltipXValueFormatter(xValue, dateFormat) {
return moment(xValue).format(dateFormat);
@ -76,7 +84,7 @@ export class PipelineListing extends Component {
options={{ xaxis: throughput.timeRange }}
/>
</EuiFlexItem>
<EuiFlexItem className="monTableCell__number" data-test-subj="eventsEmittedRate">
<EuiFlexItem css={tableCellNumberStyle} data-test-subj="eventsEmittedRate">
{formatMetric(value, '0.[0]a', throughput.metric.units)}
</EuiFlexItem>
</EuiFlexGroup>
@ -108,7 +116,7 @@ export class PipelineListing extends Component {
options={{ xaxis: nodesCount.timeRange }}
/>
</EuiFlexItem>
<EuiFlexItem className="monTableCell__number" data-test-subj="nodeCount">
<EuiFlexItem css={tableCellNumberStyle} data-test-subj="nodeCount">
{formatMetric(value, '0a')}
</EuiFlexItem>
</EuiFlexGroup>
@ -128,8 +136,7 @@ export class PipelineListing extends Component {
}
render() {
const { data, sorting, pagination, onTableChange, upgradeMessage, className, ...props } =
this.props;
const { data, sorting, pagination, onTableChange, upgradeMessage, ...props } = this.props;
const sortingOptions = sorting || { field: 'id', direction: 'asc' };
if (sortingOptions.field === 'name') {
@ -152,7 +159,7 @@ export class PipelineListing extends Component {
<EuiSpacer size="m" />
<EuiPanel>
<EuiMonitoringSSPTable
className={className || 'logstashNodesTable'}
data-test-subj={this.props['data-test-subj'] || 'logstashNodesTable'}
rows={data}
columns={columns}
sorting={sortingOptions}

View file

@ -3,7 +3,7 @@
exports[`CollapsibleStatement component renders child components 1`] = `
<EuiFlexGroup
alignItems="center"
className="monPipelineViewer__collapsibleStatement"
css={[Function]}
gutterSize="none"
responsive={false}
>

View file

@ -13,9 +13,7 @@ exports[`DetailDrawer component If vertices shows basic info and no stats for if
<EuiFlexItem
grow={false}
>
<EuiIcon
className="lspvDetailDrawerIcon"
/>
<EuiIcon />
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle>
@ -66,9 +64,7 @@ exports[`DetailDrawer component Plugin vertices Plugin does not have explicit ID
<EuiFlexItem
grow={false}
>
<EuiIcon
className="lspvDetailDrawerIcon"
/>
<EuiIcon />
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle>
@ -114,7 +110,17 @@ exports[`DetailDrawer component Plugin vertices Plugin does not have explicit ID
</EuiTableRowCell>
<EuiTableRowCell>
<div
className="lspvDetailDrawerSparklineContainer"
css={
Object {
"map": undefined,
"name": "b37u90",
"next": undefined,
"styles": "
width: 7vw;
",
"toString": [Function],
}
}
>
<Sparkline
options={
@ -162,7 +168,17 @@ exports[`DetailDrawer component Plugin vertices Plugin does not have explicit ID
</EuiTableRowCell>
<EuiTableRowCell>
<div
className="lspvDetailDrawerSparklineContainer"
css={
Object {
"map": undefined,
"name": "b37u90",
"next": undefined,
"styles": "
width: 7vw;
",
"toString": [Function],
}
}
>
<Sparkline
options={
@ -210,7 +226,17 @@ exports[`DetailDrawer component Plugin vertices Plugin does not have explicit ID
</EuiTableRowCell>
<EuiTableRowCell>
<div
className="lspvDetailDrawerSparklineContainer"
css={
Object {
"map": undefined,
"name": "b37u90",
"next": undefined,
"styles": "
width: 7vw;
",
"toString": [Function],
}
}
>
<Sparkline
options={
@ -258,7 +284,17 @@ exports[`DetailDrawer component Plugin vertices Plugin does not have explicit ID
</EuiTableRowCell>
<EuiTableRowCell>
<div
className="lspvDetailDrawerSparklineContainer"
css={
Object {
"map": undefined,
"name": "b37u90",
"next": undefined,
"styles": "
width: 7vw;
",
"toString": [Function],
}
}
>
<Sparkline
options={
@ -315,9 +351,7 @@ exports[`DetailDrawer component Plugin vertices Plugin has explicit ID shows bas
<EuiFlexItem
grow={false}
>
<EuiIcon
className="lspvDetailDrawerIcon"
/>
<EuiIcon />
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle>
@ -357,7 +391,17 @@ exports[`DetailDrawer component Plugin vertices Plugin has explicit ID shows bas
</EuiTableRowCell>
<EuiTableRowCell>
<div
className="lspvDetailDrawerSparklineContainer"
css={
Object {
"map": undefined,
"name": "b37u90",
"next": undefined,
"styles": "
width: 7vw;
",
"toString": [Function],
}
}
>
<Sparkline
options={
@ -405,7 +449,17 @@ exports[`DetailDrawer component Plugin vertices Plugin has explicit ID shows bas
</EuiTableRowCell>
<EuiTableRowCell>
<div
className="lspvDetailDrawerSparklineContainer"
css={
Object {
"map": undefined,
"name": "b37u90",
"next": undefined,
"styles": "
width: 7vw;
",
"toString": [Function],
}
}
>
<Sparkline
options={
@ -453,7 +507,17 @@ exports[`DetailDrawer component Plugin vertices Plugin has explicit ID shows bas
</EuiTableRowCell>
<EuiTableRowCell>
<div
className="lspvDetailDrawerSparklineContainer"
css={
Object {
"map": undefined,
"name": "b37u90",
"next": undefined,
"styles": "
width: 7vw;
",
"toString": [Function],
}
}
>
<Sparkline
options={
@ -501,7 +565,17 @@ exports[`DetailDrawer component Plugin vertices Plugin has explicit ID shows bas
</EuiTableRowCell>
<EuiTableRowCell>
<div
className="lspvDetailDrawerSparklineContainer"
css={
Object {
"map": undefined,
"name": "b37u90",
"next": undefined,
"styles": "
width: 7vw;
",
"toString": [Function],
}
}
>
<Sparkline
options={
@ -558,9 +632,7 @@ exports[`DetailDrawer component Queue vertices shows basic info and no stats for
<EuiFlexItem
grow={false}
>
<EuiIcon
className="lspvDetailDrawerIcon"
/>
<EuiIcon />
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle>
@ -603,9 +675,7 @@ exports[`DetailDrawer component shows vertex title 1`] = `
<EuiFlexItem
grow={false}
>
<EuiIcon
className="lspvDetailDrawerIcon"
/>
<EuiIcon />
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle>

View file

@ -2,12 +2,12 @@
exports[`Metric component does not render warning badge when no warning present 1`] = `
<EuiFlexItem
className="monPipelineViewer__metricFlexItem"
css={[Function]}
grow={false}
>
<EuiText
className="monPipelineViewer__metric metricClass"
color="subdued"
css={[Function]}
size="s"
>
<span>
@ -19,11 +19,10 @@ exports[`Metric component does not render warning badge when no warning present
exports[`Metric component renders warning badge 1`] = `
<EuiFlexItem
className="monPipelineViewer__metricFlexItem"
css={[Function]}
grow={false}
>
<EuiBadge
className="metricClass"
color="warning"
>
220

View file

@ -13,7 +13,7 @@ exports[`PipelineViewer component passes expected props 1`] = `
</EuiScreenReaderOnly>
<EuiPageSection
alignment="center"
className="monPipelineViewer"
data-test-subj="pipeline-viewer"
>
<StatementSection
elements={
@ -88,7 +88,7 @@ exports[`PipelineViewer component renders DetailDrawer when selected vertex is n
</EuiScreenReaderOnly>
<EuiPageSection
alignment="center"
className="monPipelineViewer"
data-test-subj="pipeline-viewer"
>
<StatementSection
elements={

View file

@ -3,7 +3,7 @@
exports[`PluginStatement component adds warning highlight for cpu time 1`] = `
<EuiFlexGroup
alignItems="center"
className="monPipelineViewer__pluginStatement"
css={[Function]}
gutterSize="none"
justifyContent="spaceBetween"
>
@ -20,8 +20,8 @@ exports[`PluginStatement component adds warning highlight for cpu time 1`] = `
>
<EuiButtonEmpty
aria-label="mutate"
className="monPipelineViewer__plugin"
color="primary"
css={[Function]}
flush="left"
iconType="dot"
onClick={[Function]}
@ -51,20 +51,20 @@ exports[`PluginStatement component adds warning highlight for cpu time 1`] = `
gutterSize="s"
>
<Metric
className="monPipelineViewer__metric--cpuTime"
key="cpuMetric"
type="cpuTime"
value="25%"
warning={true}
/>
<Metric
className="monPipelineViewer__metric--eventMillis"
key="eventMillis"
type="eventMillis"
value="100 ms/e"
warning={false}
/>
<Metric
className="monPipelineViewer__metric--events"
key="eventsReceived"
type="events"
value="120 e/s received"
/>
</EuiFlexGroup>
@ -75,7 +75,7 @@ exports[`PluginStatement component adds warning highlight for cpu time 1`] = `
exports[`PluginStatement component adds warning highlight for event millis 1`] = `
<EuiFlexGroup
alignItems="center"
className="monPipelineViewer__pluginStatement"
css={[Function]}
gutterSize="none"
justifyContent="spaceBetween"
>
@ -92,8 +92,8 @@ exports[`PluginStatement component adds warning highlight for event millis 1`] =
>
<EuiButtonEmpty
aria-label="mutate"
className="monPipelineViewer__plugin"
color="primary"
css={[Function]}
flush="left"
iconType="dot"
onClick={[Function]}
@ -123,20 +123,20 @@ exports[`PluginStatement component adds warning highlight for event millis 1`] =
gutterSize="s"
>
<Metric
className="monPipelineViewer__metric--cpuTime"
key="cpuMetric"
type="cpuTime"
value="25%"
warning={false}
/>
<Metric
className="monPipelineViewer__metric--eventMillis"
key="eventMillis"
type="eventMillis"
value="100 ms/e"
warning={true}
/>
<Metric
className="monPipelineViewer__metric--events"
key="eventsReceived"
type="events"
value="120 e/s received"
/>
</EuiFlexGroup>
@ -147,7 +147,7 @@ exports[`PluginStatement component adds warning highlight for event millis 1`] =
exports[`PluginStatement component does not render explicit id field if no id is specified 1`] = `
<EuiFlexGroup
alignItems="center"
className="monPipelineViewer__pluginStatement"
css={[Function]}
gutterSize="none"
justifyContent="spaceBetween"
>
@ -164,8 +164,8 @@ exports[`PluginStatement component does not render explicit id field if no id is
>
<EuiButtonEmpty
aria-label="stdin"
className="monPipelineViewer__plugin"
color="primary"
css={[Function]}
flush="left"
iconType="dot"
onClick={[Function]}
@ -185,8 +185,8 @@ exports[`PluginStatement component does not render explicit id field if no id is
gutterSize="s"
>
<Metric
className="monPipelineViewer__metric--eventsEmitted"
key="eventsEmitted"
type="eventsEmitted"
value="125 e/s emitted"
/>
</EuiFlexGroup>
@ -197,7 +197,7 @@ exports[`PluginStatement component does not render explicit id field if no id is
exports[`PluginStatement component renders input metrics and explicit id fields 1`] = `
<EuiFlexGroup
alignItems="center"
className="monPipelineViewer__pluginStatement"
css={[Function]}
gutterSize="none"
justifyContent="spaceBetween"
>
@ -214,8 +214,8 @@ exports[`PluginStatement component renders input metrics and explicit id fields
>
<EuiButtonEmpty
aria-label="stdin"
className="monPipelineViewer__plugin"
color="primary"
css={[Function]}
flush="left"
iconType="dot"
onClick={[Function]}
@ -245,8 +245,8 @@ exports[`PluginStatement component renders input metrics and explicit id fields
gutterSize="s"
>
<Metric
className="monPipelineViewer__metric--eventsEmitted"
key="eventsEmitted"
type="eventsEmitted"
value="125 e/s emitted"
/>
</EuiFlexGroup>
@ -257,7 +257,7 @@ exports[`PluginStatement component renders input metrics and explicit id fields
exports[`PluginStatement component renders processor statement metrics 1`] = `
<EuiFlexGroup
alignItems="center"
className="monPipelineViewer__pluginStatement"
css={[Function]}
gutterSize="none"
justifyContent="spaceBetween"
>
@ -274,8 +274,8 @@ exports[`PluginStatement component renders processor statement metrics 1`] = `
>
<EuiButtonEmpty
aria-label="mutate"
className="monPipelineViewer__plugin"
color="primary"
css={[Function]}
flush="left"
iconType="dot"
onClick={[Function]}
@ -305,20 +305,20 @@ exports[`PluginStatement component renders processor statement metrics 1`] = `
gutterSize="s"
>
<Metric
className="monPipelineViewer__metric--cpuTime"
key="cpuMetric"
type="cpuTime"
value="25%"
warning={false}
/>
<Metric
className="monPipelineViewer__metric--eventMillis"
key="eventMillis"
type="eventMillis"
value="100 ms/e"
warning={false}
/>
<Metric
className="monPipelineViewer__metric--events"
key="eventsReceived"
type="events"
value="120 e/s received"
/>
</EuiFlexGroup>

View file

@ -10,7 +10,7 @@ exports[`Queue component renders default elements 1`] = `
size="s"
/>
<EuiText
className="monPipelineViewer__queueMessage"
css={[Function]}
>
<MemoizedFormattedMessage
defaultMessage="Queue metrics not available"

View file

@ -5,7 +5,7 @@ exports[`Statement component renders a CollapsibleStatement with else body for n
className="monPipelineViewer__listItem"
>
<div
className="monPipelineViewer__spaceContainer"
css={[Function]}
/>
<CollapsibleStatement
collapse={[MockFunction]}
@ -25,7 +25,7 @@ exports[`Statement component renders a CollapsibleStatement with else body for n
size="xs"
>
<span
className="monPipelineViewer__conditional"
css={[Function]}
>
else
</span>
@ -40,7 +40,7 @@ exports[`Statement component renders a CollapsibleStatement with if body for bra
className="monPipelineViewer__listItem"
>
<div
className="monPipelineViewer__spaceContainer"
css={[Function]}
/>
<CollapsibleStatement
collapse={[MockFunction]}
@ -60,7 +60,7 @@ exports[`Statement component renders a CollapsibleStatement with if body for bra
size="xs"
>
<span
className="monPipelineViewer__conditional"
css={[Function]}
>
if
</span>
@ -85,7 +85,7 @@ exports[`Statement component renders a PluginStatement component for plugin mode
className="monPipelineViewer__listItem"
>
<div
className="monPipelineViewer__spaceContainer"
css={[Function]}
/>
<PluginStatement
onShowVertexDetails={[MockFunction]}
@ -117,14 +117,14 @@ exports[`Statement component renders spacers for element with depth > 0 1`] = `
className="monPipelineViewer__listItem"
>
<div
className="monPipelineViewer__spaceContainer"
css={[Function]}
>
<div
className="monPipelineViewer__spacer"
css={[Function]}
key="spacer_0"
/>
<div
className="monPipelineViewer__spacer"
css={[Function]}
key="spacer_1"
/>
</div>
@ -146,7 +146,7 @@ exports[`Statement component renders spacers for element with depth > 0 1`] = `
size="xs"
>
<span
className="monPipelineViewer__conditional"
css={[Function]}
>
else
</span>

View file

@ -1,3 +0,0 @@
.monPipelineViewer__collapsibleStatement {
padding-left: $euiSizeM;
}

View file

@ -6,18 +6,33 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { css } from '@emotion/react';
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import './collapsible_statement.scss';
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, logicalCSS, UseEuiTheme } from '@elastic/eui';
function getToggleIconType(isCollapsed) {
const collapsibleStatementStyle = ({ euiTheme }: UseEuiTheme) => css`
${logicalCSS('padding-left', euiTheme.size.m)}
`;
function getToggleIconType(isCollapsed: boolean) {
return isCollapsed ? 'arrowRight' : 'arrowDown';
}
export function CollapsibleStatement(props) {
const { collapse, expand, id, isCollapsed } = props;
interface CollapsibleStatementProps {
children: React.ReactNode;
collapse: (id: string) => void;
expand: (id: string) => void;
id: string;
isCollapsed: boolean;
}
export function CollapsibleStatement({
children,
collapse,
expand,
id,
isCollapsed,
}: CollapsibleStatementProps) {
const toggleClicked = () => {
if (isCollapsed) {
expand(id);
@ -28,10 +43,10 @@ export function CollapsibleStatement(props) {
return (
<EuiFlexGroup
responsive={false}
gutterSize="none"
alignItems="center"
className="monPipelineViewer__collapsibleStatement"
css={collapsibleStatementStyle}
gutterSize="none"
responsive={false}
>
<EuiFlexItem key={id} grow={false}>
<EuiButtonIcon
@ -42,14 +57,7 @@ export function CollapsibleStatement(props) {
size="s"
/>
</EuiFlexItem>
{props.children}
{children}
</EuiFlexGroup>
);
}
CollapsibleStatement.propTypes = {
collapse: PropTypes.func.isRequired,
expand: PropTypes.func.isRequired,
id: PropTypes.string.isRequired,
isCollapsed: PropTypes.bool.isRequired,
};

View file

@ -7,6 +7,7 @@
import React from 'react';
import { last } from 'lodash';
import { css } from '@emotion/react';
import {
EuiBadge,
EuiCodeBlock,
@ -24,13 +25,19 @@ import {
EuiText,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { Sparkline } from '../../../sparkline';
import { formatMetric } from '../../../../lib/format_number';
import { FormattedMessage } from '@kbn/i18n-react';
import './detail_drawer.scss';
// TODO: Why is this width here?
const lspvDetailDrawerSparklineContainerStyle = css`
width: 7vw;
`;
function renderIcon(vertex) {
return <EuiIcon type={vertex.iconType} className="lspvDetailDrawerIcon" />;
return <EuiIcon type={vertex.iconType} />;
}
function renderPluginBasicStats(vertex, timeseriesTooltipXValueFormatter) {
@ -50,7 +57,7 @@ function renderPluginBasicStats(vertex, timeseriesTooltipXValueFormatter) {
/>
</EuiTableRowCell>
<EuiTableRowCell>
<div className="lspvDetailDrawerSparklineContainer">
<div css={lspvDetailDrawerSparklineContainerStyle}>
<Sparkline
series={vertex.stats.millis_per_event.data}
options={{ xaxis: vertex.stats.millis_per_event.timeRange }}
@ -76,7 +83,7 @@ function renderPluginBasicStats(vertex, timeseriesTooltipXValueFormatter) {
/>
</EuiTableRowCell>
<EuiTableRowCell>
<div className="lspvDetailDrawerSparklineContainer">
<div css={lspvDetailDrawerSparklineContainerStyle}>
<Sparkline
series={vertex.eventsPerSecond.data}
options={{ xaxis: vertex.eventsPerSecond.timeRange }}
@ -108,7 +115,7 @@ function renderPluginBasicStats(vertex, timeseriesTooltipXValueFormatter) {
/>
</EuiTableRowCell>
<EuiTableRowCell>
<div className="lspvDetailDrawerSparklineContainer">
<div css={lspvDetailDrawerSparklineContainerStyle}>
<Sparkline
series={vertex.stats.events_in.data}
options={{ xaxis: vertex.stats.events_in.timeRange }}
@ -138,7 +145,7 @@ function renderPluginBasicStats(vertex, timeseriesTooltipXValueFormatter) {
/>
</EuiTableRowCell>
<EuiTableRowCell>
<div className="lspvDetailDrawerSparklineContainer">
<div css={lspvDetailDrawerSparklineContainerStyle}>
<Sparkline
series={vertex.stats.events_out.data}
options={{ xaxis: vertex.stats.events_out.timeRange }}

View file

@ -1,4 +0,0 @@
// SASSTODO: Why is this width here?
.lspvDetailDrawerSparklineContainer {
width: 7vw;
}

View file

@ -1,42 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { EuiFlexItem, EuiBadge, EuiText } from '@elastic/eui';
import classNames from 'classnames';
import './metric.scss';
export function Metric({ className, warning, value }) {
const classes = classNames('monPipelineViewer__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="monPipelineViewer__metricFlexItem" grow={false}>
{stylizedValue}
</EuiFlexItem>
);
}
Metric.propTypes = {
className: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
warning: PropTypes.bool,
};

View file

@ -1,27 +0,0 @@
.monPipelineViewer__metric {
text-align: right;
&--cpuTime {
width: $euiSizeXXL;
}
&--events,
&--eventsEmitted {
width: $euiSizeXXL * 4;
}
&--eventMillis {
width: $euiSizeXXL * 2;
}
}
@include euiBreakpoint('m') {
.monPipelineViewer__metricFlexItem {
margin-bottom: $euiSizeXS !important; // sass-lint:disable-line no-important
}
.monPipelineViewer__metric {
text-align: left;
padding-left: $euiSizeXL;
}
}

View file

@ -0,0 +1,74 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { css } from '@emotion/react';
import { EuiFlexItem, EuiBadge, EuiText, UseEuiTheme, logicalCSS } from '@elastic/eui';
type Type = 'cpuTime' | 'events' | 'eventsEmitted' | 'eventMillis';
const metricStyle =
(type: Type) =>
({ euiTheme }: UseEuiTheme) => {
let width: string;
switch (type) {
case 'cpuTime':
width = euiTheme.size.xxl;
break;
case 'events':
case 'eventsEmitted':
width = `calc(${euiTheme.size.xxl} * 4)`;
break;
case 'eventMillis':
width = `calc(${euiTheme.size.xxl} * 2)`;
break;
default:
width = 'auto';
}
return css`
text-align: right;
width: ${width};
@media (min-width: ${euiTheme.breakpoint.m}) {
text-align: left;
${logicalCSS('padding-left', euiTheme.size.xl)}
}
`;
};
const metricFlexItemStyle = ({ euiTheme }: UseEuiTheme) => css`
@media (min-width: ${euiTheme.breakpoint.m}) {
${logicalCSS('margin-bottom', euiTheme.size.xs)}
}
`;
interface MetricProps {
type: Type;
value: string;
warning?: boolean;
}
export function Metric({ type, warning, value }: MetricProps) {
let stylizedValue;
if (warning) {
stylizedValue = <EuiBadge color="warning">{value}</EuiBadge>;
} else {
stylizedValue = (
<EuiText css={metricStyle(type)} size="s" color="subdued">
<span>{value}</span>
</EuiText>
);
}
return (
<EuiFlexItem css={metricFlexItemStyle} grow={false}>
{stylizedValue}
</EuiFlexItem>
);
}

View file

@ -52,7 +52,7 @@ export class PipelineViewer extends React.Component {
/>
</h1>
</EuiScreenReaderOnly>
<EuiPageSection alignment="center" className="monPipelineViewer">
<EuiPageSection data-test-subj="pipeline-viewer" alignment="center">
<StatementSection
iconType="logstashInput"
headingText={i18n.translate('xpack.monitoring.logstash.pipelineViewer.inputsTitle', {

View file

@ -1,7 +0,0 @@
.monPipelineViewer__plugin {
margin-left: $euiSizeXS;
}
.monPipelineViewer__pluginStatement {
padding-left: $euiSizeM;
}

View file

@ -6,31 +6,48 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiBadge } from '@elastic/eui';
import { css } from '@emotion/react';
import {
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiBadge,
UseEuiTheme,
logicalCSS,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { formatMetric } from '../../../../lib/format_number';
import { Metric } from './metric';
import { i18n } from '@kbn/i18n';
import './plugin_statement.scss';
import { Vertex } from './types';
function getInputStatementMetrics({ latestEventsPerSecond }) {
const pluginStyle = ({ euiTheme }: UseEuiTheme) => css`
${logicalCSS('margin-left', euiTheme.size.xs)}
`;
const pluginStatementStyle = ({ euiTheme }: UseEuiTheme) => css`
${logicalCSS('padding-left', euiTheme.size.m)}
`;
function getInputStatementMetrics({ latestEventsPerSecond }: Vertex) {
return [
<Metric
key="eventsEmitted"
className="monPipelineViewer__metric--eventsEmitted"
type="eventsEmitted"
value={formatMetric(latestEventsPerSecond, '0.[00]a', 'e/s emitted')}
/>,
];
}
function getProcessorStatementMetrics(processorVertex) {
function getProcessorStatementMetrics(processorVertex: Vertex) {
const { latestMillisPerEvent, latestEventsPerSecond, percentOfTotalProcessorTime } =
processorVertex;
return [
<Metric
key="cpuMetric"
className="monPipelineViewer__metric--cpuTime"
type="cpuTime"
warning={processorVertex.isTimeConsuming()}
value={formatMetric(Math.round(percentOfTotalProcessorTime || 0), '0', '%', {
prependSpace: false,
@ -38,29 +55,43 @@ function getProcessorStatementMetrics(processorVertex) {
/>,
<Metric
key="eventMillis"
className="monPipelineViewer__metric--eventMillis"
type="eventMillis"
warning={processorVertex.isSlow()}
value={formatMetric(latestMillisPerEvent, '0.[00]a', 'ms/e')}
/>,
<Metric
key="eventsReceived"
className="monPipelineViewer__metric--events"
type="events"
value={formatMetric(latestEventsPerSecond, '0.[00]a', 'e/s received')}
/>,
];
}
function renderPluginStatementMetrics(pluginType, vertex) {
function renderPluginStatementMetrics(pluginType: string, vertex: Vertex) {
return pluginType === 'input'
? getInputStatementMetrics(vertex)
: getProcessorStatementMetrics(vertex);
}
interface Statement {
hasExplicitId: boolean;
id: string;
name: string;
pluginType: string;
vertex: Vertex;
}
interface PluginStatementProps {
onShowVertexDetails: (vertex: Vertex) => void;
statement: Statement;
}
export function PluginStatement({
statement: { hasExplicitId, id, name, pluginType, vertex },
onShowVertexDetails,
}) {
}: PluginStatementProps) {
const statementMetrics = renderPluginStatementMetrics(pluginType, vertex);
const onNameButtonClick = () => {
onShowVertexDetails(vertex);
};
@ -68,7 +99,7 @@ export function PluginStatement({
return (
<EuiFlexGroup
alignItems="center"
className="monPipelineViewer__pluginStatement"
css={pluginStatementStyle}
gutterSize="none"
justifyContent="spaceBetween"
>
@ -77,8 +108,8 @@ export function PluginStatement({
<EuiFlexItem grow={false}>
<EuiButtonEmpty
aria-label={name}
className="monPipelineViewer__plugin"
color="primary"
css={pluginStyle}
flush="left"
iconType="dot"
onClick={onNameButtonClick}
@ -93,9 +124,7 @@ export function PluginStatement({
onClick={onNameButtonClick}
onClickAriaLabel={i18n.translate(
'xpack.monitoring.logstash.pipelineStatement.viewDetailsAriaLabel',
{
defaultMessage: 'View details',
}
{ defaultMessage: 'View details' }
)}
>
{id}
@ -112,18 +141,3 @@ export function PluginStatement({
</EuiFlexGroup>
);
}
PluginStatement.propTypes = {
onShowVertexDetails: PropTypes.func.isRequired,
statement: PropTypes.shape({
hasExplicitId: PropTypes.bool.isRequired,
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
pluginType: PropTypes.string.isRequired,
vertex: PropTypes.shape({
latestEventsPerSecond: PropTypes.number.isRequired,
latestMillisPerEvent: PropTypes.number,
percentOfTotalProcessorTime: PropTypes.number,
}).isRequired,
}).isRequired,
};

View file

@ -1,4 +0,0 @@
.monPipelineViewer__queueMessage {
margin-left: $euiSizeL;
color: $euiColorDarkShade;
}

View file

@ -6,17 +6,24 @@
*/
import React from 'react';
import { StatementListHeading } from './statement_list_heading';
import { EuiSpacer, EuiText } from '@elastic/eui';
import { css } from '@emotion/react';
import { EuiSpacer, EuiText, UseEuiTheme, logicalCSS } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import './queue.scss';
import { StatementListHeading } from './statement_list_heading';
const queueMessageStyle = ({ euiTheme }: UseEuiTheme) => css`
${logicalCSS('margin-left', euiTheme.size.l)}
color: ${euiTheme.colors.darkShade};
`;
export function Queue() {
return (
<div>
<StatementListHeading iconType="logstashQueue" title="Queue" />
<EuiSpacer size="s" />
<EuiText className="monPipelineViewer__queueMessage">
<EuiText css={queueMessageStyle}>
<FormattedMessage
id="xpack.monitoring.logstash.pipeline.queue.noMetricsDescription"
defaultMessage="Queue metrics not available"

View file

@ -1,113 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { EuiButtonEmpty, EuiCodeBlock, EuiFlexItem } from '@elastic/eui';
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';
import './statement.scss';
function renderStatementName(name, onVertexSelected) {
return (
<EuiFlexItem grow={false} key="statementName">
<EuiButtonEmpty
aria-label={name}
color="text"
size="xs"
onClick={onVertexSelected}
flush="left"
>
<span className="monPipelineViewer__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, vertex, onShowVertexDetails) {
const showVertexDetailsClicked = () => {
onShowVertexDetails(vertex);
};
return isIf
? renderIfStatement(statement, showVertexDetailsClicked)
: renderStatementName('else', showVertexDetailsClicked);
}
function renderNestingSpacers(depth) {
const spacers = [];
for (let i = 0; i < depth; i += 1) {
spacers.push(<div key={`spacer_${i}`} className="monPipelineViewer__spacer" />);
}
return spacers;
}
function renderStatement({
collapse,
element,
element: {
id,
statement,
statement: { vertex },
},
expand,
isCollapsed,
onShowVertexDetails,
}) {
if (statement instanceof PluginStatementModel) {
return <PluginStatement statement={statement} onShowVertexDetails={onShowVertexDetails} />;
}
const statementBody = getStatementBody(
element instanceof IfElement,
statement,
vertex,
onShowVertexDetails
);
return (
<CollapsibleStatement expand={expand} collapse={collapse} isCollapsed={isCollapsed} id={id}>
{statementBody}
</CollapsibleStatement>
);
}
export function Statement(props) {
const { depth } = props.element;
return (
<li className={`monPipelineViewer__listItem`}>
<div className="monPipelineViewer__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,
};

View file

@ -1,47 +0,0 @@
.monPipelineViewer__spaceContainer {
background-color: $euiColorEmptyShade;
align-self: stretch;
display: flex;
// Separates the left border spaces properly
border-bottom: solid 2px $euiColorEmptyShade;
}
.monPipelineViewer__spacer {
width: $euiSizeM;
align-self: stretch;
margin-left: $euiSizeM;
border-left: 1px $euiBorderColor dashed;
// This allows the border to be flush
&:last-child {
width: 0;
}
&:first-child {
// Odd number is because of the single pixel border.
margin-left: $euiSizeL - 1px;
}
}
.monPipelineViewer__list {
.monPipelineViewer__listItem {
display: flex;
min-height: $euiSizeXL;
align-items: center;
padding-right: $euiSizeM;
&:nth-child(2n + 1) {
background: tintOrShade($euiColorLightestShade, 2%, 2%);
}
}
}
.monPipelineViewer__conditional {
font-weight: bold;
}
@include euiBreakpoint('m') {
.monPipelineViewer__spacer {
border: none;
}
}

View file

@ -0,0 +1,170 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { MouseEventHandler } from 'react';
import { css } from '@emotion/react';
import { EuiButtonEmpty, EuiCodeBlock, EuiFlexItem, logicalCSS, UseEuiTheme } from '@elastic/eui';
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';
import { Vertex } from './types';
const spaceContainerStyle = ({ euiTheme }: UseEuiTheme) => css`
background-color: ${euiTheme.colors.backgroundBasePlain};
align-self: stretch;
display: flex;
// Separates the left border spaces properly
${logicalCSS('border-bottom', `solid 2px ${euiTheme.colors.emptyShade}`)}
`;
const spacerStyle = ({ euiTheme }: UseEuiTheme) => css`
width: ${euiTheme.size.m};
align-self: stretch;
${logicalCSS('margin-left', euiTheme.size.m)}
${logicalCSS('border-left', `1px ${euiTheme.border.color} dashed`)}
// This allows the border to be flush
&:last-child {
width: 0;
}
&:first-child {
// Odd number is because of the single pixel border
${logicalCSS('margin-left', `calc(${euiTheme.size.l}) - 1px)`)}
}
@media (min-width: var(${euiTheme.breakpoint.m})) {
border: none;
}
`;
const listItemStyle = ({ euiTheme }: UseEuiTheme) => css`
display: flex;
min-height: ${euiTheme.size.xl};
align-items: center;
${logicalCSS('padding-right', euiTheme.size.m)}
&:nth-child(2n + 1) {
background: ${euiTheme.colors.lightestShade};
}
`;
const conditionalStyle = ({ euiTheme }: UseEuiTheme) => css`
font-weight: ${euiTheme.font.weight.bold};
`;
function renderStatementName(name: string, onVertexSelected: MouseEventHandler<HTMLButtonElement>) {
return (
<EuiFlexItem grow={false} key="statementName">
<EuiButtonEmpty
aria-label={name}
color="text"
size="xs"
onClick={onVertexSelected}
flush="left"
>
<span css={conditionalStyle}>{name}</span>
</EuiButtonEmpty>
</EuiFlexItem>
);
}
function renderIfStatement(
{ condition }: { vertex?: Vertex; condition?: string },
onVertexSelected: MouseEventHandler<HTMLButtonElement>
) {
return [
renderStatementName('if', onVertexSelected),
<EuiFlexItem key="ifContent" grow={false}>
<EuiCodeBlock fontSize="s" paddingSize="none" transparentBackground={true}>
{condition}
</EuiCodeBlock>
</EuiFlexItem>,
];
}
function getStatementBody(
isIf: boolean,
statement: { vertex?: Vertex; condition?: string },
vertex: Vertex,
onShowVertexDetails: (vertex: Vertex) => void
) {
const showVertexDetailsClicked = () => {
onShowVertexDetails(vertex);
};
return isIf
? renderIfStatement(statement, showVertexDetailsClicked)
: renderStatementName('else', showVertexDetailsClicked);
}
function renderNestingSpacers(depth: number) {
const spacers = [];
for (let i = 0; i < depth; i += 1) {
spacers.push(<div key={`spacer_${i}`} css={spacerStyle} />);
}
return spacers;
}
interface StatementProps {
collapse: () => void;
element: {
depth: number;
id: string;
statement: {
vertex: Vertex;
};
};
expand: () => void;
isCollapsed: boolean;
onShowVertexDetails: (vertex: Vertex) => void;
}
function renderStatement({
collapse,
element,
element: {
id,
statement,
statement: { vertex },
},
expand,
isCollapsed,
onShowVertexDetails,
}: StatementProps) {
if (statement instanceof PluginStatementModel) {
return <PluginStatement statement={statement} onShowVertexDetails={onShowVertexDetails} />;
}
const statementBody = getStatementBody(
element instanceof IfElement,
statement,
vertex,
onShowVertexDetails
);
return (
<CollapsibleStatement expand={expand} collapse={collapse} isCollapsed={isCollapsed} id={id}>
{statementBody}
</CollapsibleStatement>
);
}
export function Statement(props: StatementProps) {
const { depth } = props.element;
return (
<li className={`monPipelineViewer__listItem`} css={listItemStyle}>
<div css={spaceContainerStyle}>{renderNestingSpacers(depth)}</div>
{renderStatement(props)}
</li>
);
}

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export interface Vertex {
latestEventsPerSecond: number;
latestMillisPerEvent?: number;
percentOfTotalProcessorTime?: number;
isTimeConsuming: () => boolean;
isSlow: () => boolean;
}

View file

@ -3,7 +3,11 @@
exports[`Sparkline component does not show tooltip initially 1`] = `
<div>
<div
className="monSparkline"
css={
Object {
"height": "2em",
}
}
/>
</div>
`;
@ -11,7 +15,11 @@ exports[`Sparkline component does not show tooltip initially 1`] = `
exports[`Sparkline component shows tooltip on hover 1`] = `
<div>
<div
className="monSparkline"
css={
Object {
"height": "2em",
}
}
/>
<div
className="monSparklineTooltip__container"
@ -23,7 +31,7 @@ exports[`Sparkline component shows tooltip on hover 1`] = `
}
>
<i
className="fa fa-caret-left monSparklineTooltip__caret"
className="fa fa-caret-left"
style={
Object {
"display": "block",
@ -32,7 +40,7 @@ exports[`Sparkline component shows tooltip on hover 1`] = `
}
/>
<div
className="monSparklineTooltip"
css={[Function]}
style={
Object {
"height": 56,
@ -41,18 +49,18 @@ exports[`Sparkline component shows tooltip on hover 1`] = `
}
>
<div
className="monSparklineTooltip__yValue"
css={[Function]}
>
1513814914
</div>
<div
className="monSparklineTooltip__xValue"
css={[Function]}
>
25
</div>
</div>
<i
className="fa fa-caret-right monSparklineTooltip__caret"
className="fa fa-caret-right"
style={
Object {
"width": 6,

View file

@ -5,11 +5,47 @@
* 2.0.
*/
import { isEqual } from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import React from 'react';
import { css } from '@emotion/react';
import { isEqual } from 'lodash';
import { transparentize } from 'polished';
import { euiFontSize } from '@elastic/eui';
import { SparklineFlotChart } from './sparkline_flot_chart';
import './sparkline.scss';
// TODO: Replace with EUI tooltip
const sparklineTooltipStyle = (theme) => css`
font-weight: ${theme.euiTheme.font.weight.regular};
background: ${transparentize(0.3, theme.euiTheme.colors.darkestShade)};
font-size: ${euiFontSize(theme, 'xs').fontSize};
padding: ${theme.euiTheme.size.xs};
border-radius: ${theme.euiTheme.border.radius.medium};
pointer-events: none;
`;
const tooltipXValueStyle = ({ euiTheme }) => css`
color: ${transparentize(0.3, euiTheme.colors.ghost)};
`;
const tooltipYValueStyle = ({ euiTheme }) => css`
color: ${euiTheme.colors.ghost};
`;
const tooltipContainerStyle = ({ euiTheme }) => css`
position: fixed;
z-index: ${euiTheme.levels.menu};
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
`;
const tooltipCaretStyle = (theme) => css`
font-size: ${euiFontSize(theme, 'l').fontSize};
color: ${transparentize(0.3, theme.euiTheme.colors.darkestShade)};
display: none;
`;
export class Sparkline extends React.Component {
constructor(props) {
@ -97,17 +133,21 @@ export class Sparkline extends React.Component {
}
return (
<div className="monSparklineTooltip__container" style={styles.tooltipContainer}>
<i className="fa fa-caret-left monSparklineTooltip__caret" style={styles.leftCaret} />
<div className="monSparklineTooltip" style={styles.tooltip}>
<div className="monSparklineTooltip__yValue">
<div
className="monSparklineTooltip__container"
css={tooltipContainerStyle}
style={styles.tooltipContainer}
>
<i className="fa fa-caret-left" css={tooltipCaretStyle} style={styles.leftCaret} />
<div css={sparklineTooltipStyle} style={styles.tooltip}>
<div css={tooltipYValueStyle}>
{this.props.tooltip.yValueFormatter(this.state.tooltip.yValue)}
</div>
<div className="monSparklineTooltip__xValue">
<div css={tooltipXValueStyle}>
{this.props.tooltip.xValueFormatter(this.state.tooltip.xValue)}
</div>
</div>
<i className="fa fa-caret-right monSparklineTooltip__caret" style={styles.rightCaret} />
<i className="fa fa-caret-right" css={tooltipCaretStyle} style={styles.rightCaret} />
</div>
);
}
@ -124,7 +164,7 @@ export class Sparkline extends React.Component {
render() {
return (
<div>
<div className="monSparkline" ref={this.handleSparklineRef} />
<div css={{ height: '2em' }} ref={this.handleSparklineRef} />
{this.renderTooltip()}
</div>
);

View file

@ -1,36 +0,0 @@
.monSparkline {
height: 2em;
}
// SASSTODO: Replace with EUI tooltip
.monSparklineTooltip {
font-weight: normal;
background: transparentize($euiColorDarkestShade, .3);
font-size: $euiFontSizeXS;
padding: $euiSizeXS;
border-radius: $euiBorderRadius;
pointer-events: none;
}
.monSparklineTooltip__xValue {
color: transparentize($euiColorGhost, .3);
}
.monSparklineTooltip__yValue {
color: $euiColorGhost;
}
.monSparklineTooltip__caret {
font-size: $euiFontSizeL;
color: transparentize($euiColorDarkestShade, .3);
display: none;
}
.monSparklineTooltip__container {
position: fixed;
z-index: $euiZContentMenu;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}

View file

@ -1,25 +0,0 @@
.monStatusIcon {
display: inline-block;
margin-left: $euiSizeXS;
padding: calc($euiSizeXS / 2) $euiSizeS;
border-radius: $euiBorderRadius;
color: $euiColorGhost;
min-width: 1.9em;
text-align: center;
}
.monStatusIcon--green {
background-color: $euiColorSuccess;
}
.monStatusIcon--red {
background-color: $euiColorDanger;
}
.monStatusIcon--yellow {
background-color: $euiColorWarning;
}
.monStatusIcon--gray {
background-color: $euiTextColor;
}

View file

@ -26,6 +26,7 @@ export interface StatusIconProps {
type: keyof typeof STATUS_ICON_TYPES;
label: string;
}
export const StatusIcon: React.FunctionComponent<StatusIconProps> = ({ type, label }) => {
const icon = typeToIconMap[type];

View file

@ -1,9 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Summary Status Component should allow label to be optional 1`] = `
<div
class="monSummaryStatusNoWrap"
>
<div>
<div
class="euiFlexGroup emotion-euiFlexGroup-responsive-m-spaceBetween-center-row"
>
@ -12,7 +10,7 @@ exports[`Summary Status Component should allow label to be optional 1`] = `
style="max-width:200px"
>
<div
class="euiStat monSummaryStatusNoWrap__stat emotion-euiStat-left"
class="euiStat emotion-EuiStat"
data-test-subj="status"
>
<div
@ -42,7 +40,7 @@ exports[`Summary Status Component should allow label to be optional 1`] = `
style="max-width:200px"
>
<div
class="euiStat monSummaryStatusNoWrap__stat emotion-euiStat-left"
class="euiStat emotion-EuiStat"
>
<div
class="euiText euiStat__description emotion-euiText-s"
@ -62,7 +60,7 @@ exports[`Summary Status Component should allow label to be optional 1`] = `
style="max-width:200px"
>
<div
class="euiStat monSummaryStatusNoWrap__stat emotion-euiStat-left"
class="euiStat emotion-EuiStat"
>
<div
class="euiText euiStat__description emotion-euiText-s"
@ -83,9 +81,7 @@ exports[`Summary Status Component should allow label to be optional 1`] = `
`;
exports[`Summary Status Component should allow status to be optional 1`] = `
<div
class="monSummaryStatusNoWrap"
>
<div>
<div
class="euiFlexGroup emotion-euiFlexGroup-responsive-m-spaceBetween-center-row"
>
@ -99,7 +95,7 @@ exports[`Summary Status Component should allow status to be optional 1`] = `
style="max-width:200px"
>
<div
class="euiStat monSummaryStatusNoWrap__stat emotion-euiStat-left"
class="euiStat emotion-EuiStat"
>
<div
class="euiText euiStat__description emotion-euiText-s"
@ -121,7 +117,7 @@ exports[`Summary Status Component should allow status to be optional 1`] = `
style="max-width:200px"
>
<div
class="euiStat monSummaryStatusNoWrap__stat emotion-euiStat-left"
class="euiStat emotion-EuiStat"
>
<div
class="euiText euiStat__description emotion-euiText-s"
@ -142,9 +138,7 @@ exports[`Summary Status Component should allow status to be optional 1`] = `
`;
exports[`Summary Status Component should render metrics in a summary bar 1`] = `
<div
class="monSummaryStatusNoWrap"
>
<div>
<div
class="euiFlexGroup emotion-euiFlexGroup-responsive-m-spaceBetween-center-row"
>
@ -153,7 +147,7 @@ exports[`Summary Status Component should render metrics in a summary bar 1`] = `
style="max-width:200px"
>
<div
class="euiStat monSummaryStatusNoWrap__stat emotion-euiStat-left"
class="euiStat emotion-EuiStat"
data-test-subj="status"
>
<div
@ -183,7 +177,7 @@ exports[`Summary Status Component should render metrics in a summary bar 1`] = `
style="max-width:200px"
>
<div
class="euiStat monSummaryStatusNoWrap__stat emotion-euiStat-left"
class="euiStat emotion-EuiStat"
>
<div
class="euiText euiStat__description emotion-euiText-s"
@ -205,7 +199,7 @@ exports[`Summary Status Component should render metrics in a summary bar 1`] = `
style="max-width:200px"
>
<div
class="euiStat monSummaryStatusNoWrap__stat emotion-euiStat-left"
class="euiStat emotion-EuiStat"
>
<div
class="euiText euiStat__description emotion-euiText-s"

View file

@ -1,10 +0,0 @@
.monSummaryStatusNoWrap {
margin-left: $euiSizeM;
margin-right: $euiSizeM;
.monSummaryStatusNoWrap__stat {
p {
@include euiTextTruncate;
}
}
}

View file

@ -6,14 +6,34 @@
*/
import React, { Fragment } from 'react';
import {
EuiFlexGroup,
EuiFlexItem,
EuiStat,
UseEuiTheme,
euiTextTruncate,
logicalCSS,
} from '@elastic/eui';
import { capitalize } from 'lodash';
import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui';
import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { StatusIcon, StatusIconProps } from '../status_icon';
import { AlertsStatus } from '../../alerts/status';
import type { AlertsByName } from '../../alerts/types';
import './summary_status.scss';
const summaryStatusNoWrapStyle = ({ euiTheme }: UseEuiTheme) => css`
${logicalCSS('margin-left', euiTheme.size.m)}
${logicalCSS('margin-right', euiTheme.size.m)}
`;
const summaryStatusNoWrapStatStyle = css`
p {
${euiTextTruncate()}
}
`;
interface Metrics {
label: string;
@ -46,7 +66,7 @@ const wrapChild = ({ label, value, ...props }: Metrics, index: number) => (
>
<EuiStat
title={value}
className="monSummaryStatusNoWrap__stat"
css={summaryStatusNoWrapStatStyle}
titleSize="xxxs"
textAlign="left"
description={label ? `${label}` : ''}
@ -102,7 +122,7 @@ export const DefaultStatusIndicator = ({
}
titleSize="xxxs"
textAlign="left"
className="monSummaryStatusNoWrap__stat"
css={summaryStatusNoWrapStatStyle}
description={i18n.translate('xpack.monitoring.summaryStatus.statusDescription', {
defaultMessage: 'Status',
})}
@ -120,7 +140,7 @@ export function SummaryStatus({
...props
}: SummaryProps) {
return (
<div {...props} className="monSummaryStatusNoWrap">
<div {...props} css={summaryStatusNoWrapStyle}>
<EuiFlexGroup gutterSize="m" alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem
className="eui-textTruncate"
@ -136,7 +156,7 @@ export function SummaryStatus({
title={<AlertsStatus showOnlyCount={true} alerts={alerts} />}
titleSize="xxxs"
textAlign="left"
className="monSummaryStatusNoWrap__stat"
css={summaryStatusNoWrapStatStyle}
description={i18n.translate('xpack.monitoring.summaryStatus.alertsDescription', {
defaultMessage: 'Alerts',
})}

View file

@ -67,15 +67,15 @@ export const EuiMonitoringTable: FunctionComponent<Record<any, any>> = ({
}
return (
<div data-test-subj={`${props.className}Container`}>
<div data-test-subj={`${props['data-test-subj']}Container`}>
<EuiInMemoryTable
data-test-subj={
items.length && hasItems === true ? 'monitoringTableHasData' : 'monitoringTableNoData'
}
items={items}
search={search}
columns={columns}
{...props}
data-test-subj={
items.length && hasItems ? 'monitoringTableHasData' : 'monitoringTableNoData'
}
/>
{footerContent}
</div>

View file

@ -100,7 +100,7 @@ export function EuiMonitoringSSPTable({
};
return (
<div data-test-subj={`${props.className}Container`}>
<div data-test-subj={`${props['data-test-subj']}Container`}>
<EuiSearchBar {...search} onChange={onQueryChange} />
<EuiSpacer size="l" />
<EuiBasicTable

View file

@ -1,8 +0,0 @@
// Monitoring plugin styles
// Prefix all styles with "mon" to avoid conflicts.
// Examples
// monChart
// monChart__legend
// monChart__legend--small
// monChart__legend-isLoading

View file

@ -1,9 +1,15 @@
{
"extends": "../../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"outDir": "target/types"
},
"include": ["common/**/*", "public/**/*", "server/**/*", "server/**/*.json"],
"include": [
"common/**/*",
"public/**/*",
"server/**/*",
"server/**/*.json",
"../../../../../typings/emotion.d.ts"
],
"kbn_references": [
"@kbn/core",
"@kbn/data-plugin",
@ -43,12 +49,9 @@
"@kbn/react-kibana-mount",
"@kbn/react-kibana-context-render",
"@kbn/flot-charts",
"@kbn/ui-theme",
"@kbn/core-elasticsearch-server",
"@kbn/share-plugin",
"@kbn/analytics",
"@kbn/analytics"
],
"exclude": [
"target/**/*",
]
"exclude": ["target/**/*"]
}

View file

@ -10,7 +10,7 @@ export function MonitoringLogstashPipelineViewerProvider({ getService }) {
const retry = getService('retry');
const find = getService('find');
const PIPELINE_VIEWER_SELECTOR = '.monPipelineViewer';
const PIPELINE_VIEWER_SELECTOR = '[data-test-subj*="pipeline-viewer"]';
const SUBJ_PIPELINE_SECTION_PREFIX = 'pipelineViewerSection_';
const PIPELINE_SECTION_ITEM_CLS = 'monPipelineViewer__listItem';