[Lens] Inspector for annotations (#140495)

* [Lens] Inspector for annotations

* fix name for extra fields

* refactor expose method

* remove skippedCount

* use ui formatting

* filter by layer

* includes => has for performance
This commit is contained in:
Marta Bondyra 2022-09-14 10:59:00 +02:00 committed by GitHub
parent 5eea638cea
commit 0384ff8a52
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 89 additions and 18 deletions

View file

@ -24,6 +24,10 @@ export function annotationLayerFunction(): ExpressionFunctionDefinition<
inputTypes: ['datatable'],
help: strings.getAnnotationLayerFnHelp(),
args: {
layerId: {
types: ['string'],
help: strings.getLayerIdHelp(),
},
simpleView: {
types: ['boolean'],
default: false,

View file

@ -26,7 +26,13 @@ export const layeredXyVisFn: LayeredXyVisFn['fn'] = async (data, args, handlers)
if (args.singleTable) {
logDatatable(data, layers, handlers, args.splitColumnAccessor, args.splitRowAccessor);
} else {
logDatatables(layers, handlers, args.splitColumnAccessor, args.splitRowAccessor);
logDatatables(
layers,
handlers,
args.splitColumnAccessor,
args.splitRowAccessor,
args.annotations
);
}
const hasBar = hasBarLayer(dataLayers);

View file

@ -329,4 +329,12 @@ export const strings = {
i18n.translate('expressionXY.referenceLine.Value.help', {
defaultMessage: 'Reference line value',
}),
getTimeLabel: () =>
i18n.translate('expressionXY.annotation.time', {
defaultMessage: 'Time',
}),
getLabelLabel: () =>
i18n.translate('expressionXY.annotation.label', {
defaultMessage: 'Label',
}),
};

View file

@ -291,12 +291,13 @@ export interface XYProps {
}
export interface AnnotationLayerArgs {
layerId: string;
annotations: EventAnnotationOutput[];
simpleView?: boolean;
}
export type ExtendedAnnotationLayerArgs = AnnotationLayerArgs & {
layerId?: string;
layerId: string;
};
export type AnnotationLayerConfigResult = AnnotationLayerArgs & {

View file

@ -6,18 +6,26 @@
* Side Public License, v 1.
*/
import { QueryPointEventAnnotationOutput } from '@kbn/event-annotation-plugin/common';
import { Datatable, ExecutionContext } from '@kbn/expressions-plugin/common';
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
import { Dimension, prepareLogTable } from '@kbn/visualizations-plugin/common/utils';
import { LayerTypes, REFERENCE_LINE } from '../constants';
import { strings } from '../i18n';
import { CommonXYDataLayerConfig, CommonXYLayerConfig, ReferenceLineLayerConfig } from '../types';
import {
AnnotationLayerConfigResult,
CommonXYDataLayerConfig,
CommonXYLayerConfig,
ExpressionAnnotationResult,
ReferenceLineLayerConfig,
} from '../types';
export const logDatatables = (
layers: CommonXYLayerConfig[],
handlers: ExecutionContext,
splitColumnAccessor?: string | ExpressionValueVisDimension,
splitRowAccessor?: string | ExpressionValueVisDimension
splitRowAccessor?: string | ExpressionValueVisDimension,
annotations?: ExpressionAnnotationResult
) => {
if (!handlers?.inspectorAdapters?.tables) {
return;
@ -45,6 +53,35 @@ export const logDatatables = (
const logTable = prepareLogTable(layer.table, layerDimensions, true);
handlers.inspectorAdapters.tables.logDatatable(layer.layerId, logTable);
});
if (annotations) {
annotations.layers.forEach((layer) => {
const logTable = getLogAnnotationTable(annotations.datatable, layer);
handlers.inspectorAdapters.tables.logDatatable(layer.layerId, logTable);
});
}
};
const getLogAnnotationTable = (data: Datatable, layer: AnnotationLayerConfigResult) => {
const layerDimensions: Dimension[] = [
[['label'], strings.getLabelLabel()],
[['time'], strings.getTimeLabel()],
];
const layerAnnotationsId = new Set(layer.annotations.map((annotation) => annotation.id));
layer.annotations
.filter((a): a is QueryPointEventAnnotationOutput => a.type === 'query_point_event_annotation')
.forEach((annotation) => {
const dynamicDimensions: Dimension[] = [
...(annotation.extraFields ? annotation.extraFields : []),
...(annotation.textField ? [annotation.textField] : []),
].map((f) => [[`field:${f}`], f]);
layerDimensions.push(...dynamicDimensions);
});
return prepareLogTable(
{ ...data, rows: data.rows.filter((row) => layerAnnotationsId.has(row.id)) },
layerDimensions,
true
);
};
export const logDatatable = (

View file

@ -9,7 +9,7 @@
import './annotations.scss';
import './reference_lines/reference_lines.scss';
import React from 'react';
import React, { Fragment } from 'react';
import { groupBy, snakeCase } from 'lodash';
import {
AnnotationDomainType,
@ -76,7 +76,7 @@ const TooltipAnnotationHeader = ({
const TooltipAnnotationDetails = ({
row,
extraFields,
isGrouped,
timeFormat,
}: {
row: PointEventAnnotationRow;
extraFields: Array<{
@ -84,13 +84,12 @@ const TooltipAnnotationDetails = ({
name: string;
formatter: FieldFormat | undefined;
}>;
isGrouped?: boolean;
timeFormat: string;
}) => {
return (
<div className="echTooltip__item--container">
<span className="echTooltip__value">
{isGrouped && <div>{moment(row.time).format('YYYY-MM-DD, hh:mm:ss')}</div>}
<div>{moment(row.time).format(timeFormat)}</div>
<div className="xyAnnotationTooltip__extraFields">
{extraFields.map((field) => (
<div>
@ -125,7 +124,8 @@ const createCustomTooltipDetails =
(
rows: PointEventAnnotationRow[],
formatFactory: FormatFactory,
columns: DatatableColumn[] | undefined
columns: DatatableColumn[] | undefined,
timeFormat: string
): AnnotationTooltipFormatter =>
() => {
const groupedConfigs = groupBy(rows, 'id');
@ -137,7 +137,7 @@ const createCustomTooltipDetails =
const extraFields = getExtraFields(firstElement, formatFactory, columns);
return (
<div className="xyAnnotationTooltip__group">
<div className="xyAnnotationTooltip__group" key={firstElement.time}>
<TooltipAnnotationHeader row={firstElement} />
<EuiPanel
color="subdued"
@ -147,7 +147,7 @@ const createCustomTooltipDetails =
hasBorder={true}
>
{group.map((row, index) => (
<>
<Fragment key={`${row.id}-${row.time}`}>
{index > 0 && (
<>
<EuiSpacer size="xs" />
@ -157,11 +157,11 @@ const createCustomTooltipDetails =
)}
<TooltipAnnotationDetails
key={snakeCase(row.time)}
isGrouped={rows.length > 1}
row={row}
extraFields={extraFields}
timeFormat={timeFormat}
/>
</>
</Fragment>
))}
</EuiPanel>
</div>
@ -220,7 +220,8 @@ export const getAnnotationsGroupedByInterval = (
annotations: PointEventAnnotationRow[],
configs: EventAnnotationOutput[] | undefined,
columns: DatatableColumn[] | undefined,
formatFactory: FormatFactory
formatFactory: FormatFactory,
timeFormat: string
) => {
const visibleGroupedConfigs = annotations.reduce<Record<string, PointEventAnnotationRow[]>>(
(acc, current) => {
@ -249,7 +250,12 @@ export const getAnnotationsGroupedByInterval = (
icon: firstRow.icon || 'triangle',
timebucket: Number(timebucket),
position: 'bottom',
customTooltipDetails: createCustomTooltipDetails(rowsPerBucket, formatFactory, columns),
customTooltipDetails: createCustomTooltipDetails(
rowsPerBucket,
formatFactory,
columns,
timeFormat
),
isGrouped: false,
};
if (rowsPerBucket.length > 1) {

View file

@ -120,6 +120,7 @@ describe('XYChart component', () => {
useLegacyTimeAxis: false,
eventAnnotationService: eventAnnotationServiceMock,
renderComplete: jest.fn(),
timeFormat: 'MMM D, YYYY @ HH:mm:ss.SSS',
};
});
@ -3073,6 +3074,7 @@ describe('XYChart component', () => {
const createLayerWithAnnotations = (
annotations: EventAnnotationOutput[] = [defaultLineStaticAnnotation]
): AnnotationLayerConfigResult => ({
layerId: 'annotations',
type: 'annotationLayer',
layerType: LayerTypes.ANNOTATIONS,
annotations,
@ -3145,7 +3147,7 @@ describe('XYChart component', () => {
// checking tooltip
const renderLinks = mount(<div>{groupedAnnotation.prop('customTooltipDetails')!()}</div>);
expect(renderLinks.text()).toEqual(
' Event 12022-03-18, 04:25:002022-03-18, 04:25:002022-03-18, 04:25:00'
' Event 1Mar 18, 2022 @ 04:25:00.000Mar 18, 2022 @ 04:25:00.020Mar 18, 2022 @ 04:25:00.001'
);
});

View file

@ -128,6 +128,7 @@ export type XYChartRenderProps = XYChartProps & {
eventAnnotationService: EventAnnotationServiceType;
renderComplete: () => void;
uiState?: PersistedState;
timeFormat: string;
};
function getValueLabelsStyling(isHorizontal: boolean): {
@ -189,6 +190,7 @@ export function XYChart({
data,
formatFactory,
timeZone,
timeFormat,
chartsThemeService,
chartsActiveCursorService,
paletteService,
@ -408,7 +410,8 @@ export function XYChart({
lineAnnotations as PointEventAnnotationRow[],
annotationsConfigs,
annotations?.datatable.columns,
formatFactory
formatFactory,
timeFormat
);
const visualConfigs = [

View file

@ -36,6 +36,7 @@ export type GetStartDepsFn = () => Promise<{
activeCursor: ChartsPluginStart['activeCursor'];
paletteService: PaletteRegistry;
timeZone: string;
timeFormat: string;
useLegacyTimeAxis: boolean;
kibanaTheme: ThemeServiceStart;
eventAnnotationService: EventAnnotationServiceType;
@ -153,6 +154,7 @@ export const getXyChartRenderer = ({
chartsThemeService={deps.theme}
paletteService={deps.paletteService}
timeZone={deps.timeZone}
timeFormat={deps.timeFormat}
eventAnnotationService={deps.eventAnnotationService}
useLegacyTimeAxis={deps.useLegacyTimeAxis}
minInterval={calculateMinInterval(deps.data.datatableUtilities, config)}

View file

@ -97,6 +97,7 @@ export class ExpressionXyPlugin {
useLegacyTimeAxis,
eventAnnotationService,
timeZone: getTimeZone(core.uiSettings),
timeFormat: core.uiSettings.get('dateFormat'),
};
};

View file

@ -156,6 +156,7 @@ export const postprocessAnnotations = (
.map((col) => {
return {
...col,
name: swappedFieldsColIdMap[col.id], // we need to overwrite the name because esaggs column name is per bucket and not per row (eg. "First 10 fields...")
id: `field:${swappedFieldsColIdMap[col.id]}`,
};
});