mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[ML] Match Data Visualizer/Field stats table content with the popover (#140667)
This commit is contained in:
parent
5b368ca73d
commit
7b36f54808
9 changed files with 204 additions and 97 deletions
|
@ -23,7 +23,7 @@ import { css } from '@emotion/react';
|
|||
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
||||
import { FIELD_STATISTICS_LOADED } from './constants';
|
||||
import type { GetStateReturn } from '../../services/discover_state';
|
||||
import { AvailableFields$, DataRefetch$ } from '../../hooks/use_saved_search';
|
||||
import { AvailableFields$, DataRefetch$, DataTotalHits$ } from '../../hooks/use_saved_search';
|
||||
|
||||
export interface DataVisualizerGridEmbeddableInput extends EmbeddableInput {
|
||||
dataView: DataView;
|
||||
|
@ -38,6 +38,7 @@ export interface DataVisualizerGridEmbeddableInput extends EmbeddableInput {
|
|||
onAddFilter?: (field: DataViewField | string, value: string, type: '+' | '-') => void;
|
||||
sessionId?: string;
|
||||
fieldsToFetch?: string[];
|
||||
totalDocuments?: number;
|
||||
}
|
||||
export interface DataVisualizerGridEmbeddableOutput extends EmbeddableOutput {
|
||||
showDistributions?: boolean;
|
||||
|
@ -89,6 +90,7 @@ export interface FieldStatisticsTableProps {
|
|||
savedSearchRefetch$?: DataRefetch$;
|
||||
availableFields$?: AvailableFields$;
|
||||
searchSessionId?: string;
|
||||
savedSearchDataTotalHits$?: DataTotalHits$;
|
||||
}
|
||||
|
||||
export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => {
|
||||
|
@ -104,6 +106,7 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => {
|
|||
trackUiMetric,
|
||||
savedSearchRefetch$,
|
||||
searchSessionId,
|
||||
savedSearchDataTotalHits$,
|
||||
} = props;
|
||||
const services = useDiscoverServices();
|
||||
const [embeddable, setEmbeddable] = useState<
|
||||
|
@ -157,6 +160,9 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => {
|
|||
onAddFilter,
|
||||
sessionId: searchSessionId,
|
||||
fieldsToFetch: availableFields$?.getValue().fields,
|
||||
totalDocuments: savedSearchDataTotalHits$
|
||||
? savedSearchDataTotalHits$.getValue()?.result
|
||||
: undefined,
|
||||
});
|
||||
embeddable.reload();
|
||||
}
|
||||
|
@ -170,6 +176,7 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => {
|
|||
onAddFilter,
|
||||
searchSessionId,
|
||||
availableFields$,
|
||||
savedSearchDataTotalHits$,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -180,6 +180,7 @@ export const DiscoverMainContent = ({
|
|||
onAddFilter={!isPlainRecord ? onAddFilter : undefined}
|
||||
trackUiMetric={trackUiMetric}
|
||||
savedSearchRefetch$={savedSearchRefetch$}
|
||||
savedSearchDataTotalHits$={savedSearchData$.totalHits$}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -40,6 +40,7 @@ export interface GeoPointExample {
|
|||
}
|
||||
|
||||
export interface FieldVisStats {
|
||||
totalDocuments?: number;
|
||||
error?: Error;
|
||||
cardinality?: number;
|
||||
count?: number;
|
||||
|
|
|
@ -29,16 +29,18 @@ export const IndexBasedDataVisualizerExpandedRow = ({
|
|||
dataView,
|
||||
combinedQuery,
|
||||
onAddFilter,
|
||||
totalDocuments,
|
||||
}: {
|
||||
item: FieldVisConfig;
|
||||
dataView: DataView | undefined;
|
||||
combinedQuery: CombinedQuery;
|
||||
totalDocuments?: number;
|
||||
/**
|
||||
* Callback to add a filter to filter bar
|
||||
*/
|
||||
onAddFilter?: (field: DataViewField | string, value: string, type: '+' | '-') => void;
|
||||
}) => {
|
||||
const config = item;
|
||||
const config = { ...item, stats: { ...item.stats, totalDocuments } };
|
||||
const { loading, type, existsInDocs, fieldName } = config;
|
||||
|
||||
function getCardContent() {
|
||||
|
|
|
@ -10,9 +10,9 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiProgress,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiButtonIcon,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
@ -20,6 +20,9 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import classNames from 'classnames';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { DataViewField } from '@kbn/data-views-plugin/public';
|
||||
import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/data-plugin/common';
|
||||
import { css } from '@emotion/react';
|
||||
import { useDataVisualizerKibana } from '../../../kibana_context';
|
||||
import { roundToDecimalPlace, kibanaFieldFormat } from '../utils';
|
||||
import { ExpandedRowFieldHeader } from '../stats_table/components/expanded_row_field_header';
|
||||
import { FieldVisStats } from '../../../../../common/types';
|
||||
|
@ -43,17 +46,78 @@ function getPercentLabel(docCount: number, topValuesSampleSize: number): string
|
|||
}
|
||||
|
||||
export const TopValues: FC<Props> = ({ stats, fieldFormat, barColor, compressed, onAddFilter }) => {
|
||||
const {
|
||||
services: { data },
|
||||
} = useDataVisualizerKibana();
|
||||
|
||||
const { fieldFormats } = data;
|
||||
|
||||
if (stats === undefined || !stats.topValues) return null;
|
||||
const {
|
||||
topValues,
|
||||
topValuesSampleSize,
|
||||
topValuesSamplerShardSize,
|
||||
count,
|
||||
isTopValuesSampled,
|
||||
fieldName,
|
||||
sampleCount,
|
||||
topValuesSamplerShardSize,
|
||||
} = stats;
|
||||
|
||||
const totalDocuments = stats.totalDocuments;
|
||||
|
||||
const progressBarMax = isTopValuesSampled === true ? topValuesSampleSize : count;
|
||||
|
||||
const topValuesOtherCount =
|
||||
(progressBarMax ?? 0) -
|
||||
(topValues ? topValues.map((value) => value.doc_count).reduce((v, acc) => acc + v) : 0);
|
||||
|
||||
const countsElement =
|
||||
totalDocuments !== undefined ? (
|
||||
<EuiText color="subdued" size="xs">
|
||||
{isTopValuesSampled ? (
|
||||
<FormattedMessage
|
||||
id="xpack.dataVisualizer.dataGrid.field.topValues.calculatedFromSampleRecordsLabel"
|
||||
defaultMessage="Calculated from {sampledDocumentsFormatted} sample {sampledDocuments, plural, one {record} other {records}}."
|
||||
values={{
|
||||
sampledDocuments: sampleCount,
|
||||
sampledDocumentsFormatted: (
|
||||
<strong>
|
||||
{fieldFormats
|
||||
.getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER])
|
||||
.convert(sampleCount)}
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.dataVisualizer.dataGrid.field.topValues.calculatedFromTotalRecordsLabel"
|
||||
defaultMessage="Calculated from {totalDocumentsFormatted} {totalDocuments, plural, one {record} other {records}}."
|
||||
values={{
|
||||
totalDocuments,
|
||||
totalDocumentsFormatted: (
|
||||
<strong>
|
||||
{fieldFormats
|
||||
.getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER])
|
||||
.convert(totalDocuments ?? 0)}
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</EuiText>
|
||||
) : (
|
||||
<EuiText size="xs" textAlign={'center'}>
|
||||
<FormattedMessage
|
||||
id="xpack.dataVisualizer.dataGrid.field.topValues.calculatedFromSampleDescription"
|
||||
defaultMessage="Calculated from sample of {topValuesSamplerShardSize} documents per shard"
|
||||
values={{
|
||||
topValuesSamplerShardSize,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
);
|
||||
|
||||
return (
|
||||
<ExpandedRowPanel
|
||||
dataTestSubj={'dataVisualizerFieldDataTopValues'}
|
||||
|
@ -70,96 +134,125 @@ export const TopValues: FC<Props> = ({ stats, fieldFormat, barColor, compressed,
|
|||
data-test-subj="dataVisualizerFieldDataTopValuesContent"
|
||||
className={classNames('fieldDataTopValuesContainer', 'dvTopValues__wrapper')}
|
||||
>
|
||||
{Array.isArray(topValues) &&
|
||||
topValues.map((value) => (
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="center" key={value.key}>
|
||||
<EuiFlexItem data-test-subj="dataVisualizerFieldDataTopValueBar">
|
||||
<EuiProgress
|
||||
value={value.doc_count}
|
||||
max={progressBarMax}
|
||||
color={barColor}
|
||||
size="xs"
|
||||
label={kibanaFieldFormat(value.key, fieldFormat)}
|
||||
className={classNames('eui-textTruncate', 'topValuesValueLabelContainer')}
|
||||
valueText={`${value.doc_count}${
|
||||
progressBarMax !== undefined
|
||||
? ` (${getPercentLabel(value.doc_count, progressBarMax)})`
|
||||
: ''
|
||||
}`}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{fieldName !== undefined && value.key !== undefined && onAddFilter !== undefined ? (
|
||||
<>
|
||||
<EuiButtonIcon
|
||||
iconSize="s"
|
||||
iconType="plusInCircle"
|
||||
onClick={() =>
|
||||
onAddFilter(
|
||||
fieldName,
|
||||
typeof value.key === 'number' ? value.key.toString() : value.key,
|
||||
'+'
|
||||
)
|
||||
}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.dataVisualizer.dataGrid.field.addFilterAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Filter for {fieldName}: "{value}"',
|
||||
values: { fieldName, value: value.key },
|
||||
}
|
||||
)}
|
||||
data-test-subj={`dvFieldDataTopValuesAddFilterButton-${value.key}-${value.key}`}
|
||||
style={{
|
||||
minHeight: 'auto',
|
||||
minWidth: 'auto',
|
||||
paddingRight: 2,
|
||||
paddingLeft: 2,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
}}
|
||||
{Array.isArray(topValues)
|
||||
? topValues.map((value) => (
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="center" key={value.key}>
|
||||
<EuiFlexItem data-test-subj="dataVisualizerFieldDataTopValueBar">
|
||||
<EuiProgress
|
||||
value={value.doc_count}
|
||||
max={progressBarMax}
|
||||
color={barColor}
|
||||
size="xs"
|
||||
label={kibanaFieldFormat(value.key, fieldFormat)}
|
||||
className={classNames('eui-textTruncate', 'topValuesValueLabelContainer')}
|
||||
valueText={`${value.doc_count}${
|
||||
progressBarMax !== undefined
|
||||
? ` (${getPercentLabel(value.doc_count, progressBarMax)})`
|
||||
: ''
|
||||
}`}
|
||||
/>
|
||||
<EuiButtonIcon
|
||||
iconSize="s"
|
||||
iconType="minusInCircle"
|
||||
onClick={() =>
|
||||
onAddFilter(
|
||||
fieldName,
|
||||
typeof value.key === 'number' ? value.key.toString() : value.key,
|
||||
'-'
|
||||
)
|
||||
}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.dataVisualizer.dataGrid.field.removeFilterAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Filter out {fieldName}: "{value}"',
|
||||
values: { fieldName, value: value.key },
|
||||
</EuiFlexItem>
|
||||
{fieldName !== undefined && value.key !== undefined && onAddFilter !== undefined ? (
|
||||
<div
|
||||
css={css`
|
||||
width: 48px;
|
||||
`}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
iconSize="s"
|
||||
iconType="plusInCircle"
|
||||
onClick={() =>
|
||||
onAddFilter(
|
||||
fieldName,
|
||||
typeof value.key === 'number' ? value.key.toString() : value.key,
|
||||
'+'
|
||||
)
|
||||
}
|
||||
)}
|
||||
data-test-subj={`dvFieldDataTopValuesExcludeFilterButton-${value.key}-${value.key}`}
|
||||
style={{
|
||||
minHeight: 'auto',
|
||||
minWidth: 'auto',
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
paddingRight: 2,
|
||||
paddingLeft: 2,
|
||||
}}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.dataVisualizer.dataGrid.field.addFilterAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Filter for {fieldName}: "{value}"',
|
||||
values: { fieldName, value: value.key },
|
||||
}
|
||||
)}
|
||||
data-test-subj={`dvFieldDataTopValuesAddFilterButton-${value.key}-${value.key}`}
|
||||
style={{
|
||||
minHeight: 'auto',
|
||||
minWidth: 'auto',
|
||||
paddingRight: 2,
|
||||
paddingLeft: 2,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
}}
|
||||
/>
|
||||
<EuiButtonIcon
|
||||
iconSize="s"
|
||||
iconType="minusInCircle"
|
||||
onClick={() =>
|
||||
onAddFilter(
|
||||
fieldName,
|
||||
typeof value.key === 'number' ? value.key.toString() : value.key,
|
||||
'-'
|
||||
)
|
||||
}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.dataVisualizer.dataGrid.field.removeFilterAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Filter out {fieldName}: "{value}"',
|
||||
values: { fieldName, value: value.key },
|
||||
}
|
||||
)}
|
||||
data-test-subj={`dvFieldDataTopValuesExcludeFilterButton-${value.key}-${value.key}`}
|
||||
style={{
|
||||
minHeight: 'auto',
|
||||
minWidth: 'auto',
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
paddingRight: 2,
|
||||
paddingLeft: 2,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
))
|
||||
: null}
|
||||
{topValuesOtherCount > 0 ? (
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="center" key="other">
|
||||
<EuiFlexItem data-test-subj="dataVisualizerFieldDataTopValueBar">
|
||||
<EuiProgress
|
||||
value={topValuesOtherCount}
|
||||
max={progressBarMax}
|
||||
color={barColor}
|
||||
size="xs"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.dataVisualizer.dataGrid.field.topValuesOtherLabel"
|
||||
defaultMessage="Other"
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
))}
|
||||
}
|
||||
className={classNames('eui-textTruncate', 'topValuesValueLabelContainer')}
|
||||
valueText={`${topValuesOtherCount}${
|
||||
progressBarMax !== undefined
|
||||
? ` (${getPercentLabel(topValuesOtherCount, progressBarMax)})`
|
||||
: ''
|
||||
}`}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{onAddFilter ? (
|
||||
<div
|
||||
css={css`
|
||||
width: 48px;
|
||||
`}
|
||||
/>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
) : null}
|
||||
|
||||
{isTopValuesSampled === true && (
|
||||
<Fragment>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiText size="xs" textAlign={'center'}>
|
||||
<FormattedMessage
|
||||
id="xpack.dataVisualizer.dataGrid.field.topValues.calculatedFromSampleDescription"
|
||||
defaultMessage="Calculated from sample of {topValuesSamplerShardSize} documents per shard"
|
||||
values={{
|
||||
topValuesSamplerShardSize,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
{countsElement}
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -419,13 +419,14 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
|||
dataView={currentDataView}
|
||||
combinedQuery={{ searchQueryLanguage, searchString }}
|
||||
onAddFilter={onAddFilter}
|
||||
totalDocuments={overallStats.totalCount}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return m;
|
||||
}, {} as ItemIdToExpandedRowMap);
|
||||
},
|
||||
[currentDataView, searchQueryLanguage, searchString, onAddFilter]
|
||||
[currentDataView, searchQueryLanguage, searchString, onAddFilter, overallStats.totalCount]
|
||||
);
|
||||
|
||||
// Some actions open up fly-out or popup
|
||||
|
|
|
@ -54,6 +54,7 @@ export interface DataVisualizerGridInput {
|
|||
onAddFilter?: (field: DataViewField | string, value: string, type: '+' | '-') => void;
|
||||
sessionId?: string;
|
||||
fieldsToFetch?: string[];
|
||||
totalDocuments?: number;
|
||||
}
|
||||
export type DataVisualizerGridEmbeddableInput = EmbeddableInput & DataVisualizerGridInput;
|
||||
export type DataVisualizerGridEmbeddableOutput = EmbeddableOutput;
|
||||
|
@ -100,6 +101,7 @@ export const EmbeddableWrapper = ({
|
|||
dataView={input.dataView}
|
||||
combinedQuery={{ searchQueryLanguage, searchString }}
|
||||
onAddFilter={input.onAddFilter}
|
||||
totalDocuments={input.totalDocuments}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
existsInDocs: true,
|
||||
aggregatable: true,
|
||||
loading: false,
|
||||
exampleCount: 10,
|
||||
exampleCount: 11,
|
||||
docCountFormatted: '5000 (100%)',
|
||||
viewableInLens: true,
|
||||
hasActionMenu: true,
|
||||
|
@ -94,7 +94,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
loading: false,
|
||||
docCountFormatted: '5000 (100%)',
|
||||
statsMaxDecimalPlaces: 3,
|
||||
topValuesCount: 10,
|
||||
topValuesCount: 11,
|
||||
viewableInLens: true,
|
||||
hasActionMenu: false,
|
||||
},
|
||||
|
|
|
@ -29,7 +29,7 @@ export const farequoteDataViewTestData: TestData = {
|
|||
loading: false,
|
||||
docCountFormatted: '5000 (100%)',
|
||||
statsMaxDecimalPlaces: 3,
|
||||
topValuesCount: 10,
|
||||
topValuesCount: 11,
|
||||
viewableInLens: true,
|
||||
},
|
||||
],
|
||||
|
@ -70,7 +70,7 @@ export const farequoteDataViewTestData: TestData = {
|
|||
existsInDocs: true,
|
||||
aggregatable: true,
|
||||
loading: false,
|
||||
exampleCount: 10,
|
||||
exampleCount: 11,
|
||||
docCountFormatted: '5000 (100%)',
|
||||
viewableInLens: true,
|
||||
},
|
||||
|
@ -126,7 +126,7 @@ export const farequoteKQLSearchTestData: TestData = {
|
|||
loading: false,
|
||||
docCountFormatted: '5000 (100%)',
|
||||
statsMaxDecimalPlaces: 3,
|
||||
topValuesCount: 10,
|
||||
topValuesCount: 11,
|
||||
viewableInLens: true,
|
||||
},
|
||||
],
|
||||
|
@ -224,7 +224,7 @@ export const farequoteKQLFiltersSearchTestData: TestData = {
|
|||
loading: false,
|
||||
docCountFormatted: '5000 (100%)',
|
||||
statsMaxDecimalPlaces: 3,
|
||||
topValuesCount: 10,
|
||||
topValuesCount: 11,
|
||||
viewableInLens: true,
|
||||
},
|
||||
],
|
||||
|
@ -322,7 +322,7 @@ export const farequoteLuceneSearchTestData: TestData = {
|
|||
loading: false,
|
||||
docCountFormatted: '5000 (100%)',
|
||||
statsMaxDecimalPlaces: 3,
|
||||
topValuesCount: 10,
|
||||
topValuesCount: 11,
|
||||
viewableInLens: true,
|
||||
},
|
||||
],
|
||||
|
@ -420,7 +420,7 @@ export const farequoteLuceneFiltersSearchTestData: TestData = {
|
|||
loading: false,
|
||||
docCountFormatted: '5000 (100%)',
|
||||
statsMaxDecimalPlaces: 3,
|
||||
topValuesCount: 10,
|
||||
topValuesCount: 11,
|
||||
viewableInLens: true,
|
||||
},
|
||||
],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue