Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into dot-kibana-split
Browse files Browse the repository at this point in the history
  • Loading branch information
pgayvallet committed Apr 20, 2023
2 parents b1ce437 + c443753 commit 18946b3
Show file tree
Hide file tree
Showing 71 changed files with 2,047 additions and 479 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { Accessor, AccessorFn, GroupBy, SmallMultiples, Predicate } from '@elastic/charts';

interface ChartSplitProps {
splitColumnAccessor?: Accessor | AccessorFn;
splitRowAccessor?: Accessor | AccessorFn;
}

const CHART_SPLIT_ID = '__heatmap_chart_split__';
const SMALL_MULTIPLES_ID = '__heatmap_chart_sm__';

export const ChartSplit = ({ splitColumnAccessor, splitRowAccessor }: ChartSplitProps) => {
if (!splitColumnAccessor && !splitRowAccessor) return null;

return (
<>
<GroupBy
id={CHART_SPLIT_ID}
by={(spec, datum) => {
const splitTypeAccessor = splitColumnAccessor || splitRowAccessor;
if (splitTypeAccessor) {
return typeof splitTypeAccessor === 'function'
? splitTypeAccessor(datum)
: datum[splitTypeAccessor];
}
return spec.id;
}}
sort={Predicate.DataIndex}
/>
<SmallMultiples
id={SMALL_MULTIPLES_ID}
splitVertically={splitRowAccessor ? CHART_SPLIT_ID : undefined}
splitHorizontally={splitColumnAccessor ? CHART_SPLIT_ID : undefined}
style={{
verticalPanelPadding: {
outer: 0,
inner: 0.1,
},
horizontalPanelPadding: {
outer: 0,
inner: 0.1,
},
}}
/>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ import {
LegendColorPickerWrapper,
} from '../utils/get_color_picker';
import { defaultPaletteParams } from '../constants';
import { ChartSplit } from './chart_split';
import { getSplitDimensionAccessor, createSplitPoint } from '../utils/get_split_dimension_utils';
import './index.scss';

declare global {
Expand Down Expand Up @@ -207,14 +209,19 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
() => findMinMaxByColumnId([valueAccessor!], table),
[valueAccessor, table]
);

const paletteParams = args.palette?.params;
const xAccessor = args.xAccessor
? getAccessorByDimension(args.xAccessor, table.columns)
: undefined;
const yAccessor = args.yAccessor
? getAccessorByDimension(args.yAccessor, table.columns)
: undefined;
const splitChartRowAccessor = args.splitRowAccessor
? getSplitDimensionAccessor(data.columns, args.splitRowAccessor, formatFactory)
: undefined;
const splitChartColumnAccessor = args.splitColumnAccessor
? getSplitDimensionAccessor(data.columns, args.splitColumnAccessor, formatFactory)
: undefined;

const xAxisColumnIndex = table.columns.findIndex((v) => v.id === xAccessor);
const yAxisColumnIndex = table.columns.findIndex((v) => v.id === yAccessor);
Expand Down Expand Up @@ -252,7 +259,7 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
const onElementClick = useCallback(
(e: HeatmapElementEvent[]) => {
const cell = e[0][0];
const { x, y } = cell.datum;
const { x, y, smVerticalAccessorValue, smHorizontalAccessorValue } = cell.datum;

const points = [
{
Expand Down Expand Up @@ -284,6 +291,28 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
: []),
];

if (smHorizontalAccessorValue && args.splitColumnAccessor) {
const point = createSplitPoint(
args.splitColumnAccessor,
smHorizontalAccessorValue,
formatFactory,
table
);
if (point) {
points.push(point);
}
}
if (smVerticalAccessorValue && args.splitRowAccessor) {
const point = createSplitPoint(
args.splitRowAccessor,
smVerticalAccessorValue,
formatFactory,
table
);
if (point) {
points.push(point);
}
}
const context: FilterEvent['data'] = {
data: points.map((point) => ({
row: point.row,
Expand All @@ -295,6 +324,9 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
onClickValue(context);
},
[
args.splitColumnAccessor,
args.splitRowAccessor,
formatFactory,
formattedTable.formattedColumns,
onClickValue,
table,
Expand Down Expand Up @@ -579,6 +611,10 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
}}
>
<Chart ref={chartRef}>
<ChartSplit
splitColumnAccessor={splitChartColumnAccessor}
splitRowAccessor={splitChartRowAccessor}
/>
<Settings
onRenderChange={onRenderChange}
noResults={
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
import type { DatatableColumn, Datatable } from '@kbn/expressions-plugin/public';
import type { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
import { getSplitDimensionAccessor, createSplitPoint } from './get_split_dimension_utils';

const data: Datatable = {
type: 'datatable',
rows: [
{ 'col-0-1': 0, 'col-1-2': 'a', 'col-2-3': 'd' },
{ 'col-0-1': 148, 'col-1-2': 'b', 'col-2-3': 'c' },
],
columns: [
{ id: 'col-0-1', name: 'Count', meta: { type: 'number' } },
{ id: 'col-1-2', name: 'Dest', meta: { type: 'string' } },
{
id: 'col-2-3',
name: 'Test',
meta: {
type: 'number',
params: {
id: 'number',
},
},
},
],
};

describe('getSplitDimensionAccessor', () => {
const defaultFormatter = jest.fn((...args) => fieldFormatsMock.deserialize(...args));

beforeEach(() => {
defaultFormatter.mockClear();
});

const splitDimension: ExpressionValueVisDimension = {
type: 'vis_dimension',
accessor: {
id: data.columns[2].id,
name: data.columns[2].name,
meta: data.columns[2].meta,
},
format: {
params: {},
},
};

it('returns accessor which is using formatter, if meta.params are present at accessing column', () => {
const accessor = getSplitDimensionAccessor(data.columns, splitDimension, defaultFormatter);

expect(defaultFormatter).toHaveBeenCalledTimes(1);
expect(typeof accessor).toBe('function');
accessor(data.rows[0]);
});

it('returns accessor which is using default formatter, if meta.params and format are not present', () => {
const column: Partial<DatatableColumn> = {
...data.columns[2],
meta: { type: 'number' },
};
const columns = [data.columns[0], column, data.columns[2]] as DatatableColumn[];
const defaultFormatterReturnedVal = fieldFormatsMock.deserialize();
const spyOnDefaultFormatterConvert = jest.spyOn(defaultFormatterReturnedVal, 'convert');

defaultFormatter.mockReturnValueOnce(defaultFormatterReturnedVal);
const accessor = getSplitDimensionAccessor(columns, splitDimension, defaultFormatter);

expect(defaultFormatter).toHaveBeenCalledTimes(1);

expect(typeof accessor).toBe('function');
accessor(data.rows[0]);
expect(spyOnDefaultFormatterConvert).toHaveBeenCalledTimes(1);
});

it('returns accessor which returns undefined, if such column is not present', () => {
const accessor1 = getSplitDimensionAccessor(data.columns, splitDimension, defaultFormatter);

expect(typeof accessor1).toBe('function');
const result1 = accessor1({});
expect(result1).toBeUndefined();

const column2: Partial<DatatableColumn> = {
...data.columns[2],
meta: { type: 'string' },
};
const columns2 = [data.columns[0], data.columns[1], column2] as DatatableColumn[];
const accessor2 = getSplitDimensionAccessor(columns2, splitDimension, defaultFormatter);

expect(typeof accessor2).toBe('function');
const result2 = accessor1({});
expect(result2).toBeUndefined();

const column3 = {
...data.columns[2],
meta: { type: 'string' },
format: {
id: 'string',
params: {},
},
};
const columns3 = [data.columns[0], data.columns[1], column3] as DatatableColumn[];

const accessor3 = getSplitDimensionAccessor(columns3, splitDimension, defaultFormatter);
expect(typeof accessor3).toBe('function');
const result3 = accessor3({});
expect(result3).toBeUndefined();
});
});

describe('createSplitPoint', () => {
const defaultFormatter = jest.fn((...args) => fieldFormatsMock.deserialize(...args));

beforeEach(() => {
defaultFormatter.mockClear();
});

const splitDimension: ExpressionValueVisDimension = {
type: 'vis_dimension',
accessor: {
id: data.columns[2].id,
name: data.columns[2].name,
meta: data.columns[2].meta,
},
format: {
params: {},
},
};

it('returns point if value is found in the table', () => {
const point = createSplitPoint(splitDimension, 'c', defaultFormatter, data);

expect(defaultFormatter).toHaveBeenCalledTimes(1);
expect(point).toStrictEqual({ column: 2, row: 1, value: 'c' });
});

it('returns undefined if value is not found in the table', () => {
const point = createSplitPoint(splitDimension, 'test', defaultFormatter, data);

expect(defaultFormatter).toHaveBeenCalledTimes(1);
expect(point).toBeUndefined();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { AccessorFn } from '@elastic/charts';
import type { DatatableColumn, Datatable } from '@kbn/expressions-plugin/public';
import type { FormatFactory } from '@kbn/field-formats-plugin/common';
import type { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
import { getColumnByAccessor } from '@kbn/visualizations-plugin/common/utils';

export const getSplitDimensionAccessor = (
columns: DatatableColumn[],
splitDimension: ExpressionValueVisDimension | string,
formatFactory: FormatFactory
): AccessorFn => {
const splitChartColumn = getColumnByAccessor(splitDimension, columns)!;
const accessor = splitChartColumn.id;

const formatter = formatFactory(splitChartColumn.meta?.params);
const fn: AccessorFn = (d) => {
const v = d[accessor];
if (v === undefined) {
return;
}

const f = formatter.convert(v);
return f;
};

return fn;
};

export function createSplitPoint(
splitDimension: ExpressionValueVisDimension | string,
value: string | number,
formatFactory: FormatFactory,
table: Datatable
) {
const splitChartColumn = getColumnByAccessor(splitDimension, table.columns)!;
const accessor = splitChartColumn.id;

const formatter = formatFactory(splitChartColumn.meta?.params);
const splitPointRowIndex = table.rows.findIndex((row) => {
return formatter.convert(row[accessor]) === value;
});
if (splitPointRowIndex !== -1) {
return {
row: splitPointRowIndex,
column: table.columns.findIndex((column) => column.id === accessor),
value: table.rows[splitPointRowIndex][accessor],
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ export function TableHeader({
return (
<tr data-test-subj="docTableHeader" className="kbnDocTableHeader">
<th style={{ width: '24px' }} />
{displayedColumns.map((col) => {
{displayedColumns.map((col, index) => {
return (
<TableHeaderColumn
key={col.name}
key={`${col.name}-${index}`}
{...col}
customLabel={dataView.getFieldByName(col.name)?.customLabel}
isTimeColumn={dataView.timeFieldName === col.name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ export const TableRow = ({
/>
);
} else {
columns.forEach(function (column: string) {
columns.forEach(function (column: string, index) {
const cellKey = `${column}-${index}`;
if (useNewFieldsApi && !mapping(column) && row.raw.fields && !row.raw.fields[column]) {
const innerColumns = Object.fromEntries(
Object.entries(row.raw.fields).filter(([key]) => {
Expand All @@ -163,7 +164,7 @@ export const TableRow = ({

rowCells.push(
<TableCell
key={column}
key={cellKey}
timefield={false}
sourcefield={true}
formatted={formatTopLevelObject(row, innerColumns, dataView, maxEntries)}
Expand All @@ -182,7 +183,7 @@ export const TableRow = ({
);
rowCells.push(
<TableCell
key={column}
key={cellKey}
timefield={false}
sourcefield={column === '_source'}
formatted={displayField(column)}
Expand Down
Loading

0 comments on commit 18946b3

Please sign in to comment.