Skip to content

Commit

Permalink
Merge branch '8.13' into backport/8.13/pr-178005
Browse files Browse the repository at this point in the history
  • Loading branch information
jpdjere authored Mar 13, 2024
2 parents eb137f6 + 6f8e044 commit f3f0c37
Show file tree
Hide file tree
Showing 16 changed files with 447 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@ import { css } from '@emotion/react';
import { PresentationPanel } from '@kbn/presentation-panel-plugin/public';
import { PanelCompatibleComponent } from '@kbn/presentation-panel-plugin/public/panel_component/types';
import { isPromise } from '@kbn/std';
import React, { ReactNode, useEffect, useImperativeHandle, useMemo, useState } from 'react';
import React, { ReactNode, useEffect, useImperativeHandle, useMemo, useState, useRef } from 'react';
import { untilPluginStartServicesReady } from '../kibana_services';
import { EmbeddablePanelProps } from './types';

const getComponentFromEmbeddable = async (
embeddable: EmbeddablePanelProps['embeddable']
): Promise<PanelCompatibleComponent> => {
embeddable: EmbeddablePanelProps['embeddable'],
isMounted: () => boolean
): Promise<PanelCompatibleComponent | null> => {
const startServicesPromise = untilPluginStartServicesReady();
const embeddablePromise =
typeof embeddable === 'function' ? embeddable() : Promise.resolve(embeddable);
const [, unwrappedEmbeddable] = await Promise.all([startServicesPromise, embeddablePromise]);
if (!isMounted()) {
return null;
}
if (unwrappedEmbeddable.parent) {
await unwrappedEmbeddable.parent.untilEmbeddableLoaded(unwrappedEmbeddable.id);
}
Expand Down Expand Up @@ -55,9 +59,33 @@ const getComponentFromEmbeddable = async (

/**
* Loads and renders a legacy embeddable.
*
* Ancestry chain must use 'key' attribute to reset DOM and state when embeddable changes
* For example <Parent key={embeddableId}><EmbeddablePanel/></Parent>
*/
export const EmbeddablePanel = (props: EmbeddablePanelProps) => {
// can not use useMountedState
// 1. useMountedState defaults mountedRef to false and sets mountedRef to true in useEffect
// 2. embeddable can be an object or a function that returns a promise
// 3. when embeddable is an object, Promise.resolve(embeddable) returns before
// useMountedState useEffect is called and thus isMounted() returns false when component has not been unmounted
const mountedRef = useRef<boolean>(true);
useEffect(() => {
return () => {
mountedRef.current = false;
};
}, []);
const isMounted = () => {
return mountedRef.current;
};
const { embeddable, ...passThroughProps } = props;
const componentPromise = useMemo(() => getComponentFromEmbeddable(embeddable), [embeddable]);
const componentPromise = useMemo(
() => getComponentFromEmbeddable(embeddable, isMounted),
// Ancestry chain is expected to use 'key' attribute to reset DOM and state
// when embeddable needs to be re-loaded
// empty array is consistent with PresentationPanel useAsync dependency check
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
return <PresentationPanel {...passThroughProps} Component={componentPromise} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export const PresentationPanel = <
]);
const Panel = panelModule.PresentationPanelInternal;
return { Panel, unwrappedComponent };
// Ancestry chain is expected to use 'key' attribute to reset DOM and state
// when unwrappedComponent needs to be re-loaded
}, []);

if (error || (!loading && (!value?.Panel || !value?.unwrappedComponent))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,5 @@ export type PresentationPanelProps<
ApiType extends DefaultPresentationPanelApi = DefaultPresentationPanelApi,
PropsType extends {} = {}
> = Omit<PresentationPanelInternalProps<ApiType, PropsType>, 'Component'> & {
Component: MaybePromise<PanelCompatibleComponent<ApiType, PropsType>>;
Component: MaybePromise<PanelCompatibleComponent<ApiType, PropsType> | null>;
};
4 changes: 2 additions & 2 deletions x-pack/plugins/data_visualizer/common/types/field_stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ export interface StringFieldStats {
fieldName: string;
isTopValuesSampled: boolean;
topValues: Bucket[];
topValuesSampleSize: number;
topValuesSamplerShardSize: number;
topValuesSampleSize?: number;
topValuesSamplerShardSize?: number;
}

export interface DateFieldStats {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,16 @@ export const TopValues: FC<Props> = ({ stats, fieldFormat, barColor, compressed,
} = useDataVisualizerKibana();

if (stats === undefined || !stats.topValues) return null;
const { topValues, fieldName, sampleCount } = stats;
const { topValues: originalTopValues, fieldName, sampleCount } = stats;

if (topValues?.length === 0) return null;
if (originalTopValues?.length === 0) return null;
const totalDocuments = stats.totalDocuments ?? sampleCount ?? 0;

const topValues = originalTopValues.map((bucket) => ({
...bucket,
percent:
typeof bucket.percent === 'number' ? bucket.percent : bucket.doc_count / totalDocuments,
}));
const topValuesOtherCountPercent =
1 - (topValues ? topValues.reduce((acc, bucket) => acc + bucket.percent, 0) : 0);
const topValuesOtherCount = Math.floor(topValuesOtherCountPercent * (sampleCount ?? 0));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,39 @@ import type { ESQLDefaultLimitSizeOption } from '../../../embeddables/grid_embed

const options = [
{
'data-test-subj': 'dvESQLLimitSize-5000',
value: '5000',
text: i18n.translate('xpack.dataVisualizer.searchPanel.esql.limitSizeOptionLabel', {
defaultMessage: '{limit} rows',
values: { limit: '5,000' },
}),
},
{
'data-test-subj': 'dvESQLLimitSize-10000',
value: '10000',
text: i18n.translate('xpack.dataVisualizer.searchPanel.esql.limitSizeOptionLabel', {
defaultMessage: '{limit} rows',
values: { limit: '10,000' },
}),
},
{
'data-test-subj': 'dvESQLLimitSize-100000',
value: '100000',
text: i18n.translate('xpack.dataVisualizer.searchPanel.esql.limitSizeOptionLabel', {
defaultMessage: '{limit} rows',
values: { limit: '100,000' },
}),
},
{
'data-test-subj': 'dvESQLLimitSize-1000000',
value: '1000000',
text: i18n.translate('xpack.dataVisualizer.searchPanel.esql.limitSizeOptionLabel', {
defaultMessage: '{limit} rows',
values: { limit: '1,000,000' },
}),
},
{
'data-test-subj': 'dvESQLLimitSize-none',
value: 'none',
text: i18n.translate('xpack.dataVisualizer.searchPanel.esql.analyzeAll', {
defaultMessage: 'Analyze all',
Expand All @@ -62,6 +67,7 @@ export const ESQLDefaultLimitSizeSelect = ({

return (
<EuiSelect
data-test-subj="dvESQLLimitSizeSelect"
id={basicSelectId}
options={options}
value={limitSize}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,14 @@ export const getESQLKeywordFieldStats = async ({
if (isFulfilled(resp)) {
const results = resp.value?.rawResponse.values as Array<[BucketCount, BucketTerm]>;
if (results) {
const topValuesSampleSize = results?.reduce((acc: number, row) => acc + row[0], 0);

const terms = results.map((row) => ({
key: row[1],
doc_count: row[0],
percent: row[0] / topValuesSampleSize,
}));

return {
fieldName: field.name,
topValues: terms,
topValuesSampleSize,
topValuesSamplerShardSize: topValuesSampleSize,
isTopValuesSampled: false,
} as StringFieldStats;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const getESQLNumericFieldStatsInChunk = async ({
const median = values[startIndex + numericAccessorMap.p50];

const percentiles = values
.slice(startIndex + numericAccessorMap.p0, startIndex + numericAccessorMap.p100)
.slice(startIndex + numericAccessorMap.p5, startIndex + numericAccessorMap.p100 + 1)
.map((value: number) => ({ value }));

const distribution = processDistributionData(percentiles, PERCENTILE_SPACING, min);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ export const getESQLExampleFieldValues = async ({

if (textFieldsResp) {
return textFields.map((textField, idx) => {
const examples = (textFieldsResp.rawResponse.values as unknown[][]).map(
(row) => row[idx]
);
const examples = [
...new Set((textFieldsResp.rawResponse.values as unknown[][]).map((row) => row[idx])),
];

return {
fieldName: textField.name,
Expand Down
Loading

0 comments on commit f3f0c37

Please sign in to comment.