mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
# Backport This will backport the following commits from `main` to `9.0`: - [[Lens][Datatable] Fix color by value for Last value array mode (#213917)](https://github.com/elastic/kibana/pull/213917) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Marco Liberati","email":"dej611@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-03-18T09:09:03Z","message":"[Lens][Datatable] Fix color by value for Last value array mode (#213917)\n\n## Summary\n\nFixes the table side of #188263 \n\nThe [fix used for\n`Metric`](https://github.com/elastic/kibana/pull/209110) has been\ngeneralized and re-used for the datatable visualization.\n\n\n\n\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"0b9df094d1770065f79c05272a41175396caa27b","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","Team:Visualizations","Feature:Lens","backport:version","v9.1.0","v8.19.0","v8.18.1","v9.0.1"],"title":"[Lens][Datatable] Fix color by value for Last value array mode","number":213917,"url":"https://github.com/elastic/kibana/pull/213917","mergeCommit":{"message":"[Lens][Datatable] Fix color by value for Last value array mode (#213917)\n\n## Summary\n\nFixes the table side of #188263 \n\nThe [fix used for\n`Metric`](https://github.com/elastic/kibana/pull/209110) has been\ngeneralized and re-used for the datatable visualization.\n\n\n\n\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"0b9df094d1770065f79c05272a41175396caa27b"}},"sourceBranch":"main","suggestedTargetBranches":["8.x","8.18","9.0"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/213917","number":213917,"mergeCommit":{"message":"[Lens][Datatable] Fix color by value for Last value array mode (#213917)\n\n## Summary\n\nFixes the table side of #188263 \n\nThe [fix used for\n`Metric`](https://github.com/elastic/kibana/pull/209110) has been\ngeneralized and re-used for the datatable visualization.\n\n\n\n\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"0b9df094d1770065f79c05272a41175396caa27b"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.1","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"9.0","label":"v9.0.1","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Marco Liberati <dej611@users.noreply.github.com>
This commit is contained in:
parent
ab3050f295
commit
ce776eca54
8 changed files with 60 additions and 66 deletions
|
@ -24,7 +24,7 @@ import {
|
|||
import { getOriginalId } from '@kbn/transpose-utils';
|
||||
import { Datatable, DatatableColumnType } from '@kbn/expressions-plugin/common';
|
||||
import { KbnPalettes } from '@kbn/palettes';
|
||||
import { DataType } from '../../types';
|
||||
import { DataType, DatasourcePublicAPI } from '../../types';
|
||||
|
||||
/**
|
||||
* Returns array of colors for provided palette or colorMapping
|
||||
|
@ -45,8 +45,33 @@ export function getPaletteDisplayColors(
|
|||
.getCategoricalColors(palette?.params?.steps || 10, palette);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze the column from the datasource prospective (formal check)
|
||||
* to know whether it's a numeric type or not
|
||||
* Note: to be used for Lens UI only
|
||||
*/
|
||||
export function getAccessorType(
|
||||
datasource: DatasourcePublicAPI | undefined,
|
||||
accessor: string | undefined
|
||||
) {
|
||||
// No accessor means it's not a numeric type by default
|
||||
if (!accessor || !datasource) {
|
||||
return { isNumeric: false, isCategory: false };
|
||||
}
|
||||
const operation = datasource.getOperationForColumnId(accessor);
|
||||
const isNumericTypeFromOperation = Boolean(
|
||||
!operation?.isBucketed && operation?.dataType === 'number' && !operation.hasArraySupport
|
||||
);
|
||||
const isBucketableTypeFromOperationType = Boolean(
|
||||
operation?.isBucketed ||
|
||||
(!['number', 'date'].includes(operation?.dataType || '') && !operation?.hasArraySupport)
|
||||
);
|
||||
return { isNumeric: isNumericTypeFromOperation, isCategory: isBucketableTypeFromOperationType };
|
||||
}
|
||||
|
||||
/**
|
||||
* Bucketed numerical columns should be treated as categorical
|
||||
* Note: to be used within expression renderer scope only
|
||||
*/
|
||||
export function shouldColorByTerms(
|
||||
dataType?: DataType | DatatableColumnType,
|
||||
|
|
|
@ -12,14 +12,13 @@ import userEvent, { type UserEvent } from '@testing-library/user-event';
|
|||
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
|
||||
import { LayerTypes } from '@kbn/expression-xy-plugin/public';
|
||||
import { EuiButtonGroupTestHarness } from '@kbn/test-eui-helpers';
|
||||
import { FramePublicAPI, DatasourcePublicAPI, OperationDescriptor } from '../../../types';
|
||||
import { FramePublicAPI, DatasourcePublicAPI, OperationDescriptor, DataType } from '../../../types';
|
||||
import { DatatableVisualizationState } from '../visualization';
|
||||
import { createMockDatasource, createMockFramePublicAPI } from '../../../mocks';
|
||||
import { TableDimensionEditor, TableDimensionEditorProps } from './dimension_editor';
|
||||
import { ColumnState } from '../../../../common/expressions';
|
||||
import { capitalize } from 'lodash';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { DatatableColumnType } from '@kbn/expressions-plugin/common';
|
||||
import { getKbnPalettes } from '@kbn/palettes';
|
||||
|
||||
describe('data table dimension editor', () => {
|
||||
|
@ -127,14 +126,13 @@ describe('data table dimension editor', () => {
|
|||
});
|
||||
|
||||
it('should render default alignment for number', () => {
|
||||
frame.activeData!.first.columns[0].meta.type = 'number';
|
||||
mockOperationForFirstColumn({ dataType: 'number' });
|
||||
renderTableDimensionEditor();
|
||||
expect(btnGroups.alignment.selected).toHaveTextContent('Right');
|
||||
});
|
||||
|
||||
it('should render default alignment for ranges', () => {
|
||||
frame.activeData!.first.columns[0].meta.type = 'number';
|
||||
frame.activeData!.first.columns[0].meta.params = { id: 'range' };
|
||||
mockOperationForFirstColumn({ isBucketed: true, dataType: 'number' });
|
||||
renderTableDimensionEditor();
|
||||
expect(btnGroups.alignment.selected).toHaveTextContent('Left');
|
||||
});
|
||||
|
@ -178,10 +176,10 @@ describe('data table dimension editor', () => {
|
|||
expect(screen.queryByTestId('lns_dynamicColoring_edit')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each<DatatableColumnType>(['date'])(
|
||||
it.each<DataType>(['date'])(
|
||||
'should not show the dynamic coloring option for "%s" columns',
|
||||
(type) => {
|
||||
frame.activeData!.first.columns[0].meta.type = type;
|
||||
mockOperationForFirstColumn({ dataType: type });
|
||||
|
||||
renderTableDimensionEditor();
|
||||
expect(screen.queryByTestId('lnsDatatable_dynamicColoring_groups')).not.toBeInTheDocument();
|
||||
|
@ -229,7 +227,7 @@ describe('data table dimension editor', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it.each<{ flyout: 'terms' | 'values'; isBucketed: boolean; type: DatatableColumnType }>([
|
||||
it.each<{ flyout: 'terms' | 'values'; isBucketed: boolean; type: DataType }>([
|
||||
{ flyout: 'terms', isBucketed: true, type: 'number' },
|
||||
{ flyout: 'terms', isBucketed: false, type: 'string' },
|
||||
{ flyout: 'values', isBucketed: false, type: 'number' },
|
||||
|
@ -237,8 +235,7 @@ describe('data table dimension editor', () => {
|
|||
'should show color by $flyout flyout when bucketing is $isBucketed with $type column',
|
||||
async ({ flyout, isBucketed, type }) => {
|
||||
state.columns[0].colorMode = 'cell';
|
||||
frame.activeData!.first.columns[0].meta.type = type;
|
||||
mockOperationForFirstColumn({ isBucketed });
|
||||
mockOperationForFirstColumn({ isBucketed, dataType: type });
|
||||
renderTableDimensionEditor();
|
||||
|
||||
await user.click(screen.getByLabelText('Edit colors'));
|
||||
|
@ -250,8 +247,7 @@ describe('data table dimension editor', () => {
|
|||
|
||||
it('should show the dynamic coloring option for a bucketed operation', () => {
|
||||
state.columns[0].colorMode = 'cell';
|
||||
frame.activeData!.first.columns[0].meta.type = 'string';
|
||||
mockOperationForFirstColumn({ isBucketed: true });
|
||||
mockOperationForFirstColumn({ isBucketed: true, dataType: 'string' });
|
||||
|
||||
renderTableDimensionEditor();
|
||||
expect(screen.queryByTestId('lnsDatatable_dynamicColoring_groups')).toBeInTheDocument();
|
||||
|
|
|
@ -26,7 +26,7 @@ import type { DatatableVisualizationState } from '../visualization';
|
|||
import {
|
||||
defaultPaletteParams,
|
||||
findMinMaxByColumnId,
|
||||
shouldColorByTerms,
|
||||
getAccessorType,
|
||||
} from '../../../shared_components';
|
||||
|
||||
import './dimension_editor.scss';
|
||||
|
@ -34,10 +34,6 @@ import { CollapseSetting } from '../../../shared_components/collapse_setting';
|
|||
import { ColorMappingByValues } from '../../../shared_components/coloring/color_mapping_by_values';
|
||||
import { ColorMappingByTerms } from '../../../shared_components/coloring/color_mapping_by_terms';
|
||||
import { getColumnAlignment } from '../utils';
|
||||
import {
|
||||
getFieldMetaFromDatatable,
|
||||
isNumericField,
|
||||
} from '../../../../common/expressions/datatable/utils';
|
||||
import { DatatableInspectorTables } from '../../../../common/expressions/datatable/datatable_fn';
|
||||
|
||||
const idPrefix = htmlIdGenerator()();
|
||||
|
@ -90,13 +86,13 @@ export function TableDimensionEditor(props: TableDimensionEditorProps) {
|
|||
const currentData =
|
||||
frame.activeData?.[localState.layerId] ?? frame.activeData?.[DatatableInspectorTables.Default];
|
||||
const datasource = frame.datasourceLayers?.[localState.layerId];
|
||||
const { isBucketed } = datasource?.getOperationForColumnId(accessor) ?? {};
|
||||
const meta = getFieldMetaFromDatatable(currentData, accessor);
|
||||
const showColorByTerms = shouldColorByTerms(meta?.type, isBucketed);
|
||||
const currentAlignment = getColumnAlignment(column, isNumericField(meta));
|
||||
|
||||
const { isNumeric, isCategory: isBucketable } = getAccessorType(datasource, accessor);
|
||||
const showColorByTerms = isBucketable;
|
||||
const showDynamicColoringFeature = isBucketable || isNumeric;
|
||||
const currentAlignment = getColumnAlignment(column, isNumeric);
|
||||
const currentColorMode = column?.colorMode || 'none';
|
||||
const hasDynamicColoring = currentColorMode !== 'none';
|
||||
const showDynamicColoringFeature = meta?.type !== 'date';
|
||||
const visibleColumnsCount = localState.columns.filter((c) => !c.hidden).length;
|
||||
|
||||
const hasTransposedColumn = localState.columns.some(({ isTransposed }) => isTransposed);
|
||||
|
|
|
@ -56,7 +56,7 @@ import {
|
|||
defaultPaletteParams,
|
||||
findMinMaxByColumnId,
|
||||
getPaletteDisplayColors,
|
||||
shouldColorByTerms,
|
||||
getAccessorType,
|
||||
} from '../../shared_components';
|
||||
import { getColorMappingTelemetryEvents } from '../../lens_ui_telemetry/color_telemetry_helpers';
|
||||
import { DatatableInspectorTables } from '../../../common/expressions/datatable/datatable_fn';
|
||||
|
@ -147,11 +147,11 @@ export const getDatatableVisualization = ({
|
|||
|
||||
const hasTransposedColumn = state.columns.some(({ isTransposed }) => isTransposed);
|
||||
const columns = state.columns.map((column) => {
|
||||
if (column.palette) {
|
||||
const accessor = column.columnId;
|
||||
const accessor = column.columnId;
|
||||
const { isNumeric, isCategory: isBucketable } = getAccessorType(datasource, accessor);
|
||||
if (column.palette && (isNumeric || isBucketable)) {
|
||||
const showColorByTerms = isBucketable;
|
||||
const currentData = frame?.activeData?.[state.layerId];
|
||||
const { dataType, isBucketed } = datasource?.getOperationForColumnId(column.columnId) ?? {};
|
||||
const showColorByTerms = shouldColorByTerms(dataType, isBucketed);
|
||||
const palette = paletteMap.get(column.palette?.name ?? '');
|
||||
const columnsToCheck = hasTransposedColumn
|
||||
? currentData?.columns
|
||||
|
@ -163,7 +163,7 @@ export const getDatatableVisualization = ({
|
|||
if (palette && !showColorByTerms && !palette?.canDynamicColoring && dataBounds) {
|
||||
const newPalette: PaletteOutput<CustomPaletteParams> = {
|
||||
type: 'palette',
|
||||
name: showColorByTerms ? 'default' : defaultPaletteParams.name,
|
||||
name: defaultPaletteParams.name,
|
||||
};
|
||||
return {
|
||||
...column,
|
||||
|
@ -583,6 +583,10 @@ export const getDatatableVisualization = ({
|
|||
columns: columns
|
||||
.filter((c) => !c.collapseFn)
|
||||
.map((column) => {
|
||||
const { isNumeric, isCategory: isBucketable } = getAccessorType(
|
||||
datasource,
|
||||
column.columnId
|
||||
);
|
||||
const stops = getOverridePaletteStops(paletteService, column.palette);
|
||||
const paletteParams = {
|
||||
...column.palette?.params,
|
||||
|
@ -594,11 +598,11 @@ export const getDatatableVisualization = ({
|
|||
: [],
|
||||
reverse: false, // managed at UI level
|
||||
};
|
||||
const { dataType, isBucketed, sortingHint, inMetricDimension } =
|
||||
const { sortingHint, inMetricDimension } =
|
||||
datasource?.getOperationForColumnId(column.columnId) ?? {};
|
||||
const hasNoSummaryRow = column.summaryRow == null || column.summaryRow === 'none';
|
||||
const canColor = dataType !== 'date';
|
||||
const colorByTerms = shouldColorByTerms(dataType, isBucketed);
|
||||
const canColor = isNumeric || isBucketable;
|
||||
const colorByTerms = isBucketable;
|
||||
let isTransposable =
|
||||
!isTextBasedLanguage &&
|
||||
!datasource!.getOperationForColumnId(column.columnId)?.isBucketed;
|
||||
|
|
|
@ -31,14 +31,13 @@ import { getColumnByAccessor } from '@kbn/visualizations-plugin/common/utils';
|
|||
import { css } from '@emotion/react';
|
||||
import { DebouncedInput, IconSelect } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { PalettePanelContainer } from '../../shared_components';
|
||||
import { PalettePanelContainer, getAccessorType } from '../../shared_components';
|
||||
import type { VisualizationDimensionEditorProps } from '../../types';
|
||||
import { defaultNumberPaletteParams, defaultPercentagePaletteParams } from './palette_config';
|
||||
import { DEFAULT_MAX_COLUMNS, getDefaultColor, showingBar } from './visualization';
|
||||
import { CollapseSetting } from '../../shared_components/collapse_setting';
|
||||
import { MetricVisualizationState } from './types';
|
||||
import { metricIconsSet } from '../../shared_components/icon_set';
|
||||
import { isMetricNumericType } from './helpers';
|
||||
|
||||
export type SupportingVisType = 'none' | 'bar' | 'trendline';
|
||||
|
||||
|
@ -217,7 +216,7 @@ function PrimaryMetricEditor(props: SubProps) {
|
|||
return null;
|
||||
}
|
||||
|
||||
const isMetricNumeric = isMetricNumericType(props.datasource, accessor);
|
||||
const { isNumeric: isMetricNumeric } = getAccessorType(props.datasource, accessor);
|
||||
|
||||
const hasDynamicColoring = Boolean(isMetricNumeric && state.palette);
|
||||
|
||||
|
@ -417,7 +416,7 @@ export function DimensionEditorAdditionalSection({
|
|||
}: VisualizationDimensionEditorProps<MetricVisualizationState>) {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const isMetricNumeric = isMetricNumericType(datasource, accessor);
|
||||
const { isNumeric: isMetricNumeric } = getAccessorType(datasource, accessor);
|
||||
if (accessor !== state.metricAccessor || !isMetricNumeric) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,26 +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 { DatasourcePublicAPI } from '../../types';
|
||||
|
||||
/**
|
||||
* Infer the numeric type of a metric column purely on the configuration
|
||||
*/
|
||||
export function isMetricNumericType(
|
||||
datasource: DatasourcePublicAPI | undefined,
|
||||
accessor: string | undefined
|
||||
) {
|
||||
// No accessor means it's not a numeric type by default
|
||||
if (!accessor || !datasource) {
|
||||
return false;
|
||||
}
|
||||
const operation = datasource.getOperationForColumnId(accessor);
|
||||
const isNumericTypeFromOperation = Boolean(
|
||||
operation?.dataType === 'number' && !operation.hasArraySupport
|
||||
);
|
||||
return isNumericTypeFromOperation;
|
||||
}
|
|
@ -27,7 +27,7 @@ import { showingBar } from './metric_visualization';
|
|||
import { DEFAULT_MAX_COLUMNS, getDefaultColor } from './visualization';
|
||||
import { MetricVisualizationState } from './types';
|
||||
import { metricStateDefaults } from './constants';
|
||||
import { isMetricNumericType } from './helpers';
|
||||
import { getAccessorType } from '../../shared_components';
|
||||
|
||||
// TODO - deduplicate with gauges?
|
||||
function computePaletteParams(
|
||||
|
@ -105,7 +105,7 @@ export const toExpression = (
|
|||
const datasource = datasourceLayers[state.layerId];
|
||||
const datasourceExpression = datasourceExpressionsByLayers[state.layerId];
|
||||
|
||||
const isMetricNumeric = isMetricNumericType(datasource, state.metricAccessor);
|
||||
const { isNumeric: isMetricNumeric } = getAccessorType(datasource, state.metricAccessor);
|
||||
const maxPossibleTiles =
|
||||
// if there's a collapse function, no need to calculate since we're dealing with a single tile
|
||||
state.breakdownByAccessor && !state.collapseFn
|
||||
|
|
|
@ -33,7 +33,7 @@ import { toExpression } from './to_expression';
|
|||
import { nonNullable } from '../../utils';
|
||||
import { METRIC_NUMERIC_MAX } from '../../user_messages_ids';
|
||||
import { MetricVisualizationState } from './types';
|
||||
import { isMetricNumericType } from './helpers';
|
||||
import { getAccessorType } from '../../shared_components';
|
||||
|
||||
export const DEFAULT_MAX_COLUMNS = 3;
|
||||
|
||||
|
@ -658,7 +658,7 @@ export const getMetricVisualization = ({
|
|||
const hasStaticColoring = !!state.color;
|
||||
const hasDynamicColoring = !!state.palette;
|
||||
|
||||
const isMetricNumeric = isMetricNumericType(
|
||||
const { isNumeric: isMetricNumeric } = getAccessorType(
|
||||
frame?.datasourceLayers[state.layerId],
|
||||
state.metricAccessor
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue