mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[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:
parent
5eea638cea
commit
0384ff8a52
11 changed files with 89 additions and 18 deletions
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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',
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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 & {
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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'
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -97,6 +97,7 @@ export class ExpressionXyPlugin {
|
|||
useLegacyTimeAxis,
|
||||
eventAnnotationService,
|
||||
timeZone: getTimeZone(core.uiSettings),
|
||||
timeFormat: core.uiSettings.get('dateFormat'),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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]}`,
|
||||
};
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue