Skip to content

Commit

Permalink
[Metrics UI] Add support for multiple groupings to Metrics Explorer (…
Browse files Browse the repository at this point in the history
…and Alerts) (#66503) (#67065)

* [Metrics UI] Adding support for multiple groupings to Metrics Explorer

* Adding keys to title parts

* removing commented line

Co-authored-by: Elastic Machine <[email protected]>

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
simianhacker and elasticmachine authored May 19, 2020
1 parent 4a7e462 commit fbd76d3
Show file tree
Hide file tree
Showing 20 changed files with 256 additions and 85 deletions.
24 changes: 16 additions & 8 deletions x-pack/plugins/infra/common/http_api/metrics_explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,12 @@ export const metricsExplorerRequestBodyRequiredFieldsRT = rt.type({
metrics: rt.array(metricsExplorerMetricRT),
});

const groupByRT = rt.union([rt.string, rt.null, rt.undefined]);
export const afterKeyObjectRT = rt.record(rt.string, rt.union([rt.string, rt.null]));

export const metricsExplorerRequestBodyOptionalFieldsRT = rt.partial({
groupBy: rt.union([rt.string, rt.null, rt.undefined]),
afterKey: rt.union([rt.string, rt.null, rt.undefined]),
groupBy: rt.union([groupByRT, rt.array(groupByRT)]),
afterKey: rt.union([rt.string, rt.null, rt.undefined, afterKeyObjectRT]),
limit: rt.union([rt.number, rt.null, rt.undefined]),
filterQuery: rt.union([rt.string, rt.null, rt.undefined]),
forceInterval: rt.boolean,
Expand All @@ -68,7 +71,7 @@ export const metricsExplorerRequestBodyRT = rt.intersection([

export const metricsExplorerPageInfoRT = rt.type({
total: rt.number,
afterKey: rt.union([rt.string, rt.null]),
afterKey: rt.union([rt.string, rt.null, afterKeyObjectRT]),
});

export const metricsExplorerColumnTypeRT = rt.keyof({
Expand All @@ -89,11 +92,16 @@ export const metricsExplorerRowRT = rt.intersection([
rt.record(rt.string, rt.union([rt.string, rt.number, rt.null, rt.undefined])),
]);

export const metricsExplorerSeriesRT = rt.type({
id: rt.string,
columns: rt.array(metricsExplorerColumnRT),
rows: rt.array(metricsExplorerRowRT),
});
export const metricsExplorerSeriesRT = rt.intersection([
rt.type({
id: rt.string,
columns: rt.array(metricsExplorerColumnRT),
rows: rt.array(metricsExplorerRowRT),
}),
rt.partial({
keys: rt.array(rt.string),
}),
]);

export const metricsExplorerResponseRT = rt.type({
series: rt.array(metricsExplorerSeriesRT),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export const Expressions: React.FC<Props> = props => {
]);

const onGroupByChange = useCallback(
(group: string | null) => {
(group: string | null | string[]) => {
setAlertParams('groupBy', group || '');
},
[setAlertParams]
Expand Down Expand Up @@ -206,7 +206,10 @@ export const Expressions: React.FC<Props> = props => {
convertKueryToElasticSearchQuery(md.currentOptions.filterQuery, derivedIndexPattern) || ''
);
} else if (md && md.currentOptions?.groupBy && md.series) {
const filter = `${md.currentOptions?.groupBy}: "${md.series.id}"`;
const { groupBy } = md.currentOptions;
const filter = Array.isArray(groupBy)
? groupBy.map((field, index) => `${field}: "${md.series?.keys?.[index]}"`).join(' and ')
: `${groupBy}: "${md.series.id}"`;
setAlertParams('filterQueryText', filter);
setAlertParams(
'filterQuery',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export enum AGGREGATION_TYPES {

export interface MetricThresholdAlertParams {
criteria?: MetricExpression[];
groupBy?: string;
groupBy?: string | string[];
filterQuery?: string;
sourceId?: string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { getChartTheme } from './helpers/get_chart_theme';
import { useKibanaUiSetting } from '../../../../utils/use_kibana_ui_setting';
import { calculateDomain } from './helpers/calculate_domain';
import { useKibana, useUiSetting } from '../../../../../../../../src/plugins/kibana_react/public';
import { ChartTitle } from './chart_title';

interface Props {
title?: string | null;
Expand Down Expand Up @@ -91,16 +92,17 @@ export const MetricsExplorerChart = ({
chartOptions.yAxisMode === MetricsExplorerYAxisMode.fromZero
? { ...dataDomain, min: 0 }
: dataDomain;

return (
<div style={{ padding: 24 }}>
{options.groupBy ? (
<EuiTitle size="xs">
<EuiFlexGroup alignItems="center">
<ChartTitle>
<ChartTitleContainer>
<EuiToolTip content={title} anchorClassName="metricsExplorerTitleAnchor">
<span>{title}</span>
<ChartTitle series={series} />
</EuiToolTip>
</ChartTitle>
</ChartTitleContainer>
<EuiFlexItem grow={false}>
<MetricsExplorerChartContextMenu
timeRange={timeRange}
Expand Down Expand Up @@ -169,7 +171,7 @@ export const MetricsExplorerChart = ({
);
};

const ChartTitle = euiStyled.div`
const ChartTitleContainer = euiStyled.div`
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,16 @@ export interface Props {

const fieldToNodeType = (
source: SourceConfiguration,
field: string
groupBy: string | string[]
): InventoryItemType | undefined => {
if (source.fields.host === field) {
const fields = Array.isArray(groupBy) ? groupBy : [groupBy];
if (fields.includes(source.fields.host)) {
return 'host';
}
if (source.fields.pod === field) {
if (fields.includes(source.fields.pod)) {
return 'pod';
}
if (source.fields.container === field) {
if (fields.includes(source.fields.container)) {
return 'container';
}
};
Expand Down Expand Up @@ -88,10 +89,16 @@ export const MetricsExplorerChartContextMenu: React.FC<Props> = ({
// onFilter needs check for Typescript even though it's
// covered by supportFiltering variable
if (supportFiltering && onFilter) {
onFilter(`${options.groupBy}: "${series.id}"`);
if (Array.isArray(options.groupBy)) {
onFilter(
options.groupBy.map((field, index) => `${field}: "${series.keys?.[index]}"`).join(' and ')
);
} else {
onFilter(`${options.groupBy}: "${series.id}"`);
}
}
setPopoverState(false);
}, [supportFiltering, options.groupBy, series.id, onFilter]);
}, [supportFiltering, onFilter, options, series.keys, series.id]);

// Only display the "Add Filter" option if it's supported
const filterByItem = supportFiltering
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { Fragment } from 'react';
import { EuiText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { MetricsExplorerSeries } from '../../../../../common/http_api';

interface Props {
series: MetricsExplorerSeries;
}

export const ChartTitle = ({ series }: Props) => {
if (series.keys != null) {
const { keys } = series;
return (
<EuiFlexGroup gutterSize="xs">
{keys.map((name, i) => (
<Fragment key={name}>
<EuiFlexItem grow={false}>
<EuiText size="m" color={keys.length - 1 > i ? 'subdued' : 'default'}>
<strong>{name}</strong>
</EuiText>
</EuiFlexItem>
{keys.length - 1 > i && (
<EuiFlexItem grow={false}>
<EuiText size="m" color="subdued">
<span>/</span>
</EuiText>
</EuiFlexItem>
)}
</Fragment>
))}
</EuiFlexGroup>
);
}
return <span>{series.id}</span>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import { NoData } from '../../../../components/empty_states/no_data';
import { MetricsExplorerChart } from './chart';
import { SourceQuery } from '../../../../graphql/types';

type stringOrNull = string | null;

interface Props {
loading: boolean;
options: MetricsExplorerOptions;
chartOptions: MetricsExplorerChartOptions;
onLoadMore: (afterKey: string | null) => void;
onLoadMore: (afterKey: stringOrNull | Record<string, stringOrNull>) => void;
onRefetch: () => void;
onFilter: (filter: string) => void;
onTimeChange: (start: string, end: string) => void;
Expand Down Expand Up @@ -73,6 +75,8 @@ export const MetricsExplorerCharts = ({
);
}

const and = i18n.translate('xpack.infra.metricsExplorer.andLabel', { defaultMessage: '" and "' });

return (
<div style={{ width: '100%' }}>
<EuiFlexGrid gutterSize="s" columns={data.series.length === 1 ? 1 : 3}>
Expand Down Expand Up @@ -104,7 +108,9 @@ export const MetricsExplorerCharts = ({
values={{
length: data.series.length,
total: data.pageInfo.total,
groupBy: options.groupBy,
groupBy: Array.isArray(options.groupBy)
? options.groupBy.join(and)
: options.groupBy,
}}
/>
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,25 @@ import { MetricsExplorerOptions } from '../hooks/use_metrics_explorer_options';

interface Props {
options: MetricsExplorerOptions;
onChange: (groupBy: string | null) => void;
onChange: (groupBy: string | null | string[]) => void;
fields: IFieldType[];
}

export const MetricsExplorerGroupBy = ({ options, onChange, fields }: Props) => {
const handleChange = useCallback(
selectedOptions => {
const groupBy = (selectedOptions.length === 1 && selectedOptions[0].label) || null;
(selectedOptions: Array<{ label: string }>) => {
const groupBy = selectedOptions.map(option => option.label);
onChange(groupBy);
},
[onChange]
);

const selectedOptions = Array.isArray(options.groupBy)
? options.groupBy.map(field => ({ label: field }))
: options.groupBy
? [{ label: options.groupBy }]
: [];

return (
<EuiComboBox
placeholder={i18n.translate('xpack.infra.metricsExplorer.groupByLabel', {
Expand All @@ -35,8 +41,8 @@ export const MetricsExplorerGroupBy = ({ options, onChange, fields }: Props) =>
defaultMessage: 'Graph per',
})}
fullWidth
singleSelection={true}
selectedOptions={(options.groupBy && [{ label: options.groupBy }]) || []}
singleSelection={false}
selectedOptions={selectedOptions}
options={fields
.filter(f => f.aggregatable && f.type === 'string')
.map(f => ({ label: f.name }))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,21 @@ export const createFilterFromOptions = (
}
if (options.groupBy) {
const id = series.id.replace('"', '\\"');
filters.push(`${options.groupBy} : "${id}"`);
const groupByFilters = Array.isArray(options.groupBy)
? options.groupBy
.map((field, index) => {
if (!series.keys) {
return null;
}
const value = series.keys[index];
if (!value) {
return null;
}
return `${field}: "${value.replace('"', '\\"')}"`;
})
.join(' and ')
: `${options.groupBy} : "${id}"`;
filters.push(groupByFilters);
}
return { language: 'kuery', query: filters.join(' and ') };
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ interface Props {
defaultViewState: MetricExplorerViewState;
onRefresh: () => void;
onTimeChange: (start: string, end: string) => void;
onGroupByChange: (groupBy: string | null) => void;
onGroupByChange: (groupBy: string | null | string[]) => void;
onFilterQuerySubmit: (query: string) => void;
onMetricsChange: (metrics: MetricsExplorerMetric[]) => void;
onAggregationChange: (aggregation: MetricsExplorerAggregation) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const useMetricsExplorerState = (
derivedIndexPattern: IIndexPattern
) => {
const [refreshSignal, setRefreshSignal] = useState(0);
const [afterKey, setAfterKey] = useState<string | null>(null);
const [afterKey, setAfterKey] = useState<string | null | Record<string, string | null>>(null);
const {
defaultViewState,
options,
Expand Down Expand Up @@ -63,7 +63,7 @@ export const useMetricsExplorerState = (
);

const handleGroupByChange = useCallback(
(groupBy: string | null) => {
(groupBy: string | null | string[]) => {
setAfterKey(null);
setOptions({
...options,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const renderUseMetricsExplorerDataHook = () => {
source,
derivedIndexPattern,
timeRange,
afterKey: null as string | null,
afterKey: null as string | null | Record<string, string | null>,
signal: 1,
},
wrapper,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function useMetricsExplorerData(
source: SourceQuery.Query['source']['configuration'] | undefined,
derivedIndexPattern: IIndexPattern,
timerange: MetricsExplorerTimeOptions,
afterKey: string | null,
afterKey: string | null | Record<string, string | null>,
signal: any,
fetch?: HttpHandler
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export interface MetricsExplorerChartOptions {
export interface MetricsExplorerOptions {
metrics: MetricsExplorerOptionsMetric[];
limit?: number;
groupBy?: string;
groupBy?: string | string[];
filterQuery?: string;
aggregation: MetricsExplorerAggregation;
forceInterval?: boolean;
Expand Down
Loading

0 comments on commit fbd76d3

Please sign in to comment.